The pool's central incentive is a k=2 time-weighted bonus meant to "crush late entrants within the observed risk window": a small, long-bearing staker should out-earn a large, last-minute one. docs/DESIGN.md Section 3 relies on this as a security property (deposits during UNDER_ATTACK are "crushed to ~zero by the k=2 weighting"), backed by the test testQuadraticCrushesInsiderLastMinuteStaker.
This protection is bypassed whenever the active-risk window is not promptly observed. riskWindowStart is sealed lazily on the FIRST observation (_observePoolState -> _markRiskWindowStart) and records the OBSERVATION time, not when the registry actually entered active-risk:
If nobody pokes/stakes/withdraws while the registry is in UNDER_ATTACK/PROMOTION_REQUESTED, a late whale whose OWN stake is that first observation seals riskWindowStart at a late instant. At claim time _clampUserSums floors EVERY earlier staker's effective entry to that same late instant, so all deposits share the identical factor (T - riskWindowStart)^2. In share = mulDiv(userScore, snapshotTotalBonus, globalScore) with userScore = sum of amount*(T - entry)^2, that common factor cancels, leaving share proportional to eligibleStake. The k=2 time-weighting collapses to amount-weighting: a whale with 1000x the stake and ~zero real at-risk time takes ~1000x the bonus, draining it from the long-term stakers it was meant to reward.
Section 3 argues the joiner's score (T - entry)^2 ~= 0, but that is the ABSOLUTE score; the payout is a RELATIVE split, and when every score carries the same small (T - riskWindowStart)^2 the whale's share is simply its stake fraction. Section 7 calls pre-seal amount-weighting intentional because "pre-risk calendar time is not at-risk time" - but that premise is FALSE here: the interval collapsed into the cohort is genuine at-risk time (the registry was in UNDER_ATTACK). Every existing test in this area pokes promptly so the seal tracks risk onset; none models an unobserved window followed by a late whale.
Likelihood: Medium
Observation is lazy/permissionless and passive stakers do not proactively poke, so an unobserved active-risk window is the realistic default. An attacker can monitor and SELECT pools already in this state (getAgreementState == UNDER_ATTACK while riskWindowStart == 0) and strike late.
The seal is one-way, so once the whale seals it late the outcome is irreversible.
Impact: Medium
The protocol's core "reward early confidence / crush late entrants" property (Section 3) is defeated; the bonus (the primary staker incentive per the README) is redirected from long-term honest stakers to an opportunistic late whale.
Works on both the EXPIRED auto-resolution path (cheapest - the whale's principal is exposed only minutes before expiry) and the moderator SURVIVED path.
Drop this into test/unit/Poc.t.sol and run forge test --match-path test/unit/Poc.t.sol -vv. Both tests PASS against the UNMODIFIED contracts. alice stakes 100 and bears ~30 days of real at-risk time; bob stakes 100,000 (1000x) during UNDER_ATTACK shortly before resolution, with no prior observation. The control test proves a single prompt pokeRiskWindow() restores k=2 and crushes the whale, isolating delayed observation as the trigger. (The moderator SURVIVED path behaves identically.)
Output (unmodified contracts):
The exploit test is the direct inverse of the team's testQuadraticCrushesInsiderLastMinuteStaker, differing only in whether the risk window is promptly poked.
Root cause: riskWindowStart records OBSERVATION time, not risk-onset time, so honest at-risk time is discarded when observation lags.
Make a deposit that itself first seals the risk window ineligible for bonus (score 0) rather than amount-weighted, preserving the Section 3 guarantee even when observation lagged.
Or gate resolution on a minimum observed risk duration / require the window to be sealed within a bounded delay of the registry transition (incentivized keeper), so riskWindowStart cannot be deferred to collapse the time-weighting.
At minimum, document that stakers MUST promptly pokeRiskWindow() when the registry enters active-risk, or forfeit their time-weighted bonus.
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.