OpenZeppelin Upgrades Core & CLI

The @openzeppelin/upgrades-core package provides a validate command to check for upgrade safety and storage layout compatibility in upgradeable contracts. It can be used throughout your development process to ensure that your contracts are upgrade safe and compatible with previous versions.

It also provides APIs to perform these checks programmatically, and contains the core logic for these checks to be performed with the OpenZeppelin Upgrades plugins.

CLI: Validate Command

Detects upgradeable contracts from a directory containing build info files and validates whether they are upgrade safe. Use this if you want to validate all of your project’s upgradeable contracts from the command line, in a script, or as part of your CI/CD pipeline.

"Build info files" are generated by your compilation toolchain (Hardhat, Foundry) and contain the inputs and outputs of the compilation process.

Prerequisites

Before using the validate command, you must define upgradeable contracts so that they can be detected and validated, define reference contracts for storage layout comparisons, and compile your contracts.

Define Upgradeable Contracts

The validate command performs upgrade safety checks on contracts that look like upgradeable contracts. Specifically, it performs checks on implementation contracts that meet any of the following criteria:

  • Inherits Initializable.

  • Has an upgradeTo(address) or upgradeToAndCall(address,bytes) function. This is the case for contracts that inherit UUPSUpgradeable.

  • Has the NatSpec annotation @custom:oz-upgrades

  • Has the NatSpec annotation @custom:oz-upgrades-from <reference> according to Define Reference Contracts below.

Simply add the NatSpec annotation @custom:oz-upgrades or @custom:oz-upgrades-from <reference> to each implementation contract so that it can be detected as an upgradeable contract for validation.

Define Reference Contracts

If an implementation contract is meant to deployed as an upgrade to an existing proxy, you MUST define a reference contract for storage layout comparisons. Otherwise, you will not receive errors if there are any storage layout incompatibilities.

Define a reference contract by adding the NatSpec annotation @custom:oz-upgrades-from <reference> to your implementation contract, where <reference> is the contract name or fully qualified contract name of the reference contract to use for storage layout comparisons. The contract does not need to be in scope, and the contract name will suffice if it is unambiguous across the project.

Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @custom:oz-upgrades-from MyContractV1
contract MyContractV2 {
  ...
}

Or:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @custom:oz-upgrades-from contracts/MyContract.sol:MyContractV1
contract MyContractV2 {
  ...
}

Compile Contracts with Storage Layouts

Compile your contracts and ensure that your build is configured to output JSON files with Solidity compiler inputs and outputs in a build info directory. The compiler output must include storage layouts. If any previous build artifacts exist, they must be cleaned first to avoid duplicate contract definitions.

Hardhat

Configure hardhat.config.js or hardhat.config.ts to include storage layout in the output selection:

module.exports = {
  solidity: {
    settings: {
      outputSelection: {
        '*': {
          '*': ['storageLayout'],
        },
      },
    },
  },
};

Then compile your contracts:

npx hardhat clean && npx hardhat compile
Foundry

Configure foundry.toml to include build info and storage layout:

[profile.default]
build_info = true
extra_output = ["storageLayout"]

Then compile your contracts:

forge clean && forge build

Usage

After performing the prerequisites, run the npx @openzeppelin/upgrades-core validate command to validate your contracts:

npx @openzeppelin/upgrades-core validate [<BUILD_INFO_DIR>] [<OPTIONS>]

If any errors are found, the command will exit with a non-zero exit code and print a detailed report of the errors to the console.

Parameters:

  • <BUILD_INFO_DIR> - Optional path to the build info directory which contains JSON files with Solidity compiler input and output. Defaults to artifacts/build-info for Hardhat projects or out/build-info for Foundry projects. If your project uses a custom output directory, you must specify its build info directory here.

Options:

  • --contract <CONTRACT> The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated.

  • --reference <REFERENCE_CONTRACT> Can only be used when the --contract option is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the @custom:oz-upgrades-from annotation if it is defined in the contract that is being validated.

  • --requireReference Can only be used when the --contract option is also provided. Not compatible with --unsafeSkipStorageCheck. If specified, requires either the --reference option to be provided or the contract to have a @custom:oz-upgrades-from annotation.

  • --unsafeAllow "<VALIDATION_ERRORS>" - Selectively disable one or more validation errors. Comma-separated list with one or more of the following: state-variable-assignment, state-variable-immutable, external-library-linking, struct-definition, enum-definition, constructor, delegatecall, selfdestruct, missing-public-upgradeto

  • --unsafeAllowRenames - Configure storage layout check to allow variable renaming.

  • --unsafeSkipStorageCheck - Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort.

High-Level API

The high-level API is a programmatic equivalent to the validate command. Use this API if you want to validate all of your project’s upgradeable contracts from a JavaScript or TypeScript environment.

Prerequisites

Same prerequisites as the validate command.

Usage

Import the validateUpgradeSafety function:

import { validateUpgradeSafety } from '@openzeppelin/upgrades-core';

Then call the function to validate your contracts and get a project report with the validation results.

validateUpgradeSafety

