The protocol allows deposits while the agreement is UNDER_ATTACK because the design expects the quadratic bonus formula to make a late deposit economically unattractive.
Section 3 of docs/DESIGN.md states:
“Their bonus share is crushed to ~zero by the k=2 weighting — entry is floored at
riskWindowStart ≈ now, so(T − entry)² ≈ 0.”
It consequently concludes:
“There is no late-join advantage to gate against...”
That conclusion holds only when riskWindowStart was already sealed before the late depositor entered. The pool does not read the registry continuously. Instead, riskWindowStart remains zero until a pool interaction observes an active-risk state.
When stake() is the first call to observe UNDER_ATTACK, _observePoolState() invokes _markRiskWindowStart() before accounting for the new deposit:
_markRiskWindowStart() rewrites the global score inputs for all existing stake as though it entered at the observation timestamp:
The new depositor is then recorded with that same timestamp. Therefore, if Bob is the first observer one second before expiry, both Alice’s old stake and Bob’s new stake receive an effective entry time of expiry - 1.
At expiry, both positions have the same score per token:
The bonus consequently becomes amount-weighted. If Alice has 100 tokens and Bob deposits 900 tokens, Bob receives 90% of the bonus despite bearing only one second of risk.
Section 7 of the design documentation acknowledges the underlying timestamp behavior:
“Every deposit made before the seal (including the one that triggers it) floors to
riskWindowStartand is amount-weighted among that cohort. Intentional...”
However, this directly conflicts with Section 3’s broader assurance that an UNDER_ATTACK depositor’s bonus is crushed to approximately zero and that there is no late-join advantage. A deposit that triggers the first observation is not crushed relative to existing stakers; it receives exactly the same per-token weight.
The documented safety argument therefore depends on an unstated operational assumption that an economically interested participant will call pokeRiskWindow() promptly after the registry enters UNDER_ATTACK. The contract does not enforce that assumption.
Likelihood:
The agreement must remain UNDER_ATTACK without any pool interaction until shortly before expiry. Any earlier call that seals riskWindowStart prevents the attack.
Impact:
A late depositor can capture an amount-weighted portion of the bonus while bearing almost no risk, diluting the bonus owed to earlier capital providers.
Add the following test to test/unit/LateUnderAttackBonus.t.sol and run:
The test passes and shows that Bob receives 90% of the bonus after depositing only one second before resolution.
Maybe require the risk window to be observed before any UNDER_ATTACK deposit is accepted, and reject deposits that would enter too close to expiry. This stops a late first observer from sealing the window and immediately capturing an amount-weighted bonus share.
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.