来源:https://jaechung.net/writings/starting-a-quant-trading-firm/#:~:text=adapt%20to%20them.-,Gas%20optimizations,-The%20total%20gas

以下学习和理解是人写的,避免AI格式的和AI说明,以便于学习和回顾。

这边提到了一个 MEV/套利机器人常用的一个 Gas 优化技术,核心主要是两个知识点

  1. 存储位打包(bit packing)
  2. calldata 压缩

首先,我们先要理解在以太坊上 gas 费是如何计算的,每笔交易的费用大致是

总费用 = 消耗的 gas 数量 × 每单位 gas 的价格

通常我们在交易 bot 中填写的就是消耗的 gas 数量,因为消耗的 gas 数量是可以由交易者所决定的,而每单位 gas 的价格通常是由当前网络的拥堵程度决定的。

所以显然总费用和消耗的 gas 数量存在线性关系,如果可以优化消耗的 gas 数量,那么就可以优化总费用。

然后就是要理解,gas 花费在哪里了?

  1. 读写操作(SLOAD / SSTORE),这是大头
  2. 交易本身携带的数据,也就是发送给合约的每一个字节我们都需要进行付费

(付费规则:一个非零字节是 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 就是要从各种地方去进行省钱呀