FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: low
Likelihood: high

totalBonus remains stale after all bonus has been paid through claims

Author Revealed upon completion

Root + Impact

Description

  • Normally, totalBonus appears to represent the bonus pool tracked by the contract, while claimedBonus tracks the amount already paid to stakers. After all bonus has been paid and the pool token balance is zero, external observers would expect the live remaining bonus to be zero as well.

    The issue is that claimSurvived and claimExpired increment claimedBonus but never reduce totalBonus. Consequently, totalBonus can keep reporting the originally contributed bonus after the entire bonus has already been transferred out.

```solidity
uint256 bonusShare = _bonusShare(msg.sender, userEligible);
uint256 payout = userEligible + bonusShare;
totalEligibleStake -= userEligible;
@> claimedBonus += bonusShare;
@> // totalBonus is not reduced here
delete eligibleStake[msg.sender];
delete userSumStakeTime[msg.sender];
delete userSumStakeTimeSq[msg.sender];

Risk

Likelihood:

  • This occurs after any SURVIVED or EXPIRED claim pays a nonzero bonus share.

  • The stale value becomes most visible after all stakers have claimed and the pool's token balance is zero.

Impact:

  • Dashboards, monitors, and integrators can misread totalBonus as live remaining bonus.

  • Accounting fields become internally inconsistent: claimedBonus can equal the full bonus while totalBonus still shows the original contributed amount.

Proof of Concept

function testAuditTotalBonusRemainsStaleAfterAllBonusPaid() external {
_stake(alice, 100 * ONE);
_contributeBonus(bob, 50 * ONE);
_passThroughUnderAttack();
attackRegistry.setAgreementState(IAttackRegistry.ContractState.PRODUCTION);
vm.prank(moderator);
pool.flagOutcome(PoolStates.Outcome.SURVIVED, false, address(0));
vm.prank(alice);
pool.claimSurvived();
assertEq(token.balanceOf(address(pool)), 0);
assertEq(pool.claimedBonus(), 50 * ONE);
assertEq(pool.totalBonus(), 50 * ONE);
}

Recommended Mitigation

```diff
uint256 bonusShare = _bonusShare(msg.sender, userEligible);
uint256 payout = userEligible + bonusShare;
totalEligibleStake -= userEligible;
claimedBonus += bonusShare;
+ totalBonus -= bonusShare;
```
Alternatively, rename or document `totalBonus` as cumulative contributed bonus rather than live remaining bonus.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!