
如何使用 OpenZeppelin Contracts Upgradeable 包以 initializer 替换构造函数并部署代理合约【免费下载链接】openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development.项目地址: https://gitcode.com/GitHub_Trending/op/openzeppelin-contracts如果你要把合约部署成可升级的例如配合 OpenZeppelin Upgrades Plugins 使用就不能直接用openzeppelin/contracts里的普通合约而要改用专门的 Upgradeable 变体包openzeppelin/contracts-upgradeable。这个任务包含三步安装 Upgradeable 包、把构造函数改写为 initializer 函数、用 Upgrades Plugins 部署代理合约。本文基于仓库中的 upgradeable 文档、Initializable 源码 和 Proxy 模块说明 给出完整的操作路径。一个必须先知道的前提OpenZeppelin Contracts 用语义化版本来承诺 API 与存储布局的向后兼容不同大版本之间的存储布局应视为不兼容例如从 4.9.3 升级到 5.0.0 是不安全的见 index 文档。所以升级前先在文档中确认版本约束。安装 Upgradeable 包Upgradeable 变体是独立发布的 npm 包openzeppelin/contracts-upgradeable并且以openzeppelin/contracts作为 peer dependency因此两个包要一起安装$ npm install openzeppelin/contracts-upgradeable openzeppelin/contracts包的结构与主包一致只是每个文件和合约名都带Upgradeable后缀。唯一的例外接口interface和库library不包含在 Upgradeable 包里仍从主包openzeppelin/contracts导入。把构造函数替换为 initializer 函数改写分两部分导入与继承以及构造函数改写成 initializer。以 ERC721 为例导入和继承的改动是-import {ERC721} from openzeppelin/contracts/token/ERC721/ERC721.sol; import {ERC721Upgradeable} from openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol; -contract MyCollectible is ERC721 { contract MyCollectible is ERC721Upgradeable {构造函数的改写遵循固定命名约定Upgradeable 包中每个合约的构造函数对应一个内部函数__{ContractName}_init。因为它是internal的你必须在自己的合约里定义一个public的 initializer 函数并调用父合约的 init 函数- constructor() ERC721(MyCollectible, MCO) { function initialize() initializer public { __ERC721_init(MyCollectible, MCO); }initializer修饰符保证该函数最多只能被成功调用一次这是代理部署替代构造函数的核心保护机制见 Initializable.sol。为什么部署前建议锁定实现合约Initializable 源码 的文档明确警告未初始化的合约可能被攻击者接管这对代理和它背后的实现合约都适用。为防止实现合约本身被误初始化推荐在构造器中调用_disableInitializers()自动上锁/// custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); }_disableInitializers()会把合约锁定阻止其被初始化到任何版本文档建议用于设计为通过代理调用的实现合约。用 Upgrades Plugins 部署代理合约合约写好并编译后用 OpenZeppelin Upgrades Plugins 部署代理。Proxy 模块说明 指出正确使用升级代理需要深入理解代理模式、Solidity 和 EVM除非你想做低层控制否则推荐直接使用 Hardhat 和 Foundry 的 Upgrades Plugins。以下是 upgradeable 文档 给出的 Hardhat 部署脚本示例放在scripts/目录下// scripts/deploy-my-collectible.js const { ethers, upgrades } require(hardhat); async function main() { const MyCollectible await ethers.getContractFactory(MyCollectible); const mc await upgrades.deployProxy(MyCollectible); await mc.waitForDeployment(); console.log(MyCollectible deployed to:, await mc.getAddress()); } main();执行脚本后终端会打印代理地址上面console.log输出的MyCollectible deployed to:即为代理合约地址这是文档给出的部署完成标志。部署后如何验证初始化成功两个来自文档的验证依据Initialized事件。initializer修饰符在成功初始化后会发出event Initialized(uint64 version)事件见 Initializable.sol。在部署交易的回执中确认该事件版本为 1说明initialize()已成功执行过一次。重复调用应失败。initializer修饰符的语义是最多调用一次重复调用会 revertInvalidInitialization()。如果意外发现还能再次调用成功说明保护机制没有生效属于异常情况。另外注意底层代理的行为如果不用 Upgrades Plugins 而直接使用 ERC1967Proxy其构造函数要求传入_data即编码好的初始化调用_data为空时构造会失败——文档建议把initialize的编码调用作为_data尽早传入以避免代理停留在未初始化状态。使用deployProxy时插件会自动处理这一步。限制与边界多继承需要特别注意。initializer 函数不像构造函数那样由编译器线性化每个__{ContractName}_init内嵌了对所有父合约 initializer 的线性化调用因此两个init函数可能把同一个合约初始化两次。每个合约都提供__{ContractName}_init_unchained即去掉父调用后的 initializer可以手动规避双重初始化但文档不推荐手动这么做。命名空间存储ERC-7201。Upgradeable 包中的合约用带custom:storage-location erc7201:NAMESPACE_ID注解的 struct 存放状态变量每个合约拥有独立的存储命名空间。这使得日后新增状态变量不会推移继承链中下方变量的存储位置从而保持存储布局兼容。大版本存储不兼容。跨大版本如 4.x 到 5.0.0升级前按 Backwards Compatibility 文档确认存储布局并使用 Upgrades Plugins 检查存储兼容性。完成以上步骤后你就有了一个已完成初始化、地址可从部署日志或Initialized事件中确认的代理合约。后续的升级操作如upgradeProxy到新版本实现、用reinitializer(n)初始化新增模块属于 OpenZeppelin Upgrades Plugins 文档的范畴本仓库 upgradeable 文档 将其列为延伸阅读。【免费下载链接】openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development.项目地址: https://gitcode.com/GitHub_Trending/op/openzeppelin-contracts创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考