Broken token burn logic prevents undistributed reward cleanup
_handleNXLExhaustion passes the entire NXLToken balance to burnUndistributed, which always reverts because the burn check is against availableRewards (excluding vesting), so cleanup never runs.
Description
The _handleNXLExhaustion() function attempts to burn undistributed NXL rewards when a product's reward pool is exhausted. However, it incorrectly passes the entire NXL token contract balance to the burnUndistributed() function instead of only the available rewards amount.
// NexumManager.sol:L303try nxlToken.burnUndistributed(nxlToken.balanceOf(address(nxlToken))) {emit NXLRewardsExhausted(productId);} catch {}
The burnUndistributed() function in NXLToken.sol protects vested tokens by checking against getAvailableRewards():
// NXLToken.sol:L231-241function burnUndistributed(uint256 amount) external {require(msg.sender == nexumManager, "Only NexumManager");require(amount > 0, "Amount must be > 0");uint256 availableRewards = getAvailableRewards();require(availableRewards >= amount, "Insufficient balance");_burn(address(this), amount);emit TokensBurned(amount);}
Vulnerable Scenario:
- NXL reward pool becomes exhausted during a ticket purchase
_handleNXLExhaustion()is called and attempts to burn tokens- Contract passes
balanceOf(address(nxlToken))which includes both available rewards and reserved vesting tokens (e.g., 50M available + 4M vesting = 54M total). burnUndistributed()receives 54M butgetAvailableRewards()returns only 50M- The
require(availableRewards >= amount)check fails: 50M < 54M - Transaction reverts but the empty catch {} silently swallows the error
- No tokens are actually burned
- The NXLRewardsExhausted event is never emitted
- System believes cleanup occurred but nothing happened
Impact
The token cleanup mechanism fails silently every time it's triggered. When NXL rewards are exhausted, any remaining undistributed reward tokens (dust amounts or leftovers) remain in the contract instead of being burned as intended. The NXLRewardsExhausted event is never emitted, making it impossible to track when products have actually exhausted their reward pools through event monitoring. While vested tokens are protected from destruction by the getAvailableRewards() check, the intended cleanup behavior never executes.
Recommendation
Check available rewards explicitly before burning and always emit the exhaustion event. Remove try/catch which masks real errors:
function _handleNXLExhaustion(uint256 productId) private {products[productId].active = false;// Query available rewards firstuint256 availableRewards = nxlToken.getAvailableRewards();// Burn only if there are tokens to burnif (availableRewards > 0) {nxlToken.burnUndistributed(availableRewards);}// Always emit the exhaustion eventemit NXLRewardsExhausted(productId);// ... rest of function}
This approach:
- Explicitly checks state before taking action
- Only calls burn when there are actually tokens to burn
- Doesn't hide unexpected errors (if burn fails, the transaction reverts as it should)
- Always emits the exhaustion event for proper monitoring
- Makes the code logic clear and predictable
Resolution
Nexalo: Token Burn Mechanism removed.
Zealynx: Bug avoided by removing the feature. This is a significant protocol change, not a fix.

