Created
September 22, 2021 15:55
-
-
Save nathan-websculpt/eafd85362f0969814cc1e5060fbb5c2d to your computer and use it in GitHub Desktop.
This example shows a simple Reentrancy Guard in action. Even though the balance is not zero-ed out until after the call, this reentrancy guard stops the attacker's receive() from completing the attack. For learning purposes only.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: GPL-3.0 | |
pragma solidity >=0.7.0 <0.9.0; | |
contract ReentrancyGuard { | |
bool private guardLocked; | |
modifier noReentry() { | |
require(!guardLocked, "Prevented by noReentry in ReentrancyGuard"); | |
guardLocked = true; | |
_; | |
guardLocked = false; | |
} | |
} | |
contract Attackee is ReentrancyGuard { | |
mapping(address => uint) public attackeeBalances; | |
function depositIntoAttackee() external payable { | |
attackeeBalances[msg.sender] += msg.value; | |
} | |
function withdrawFromAttackee() external noReentry { | |
uint senderBalance = attackeeBalances[msg.sender]; | |
require(senderBalance > 0); | |
(bool success, ) = address(msg.sender).call{ value: senderBalance }(""); | |
require(success, "withdrawFromAttackee failed to send"); | |
attackeeBalances[msg.sender] = 0; | |
} | |
function getBalanceFromAttackee() external view returns (uint) { | |
return address(this).balance; | |
} | |
} | |
contract Attacker { | |
Attackee public contractToAttack; | |
constructor(address _contractToAttackAddress) { | |
contractToAttack = Attackee(_contractToAttackAddress); | |
} | |
//this is called when Attackee sends Ether to this contract (Attacker) | |
receive() external payable { | |
//comment this out to allow the withdrawal | |
if(address(contractToAttack).balance >= 1 ether) { | |
contractToAttack.withdrawFromAttackee(); | |
} | |
} | |
function depositIntoAttackee() external payable { | |
require(msg.value >= 1 ether); | |
contractToAttack.depositIntoAttackee{value: msg.value}(); | |
} | |
function performAttack() external { | |
contractToAttack.withdrawFromAttackee(); | |
} | |
function getBalanceFromAttacker() external view returns (uint) { | |
return address(this).balance; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Used as an example in This Blog Post