有意思的 gas 优化技术
以下学习和理解是人写的,避免AI格式的和AI说明,以便于学习和回顾。
这边提到了一个 MEV/套利机器人常用的一个 Gas 优化技术,核心主要是两个知识点
- 存储位打包(bit packing)
- calldata 压缩
首先,我们先要理解在以太坊上 gas 费是如何计算的,每笔交易的费用大致是
总费用 = 消耗的 gas 数量 × 每单位 gas 的价格
通常我们在交易 bot 中填写的就是消耗的 gas 数量,因为消耗的 gas 数量是可以由交易者所决定的,而每单位 gas 的价格通常是由当前网络的拥堵程度决定的。
所以显然总费用和消耗的 gas 数量存在线性关系,如果可以优化消耗的 gas 数量,那么就可以优化总费用。
然后就是要理解,gas 花费在哪里了?
- 读写操作(SLOAD / SSTORE),这是大头
- 交易本身携带的数据,也就是发送给合约的每一个字节我们都需要进行付费
(付费规则:一个非零字节是 16 gas,一个零字节 4 gas)
正好这里做一下区别 gas 和 gwei
费用(ETH)= gas 数量(gas) × gas 单价(gwei/gas)
Bit Packing(位打包)
EVM的存储是按照32字节(256bit)一个槽(slot)来组织的。关键之处在于,不过你存的数据多小,实际读写都是按照“槽”这个单位去进行计费的。
假设你的合约要存三个变量:
// 写法 A:没打包,占 3 个槽
uint256 price; // 槽 0(32 字节,占满)
uint32 timestamp; // 槽 1(只用 4 字节,浪费 28 字节)
uint64 amount; // 槽 2(只用 8 字节,浪费 24 字节)
// 写法 B:打包后,只占 2 个槽
uint32 timestamp; // ┐
uint64 amount; // ┘ 两个加起来 12 字节,挤进同一个槽 0
uint256 price; // 槽 1
请注意写法A和写法B的变量顺序也有所不同,这是由于 Solidity 有个规则,相邻声明的小类型变量,如果加起来不超过 32 字节,会自动合并进同一个槽。
所以在写法B中,我们是把 timestamp 和 amout 塞进了一个槽中
- 同时更新这两个变量,我们只需要进行一次写操作(sstore)而不是两次
- 读(sload)也只需要一次操作
一次 SSTORE 动辄几千到几万gas,合并一次就是实打实的省钱。这个操作或者说方法就是在省钱。
bit packing:就是把小数据拼进 256 bit。
注意:变量声明必须要相邻才能打包;同时要刚好够用的小类型(uint32、uint64等),而不是全部无脑去用 uint256。
Calldata 压缩
省传数据的钱,标准的合约调用(比如说通过 uniswap router 做 swap)使用 ABI 编码,规则是每个参数都补齐到32字节。
比方说,要传一个金额 1000,
我们对其进行编码
1000 = 3 × 256 + 14 × 16 + 8 = 3 e 8 → 0x3e8
00000000000000000000000000000000000000000000000000000000000003e8
前面一长串零。虽然零字节便宜(4 gas/字节),但也不是免费的,而且一笔多跳 swap 会带很多参数,零填充累积起来相当可观。
所以很多套利机器人的做法是:不用标准 ABI 编码,自己设计一套紧凑的自定义编码,由自己写的合约来解析。比如:
- 金额用 8 字节而不是 32 字节
- 交易池地址不传完整 20 字节 ,而是传一个 1–2 字节的索引(合约里预先存好常用池子的地址表)
- 多个小参数直接首尾相接,不做任何填充
这样一笔 2-hop 交易的 calldata 可能比别人一笔普通 1-hop 交易还短。数据量小了,这部分费用就直接线性下降。
总结:
套利/MEV 就是要从各种地方去进行省钱呀