
UUPS contract
本文最后更新于 2024-08-13,本文发布时间距今超过 90 天, 文章内容可能已经过时。最新内容请以官方内容为准
UUPS 代理合约
引言 🌐
在区块链开发的多变领域中,合约的可升级性是一个关键特性,它允许开发者随着时间推移更新和改进他们的合约。**UUPS(通用可升级代理标准)**是一个关键的标准,它在 Solidity 中实现了合约的可升级性。这个标准特别有趣,因为它通过将升级函数直接放在逻辑合约中,解决了选择器冲突问题。
UUPS 是什么?🤔
UUPS 代理模式是用于创建可升级智能合约的设计。它建立在透明代理模式之上,但将升级合约的责任转移到逻辑合约本身。这种方法确保只有授权实体可以升级合约,并且简化了管理代理和逻辑合约的过程。
关键组件:
- 代理合约:保存实现地址,并将所有调用委托给逻辑合约。
- 逻辑合约(实现):包含业务逻辑和升级函数。
- 升级函数:由逻辑合约管理,允许在保留状态的同时更新实现合约。
为什么使用 UUPS?🚀
UUPS 旨在高效灵活,允许以最小的燃气成本无缝升级。实现合约中的升级逻辑还提供了更清晰的架构,避免了与函数选择器相关的潜在安全问题。
实现细节 🔧
UUPS 的逻辑流程
让我们将 UUPS 模式分解为其核心组件:
-
代理合约:
- 该合约存储实现地址。
- 它将所有调用委托给实现合约。
-
逻辑合约:
- 包含升级函数。
- 处理核心业务逻辑。
-
升级过程:
- 代理合约将调用委托给逻辑合约。
- 逻辑合约中的升级函数更新代理中的实现地址。
流程图 🎨
graph TD;
A[用户调用] --> B[代理合约];
B --> C[委托给逻辑合约];
C --> D[执行逻辑];
B --> E[升级逻辑];
E --> F[更改实现地址];
合约调用逻辑:
代码实现 📝
让我们通过一个完整的示例,包括代理部署,来增强初始实现。
代理合约
UUPS 的代理合约看起来像是个不可升级的代理合约,非常简单,因为升级函数被放在了逻辑合约中。它包含 3 个变量:
- implementation:逻辑合约地址。
- admin:admin 地址。
-words:字符串,可以通过逻辑合约的函数改变。
它包含 2 个函数
- 构造函数:初始化 admin 和逻辑合约地址。
- fallback():回调函数,将调用委托给逻辑合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract UUPSProxy {
address public implementation; // 逻辑合约地址
address public admin; // admin地址
string public words; // 字符串,可以通过逻辑合约的函数改变
// 构造函数,初始化admin和逻辑合约地址
constructor(address _implementation){
admin = msg.sender;
implementation = _implementation;
}
// fallback函数,将调用委托给逻辑合约
fallback() external payable {
(bool success, bytes memory data) = implementation.delegatecall(msg.data);
}
}
逻辑合约
UUPS 的逻辑合约与透明代理中的不同是多了个升级函数。UUPS 逻辑合约包含 3 个状态变量,与保持代理合约一致,防止插槽冲突。它包含 2 个
- upgrade():升级函数,将改变逻辑合约地址 implementation,只能由 admin 调用。
- foo():旧 UUPS 逻辑合约会将 words 的值改为"old",新的会改为"new"。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// UUPS逻辑合约(升级函数写在逻辑合约内)
contract UUPS1{
// 状态变量和proxy合约一致,防止插槽冲突
address public implementation;
address public admin;
string public words; // 字符串,可以通过逻辑合约的函数改变
// 改变proxy中状态变量,选择器: 0xc2985578
function foo() public{
words = "old";
}
// 升级函数,改变逻辑合约地址,只能由admin调用。选择器:0x0900f010
// UUPS中,逻辑函数中必须包含升级函数,不然就不能再升级了。
function upgrade(address newImplementation) external {
require(msg.sender == admin);
implementation = newImplementation;
}
}
// 新的UUPS逻辑合约
contract UUPS2{
// 状态变量和proxy合约一致,防止插槽冲突
address public implementation;
address public admin;
string public words; // 字符串,可以通过逻辑合约的函数改变
// 改变proxy中状态变量,选择器: 0xc2985578
function foo() public{
words = "new";
}
// 升级函数,改变逻辑合约地址,只能由admin调用。选择器:0x0900f010
// UUPS中,逻辑函数中必须包含升级函数,不然就不能再升级了。
function upgrade(address newImplementation) external {
require(msg.sender == admin);
implementation = newImplementation;
}
}
示例用法 🛠️
以下是在真实场景中如何使用这些合约的示例:
- 部署 old 逻辑合约:这是您的初始逻辑合约。
- 部署 代理合约:这个代理将委托调用给
逻辑合约
。 - 通过代理初始化:您将通过代理调用
initialize
函数来设置初始状态。
当需要升级时:
- 部署 new 逻辑合约:这是您的逻辑合约的新版本。
- 升级代理:在代理上调用
upgradeTo
函数,该函数反过来调用_authorizeUpgrade
方法来执行升级。
安全考虑
- 总是在
_authorizeUpgrade
函数中包含访问控制。 - 对
_canUpgrade
方法保持谨慎,因为它定义了谁可以触发升级。
结论
UUPS 代理提供了一种强大的解决方案,用于创建可升级的智能合约。通过理解模式并仔细实施,开发者可以确保他们的合约长期保持灵活性和安全性。
参考资料 📚
这个增强版教程为实现和理解 UUPS 代理合约提供了全面的指南,包括详细的代码示例和安全考虑。