Huff

1. Hello Huff
2. 存储
3. 常量
4. 宏
5. Main宏
6. 控制流
7. 接口
8. 事件
9. Error
10. Constructor
6. 控制流
控制流

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

推特:@0xAA_Science

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

所有代码和教程开源在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]
    0x00 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. CALLDATALOAD从calldata中读取值
  2. 用EQ对比数据是否为0。若calldata为0,suc = 1,程序计数器(PC)通过JUMPI跳转到0x0b位置,也就时跳转标签jump_one标记的地方。若calldata不为0,则会继续运行,直到在0xfd的REVERT指令回滚交易。
  3. 在0x0b继续运行,程序会遇到JUMP指令,PC跳转到0x13位置,也就是跳转标签jump_two标记的地方。
  4. 在0x13继续运行,见到RETURN,返回数据并结束交易。

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

总结

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

PreviousNext