In recent months, there have been a number of successful Ethereum attacks targeting decentralized exchanges and DeFi protocols. Two of the attacks used the fact that Ethereum’s ERC-777 token standard’s compatibility with the ERC-20 interface while adding a side-effect of calling an untrusted hook. Although technically ERC-20 on Ethereum does not prohibit calling external (even untrusted) contracts from
transfer, ERC-20 tokens usually do not use this possibility, and most of
transfer calls are reentrancy-safe.
Uniswap protocol and Lendf.me (which uses slightly modified Compound V1 contracts) assumed that calling token
transfer can not yield external contract calls and, hence, reentrancy attacks.
These attacks are hard to make on Tezos due to the operation queue concept. While the exact replicas of attacks are not possible, there are some security issues associated mainly with the currently recommended view pattern. Below, we analyze the details of attacks and the specific differences between Ethereum and Tezos.
Lendf.me tracks the internal token balances of each address. When one invokes the
supply(…) method of the contract to deposit the tokens, the implementation uses the
token.transferFrom(…) method to transfer the funds. However, the balance of the account is updated after this transfer goes through. Making state modifications after external interactions is generally considered to be a bad practice. Probably the contract developers expected
token.transferFrom(…) to not call any untrusted contracts but with ERC-777 making such calls is quite possible and simple.
1. An attacker deposits 250 tokens using
2. He then deposits 0.00000001 more tokens using
supply(…) but this time he also turns on a pre-transfer hook that calls
withdraw(…) executes before any balance changes in (2) and kindly sends 250 tokens back to the attacker.
supply(…) call from (2) continues its execution and sets the hacker’s balance to 250.00000001 tokens.
5. Now the hacker has his 250 tokens back and 250.00000001 on Lendf.me balance, effectively stealing the money from Lendf.me.
The call chain of this attack in Ethereum (starting from step 2) looks like this:
Ethereum call chain
Current call Next callssupply -> ”[token.transferFrom, supply continuation]”
token.transferFrom -> ”[hook.tokensToSend, supply continuation]”
hook.tokensToSend -> ”[withdraw, supply continuation]”
withdraw -> ”[token.transfer (to attacker), supply continuation]”
token.transfer -> [supply continuation]
So the attack arises due to the fact that
supply continuation modifies state after the external transaction is made. Unless explicitly programmed as a continuation, state modifications in Tezos are forced to happen before any external operations. So, in the case of FA2, the order of operations would not feature the state modifying continuation even if the sender hook is set to call
Tezos call chain
Current call Next calls
supply -> [FA2.transfer]
FA2.transfer -> [senderHook]
senderHook -> [withdraw]
withdraw -> [FA2.transfer (to attacker)]
Since we don’t have this
supply continuation here, the exact same attack is not possible.
Tezos has taken deliberate precautions against these kinds of reentrancy attacks. Instead of allowing direct calls to external contracts, it requires a new operation to only be emitted when all state modifications are done. Hence, this exact type of attack would be mitigated.
Uniswap protocol breach
Contrary to the previous attack, this one does not exploit the incorrect order of effects and interactions. Instead, the attacker reenters between two interactions and uses the economical nature of Uniswap to gain unfair profit. Here we will present a simplified description of the attack; for the details see the Uniswap audit report.
Uniswap calculates the rate of different assets based on their availability in the liquidity pool. Thus, a temporary disbalance in the amount of tokens and Ethereum in the pool creates an opportunity for an unfair trade. Uniswap’s function
tokenToTokenInput(…) performs two interactions:
1. Claims tokens using
2. Disburses Ether
The attacker reenters after (1) but before (2). At this point, tokens have already been deposited to Uniswap but the Ether has not been sent yet. Using the disbalance, the attacker drops the exchange rate to near zero Ether per token and gets the tokens almost for free.
Let’s look at the order of execution in Ethereum:
Ethereum call chain
Current call Next callstokenToTokenInput -> ”[token.transferFrom, send ETH]”
token.transferFrom -> ”[hook.tokensToSend, send ETH]”
hook.tokensToSend -> ”[Reenter, send ETH]”
In Tezos, due to the breadth-first order of transaction execution, we would have:
Tezos call chain
Current call Next callstokenToTokenInput -> ”[FA2.transfer, send XTZ]”
FA2.transfer -> ”[send XTZ, senderHook]”
send XTZ -> [senderHook]
senderHook -> [Reenter]
In Tezos, if some transaction emits two internal operations, it is impossible to inject a new operation in between. This makes the execution of this attack impossible since the ratio between XTZ and tokens at the point of reentrancy doesn’t change significantly.
Tezos operation queue concept makes it hard to execute straightforward reentrancy attacks. However, although the described attacks cannot be directly repeated in Tezos, it is still possible to harness reentrancy to deceive contracts, and sometimes having an operation queue as an only option to communicate with other contracts leads to overly-complex interactions between them.
The main culprit, from our perspective, is the currently recommended view pattern. If a contract needs some piece of data from another contract, it is necessary to initiate a continuation-passing style call chain of the form:
A.doX -> B.viewY -> A.viewYCallback
With the right transaction ordering, it is possible to make a fake callback arrive before the real one, which can give an unfair benefit to an attacker. Combined with other kinds of reentrancy attacks, such complex contract interactions can lead to unforeseeable attack vectors.
The drawbacks of the CPS views have been already brought to public attention:
We think that the initiatives described in these proposals should be discussed, supported, and integrated into Tezos with a high priority as having a consistent view of blockchain state and a thought-through interaction model is the clue to smart contract security.