本文档介绍了OpenZeppelin Upgrades Core库和CLI工具,用于检测智能合约的升级安全性及存储布局兼容性。重点介绍了CLI的validate命令,以及High-Level API和Low-Level API的使用方法,包括环境配置、参数说明和返回结果,方便开发者在开发流程中尽早发现和解决潜在的升级问题。
@openzeppelin/upgrades-core
包提供了一个 validate
命令,用于检查可升级合约的升级安全性和存储布局兼容性。它可以在你的整个开发过程中使用,以确保你的合约是升级安全的,并且与之前的版本兼容。
它还提供了以编程方式执行这些检查的 API,并且包含了使用 OpenZeppelin Upgrades 插件执行这些检查的核心逻辑。
从包含构建信息文件的目录中检测可升级合约,并验证它们是否是升级安全的。如果你想从命令行、脚本或 CI/CD 管道中验证你项目的所有可升级合约,请使用此命令。
“构建信息文件” 由你的编译工具链(Hardhat, Foundry)生成,包含编译过程的输入和输出。 |
在使用 validate
命令之前,你必须定义可升级合约,以便可以检测和验证它们,定义用于存储布局比较的参考合约,并编译你的合约。
validate
命令对看起来像可升级合约的合约执行升级安全检查。具体来说,它对满足以下任何条件的实现合约执行检查:
继承自 Initializable
。
具有 upgradeTo(address)
或 upgradeToAndCall(address,bytes)
函数。对于继承自 UUPSUpgradeable
的合约来说是这种情况。
具有 NatSpec 注释 @custom:oz-upgrades
具有 NatSpec 注释 @custom:oz-upgrades-from <reference>
,根据下面的定义参考合约。
只需将 NatSpec 注释 @custom:oz-upgrades 或 @custom:oz-upgrades-from <reference> 添加到每个实现合约,以便它可以被检测为用于验证的可升级合约。 |
如果一个实现合约打算作为对现有代理的升级来部署,你 必须 定义一个参考合约用于存储布局比较。 否则,如果存在任何存储布局不兼容,你将不会收到错误。 |
通过将 NatSpec 注释 @custom:oz-upgrades-from <reference>
添加到你的实现合约来定义参考合约,其中 <reference>
是用于存储布局比较的参考合约的合约名称或完全限定的合约名称。该合约不需要在作用域内,如果它在项目中是不明确的,则合约名称就足够了。
例子:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:oz-upgrades-from MyContractV1
contract MyContractV2 {
...
}
或者:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @custom:oz-upgrades-from contracts/MyContract.sol:MyContractV1
contract MyContractV2 {
...
}
编译你的合约,并确保你的构建配置为在构建信息目录中输出包含 Solidity 编译器输入和输出的 JSON 文件。编译器输出必须包括存储布局。如果存在任何以前的构建工件,必须首先清除它们以避免重复的合约定义。
配置 hardhat.config.js
或 hardhat.config.ts
以在输出选择中包含存储布局:
module.exports = {
solidity: {
settings: {
outputSelection: {
'*': {
'*': ['storageLayout'],
},
},
},
},
};
然后编译你的合约:
npx hardhat clean && npx hardhat compile
配置 foundry.toml
以包含构建信息和存储布局:
[profile.default]
build_info = true
extra_output = ["storageLayout"]
然后编译你的合约:
forge clean && forge build
在执行完前提条件之后,运行 npx @openzeppelin/upgrades-core validate
命令来验证你的合约:
npx @openzeppelin/upgrades-core validate [<BUILD_INFO_DIR>] [<OPTIONS>]
如果发现任何错误,该命令将以非零退出代码退出,并将错误的详细报告打印到控制台。
参数:
<BUILD_INFO_DIR>
- 可选的构建信息目录路径,其中包含带有 Solidity 编译器输入和输出的 JSON 文件。对于 Hardhat 项目,默认为 artifacts/build-info
,对于 Foundry 项目,默认为 out/build-info
。如果你的项目使用自定义输出目录,你必须在此处指定其构建信息目录。选项:
--contract <CONTRACT>
- 要验证的合约的名称或完全限定名称。如果未指定,将验证构建信息目录中的所有可升级合约。
--reference <REFERENCE_CONTRACT>
- 只能在同时提供 --contract
选项时使用。 用于存储布局比较的参考合约的名称或完全限定名称。如果未指定,则使用 @custom:oz-upgrades-from
注释(如果它定义在正在验证的合约中)。
--requireReference
- 只能在同时提供 --contract
选项时使用。 与 --unsafeSkipStorageCheck
不兼容。如果指定,则需要提供 --reference
选项或合约具有 @custom:oz-upgrades-from
注释。
--referenceBuildInfoDirs "<BUILD_INFO_DIR>[,<BUILD_INFO_DIR>…]"
- 用于存储布局比较的项目先前版本的其他构建信息目录的可选路径。使用此选项时,在使用 --reference
选项或 @custom:oz-upgrades-from
注释中的合约名称或完全限定名称之前,使用前缀 <dirName>:
引用这些目录之一,其中 <dirName>
是目录短名称。每个目录短名称必须是唯一的,包括与主构建信息目录相比。如果传入多个目录,请用逗号分隔它们或多次调用该选项,每个目录一次。
--exclude "<GLOB_PATTERN>" [--exclude "<GLOB_PATTERN>"…]
- 排除对源文件路径中与任何给定 glob 模式匹配的合约的验证。例如,--exclude "contracts/mocks/**/*.sol"
。不适用于参考合约。如果传入多个模式,请多次调用该选项,每个模式一次。
--unsafeAllow "<VALIDATION_ERROR>[,<VALIDATION_ERROR>…]"
- 有选择地禁用一个或多个验证错误或警告。 逗号分隔的列表,其中包含以下一个或多个:
错误:state-variable-assignment, state-variable-immutable, external-library-linking, struct-definition, enum-definition, constructor, delegatecall, selfdestruct, missing-public-upgradeto, internal-function-storage, missing-initializer, missing-initializer-call, duplicate-initializer-call
警告:incorrect-initializer-order
--unsafeAllowRenames
- 配置存储布局检查以允许变量重命名。
--unsafeSkipStorageCheck
- 跳过检查存储布局兼容性错误。这是一个危险的选项,旨在作为最后的手段使用。
high-level API 是 validate 命令 的编程等效项。如果要从 JavaScript 或 TypeScript 环境验证项目的所有可升级合约,请使用此 API。
与 validate 命令 的前提条件相同。
导入 validateUpgradeSafety
函数:
import { validateUpgradeSafety } from '@openzeppelin/upgrades-core';
然后调用该函数以验证你的合约,并获取包含验证结果的项目报告。
validateUpgradeSafety(
buildInfoDir?: string,
contract?: string,
reference?: string,
opts: ValidateUpgradeSafetyOptions = {},
referenceBuildInfoDirs?: string[],
exclude?: string[],
): Promise<ProjectReport>
从构建信息目录中检测可升级合约,并验证它们是否是升级安全的。返回包含结果的项目报告。
请注意,此函数不会直接抛出验证错误。 相反,你必须使用项目报告来确定是否发现任何错误。
参数:
buildInfoDir
- 构建信息目录的路径,其中包含带有 Solidity 编译器输入和输出的 JSON 文件。对于 Hardhat 项目,默认为 artifacts/build-info
,对于 Foundry 项目,默认为 out/build-info
。如果你的项目使用自定义输出目录,你必须在此处指定其构建信息目录。
contract
- 要验证的合约的名称或完全限定名称。如果未指定,将验证构建信息目录中的所有可升级合约。
reference
- 只能在同时提供 contract
参数时使用。 用于存储布局比较的参考合约的名称或完全限定名称。如果未指定,则使用 @custom:oz-upgrades-from
注释(如果它定义在正在验证的合约中)。
opts
- 一个对象,其中包含 通用选项 中定义的以下选项:
unsafeAllow
unsafeAllowRenames
unsafeSkipStorageCheck
requireReference
- 只能在同时提供 contract
参数时使用。与 unsafeSkipStorageCheck
选项不兼容。如果指定,则需要提供 reference
参数或合约具有 @custom:oz-upgrades-from
注释。
referenceBuildInfoDirs
- 用于存储布局比较的项目先前版本的其他构建信息目录的可选路径。使用此选项时,在使用 reference
参数或 @custom:oz-upgrades-from
注释中的合约名称或完全限定名称之前,使用前缀 <dirName>:
引用这些目录之一,其中 <dirName>
是目录短名称。每个目录短名称必须是唯一的,包括与主构建信息目录相比。
exclude
- 排除对源文件路径中与任何给定 glob 模式匹配的合约的验证。
返回:
interface ProjectReport {
ok: boolean;
explain(color?: boolean): string;
numPassed: number;
numTotal: number;
}
一个对象,表示升级安全检查和存储布局比较的结果,并包含所有发现的错误的报告。
成员:
ok
- 如果发现任何错误,则为 false
,否则为 true
。
explain()
- 返回一条详细解释错误的详细信息的消息(如果有)。
numPassed
- 通过升级安全检查的合约数量。
numTotal
- 检测到的可升级合约的总数。
此 low-level API 已弃用。 请改用 High-Level API。 |
low-level API 使用 Solidity 输入和输出 JSON 对象,并允许你对单个合约执行升级安全检查和存储布局比较。 如果要验证特定合约而不是整个项目,请使用此 API。
编译你的合约以生成 Solidity 输入和输出 JSON 对象。 编译器输出必须包括存储布局。
请注意,validate 命令 中的其他前提条件不是必需的,因为 low-level API 不会自动检测可升级合约。 相反,你必须为你想要验证的每个实现合约创建一个 UpgradeableContract
的实例,并调用它上面的函数以获取升级安全和存储布局报告。
导入 UpgradeableContract
类:
import { UpgradeableContract } from '@openzeppelin/upgrades-core';
然后为你想要验证的每个实现合约创建一个 UpgradeableContract
的实例,并调用它上面的 .getErrorReport()
和/或 .getStorageLayoutReport()
以分别获取升级安全和存储布局报告。
此类表示可升级合约的实现,并提供对错误报告的访问。
constructor UpgradeableContract(
name: string,
solcInput: SolcInput,
solcOutput: SolcOutput,
opts?: {
unsafeAllow?: ValidationError[],
unsafeAllowRenames?: boolean,
unsafeSkipStorageCheck?: boolean,
kind?: 'uups' | 'transparent' | 'beacon',
},
solcVersion?: string,
): UpgradeableContract
创建一个新的 UpgradeableContract
实例。
参数:
name
- 实现合约的名称,可以是完全限定的名称或合约名称。 如果多个合约具有相同的名称,则必须使用完全限定的名称,例如 contracts/Bar.sol:Bar
。
solcInput
- 实现合约的 Solidity 输入 JSON 对象。
solcOutput
- 实现合约的 Solidity 输出 JSON 对象。
opts
- 一个对象,其中包含 通用选项 中定义的以下选项:
kind
unsafeAllow
unsafeAllowRenames
unsafeSkipStorageCheck
solcVersion
- 用于编译实现合约的 Solidity 版本。
在 Hardhat 中,solcInput 和 solcOutput 可以从 Build Info 文件中获取,该文件本身可以使用 hre.artifacts.getBuildInfo 检索。 |
getErrorReport(): Report
返回:
selfdestruct
。getStorageUpgradeReport(
upgradedContract: UpgradeableContract,
opts?: {
unsafeAllow?: ValidationError[],
unsafeAllowRenames?: boolean,
unsafeSkipStorageCheck?: boolean,
kind?: 'uups' | 'transparent' | 'beacon',
},
): Report
将可升级合约的存储布局与建议的升级的存储布局进行比较。
参数:
upgradedContract
- 另一个 UpgradeableContract
实例,表示建议的升级。
opts
- 一个对象,其中包含 通用选项 中定义的以下选项:
kind
unsafeAllow
unsafeAllowRenames
unsafeSkipStorageCheck
返回:
selfdestruct
和存储布局冲突。interface Report {
ok: boolean;
explain(color?: boolean): string;
}
一个对象,表示分析的结果。
成员:
ok
- 如果发现任何错误,则为 false
,否则为 true
。
explain()
- 返回一条详细解释错误的详细信息的消息(如果有)。
- 原文链接: docs.openzeppelin.com/up...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!