深入探讨代理(Proxy) 、可初始化代理、可升级代理、透明代理、UUPS 、信标代理、 变形合约
- 原文链接:proxies.yacademy.dev/pages...
- 译者:AI翻译官,校对:翻译小组
- 本文链接:learnblockchain.cn/article…
Vitalik 说:“要有代理(Let there be Proxies)!”
代理(Proxy)本身并不是固有可升级的,但代理是几乎所有可升级代理模式的基础。对代理合约的调用会通过 delegatecall
转发到实现合约。实现合约也称为逻辑合约。
在一些变体中,只有当调用者匹配“所有者”地址时,调用才会被转发到代理。
实现地址 - 在代理合约中是不可变的。
升级逻辑 - 纯代理合约中没有可升级性。
合约验证 - 可与 Etherscan ( 示例 )和其他区块浏览器配合使用。
delegatecall
的费用。“但我们该如何在没有
constructor()
的情况下工作?”
现代的代理大多数都是可初始化的(initializeable)。使用代理的主要好处之一是只需部署一次实现合约(即逻辑合约),然后可以部署多个指向它的代理合约。然而,缺点是你无法在创建新代理时使用已经部署的实现合约中的构造函数。
相反,使用 initialize()
函数来设置初始存储值:
uint8 private _initialized;
function initializer() external {
require(msg.sender == owner);
require(_initialized < 1);
_initialized = 1;
// 设置一些状态变量
// 做初始化工作
}
delegatecall
的 calldata 中,实现合约函数随后从 calldata 中读取参数。此模式可以消除使用初始化器的需要,但缺点是当前该合约无法在 Etherscan 上验证 。人们说:“但我们想升级我们的不可变合约!”
可升级代理类似于代理 ,只不过实现合约地址是可设置的,并保存在代理合约中。代理合约还包含授权的升级功能。第一个可升级代理合约之一由 Nick Johnson 在 2016 年编写。
出于安全考虑,还建议使用某种形式的访问控制,以区分拥有者/调用者和拥有权限升级合约的管理员。
实现地址 - 位于代理存储中。
升级逻辑 - 位于代理合约中。
合约验证 - 根据具体实现,可能无法与像 Etherscan 这样的区块浏览器配合使用。
delegatecall
费用。解决存储冲突的“解决方案”
这类似于 可升级代理 ,但通过使用无结构存储模式减少存储冲突的风险。它不将实现合约地址存储在槽 0 或任何其他标准存储槽中。
相反,地址存储在预先商定的槽中。例如,OpenZeppelin 合约使用字符串“eip1967.proxy.implementation”的 keccak-256 哈希值减去 1。由于这个槽广泛使用,区块浏览器可以识别并处理代理的使用。
*减去 1 提供了额外的安全性,因为没有它,存储槽有一个已知的预映像,但在减去 1 之后,预映像是未知的。对于已知的预映像,存储槽可能会通过映射被覆盖,例如,其中存储槽的键是通过 keccak-256 哈希确定的。
EIP-1967 还指定了一个用于管理员存储(auth)的槽,以及将要详细讨论的 Beacon 代理。
实现地址 - 位于代理合约中的唯一存储槽。
升级逻辑 - 根据实现而有所不同。
合约验证 - 是的,大多数 EVM 区块浏览器支持它。
delegatecall
成本。解决函数冲突的“解决方案”
这与 可升级代理 类似,通常结合 EIP-1967。但是,如果调用者是代理的管理员,代理将不会委托任何调用;如果调用者是其他地址,代理将始终委托调用,即使函数签名与代理自己的某个函数匹配。这通常通过像 OpenZeppelin 中的修饰符实现:
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback(); // 将调用重定向到代理
}
}
以及在 fallback()
中的检查:
require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
实现地址 - 位于代理合约中的唯一存储槽 (EIP-1967)。
升级逻辑 - 位于代理合约中,使用 修饰符 来重新路由非管理员调用者。
合约验证 - 是的,大多数 EVM 区块浏览器支持它。
delegatecall
的运行时 gas 成本,还会产生检查调用者是否为管理员的 SLOAD 成本。如果我们将升级逻辑移到实现合约呢?🤔
EIP-1822 描述了一种可升级代理模式的标准,其中 upgrade
逻辑存储在实现合约中。这样,就不需要在代理级别检查调用者是否为管理员,从而节省 gas。它还消除了实现合约上的函数与代理中的升级逻辑冲突的可能性。
UUPS 的缺点是它被认为比 TPP 更具风险。如果代理没有正确初始化,或者实现合约被自毁,则无法保存代理,因为升级逻辑位于实现合约上。
UUPS 代理在升级时还包含一个额外的检查,以确保新的实现合约是可升级的。
该代理合约通常结合 EIP-1967。
实现地址 - 位于代理合约中的唯一存储槽 (EIP-1967)。
升级逻辑 - 位于实现合约中。
合约验证 - 是的,大多数 EVM 区块浏览器支持它。
fallback()
之外没有逻辑,该逻辑委托调用到实现合约。fallback()
之外不包含任何逻辑。selfdestruct
或因初始化不当而处于不良状态。如果实现合约出现问题,则代理无法恢复。delegatecall
成本。你代理中的信标,是你的快乐让我感到惊讶吗?🤔
目前讨论的大多数代理将实现合约地址存储在代理合约存储中。信标模式由 Dharma 于 2019 年推广,它将实现合约的地址存储在一个单独的“信标”合约中。信标的地址使用 EIP-1967 存储模式存储在代理合约中。
使用其他类型的代理时,当实现合约升级时,所有代理都需要更新。但是,使用信标代理时,只需要更新信标合约本身。
代理中的信标地址以及信标上的实现合约地址都可以由管理员设置。这在处理需要以不同方式分组的大量代理合约时,提供了许多强大的组合。
实现地址 - 位于信标合约的唯一存储槽中。信标地址位于代理合约的唯一存储槽中。
升级逻辑 - 升级逻辑通常存在于信标合约中。
合约验证 - 是的,大多数 EVM 区块浏览器支持。
EXTCODECOPY
直接从信标加载它。“钻石是代理最好的朋友吗?”
EIP-2535 “钻石”是能够在部署后进行升级/扩展的模块化智能合约系统,几乎没有大小限制。从 EIP:
钻石是一个具有外部函数的合约,这些函数由称为侧面的合约提供。侧面是可以共享内部函数、库和状态变量的独立合约。
钻石模式由一个中央的 Diamond.sol 代理合约组成。除了其他存储外,该合约包含可以在称为侧面的外部合约上调用的函数注册表。
钻石代理使用独特词汇的术语表:
钻石术语 | 定义 |
---|---|
钻石(Diamond) | 代理 |
侧面(Facet) | 实现 |
削减(Cut) | 升级 |
Loupe | 委托函数列表 |
完成的钻石 | 不可升级 |
单削减钻石 | 移除升级功能 |
合约验证 - 合约可以通过一个名为 Louper 的工具在 Etherscan 上进行验证( 示例 )。
变形方法: “create2, 使用, selfdestruct, 再创建(新合约), 重复…”
变形合约与所有其他可升级模式的不同之处在于它不使用代理。没有对外部逻辑合约的 delegatecall
。
当需要升级时,变形合约使用 selfdestruct
,并使用 create2
将新合约部署到相同地址。
这可以通过让 initcode 从一个单独的外部合约的存储中检索创建代码来实现。通过这种方式,initcode 将始终相同,因此可以使用 create2
部署到相同地址。
不建议为新合约使用变形合约,因为 selfdestruct
操作码计划在不久的将来从以太坊中移除。有关详细信息,请参见 EIP-4758。
合约验证 - 是的,变形合约可以被验证。
delegatecall
的代理。initialize()
而不是 constructor()
。selfdestruct
。selfdestruct
在交易结束时清除代码,因此升级需要两笔交易:一笔删除当前合约,另一笔创建新合约。在这两笔交易之间到达我们合约的任何交易都会失败。selfdestruct
操作码可能在未来被移除。如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!