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

Large accepted stake overflows k=2 bonus accounting and locks claims

Author Revealed upon completion

Description

ConfidencePool accepts extremely large standard ERC20 stakes without checking whether the accepted amount can later be processed by the k=2 bonus accounting formula.

The issue is in src/ConfidencePool.sol inside _bonusShare():

uint256 userPlus = T * T * userEligible + userSumStakeTimeSq[u];
uint256 plus = T * T * snapshotTotalStaked + snapshotSumStakeTimeSq;

Because T is an absolute Unix timestamp, T * T is already a very large value. When it is multiplied by an extremely large accepted stake amount, the calculation can overflow during claimSurvived() or claimExpired().

The stake itself is accepted successfully by stake(), but later claims revert when _bonusShare() is evaluated.

This does not rely on a malicious token. The PoC uses standard ERC20 behavior only:

  • no fee-on-transfer

  • no rebasing

  • no hooks

  • no callback

  • no malformed return value

The attacker only needs to be a normal staker after the stake token has been allowlisted.

Risk

A normal staker can make both normal payout paths unusable:

  • claimSurvived()

  • claimExpired()

Once the overflow-triggering stake is accepted, honest stakers cannot claim their principal or bonus because the claim path reverts in _bonusShare().

This is submitted as Low severity because the required stake amount is astronomically large and the attacker also locks their own stake. However, the root cause is still valid: the contract accepts stake amounts that cannot safely be processed by its own later accounting logic.

Proof of Concept

PoC file:

test/poc/K2BonusOverflowLocksClaims.t.sol

Run:

forge test --match-path test/poc/K2BonusOverflowLocksClaims.t.sol -vvv

Result:

4 tests passed

The PoC proves:

  1. A normal SURVIVED claim works with ordinary stake amounts.

  2. An extremely large standard ERC20 stake is accepted by stake().

  3. After that stake is accepted, claimSurvived() reverts due to _bonusShare() overflow.

  4. The same accepted stake also makes claimExpired() revert.

  5. Pool funds remain stuck after the failed claims.

The overflow threshold used in the PoC is calculated as:

minimum total stake = floor(type(uint256).max / (T^2 + riskWindowStart^2)) + 1

For the PoC values:

riskWindowStart = 1750000000
T = 1750864000

the minimum total stake required to trigger the overflow is:

18895499614867910916004075352072654061479200862735367356747 raw units

With a victim staking 1e18, the attacker stake is:

18895499614867910916004075352072654061478200862735367356747 raw units

This is intentionally reported as Low because the amount is unrealistic for ordinary capped ERC20 assets.

Impact

The pool can enter a state where honest stakers cannot claim through either normal resolution path.

Locked funds include:

  • honest staker principal

  • honest staker bonus share

  • attacker stake

  • pool bonus funds

The attack is self-sacrificing and requires an extremely large token balance, so the practical likelihood is Low.

Tools Used

Manual review, Foundry, Slither.

Recommended Mitigation

Do not use absolute Unix timestamp squared values in the k=2 bonus formula.

Use bounded relative time values instead. For example, use elapsed time from riskWindowStart rather than the absolute timestamp:

uint256 elapsed = T - riskWindowStart;

Then calculate bonus weights from bounded elapsed values.

Also add a conservative total stake cap so every accepted stake is guaranteed to be claimable later:

uint256 public constant MAX_TOTAL_STAKE = type(uint128).max;
require(totalStaked + amount <= MAX_TOTAL_STAKE, "stake cap exceeded");

Recommended changes:

  1. Replace absolute timestamp squared accounting with bounded relative-time accounting.

  2. Add a maximum total stake cap.

  3. Add invariant tests proving that any accepted stake cannot later make claimSurvived() or claimExpired() revert.

Support

FAQs

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

Give us feedback!