# Token Callback Reentrancy

StakingBase claim/withdraw violates CEI: virtual _transfer external call before persisting balance lets a malicious receiver reenter and drain funds via stale accounting.

- Payout: Contest reward
- Severity: high
- Program: Olas
- Credited: v12
- Disclosed: 2026-05-18
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2026-01-olas#h-10
- Library page: https://bugbountylibrary.com/r/c4%3A2026-01-olas%3AH-10
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:source-review vuln:race-condition vuln:reentrancy

**Target:** StakingBase claim/withdraw and ServiceManager.create (Solidity staking contracts)
**Root cause:** StakingBase computes a local updatedBalance, performs external transfers via overridable virtual _transfer, and only writes storage afterward with no reentrancy guard; reentrant withdrawal sees stale balance and outer call overwrites with pre-deduction value.
**Steps:**
1. Attacker is a payment receiver or controls a malicious token whose transfer triggers a callback.
2. Call claim/withdraw so _claim zeroes rewards and _withdraw computes local updatedBalance.
3. During _transfer external call, reenter claim/withdraw; stale stored balance passes checks again.
4. Reentrant withdrawal deducts funds; outer _withdraw then overwrites storage with stale updatedBalance, desyncing accounting.
5. Repeat overlapping withdrawals to drain more than stored balance allows.
**How found:** Code review of StakingBase claim/withdraw flow noting CEI violation: virtual _transfer external interaction occurs before persistent balance write, with no reentrancy guard.
**Exploitation:** Attacker must be a recipient or control a recipient/token hook and hold other claimable rewards; reenters claim/withdraw during _transfer to perform overlapping withdrawals that each pass checks, then outer call persists stale balance, draining contract-held tokens.
**Escalation:** Chaining reentrant withdrawals across multiple receivers/tokens to cumulatively drain full contract balance and corrupt internal accounting.
**Insight:** When a transfer helper is virtual/overridable, treat it as an untrusted external call: check whether storage balance is written only after the loop, since a reentrant call reads stale state and the outer frame overwrites the reentrant deduction.
