Raffles may not distribute all funds, resulting in locked stablecoins during distribution
When buyers lack complete referral chains, level-2 and level-3 commission percentages remain stuck in the ReferralNetwork contract with no mechanism to recover or redistribute them.
Description
When a raffle round ends and funds are distributed, the ReferralNetwork::distributeCommissions() function fails to distribute all allocated referral commissions. When a buyer doesn't have all three referral levels populated, the corresponding commission percentages (5%, 3%, or 2%) are not distributed and remain stuck in the ReferralNetwork contract.
Vulnerable Scenario:
The following steps help understand the issue:
- A raffle round completes with 1000 tickets sold at 1 USDT each, collecting 1000 USDT total.
- The
_distributeRound()function allocates 10% (100 USDT) for multilevel referral commissions and transfers it to the ReferralNetwork contract, then callsdistributeCommissions(). - If the winner only has 1 referral level (no level 2 or level 3 referrers):
- Level 1 receives: (100 * 5000) / 10000 = 5 USDT
- Level 2 would receive: (100 * 3000) / 10000 = 3 USDT → Not distributed (no referrer)
- Level 3 would receive: (100 * 2000) / 10000 = 2 USDT → Not distributed (no referrer)
- Result: 5 USDT stuck in ReferralNetwork contract
- Over hundreds of rounds, these undistributed commissions accumulate into significant locked funds that cannot be recovered or redistributed.
Impact
Funds will progressively accumulate in the ReferralNetwork contract with no mechanism to recover or redistribute them. The referral commission system fails to achieve its intended 10% distribution when buyers lack complete referral chains, reducing the effectiveness of the referral incentive program. Over time, the cumulative undistributed commissions can result in substantial locked value.
Recommendation
Modify distributeCommissions() to track distributed amounts and return undistributed funds to the caller (NexumManager):
function distributeCommissions(address buyer, uint256 totalAmount) external onlyManager nonReentrantreturns (uint256 amountUndistributed){require(totalAmount > 0, "Invalid amount");uint256 leftToDistribute = totalAmount;address level1 = referrer[buyer];if (level1 != address(0)) {uint256 amount1 = (totalAmount * LEVEL1_PCT) / 10000;if (amount1 > 0) {require(stablecoin.transfer(level1, amount1), "Transfer failed");totalEarned[level1] += amount1;leftToDistribute -= amount1;emit CommissionPaid(level1, buyer, 1, amount1);}}// ... similar logic for level 2 and 3 ...// Return undistributed funds to NexumManagerif (leftToDistribute > 0) {require(stablecoin.transfer(msg.sender, leftToDistribute), "Return transfer failed");}return leftToDistribute;}
Resolution
Nexalo: Fixed.
Zealynx: Verified. Fixed.

