智能合约可升级方式之通用可升级代理合约

  • 木西
  • 发布于 2025-03-04 15:43
  • 阅读 102

前言合约一旦署上链是不可修改,为了解决后续合约的新增功能,本文将介绍智能合约可升级的的方案,实现升级智能合约的方案主要通过使用代理合约来实现合约的升级;通用可升级代理合约作用:因为智能合约一旦部署就不可修改,为了解决合约可升级可修改的,才使用代理合约来实现,一句话总结允许在不更改合约地址

前言

合约一旦署上链是不可修改,为了解决后续合约的新增功能,本文将介绍智能合约可升级的的方案,实现升级智能合约的方案主要通过使用代理合约来实现合约的升级;

通用可升级代理合约

作用:因为智能合约一旦部署就不可修改,为了解决合约可升级可修改的,才使用代理合约来实现, 一句话总结允许在不更改合约地址的情况下更新合约的逻辑部分;

特点:

  1. 升级逻辑在逻辑合约中:这种设计使得逻辑合约本身可以被升级;
  2. 部署成本较低:由于不需要在代理合约中实现复杂的升级逻辑,部署成本相对较低;
  3. 维护复杂性:维护起来更具挑战性,因为升级逻辑需要在逻辑合约中实现;

    注意事项

    1.存储布局

    • 不要修改已有的存储变量顺序
    • 只能在末尾添加新的存储变量
    • 不要修改已有的函数签名

      2.初始化

    • 使用 initialize 而不是 constructor
    • 确保初始化函数只能调用一次
    • 正确初始化所有继承的合约

      3.实现合约

    • 禁用实现合约的初始化
    • 实现必要的授权检查
    • 保持合约逻辑的独立性

      4.升级过程

    • 仔细测试新版本
    • 保持向后兼容性
    • 考虑紧急回退机制

      herdhat.config文件配置

  4. 使用@openzeppelin/contracts-upgradeable@openzeppelin/hardhat-upgrades
  5. 下载 npm i -D @openzeppelin/contracts-upgradeable @openzeppelin/hardhat-upgrades
  6. 在hardhat.config.js中导入require(@openzeppelin/hardhat-upgrades)

    合约开发

    代理合约

    
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract Box is Initializable, OwnableUpgradeable, UUPSUpgradeable { uint256 private _value;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
    _disableInitializers();
}

function initialize() public initializer {
    __Ownable_init(msg.sender);
    __UUPSUpgradeable_init();
}

// 存储值
function store(uint256 value) public {
    _value = value;
}

// 读取值
function retrieve() public view returns (uint256) {
    return _value;
}

// 必需的函数,用于授权升级
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

}

编译指令

npx hardhat compile

#### 可升级合约
**说明**:升级合约添加了两个功能:加值和减值的方法;

// SPDX-License-Identifier: MIT pragma solidity ^0.8.22;

import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract BoxV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable { uint256 private _value;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
    _disableInitializers();
}

function initialize() public initializer {
    __Ownable_init(msg.sender);
    __UUPSUpgradeable_init();
}

// 存储值
function store(uint256 value) public {
    _value = value;
}

// 读取值
function retrieve() public view returns (uint256) {
    return _value;
}

// 新增:递增值
function increment() public {
    _value += 1;
}
//减值
function decrement() public {
    _value -= 1;
}
// 必需的函数,用于授权升级
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

}

编译指令

npx hardhat compile

# 合约测试

const { ethers, upgrades } = require("hardhat"); const { expect } = require("chai");

describe("代理合约", function () { let box; let boxV2; //部署代理合约 beforeEach(async function () { const Box = await ethers.getContractFactory("Box"); const BoxV2 = await ethers.getContractFactory("BoxV2");

    // 部署初始合约
    box = await upgrades.deployProxy(Box, [], {
        initializer: "initialize",
        kind: "uups"
    });
    await box.waitForDeployment();
});

describe("部署合约", function () {
    it("正确的地址部署", async function () {
        console.log("box合约地址",await box.getAddress())
        expect(await box.getAddress()).to.be.properAddress;
    });

    it("返回值", async function () {
        expect(await box.retrieve()).to.equal(0);
    });

    it("存储值", async function () {
        await box.store(42);
        expect(await box.retrieve()).to.equal(42);
    });
});

describe("升级", function () {
    // 升级合约
    beforeEach(async function () {
        const BoxV2 = await ethers.getContractFactory("BoxV2");
        boxV2 = await upgrades.upgradeProxy(await box.getAddress(), BoxV2);
    });

    it("验证新功能方法", async function () {
        //新增
        await boxV2.store(42);
        await boxV2.increment();
        expect(await boxV2.retrieve()).to.equal(43);
        //减少
        await boxV2.store(42);
        await boxV2.decrement();
        expect(await boxV2.retrieve()).to.equal(41);
    });

    it("存储内容", async function () {
        const BoxV2 = await ethers.getContractFactory("BoxV2");
        console.log("box合约地址",await box.getAddress())
        await box.store(42);
        boxV2 = await upgrades.upgradeProxy(await box.getAddress(), BoxV2);
        expect(await boxV2.retrieve()).to.equal(42);
    });
});

describe("验证升级权限", function () {
    it("非所有者升级", async function () {
        const [owner, addr1] = await ethers.getSigners();
        const BoxV2 = await ethers.getContractFactory("BoxV2", addr1);
        // console.log("报错", await upgrades.upgradeProxy(await box.getAddress(), BoxV2))
        await expect(
            upgrades.upgradeProxy(await box.getAddress(), BoxV2)
        ).to.be.revertedWithCustomError(
            box,
            "OwnableUnauthorizedAccount"
        );
    });
});

});

测试指令

npx hardhat test ./test/xxx.js

# 部署合约
#### 代理合约

const { ethers, upgrades } = require("hardhat");

async function main() { const Box = await ethers.getContractFactory("Box");

const box = await upgrades.deployProxy(Box, [], {
    initializer: "initialize",
    kind: "uups"
});
await box.waitForDeployment();
console.log("代理合约地址:", await box.getAddress());

}

main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });

部署合约

npx hardhat run ./scripts/xxx.js --network '链名'//

#### 升级合约

const { ethers, upgrades } = require("hardhat"); async function main() { //部署代理合约 const Box = await ethers.getContractFactory("Box"); const box = await upgrades.deployProxy(Box, [], { initializer: "initialize", kind: "uups" }); await box.waitForDeployment(); console.log("代理合约地址:", await box.getAddress()); //升级合约 const BoxV2 = await ethers.getContractFactory("BoxV2"); console.log("Upgrading Box...");

// 替换为你的代理合约地址
// const PROXY_ADDRESS = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512";//your_proxy_address_here

await upgrades.upgradeProxy(await box.getAddress(), BoxV2);
console.log("Box upgraded");

}

main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });

部署合约

npx hardhat run ./scripts/xxx.js --network '链名'//


# 总结
以上就是借助openzeppelin的库实现通用可升级代理合约的方法包含了开发、测试、部署以及此方法的概念、特点和相关注意事项。`注意`:编写合约一定要按照注意事项的规定;
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
江湖只有他的大名,没有他的介绍。