How to spot Reentrancy bugs

Alexandre Ramos
Coinmonks
4 min readApr 8, 2022

--

Reentrancy may be the most common and famous exploit in solidity smart contracts.

Photo by FLY:D on Unsplash

First, let me explain how a reentrancy attack occurs in this code example, if you are not familiar with this exploit.

One of the major dangers of calling external contracts is that they can take over the control flow. In the reentrancy attack (a.k.a. recursive call attack), a malicious contract calls back into the calling contract before the first invocation of the function is finished. This may cause the different invocations of the function to interact in undesirable ways.

<https://swcregistry.io/docs/SWC-107>

There’s already a lot of tutorials and articles talkings about reentrancy, but most of them are pretty simple and show one example in particular or a variation of it.

If you look at this code, you can see that, if someone has a balance there, it reads the balance and sends it. But the process of receiving this forwarding of ether would allow the attacker to reentry the function and withdraw their balance two or more times.

For me, the problem of the examples out there, like this one, limits the scope of reentrancy. Most people think that is a problem when you send ether.

For a long time the best practice was, stop sending ether using call function, because you forward all your gas, start using transfer(), send() functions.

Since December 2019 this is no longer the case, with the lowering of gas costs, call functions come back to be the best option again.

But what about reentrancy?

The simplest way to eliminate reentrancy bugs is to use Checks-Effects-Interactions Pattern.
Remember our code above with a reentrancy bug? Now let’s fix it with this pattern.

Notice that now, the balance is modified before the transfer, so trying to reentrancy this function won’t have a benefit at all.

Another way to prevent this attack in this situation is to use the Reentrancy Guard.
(Openzeppelin has a gas-efficient implementation of this, it is worth checking it out).

Reentrancy becomes a more important subject now, because if more and more people start to adopt the call function again, these attacks can become more possible.

Now, let’s go back to the purpose of this article, and it’s how to spot and protect against reentrancy attacks inside a more elegant and complex code then the one i showed above.

There are three signs in a code that we should look for:

  • Before: Control Flow
  • Before: Memory variables created and used after
  • After: Storage writes

To help you to understand these signs to look for, let’s use a famous example of a reentrancy bug, The DAO hack.

The line 6 is actually a send of ether.

Let’s see which signs we can spot in this code.

  1. At line 3, we have a Control flow Before, based on storage values.
  2. At line 5, we have a variable created Before and used after, at line 8.
  3. At line 8, we have a After storage write

As you can see, in this code we can spot all three signs of a reentrancy bug.

Uniswap & ERC777

Another very well known example is Uniswap & ERC777.

Basically if you put ERC777 tokens on Uniswap, they can be stolen through reentrancy attacks.

Let ‘s see the code.

This one is very interesting because doesnt show any of the three signs you talked about earlier, but what was actually the reason for the reentrancy bug?

Spoilers, the vulnerability comes from line 9.

If you don’t know ERC777, it’s ok, for this example you only need to know one feature of this contract, hooks.

In any ERC777 transfer, it goes like this:

  1. Call the sender of tokens
  2. Execute the transfer
  3. Call the recipient of the tokens

So, since the ERC777 hook is executed before the actual transfer, the sender is called and can execute code.

In the Uniswap’s case, when the sender is called, we have this situation:

  • The ETH reserves were already decreased.
  • The token reserve was not yet increased.
  • The sender contract can decide what to do now.

When the sender contract is called, it can reenter the Uniswap contract calling the tokenToEthOutput function again.

In this second call the ETH reserve will be lower, but the token reserve will be the same, and using the Uniswap’s formula to determine the price, the ETH price will be cheaper.

The attacker can reenter the contract as many times the ETH reserve lasts or until your token supply runs out.

Conclusion

This was a quick explanation on how to spot reentrancy bugs in a more elegant and complex codes.

I hope everyone enjoyed.

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

Also, Read

--

--

Alexandre Ramos
Coinmonks

Software Engineer, Blockchain Enthusiast & Crypto Investor