
如何使用 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),僅供參考