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

fallback中使用delegatecall将调用委托给逻辑合约upgradeTo)才会被执行。常与 ProxyAdmin 管理合约配合使用,优点是维护直观、生态成熟,但部署与运行略重。 
upgradeTo 等),代理更轻;需谨慎实现授权钩子,确保只有预期角色可升级。相较 Transparent,UUPS 部署成本更低,但对实现者规范性要求更高。 一句话总结:新项目优先 UUPS(轻量、直观);若你需要与现有工具链/审计实践完全对齐,也可选 Transparent。
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升级模块 // 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 {} } // 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"; } } reinitializer(2)初始化新变量function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} // 部署实现合约 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); // 部署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 ✅ reinitializer为新版本初始化新变量💡 实战总结:UUPS模式通过将升级逻辑下沉到实现合约,实现了轻量级的可升级方案。关键在于正确处理存储布局、权限控制和状态迁移。
一句话总结:UUPS + 多签 + Timelock 是当前最实用的可升级治理组合:代码上用初始化器替代构造器、架构上用代理解耦状态与逻辑、流程上用多签与延时把风险“物理隔离”。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!