我最近在重新学以太坊opcodes,也写一个“WTF EVM Opcodes极简入门”,供小白们使用。
所有代码和教程开源在github: github.com/WTFAcademy/WTF-Opcodes
这一讲,我们将综合应用之前所学的内容,用PUSH0
指令优化EIP-1167最小代理合约(Minimal Proxy Contract),减少合约长度并降低gas。

最小代理合约
当人们需要反复部署同一个合约时,比如每个用户都需要部署一遍抽象账户合约,代理合约是最好的解决办法。在这个模式下,复杂的逻辑合约可以被重复利用,用户只需要部署一个简单的代理合约,从而降低gas成本。

由于代理合约会被用户重复部署,因此我们必须要优化它。在WTF Solidity教程第46讲我们用Solidity写了一个代理合约,在没有经过任何优化的情况下,它的合约bytecode
有573
字节。
那么经过优化后的代理合约有多大呢?EIP-1677
提出了最小代理合约,完全用字节码写成,合约长度仅有55
字节,能节省超过90%的gas!😱,手撸字节码就是这么强大。
我第一次见到这一串字节码就像见到了天书,不知所措,相信现在的你也能感同身受。但是,在我们学习完之前的章节之后,不单要看懂它,还要优化它!优化后的代理合约:
- 使用了Shanghai升级后引入的新opcode:
PUSH0
。 - 合约仅需
54
字节,部署时节省200
gas,运行时节省5
gas。
我们基于优化后的代理合约,提出一个新的EIP-7511: 使用PUSH0
的最小代理合约。
从头搭建最小代理合约
代理合约中最重要的操作码是什么?对,是DELEGATECALL,它可以将用户对代理合约的调用委托给逻辑合约。

因此,最小代理合约的核心元素包括:
- 使用
CALLDATACOPY
复制交易的calldata。 - 使用
DELEGATECALL
将calldata转发到逻辑合约。 - 将
DELEGATECALL
返回的数据复制到内存。 - 根据
DELEGATECALL
是否成功来返回结果或回滚交易。
第一步:复制Calldata
为了复制calldata,我们需要为CALLDATACOPY
操作码提供参数,这些参数是[0, 0, cds]
,其中cds
代表calldata的大小。
pc | op | opcode | stack |
---|---|---|---|
[00] | 36 | CALLDATASIZE | cds |
[01] | 5f | PUSH0 | 0 cds |
[02] | 5f | PUSH0 | 0 0 cds |
[03] | 37 | CALLDATACOPY |
第二步:Delegatecall
为了将calldata转发到委托调用,我们要在堆栈中准备DELEGATECALL
操作码所需的参数,这些参数分别是[gas 0xbebe. 0 cds 0 0]
,其中gas
代表剩余的gas,0xbebe.
代表逻辑合约的地址(20字节,实际使用时需要替换成你的逻辑合约地址),suc
代表delegatecall是否成功。
pc | op | opcode | stack |
---|---|---|---|
[04] | 5f | PUSH0 | 0 |
[05] | 5f | PUSH0 | 0 0 |
[06] | 36 | CALLDATASIZE | cds 0 0 |
[07] | 5f | PUSH0 | 0 cds 0 0 |
[08] | 73bebe. | PUSH20 0xbebe. | 0xbebe. 0 cds 0 0 |
[1d] | 5a | GAS | gas 0xbebe. 0 cds 0 0 |
[1e] | f4 | DELEGATECALL | suc |
第三步:将DELEGATECALL
返回的数据复制到内存
进行完DELEGATECALL
之后,我们就可以处理返回的数据了。这一步,我们要使用``RETURNDATACOPY操作码将返回的数据复制到内存,它的参数是
[0, 0, rds],其中
rds代表从
DELEGATECALL`返回的数据长度。
pc | op | opcode | stack |
---|---|---|---|
[1f] | 3d | RETURNDATASIZE | rds suc |
[20] | 5f | PUSH0 | 0 rds suc |
[21] | 5f | PUSH0 | 0 0 rds suc |
[22] | 3e | RETURNDATACOPY | suc |
第四步:返回数据或回滚交易
最后,我们需要根据DELEGATECALL
是否成功(suc
)选择返回数据或回滚交易。因为EVM操作码中没有if/else
,我们需要使用JUMPI
和JUMPDEST
。JUMPI
的参数是[0x2a, suc]
,其中0x2a
是条件跳转的目的地。
我们还需要在JUMPI
之前为REVERT
和RETURN
操作码准备参数[0, rds]
,否则我们就要在返回/回滚条件下重复准备两次。另外,我们不能避免使用SWAP
操作交换rds
和suc
在堆栈中的位置,因为我们只能在DELEGATECALL
之后获得返回数据的长度rds
。
pc | op | opcode | stack |
---|---|---|---|
[23] | 5f | PUSH0 | 0 suc |
[24] | 3d | RETURNDATASIZE | rds 0 suc |
[25] | 91 | SWAP2 | suc 0 rds |
[26] | 602a | PUSH1 0x2a | 0x2a suc 0 rds |
[27] | 57 | JUMPI | 0 rds |
[29] | fd | REVERT | |
[2a] | 5b | JUMPDEST | 0 rds |
[2b] | f3 | RETURN |
希望前面的步骤你都跟上了,如果没跟上的话,可以反复看几遍。其实逻辑很简单,就是为核心的指令准备参数,然后调用它。
最后,我们就得到了带有PUSH0
的最小代理合约的运行时代码:
优化后的代码长度是44
字节,比之前的最小代理合约少了1
字节。此外,它用PUSH0
替换了RETURNDATASIZE
和DUP
操作,节省了gas并提高了代码的可读性。总结一下,优化后的最小代理合约在部署时节省200
gas,在运行时节省5
gas,同时保持了与之前版本相同的功能。
你可以在evm.codes中测试下它。

部署最小代理合约
最小创建时代码
优化后的最小代理合约的创建时代码为:
总共53
字节,其中前9
字节为initcode
,你可以结合第21讲,思考它为什么长这样:
剩余部分是我们刚才建立的代理合约的运行时代码。
部署合约
我们可以用下面的Solidity
合约来部署优化后的最小代理合约:
总结
这一讲,我们结合了前面24讲学习的内容,从头构建了最小代理合约,并且使用PUSH0
优化了它。优化后最小代理合约的代码长度减少了1
字节,在部署时节省200
gas,在运行时生生5
gas,同时保持了与之前版本相同的功能。
相信你在学习完本教程后,对EVM,字节码,和最小代理合约的认识会有质的飞跃!如果你对本教程有疑问或建议,欢迎推特联系我们或者在GitHub上提issue。另外也欢迎你对EIP-7511的草稿给出改进建议,它是这门课程的结晶!
延伸阅读
-
Peter Murray (@yarrumretep), Nate Welch (@flygoing), Joe Messerman (@JAMesserman), "ERC-1167: Minimal Proxy Contract," Ethereum Improvement Proposals, no. 1167, June 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1167.
-
Alex Beregszaszi (@axic), Hugo De la cruz (@hugo-dc), Paweł Bylica (@chfast), "EIP-3855: PUSH0 instruction," Ethereum Improvement Proposals, no. 3855, February 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3855.
-
Martin Abbatemarco, Deep dive into the Minimal Proxy contract, https://blog.openzeppelin.com/deep-dive-into-the-minimal-proxy-contract
-
0age, The More-Minimal Proxy, https://medium.com/@0age/the-more-minimal-proxy-5756ae08ee48