ERC777 Smart Contract Security

ERC777 is an advanced token standard designed to improve on ERC20 by adding hooks that notify sender and recipient contracts on every transfer. These hooks — tokensToSend and tokensReceived — make ERC777 tokens inherently dangerous to integrate without reentrancy guards, and have been exploited in real attacks.

How ERC777 hooks enable reentrancy

Every ERC777 transfer calls an external function on the sender's registered hook (tokensToSend) and the recipient's registered hook (tokensReceived). These are arbitrary external calls that execute during the transfer — before any state updates in your contract complete.

This turns every token transfer into a potential reentrancy vector, even if you're following the checks-effects-interactions pattern for Ether transfers. The attack flow:

  1. Attacker deploys a contract that registers a malicious tokensReceived hook
  2. Your protocol calls token.transfer(attacker, amount)
  3. ERC777 calls attacker.tokensReceived() before your state is updated
  4. Attacker re-enters your protocol's withdraw function and drains funds

Real-world exploit: imBTC + Uniswap V1 (2020)

In April 2020, an attacker drained approximately $300,000 from the imBTC/ETH pool on Uniswap V1. imBTC is an ERC777 token — its tokensReceived hook was called during the swap, re-entering Uniswap V1's single-function design before the balance was updated. See the imBTC token risk profile and AMP token risk profile for the affected tokens.

Affected tokens

ERC777 tokens in widespread use include AMP and imBTC. ERC777 is also backward-compatible with ERC20 — meaning a contract can be both ERC20 and ERC777 simultaneously, and many tools won't warn you.

How to protect your protocol

  • ReentrancyGuard on all external-token-interaction functions: Add OpenZeppelin's nonReentrant modifier to any function that calls transfer or transferFrom on external tokens
  • Checks-effects-interactions: Always update all state variables before calling external contracts — including token contracts
  • Token allowlist: If your protocol only needs to support specific tokens, maintain an explicit list and reject ERC777 tokens
  • ERC1820 registry check: Use the ERC1820 registry to detect whether a token has registered hooks before allowing deposits
// ERC1820 registry — detect if token has hooks
IERC1820Registry constant ERC1820 = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
bytes32 constant TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient");

bool hasHook = ERC1820.getInterfaceImplementer(tokenAddress, TOKENS_RECIPIENT_INTERFACE_HASH) != address(0);

See reentrancy for the full vulnerability explanation and prevention patterns.