FallBack - LVL #1

Fallback Challenge from Ethernaut CTFs created by OppenZeppelin.

FallBack - LVL #1

To solve this Easy Challenge..,

You have to:

1- Claim ownership of the Contract.

2- Reduce its Balance to 0.

1. Let's study the Contract of this Challenge First.

here is the full Code:

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

1.1. The mapping.

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

There are 2 state variables.., The mapping tells us that the address connected to this state Variable (unsigned integer (uint)) will be the number of contributions tracked to a certain address. So if we get an address we will get its contributions., The second State Variable will be address and public to be the owner.

What is mapping in Solidity?

Mapping is a hash table in Solidity that stores data as key-value pairs, where the key can be any of the built-in data types, excluding reference types, and the value of the data type can be any type.

Mappings are most typically used in Solidity and the Ethereum blockchain to connect a unique Ethereum address to a corresponding value type.

In any other programming language, a mapping is equivalent to a dictionary.


1.2. The constructor.

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

The constructor() function it's executed Only Once when the contract first gets deployed., and when it gets executed the msg.sender will be the owner and the owner will be the state variable for address and also this address that became the owner (He is the owner coz he the deployer of this contract) will contribute with this amount of ether and it's a huge amount. and that means for the mapping that this address (the owner now) contributes with those ethers.


1.3. Modifier with withdraw.

  modifier onlyOwner {
        require(
            msg.sender == owner,
            "caller is not the owner"
        );
        _;
    }

A modifier can get applied to any Function and when it hit a function it executes its code first (the modifier), Then it's going to the function to execute its code...

So here this onlyOwner modifier is for withdraw Function and it's for access control purposes.

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

And this modifier says the msg.sender for the withdraw function MUST be the Only Owner for this contract to be able to withdraw all the Balance ((Remember this well)).and if he is not the owner it will send "caller is not the owner" .

this _; at the end of the modifier means: if the conditions of this modifier is true, go to the function and execute its code.

and there is how the withdraw function works:

payable(owner).transfer(address(this).balance);

Transfer to this address the balance from this contract to the owner.

That's all XD..


1.4. The contributing.

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

The contribute function is used to allow contributions from any address but these contributions must be less than 0.001 ether for each contribution, and the value of the contribution will be added to the contributions of the sender. and if all contributions for one address become more than the contribution of the owner.., this address will be the owner. but this is very hard because you have to have 1000 ether to own the contract (Are you forget the constructor function!?).

  function getContribution() public view returns (uint) {
    return contributions[msg.sender];
  }

And this getContribution is just used to know the address contributions.


1.5. The Fallback.

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

This receive function is used to send money to the contract and it's called a Fallback function and it has no name. just it is accepting money from any address, BUT this address must be Contributed before and have money to send it. And This address will be the Owner.



2. Solving the challenge.

The answer is in the name of this easy challenge.., We know that we have two missions in this challenge to complete it, First is to take ownership of the contract, and the Second is to get all the Balance in the contract.

Let's Go...

2.1. Claim ownership of the contract.

If you take a look at the code, you will see that the msg.sender could be the owner in only 2 functions.., the contribute() function and the receive() function.

For the contribute() function.., You have to have a huge amount of ether more than the main owner has and send the 1000 ether by sending 0.0001 ether in each transaction, so you have to make about 10M transactions each one less than 0.001 ether to be the owner. So that's waste of time and money to take ownership. and We are the Hackers here we won't pay anything.

For the receive() function.., It's also called the Fallback function as the name of the challenge. So If we want to be the owner using this function we only have to have previously contributed to this contract and then send any amount of ether using receive function.

So Let's Contribute to this contract:

I will use the browser console to solve the challenge, and there are several methods to solve it.., you can use Foundry/Hardhat/Brownie with your script (solidity/Javascript/python) to solve it or use Remix ide online.

await contract.contribute({value: 1}) I used the contribute() function to send 1 Wei of ether and become a contributor to this contract.

Then, Let's send some ether to the contract using the receive() function to becoming the owner.

await contract.sendTransaction({value: 3})

This one web3 send ether will tell you how to use js in web3 to send ethers.

Now Let's Check who is the Owner...

await contract.owner()

As You will see the Contract answered me with my address and MetaMask prove that..

So Congrats... I'm Now the Owner

2.2. Reduce its Balance to Zero.

Let's know how much ether is in the balance await getBalance(contract.address).

Now the contract has 4 Wei in the balance.., Let's steal them.

We will use withdraw() function here to reduce its balance to 0.

await contract.withdraw()

Now The Contract has No balance...

Let's Submit the instance... Mission accomplished Successfully...