Lesson 8 of 23
In Progress

Reentrancy Attacks

Reentrancy attacks are a type of vulnerability that can occur in smart contracts if they are not properly designed to handle external calls. These attacks can allow an attacker to repeatedly call a contract’s functions in a way that drains the contract’s resources or exposes sensitive information. In this article, we’ll explore what reentrancy attacks are and how to prevent them in your smart contracts.

What is a Reentrancy Attack?

A reentrancy attack occurs when a contract calls an external contract or function, and the external contract or function calls back into the original contract before the original call has completed. This can cause an infinite loop of calls between the two contracts, which can drain the resources of the original contract or expose sensitive information.

Here is an example of a simple contract that is vulnerable to reentrancy attacks:

pragma solidity ^0.6.0;

contract ReentrancyVulnerability {
    uint public balance;

    function sendEther() public payable {
        balance += msg.value;
    }
}

In this contract, the sendEther function allows a caller to send ether to the contract and increments the balance variable to track the total amount of ether received. However, this contract is vulnerable to reentrancy attacks because it does not check for external calls before updating the balance variable.

How to Prevent Reentrancy Attacks

There are several ways to prevent reentrancy attacks in your smart contracts. One common method is to use a mutex, which is a mechanism that allows only one call to a contract’s functions at a time.

Here is an example of a contract that uses a mutex to prevent reentrancy attacks:

pragma solidity ^0.6.0;

contract ReentrancySafe {
    uint public balance;
    bool mutex;

    function sendEther() public payable {
        require(!mutex); // prevent reentrancy
        mutex = true;
        balance += msg.value;
        mutex = false;
    }
}

In this contract, the mutex variable acts as a flag that prevents multiple calls to the sendEther function at the same time. The require statement at the beginning of the function checks the value of the mutex variable and throws an exception if it is true, which prevents reentrancy.

Conclusion

Reentrancy attacks can be a serious vulnerability in smart contracts if not properly addressed. By using a mutex or other mechanism to prevent multiple concurrent calls to a contract’s functions, you can protect your contracts from these attacks and ensure their security.

Exercises

To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.

Write a Solidity contract that defines a function named sendEther that allows a caller to send ether to the contract and increments a balance variable to track the total amount of ether received. The contract should be vulnerable to reentrancy attacks.

pragma solidity ^0.6.0;

contract ReentrancyVulnerability {
    uint public balance;

    function sendEther() public payable {
        balance += msg.value;
    }
}

Write a Solidity contract that defines a function named sendEther that allows a caller to send ether to the contract and increments a balance variable to track the total amount of ether received. The contract should use a mutex to prevent reentrancy attacks.

pragma solidity ^0.6.0;

contract ReentrancySafe {
    uint public balance;
    bool mutex;

    function sendEther() public payable {
        require(!mutex); // prevent reentrancy
        mutex = true;
        balance += msg.value;
        mutex = false;
    }
}

Write a Solidity contract that defines a function named callExternal that calls a function in an external contract and increments a counter variable. The external contract should call back into the original contract and increment the counter variable again before the original call has completed. The contract should be vulnerable to reentrancy attacks.

pragma solidity ^0.6.0;

import "./ReentrancyVulnerability.sol";

contract ReentrancyAttack {
    ReentrancyVulnerability public target;
    uint public counter;

    function callExternal() public {
        target.sendEther();
        counter++;
    }
}

Modify the ReentrancyAttack contract from Exercise 3 to use a mutex to prevent reentrancy attacks.

pragma solidity ^0.6.0;

import "./ReentrancySafe.sol";

contract ReentrancySafeAttack {
    ReentrancySafe public target;
    uint public counter;
    bool mutex;

    function callExternal() public {
        require(!mutex); // prevent reentrancy
        mutex = true;
        target.sendEther();
        counter++;
        mutex = false;
    }
}

Write a Solidity contract that defines a function named callExternal that calls a function in an external contract and increments a counter variable. The external contract should check for external calls before incrementing the counter variable.

pragma solidity ^0.6.0;

import "./ReentrancySafe.sol";

contract ReentrancySafeAttack {
    ReentrancySafe public target;
    uint public counter;

    function callExternal() public {
        target.sendEther();
        counter++;
    }
}