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进行流程控制。