title | tags | ||||
---|---|---|---|---|---|
15. 引用 |
|
我最近在重新学Huff,巩固一下细节,也写一个“Huff极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
所有代码和教程开源在github: github.com/AmazingAng/WTF-Huff
这一讲,我们介绍如何在Huff合约中引入其他合约,复用代码。
Huff支持在合约中使用#include
关键字引入其他合约,方便复用代码。下面我们介绍引入的规则。
当一个Huff合约(例如C.huff
)引入了其他合约时,这些被引入的合约将按照它们被引入的顺序插入到当前的合约上方。
例如,如果你有一个C.huff
合约,它首先引入了A.huff
,然后引入了B.huff
,那么这与直接在一个合约中按顺序有A
、B
、C
是一样的。
这意味着合约内容的逻辑结构将是这样的:
内容来自A.huff
----------
内容来自B.huff
----------
内容来自C.huff
如果在引入的合约序列中,某个函数或常量被后续的合约覆盖,那么最后覆盖的版本会生效。
例如,如果A.huff
有一个函数MAIN()
,B.huff
也有一个函数MAIN()
,并且C.huff
再次有一个函数MAIN()
,那么最终生效的将是C.huff
中的MAIN()
函数。这与A.huff
和B.huff
中没有MAIN()
函数的结果是相同的。
在Huff中,你可以使用#include
指令来引入其他Huff合约。格式如下:
#include "合约路径/合约名.huff"
例如,要引入当前文件夹下名为ContractName.huff
的合约,你可以这样写:
#include "./ContractName.huff"
我们可以将WTF Huff第7讲中的07_Interface.huff合约分成两个合约,然后用引入的方式将两部分并在一起。
第一部分15_Imported.huff
包含接口、存储槽,和Main宏:
/* 接口 */
#define function setValue(uint256) nonpayable returns ()
#define function getValue() view returns (uint256)
/* 存储槽位 */
#define constant VALUE_LOCATION = FREE_STORAGE_POINTER()
// 合约的主入口,判断调用的是哪个函数
#define macro MAIN() = takes (0) returns (0) {
// 通过selector判断要调用哪个函数
0x00 calldataload 0xE0 shr
dup1 __FUNC_SIG(setValue) eq set jumpi
dup1 __FUNC_SIG(getValue) eq get jumpi
// 如果没有匹配的函数,就revert
0x00 0x00 revert
set:
SET_VALUE()
get:
GET_VALUE()
}
第二部分包含SET_VALUE()
和GET_VALUE
两个方法,并且引入了第一部分:
#include "./15_Imported.huff"
/* 方法 */
#define macro SET_VALUE() = takes (0) returns (0) {
0x04 calldataload // [value]
[VALUE_LOCATION] // [ptr, value]
sstore // []
stop // []
}
#define macro GET_VALUE() = takes (0) returns (0) {
// 从存储中加载值
[VALUE_LOCATION] // [ptr]
sload // [value]
// 将值存入内存
0x00 mstore
// 返回值
0x20 0x00 return
}
我们可以使用huffc
命令获取上面合约的runtime code:
huffc src/15_Import.huff -r
打印出的bytecode为:
5f3560e01c8063552410771461001e5780632096525514610025575f5ffd5b6004355f55005b5f545f5260205ff3
这与第7讲的合约字节码一致,说明我们成功引入了第一部分的合约。
理解Huff的引入规则是非常重要的,因为它决定了代码的结构以及哪些函数/常量会在合约中生效。在编写或分析Huff合约时,我们需要考虑到这些规则。