Table of Contents
To get a local copy up and running follow these simple example steps.
To get a local copy up and running follow these simple example steps.
-
npm
npm install npm@latest -g
-
hardhat
npm install --save-dev hardhat
npm install @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle
run:
npx hardhat
- Clone the repo
git clone https://github.com/Aboudoc/Reentrancy-demo.git
- Install NPM packages
npm install
If you need testnet funds, use the Alchemy testnet faucet.
This project shows the re-entrancy attack and how to protect against it
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
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.
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.
- unit test
See the open issues for a full list of proposed features (and known issues).
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!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the MIT License. See LICENSE.txt
for more information.
Reda Aboutika - @twitter - reda.aboutika@gmail.com
Project Link: https://github.com/Aboudoc/Reentrancy-demo