EIP-1167:带初始化的最小代理标准(克隆模式)

  • RareSkills
  • 发布于 2023-02-23 19:11
  • 阅读 207

本文详细介绍了EIP-1167标准,即最小代理合约,用于廉价创建代理克隆。文章深入解析了其工作原理、字节码结构、初始化函数及实际应用示例,帮助开发者理解如何高效部署相似合约。

clones
图片来自 <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 (LinkedInTwitter) 作为 RareSkills 技术写作项目的一部分共同撰写。

EIP-1167 的工作原理

作为一种典型的代理,它通过调用接收事务数据,将数据转发到实现智能合约,获得外部调用的结果。如果外部调用成功,则返回结果;如果出现错误,则回滚。

最小代理的字节码

最小代理合约的字节码简洁,仅有 55 字节。该字节码由以下组成:

  • 初始化代码
  • 包含接收事务 calldata 指令的运行时代码
  • 20 字节的实现合约地址
  • 执行 delegate call 的命令
  • 返回结果,或在出现错误时触发 revert。

以下是最小代理的字节码:

3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3

虚拟的地址: 0xbebebebebebebebebebebebebebebebebebebebe 将被实现合约地址替代。

让我们来拆解一下。

克隆合约字节码,其中字节码的区域被高亮显示

初始化代码部分

字节码的前 10 字节包含仅运行一次的初始化代码,用于部署最小代理。

要了解有关智能合约创建和部署的更多信息,请参见我们有关 以太坊智能合约创建代码 的文章。

以下是在 EVM 中执行的命令。

// 复制最小代理的运行时字节码 
// 从偏移量 10 开始,并将其保存到区块链上

[00] RETURNDATASIZE 
[01] PUSH1    2d
[03] DUP1   

// 将 10 - 偏移量推入,以从中复制运行时代码
[04] PUSH1    0a     
[06] RETURNDATASIZE 

// 复制运行时代码并将其保存到区块链上
[07] CODECOPY   
[08] DUP2   
[09] RETURN

复制 calldata

初始化代码部署合约并将运行时字节码从偏移量 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

Delegatecall 部分

在将事务 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

// 如果调...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

1 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/