本文档介绍了 OpenZeppelin Hardhat Upgrades 插件提供的 API,用于在Hardhat环境中使用OpenZeppelin Contracts进行智能合约的代理部署和升级。
deployProxy 和 upgradeProxy 函数都将返回 ethers.js 合约 的实例,并且需要 ethers.js 合约工厂 作为参数。 对于 信标,deployBeacon 和 upgradeBeacon 都将返回一个可升级的信标实例,该实例可以与信标代理一起使用。 所有部署和升级函数都会验证实现合约是否是升级安全的,否则将失败。
以下选项是一些函数通用的。
kind: ( "uups" | "transparent" | "beacon") 要部署、升级或导入的代理的类型,或实现将使用的代理的类型。 deployProxy() 和 upgradeProxy() 仅支持值 "uups" | "transparent"。 默认为 "transparent"。 请参阅 Transparent vs UUPS。
unsafeAllow: ( ValidationError[]) 有选择地禁用一个或多个验证错误或警告:
"external-library-linking": 允许部署链接到实现合约的外部库。(否则,外部库尚未支持。)
"struct-definition", "enum-definition": 过去部署具有结构体或枚举的合约是必需的。 现在不再需要。
"state-variable-assignment": 允许在合约中分配状态变量,即使它们将存储在实现中。
"state-variable-immutable": 允许使用 immutable 变量,这些变量不是不安全的
"constructor": 允许定义构造函数。 请参阅 constructorArgs。
"delegatecall", "selfdestruct": 允许使用这些操作。 不正确地使用此选项可能会使资金面临永久损失的风险。 请参阅 我可以安全地使用 delegatecall 和 selfdestruct 吗?
"missing-public-upgradeto": 允许不包含公共 upgradeTo 或 upgradeToAndCall 函数的 UUPS 实现。 启用此选项很可能会由于内置的 UUPS 安全机制而导致 revert。
"internal-function-storage": 允许在存储变量中使用内部函数。 内部函数是代码指针,升级后将不再有效,因此必须在升级期间重新分配。 请参阅 如何在存储变量中使用内部函数?
"missing-initializer": 允许未检测到初始化函数的实现。
"missing-initializer-call": 允许未从子初始化函数调用父初始化函数的实现。
"duplicate-initializer-call": 允许从子初始化函数多次调用父初始化函数的实现。
"incorrect-initializer-order": 允许未按线性化顺序调用父初始化函数的实现。 注意:默认情况下,此条件会显示警告,并且设置此选项将使警告静音。
unsafeAllowRenames: ( boolean) 配置存储布局检查以允许变量重命名。
unsafeSkipStorageCheck: ( boolean) 升级代理或信标,而不先检查存储布局兼容性错误。 这是一个危险的选项,旨在作为最后的手段使用。
constructorArgs: ( unknown[]) 提供实现合约的构造函数的参数。 请注意,这些参数与初始化函数参数不同,将用于实现合约本身的部署。 可用于初始化 immutable 变量。
initialOwner: ( string) 要设置为透明代理的管理员的初始所有者,或信标的初始所有者的地址。 默认为部署透明代理或信标的外部所有帐户。 不支持 UUPS 代理。
Since: @openzeppelin/hardhat-upgrades@3.0.0
unsafeSkipProxyAdminCheck: ( boolean) 部署透明代理时,跳过检查 initialOwner 选项。 部署透明代理时,initialOwner 必须是 EOA 或可以调用 ProxyAdmin 上的函数的合约的地址。 它不能是 ProxyAdmin 合约本身。 如果你遇到由于此检查而导致的错误,并且确定 initialOwner 不是 ProxyAdmin 合约,请使用此选项。
Since: @openzeppelin/hardhat-upgrades@3.4.0
timeout: ( number) 部署实现合约时,等待交易确认的超时时间(以毫秒为单位)。 默认为 60000。 使用 0 无限期等待。
pollingInterval: ( number) 部署实现合约时,检查交易确认之间的轮询间隔(以毫秒为单位)。 默认为 5000。
redeployImplementation: ( "always" | "never" | "onchange") 确定是否将重新部署实现合约。 默认为 "onchange"。
如果设置为 "always",即使之前已使用相同的字节码部署了实现合约,也会始终重新部署。 当通过 OpenZeppelin Defender 部署代理时,可以将它与 salt 选项一起使用,以确保使用与代理相同的 salt 部署实现合约。
如果设置为 "never",则永远不会重新部署实现合约。 如果之前未部署实现合约或未在网络文件中找到,将抛出错误。
如果设置为 "onchange",则仅当字节码与之前的部署相比发生更改时,才会重新部署实现合约。
txOverrides: ( ethers.Overrides) 一个 ethers.js Overrides 对象,用于覆盖交易参数,例如 gasLimit 和 gasPrice。 适用于此选项的函数发送的所有交易,即使该函数发送多个交易。 对于 OpenZeppelin Defender 部署,仅支持 gasLimit、gasPrice、maxFeePerGas 和 maxPriorityFeePerGas 参数。
useDefenderDeploy: ( boolean) 使用 OpenZeppelin Defender 而不是 ethers.js 部署合约。 请参阅 与 OpenZeppelin Defender 一起使用。
verifySourceCode: ( boolean) 使用 OpenZeppelin Defender 部署时,是否在区块浏览器上验证源代码。 默认为 true。
relayerId: ( string) 使用 OpenZeppelin Defender 部署时,用于部署的中继器的 ID。 默认为在 Defender 上为你的部署环境配置的中继器。
salt: ( string) 使用 OpenZeppelin Defender 部署时,如果未设置此选项,将使用 CREATE 操作码执行部署。 如果设置了此选项,将使用 CREATE2 操作码和提供的 salt 执行部署。 请注意,使用 Safe 的部署是使用 CREATE2 完成的,并且需要 salt。 警告:CREATE2 影响 msg.sender 行为。 有关更多信息,请参阅 注意事项。
metadata: ( { commitHash?: string; tag?: string; [k: string]: any; }) 使用 OpenZeppelin Defender 部署时,可以使用此选项来标识、标记或分类部署。 请参阅 元数据。
proxyFactory: ( ethers.ContractFactory) 自定义用于部署代理的 ethers 合约工厂,允许部署自定义代理合约。 请参阅 factories.ts,了解每种代理类型的默认合约工厂。
Since: @openzeppelin/hardhat-upgrades@3.7.0
deployFunction: ( (hre, opts, factory, …args) ⇒ Promise<EthersOrDefenderDeployment>) 自定义用于部署代理的函数。 可以与 proxyFactory 选项一起使用,以覆盖自定义代理部署的构造函数参数。 请参阅 deploy.ts,了解默认的部署函数。
Since: @openzeppelin/hardhat-upgrades@3.7.0
请注意,如果使用 Solidity >=0.8.2,也可以直接在源代码中以更精细的方式指定选项 unsafeAllow。 请参阅 如何禁用某些检查?
以下选项已被弃用。
unsafeAllowLinkedLibraries: 等效于在 unsafeAllow 中包含 "external-library-linking"。
unsafeAllowCustomTypes: 等效于在 unsafeAllow 中包含 "struct-definition" 和 "enum-definition"。 现在不再需要。
useDeployedImplementation: ( boolean) 等效于将 redeployImplementation 设置为 "never"。
async function deployProxy(
Contract: ethers.ContractFactory,
args: unknown[] = [],
opts?: {
initializer?: string | false,
unsafeAllow?: ValidationError[],
constructorArgs?: unknown[],
initialOwner?: string,
unsafeSkipProxyAdminCheck?: boolean,
timeout?: number,
pollingInterval?: number,
redeployImplementation?: 'always' | 'never' | 'onchange',
txOverrides?: ethers.Overrides,
kind?: 'uups' | 'transparent',
useDefenderDeploy?: boolean,
proxyFactory?: ethers.ContractFactory,
deployFunction?: () => Promise<EthersOrDefenderDeployment>,
},
): Promise<ethers.Contract>
给定一个 ethers 合约工厂用作实现来创建 UUPS 或 Transparent 代理,并返回一个具有代理地址和实现接口的合约实例。 如果设置了 args,将在代理部署期间使用提供的 args 调用初始化函数 initialize。
如果你为同一实现合约多次调用 deployProxy,将部署多个代理,但只会使用一个实现合约。
参数:
Contract - 用作实现的 ethers 合约工厂。
args - 初始化函数的参数。
opts - 包含选项的对象:
initializer:设置要调用的其他初始化函数(请参阅 指定片段),或指定 false 以禁用初始化。
其他选项如 常用选项 中所述。
返回:
async function upgradeProxy(
proxy: string | ethers.Contract,
Contract: ethers.ContractFactory,
opts?: {
call?: string | { fn: string; args?: unknown[] },
unsafeAllow?: ValidationError[],
unsafeAllowRenames?: boolean,
unsafeSkipStorageCheck?: boolean,
constructorArgs?: unknown[],
timeout?: number,
pollingInterval?: number,
redeployImplementation?: 'always' | 'never' | 'onchange',
txOverrides?: ethers.Overrides,
kind?: 'uups' | 'transparent',
},
): Promise<ethers.Contract>
将指定地址的 UUPS 或 Transparent 代理升级到新的实现合约,并返回一个具有代理地址和新实现接口的合约实例。
参数:
proxy - 代理地址或代理合约实例。
Contract - 用作新实现的 ethers 合约工厂。
opts - 包含选项的对象:
call:允许在升级过程中执行任意函数调用。 使用函数名称、签名或选择器(请参阅 指定片段)和可选参数来描述此调用。 它被批量处理到升级交易中,从而可以安全地调用迁移初始化函数。
其他选项如 常用选项 中所述。
返回:
async function deployBeacon(
Contract: ethers.ContractFactory,
opts?: {
unsafeAllow?: ValidationError[],
constructorArgs?: unknown[],
initialOwner?: string,
timeout?: number,
pollingInterval?: number,
redeployImplementation?: 'always' | 'never' | 'onchange',
txOverrides?: ethers.Overrides,
},
): Promise<ethers.Contract>
给定一个 ethers 合约工厂用作实现来创建 可升级信标,并返回信标合约实例。
参数:
Contract - 用作实现的 ethers 合约工厂。
opts - 包含选项的对象:
其他选项如 常用选项 中所述。
返回:
Since:
@openzeppelin/hardhat-upgrades@1.13.0async function upgradeBeacon(
beacon: string | ethers.Contract,
Contract: ethers.ContractFactory,
opts?: {
unsafeAllow?: ValidationError[],
unsafeAllowRenames?: boolean,
unsafeSkipStorageCheck?: boolean,
constructorArgs?: unknown[],
timeout?: number,
pollingInterval?: number,
redeployImplementation?: 'always' | 'never' | 'onchange',
txOverrides?: ethers.Overrides,
},
): Promise<ethers.Contract>
将指定地址的 可升级信标 升级到新的实现合约,并返回信标合约实例。
参数:
beacon - 信标地址或信标合约实例。
Contract - 用作新实现的 ethers 合约工厂。
opts - 包含选项的对象:
其他选项如 常用选项 中所述。
返回:
Since:
@openzeppelin/hardhat-upgrades@1.13.0async function deployBeaconProxy(
beacon: string | ethers.Contract,
attachTo: ethers.ContractFactory,
args: unknown[] = [],
opts?: {
initializer?: string | false,
txOverrides?: ethers.Overrides,
useDefenderDeploy?: boolean,
proxyFactory?: ethers.ContractFactory,
deployFunction?: () => Promise<EthersOrDefenderDeployment>,
},
): Promise<ethers.Contract>
给定一个现有的信标合约地址和一个与信标的当前实现合约相对应的 ethers 合约工厂,来创建一个 信标代理,并返回一个具有信标代理地址和实现接口的合约实例。 如果设置了 args,将在代理部署期间使用提供的 args 调用初始化函数 initialize。
参数:
beacon - 信标地址或信标合约实例。
attachTo - 与信标的当前实现合约相对应的 ethers 合约工厂。
args - 初始化函数的参数。
opts - 包含选项的对象:
initializer:设置要调用的其他初始化函数(请参阅 指定片段),或指定 false 以禁用初始化。
其他选项如 常用选项 中所述。
返回:
Since:
@openzeppelin/hardhat-upgrades@1.13.0async function forceImport(
address: string,
deployedImpl: ethers.ContractFactory,
opts?: {
kind?: 'uups' | 'transparent' | 'beacon',
},
): Promise<ethers.Contract>
强制导入现有的代理、信标或实现合约部署,以与此插件一起使用。 提供现有代理、信标或实现的地址,以及已部署的实现合约的 ethers 合约工厂。
导入代理或信标时,deployedImpl 参数必须是正在使用的当前实现合约版本的合约工厂,而不是你计划升级到的版本。 |
使用此函数通过导入先前的部署来重新创建丢失的 网络文件,或者注册代理或信标以进行升级,即使它们最初不是由此插件部署的。 支持 UUPS、Transparent 和 Beacon 代理,以及信标和实现合约。
参数:
address - 现有代理、信标或实现的地址。
deployedImpl - 已部署的实现合约的 ethers 合约工厂。
opts - 包含选项的对象:
kind: ( "uups" | "transparent" | "beacon") 强制将代理视为 UUPS、Transparent 或 Beacon 代理。 如果未提供,将自动检测代理类型。
返回:
Since
@openzeppelin/hardhat-upgrades@1.15.0async function validateImplementation(
Contract: ethers.ContractFactory,
opts?: {
unsafeAllow?: ValidationError[],
kind?: 'uups' | 'transparent' | 'beacon',
},
): Promise<void>
验证实现合约,而不部署它。
参数:
Contract - 实现合约的 ethers 合约工厂。
opts - 包含选项的对象:
其他选项如 常用选项 中所述。
Since:
@openzeppelin/hardhat-upgrades@1.20.0async function deployImplementation(
Contract: ethers.ContractFactory,
opts?: {
unsafeAllow?: ValidationError[],
constructorArgs?: unknown[],
timeout?: number,
pollingInterval?: number,
redeployImplementation?: 'always' | 'never' | 'onchange',
txOverrides?: ethers.Overrides,
getTxResponse?: boolean,
kind?: 'uups' | 'transparent' | 'beacon',
useDefenderDeploy?: boolean,
},
): Promise<string | ethers.providers.TransactionResponse>
验证并部署实现合约,并返回其地址。
参数:
Contract - 用作实现的 ethers 合约工厂。
opts - 包含选项的对象:
getTxResponse: 如果设置为 true,则使此函数返回与新实现合约的部署相对应的 ethers 交易响应,而不是其地址。 请注意,如果最初由于 forceImport 导入了新的实现合约,则只会返回该地址。
其他选项如 常用选项 中所述。
返回:
Since:
@openzeppelin/hardhat-upgrades@1.20.0async function validateUpgrade(
referenceAddressOrContract: string | ethers.ContractFactory,
newContract: ethers.ContractFactory,
opts?: {
unsafeAllow?: ValidationError[],
unsafeAllowRenames?: boolean,
unsafeSkipStorageCheck?: boolean,
kind?: 'uups' | 'transparent' | 'beacon',
},
): Promise<void>
验证新的实现合约,而不部署它,也不实际升级到它。 将当前的实现合约与新的实现合约进行比较,以检查存储布局兼容性错误。 如果 referenceAddressOrContract 是当前的实现地址,则需要 kind 选项。
参数:
referenceAddressOrContract - 使用当前实现的代理或信标地址,或与当前实现相对应的地址或 ethers 合约工厂。
newContract - 新的实现合约。
opts - 包含选项的对象:
其他选项如 常用选项 中所述。
Since:
@openzeppelin/hardhat-upgrades@1.20.0示例:
验证将现有代理升级到新合约(将 PROXY_ADDRESS 替换为你的代理地址):
const { ethers, upgrades } = require('hardhat');
const BoxV2 = await ethers.getContractFactory('BoxV2');
await upgrades.validateUpgrade(PROXY_ADDRESS, BoxV2);
验证两个合约实现之间的升级:
const { ethers, upgrades } = require('hardhat');
const Box = await ethers.getContractFactory('Box');
const BoxV2 = await ethers.getContractFactory('BoxV2');
await upgrades.validateUpgrade(Box, BoxV2);
async function prepareUpgrade(
referenceAddressOrContract: string | ethers.Contract,
Contract: ethers.ContractFactory,
opts?: {
unsafeAllow?: ValidationError[],
unsafeAllowRenames?: boolean,
unsafeSkipStorageCheck?: boolean,
constructorArgs?: unknown[],
timeout?: number,
pollingInterval?: number,
redeployImplementation?: 'always' | 'never' | 'onchange',
txOverrides?: ethers.Overrides,
getTxResponse?: boolean,
kind?: 'uups' | 'transparent' | 'beacon',
useDefenderDeploy?: boolean,
},
): Promise<string | ethers.providers.TransactionResponse>
验证并部署新的实现合约,并返回其地址。 如果 referenceAddressOrContract 是当前的实现地址,则需要 kind 选项。 使用此方法准备从你不直接控制或无法从 Hardhat 使用的管理地址运行的升级。
参数:
referenceAddressOrContract - 代理、信标或实现地址或合约实例。
Contract - 新的实现合约。
opts - 包含选项的对象:
getTxResponse: 如果设置为 true,则使此函数返回与新实现合约的部署相对应的 ethers 交易响应,而不是其地址。 请注意,如果最初由于 forceImport 导入了新的实现合约,则 ethers 交易响应将未定义。
其他选项如 常用选项 中所述。
返回:
async function deployContract(
Contract: ethers.ContractFactory,
args: unknown[] = [],
opts?: {
unsafeAllowDeployContract?: boolean,
pollingInterval?: number,
},
): Promise<ethers.Contract>
使用 OpenZeppelin Defender 部署不可升级的合约,并返回合约实例。 如果合约看起来像实现合约,则抛出错误。
| 不要使用此函数来部署可升级合约的实现,因为此函数不执行升级安全验证。 对于实现合约,请改用 deployImplementation。 |
参数:
Contract - 用作要部署的合约的 ethers 合约工厂。
opts - 包含选项的对象:
unsafeAllowDeployContract: 如果设置为 true,则即使合约看起来像实现合约,也允许部署合约。 默认为 false。
pollingInterval:在对结果合约实例调用 .waitForDeployment() 时,检查交易确认之间的轮询间隔(以毫秒为单位)。 默认为 5000。
返回:
Since:
@openzeppelin/hardhat-upgrades@2.2.0async function getDeployApprovalProcess(
): Promise<{
approvalProcessId: string,
address?: string,
viaType?: 'EOA' | 'Contract' | 'Multisig' | 'Safe' | 'Gnosis Multisig' | 'Relayer' | 'Unknown' | 'Timelock Controller' | 'ERC20' | 'Governor' | 'Fireblocks',
}>
获取在 OpenZeppelin Defender 上为你的部署环境配置的默认部署审批流程。
返回:
Since:
@openzeppelin/hardhat-upgrades@2.5.0async function getUpgradeApprovalProcess(
): Promise<{
approvalProcessId: string,
address?: string,
viaType?: 'EOA' | 'Contract' | 'Multisig' | 'Safe' | 'Gnosis Multisig' | 'Relayer' | 'Unknown' | 'Timelock Controller' | 'ERC20' | 'Governor' | 'Fireblocks',
}>
获取在 OpenZeppelin Defender 上为你的部署环境配置的默认升级审批流程。 例如,这对于确定你可以在脚本中使用的默认多重签名钱包以分配为代理的所有者非常有用。
返回:
Since:
@openzeppelin/hardhat-upgrades@2.5.0async function proposeUpgradeWithApproval(
proxyAddress: string,
ImplFactory: ContractFactory,
opts?: {
unsafeAllow?: ValidationError[],
unsafeAllowRenames?: boolean,
unsafeSkipStorageCheck?: boolean,
constructorArgs?: unknown[],
timeout?: number,
pollingInterval?: number,
redeployImplementation?: 'always' | 'never' | 'onchange',
kind?: 'uups' | 'transparent' | 'beacon',
useDefenderDeploy?: boolean,
approvalProcessId?: string,
},
): Promise<{
proposalId: string,
url: string,
txResponse?: ethers.providers.TransactionResponse,
}>
使用 OpenZeppelin Defender 上的升级审批流程来提议升级。
类似于 prepareUpgrade。 此方法验证并部署新的实现合约,但也会使用 OpenZeppelin Defender 上的升级审批流程来提议升级。 支持 UUPS 或 Transparent 代理。 目前不支持信标代理或信标。 对于信标,请使用 prepareUpgrade 以及 Defender 上的交易提案,以将信标升级到已部署的实现。
参数:
proxyAddress - 代理地址。
ImplFactory - 新的实现合约。
opts - 包含选项的对象:
approvalProcessId:升级审批流程的 ID。 默认为在 Defender 上为你的部署环境配置的升级审批流程。
其他选项如 常用选项 中所述。
返回:
forceImport 导入了新的实现合约,则 ethers 交易响应将未定义。Since:
@openzeppelin/hardhat-upgrades@2.2.0async function changeProxyAdmin(
proxyAddress: string,
newAdmin: string,
signer?: ethers.Signer,
opts?: {
txOverrides?: ethers.Overrides,
}
): Promise<void>
更改特定代理的管理。
| 此函数不支持 OpenZeppelin Contracts 5.x 中的管理员或代理。 |
参数:
proxyAddress - 要更改的代理的地址。
newAdmin - 新的管理地址。
signer - 用于交易的签名者。
opts - 包含选项的对象:
其他选项如 常用选项 中所述。
async function transferProxyAdminOwnership(
proxyAddress: string,
newOwner: string,
signer?: ethers.Signer,
opts?: {
silent?: boolean,
txOverrides?: ethers.Overrides,
}
): Promise<void>
更改特定代理的代理管理合约的所有者。
自 @openzeppelin/hardhat-upgrades@3.0.0 起,proxyAddress 参数是必需的 |
参数:
proxyAddress - 要转移其管理所有者的代理的地址。
newOwner - 代理管理合约的新所有者地址。
signer - 用于交易的签名者。
opts - 包含选项的对象:
silent:如果设置为 true,则会使有关受管理所有权转移影响的每个代理的控制台日志静音。
其他选项如 常用选项 中所述。
Since:
@openzeppelin/hardhat-upgrades@3.0.0async function erc1967.getImplementationAddress(proxyAddress: string): Promise<string>;
async function erc1967.getBeaconAddress(proxyAddress: string): Promise<string>;
async function erc1967.getAdminAddress(proxyAddress: string): Promise<string>;
此模块中的函数提供对代理合约的 ERC1967 变量的访问。
参数:
proxyAddress - 代理地址。返回:
async function beacon.getImplementationAddress(beaconAddress: string): Promise<string>;
此模块提供了一个便捷函数,用于从信标合约获取实现地址。
参数:
beaconAddress - 信标地址。返回:
Since:
@openzeppelin/hardhat-upgrades@1.13.0function silenceWarnings()
| 此函数对于测试很有用,但不鼓励在生产部署脚本中使用它。 |
使所有后续关于使用不安全标志的警告静音。 在执行此操作之前,打印一条最后的警告。
扩展 hardhat-verify 的 verify 任务,以在 Etherscan 上完全验证代理。 这支持验证由 Hardhat Upgrades 插件部署的代理合约。
参数与 hardhat-verify 的 verify 任务相同。 如果提供的地址是代理,则此任务将验证代理的实现合约、代理本身和任何与代理相关的合约,以及将代理链接到 Etherscan 上的实现合约的 ABI。 如果提供的地址不是代理,则将改为在该地址上运行 hardhat-verify 中的常规 verify 任务。
当你在代理地址上运行此任务时,将验证以下合约:
你的实现合约
ERC1967Proxy 或 TransparentUpgradeableProxy 或 BeaconProxy(分别用于 UUPS、透明或信标代理)
ProxyAdmin(使用透明代理)
UpgradeableBeacon(使用信标代理)
Since:
@openzeppelin/hardhat-upgrades@2.0.0用法:
要使用此任务,请确保已安装 hardhat-verify:
npm install --save-dev @nomicfoundation/hardhat-verify
然后在你的 Hardhat 配置中导入 @nomicfoundation/hardhat-verify 插件以及 @openzeppelin/hardhat-upgrades 插件。
例如,如果你使用 JavaScript,请在 hardhat.config.js 中导入插件:
require("@nomicfoundation/hardhat-verify");
require("@openzeppelin/hardhat-upgrades");
或者,如果你使用 TypeScript,请在 hardhat.config.ts 中导入插件:
import "@nomicfoundation/hardhat-verify";
import "@openzeppelin/hardhat-upgrades";
最后,按照 hardhat-verify 的用法文档 配置你的 Etherscan API 密钥,并从命令行使用代理地址运行 verify 任务:
npx hardhat verify --network mainnet PROXY_ADDRESS
或以编程方式使用 verify:verify 子任务:
await hre.run("verify:verify", {
address: PROXY_ADDRESS,
});
请注意,如果你的实现合约仅使用初始化函数,则在验证时无需包含构造函数参数。 但是,如果你的实现合约具有带有参数的实际构造函数(例如,用于设置 immutable 变量),则根据 [
- 原文链接: docs.openzeppelin.com/up...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!