如何在OpenZeppelin可升级合约中实现多重继承?
多重继承下实现OpenZeppelin可升级合约的正确方案
针对你继承ERC20BurnableUpgradeable和[draft-]ERC20PermitUpgradeable的场景,结合Solidity 0.8.9、Hardhat、OpenZeppelin 4.7.3的环境,正确操作如下:
1. 合约代码编写规范
由于ERC20BurnableUpgradeable和ERC20PermitUpgradeable都继承自ERC20Upgradeable,它们的普通初始化函数(__ERC20Burnable_init、__ERC20Permit_init)都会自动调用__ERC20_init,直接调用这两个函数会导致基础ERC20逻辑被重复初始化,引发问题。正确的做法是手动控制初始化流程:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20PermitUpgradeable { /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } function initialize(string memory name, string memory symbol) initializer public { // 仅调用一次基础ERC20的初始化逻辑 __ERC20_init(name, symbol); // 调用Burnable扩展的无父依赖初始化函数 __ERC20Burnable_init_unchained(); // 调用Permit扩展的无父依赖初始化函数(需传入代币名称) __ERC20Permit_init_unchained(name); } }
代码说明
- 继承
Initializable是可升级合约的必要前提,构造函数中调用_disableInitializers()是为了禁止通过构造函数初始化合约(可升级合约必须通过代理调用initialize函数)。 - 使用
_unchained后缀的初始化函数,这类函数不会自动调用父合约的初始化逻辑,避免重复执行__ERC20_init。 - 手动先完成基础ERC20的初始化,再依次初始化各个扩展功能,确保所有逻辑被正确初始化且无重复。
2. 部署流程
使用Hardhat的OpenZeppelin Upgrades插件部署时,必须使用deployProxy函数(而非普通deploy),它会自动创建代理合约并调用initialize函数:
const { ethers, upgrades } = require("hardhat"); async function main() { const MyToken = await ethers.getContractFactory("MyToken"); // 传入initialize函数所需的参数:代币名称、符号 const myToken = await upgrades.deployProxy(MyToken, ["MyToken", "MTK"]); await myToken.deployed(); console.log("可升级代币合约部署地址:", myToken.address); } main().catch((error) => { console.error(error); process.exitCode = 1; });
3. 通用规则
- 所有多重继承的可升级合约,禁止在初始化函数中调用多个父合约的普通
init函数(无_unchained后缀的),避免重复初始化父类逻辑。 - 统一调用最基础父合约的
init函数一次,再依次调用各个扩展合约的_unchained版本初始化函数,确保每个扩展的独立逻辑被正确初始化。
内容的提问来源于stack exchange,提问作者Can Poyrazoğlu
相关产品推荐
相关产品推荐

