In this blog, we describe reentrancy attacks in the ERC-777 standard.

A hands-on example contracts and descriptions is here, and the executable test is here.

Clone this repository and run $wake up. Then, run $wake test tests/test_6_erc777.py to run on your local environment.

The ERC-777 is a standard for fungible tokens with a transfer hook. The exchange contract allows users to exchange ETH to SSSToken at a calculated rate.

The calculation uses the total amount of SSSToken in the Exchange contract, the total amount of ETH in the Exchange contract with the corresponding token amount that the user wants to exchange.

Expected usage

  • Users can call the tokenToEthInput function to exchange the SSSToken to ETH.

  • Users can call the ethToTokenInput function to convert ETH to the SSSToken.

This is a vulnerable Exchange contract.

Token contract

We use MyERC777Token as an ERC-777 token and deploy with the Exchange vault as the defaultOperator. Same as the default usage of ERC-777 – this send confirms that the target contract implemented ERC-777, so it can prevent lockout of the values.

The ERC777 feature enables the attack. When transferring the token, it does an external call. The target of this external call is the transfer target. This feature is for notifying of balance changes, but this feature is vulnerable to reentrancy attacks.

Attack example

In this case, we can attack the tokenToEthInput function. It already has the external call recipient.call{value: ethBought}(""); at the end of the function. Moreover, other computations are done above this function. Therefore, it seems the Checks-Effects-Interactions prevention is done.

However, according to the ERC777 feature, it is vulnerable to attack. There is an external call when transferring the SSSToken. Therefore, we can reenter the contract without sending the ETH value.

So while re-entering, Exchange.balance i.e. the balance of ETH in Exchange does not change. This balance value is used for computing the getInputPrice function.

In getInputPrice function, the calculation is done by this formula.

ETHVAULT = SSSTOKENVALUE * 997 * ETH_BALANCE_IN_EXCHANGE / (SSSTOKEN_IN_EXCHANGE1000 + SSSTOKENVALUE997)

And the ETH_BALANCE_IN_EXCHANGE is higher than it should be, and ETHVALUE is sent to the attacker.

Attacker contract

This is the attack contract.

Exploit

This is the exploit.

This is the beginning of Wake’s output. We exchanged to MyERC777Token and we got 90661089388014913158 tokens from 100ETH.

This is how it exchanges tokens for ETH using reentrancy.

This is the ending part of Wake’s output. It sends 1.2ETH every time from the Exchange contract to the attacker.

In the end, the attacker received around 9 ETH from this attack.

Prevention

A simple reentrancy guard would prevent this attack:

Conclusion

The ERC-777 has an external call to the target. It breaks the Checks-Effects-Interactions patterns and it can easily be vulnerable.

We have a Reentrancy Examples Github Repository. There are other types of reentrancy attacks, as well as protocol-specific reentrancies.

Check out our other type-specific reentrancy blogs below:

Resources

https://blog.openzeppelin.com/exploiting-uniswap-from-reentrancy-to-actual-profit