批量创建相同逻辑的金库、账户或市场时,逐份部署完整字节码会重复付出代码存储成本。EIP-1167定义一段极短代理代码:每个实例只把调用转给同一个实现合约,逻辑复用,状态仍保存在各自地址。省Gas来自少部署代码,不是少执行所有逻辑。
| 元素 | 放在哪里 | 变化方式 |
|---|---|---|
| 业务逻辑代码 | implementation | 部署后由实现地址决定 |
| 用户状态 | clone地址的storage | 每个克隆独立变化 |
| 实现地址 | 克隆字节码常量 | 标准EIP-1167不提供更新入口 |
| 初始化参数 | clone的初始化调用 | 必须防止重复或抢先初始化 |
克隆代理的三个不变量
- EIP-1167代理把所有调用通过DELEGATECALL转发到写死的实现地址,并把返回值或revert原样返回。
- 代码在实现合约,实例状态和msg.sender上下文属于克隆地址,因此每个克隆都需要独立初始化。
- 标准最小代理本身没有实现地址更新入口;确定性克隆可用salt预计算地址,但同一部署者和salt不能重复部署。
DELEGATECALL保留谁的上下文
用户调用clone,最小代理把calldata交给implementation执行。实现代码看到的address(this)是clone,读写的是clone storage,msg.sender仍是原调用者。于是实现合约构造函数写入的状态不会自动出现在克隆里,逻辑必须提供安全的initializer。
初始化是最常见失误
工厂若先部署、后用另一笔交易初始化,第三方可能抢先调用初始化并取得owner。更稳妥的流程是在同一交易创建后立刻初始化,并让初始化函数只能执行一次。实现合约自身也应锁定初始化,避免有人把实现地址配置成可被利用的独立实例。
批量部署前的工厂检查
- 从clone字节码提取并核对implementation
- 确认每个实例初始化已原子完成
- 检查storage layout与实现预期一致
- 区分cloneDeterministic的salt和部署者
- 不要误把标准克隆当可升级代理
标准克隆通常不是升级代理
实现地址被嵌入极短运行时代码,clone没有admin和implementation slot。要变更逻辑通常部署新实现和新clone,或让实现内部再读取其他路由;后者已经不是单纯EIP-1167。审核时应识别真实字节码,而不是看到“proxy”标签就套用透明代理规则。
代码复用也会放大共同漏洞
同一个implementation并不意味着所有克隆共享余额;状态位于各自地址。反过来,实现合约有漏洞时,大量克隆会同时受影响。代码复用放大了审计价值,也放大了共同缺陷,工厂必须能明确列出版本和迁移策略。
确定性克隆地址依赖CREATE2,可先看CREATE2地址公式;代理状态核对可结合账户与存储证明,交互前再执行合约连接检查。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。