Skip to main content

WTF Huff极简入门: 08. 事件

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

推特:@0xAA_Science

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

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


这一讲,我们将介绍Huff中的事件,和Solidity中的事件一样,它可以将数据存储在EVM的日志中。

事件

在Solidity中,我们常常使用event来定义和触发事件。当这些事件被触发时,它们会生成日志,将数据永久存储在区块链上。日志分为主题(topic)和数据(data)。第一个主题通常是事件签名的哈希值,后面的主题是由indexed修饰的事件参数。如果你对event不了解,请阅读WTF Solidity的相应章节

EVM中的LOG指令用于创建这些日志。指令LOG0LOG4的区别在于它们包含的主题数量。例如,LOG0没有主题,而LOG4有四个主题。如果你不了解它们,请阅读WTF EVM Opcodes第15讲

Huff中的事件

下面我们改造上一讲的Simple Store合约,在调用SET_VALUE()方法改变值的时候,会释放一个ValueChanged事件,将新值记录到EVM日志中。

首先你可以在Huff接口中定义合约的事件:

#define event ValueChanged(uint256 indexed)

接下来我们在SET_VALUE()方法中释放ValueChanged事件。首先,要确定我们要用哪个LOG指令来释放事件。因为我们事件只有一个被索引的数据,再加上事件哈希,就是2个主题,应使用log2,输入堆栈为[0, 0, sig, value]。接下来,我们只需要在方法中构造所需的堆栈,再在结尾使用log2输出日志即可。我们可以使用内置函数__EVENT_HASH()将事件哈希压入堆栈。

#define macro SET_VALUE() = takes (0) returns (0) {
0x04 calldataload // [value]
dup1 // [value, value]
[VALUE_LOCATION] // [ptr, value, value]
sstore // [value]
// 释放事件
__EVENT_HASH(ValueChanged) // [sig, value]
push0 push0 // [0, 0, sig, value]
log2 // []
stop // []
}

输出Solidity接口/ABI

我们可以使用huffc -g命令将Huff合约的接口转为Solidity合约接口/ABI:

huffc src/08_Event.huff -g

输出的接口将保存在和08_Event.huff相同的文件夹下,例如src/I08_Event.sol。可以看到,我们定义的事件已经被包含在接口中:

interface I08_Event {
event ValueChanged(uint256 indexed);
function getValue() external view returns (uint256);
function setValue(uint256) external;
}

分析合约字节码

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

huffc src/08_Events.huff -r

打印出的bytecode为:

5f3560e01c8063552410771461001e578063209652551461004a575f5ffd5b600435805f557fd9ce50fb8c432a73c4ed7e62e6128c95e62f29d3ee56042781a0368f192ccdb45f5fa2005b5f545f5260205ff3

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

pcopopcodestack
[00]5fPUSH00x00
[01]35CALLDATALOADcalldata
[02]60 e0PUSH1 0xE00xE0 calldata
[04]1cSHRselector
[05]80DUP1selector selector
[06]63 55241077PUSH4 0x552410770x55241077 selector selector
[0a]14EQsuc selector
[0b]61 001ePUSH2 0x001E0x001E suc selector
[0e]57JUMPIselector
[0f]80DUP1selector selector
[10]63 209652PUSH4 0x209652550x20965255 selector selector
[14]14EQsuc selector
[15]61 0049PUSH2 0x00490x0049 suc selector
[18]57JUMPIselector
[19]5fPUSH00x00
[1a]5fPUSH00x00 0x00
[1b]fdREVERT
[1c]5bJUMPDEST
[1d]60 04PUSH1 0x040x04
[1f]35CALLDATALOADcalldata@0x04
[20]5fPUSH00x00 calldata@0x04
[21]55SSTORE
[22]5bJUMPDEST
[23]60 04PUSH1 0x040x04
[25]35CALLDATALOADcalldata@0x04
[26]80DUP1calldata@0x04 calldata@0x04
[27]5fPUSH00x00 calldata@0x04 calldata@0x04
[28]55SSTOREcalldata@0x04
[29]7f d9ce50.PUSH32 0xd9ce50..0xd9ce50.. calldata@0x04
[46]5fPUSH00x00 0xd9ce50 calldata@0x04
[47]5fPUSH00x00 0x00 0xd9ce50 calldata@0x04
[48]a2LOG2
[49]00STOP
[4a]5bJUMPDEST
[4b]5fPUSH00x00
[4c]54SLOADvalue
[4d]5fPUSH00x00 value
[4e]52MSTORE
[4f]60 20PUSH1 0x200x20
[51]5fPUSH00x00 0x20
[52]f3RETURN

其中,[22]-[49]SET_VALUE()方法的字节码。我们可以看到,这段代码在准备好堆栈[0x00 0x00 0xd9ce50 calldata@0x04]之后,使用log2释放事件。

总结

这一讲,我们介绍了Huff中的事件,它与Solidity的事件一样,可以将数据记录在EVM日志中。Huff提供了内置方法__EVENT_HASH(),方便我们计算事件哈希并将它压入堆栈。