You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无代理部署可治理升级的ERC20Upgradeable合约问题求助

问题分析与解决方案

核心问题拆解

你遇到的问题本质是对UUPS升级模式的逻辑理解偏差:

  1. 直接部署的V2逻辑合约因为构造函数中_disableInitializers()的限制,无法调用initialize,导致合约owner为零地址——但逻辑合约本身不需要初始化,所有状态(包括owner)都存储在代理合约中,逻辑合约只是业务逻辑的实现载体。
  2. 错误提示的Ownable: caller is not the owner,是因为你尝试对逻辑合约执行需要owner权限的操作,但逻辑合约本身未初始化,owner为零地址,而你根本不需要操作逻辑合约。

正确的升级流程

1. 编写兼容的V2合约

确保V2合约继承与V1一致的升级基类,且存储布局完全兼容(只能在状态变量末尾新增,不能修改原有顺序):

// GovernanceTokenV2.sol
contract GovernanceTokenV2 is Initializable, ERC20Upgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, OwnableUpgradeable, UUPSUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    // 保留V1的initialize逻辑(升级时不会触发,代理已完成初始化)
    function initialize() initializer public {
        __ERC20_init("GovernanceToken", "GT");
        __ERC20Permit_init("GovernanceToken");
        __ERC20Votes_init();
        __Ownable_init();
        __UUPSUpgradeable_init();
    }

    // 新增V2专属功能示例
    function getVersion() external pure returns (string memory) {
        return "V2";
    }

    // 保持UUPS升级权限控制(继承OwnableUpgradeable,仅代理存储中的owner可执行升级)
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}

2. 修改升级脚本

只需部署V2逻辑合约,然后通过治理流程让Timelock触发代理的升级操作:

// 00-deploy-upgraded-Token.ts
const { getNamedAccounts, deployments, network } = hre;
const { deploy, log } = deployments;
const { deployer } = await getNamedAccounts();

// 替换为你的代理合约地址(原deployProxy返回的deployedProxy.address)
const PROXY_ADDRESS = "0x...";
// 替换为你的治理合约地址
const GOVERNOR_ADDRESS = "0x...";

// 部署V2逻辑合约(无需初始化)
const governanceTokenV2 = await ethers.getContractFactory("GovernanceTokenV2");
const v2Implementation = await governanceTokenV2.deploy();
await v2Implementation.deployed();

log(`V2逻辑合约部署完成:${v2Implementation.address}`);

// 获取代理合约实例,构造升级交易数据
const proxyContract = await ethers.getContractAt("GovernanceToken", PROXY_ADDRESS);
const upgradeCalldata = proxyContract.interface.encodeFunctionData("upgradeTo", [v2Implementation.address]);

// 提交治理提案,让Timelock执行升级操作(示例基于OpenZeppelin Governor)
const governor = await ethers.getContractAt("GovernorContract", GOVERNOR_ADDRESS);
const proposalTx = await governor.propose(
    [PROXY_ADDRESS],    // 目标合约:代理合约
    [0],                // 转账金额:0
    [upgradeCalldata],  // 调用数据:触发upgradeTo
    "升级GovernanceToken至V2版本"  // 提案描述
);
await proposalTx.wait(1);

log(`升级提案已提交,等待投票通过后由Timelock执行`);

关键注意事项

  • 逻辑合约(V2)不需要初始化:所有状态(包括owner=Timelock)都存储在代理合约中,代理在V1部署时已完成初始化。
  • UUPS升级权限由_authorizeUpgrade控制:你的合约继承OwnableUpgradeable,因此只有代理存储中的owner(即Timelock)可以触发升级,符合治理流程要求。
  • 必须保证存储布局兼容:新增状态变量只能放在现有变量末尾,否则会破坏代理中的数据结构。

内容的提问来源于stack exchange,提问作者Ritzy Dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 09:25:45