Exploiting an ERC777-token Uniswap Exchange
Exploiting any Uniswap exchange that trades an ERC777 token by leveraging the reentrant microtrading attack vector
Table of Contents
Install
- Setup a Python virtual environment
$ pip3 install virtualenv
$ virtualenv -p python3 venv
- Activate virtual env & install Python dependencies (just Vyper)
$ source venv/bin/activate
$ pip install -r requirements.txt
If source venv/bin/activate
does not work for you, try out with bash venv/bin/activate
.
- Install NPM dependencies
$ npm install
Run
Once in the virtual environment (where Vyper must be installed), run
(venv)$ npm test
Exploit details
The proof of concept for the exploit is located in the test/uniswap.exploit.js
file. It takes care of setting up the entire environment and running three test case scenarios.
- A "template" Exchange
- A Uniswap Exchange Factory (see
uniswap_factory.vy
- taken from Uniswap's repository) - The ERC777 token to be exchanged
- The ERC1820 registry to register interfaces
- The actual Exchange for the token (see
uniswap_exchange.vy
- taken from Uniswap's repository) - Sending / approving the necessary ETH and tokens to all actors
The three test cases are:
-
Legitimate trading with a single external sale: a user that holds tokens wants to operate on the exchange, to deposit tokens and receive ETH. This is done in a single transaction calling the
tokenToEthSwapInput
function. This is the regular use case for a Uniswap exchange. -
Legitimate trading with multiple external sales: same as case (1), but now the user submits multiple transactions instead of just 1. Therefore, this results in less profit than (1).
-
Exploiting: the attacker deploys an attacker contract that will be in charge of operating in the exchange. The exploit is executed in a single transaction, reentering several times in the vulnerable function
tokenToEthSwapInput
by leveraging the ERC777tokensToSend
hook.
Why it works
By leveraging the tokensToSend
hook, the attacker contract is called after receiving ETH (i.e. the exchange ETH balance has decreased) but before the token balance is modified (i.e. the exchange token balance has not decreased). As a consequence, reentering the vulnerable tokenToEthSwapInput
will re-calculate the token-ETH exchage price, but this time with less ETH and same amount of tokens in reserves. Thus, the exchange will be buying the attacker tokens, paying in ETH, at a higher price than it should.
Learning resources
- Uniswap docs
- EIP 777 - tokensToSend hook
- Reentrancy attack: SWC 107
Disclaimer
This is a proof-of-concept exploit of an already public, disclosed and acknowledged vulnerability in Uniswap related to reentrancy attacks. Were that not the case, under no circumstances this proof-of-concept exploit would have been made public. Should you find any 0-day vulnerability in these contracts, please report directly to Uniswap.