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.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.