Skip to content

How reentrancy attack work and How to protect your contracts against it

Notifications You must be signed in to change notification settings

Aboudoc/Reentrancy-attack

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Contributors Forks Stargazers Issues MIT License LinkedIn


Logo

Reentrancy Attack

project_description
Explore the docs »

View Demo · Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Roadmap
  5. Contributing
  6. License
  7. Contact
  8. Acknowledgments

About The Project

Product Name Screen Shot

(back to top)

Built With

  • Hardhat
  • Ethers

(back to top)

Getting Started

To get a local copy up and running follow these simple example steps.

Getting Started

To get a local copy up and running follow these simple example steps.

Prerequisites

  • npm

    npm install npm@latest -g
  • hardhat

    npm install --save-dev hardhat
    npm install @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle

    run:

    npx hardhat

Installation

  1. Clone the repo
    git clone https://github.com/Aboudoc/Reentrancy-demo.git
  2. Install NPM packages
    npm install

(back to top)

Usage

If you need testnet funds, use the Alchemy testnet faucet.

This project shows the re-entrancy attack and how to protect against it

Checks-Effects-Interactions Pattern

The simplest way to eliminate reentrancy bugs is to use the checks-effects-interactions pattern.

    function withdraw() external {
        require(balances[msg.sender] > 0);
        (bool success, ) = msg.sender.call{value: balances[msg.sender]}("");
        require(success);
        balances[msg.sender] = 0;
    }

If msg.sender is a smart contract, it has an opportunity to call withdraw() again before the next line happens. In that second call, balanceOf[msg.sender] is still the original amount, so it will be transferred again. This can be repeated as many times as necessary to drain the smart contract.

    function withdraw() external {
        require(balances[msg.sender] > 0);
        balances[msg.sender] = 0;
        (bool success, ) = msg.sender.call{value: balances[msg.sender]}("");
        require(success);
    }

The idea of the checks-effects-interactions pattern is to make sure that all your interactions (external calls) happen at the end

Use a Reentrancy Guard

Another approach to preventing reentrancy is to explicitly check for and reject such calls. Here’s a simple version of a reentrancy guard so you can see the idea:

    bool locked = false;

    function withdraw() external {
        require(!locked, "Reentrant call detected")

        locked = true;
        require(balances[msg.sender] > 0);
        (bool success, ) = msg.sender.call{value: balances[msg.sender]}("");
        require(success);
        balances[msg.sender] = 0;

        lock = false;
    }

With this code, if a reentrant call is attempted, the require will reject it because lock is still set to true

A more sophisticated and gas-efficient version of this can be found in OpenZeppelin’s ReentrancyGuard contract. If you inherit from ReentrancyGuard, you just need to decorate functions with nonReentrant to prevent reentrancy.

Please note that this method only protects you if you explicitly apply it to all the right functions. It also carries an increased gas cost due to the need to persist a value in storage.

Stop Using Solidity's transfer()

EIP-1884 increases the gas cost of the SLOAD operation and therefore breaks some existing smart contracts because their fallback functions used to consume less than 2300 gas, and they’ll now consume more.

Why is 2300 gas significant? It’s the amount of gas a contract’s fallback function receives if it’s called via Solidity’s transfer() or send() methods.

Since its introduction, transfer() has typically been recommended by the security community because it helps guard against reentrancy attacks. This guidance made sense under the assumption that gas costs wouldn’t change, but that assumption turned out to be incorrect. We now recommend that transfer() and send() be avoided.

Any smart contract that uses transfer() or send() is taking a hard dependency on gas costs by forwarding a fixed amount of gas: 2300.

The whole reason transfer() and send() were introduced was to address the cause of the infamous hack on The DAO. The idea was that 2300 gas is enough to emit a log entry but insufficient to make a reentrant call that then modifies storage.

Remember, though, that gas costs are subject to change, which means this is a bad way to address reentrancy anyway. When Constantinople fork was delayed it was because lowering gas costs caused code that was previously safe from reentrancy to no longer be.

(back to top)

Roadmap

  • unit test

See the open issues for a full list of proposed features (and known issues).

(back to top)

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

(back to top)

License

Distributed under the MIT License. See LICENSE.txt for more information.

(back to top)

Contact

Reda Aboutika - @twitter - reda.aboutika@gmail.com

Project Link: https://github.com/Aboudoc/Reentrancy-demo

(back to top)

Acknowledgments

(back to top)

About

How reentrancy attack work and How to protect your contracts against it

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published