# The Ethernaut CTF Solutions | 18 - Magic Number

## Goals

[![](https://github.com/Pedrojok01/Ethernaut-Solutions/raw/main/assets/requirements/18-magicNumber-requirements.webp align="left")](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/assets/requirements/18-magicNumber-requirements.webp)

Ok, so here it gets way more advanced than previous levels. This level while looking quite simple, requires a deep knowledge of EVM opcode, how the stack works, and contract creation code and runtime code.

## The Contract

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MagicNum {
    address public solver;

    constructor() {}

    function setSolver(address _solver) public {
        solver = _solver;
    }

    /*
    ____________/\\\_______/\\\\\\\\\_____        
     __________/\\\\\_____/\\\///////\\\___       
      ________/\\\/\\\____\///______\//\\\__      
       ______/\\\/\/\\\______________/\\\/___     
        ____/\\\/__\/\\\___________/\\\//_____    
         __/\\\\\\\\\\\\\\\\_____/\\\//________   
          _\///////////\\\//____/\\\/___________  
           ___________\/\\\_____/\\\\\\\\\\\\\\\_ 
            ___________\///_____\///////////////__
  */
}
```

## The hack

Unfortunately, the following will not work:

```solidity
contract Solver {
    function whatIsTheMeaningOfLife() public pure returns (uint256) {
        return 42;
    }
}
```

This is because solidity is a high-level language that has a lot of "built-in" features and checks implemented for us. In other words, way too many opcodes to meet the challenge's requirements.

So, as suggested by the challenge, we need to write the contract in raw bytecode.

First, let's delimit the scope of what we need to achieve. The only things we have to worry about are:

* the contract deployment;
    
* return `42` whenever called.
    

In bytecode, this is the equivalent of:

* The **creation bytecode** in charge of deploying the contract;
    
* The **runtime bytecode** which will live on-chain and be in charge of executing the contract's code.
    

The creation bytecode is the first thing that gets executed when deploying a contract. It is in charge of deploying the contract and returning the runtime bytecode. This is why we will start with the runtime bytecode.

### Runtime bytecode

The absolute minimal setup for this contract to return the number 42 whenever called would be the following:

1. Store the number 42 in memory;
    
2. Return the number 42 from memory.
    

In raw bytecode, we can write it like this:

1. Using `MSTORE` to store the number 42 in memory: `mstore(pointer, value)`
    

| BYTECODE | OPCODE | VALUE | COMMENT |
| --- | --- | --- | --- |
| 602a | 60 | PUSH1 | 2a | 42 is (0x)2a in hexadecimal |
| 6080 | 60 | PUSH1 | 80 | Memory pointer 0x80 |
| 52 | 52 | MSTORE |  | Store 42 at memory position 0x80 |

2. Using `RETURN` to return the number 42 from memory: `return(pointer, size)`
    

| BYTECODE | OPCODE | VALUE | COMMENT |
| --- | --- | --- | --- |
| 6020 | 60 | PUSH1 | 20 | 32 bytes in hexadecimal is (0x)20 |
| 6080 | 60 | PUSH1 | 80 | Memory pointer 0x80 |
| f3 | f3 | RETURN |  | Return 32 bytes from memory pointer 0x80 |

So here is our full runtime bytecode (smart contract code): `602a60805260206080f3`. 6 opcodes and a total of 10 bytes.

Now, we need to handle the creation code, so we can deploy this super tiny useless contract.

### Creation bytecode

Again, let's start with the absolute minimum we'll need to deploy our contract:

1. Store the runtime bytecode in memory;
    
2. Return the runtime bytecode.
    

In raw bytecode, we can write it like this:

1. Using `CODECOPY` to store the runtime bytecode in memory: `codecopy(value, position, destination)`
    

| BYTECODE | OPCODE | VALUE | COMMENT |
| --- | --- | --- | --- |
| 600a | 60 | PUSH1 | 0a | Push 10 bytes (runtime code size) |
| 600c | 60 | PUSH1 | 0c | Copy from memory position at index 12 (initialization code takes 12 bytes, runtime comes after that) |
| 6000 | 60 | PUSH1 | 00 | Paste to memory slot 0 |
| 39 | 39 | CODECOPY |  | Store runtime code at memory slot 0 |

2. Using `RETURN` to return the 10 bytes runtime bytecode from memory starting at offset 22: `return(pointer, size)`
    

| BYTECODE | OPCODE | VALUE | COMMENT |
| --- | --- | --- | --- |
| 600a | 60 | PUSH1 | 0a | 10 bytes in hexadecimal |
| 6000 | 60 | PUSH1 | 00 | Memory pointer 0 |
| f3 | f3 | RETURN |  | Return 10 bytes from memory pointer 0 |

Here is the full creation/deployment bytecode: `600a600c600039600a6000f3`.

And concatenating the two and adding `0x` in front, we get the following bytecode: `0x600a600c600039600a6000f3602a60805260206080f3`.

## Solution

Now that we have our raw bytecode ready, we can deploy the contract.

### In the browser's console

```javascript
const receipt = await web3.eth.sendTransaction({
  from: player,
  data: "0x600a600c600039600a6000f3602a60805260206080f3",
});
await contract.setSolver(receipt.contractAddress);
```

If you want to test it, you can use the following interface in Remix:

```solidity
interface IMeaningOfLife {
  function whatIsTheMeaningOfLife() external view returns (uint256);
}
```

It will return 42. Of course, you could give any name to this function, it will return 42 regardless. This is simply an interface.

### With Foundry using `forge`:

Let's prepare our script accordingly:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script, console2} from "forge-std/Script.sol";

interface IMagicNumber {
    function setSolver(address _solver) external;
}

interface IMeaningOfLife {
    function whatIsTheMeaningOfLife() external view returns (uint256);
}

contract PoC is Script {
    // Replace with your Magic Number instance
    IMagicNumber magicNumber =
        IMagicNumber(0x852e71dDefd3c88Dd7bF73ABcACAeDaABbce5ddE);

    function run() external {
        uint256 deployer = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployer);

        bytes
            memory bytecode = hex"600a600c600039600a6000f3602a60805260206080f3";
        address solver;

        assembly {
            solver := create(0, add(bytecode, 0x20), mload(bytecode))
        }
        uint256 meaningOfLife = IMeaningOfLife(solver).whatIsTheMeaningOfLife();
        require(meaningOfLife == 42, "Not 42");

        console2.log("Solver deployed at", solver);
        console2.log("What is the meaning of life?", meaningOfLife);

        magicNumber.setSolver(solver);

        vm.stopBroadcast();
    }
}
```

You can use the following command:

```bash
forge script script/18_MagicNumber.s.sol:PoC --rpc-url sepolia --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY
```

And that's it! We have successfully deployed the contract and solved the level.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1713517267075/bf46ac36-a981-4a74-9810-7a5808efc7a6.png align="left")

**🎉 Level completed 🎉**

## Takeaway

* How the EVM and opcodes work at a low level.
    
* From low level to high level: Bytecode &gt; Yul/Assembly &gt; Solidity.
    

## Reference

* EVM opcodes: [https://www.evm.codes/](https://www.evm.codes/)
    
* Simple bytecode contract: [https://www.youtube.com/watch?v=0qQUhsPafJc](https://www.youtube.com/watch?v=0qQUhsPafJc)
    

---

You can find all the codes, challenges, and their solutions on my GitHub: [https://github.com/Pedrojok01/Ethernaut-Solutions/](https://github.com/Pedrojok01/Ethernaut-Solutions/blob/main/solutions/01_Fallback.md)
