Openzeppelin Initializable与Hardhat upgrades是否等效?能否结合使用?
OpenZeppelin Initializable 与 Hardhat Upgrades 初始化方案对比
核心原理是否相同?
两者底层原理完全一致:都是为了解决可升级合约无法使用构造函数初始化状态的问题。因为基于透明代理或UUPS的可升级架构中,逻辑合约的构造函数只会在自身部署时执行,代理合约无法继承这部分初始化逻辑,必须通过代理调用专门的初始化函数来完成状态设置。
实现方式有何差异?
OpenZeppelin Initializable
- 依赖
Initializable基类合约和initializer修饰器,内部通过_initialized、_initializing状态变量自动处理初始化函数只能被调用一次的逻辑,同时防护重入风险。 - 通常约定初始化函数名为
initialize(支持重载),修饰器会强制限制该函数的调用时机和次数,无需手动编写重复检查逻辑。
Hardhat Upgrades 插件
- 对初始化函数名称无强制要求,你可以自定义为
init、setup等任意名称,只需在部署/升级时通过插件API(如deployProxy、upgradeProxy)指定要调用的函数名和参数即可。 - 插件负责管控初始化函数的调用时机,但不会自动处理重复调用防护——如果需要确保初始化仅执行一次,需自行实现检查逻辑,或结合OpenZeppelin的
initializer修饰器。
能否同时采用两种方案?
完全可以,且这是生产环境中推荐的组合方式,两者互补无冲突:
- 继承OpenZeppelin的
Initializable合约,给自定义初始化函数加上initializer修饰器,利用其成熟的重复调用防护机制。 - 通过Hardhat Upgrades插件的API,在部署/升级时指定调用该初始化函数,借助插件简化代理部署、升级的流程和校验工作。
示例代码
Solidity合约部分
// 继承OpenZeppelin Initializable基类 pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; contract MyUpgradeableContract is Initializable { uint256 public storedValue; // 用initializer修饰器确保仅执行一次 function initialize(uint256 _initValue) public initializer { storedValue = _initValue; } }
Hardhat部署脚本部分
const { ethers, upgrades } = require("hardhat"); async function main() { const MyContract = await ethers.getContractFactory("MyUpgradeableContract"); // 通过deployProxy调用initialize函数,传入初始化参数 const proxyInstance = await upgrades.deployProxy(MyContract, [100], { initializer: "initialize" }); await proxyInstance.deployed(); console.log("Proxy deployed to:", proxyInstance.address); } main().catch((error) => { console.error(error); process.exitCode = 1; });
额外注意事项
如果仅使用Hardhat Upgrades而不结合OpenZeppelin的initializer修饰器,需手动实现初始化状态检查(比如添加bool public isInitialized变量,在初始化函数开头判断并标记),否则存在重复调用初始化函数篡改状态的风险。
内容的提问来源于stack exchange,提问作者Chamara Ariyarathne
相关产品推荐
相关产品推荐

