The ConfidencePool uses a k=2 time-weighted formula score = stake × (T − entry)² to distribute the bonus pool, where T = riskWindowEnd. The quadratic weighting is designed to crush late entrants: the closer a staker enters to T, the smaller their score. The withdraw() function is hardened against upstream registry rewinds using the pool's own one-way riskWindowStart != 0 latch, with an explicit code comment: "gate on it so an upstream registry rewind cannot re-open withdrawals."
The symmetric riskWindowEnd != 0 latch is not applied to stake(). The _assertDepositsAllowed function is pure and gates exclusively on the live upstream registry state. When a benign registry rewind (e.g., DAO migration, registry repointing) reverts the live state from PRODUCTION back to UNDER_ATTACK or NEW_DEPLOYMENT, staking is silently re-enabled despite riskWindowEnd already being sealed. A late staker enters with entry > T, and because the O(1) algebraic expansion T²·a − 2T·(a·t) + (a·t²) computes a perfect square that is always non-negative, the "negative" distance past T produces a positive and growing score — inverting the penalty into a reward that allows the late staker to steal the majority of the bonus pool.
Likelihood:
The protocol already explicitly defends withdraw() against registry rewinds using the one-way riskWindowStart latch, establishing that rewinds are a considered scenario — not a purely theoretical concern. The asymmetric absence of the equivalent riskWindowEnd guard on stake() is an oversight in an otherwise consistent defense pattern.
Registry rewinds occur as routine DAO administrative actions: registry migration, bug-fix repointing, or temporary state corrections. These are benign governance operations that produce the exact state sequence the exploit requires. The attacker is a third-party opportunist monitoring for such events, not the DAO itself.
Impact:
The late staker captures the majority of the bonus pool from honest at-risk stakers while bearing zero risk. With entry = T + 10 days and an honest staker at entry = T − 5 days, the attacker's score is (10d)² = 100d² vs (5d)² = 25d² — 4× the honest staker's share. The further past T the attacker enters, the larger their share grows (quadratic scaling).
Once entry > 2T − riskWindowStart, the attacker's score strictly dominates even the earliest possible staker (one who entered at riskWindowStart), enabling near-total drainage of the bonus pool. The attacker can stake and resolve in the same block after expiry, making the attack risk-free.
Mirror the riskWindowStart latch on withdraw() by adding a riskWindowEnd check to _assertDepositsAllowed, permanently closing deposits once a terminal state has been observed:
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.
View preliminary resultsAppeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.