Broken ticket index continuity leads to invalid winner selection and product.maxTickets invariant break
buySpecificTickets allows non-consecutive ticket indices but only increments ticketsSold by count, creating gaps that break maxTickets enforcement and enable winner selection on unowned indices.
Description
The contract assumes that ticket indices from 0 to round.ticketsSold - 1 are continuously owned. However, NexumManager::buySpecificTickets allows users to purchase any ticket numbers while only increasing round.ticketsSold by the number of tickets bought, not by the highest index used. This creates gaps in ticket ownership that violate the round internal accounting assumptions and also breaks the maxTickets check.
Vulnerable Scenario:
- A round starts with
round.ticketsSold = 0andproduct.maxTickets = 1000. - A user buys tickets through
buySpecificTickets, choosing not consecutive ticket numbers such as {10, 11, 12, 13, 14}. - At this point,
round.ticketsSold == 5, but ticket indices like {0, 1, 2, 3, 4} have no owner. - Other users continue buying tickets through
buyTickets, which increasesround.ticketsSoldsequentially, untilround.ticketsSoldreaches 1000. - When the protocol selects winners using randomness, it may select an index that has no owner.
- If that index is chosen, the reward is sent to
address(0)causing loss of funds.
Impact
- Loss of rewards if a ticket with no owner becomes the winner.
- Higher odds to win since the maxTickets check can be bypassed.
- Loss of trust.
Recommendation
- Apply the maxTickets checks present in
buyTicketsintobuySpecificTickets. - Maintain a counter of valid ticket indices and use that structure for winner selection instead of assuming index continuity.
Resolution
Nexalo: Fixed. Applied the maxTickets checks present in buyTickets into buySpecificTickets.
Zealynx: Partially fixed. We recommend to also maintain a counter of valid ticket indices and use that structure for winner selection instead of assuming index continuity.

