# Insolvency via Cross-Service Reentrancy in StakingBase._withdraw

StakingBase._withdraw updates global balance after external transfers, letting cross-service reentrancy overwrite deductions and inflate recorded balance, causing insolvency.

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

**Target:** StakingBase.sol staking contract _withdraw/unstake
**Root cause:** _withdraw caches global balance in a local, performs external transfers in a loop, and only writes the global balance at the end, violating CEI with no nonReentrant guard.
**Steps:**
1. Attacker controls two service IDs A and B.
2. Call `unstake(Service A)`; `_withdraw` reads global balance (e.g. 100 ETH) into `updatedBalance`.
3. Contract transfers rewards for Service A; attacker's `receive()` fallback reenters `unstake(Service B)`.
4. Inner call reads stale 100 ETH, computes 90 ETH, writes to storage, returns.
5. Outer call resumes with its local `updatedBalance` and overwrites global balance, erasing Service B's deduction.
**How found:** Code review of _withdraw noticing CEI violation: local balance cache, external transfer loop, and late global state write, combined with absence of a global nonReentrant modifier.
**Exploitation:** Attacker with two staked services reenters unstake from the receive() fallback during reward transfer; the outer frame's stale local balance overwrites the inner frame's deduction, so recorded balance exceeds actual ETH, accumulating insolvency each iteration.
**Escalation:** Repeated across services/iterations to accumulate accounting discrepancy until contract is insolvent.
**Insight:** When a function caches a global accounting variable locally and writes it back after external calls, cross-function reentrancy (not just same-function) can overwrite another frame's deduction; check for missing global reentrancy guards even when per-service state deletion blocks single-service reentry.