validateUpgradeSafety(
  buildInfoDir?: string,
  contract?: string,
  reference?: string,
  opts: ValidateUpgradeSafetyOptions = {},
): Promise<ProjectReport>

Detects upgradeable contracts from a build info directory and validates whether they are upgrade safe. Returns a project report with the results.

Note that this function does not throw validation errors directly. Instead, you must use the project report to determine whether any errors were found.

Parameters:

  • buildInfoDir - the path to the build info directory which contains JSON files with Solidity compiler input and output. Defaults to artifacts/build-info for Hardhat projects or out/build-info for Foundry projects. If your project uses a custom output directory, you must specify its build info directory here.

  • contract - The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated.

  • reference - Can only be used when the contract argument is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the @custom:oz-upgrades-from annotation if it is defined in the contract that is being validated.

  • opts - an object with the following options as defined in Common Options:

    • unsafeAllow

    • unsafeAllowRenames

    • unsafeSkipStorageCheck

    • requireReference - Can only be used when the contract argument is also provided. Not compatible with the unsafeSkipStorageCheck option. If specified, requires either the reference argument to be provided or the contract to have a @custom:oz-upgrades-from annotation.

Returns:

ProjectReport

interface ProjectReport {
  ok: boolean;
  explain(color?: boolean): string;
  numPassed: number;
  numTotal: number;
}

An object that represents the result of upgrade safety checks and storage layout comparisons, and contains a report of all errors found.

Members:

  • ok - false if any errors were found, otherwise true.

  • explain() - returns a message explaining the errors in detail, if any.

  • numPassed - number of contracts that passed upgrade safety checks.

  • numTotal - total number of upgradeable contracts detected.

Low-Level API

This low-level API is deprecated. Use the High-Level API instead.

The low-level API works with Solidity input and output JSON objects and lets you perform upgrade safety checks and storage layout comparisons on individual contracts. Use this API if you want to validate specific contracts rather than a whole project.

Prerequisites

Compile your contracts to generate Solidity input and output JSON objects. The compiler output must include storage layouts.

Note that the other prerequisites from the validate command are not required, because the low-level API does not detect upgradeable contracts automatically. Instead, you must create an instance of UpgradeableContract for each implementation contract that you want to validate, and call functions on it to get the upgrade safety and storage layout reports.

Usage

Import the UpgradeableContract class:

import { UpgradeableContract } from '@openzeppelin/upgrades-core';

Then create an instance of UpgradeableContract for each implementation contract that you want to validate, and call .getErrorReport() and/or .getStorageLayoutReport() on it to get the upgrade safety and storage layout reports, respectively.

UpgradeableContract

This class represents the implementation for an upgradeable contract and gives access to error reports.

constructor UpgradeableContract
constructor UpgradeableContract(
  name: string,
  solcInput: SolcInput,
  solcOutput: SolcOutput,
  opts?: {
    unsafeAllow?: ValidationError[],
    unsafeAllowRenames?: boolean,
    unsafeSkipStorageCheck?: boolean,
    kind?: 'uups' | 'transparent' | 'beacon',
  },
  solcVersion?: string,
): UpgradeableContract

Creates a new instance of UpgradeableContract.

Parameters:

  • name - the name of the implementation contract as either a fully qualified name or contract name. If multiple contracts have the same name, you must use the fully qualified name e.g., contracts/Bar.sol:Bar.

  • solcInput - the Solidity input JSON object for the implementation contract.

  • solcOutput - the Solidity output JSON object for the implementation contract.

  • opts - an object with the following options as defined in Common Options:

    • kind

    • unsafeAllow

    • unsafeAllowRenames

    • unsafeSkipStorageCheck

  • solcVersion - the Solidity version used to compile the implementation contract.

In Hardhat, solcInput and solcOutput can be obtained from the Build Info file, which itself can be retrieved with hre.artifacts.getBuildInfo.
.getErrorReport
getErrorReport(): Report

Returns:

  • a report about errors pertaining to proxied contracts, e.g. the use of selfdestruct.

.getStorageUpgradeReport
getStorageUpgradeReport(
  upgradedContract: UpgradeableContract,
  opts?: {
    unsafeAllow?: ValidationError[],
    unsafeAllowRenames?: boolean,
    unsafeSkipStorageCheck?: boolean,
    kind?: 'uups' | 'transparent' | 'beacon',
  },
): Report

Compares the storage layout of an upgradeable contract with that of a proposed upgrade.

Parameters:

  • upgradedContract - another instance of UpgradeableContract representing the proposed upgrade.

  • opts - an object with the following options as defined in Common Options:

    • kind

    • unsafeAllow

    • unsafeAllowRenames

    • unsafeSkipStorageCheck

Returns:

  • a report about errors pertaining to proxied contracts, e.g. the use of selfdestruct, and storage layout conflicts.

Report

interface Report {
  ok: boolean;
  explain(color?: boolean): string;
}

An object that represents the results of an analysis.

Members:

  • ok - false if any errors were found, otherwise true.

  • explain() - returns a message explaining the errors in detail, if any.