前言
前段时间在 Etherscan 上查看交易时,发现一个很有趣的地址:0x00000000a03396F6F864B496713f2623b6756Be2。这个地址是以 0 x 00000000 开头,抱着探索未知的心理对该地址进行了简单的搜索,发现该地址并不简单,它不仅构造独特同时,还能节省 Gas 费。
什么是 Gas
在讲这个地址之前,我们首先需要理解什么是 Gas。
Gas 是用于测量在以太坊区块链上执行特定操作所需的计算工作量的单位,即以太坊网络上的计算单位。我们可以简单的把 Gas 理解为汽油,其保证了以太坊网络的正常运行。在以太坊区块链上,执行写入操作都需要支付一定的 Gas 费用。
在以太坊中,货币是以太币 (ether), 1 ether = 1 x 10 18 Wei。在以太坊区块链上进行操作,如发送代币、调用合约等都要支付相应 Gas,且以 Wei 作为单位来计算。Wei 是 ETH 原生最小的单位,ETH 消耗的 Gas 单位是 Gwei, 1 Gwei = 1 x 10 9 Wei。
2.1 Gas Price
Gas Price 是以太坊内消耗 1 个 Gas 对应多少的 Gwei,当然交易发送方可以自定义愿意支付的每单位 Gas 价格。比如交易发送方的一笔交易需要耗费 10 Gas,交易发送者愿意支付 3 Wei/Gas,交易的成本总价就是 30 Wei。在我们使用的钱包(如 MetaMask)里都会有调整 Gas 费的高级选项,Gas Price 越高,打包的优先级就越高。
2.2 Gas Limit
Gas Limit 是消耗 Gas 的限制单位,即交易发送方在完成每笔交易时最多能使用的用于执行交易的 Gas 量。如果没有 Gas Limit 做限制,可能会导致交易发送方的账户余额因错误操作而消耗完,Gas Limit 是安全机制,防止把账户中所有 ETH 消耗掉。
2.3 固有成本 Gas
发送一笔交易的成本包括两部分:
1、固有成本
2、执行成本
执行成本顾名思义和执行有关,执行一笔交易的操作越多,它所需要的使用的 EVM 资源就越多,执行成本就越高。
而固有成本由交易的负载决定:
1、创建智能合约的负载是创建智能合约的 EVM 代码
2、调用智能合约函数的负载是执行消息时输入的数据
3、两个账户之间转账的负载为空
我们从以太坊黄皮书附录 G 中可得知创建合约和执行交易的相关成本 Gas。
我们假设 $N{zeros}$ 代表的是交易负载中字节为 0 的字节总数,$N{nonzeros}$ 代表交易负载中字节不为 0 的字节总数,根据黄皮书 6.2 章节关于执行的描述,可以得到固有成本。
以太坊地址中零开头的地址
由于 Gas 费用现在比较昂贵,所以优化 Gas 变得很重要,优化 Gas 费用的方法有很多,这里我们不一一列举,本文只以以太坊地址中 0 字节降低 Gas 费来讨论,这是从 EVM 层面来优化 Gas。通过使用比平时更多的 0 字节(即汉明重量)的地址,并且在某些情况下,地址开头有更多的 0 字节,我们可以在许多类型的交易中节省汽油。
汉明重量是一串符号中非零符号的个数。全零符号的汉明重量为 0 , 1101 的汉明重量为 3 。
在以太坊黄皮书附录 G 中可以看到,使用 0 字节时需要 4 Gas,而使用非 0 字节时需要 16 Gas:
每次使用 0 字节代替非 0 字节时,就可以节省 16 个 Gas,因此,每次在 msg.data 中用一个 0 字节代替一个非 0 字节,都将给我们节省 12 个 Gas。在以太坊中,地址是由 0 x 开头的 40 个随机字符的 16 进制字符串(20 个字节),当以 16 进制形式查看字节串时,由于每对数字(每个字符代表 16 个可能的数字之一)构成一个字节(16 2 = 2 8 = 256 位),所以单个 16 进制的 0 ,或相邻的 0 分布在两个不同的字节中,不会减少字节串的汉明重量,所以只有连续 2 个 16 进制的 0 才会减少汉明重量。0 字节的排序对所产生的 Gas 优化也没有影响(如地址0x00a0009e638D25EFE5a894f6a36F42734477dECa中有 2 个 0 字节)。
在 ERC 20 的 transfer() 函数中,msg.sender 的汉明重量没有区别,而作为 transfer() 函数的一部分传入 msg.data 的 _to 地址参数就有区别。
使用 OpenZeppelin 的 StandardToken 作为参考实例。向一个没有 0 字节的地址(如:0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)进行标准转账需要花费 35039 Gas,然而向一个有 4 个 0 字节的地址(如:0x00000000b011402777E34EFf44905BE85e3CdD01,即地址中有 4 对 0 )进行转账只需要 34991 Gas,他们相差了 $ 35039 - 34991 = 48 \, Gas$。而我们前面说到每次使用 0 字节代替非 0 字节时,可以节省 12 个 Gas(而在早先的以太坊版本中,甚至能节省高达 64 Gas),这里的 48 Gas 可以表示成 $ 4 \times 12 = 48 \, Gas$,和我们从黄皮书中得到的期望值一样。
而 2 个 0 字节的地址0x00a0009e638D25EFE5a894f6a36F42734477dECa和上面 2 个差值分别都是 24 Gas,说明也是符合期望的。
但是如果我们地址尾部存在 0 字节时,又容易被短地址进行攻击,所以以 0 x 00 开头的具有多个 0 字节的地址就成为了更好的选择。如果地址至少有 4 个前导 0 字节(即 8 个前导 0 的 16 进制编码格式),那么每个地址将只需要占用 16 个字节,这样两个地址就可以装入一个 32 字节包了。这样的优化不仅在于使用了 $G_{txdatazero}$ 带来的 Gas 优化,而且由于不用从调用数据中读写更多的字节,我们还能省下更多的 Gas,所以向这样的地址转账时,不管该地址是合约地址还是普通账户地址,都能省下 5% 左右的费用。
由去中心化交易所聚合器 1inch.exchange 开发的 CHI GasToken 就是采用这样的原理,也有不少的普通账户地址使用前导 0 作为自己的地址。
如何得到更多前导 0 开头的地址呢,这里就和大家看到的以太坊靓号生成是一样的,这里就不提供生成方法了(因为之前提供靓号生成服务的 Profanity 出现了漏洞)。当然了,想要得到的开头 0 越多,难度就越大,需要的时间也就会越久。
92.47% 的机会在地址上找到 0 个 0 字节。
7.25% 的机会找到 1 个 0 字节。
0.27% 的机会找到 2 个 0 字节的机会。
0.00635% 的机会找到 3 个 0 字节的机会。
0.00000106% 的机会找到 4 个 0 字节的机会。
后记
优化 Gas 的方法有很多,这里我们就以 EVM 底层视角研究了节省 Gas 的方法,通过研究我们可以得到一个结论:以太坊地址里面的 00 越多(这里需要注意是成对出现的 0 ,至于原因我们在第 4 节中已经提到),不管这个地址是合约地址还是普通地址,不管是转入地址还是转出地址,都能够节省不少的 Gas。