The pool splits its bonus with a k=2, time-weighted formula whose purpose is to reward stakers for the duration they kept principal at risk: a staker's score is eligibleStake · (T − entry)², so someone exposed for the whole risk window should out-earn a late joiner of equal size.
The lower bound of that window, riskWindowStart, is sealed at the first pool call that happens to observe the registry in an active-risk state - not at the registry's real transition to UNDER_ATTACK. Because stake() observes the registry before it records the new deposit, a late entrant's own stake() can be the first observer.
When it seals the window, _markRiskWindowStart retroactively rewrites the global accumulators to totalEligibleStake · t and _clampUserSums floors every earlier depositor's entry to t.
Every staker present at the seal ends with the same entry == riskWindowStart, so (T − entry) is identical for all of them and the k=2 weighting collapses into plain amount-weighting for the whole pre-seal cohort.
The earlier stakers lose the at-risk time they actually bore; that time-premium is transferred to whoever sealed late.
ConfidencePool.sol - stake() observes before recording the deposit:
_markRiskWindowStart retroactively resets all current stakers to t:
_clampUserSums erases each earlier depositor's accrued at-risk time:
With every entry == riskWindowStart, _bonusShare's userScore = eligibleStake · (T − entry)² reduces to a constant (T − entry)² times eligibleStake - i.e. pure amount-weighting.
Likelihood:
The window seals on the first call that observes active risk - not at the real UNDER_ATTACK transition. Nothing on-chain ties the two together. As long as the registry is UNDER_ATTACK and nobody has poked the pool, the window is open. An insider just watches the registry and makes sure their stake() is the first observer.
Impact:
The k=2 time-weighting is defeated. Every staker present at the late seal is floored to the same entry time, so the bonus splits by stake amount alone - the duration-of-risk premium is erased and handed to the late sealer.
How much is transferred (bonus only; principal is untouched):
Equal stakes - pure theft: an early staker who bore 21 days of risk drops from 98.5% → 50%, and the 3-day late sealer jumps from 1.5% → 50%.
Running the suite:
The insider's principal is genuinely at risk during the exploit: once the window is open, deposits close on
PROMOTION_REQUESTED(no last-second entry) and the insider cannotwithdraw(), so their capital is locked for the full promotion delay. The late seal is therefore not a "risk-free last-second" trick - it is the retroactive erasure of everyone else's at-risk time.
The root cause is that riskWindowStart is derived from the first observer's block.timestamp instead of the registry's actual UNDER_ATTACK transition time, which lets a self-interested late depositor push it forward.
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.