# Reentrancy in function leave() leads to halting of bottom up checkpoints

Reentrancy in SubnetActorManagerFacet::leave() lets an attacker bootstrap a subnet mid-call, draining funds so later bottom-up checkpoint confirmations revert, halting checkpoints.

- Payout: Contest reward
- Severity: high
- Program: Recall
- Credited: sin1st3r__
- Disclosed: 2026-03-13
- Write-up quality: 8/10
- Original: https://code4rena.com/reports/2025-02-recall#h-06
- Library page: https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-06
- Tags: domain:web3 impact:dos impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained vuln:business-logic vuln:dos vuln:reentrancy

**Target:** SubnetActorManagerFacet::leave() / LibStaking confirmChange
**Root cause:** leave() transfers native ETH (genesisBalance) to the caller before completing withdrawal logic, allowing reentrancy that bootstraps the subnet and moves all funds to the gateway mid-execution.
**Steps:**
1. Deploy malicious contract; join() subnet with minimal stake and preFund() 1 wei to set genesisBalance.
2. From second wallet, stake just under the bootstrap threshold (e.g. ~9.99999 ETH of remaining 10 ETH).
3. Malicious contract calls leave(); genesisBalance transfer triggers fallback.
4. In fallback, stake() the remaining tiny amount to cross threshold, triggering bootstrapSubnetIfNeeded() -> registerInGateway() which moves all funds to gateway.
5. Still in fallback, stake() a minimal amount so leave()'s subsequent transferFunds(amount) succeeds.
6. leave() resumes, calls withdrawWithConfirm() and transfers staked funds back despite subnet now bootstrapped.
7. Later bottom-up checkpoint submitCheckpoint() -> LibStaking.confirmChange() -> addStake() reverts due to empty SubnetActor balance, halting checkpoints.
**How found:** Code review of leave() noting native ETH transfer before withdrawWithConfirm() and the bootstrapped-state branch, combined with understanding of bootstrap fund flow to gateway.
**Exploitation:** Attacker uses a malicious contract with genesisBalance to reenter leave() via native ETH transfer, bootstraps the subnet mid-call to drain SubnetActor funds to the gateway, then re-stakes minimally so leave() completes, leaving a dangling deposit request that later reverts checkpoint confirmation.
**Escalation:** Breaks two invariants: post-bootstrap funds should never be returned by SubnetActor, and confirmations must not occur post-bootstrap without child confirmation; results in permanent checkpoint halt (DoS).
**Insight:** In staking/subnet contracts, native ETH transfers in leave/unstake paths before state finalization enable reentrancy that flips bootstrapped state and drains treasury; check whether post-bootstrap branches still execute legacy pre-bootstrap transfers.
