Insufficient available rewards will result in locked staked funds for users
The receiveFunds() accounting fails to track monthly reward deposits and reward claims, causing arithmetic underflow that locks staked funds for users when WBTC reserves are insufficient.
Description
The receiveFunds() function calculates new deposits by comparing the current stablecoin balance against expected balance based on totalDeposited and totalWithdrawnForStaking. However, this accounting formula fails to track two critical balance changes: monthly reward deposits and reward claims, causing arithmetic underflow and permanent function failure.
function receiveFunds() external {uint256 balance = stablecoin.balanceOf(address(this));uint256 newDeposit = balance - (totalDeposited - totalWithdrawnForStaking);if (newDeposit > 0) {totalDeposited += newDeposit;emit FundsReceived(msg.sender, newDeposit);}}
The formula assumes: expectedBalance = totalDeposited - totalWithdrawnForStaking
However, the actual balance is affected by:
- Monthly reward deposits (line 119-138 in
depositMonthlyRewards) which increase balance without updatingtotalDeposited - Reward claims (lines 158, 191 in
claimRewardsandclaimMultipleRewards) which decrease balance and updatetotalRewardsDistributed, but this variable is never used in thereceiveFunds()calculation
Vulnerable Scenario:
The following steps demonstrate the issue:
- NexumManager deposits 10,000 USDC via
depositFunds():- balance = 10,000
- totalDeposited = 10,000
- totalWithdrawnForStaking = 0
- Owner deposits 5,000 USDC as monthly BTC staking rewards via
depositMonthlyRewards(5000e6):- balance = 15,000
- totalDeposited = 10,000 (unchanged - this is the problem)
- Monthly rewards are NOT tracked in totalDeposited
- Users claim 2,000 USDC in rewards:
- balance = 13,000
- totalDeposited = 10,000 (unchanged)
- totalRewardsDistributed = 2,000 (tracked but not used in receiveFunds())
- Someone calls receiveFunds():
- expectedBalance = 10,000 - 0 = 10,000
- actualBalance = 13,000
- newDeposit = 13,000 - 10,000 = 3,000
- Function succeeds but incorrectly attributes 3,000 as new deposit
- totalDeposited = 13,000 (now includes some of the reward pool)
- Users claim another 4,000 USDC in rewards:
- balance = 9,000
- totalDeposited = 13,000
- totalWithdrawnForStaking = 0
- Anyone calls receiveFunds():
- expectedBalance = 13,000 - 0 = 13,000
- actualBalance = 9,000
- newDeposit = 9,000 - 13,000 = -4,000
- Arithmetic underflow in Solidity 0.8+ → Transaction reverts
- All subsequent calls to receiveFunds() permanently revert because the actual balance is now below the expected balance.
The root cause: Monthly reward deposits temporarily inflate the balance above expected levels, masking the accounting issue. Once total reward claims exceed the untracked monthly reward deposits, the actual balance falls below the calculated expected balance, causing permanent underflow.
Impact
The receiveFunds() function becomes permanently unusable after normal protocol operations involving monthly reward deposits and user claims. This creates a denial of service for the permissionless accounting mechanism that NexumManager relies on to deposit treasury funds.
While depositFunds() remains functional as an alternative, the broken receiveFunds() function prevents automatic balance reconciliation and causes integration issues between NexumManager and TreasuryBTC. The function failure also prevents proper tracking of all funds entering the contract.
Recommendation
Implement a pull base system allowing users to pull rewards themselves.
// Add state variableuint256 public totalRewardDeposits;function depositMonthlyRewards(uint256 rewardAmount) external onlyOwner {require(rewardAmount > 0, "Amount must be > 0");require(stablecoin.transferFrom(msg.sender, address(this), rewardAmount), "Transfer failed");totalRewardDeposits += rewardAmount;// ...}function receiveFunds() external {uint256 balance = stablecoin.balanceOf(address(this));uint256 expectedBalance = totalDeposited+ totalRewardDeposits- totalWithdrawnForStaking- totalRewardsDistributed;if (balance > expectedBalance) {uint256 newDeposit = balance - expectedBalance;totalDeposited += newDeposit;emit FundsReceived(msg.sender, newDeposit);}}
This ensures the expected balance calculation accounts for all funds entering and leaving the contract, preventing underflow and maintaining accurate accounting.
Resolution
Nexalo: Fixed.
Zealynx: The DOS vulnerability remains. Users can still be locked out of staking/unstaking when WBTC reserves are insufficient to pay their pending rewards.

