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().
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.
The contest is complete and the rewards are being distributed.