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