Normally, once the pool observes a terminal registry state it freezes the bonus timestamp T in the one-way riskWindowEnd latch, and the k=2 time-weighted survivor bonus (amount × (T − entryTime)²) is meant to reward only the stakers who bore risk during the measured window. withdraw() is correctly protected against registry rewinds by the one-way riskWindowStart != 0 latch.
stake() does not consult the riskWindowEnd latch. It gates new deposits only on the registry's current state via _assertDepositsAllowed(_observePoolState()), which permits a rewound NEW_DEPLOYMENT state even though the pool already recorded a terminal observation. Because the bonus timestamp T stays frozen while a fresh deposit is accepted with entryTime > T, and the score squares (T − entryTime), staking after T yields a positive and growing score. A one-token stake entered 29 days after T scores 1 × 29² = 841, dwarfing two honest 100-token/one-day stakes (200 × 1²) and capturing over 80% of the bonus.
Likelihood:
A legitimate registry migration or other upstream operation transiently reports a pre-terminal state (NEW_DEPLOYMENT) after the pool has already observed a terminal state and frozen riskWindowEnd. No malicious or compromised registry is required — the vulnerability is the pool's own unsafe behavior during an otherwise legitimate rewind.
An attacker monitors registry state and executes the minimum-stake deposit permissionlessly during the rewind window, before the moderator finalizes the outcome as SURVIVED.
Impact:
A late minimum staker captures most of the SURVIVED bonus, redirecting bonus funds to a participant who bore no risk during the measured window.
The stakers who actually held risk during the window are diluted and receive a fraction of the bonus they earned. Principal remains solvent; the loss is confined to bonus distribution.
Add the poc in test/unit/ConfidencePool.poc.t.sol stakes 200 tokens before risk, closes the window after one day, rewinds the registry, and deposits the one-token minimum 29 days later, proving the late staker receives more than 80 of the 100 bonus tokens.
Run with:
Make terminal deposit closure one-way at the pool level: once riskWindowEnd is latched, reject deposits regardless of the current (rewindable) registry value, and apply the same guard consistently to stake() and contributeBonus().
Impact - Medium Bonus pool only, principal always returns under SURVIVED/EXPIRED, but the quadratic advantage is unbounded and a tiny late stake can take nearly all of it. Likelihood - Low It needs a DAO registry migration to land inside the window where a pool is unresolved with riskWindowEnd already sealed, and the attacker still has to be watching. No attacker action can create the precondition.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.