F-2025-0006·logic-error

Raffles may not distribute all funds, resulting in locked stablecoins during distribution

Fixedrafflelotteryvrf
TL;DR

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.

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

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:

  1. A raffle round completes with 1000 tickets sold at 1 USDT each, collecting 1000 USDT total.
  2. The _distributeRound() function allocates 10% (100 USDT) for multilevel referral commissions and transfers it to the ReferralNetwork contract, then calls distributeCommissions().
  3. 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
  4. Over hundreds of rounds, these undistributed commissions accumulate into significant locked funds that cannot be recovered or redistributed.
03Section · Impact

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.

04Section · Recommendation

Recommendation

Modify distributeCommissions() to track distributed amounts and return undistributed funds to the caller (NexumManager):

solidity
function distributeCommissions(address buyer, uint256 totalAmount) external onlyManager nonReentrant
returns (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 NexumManager
if (leftToDistribute > 0) {
require(stablecoin.transfer(msg.sender, leftToDistribute), "Return transfer failed");
}
return leftToDistribute;
}
05Section · Resolution

Resolution

Nexalo: Fixed.

Zealynx: Verified. Fixed.

Status
Fixed
F-2025-0006

oog
zealynx

Smart Contract Security Digest

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

© 2026 Zealynx