WinDeveloper Coin Tracker

  • Home
  • Ethereum
  • Reentrancy Bugs (Getting Started with Solidity Programming for Ethereum Part 6)

Reentrancy Bugs (Getting Started with Solidity Programming for Ethereum Part 6)

  • Published: Sep 26, 2020
  • Category: Ethereum, Solidity
  • Votes: 5.0 out of 5 - 1 Vote
Cast your Vote
Poor Excellent

In this article we'll explain what a reentrancy bug is and how to avoid them. This is article forms as part 6 of the Getting Started with Solidity Programming for Ethereum series: There are ample resources out there to get started with smart contract development. Many go into detail in regards to how Blockchains work, and the ins and outs of Ethereum. This article is intended for those who are less patient and want to create a first smart contract now.

Consider the following function (from Part 5 of the Getting Started with Solidity Programming for Ethereum article series), particularly the red line:

function breakPiggyBank() public {
    require(msg.sender == twin1 || msg.sender == twin2, 
                "Only a twin can try to break the piggy bank!");
    require(canBreakAfter <= now, 
                "You have to have waited ten years before trying to break the piggy bank!");
        
    if (msg.sender == twin1) {
        twin1brokeIt = true;
    }
        
    if (msg.sender == twin2) {
        twin2brokeIt = true;
    }
        
    if (!twin1brokeIt || !twin2brokeIt) {
        return;
    }
    
    uint half = amountPutIn / 2;     
    twin1.transfer(half);
    twin2.transfer(half);
}

There is a subtle bug in this. First, before highlighting the bug, we'll provide a simplified example

To simplify, consider the following code code:

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;
    }

}

The above smart contract is a shared wallet. It should provide functionality that allows for anyone to deposit funds into the smart contract, and later withdraw their funds from the smart contract.

Here we introduce a mapping. The balances mapping above allows for us to keep track of different values for different identifiers. I.e. inside of it we can keep track of the fact that wallet A has a balance of say 10 ETH, whilst wallet B has a balance of 2 ETH.

The deposit function allows for anyone to send Ether into the smart contract, and will add the amount sent it (which we retrieve using msg.value) to the balance associated with the user that sent the Ether in. The user that initiated the call is msg.sender and we store their balance by finding the slot associated with their account in the mapping, i.e. balances[msg.sender] .

The withdraw function allows for a user to withdraw their funds previously deposited in the smart contract. So, the first thing we do is check to make sure that the user has enough funds in the smart contract by checking that the amount that they are trying to withdraw is less than the their balance held in the smart contract, i.e. balances[msg.sender] .

If the user has enough funds stored within the smart contract, then we proceed to transfer the amount requested to the user that called the function (msg.sender).

We immediately proceed to deduct the amount withdraw from the balances we are storing in the smart contract, so that if the user calls the withdraw function again, it would have reduced his balance to make sure the user cannot withdraw more funds than they have.

Great, right? Well, that's what many would think when starting smart contract development. However, there is an intricate bug which we'll cover next time.

Copyright 2020 All rights reserved. BlockchainThings.io