FoundrySolidityLayer 2
7.25 ETH
Submission Details
Impact: medium
Likelihood: medium

Bonus pool Denial of Service (DoS) via massive contribution during `UNDER_ATTACK`

Author Revealed upon completion

Root + Impact

An attacker can contribute an extremely large amount of bonus tokens during the UNDER_ATTACK state to cause an arithmetic overflow in the _bonusShare calculation. This prevents all stakers from successfully calling claimSurvived or claimExpired, effectively locking their principal and rewards in the contract.

Description

The _bonusShare function calculates a user's reward using the formula: userScore * snapshotTotalBonus / globalScore.

While the Math.mulDiv library is used to handle 512-bit intermediate multiplication, it cannot prevent an overflow if the product userScore * snapshotTotalBonus is calculated or passed in a way that exceeds uint256 before the division is applied, or if the intermediate values exceed the library's capacity in extreme edge cases. Specifically, an attacker can call contributeBonus with a massive value while the registry is UNDER_ATTACK. Since the flagOutcome snapshots the totalBonus into snapshotTotalBonus, all subsequent claim attempts will revert due to overflow, resulting in a permanent DoS of the withdrawal/claim path for stakers.

function _bonusShare(address u, uint256 userEligible) internal view returns (uint256) {
// ...
@> return Math.mulDiv(userScore, snapshotTotalBonus, globalScore);
}

Risk

Likelihood:

  • Occurs when the registry is in the UNDER_ATTACK state, which is a required phase for most pools.

  • Requires the attacker to have sufficient capital, although with high-precision tokens or low-value tokens, the required "massive" amount is easily reachable.

Impact:

  • Permanent freezing of all staker funds (Principal + Bonus).

  • Complete failure of the pool's settlement mechanism.

Proof of Concept

function test_PoC_BonusInjectionDoS() public {
_stake(alice, ONE);
attackRegistry.setAgreementState(IAttackRegistry.ContractState.UNDER_ATTACK);
pool.pokeRiskWindow();
_contributeBonus(bob, 1e35); // Inject massive bonus to break math
attackRegistry.setAgreementState(IAttackRegistry.ContractState.PRODUCTION);
pool.pokeRiskWindow();
vm.prank(moderator);
pool.flagOutcome(PoolStates.Outcome.SURVIVED, false, address(0));
vm.prank(alice);
try pool.claimSurvived() {} catch { console.log("DoS Triggered"); }
}

Recommended Mitigation

Implement a cap on totalBonus relative to totalEligibleStake or use a more robust scaling mechanism to ensure that the time-weighted products never exceed arithmetic limits.

Support

FAQs

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

Give us feedback!