# Attacker can steal RToken holders' funds by performing reentrancy attack during redeem() function token transfers

RToken.redeem() transfers basket tokens before all balances settle; an ERC777 hook lets an attacker reenter redeem() or manageTokens() to over-withdraw or misclassify backing as revenue.

- Payout: Contest reward
- Severity: medium
- Program: Reserve
- Credited: unforgiven
- Disclosed: 2023-04-27
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-01-reserve-findings/issues/347
- Library page: https://bugbountylibrary.com/r/c4%3A2023-01-reserve%3AM-07
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:chained technique:source-review vuln:accounting-error vuln:business-logic vuln:reentrancy

**Target:** RToken.redeem() / BackingManager.manageTokens() collateral transfers
**Root cause:** redeem() updates basketsNeeded and burns RToken, then transfers basket tokens one-by-one; during an ERC777 transfer hook the BackingManager balances are only partially updated, so reentrant calls read stale balances and stale collateralization state.
**Steps:**
1. Basket contains [SOME_ERC777, USDT]; BackingManager holds 200K ERC777 and 100K USDT, basketsNeeded=150K, supply=150K, attacker holds 30K RToken.
2. Attacker registers an ERC777 tokensReceived hook on their address.
3. Call `redeem(15K)`; basketsNeeded drops to 135K, prorata bounds amounts to 15K ERC777 + 10K USDT.
4. During the ERC777 transfer hook (USDT not yet sent), reenter `redeem(15K)`; supply/basketsNeeded now 135K, USDT balance still 100K, so second withdrawal computes 15K ERC777 + 11.1K USDT.
5. Hook returns; outer redeem sends remaining 10K USDT. Attacker received 30K ERC777 + 21.1K USDT for 30K RToken instead of the prorata-bounded 20K USDT.
6. Scenario 2: during hook call `BackingManager.manageTokens()`; with basketsNeeded reduced but ERC777 balance not yet debited, fullyCollateralized() sees excess and handoutExcessAssets() mints/distributes backing to RSR stakers.
**How found:** Code reading of redeem(): noticed state (basketsNeeded, burn) is updated before the interaction loop of safeTransferFrom calls, and that ERC777/hook tokens invoke receiver code mid-loop while BackingManager balances are only partially decremented; then checked fullyCollateralized()/manageTokens() to see stale balances would be read as excess.
**Exploitation:** Attacker needs an ERC777 (or hook-bearing) token admitted as basket collateral and RToken holdings up to the withdrawal battery charge. They redeem, and inside the ERC777 transfer callback reenter redeem() to bypass the under-collateralization prorata bound, or call manageTokens() to have backing tokens counted as excess revenue and distributed to RSR stakers.
**Escalation:** Repeated reentrant redeems within one block up to battery charge amplify the over-withdrawal; manageTokens() path diverts holder backing into RSR reward distribution.
**Insight:** When a redeem/withdraw loop transfers multiple assets after committing accounting state, any hook-bearing asset in the basket is a reentrancy vector: check whether collateralization or prorata bounds are recomputed from live balances mid-loop, and whether a permissionless manage/harvest function can be triggered while balances are stale.
