Root cause: riskWindowStart seals on the first on-chain observation of an active-risk registry state (_markRiskWindowStart, ConfidencePool.sol#L801), not on the true attack-start block. _clampUserSums then floors every earlier staker's effective entry up to that seal (#L677), and _bonusShare weights each deposit by (T − effectiveEntry)² (#L696).
Impact: A staker who deposits late during an UNDER_ATTACK phase nobody has observed yet makes their own deposit the first observation, sealing riskWindowStart late. Every earlier staker's entry floors up to it, erasing the at-risk time they actually bore. The k=2 score spread collapses toward an amount-weighted split, transferring the early honest stakers' earned bonus premium to the late (larger) staker — a ~88% loss of the early staker's fair bonus in the PoC.
The bonus rewards time at risk: each deposit scores amount × (T − entryTime)², so a longer-underwriting staker earns more. But riskWindowStart — which all pre-seal entries floor to — is set by the first party to touch the pool during an active-risk state, not by the true transition. A passive early staker doesn't interact during an unobserved attack, so the window doesn't seal on its own; a late staker makes their own deposit the first observation, seals riskWindowStart late, and wipes out earlier stakers' real at-risk time.
This is not the timing residual the docs accept: that residual is for the end anchor T (riskWindowEnd), framed as a symmetric public race. A4 manipulates the start anchor and is not symmetric — the loss falls entirely on a passive early staker. The time it erases is genuinely at-risk time (true UNDER_ATTACK → late first-observation), which the docs never address.
Likelihood: Medium — occurs whenever an active-risk interval passes with no pool interaction to seal the window (normal for passive stakers); the late large staker is economically incentivised to withhold the poke and stake late.
Impact: Medium — a specific honest staker loses the majority of their fair bonus to the attacker; redistribution among stakers, no principal loss.
test/audit/AdversarialA4.t.sol (both tests pass). Alice stakes 10 early, bears ~21 days at risk; a whale stakes 90 one day before resolution, its late stake sealing riskWindowStart. Baseline is identical but seals at the true start via one pokeRiskWindow().
| Scenario | Alice (early, ~21d) | Whale (late, ~1d) |
|---|---|---|
| Attack (late seal) | 10 | 90 |
| Honest baseline (early poke) | 98 | 2 |
Seal riskWindowStart from the registry's canonical active-risk transition timestamp if available, so a late observer cannot move it. If no on-chain transition timestamp exists, credit at-risk time from min(depositTime, earliest-known-active-risk) rather than flooring up to a possibly-late riskWindowStart, or auto-seal the window on the registry transition instead of lazily on first interaction.
For example, replace the lazy first-observation seal with a value derived from the transition itself:
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.