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

# `corruptedClaimDeadline` uint32 truncation forfeits the attacker bounty near the 2106 ceiling

Author Revealed upon completion

Summary

expiry may be set up to type(uint32).max. The good-faith claim deadline is computed then cast back to uint32; when flagged within 180 days of the ceiling the cast wraps to a past value, so claimAttackerBounty reverts ClaimWindowExpired and the bounty is forfeited.

Description

src/ConfidencePool.sol:364-372:

_firstGoodFaithCorruptedAt = uint32(block.timestamp); // L366
corruptedClaimDeadline = uint32(_firstGoodFaithCorruptedAt + CORRUPTED_CLAIM_WINDOW); // L372, 180 days

The uint256 sum is truncated by the uint32(...) cast. If now + 180 days > type(uint32).max, the stored deadline is a small past timestamp, and the named attacker can never claim.

Risk

Likelihood: Low — requires expiry near type(uint32).max and a good-faith flag within 180 days of it (~2105–2106).

Impact: High (in that regime) — a valid whitehat bounty becomes permanently unclaimable and the pool sweeps to recoveryAddress instead.

Proof of Concept

test/poc/ManualReviewPoC.t.sol::testPoC_deadlineUint32TruncationForfeitsBounty:

function testPoC_deadlineUint32TruncationForfeitsBounty() external {
pool.setExpiry(type(uint32).max);
_stake(alice, 100 * ONE);
_contributeBonus(carol, 50 * ONE);
vm.warp(4_294_000_000); // within 180 days of the uint32 ceiling
attackRegistry.setAgreementState(IAttackRegistry.ContractState.CORRUPTED);
vm.prank(moderator);
pool.flagOutcome(PoolStates.Outcome.CORRUPTED, true, whitehat);
vm.prank(whitehat);
vm.expectRevert(IConfidencePool.ClaimWindowExpired.selector);
pool.claimAttackerBounty();
assertLt(pool.corruptedClaimDeadline(), block.timestamp);
}

Result (-vv):

[PASS] testPoC_deadlineUint32TruncationForfeitsBounty()
now 4294000000
expected deadline (now + 180d) 4309552000
stored (uint32) deadline 14584704 // truncated into the past
whitehat claim reverted: ClaimWindowExpired

4309552000 overflows type(uint32).max (4294967295) and truncates to 14584704.

Recommended Mitigation

Reject the overflowing flag before the cast (the addition promotes to uint256, so the compare is safe), or store the two timestamps as uint256.

if (willBeGoodFaithCorrupted) {
if (_firstGoodFaithCorruptedAt == 0) {
_firstGoodFaithCorruptedAt = uint32(block.timestamp);
}
- corruptedClaimDeadline = uint32(_firstGoodFaithCorruptedAt + CORRUPTED_CLAIM_WINDOW);
+ uint256 deadline = _firstGoodFaithCorruptedAt + CORRUPTED_CLAIM_WINDOW;
+ if (deadline > type(uint32).max) revert ExpiryTooFar();
+ corruptedClaimDeadline = uint32(deadline);
}

Support

FAQs

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

Give us feedback!