跳到主要内容

WTF Huff极简入门: 09. Error

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

推特:@0xAA_Science

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

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


Huff允许你在合约自定义错误Error,这一讲我们将介绍它。

Error

Solidity中有三种抛出异常的方法errorrequireassert,他们都是基于EVMrevert指令。在Huff中,我们可以直接使用revert指令来抛出错误并返回错误信息。

定义错误

你可以在合约接口中定义错误:

/* 接口 */
#define function getError() view returns (uint256)
#define error CustomError(uint256)

使用错误

在方法中,你可以使用内置函数__ERROR()将错误选择器(error selector)推到堆栈上。

#define macro GET_ERROR() = takes (0) returns (0) {
__ERROR(PanicError) // [panic_error_selector, panic_code]
0x00 mstore // [panic_code]
0x04 mstore // []
0x24 0x00 revert
}

然后我们写一个Main宏作为合约的入口:

// 合约的主入口,判断调用的是哪个函数
#define macro MAIN() = takes (0) returns (0) {
// 通过selector判断要调用哪个函数
0x00 calldataload 0xE0 shr
dup1 __FUNC_SIG(GET_ERROR) eq get_error jumpi
// 如果没有匹配的函数,就revert
0x00 0x00 revert

get_error:
GET_ERROR()
}

分析合约字节码

我们可以使用huffc命令获取上面合约的runtime code:

huffc src/09_Error.huff -r

打印出的bytecode为:

5f3560e01c8063ee23e35814610013575f5ffd5b60697f110b3655000000000000000000000000000000000000000000000000000000005f5260045260245ffd

转换成格式化的表格(后半部分在stack中省略了一个用不上的selector):

pcopopcodestack
[00]5fPUSH00x00
[01]35CALLDATALOADcalldata
[02]60 e0PUSH1 0xE00xE0 calldata
[04]1cSHRselector
[05]80DUP1selector selector
[06]63 ee23e358PUSH4 0xEE23E3580xEE23E358 selector selector
[0b]14EQsuc selector
[0c]61 0013PUSH2 0x00130x0013 suc selector
[0f]57JUMPIselector
[10]5fPUSH00x00 selector
[11]5fPUSH00x00 0x00 selector
[12]fdREVERTselector
[13]5bJUMPDEST
[14]60 69PUSH1 0x690x69
[16]7f 0x110b...PUSH32 0x110b...0x69 0x110b...
[2d]5fPUSH00x00 0x69 0x110b...
[2e]52MSTORE0x110b...
[2f]60 04PUSH1 0x040x04 0x110b...
[31]52MSTORE
[32]60 24PUSH1 0x240x24
[34]5fPUSH00x00 0x24
[35]fdREVERT

[16]可以看到,目前内置函数__ERROR并没有被优化的很好,因为它会先计算出4字节的error selector(此处为0x110b3655),然后再将它转换为32字节的数据(右填充0,变为110b365500000000000000000000000000000000000000000000000000000000),最后使用PUSH32压入堆栈。但实际上,我们需要的只是前4字节,这样造成了gas浪费。

总结

这一讲,我们介绍了如何在Huff中自定义错误并使用它。Huff提供了内置函数__ERROR来获取错误选择器,但它没有被很好的优化。