WTF Huff极简入门: 06. 控制流
我最近在重新学Huff,巩固一下细节,也写一个“Huff极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
所有代码和教程开源在github: github.com/AmazingAng/WTF-Huff
这一讲,我们将介绍Huff中的控制流,包括跳转标签和JUMPDEST
指令。
控制流
EVM底层主要是使用跳转指令JUMP
,JUMPI
,和JUMPDEST
进行代码的流程控制。如果你对它们不了解,建议阅读WTF EVM Opcodes教程第9讲。
为了方便开发者使用跳转指令,Huff提供了跳转标签,可以在宏或函数哪定义,由冒号后跟一个单词表示。注意,虽然看起来标签是由于缩进而作为代码块的作用域,但它们实际上只是字节码中的跳转目的地。如果标签下面存在操作,除非程序计数器被更改或执行由revert
、return
、stop
或selfdestruct
操作码中断,否则它们将被执行。
#define macro MAIN() = takes (0) returns (0) {
// 从 calldata 读取值
0x00 calldataload // [calldata @ 0x00]
0 eq
jump_one jumpi
// 如果到达此点,则revert
0x00 0x00 revert
// 跳转标签1
jump_one:
jump_two jump
// 如果到达此点,则revert
0x00 0x00 revert
// 跳转标签2
jump_two:
0x00 0x00 return
}
在合约中,Main
宏先会读取调用的calldata
,如果为0
,则先跳转到jump_one
,接着跳转到jump_two
;如果不为0
,则不会跳转,继续运行到revert
回滚交易。
分析合约字节码
我们可以使用huffc
命令获取上面合约的runtime code:
huffc src/06_ControlFlow.huff -r
打印出的bytecode为:
5f355f1461000b575f5ffd5b610013565f5ffd5b5f5ff3
转换成格式化的表格:
pc | op | opcode | stack |
---|---|---|---|
[00] | 5f | PUSH0 | 0x00 |
[01] | 35 | CALLDATALOAD | calldata |
[02] | 5f | PUSH0 | 0x00 calldata |
[03] | 14 | EQ | suc |
[04] | 61 000b | PUSH2 0x000b | 0x000b suc |
[07] | 57 | JUMPI | |
[08] | 5f | PUSH0 | 0x00 |
[09] | 5f | PUSH0 | 0x00 0x00 |
[0a] | fd | REVERT | |
[0b] | 5b | JUMPDEST | |
[0c] | 61 0013 | PUSH2 0x0013 | 0x0013 |
[0e] | 56 | JUMP | |
[10] | 5f | PUSH0 | 0x00 |
[11] | 5f | PUSH0 | 0x00 0x00 |
[12] | fd | REVERT | |
[13] | 5b | JUMPDEST | |
[14] | 5f | PUSH0 | 0x00 |
[15] | 5f | PUSH0 | 0x00 0x00 |
[16] | f3 | RETURN |
我们可以看到,这段字节码的功能:
CALLDATALOAD
从calldata
中读取值- 用
EQ
对比数据是否为0
。若calldata
为0
,suc = 1
,程序计数器(PC)通过JUMPI
跳转到0x0b
位置,也就时跳转标签jump_one
标记的地方。若calldata
不为0
,则会继续运行,直到在0xfd
的REVERT
指令回滚交易。 - 在
0x0b
继续运行,程序会遇到JUMP
指令,PC跳转到0x13
位置,也就是跳转标签jump_two
标记的地方。 - 在
0x13
继续运行,见到RETURN
,返回数据并结束交易。
可以看到Huff的编译器在这里并没有做到最优,因为合约中JUMPDEST
的位置可以用1
字节表示,但它却用了2
字节,0x000b
和0x0013
,浪费了gas。
总结
这一讲,我们介绍了Huff中的控制流。Huff提供了跳转标签,方便开发者使用JUMP
和JUMPI
进行流程控制。