本文详细介绍了EIP-1167标准,即最小代理合约,用于廉价创建代理克隆。文章深入解析了其工作原理、字节码结构、初始化函数及实际应用示例,帮助开发者理解如何高效部署相似合约。
图片来自 <https://pixabay.com/photos/stormtrooper-star-wars-lego-storm-2899993/>
EIP-1167,也被称为最小代理合约,是一种常用的 solidity 模式,用于廉价创建代理克隆。
如果用例需要重复部署相同(或非常相似)的合约,这是一种更为节省 gas 的方式。
例如, gnosis safe 在创建新安全合约时使用克隆模式。当你与 gnosis safe 互动时,实际上你是在与它的一个克隆进行互动。
克隆合约像是一个无法升级的代理。由于代理合约相对于实现合约是非常小的,因此部署成本较低。
与代理模式类似,克隆将所有调用委派给实现合约,但将状态保留在自己的存储中。
不同于常规的代理模式,多个克隆可以指向同一个实现合约。克隆无法升级。
实现合约的地址存储在字节码中。与存储相比,这节省了 gas,并防止克隆指向另一个实现。
这种设计使得部署变得相当便宜,因为克隆代理的字节码通常比实现合约的字节码小得多。事实上,EIP-1167 的大小仅为 55 字节(运行时占 45 字节),包括初始化代码。然而,在执行期间调用的成本会更高,因为总是会增加一个 delegatecall。
本文将描述 EIP 和用于初始化相当于构造函数参数的初始化函数。
本文由 Jesse Raymond (LinkedIn,Twitter) 作为 RareSkills 技术写作项目的一部分共同撰写。
作为一种典型的代理,它通过调用接收事务数据,将数据转发到实现智能合约,获得外部调用的结果。如果外部调用成功,则返回结果;如果出现错误,则回滚。
最小代理合约的字节码简洁,仅有 55 字节。该字节码由以下组成:
以下是最小代理的字节码:
3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
虚拟的地址: 0xbebebebebebebebebebebebebebebebebebebebe
将被实现合约地址替代。
让我们来拆解一下。
字节码的前 10 字节包含仅运行一次的初始化代码,用于部署最小代理。
要了解有关智能合约创建和部署的更多信息,请参见我们有关 以太坊智能合约创建代码 的文章。
以下是在 EVM 中执行的命令。
// 复制最小代理的运行时字节码
// 从偏移量 10 开始,并将其保存到区块链上
[00] RETURNDATASIZE
[01] PUSH1 2d
[03] DUP1
// 将 10 - 偏移量推入,以从中复制运行时代码
[04] PUSH1 0a
[06] RETURNDATASIZE
// 复制运行时代码并将其保存到区块链上
[07] CODECOPY
[08] DUP2
[09] RETURN
初始化代码部署合约并将运行时字节码从偏移量 10(提取 calldata 部分)开始存储到链上。
在最小代理部署并接收到调用后,它复制事务 calldata 到内存,推送实现合约的 20 字节地址,并对实现合约执行 delegatecall。
此 calldata 复制通过以下操作码完成。
// 将事务 calldata 复制到内存
[0a] CALLDATASIZE
[0b] RETURNDATASIZE // 这是一个黑客方式,以更低的 gas 将 0 推送到栈中,而不是使用 PUSH 0
[0c] RETURNDATASIZE
[0d] CALLDATACOPY
[0e] RETURNDATASIZE
[0f] RETURNDATASIZE
[10] RETURNDATASIZE
[11] CALLDATASIZE
[12] RETURNDATASIZE
// 将实现合约的 20 字节地址推送到栈中
[13] PUSH20
在将事务 calldata 复制到内存后,栈已准备好进行 delegatecall,并将实现合约的 20 字节地址推送到栈顶。在上一部分中,我们看到它以 PUSH20 结束。接下来是实现合约的地址。
// 将实现合约的地址推送到栈中。此处的地址只是一个虚拟地址
[13] PUSH20 bebebebebebebebebebebebebebebebebebebebe
在将事务 calldata 复制到内存并将实现合约地址置于栈顶之后,最小代理准备对实现合约执行 delegatecall。
如果你需要关于 delegatecall 工作原理的回顾,请阅读我们的教程 delegatecall。
执行 delegatecall 后,最小代理会返回调用的结果,如果成功则返回结果,如果出现错误则回滚。
delegatecall 部分的操作码如下。
// 对实现合约执行 delegate call,并转发所有可用的 gas
[28] GAS
[29] DELEGATECALL
// 将调用的返回数据复制到内存
[2a] RETURNDATASIZE
[2b] DUP3
[2c] DUP1
[2d] RETURNDATACOPY
// 为条件跳转准备栈
[2e] SWAP1
[2f] RETURNDATASIZE
[30] SWAP2
[31] PUSH1 2b
// 如果调...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!