可升级合约从入门到上线:UUPS + 多签 + Timelock 一把梭

老道的学习日志

是什么

众所周知,智能合约部署上链后代码不可改变,但总是会有需要加功能、bug维护、优化gas之类的时候,而且还要减少用户迁移带来的成本。因此有了'可升级'合约,而可升级并不是说修改旧的合约,而是通过将状态与逻辑分离,在保持同一地址对外服务的前提下替换逻辑合约。

image.png

怎么搞

  • 代理模式:
    • 用户与代理合约进行交互
    • 代理在fallback中使用delegatecall将调用委托给逻辑合约
    • 逻辑代码在代理的执行上下文里运行,因此状态(存储)保存在代理,而逻辑可替换,实现“状态-逻辑解耦”。设计时要特别注意存储冲突与函数选择器冲突(尤其是管理函数与业务函数的选择器重叠问题)

主流模式

  • Transparent 透明代理:用户调用都转发给逻辑合约;只有管理员地址调用时,代理自身的管理函数(如 upgradeTo)才会被执行。常与 ProxyAdmin 管理合约配合使用,优点是维护直观、生态成熟,但部署与运行略重。

image-1.png

  • UUPS(EIP-1822):升级逻辑下沉到逻辑合约自身(实现 upgradeTo 等),代理更轻;需谨慎实现授权钩子,确保只有预期角色可升级。相较 Transparent,UUPS 部署成本更低,但对实现者规范性要求更高。

一句话总结:新项目优先 UUPS(轻量、直观);若你需要与现有工具链/审计实践完全对齐,也可选 Transparent。

上手

UUPS 实战项目

项目结构

code/ ├── contracts/ │ ├── MyUUPSV1.sol # V1实现合约 │ ├── MyUUPSV2.sol # V2实现合约(新功能) │ └── SimpleProxy.sol # 简化的代理合约 ├── scripts/ │ └── upgrade-demo.ts # 完整的升级演示脚本 ├── test/ │ └── UUPS-Upgrade.test.ts # 全面的测试套件 └── ignition/modules/ ├── MyUUPSV1.ts # V1部署模块 └── MyUUPSV2.ts # V2升级模块

核心合约实现

MyUUPSV1.sol - 初始版本

// SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; contract MyUUPSV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable { uint256 public value; function initialize(uint256 _value) public initializer { __Ownable_init(msg.sender); __UUPSUpgradeable_init(); value = _value; } function setValue(uint256 _value) public { value = _value; } function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} }

MyUUPSV2.sol - 升级版本

// SPDX-License-Identifier: MIT pragma solidity ^0.8.28; import "./MyUUPSV1.sol"; contract MyUUPSV2 is MyUUPSV1 { // 新增状态变量(注意存储布局) string public name; uint256 public counter; // V2的初始化函数 function initializeV2(string memory _name) public reinitializer(2) { name = _name; counter = 0; } // 新增功能:计数器操作 function increment() public { counter++; } function decrement() public { require(counter > 0, "Counter cannot be negative"); counter--; } // 新增功能:设置名称 function setName(string memory _name) public { name = _name; } // 版本信息 function getVersion() public pure returns (string memory) { return "v2.0.0"; } }

关键实现要点

1. 存储布局兼容性

  • V2继承V1,确保存储槽位置不变
  • 新状态变量添加在原有变量之后
  • 使用reinitializer(2)初始化新变量

2. 升级授权

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
  • 只有合约所有者可以执行升级
  • 在V1和V2中都需要实现此函数

3. 代理部署

// 部署实现合约 const MyUUPSV1 = await ethers.getContractFactory("MyUUPSV1"); const v1Implementation = await MyUUPSV1.deploy(); // 编码初始化数据 const initData = v1Implementation.interface.encodeFunctionData("initialize", [100]); // 部署代理 const SimpleProxy = await ethers.getContractFactory("SimpleProxy"); const proxy = await SimpleProxy.deploy(v1Implementation.target, initData);

4. 升级流程

// 部署V2实现 const MyUUPSV2 = await ethers.getContractFactory("MyUUPSV2"); const v2Implementation = await MyUUPSV2.deploy(); // 编码V2初始化数据 const initV2Data = v2Implementation.interface.encodeFunctionData("initializeV2", ["UUPS Demo Contract"]); // 执行升级 const v1Proxy = MyUUPSV1.attach(proxy.target); await v1Proxy.upgradeToAndCall(v2Implementation.target, initV2Data);

测试验证

完整测试套件

describe("UUPS Upgrade Test", function () { it("应该成功部署V1合约", async function () { // 部署测试 }); it("应该能够使用V1功能", async function () { // V1功能测试 }); it("应该成功升级到V2", async function () { // 升级测试 }); it("应该能够使用V2的新功能", async function () { // V2新功能测试 }); it("应该保持V1的原有功能", async function () { // 向后兼容性测试 }); it("计数器不应该允许负数", async function () { // 边界条件测试 }); });

演示结果

成功的升级流程

🚀 开始UUPS可升级合约演示... 📦 部署V1实现合约... ✅ 📦 部署代理合约... ✅ 🔍 测试V1功能: 初始值: 100, 设置新值后: 200 ✅ 📦 部署V2实现合约... ✅ ⬆️ 升级到V2... ✅ 🔧 初始化V2新功能... ✅ 🔍 验证升级后的状态: 升级后的值 (应该保持不变): 200 ✅ 版本信息: v2.0.0 ✅ 合约名称: UUPS Demo Contract ✅ 计数器初始值: 0 ✅ 🆕 测试V2新功能: 增加计数器后: 2 ✅ 减少计数器后: 1 ✅ 更新后的名称: 升级成功的UUPS合约 ✅ 🔄 验证V1功能仍然可用: 最终值: 500 ✅

测试结果

  • ✅ 6个测试全部通过
  • ✅ 状态完美保持
  • ✅ 新功能正常工作
  • ✅ 向后兼容性良好

核心优势

  1. 状态保持 - 升级后V1的状态完全保持
  2. 新功能添加 - V2成功添加了计数器和名称功能
  3. 向后兼容 - V1的所有功能在升级后依然可用
  4. 安全升级 - 只有合约所有者可以执行升级
  5. 完整测试 - 覆盖了所有升级场景和边界条件

最佳实践

  1. 存储布局 - 新版本只能在末尾添加状态变量
  2. 初始化器 - 使用reinitializer为新版本初始化新变量
  3. 权限控制 - 严格控制升级权限,通常限制为合约所有者
  4. 测试覆盖 - 全面测试升级前后的功能和状态保持
  5. 渐进升级 - 分步骤验证每个升级环节

💡 实战总结:UUPS模式通过将升级逻辑下沉到实现合约,实现了轻量级的可升级方案。关键在于正确处理存储布局、权限控制和状态迁移。

一句话总结UUPS + 多签 + Timelock 是当前最实用的可升级治理组合:代码上用初始化器替代构造器、架构上用代理解耦状态与逻辑、流程上用多签与延时把风险“物理隔离”

参考文献

  1. 如何部署和使用可升级的智能合约
  2. https://mirror.xyz/0x59749c32329F8B52A4Ef7a1b0A83Aa94B9D20f2F/zbn1gLdKPED6V7V3C5Ik6BO1UR1GpvD-xHDAUzFTVU0 (感谢图片,这个图片真的很清晰)
  3. https://learnblockchain.cn/article/4880
  • 发表于 2025-10-02 17:35
  • 阅读 ( 1164 )
  • 学分 ( 37 )
  • 分类:合约交互
  • 标签:
点赞 1
收藏 1
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

你可能感兴趣的文章

0 条评论

请先 登录 后评论