F-2025-0012·all-or-nothing-distribution

Funds available for distribution are burned if a user paid for more rewards, resulting in a loss of rewards

Fixedrafflelotteryvrf
TL;DR

When NXL rewards are insufficient for the requested amount, the protocol reverts and burns remaining tokens instead of distributing the partial amount available.

Severity
MEDIUM
Impact
MEDIUM
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
MEDIUM
02Section · Description

Description

When users purchase raffle tickets in NexumManager, they are entitled to receive NXL token rewards proportional to the number of tickets purchased. However, the current implementation follows an "all-or-nothing" approach: if the NXL contract doesn't have enough tokens to fulfill the entire reward amount, the user receives nothing at all, despite having paid for their tickets.

The issue occurs in the _distributeNXL() function which uses a try-catch block. When nxlToken.distributeReward() reverts due to insufficient rewards, the catch block calls _handleNXLExhaustion() which deactivates the product and attempts to burn remaining tokens, but the user who just purchased tickets receives zero NXL tokens.

Vulnerable Scenario:

  1. The NXL token contract has 5 NXL tokens remaining in its rewards pool (after accounting for vesting reserves).

  2. Alice purchases 10 tickets for the BLACKBLOK product, which should reward her with 10 tickets * 1 NXL per ticket = 10 NXL total.

  3. The _distributeNXL() function is called with amount = 10 NXL.

  4. The distributeReward() function in NXLToken checks available rewards and finds only 5 NXL available.

  5. The function reverts with "Insufficient rewards available" because it requires exactly 10 NXL but only 5 are available.

  6. The catch block in _distributeNXL() triggers _handleNXLExhaustion(), which:

    • Deactivates the BLACKBLOK product
    • Attempts to burn remaining tokens
    • Alice receives 0 NXL despite paying full price for 10 tickets
    • If she had paid the price for 5 tickets, she would have gotten 5 NXL
  7. Alice has paid for 10 tickets but received no NXL rewards, while the contract still holds 5 NXL that could have been partially distributed to her.

03Section · Impact

Impact

Users who purchase tickets when the NXL rewards pool is running low receive zero NXL tokens despite paying full price. This creates severe unfairness as users pay full stablecoin price but receive 0% of expected NXL rewards when partial rewards are available.

For example, with 5 NXL remaining, a user expecting 10 NXL receives nothing instead of the available 5 NXL (100% loss vs 50% distribution).

04Section · Recommendation

Recommendation

Modify the reward distribution logic to support partial distributions when full rewards are unavailable:

Update NXLToken::distributeReward() to accept partial distributions:

solidity
function distributeReward(address recipient, uint256 amount) external {
require(msg.sender == nexumManager, "Only NexumManager");
require(recipient != address(0), "Invalid recipient");
require(amount > 0, "Amount must be > 0");
uint256 availableRewards = getAvailableRewards();
// Distribute available amount (full or partial)
uint256 amountToDistribute = availableRewards >= amount ? amount : availableRewards;
require(amountToDistribute > 0, "No rewards available");
rewardsDistributed += amountToDistribute;
_transfer(address(this), recipient, amountToDistribute);
emit RewardDistributed(recipient, amountToDistribute);
}
05Section · Resolution

Resolution

Nexalo: Fixed.

Zealynx: Verified. Fixed.

Status
Fixed
F-2025-0012

oog
zealynx

Smart Contract Security Digest

Monthly exploit breakdowns, audit checklists, and DeFi security research — straight to your inbox

© 2026 Zealynx