# Read-only reentrancy is possible

Transmuter swap mints agToken after collateral transfer, letting an ERC777 hook call SavingsVest.accrue() on a stale collateral ratio and over-mint rewards.

- Payout: Contest reward
- Severity: medium
- Program: Angle Protocol
- Credited: auditor0517
- Disclosed: 2023-08-09
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2023-06-angle-findings/issues/30
- Library page: https://bugbountylibrary.com/r/c4%3A2023-06-angle%3AM-03
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:chained technique:source-review vuln:reentrancy vuln:reward-distribution

**Target:** Transmuter Swapper/Redeemer and SavingsVest contracts
**Root cause:** Swapper._swap transfers collateral in (ERC777 hook) before minting agToken and updating normalizedStables, so getCollateralRatio() read mid-transaction returns an inflated ratio.
**Steps:**
1. Assume collatRatio == BASE_9 (100%) and an accepted ERC777 collateral.
2. Alice calls Swapper.swap collateral->agToken; _swap executes safeTransferFrom(msg.sender, address(this), amountIn) before IAgToken.mint.
3. In the ERC777 tokensReceived hook, Alice calls SavingsVest.accrue().
4. accrue() reads getCollateralRatio() which is still computed without the pending mint, so ratio > BASE_9 + BASE_6.
5. Extra agToken rewards are minted to Alice; swap then completes minting the real amount.
**How found:** Code reading of Swapper._swap ordering: noticed safeTransferFrom (external call, ERC777 hook) precedes agToken mint and normalizedStables update, and that SavingsVest.accrue reads getCollateralRatio without a reentrancy guard.
**Exploitation:** Attacker needs an accepted ERC777 collateral token; during the collateral transfer hook they reenter SavingsVest.accrue() to trigger reward minting against a stale, inflated collateral ratio, extracting extra agToken rewards.
**Insight:** When a protocol exposes a view like getCollateralRatio() used by a separate reward contract, check whether any state-changing path performs an external token transfer before updating the accounting that view reads; ERC777/ERC677 hooks turn that ordering into a read-only reentrancy that mints value elsewhere.
