The survivor bonus is split by a k=2 time-weighted score: each deposit contributes
amount × (T − entry)², with entry floored at riskWindowStart and T = riskWindowEnd
(_bonusShare). A staker's payout is userScore / globalScore × snapshotTotalBonus, so any score
added near resolution both pays the late depositor and dilutes every honest staker's share (their
userScore is unchanged but globalScore grows).
Deposits are still allowed while the registry is UNDER_ATTACK (_assertDepositsAllowed blocks only
PROMOTION_REQUESTED / PRODUCTION / CORRUPTED), riskWindowEnd is sealed lazily on the first
pool call observing a terminal state, and a deposit's entry is block.timestamp. So any deposit
landing before riskWindowEnd is sealed still scores amount × (T − entry)² with a positive
(T − entry) that grows the earlier the deposit lands relative to the seal.
Attack. The registry state is public, and the exploit plays out across several transactions on
two contracts - the pool reads the registry lazily, so the promotion and the pool's reaction are not
one atomic step:
[registry tx] the DAO calls instantPromote on the AttackRegistry → the agreement becomes
PRODUCTION. The pool does not observe this yet.
[pool tx] the attacker front-runs, calling stake(hugeAmount) while the registry is still
UNDER_ATTACK (deposits require a non-terminal state); entry is block.timestamp.
[pool tx] later, some call (flagOutcome / claimExpired / pokeRiskWindow) first observes
the terminal state and seals riskWindowEnd = T.
[pool tx] the attacker claims — full principal plus a bonus slice
hugeAmount × δ² / globalScore × snapshotTotalBonus.
PRODUCTION is terminal and irreversible (the registry's promoted flag is one-way and checked
first in state derivation, so PRODUCTION can never become CORRUPTED), so once step 1 lands the
pool can only resolve SURVIVED and the attacker's principal is safe. Crucially, T is sealed
lazily in step 3, not by the registry promotion in step 1, and the entire span between
PRODUCTION and that sealing is CORRUPTED-safe - so the longer step 3 is delayed after
PRODUCTION, the larger the attacker's risk-free δ (= T − entry) and the larger his slice, up
to T = expiry if resolution drifts to claimExpired. PROMOTION_REQUESTED deposits are already
blocked on purpose, so this residual only survives on the path that skips it - the single-hop
instantPromote.
Remove the k=2 time-weight decay entirely and simply forbid staking once the risk window opens: block
stake (and contributeBonus) the moment the registry is observed in UNDER_ATTACK. With no
deposits accepted after the window opens, every eligible staker entered pre-risk and is floored to
riskWindowStart, so there is no time spread left to weight — an amount-weighted split suffices and
the δ the grab depends on disappears entirely.
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.