本文档介绍了如何在使用OpenZeppelin升级插件等工具部署可升级合约时,使用@openzeppelin/contracts-upgradeable
包。 该包通过使用initializer函数替换构造函数,并在小版本之间检查存储不兼容性,遵循可升级合约的编写规则,同时还讨论了多重继承和命名空间存储等问题。
如果你的合约将要以可升级性进行部署,例如使用 OpenZeppelin Upgrades Plugins,你将需要使用 OpenZeppelin Contracts 的 Upgradeable 变体。
这个变体可以作为一个单独的包 @openzeppelin/contracts-upgradeable
使用,它托管在 OpenZeppelin/openzeppelin-contracts-upgradeable 仓库中。它使用 @openzeppelin/contracts
作为 peer dependency。
它遵循 编写可升级合约 的所有规则:构造函数被初始化器函数替换,状态变量在初始化器函数中初始化,并且我们还会检查跨小版本之间的存储不兼容性。
OpenZeppelin 提供了一整套用于部署和保护可升级智能合约的工具。查看完整的资源列表。 |
$ npm install @openzeppelin/contracts-upgradeable @openzeppelin/contracts
Upgradeable 包复制了主 OpenZeppelin Contracts 包的结构,但是每个文件和合约都有后缀 Upgradeable
。
-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 包中,而是从主 OpenZeppelin Contracts 包中导入。 |
构造函数被内部初始化器函数替换,遵循命名约定 __{ContractName}_init
。由于这些是内部的,你必须始终定义你自己的公共初始化器函数,并调用你扩展的合约的父初始化器。
- constructor() ERC721("MyCollectible", "MCO") public {
+ function initialize() initializer public {
+ __ERC721_init("MyCollectible", "MCO");
}
与多重继承一起使用需要特别注意。请参阅下面标题为多重继承的部分。 |
一旦这个合约设置并编译完成,你可以使用 Upgrades Plugins 部署它。下面的代码片段展示了一个使用 Hardhat 的示例部署脚本。
// 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();
初始化器函数不像构造函数那样被编译器线性化。因此,每个 __{ContractName}_init
函数都嵌入了对所有父初始化器的线性化调用。因此,调用两个这样的 init
函数可能会多次初始化同一个合约。
在每个合约中找到的函数 __{ContractName}_init_unchained
是初始化器函数,减去了对父初始化器的调用,可以用来避免双重初始化问题,但是不建议手动执行此操作。我们希望能够在未来版本的 Upgrades Plugins 中为此实现安全检查。
你可能会注意到,合约使用带有 @custom:storage-location erc7201:<NAMESPACE_ID>
注释的结构来存储合约的状态变量。这遵循 ERC-7201: 命名空间存储布局 模式,其中每个合约在与其继承链中的其他合约分离的命名空间中都有自己的存储布局。
如果没有命名空间存储,简单地添加一个状态变量是不安全的,因为它会“向下移动”继承链中下面的所有状态变量。这使得存储布局不兼容,如 编写可升级合约 中所述。
Upgradeable 包中使用的命名空间存储模式允许我们在将来自由地添加新的状态变量,而不会损害与现有部署的存储兼容性。它还允许更改继承顺序,而不会影响最终的存储布局,只要所有继承的合约都使用命名空间存储。
- 原文链接: docs.openzeppelin.com/co...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!