When the risk window opens and closes in the same block (riskWindowStart == riskWindowEnd), the k=2 bonus denominator collapses to zero and _bonusShare falls back to a pure stake-proportional split. A large deposit made during UNDER_ATTACK in that block is included in snapshotTotalStaked at full weight, so a late whale captures nearly the entire bonus pool and early risk-bearing stakers receive almost nothing.
Under normal operation, the bonus pool is distributed with a k=2 time-weighted formula: each deposit earns share proportional to (T − entryTime)², which heavily favors stakers who bore risk early and crushes late entrants. docs/DESIGN.md §3 states that deposits during UNDER_ATTACK earn “~zero” bonus because entry time is floored at riskWindowStart.
When riskWindowStart and riskWindowEnd are observed in the same block, every staker’s effective entry equals T, so globalScore == 0. The contract then uses an amount-weighted fallback (userEligible / snapshotTotalStaked × snapshotTotalBonus). Staking remains open during UNDER_ATTACK, so a whale can deposit immediately after pokeRiskWindow() and before the registry transitions to PRODUCTION and flagOutcome(SURVIVED) snapshots stake — bypassing the k=2 late-entrant penalty entirely.
Likelihood: Low
The registry transitions from UNDER_ATTACK to PRODUCTION within the same block as pokeRiskWindow(), producing riskWindowStart == riskWindowEnd and globalScore == 0 at resolution time.
A whale stakes a large amount during UNDER_ATTACK after the risk window opens but before flagOutcome(SURVIVED) snapshots totalEligibleStake, which occurs in that same block or shortly after while the fallback path is active.
This is not a flash-loan attack: claimSurvived() requires a prior flagOutcome in a separate transaction, so the attacker must lock capital from stake through resolution.
Impact: High
Early stakers who deposited before the risk window lose almost all of their expected bonus to the late whale (e.g. ~98% of a 60-token bonus pool in the PoC below).
Principal is returned on SURVIVED claims; the loss is confined to the bonus pool, but that pool is the economic incentive the protocol uses to reward early confidence — making the misallocation a material harm to honest participants.
Severity: Medium (Low likelihood × High impact on bonus allocation)
From the repo root (after forge install and forge build):
The PoC lives in test/unit/KBAuditWhaleDrain.t.sol. A passing run shows the late whale’s bonus exceeds Alice’s by more than 100×.
PoC file: test/unit/KBAuditWhaleDrain.t.sol
| Actor | Stake | Bonus received (approx.) | Share of 60 bonus |
|---|---|---|---|
| Alice | 100 tokens | ~0.59 tokens | ~0.99% |
| Bob | 50 tokens | ~0.30 tokens | ~0.49% |
| Attacker | 10,000 tokens | ~59.11 tokens | ~98.5% |
snapshotTotalStaked at flag time: 10,150 tokens. Attacker bonus exceeds Alice’s by >100× (assertGt(attackerBonus, aliceBonus * 100)).
The existing regression test testBonusFallsBackToAmountWeightedWhenFlagSameBlockAsRiskWindow covers the fallback with only pre-window stakers; it does not include a late UNDER_ATTACK whale.
Exclude post-window deposits from the amount-weighted fallback, or block staking once the terminal risk moment is sealed.
Option A — exclude late entries from fallback denominator:
Option B — close staking when the risk window has ended (simpler):
Option B prevents any deposit after riskWindowEnd is sealed, including late UNDER_ATTACK stakes in a same-block open/close window, while preserving the existing amount-weighted fallback for stakers who were already in the pool before the window opened.
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.