How to Escape Smart Contracts from The Clutch of Reentrancy Attacks?

Read Time: 6 minutes

If we take a closer look into the biggest crypto hacks and the eye-watering figures lost to them, they would have deep-rooted from the coding flaws.

One such common occurrence of security vulnerability is the Reentrancy attack. However, the destructive effect caused due to mishandled reentrancy may not sound as simple as launching the attack itself.

Despite being a familiar and well-publicized issue, the appearance of the Reentrancy bug in smart contracts is always inevitable. 

How often had the Reentrancy vulnerability been exploited by hackers in the past years? How does it work? How to restrain smart contracts from losing funds to Reentrancy bugs? Find answers to these questions in this blog.

So, before long, let’s brush up on the largest re-entrancy attacks in memory. 

Some of The Most Infamous Real-time Reentrancy Hacks 

Reentrancy attacks that caused the most devastating effects on the projects ended up doing one of these two or even both. 

  • Drain off the Ether completely from the smart contracts
  • Hackers sneaking their way into the smart contract code

We can now observe a few cases of Reentrancy attacks and their impact. 

Jun 2016: DAO attack – 3.54M or $150M Ether

Apr 2020: Uniswap/Lendf.Me hack – $25M

May 2021: The BurgerSwap hack – $7.2M

Aug 2021: CREAM FINANCE hack – $18.8M

Mar 2022: Ola Finance – $3.6M

Jul 2022: OMNI protocol – $1.43M

It is crystal clear that Reentrancy attacks have never went out-of-style. Let’s gain deep insights into it in the following passages. 

Overview of Reentrancy attack

As from the name “Reentrancy”, which implies “Reentering again and again.” Reentrancy attack involves two contracts: The victim contract and the Attacker contract. 

The attacker contract exploits the reentrancy vulnerability in the victim contract. It uses withdraw function to achieve it. 

The attacker contract calls the withdraw function to drain the funds from the victim contract by making repeated calls before the balance in the victim contract is updated. The victim contract will check the balance, send funds and update the balance. 

But within the time frame of sending the funds and updating the balance in the contract, the attacker contract makes the continuous call to withdraw funds. As a result, the balance is not updated in the victim contract until the attacker contract drains off all the funds.

The severity and the cost of reentrancy exploitation alarm the dire need for performing smart contract audits to rule out the possibility of overlooking such errors. 

Illustrative view of Reentrancy Attack

Let’s fathom the concept of reentrancy attack from the simplified illustration below. 

Here are two contracts: The vulnerable contract and the Hacker contract

The hacker contract makes a call to withdraw from the vulnerable contract. On receiving the call, the vulnerable contract checks for the funds in the hacker contract and then transfers the funds to the hacker. 

The hacker receives the funds and implements the fallback function, which calls again into the vulnerable contract even before the balance is updated in the vulnerable contract. Thus repeating the same operation, the hacker withdraws the funds completely from the vulnerable contract. 

Features Of Fallback Function Used By Attacker 

  • They are externally callable. I.e. they cannot be called from within the contract they are written
  • Unnamed function
  • The fallback function doesn’t include arbitrary logic inside it
  • Fallback is triggered when ETH is sent to its enclosing smart contract, and no receive() function is declared.

Analysing Reentrancy Attack From A Technical View 

Let’s take a sample contract and understand how the reentrancy attack occurs.

Malicious Contract

contract Attack 
    DepositFunds public depositFunds;

    constructor(address _depositFundsAddress) 
        depositFunds = DepositFunds(_depositFundsAddress);

    // Fallback is called when DepositFunds sends Ether to this contract.
    fallback() external payable 
        if (address(depositFunds).balance >= 1 ether) 

    function attack() external payable 
        require(msg.value >= 1 ether);
        depositFunds.depositvalue: 1 ether();

This is the attacker contract wherein the attacker deposits 2ETH. The attacker calls the withdraw function in the vulnerable contract. Once the funds are received from the vulnerable contract, the fallback function is triggered. 

The fallback then executes the withdraw function and drains the fund from the vulnerable contract. This cycle goes on until the funds are completely exhausted from the vulnerable contract.

Vulnerable Contract

contract DepositFunds 
    mapping(address => uint) public balances;

    function deposit() public payable 
        balances[msg.sender] += msg.value;

    function withdraw() public 
        uint bal = balances[msg.sender];
        require(bal > 0);

        (bool sent, ) = msg.sender.callvalue: bal("");
        require(sent, "Failed to send Ether");

        balances[msg.sender] = 0;

The vulnerable contract has 30ETH. Herein the withdraw() function sends the requested amount to the attacker. Since the balance is not updated, the tokens are transferred to the attacker repeatedly. 

Types Of Reentrancy Attacks

  • Single function reentrancy 
function withdraw() external 
   uint256 amount = balances[msg.sender];
   balances[msg.sender] = 0;

The transfers the funds after which the attacker contract fallback function calls withdraw()again before the  balances[msg.sender] = 0 is updated.

  • Cross-function Reentrancy
function transfer(address to, uint amount) external 
   if (balances[msg.sender] >= amount) 
       balances[to] += amount;
       balances[msg.sender] -= amount;

function withdraw() external 
   uint256 amount = balances[msg.sender];
   balances[msg.sender] = 0;

Cross-function reentrancy is way more complex to identify. The difference here is that the fallback function calls transfer, unlike in single-function reentrancy, where it calls withdraw.

Prevention Against Reentrancy Attacks

Checks-Effects-Interactions Pattern: Checks-effects-interactions pattern helps in structuring the functions. 

The program should be coded in a way that checks the conditions first. Once passing the checks, the effects on the contracts’ state should be resolved, after which the external functions can be called. 

function withdraw() external 
   uint256 amount = balances[msg.sender];
   balances[msg.sender] = 0;

The rewritten code here follows the checks-effects-interactions pattern. Here the balance is made zero before making an external call. 

Use of modifier

The modifier noReentrant applied to the function ensures there are no reentrant calls. 

contract ReEntrancyGuard 
    bool internal locked;

    modifier noReentrant() 
        require(!locked, "No re-entrancy");
        locked = true;
        locked = false;

In The End

The most effective step is to take up smart contract audits from a leading security firm like QuillAudits, wherein the auditors keep a close eye on the structure of the code and check how the fallback function performs. Based on the studied patterns, recommendations are given to restructuring the code if there seem to be any vulnerable behaviours

Safety of funds is ensured right before falling victim to any losses. 


What is a reentrancy attack?

A reentrancy attack happens when a function in the vulnerable contract makes a call to an untrusted contract. The untrusted contract will be the attacker’s contract making recursive calls to the vulnerable contract until the funds are completely drained off. 

What is a reentrant?

The act of re-entering means interrupting the execution of the code and initiating the process all over again, which is also known as re-entering.

What is a reentrancy guard?

Reentrancy guard uses a modifier which prevents the function from being called repeatedly. Read the blog above to find the example for reentrancy guard.

What are some of the attacks on smart contracts?

Smart contracts are exposed to numerous vulnerabilities, such as reentrancy, timestamp dependence, arithmetic overflows, DoS attacks, and so on. Therefore, auditing is a must to ensure there are no bugs that collapse the logic of the contract.


Source link

%d bloggers like this: