stake() and contributeBonus() account for the whole pool balance increase observed around safeTransferFrom. If the token can execute a callback during transferFrom, unrelated same-token funds can be pushed into the pool before balanceAfter is read. The pool then treats those unrelated funds as the caller's stake or bonus contribution.
This affects both principal accounting and bonus accounting. The same root also makes recovery-chain and multi-hop variants possible, because any sweep that lands in the target pool during the token callback is included in the caller's received amount.
Normal staking should credit the caller only for tokens transferred by that caller. Instead, stake() trusts the pool's full balance delta across the external token call:
The same pattern exists in contributeBonus():
During the safeTransferFrom callback, an attacker can call a second pool that sends tokens into the target pool. Examples include claimCorrupted() on a pool whose recoveryAddress is the target pool, sweepUnclaimedBonus() on a pool whose recovery address is the target pool, or a batch that sweeps multiple pools through a recovery chain.
Because the target pool only compares balanceBefore and balanceAfter, it cannot distinguish the attacker's actual transfer from unrelated funds that arrived during the callback.
An attacker can pay a small stated stake and receive a much larger principal credit backed by funds from another pool. The inflated stake can be withdrawn before risk starts or claimed later through claimSurvived() / claimExpired() even when direct withdrawals are disabled.
The bonus path lets an attacker reclassify another pool's recovery funds as bonus in the target pool, then claim those funds through the target pool's payout path.
The loss scales with the amount swept into the callback. A multi-hop recovery chain can let one small stake absorb balances from multiple upstream pools.
Cross-pool principal inflation through stake():
Cross-pool bonus inflation through contributeBonus():
The same bug can use sweepUnclaimedBonus() as the inflow source:
Do not credit balance deltas that can include unrelated inflows. For exact-transfer tokens, require the post-transfer balance to equal balanceBefore + amount and credit amount.
Apply this to both stake() and contributeBonus():
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.