NXL distribution failure during ticket purchase leads to permanent fund lockup and broken lottery rounds
When NXL rewards are exhausted, the product deactivates mid-transaction after payment is collected and tickets are assigned, leaving the round incomplete with funds permanently trapped.
Description
The buyTickets() and buySpecificTickets() functions accept payment and assign tickets before attempting to distribute NXL rewards. When NXL rewards are exhausted, the _distributeNXL() function catches the failure and calls _handleNXLExhaustion(), which immediately deactivates the product. This creates a critical state where:
- User payment has already been collected
- Tickets have already been assigned
- Product becomes deactivated mid-transaction
- Round remains incomplete with insufficient tickets to trigger winner selection
// Current flow in buyTickets()function buyTickets(...) external validProduct(productId) {// Step 1: Product is active, validation passes// Step 2: Payment collectedstablecoin.transferFrom(msg.sender, address(this), totalPrice);// Step 3: Tickets assignedticketOwner[productId][roundId][ticketNumber] = msg.sender;round.ticketsSold++;// Step 4: NXL distribution fails_distributeNXL(msg.sender, nxlAmount, productId);// -> catch block triggers _handleNXLExhaustion(productId)// -> products[productId].active = false// Product now deactivated but transaction continues}
Vulnerable Scenario:
- NXL rewards pool becomes exhausted or nearly exhausted
- First buyer after exhaustion calls
buyTickets()for FLASH product (needs 1000 tickets) - User pays 1 USDT, receives ticket #0
- NXL distribution fails, product deactivated
- Round now shows: 1 ticket sold / 1000 needed
- All subsequent buyers revert due to validProduct modifier:
require(products[productId].active, "Product inactive") - Round can never reach
round.ticketsSold >= product.maxTickets - VRF randomness is never requested
- Winner is never selected
- All funds collected in this round are permanently locked in the contract
Impact
This vulnerability results in permanent fund lockup and complete system failure once NXL rewards are depleted. The contract accepts payment and assigns tickets, then fails during NXL distribution and immediately deactivates the product mid-transaction. The round is left in an irrecoverable state with tickets sold but insufficient capacity to ever trigger winner selection through VRF.
All stablecoin payments collected in the affected round become permanently trapped in the NexumManager contract. The contract provides no refund mechanism for incomplete rounds and no administrative function exists to manually resolve or complete stuck rounds, making fund recovery impossible.
The first victim of NXL exhaustion pays full price for their tickets but receives no NXL rewards, with no clear error message indicating the failure. All subsequent purchase attempts revert immediately at the validProduct modifier, completely blocking further participation in that lottery product.
Recommendation
Replace the current try/catch logic in _distributeNXL() with explicit state validation:
// Fixed implementationfunction _distributeNXL(address recipient, uint256 amount, uint256 productId) private {if (amount == 0) return;// Explicitly check NXL availability before distributionuint256 available = nxlToken.getAvailableRewards();if (available < amount) {// Actually exhausted, handle it appropriately_handleNXLExhaustion(productId);return;}// Safe to distribute, any failure here is a real error that should revertnxlToken.distributeReward(recipient, amount);}
Resolution
Nexalo: Fixed.
Zealynx: Verified. Fixed.

