Skip to main content

WTF Huff极简入门: 06. 控制流

我最近在重新学Huff,巩固一下细节,也写一个“Huff极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。

推特:@0xAA_Science

社区:Discord微信群官网 wtf.academy

所有代码和教程开源在github: github.com/AmazingAng/WTF-Huff


这一讲,我们将介绍Huff中的控制流,包括跳转标签和JUMPDEST指令。

控制流

EVM底层主要是使用跳转指令JUMPJUMPI,和JUMPDEST进行代码的流程控制。如果你对它们不了解,建议阅读WTF EVM Opcodes教程第9讲

为了方便开发者使用跳转指令,Huff提供了跳转标签,可以在宏或函数哪定义,由冒号后跟一个单词表示。注意,虽然看起来标签是由于缩进而作为代码块的作用域,但它们实际上只是字节码中的跳转目的地。如果标签下面存在操作,除非程序计数器被更改或执行由revertreturnstopselfdestruct操作码中断,否则它们将被执行。

#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

转换成格式化的表格:

pcopopcodestack
[00]5fPUSH00x00
[01]35CALLDATALOADcalldata
[02]5fPUSH00x00 calldata
[03]14EQsuc
[04]61 000bPUSH2 0x000b0x000b suc
[07]57JUMPI
[08]5fPUSH00x00
[09]5fPUSH00x00 0x00
[0a]fdREVERT
[0b]5bJUMPDEST
[0c]61 0013PUSH2 0x00130x0013
[0e]56JUMP
[10]5fPUSH00x00
[11]5fPUSH00x00 0x00
[12]fdREVERT
[13]5bJUMPDEST
[14]5fPUSH00x00
[15]5fPUSH00x00 0x00
[16]f3RETURN

我们可以看到,这段字节码的功能:

  1. CALLDATALOADcalldata中读取值
  2. EQ对比数据是否为0。若calldata0suc = 1,程序计数器(PC)通过JUMPI跳转到0x0b位置,也就时跳转标签jump_one标记的地方。若calldata不为0,则会继续运行,直到在0xfdREVERT指令回滚交易。
  3. 0x0b继续运行,程序会遇到JUMP指令,PC跳转到0x13位置,也就是跳转标签jump_two标记的地方。
  4. 0x13继续运行,见到RETURN,返回数据并结束交易。

可以看到Huff的编译器在这里并没有做到最优,因为合约中JUMPDEST的位置可以用1字节表示,但它却用了2字节,0x000b0x0013,浪费了gas。

总结

这一讲,我们介绍了Huff中的控制流。Huff提供了跳转标签,方便开发者使用JUMPJUMPI进行流程控制。