Hi mọi người!
Đã rất lâu rồi chúng ta mới có dịp quay lại với chuỗi bài Ethernaut, giờ đây đã có thêm 8 bài mới. Chúng ta hãy lần lượt đi tìm lời giải cho các bài toán mới nhé!
18. Magic Number
Nhiệm vụ: Viết một contract siêu ngắn tối đa 10 opcodes để trả về con số được mệnh danh là The Meaning of Life - con số 42.
Về con số 42, bạn có thể đọc thêm tại đây
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract MagicNum {
  address public solver;
  constructor() public {}
  function setSolver(address _solver) public {
    solver = _solver;
  }
  /*
    ____________/\\\_______/\\\\\\\\\_____
     __________/\\\\\_____/\\\///////\\\___
      ________/\\\/\\\____\///______\//\\\__
       ______/\\\/\/\\\______________/\\\/___
        ____/\\\/__\/\\\___________/\\\//_____
         __/\\\\\\\\\\\\\\\\_____/\\\//________
          _\///////////\\\//____/\\\/___________
           ___________\/\\\_____/\\\\\\\\\\\\\\\_
            ___________\///_____\///////////////__
  */
}Phân tích
- Đây là một bài tập rất khó nhằn nếu ta chỉ làm việc ở tầng high-level với solidity code.
- Với giới hạn tối đa 10 opcodes, không có cách nào khác chúng ta sẽ phải viết contract bằngraw-bytecode.
Về bytecode và opcode
- Khi ta viết contract thì viết bằng Solidity, nhưng khi compile ra thì sẽ ra bytecode, tức dạng0x6080604052348015600f57600080fd5b5....
- Smart contract chạy trên Ethereum Virtual Machine (EVM). EVM sẽ đọc bytecode - tức contract đã được compile.
- opcodelà- bytecodeđược biểu diễn dưới dạng- assembly codeđể có thể dễ đọc & biểu diễn logic hơn. Mỗi opcode là một byte - tức 2 ký tự hexa. Ta có thể tham khảo bảng opcodes tại đây
- Contract khi được deploy sẽ gồm có 2 phần: creation codesvàruntime codes.- creation codeschính là phần constructor, nó sẽ được thực hiện để khởi tạo contract và trả về địa chỉ của contract. Phần- creation bytecodekhông lưu trên EVM.
- runtime codessẽ được copy vào memory và lưu trữ trên EVM tại địa chỉ đã được deploy bên trên.
 

Vậy tóm lại ta sẽ cần một chuỗi gồm:
- runtime codesvới tối ra 10 opcodes trả về giá trị 42
- creation codesđể deploy- runtime codesbên trên
EVM = Stack Machine
EVM là một stack machine, các phép toán được đưa vào dưới dạng hậu tố và thực hiện theo quy tắc Last In First Out của stack.
Vì thế khi tạo chuỗi opcodes ta cũng sẽ đưa nó vào chuỗi theo cấu trúc stack.
Solution
Tạo runtime bytecode
Ta cần trả về giá trị 42 với không quá 10 opcodes.
| # | Stack | OPCODE | Ý nghĩa | bytecode | 
|---|---|---|---|---|
| 00 | <empty> | PUSH1(60) 2a | push 2a (hex) = 42 (dec) vào stack | 602a | 
| 02 | 2a | PUSH1(60) 00 | push 00 vào stack | 6000 | 
| 05 | 00, 2a | MSTORE(52) | mstore(0, 2a), lưu trữ 2a = 42 vào vị trí 0 trong memory | 52 | 
| 06 | <empty> | PUSH1(60) 20 | push 20 (hex) = 32 (dec) vào stack (vì mỗi slot nhớ là 32 bytes) | 6020 | 
| 08 | 20 | PUSH1(60) 00 | push 00 vào stack | 6000 | 
| 10 | 00, 20 | RETURN(f3) | return(0, 20), trả về 32 bytes ở vị trí 0 | f3 | 
Ta được chuỗi runtime bytecodes là 602a60005260206000f3 có độ dài 10 opcodes.
Tạo creation bytecode
- Trong mỗi slot nhớ có 32 bytes, mà runtime codes của ta có độ dài 10 bytes, do vậy ta sẽ trả về 10 bytes kể từ vị trí 22 trong memory cho EVM lưu trữ.
| Stack | OPCODE | Ý nghĩa | bytecode | 
|---|---|---|---|
| <empty> | PUSH10(69) 602a60005260206000f3 | push 10 bytes của runtime codes vào stack | 69602a60005260206000f3 | 
| 602a60005260206000f3 | PUSH1(60) 00 | push 0 vào trong stack | 6000 | 
| 00, 602a60005260206000f3 | MSTORE(52) | lưu trữ runtime codes vào vị trí 00 trong memory | 52 | 
| <empty> | PUSH1(60) 0a | push 0a (hex) = 10 (dec) vào stack | 600a | 
| 0a | PUSH1(60) 16 | push 16 (hex) = 22 (dec) vào stack | 6016 | 
| 16, 0a | RETURN(f3) | trả về 10 bytes từ vị trí 22 | f3 | 
Ta được chuỗi creation codes là 69602a60005260206000f3600052600a6016f3
Deploy & Set Solver
- Trên chrome console chạy lệnh để deploy contract:
sendTransaction({
  from: player,
  data: `69602a60005260206000f3600052600a6016f3`,
});transction hoàn thành sẽ tạo ra một contract mới, trong trường hợp của mình là 0x0F9F67f93D846fb2406De62195A8A50bd901d548
- kiểm tra xem có đúng contract trả về 42không
parseInt(await web3.eth.call({ to: '0x0F9F67f93D846fb2406De62195A8A50bd901d548', data: '0x' }));
> 42- gọi hàm setSolverđể set địa chỉ solver là contract ta mới tạo ra
contract.setSolver("0x0F9F67f93D846fb2406De62195A8A50bd901d548");- Submit & all done!
