WinDeveloper Coin Tracker

Reentrancy Bugs Part 2

  • Published: Oct 27, 2020
  • Category: Ethereum, Solidity
  • Votes: 5.0 out of 5 - 13 Votes
Cast your Vote
Poor Excellent

In the second part of this series on reentrancy bugs, we demonstrate what they are and how to avoid them.

In the last article we left off with this shared wallet implementation below. Whilst many may assume the code is sound, it contains an intricate bug known as the 'reentrancy' bug. The bug exists within the red lines below.

pragma solidity >=0.5.0 <0.7.0;
contract SharedWalletContract {

    mapping(address => uint) private balances;
    
    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }
    
    function withdraw(uint amount) public payable {
        require(balances[msg.sender] >= amount, "Insufficient funds in account");
        msg.sender.transfer(amount);
        balances[msg.sender] -= amount;
    }

}

When a smart contract transfers Ether to another account, the other account could belong to a person, however it could also belong to another smart contract. This is a problem because upon receiving Ether, smart contracts can execute code. Now, imagine the receiving smart contract's code that will be executed on receiving Ether will make a second request to withdraw. The balance for the associated account would not have been updated yet, and the attacker would have managed to withdraw more funds then they had deposited.

The image below depicts how the reentrancy bug works.

Smart contract reentrancy bug

To fix this we need to make sure that the balance is updated prior to transferring out Ether, as per the switched red lines below:

pragma solidity >=0.5.0 <0.7.0;
contract SharedWalletContract {

    mapping(address => uint) private balances;
    
    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }
    
    function withdraw(uint amount) public payable {
        require(balances[msg.sender] >= amount, "Insufficient funds in account");
        balances[msg.sender] -= amount;
        msg.sender.transfer(amount);
    }

}

As a general principle, to avoid reentrancy bugs it is recommended that developers follow a check-effects-interactions pattern, where code: (i) first undertakes any checks to ensure it should continue to make progress within the function; then is followed by (ii) any code that manipulates contract state; and finally followed by (iii) any code that interacts with any other contracts or external parties. This pattern is depicted below.

Check, effects, interactions pattern

In a future article we'll demonstrate how you could write a smart contract which undertakes such an attack (for educational purposes).

Copyright 2024 All rights reserved. BlockchainThings.io