https://github.com/daltyboy11/more-evm-puzzles
这是难度加深的EVM谜题集 more-evm-puzzles

EXP:a**b
PC:程序计数器
00 36 CALLDATASIZE
01 34 CALLVALUE
02 0A EXP //CALLVALUE**CALLDATASIZE=0x40=64 //CALLVALUE=2
03 56 JUMP
04 FE INVALID
........................
3F FE INVALID
40 5B JUMPDEST
41 58 PC //41
42 36 CALLDATASIZE // 6
43 01 ADD //PC +CALLDATASIZE=47
44 56 JUMP
45 FE INVALID
46 FE INVALID
47 5B JUMPDEST
48 00 STOP

00 36 CALLDATASIZE
01 6000 PUSH1 00
03 6000 PUSH1 00
05 37 CALLDATACOPY //将calldata写入memory
06 36 CALLDATASIZE
07 6000 PUSH1 00
09 6000 PUSH1 00
0B F0 CREATE //将calldata写入合约
0C 6000 PUSH1 00
0E 80 DUP1
0F 80 DUP1
10 80 DUP1
11 80 DUP1
12 94 SWAP5
13 5A GAS
14 F1 CALL //call(gas,address,0,0,0,0,0)
15 3D RETURNDATASIZE //return 返回数据的大小=0A
16 600A PUSH1 0A //
18 14 EQ //
19 601F PUSH1 1F
1B 57 JUMPI
1C FE INVALID
1D FE INVALID
1E FE INVALID
1F 5B JUMPDEST
20 00 STOP
create操作码执行结束后就只剩下return返回的结果。
call是对合约的调用,也就是对运行时代码的执行
这道题是有点绕的:
call要返回10(0A)个字节个数据,下面的代码是调call要执行的
00 600A PUSH1 0A [0x0A]
02 6000 PUSH1 00 [0x00, 0x0A] (offset, size)
04 F3 RETURN []
上面的代码是create之后返回的,所以create return的内容是操作码 600A600F3
所以create合约的操作码:
00 64600A6000F3 PUSH5 600A6000F3 [0x600A6000F3]
06 6000 PUSH1 00 [0x0, 0x600A6000F3] (offset, value)
08 52 MSTORE []
00 600A PUSH1 0A [0x0A]
02 601B PUSH1 1B [0x1B, 0x0A] (offset, size)
04 F3 RETURN []
0x1B尽管我们将运行时字节码存储在0x0- 那是因为它看起来像是 memory: 0x000000000000000000000000000000000000000000000000000000600A6000F3。
我们必须跳过值开头的 27 个零才能到达字节码。我们本可以阻止使用PUSH32 0x600A6000F3000000000000000000000000000000000000000000000000000000,但这会显着增加构造字节码的大小。
这段构造字节码:0x64600A6000F3600052600A601BF3
用CODECOPY优化:
00 600A PUSH1 0A [0x0A]
02 6000 PUSH1 00 [0x00, 0x0A]
04 6000 PUSH1 00 [0x00, 0x00, 0x0A] (destOffset, offset, size)
06 39 CODECOPY []
07 600A PUSH1 0A [0x0A]
09 6000 PUSH1 00 [0x00, 0x0A] (offset, size)
0B F3 RETURN []
再优化,用DUP1替代重复push
00 600A PUSH1 0A [0x0A]
02 80 DUP1 [0x0A, 0x0A]
03 6000 PUSH1 00 [0x00, 0x0A, 0x0A]
05 80 DUP1 [0x00, 0x00, 0x0A, 0x0A] (destOffset, offset, size), size
06 39 CODECOPY [0x0A]
07 6000 PUSH1 00 [0x00, 0x0A] (offset, size)
09 F3 RETURN []
最终字节码:0x600A80600080396000F3

补充CALL和DELEGATECALL的区别,为什么CALL可以发value,DELEGATECALL不能发送value
CALL 指令是用于在当前合约和另一个地址之间进行消息传递的,可以发送以太币(value),并且可以选择在接收方合约执行的函数和参数。当执行 CALL 指令时,可以指定发送的以太币数量,这个值可以为0。
相比之下,DELEGATECALL 指令也是用于在当前合约和另一个地址之间进行消息传递的,但不同之处在于它不会创建新的执行环境,而是在当前合约的执行环境中执行被调用合约的代码。换句话说,DELEGATECALL 可以将当前合约的执行上下文传递给被调用的合约,并在其中执行被调用合约的代码。
因为 DELEGATECALL 不会创建新的执行环境,所以它不能像 CALL 一样发送以太币。如果在 DELEGATECALL 中发送以太币,那么这些以太币会被发送到被调用的合约地址,而不是当前合约的地址,这可能会导致意外的结果和安全问题。因此,DELEGATECALL 指令没有提供发送以太币的选项。
00 36 CALLDATASIZE
01 6000 PUSH1 00
03 6000 PUSH1 00
05 37 CALLDATACOPY
06 36 CALLDATASIZE
07 6000 PUSH1 00
09 6000 PUSH1 00
0B F0 CREATE
0C 6000 PUSH1 00
0E 80 DUP1
0F 80 DUP1
10 80 DUP1
11 93 SWAP4
12 5A GAS
13 F4 DELEGATECALL
14 6005 PUSH1 05
16 54 SLOAD
17 60AA PUSH1 AA
19 14 EQ
1A 601E PUSH1 1E
1C 57 JUMPI
1D FE INVALID
1E 5B JUMPDEST
1F 00 STOP
通过DELEGATECALL调用,将值存储在Storage上,SLOAD从storage再取出存入的值与AA相同,则DELEGATECALL实现的操作码:
00 60AA PUSH1 AA [0xAA]
02 6005 PUSH1 05 [0x05, 0x0A] (key, value)
04 55 SSTORE []
字节码为60AA600555 那create构造字节码:
00 6460AA600555 PUSH5 60AA600555 [0x60AA600555]
06 6000 PUSH1 00 [0x0, 0x60AA600555] (offset, value)
08 52 MSTORE []
00 600A PUSH1 0A [0x0A]
02 601B PUSH1 1B [0x1B, 0x0A] (offset, size)
04 F3 RETURN []
我们将运行时字节码写入内存,这样我们就可以RETURN在启动时使用它
构造字节码:0x6460AA600555600052600A601BF3
