F-2025-0013·accounting-error

Incomplete accounting in receiveFunds() leads to permanent denial of service after reward claims

Fixedrafflelotteryvrf
TL;DR

receiveFunds() compares balance against totalDeposited - totalWithdrawnForStaking but ignores monthly reward deposits and reward claims, causing arithmetic underflow and permanent function failure.

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

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.

solidity
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 updating totalDeposited
  • Reward claims (lines 158, 191 in claimRewards and claimMultipleRewards) which decrease balance and update totalRewardsDistributed, but this variable is never used in the receiveFunds() calculation

Vulnerable Scenario:

  1. NexumManager deposits 10,000 USDC via depositFunds(): balance = 10,000, totalDeposited = 10,000

  2. Owner deposits 5,000 USDC as monthly BTC staking rewards via depositMonthlyRewards(5000e6): balance = 15,000, totalDeposited unchanged

  3. Users claim 2,000 USDC in rewards: balance = 13,000, totalDeposited unchanged

  4. Someone calls receiveFunds(): expectedBalance = 10,000, actualBalance = 13,000, newDeposit = 3,000 (incorrectly attributed)

  5. Users claim another 4,000 USDC: balance = 9,000, totalDeposited = 13,000

  6. Anyone calls receiveFunds(): expectedBalance = 13,000, actualBalance = 9,000, newDeposit = 9,000 - 13,000 = -4,000 → underflow → revert

  7. All subsequent calls to receiveFunds() permanently revert.

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.

03Section · Impact

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.

04Section · Recommendation

Recommendation

Redesign the accounting system to track all balance-changing operations:

solidity
// Add state variable
uint256 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;
// ... rest of function
}
function receiveFunds() external {
uint256 balance = stablecoin.balanceOf(address(this));
// Calculate expected balance including all tracked operations
uint256 expectedBalance = totalDeposited
+ totalRewardDeposits
- totalWithdrawnForStaking
- totalRewardsDistributed;
if (balance > expectedBalance) {
uint256 newDeposit = balance - expectedBalance;
totalDeposited += newDeposit;
emit FundsReceived(msg.sender, newDeposit);
}
}
05Section · Resolution

Resolution

Nexalo: Fixed.

Zealynx: Verified. Fixed.

Status
Fixed
F-2025-0013

oog
zealynx

Smart Contract Security Digest

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

© 2026 Zealynx