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.
opcode
làbytecode
được biểu diễn dưới dạngassembly 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 codes
vàruntime codes
.creation codes
chí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ầncreation bytecode
không lưu trên EVM.runtime codes
sẽ đượ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 codes
với tối ra 10 opcodes trả về giá trị 42creation codes
để deployruntime codes
bê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ề
42
khô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!