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

243 阅读4分钟

前言

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

通用可升级代理合约

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

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

注意事项

1.存储布局
  • 不要修改已有的存储变量顺序
  • 只能在末尾添加新的存储变量
  • 不要修改已有的函数签名
2.初始化
  • 使用 initialize 而不是 constructor
  • 确保初始化函数只能调用一次
  • 正确初始化所有继承的合约
3.实现合约
  • 禁用实现合约的初始化
  • 实现必要的授权检查
  • 保持合约逻辑的独立性
4.升级过程
  • 仔细测试新版本
  • 保持向后兼容性
  • 考虑紧急回退机制

herdhat.config文件配置

  1. 使用@openzeppelin/contracts-upgradeable@openzeppelin/hardhat-upgrades
  2. 下载 npm i -D @openzeppelin/contracts-upgradeable @openzeppelin/hardhat-upgrades
  3. 在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的库实现通用可升级代理合约的方法包含了开发、测试、部署以及此方法的概念、特点和相关注意事项。注意:编写合约一定要按照注意事项的规定;