Skip to main content

WTF Huff极简入门: 01. Hello Huff

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

推特:@0xAA_Science

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

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


这一讲,我们将介绍Huff语言,并使用Foundry运行模版合约。

Huff

大家可能很熟悉Solidity,但是几乎没听说过Huff。Huff是一个低级的、为Ethereum智能合约设计的编程语言,它允许开发者编写高度优化的EVM字节码。Huff的两大特点:

  1. 难学: Huff不像Solidity抽象了EVM的底层工作原理,而是让开发者直接操作EVM的堆栈、内存、和存储。

  2. 高效: Huff像是给EVM Opcodes套了一层壳,几乎是直接在字节码层面编写合约,gas优化到极致。

因此,你在学习Huff前需要熟悉Solidity和EVM的工作原理。推荐的先修课程:

  1. WTF Solidity
  2. WTF EVM Opcodes

Hello Huff

下面我们通过一个简单的合约SimpleStore.huff来学习Huff合约的结构。

/* 接口 */
#define function setValue(uint256) nonpayable returns ()
#define function getValue() view returns (uint256)

/* 存储槽位 */
#define constant VALUE_LOCATION = FREE_STORAGE_POINTER()

/* 方法 */
#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
}

// 合约的主入口,判断调用的是哪个函数
#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()
}

下面,我们分段来学习这个huff合约。

首先,你需要定义合约的接口,像Solidity的接口一样,需要使用#define关键字。

/* 接口 */
#define function setValue(uint256) nonpayable returns ()
#define function getValue() view returns (uint256)

接下来,你需要声明存储槽位(storage slot),就像在Solidity合约中声明状态变量一样。FREE_STORAGE_POINTER()指向合约中未使用的存储槽(free storage)。

/* 存储槽位 */
#define constant VALUE_LOCATION = FREE_STORAGE_POINTER()

最后一部分要写合约中的方法(函数),本合约有3个方法:

  1. SET_VALUE(): 改变VALUE_LOCATION存储的值。它先使用calldataload从calldata中读取变量的新值,然后使用sstore将新值存储到VALUE_LOCATION
  2. GET_VALUE(): 读取VALUE_LOCATION存储的值。它利用sloadVALUE_LOCATION存储的值推入堆栈,然后利用mstore将值存到内存,最后使用return返回。
  3. MAIN(): 主宏,定义了合约的主入口。当对合约进行外部调用时,会运行这段代码来确定应该调用哪个函数。他先用0x00 calldataload 0xE0 shr读取calldata中的函数选择器,然后查看它是否与SET_VALUE()GET_VALUE()匹配。如果匹配,就调用相应的函数;否则,回滚交易。
/* 方法 */
#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
}

// 合约的主入口,判断调用的是哪个函数
#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()
}

运行模版项目

下面,我们介绍如何使用Foundry的插件foundry-huff运行模版项目。

配置环境

首先,你需要在本地安装以下内容:

  • Git
    • 如果您可以运行git --version,则说明您已正确安装。
  • Foundry / Foundryup
    • 这将会安装forgecastanvil
    • 通过运行forge --version并获取类似forge 0.2.0 (92f8951 2022-08-06T00:09:32.96582Z)的输出,您可以检测是否已正确安装。
    • 要获取每个工具的最新版本,只需运行foundryup
  • Huff Compiler
    • 如果您可以运行huffc --version并获取类似huffc 0.3.0的输出,则说明您已正确安装。

快速开始

  1. 克隆[https://github.com/WTFAcademy/WTF-Huff]Huff模版仓库

运行:

git clone https://github.com/WTFAcademy/WTF-Huff
cd WTF-Huff
  1. 安装依赖

克隆并进入您的仓库后,您需要安装必要的依赖项。为此,只需运行:

forge install
  1. 构建 & 测试

要构建并测试您的合约,您可以运行:

forge build
forge test

  1. 使用huffc打印huff合约的字节码:
huffc src/SimpleStore.huff -b

控制台输会输出合约的字节码(creation code):

602e8060093d393df35f3560e01c8063552410771461001e5780632096525514610025575f5ffd5b6004355f55005b5f545f5260205ff3

如果想获取runtime code,可以使用huffc -r

有关如何使用Foundry的更多信息,请查看Foundry Github Repositoryfoundry-huff library repository

项目结构图

lib
├─ forge-std — https://github.com/foundry-rs/forge-std
├─ foundry-huff — https://github.com/huff-language/foundry-huff
scripts
├─ Deploy.s.sol — 部署脚本
src
├─ SimpleStore — Huff中的简单存储合约
test
└─ SimpleStore.t — SimpleStore测试

总结

这一讲,我们介绍了Huff语言,学习了Huff合约结构,并运行了一个模版项目。Huff是一个低级的、为以太坊智能合约设计的编程语言,学习它不仅可以让你写出更优化的合约,还可以让你深入理解EVM。