Ethernaut Puzzle 01 Fallback

This puzzle asks to be the owner of the contract and withdraw all your contributions in the map.

And this is the smart contract:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/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);
}

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

The problem asks us to become the owner of this contract. From the codes, we can see there are two ways to become the owner:

  • When the contract is built, the msg.sender will become the owner. But this contract is already created, we can’t change now. Also, the creator has 1000 * (1 ether) as his initial value.
  • The contribute function. If we can contribute more than the owner, then we can become the owner. But from the above, we can know that it is almost impossible because the owner has already 1000 * (1 ether) as his assets. How could we exceed this value? It is not feasible.
  • We can see from receiver() function, if we send a bit ether to this function, then we can become the owner. This way seems feasible.

By the way, here is the official explanation of Receive Ether Function

A contract can have at most one receive function, declared using receive() external payable { … } (without the function keyword). This function cannot have arguments, cannot return anything and must have external visibility and payable state mutability. It can be virtual, can override and can have modifiers.
The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception.

In the browser console, type

1
await sendTransaction({from: player, to: contract.address, value: toWei('0.000001')})

And then we can verify:

1
await contract.owner()

It should be same with your address.

Finally, in the console type:

1
await contract.withdraw()

Then we can pass this problem !