Magic Number

To solve this level, you only need to provide the Ethernaut with a Solver, a contract that responds to whatIsTheMeaningOfLife() with the right number.

Easy right? Well… there’s a catch.

The solver’s code needs to be really tiny. Really reaaaaaallly tiny. Like freakin’ really really itty-bitty tiny: 10 opcodes at most.

Hint: Perhaps its time to leave the comfort of the Solidity compiler momentarily, and build this one by hand O_o. That’s right: Raw EVM bytecode.

Good luck!

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

contract MagicNum {

  address public solver;

  constructor() {}

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

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

Solution:

I recommand using this playground https://www.evm.codes/playground to debug what your are doing.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "forge-std/console.sol";

interface Solver {
  function whatIsTheMeaningOfLife() external returns(uint);
}

interface MagicNum {
  function solver() external returns(address);
  function setSolver(address _solver) external;
}

contract POC is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        address addr = vm.envAddress("INSTANCE_18");

        vm.startBroadcast(deployerPrivateKey);

        MagicNum magicNum = MagicNum(addr);

        address deployedAddress;

        assembly {
          let ptr := mload(0x40)
          let paddedBytes := shl(mul(8, 13), 0x69602a60005260206000f3600052600a6016f3)
          mstore(ptr, paddedBytes)
          deployedAddress := create(0, ptr, 19)
        }
        magicNum.setSolver(address(deployedAddress));

        Solver(address(magicNum.solver())).whatIsTheMeaningOfLife();

        vm.stopBroadcast();

    }
}

There are two parts in this byte code : 0x69{602a60005260206000f3}600052600a6016f3 . The part inside the curly braces {} correspond to the part that will be deployed by the contract constructor (which is the part outside the curly braces).

Each two digit byte corresponnd to an op code + its following parameters if expected.

So:

Then if we call the return value: