DEV Community

Valli Nayaki Chidambaram
Valli Nayaki Chidambaram

Posted on

What is Reentrancy?

Reentrancy in Smart Contracts means calling a function recursively until the called contract is depleted of funds. This is one of the most common smart contract hacks.

The example below will give a clear idea of the reentrancy attack

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
//Understand what reentrany is
//This contract contains bug. DONOT use this
contract BadBank {
    receive() external payable {}
    mapping(address => uint256) public balances;
    constructor() payable {}

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

    function withdraw() external {
        (bool ok, ) = payable(msg.sender).call{value: balances[msg.sender]}("");
        require(ok, "transfer failed");
        balances[msg.sender] = 0;
    }
}

contract BankDrainer {
    receive() external payable {
        while (msg.sender.balance >= 1 ether) {
            BadBank(payable(msg.sender)).withdraw();
        }
    }

    function attack(BadBank _badBank) external payable {
        _badBank.deposit{value: 1 ether}();
        _badBank.withdraw();
    }
}

Enter fullscreen mode Exit fullscreen mode

There are 2 contracts - BadBank which has the bug and BankDrainer which attacks BadBank

Structure of BadBank

BadBank has one state variable - balances - which tracks how much each user deposits into the contract

receive function - which makes the contract accept payment!

deposit function - adds the input msg.value to the existing balance of the user and transfers money from user wallet to contract (msg.value should be given - this function is payable)

withdraw function - this is where the problem lies :D
This function has three lines of code

First line - transfers user balance (which the user deposited into the contract) back to the user

(bool ok, ) = payable(msg.sender).call{value: balances[msg.sender]}("");

second line - Check if the transfer is successful (ok is true). Else throw error

require(ok, "transfer failed");
Third line: Set the user's balance to zero (as you have already transferred all the money back to the user!).

balances[msg.sender] = 0;

Pretty straightforward right!

Let's see how BankDrainer tries to hack the money stored in BadBank (which is a classic example of a reentrancy attack)

Structure of BankDrainer

attack function - calls deposit and withdraw function in BadBank contract consecutively

receive function - there is some weird logic there! Inside receive if the msg.sender's balance is >= 1 Ether, we are calling BadBank's withdraw again!

Do you remember when the receive() function will be called? Yes! Whenever the contract receives ETH! So this logic will be executed whenever someone / some contract tries to send ETH to BankDrain.

Great! Let's see how these work together to perform the attack!

Deploy the contracts using remix and follow the steps

Image description

Step 1: We'll execute the attack() function using BadBank's contract address.

Image description

deposit() in BadBank will keep track of balance[BankDrainer's] address = 1 ETH and 1 ETH will be transferred to BadBank

Step 2: Calling BadBank's withdraw() function. Moving to withdraw(),

(bool ok, ) = payable(msg.sender).call{value: balances[msg.sender]}("");

That is BadBank sending 1 ETH to BankDrainer. This will redirect the control to receive() function in BankDrainer

So inside receive() function, we are calling withdraw again (as msg.sender = BadBank's address inside BankDrainer >= 1 ETH).

 withdraw() in BadBank -> receive() in BankDrainer -> withdraw() in BadBank -> receive() in BankDrainer -> and on and on ...
Enter fullscreen mode Exit fullscreen mode

This will be recursively called until BadBank runs out of balance.

Eventually, all the money in the BadBank contract will end up in BankDrainer. And that's how you demonstrate a reentrancy attack!

Image description

Happy Learning!

Please leave your appreciation by commenting on this post!

Join to comment

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

πŸ‘‹ Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay