Ethernaut level #1 - Fallback
Tags: solidity, ethernaut
Published: 2023-11-07 21:53:52

Today I decided to go through [Ethernaut](https://ethernaut.openzeppelin.com/) Challenges. Ethernaut is a good resource to sharpen smart contract security and web3 library skills. There are lot's of solutions to this CTF series and now it's time to add my own. Without further ado - let's crack the [Fallback](https://ethernaut.openzeppelin.com/level/0x3c34A342b2aF5e885FcaA3800dB5B205fEfa3ffB) challenge. 

## Requirements 
Here is the smart contract: 

```solidity
// 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;
  }
}

```
The goals are: 

- claim ownership of the contract
- reduce its balance to 0

## Solution

There are two functions which set the owner `contribute()`and `receive()`. Let's see which one is more suitable for our needs. 

`contribute()` updates the owner in case `msg.sender` sends more than initial owner. We can notice in `constructor()` that the contract deployer has generously set their contribution to 1000 ETH. So, utilization of `contribute()` is a long and expensive way. 

Let's take a closer look to `receive()` function: 

```solidity
 receive() external payable {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
  }
```
Seems interesting. In fact, we just have to send any non-zero amount of Ether and become the next owner! But how does `receive()` function work? Here is an [explanation](https://docs.soliditylang.org/en/latest/contracts.html#receive-ether-function) in official Solidity docs: 

> The receive function is executed on a call to the contract with empty calldata. 

Basically, if we send Ether without specifying any method name, `receive()` should be triggered. Enough talking, let's check it out! 

Connect your Metamask using a test network supported by Ethernaut. I use Sepolia. In case you need some testnet ETH go to Sepolia Faucet, e.g. on [Alchemy](https://sepoliafaucet.com/) or [Infura](https://www.infura.io/faucet/sepolia). Both websites will ask you to create an account. 

Open up browser console. We can see that `receive()` requires us to make some contributions. But it only checks if the contribution is greater than zero. Let's call `contribute()` method with `msg.value` equal to `1 wei` which is the smallest possible non-zero amount we could send:

1 wei = 0.000000000000000001 ETH

We call `contribute()` like this:

```javascript
await contract.contribute.sendTransaction({value: 1})
```
We are official contributors! But we still didn't reach our goal. Time to conquer this contract. Output current owner address with this command: 
```javascript
await contract.owner()
```
Looks like it is not someone that we want, right? It's an appropriate time to trigger the `receive()` function. Again, we can just send 1 wei: 
```javascript
await contract.sendTransaction({value: 1})
```
Check the owner once again: 
```javascript
await contract.owner()
```
Yes! We pnwed the contract. The last but not least we need to `withdraw()` all the contract's balance to our own address. We need to hurry before someone else does the same change of ownership trick as we just did :D. Withdrawal is a trivial task for owners: 
```javascript
await contract.withdraw()
```
Done. All the balance from the contract has moved to our address. Here is a complete console output: 

![Fallback solution in browser console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zl4vftsujuxvu310260c.png)

## Summary 
This exercise demonstrates that incorrect business logic can lead to loss of funds despite of having `Ownable` functions in place.
___
If you like this article, feel free to subscribe to my social media accounts: 
- https://dev.to/wallebach
- https://twitter.com/just_buidl