我最近在重新学Huff,巩固一下细节,也写一个“Huff极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
所有代码和教程开源在github: github.com/AmazingAng/WTF-Huff
Huff允许你在合约自定义错误Error,这一讲我们将介绍它。
Error
Solidity中有三种抛出异常的方法error,require和assert,他们都是基于EVM的revert指令。在Huff中,我们可以直接使用revert指令来抛出错误并返回错误信息。
定义错误
你可以在合约接口中定义错误:
使用错误
在方法中,你可以使用内置函数__ERROR()将错误选择器(error selector)推到堆栈上。
然后我们写一个Main宏作为合约的入口:
分析合约字节码
我们可以使用huffc命令获取上面合约的runtime code:
打印出的bytecode为:
转换成格式化的表格(后半部分在stack中省略了一个用不上的selector):
| pc | op | opcode | stack | 
|---|---|---|---|
| [00] | 5f | PUSH0 | 0x00 | 
| [01] | 35 | CALLDATALOAD | calldata | 
| [02] | 60 e0 | PUSH1 0xE0 | 0xE0 calldata | 
| [04] | 1c | SHR | selector | 
| [05] | 80 | DUP1 | selector selector | 
| [06] | 63 ee23e358 | PUSH4 0xEE23E358 | 0xEE23E358 selector selector | 
| [0b] | 14 | EQ | suc selector | 
| [0c] | 61 0013 | PUSH2 0x0013 | 0x0013 suc selector | 
| [0f] | 57 | JUMPI | selector | 
| [10] | 5f | PUSH0 | 0x00 selector | 
| [11] | 5f | PUSH0 | 0x00 0x00 selector | 
| [12] | fd | REVERT | selector | 
| [13] | 5b | JUMPDEST | |
| [14] | 60 69 | PUSH1 0x69 | 0x69 | 
| [16] | 7f 0x110b... | PUSH32 0x110b... | 0x69 0x110b... | 
| [2d] | 5f | PUSH0 | 0x00 0x69 0x110b... | 
| [2e] | 52 | MSTORE | 0x110b... | 
| [2f] | 60 04 | PUSH1 0x04 | 0x04 0x110b... | 
| [31] | 52 | MSTORE | |
| [32] | 60 24 | PUSH1 0x24 | 0x24 | 
| [34] | 5f | PUSH0 | 0x00 0x24 | 
| [35] | fd | REVERT | 
从[16]可以看到,目前内置函数__ERROR并没有被优化的很好,因为它会先计算出4字节的error selector(此处为0x110b3655),然后再将它转换为32字节的数据(右填充0,变为110b365500000000000000000000000000000000000000000000000000000000),最后使用PUSH32压入堆栈。但实际上,我们需要的只是前4字节,这样造成了gas浪费。
总结
这一讲,我们介绍了如何在Huff中自定义错误并使用它。Huff提供了内置函数__ERROR来获取错误选择器,但它没有被很好的优化。