diff --git a/content/upgrades-plugins/api-hardhat-upgrades.mdx b/content/upgrades-plugins/api-hardhat-upgrades.mdx index 527cf63a..af077204 100644 --- a/content/upgrades-plugins/api-hardhat-upgrades.mdx +++ b/content/upgrades-plugins/api-hardhat-upgrades.mdx @@ -6,11 +6,21 @@ title: OpenZeppelin Hardhat Upgrades API This is the documentation for Hardhat 3. For Hardhat 2, see the [Hardhat Upgrades API docs (Hardhat 2)](/upgrades-plugins/hardhat-2/api-hardhat-upgrades). -Both `deployProxy` and `upgradeProxy` functions will return instances of [ethers.js contracts](https://docs.ethers.org/v6/api/contract/#Contract), and require [ethers.js contract factories](https://docs.ethers.org/v6/api/contract/#ContractFactory) as arguments. For [beacons](/contracts/5.x/api/proxy#beacon), `deployBeacon` and `upgradeBeacon` will both return an upgradable beacon instance that can be used with a beacon proxy. All deploy and upgrade functions validate that the implementation contract is upgrade-safe, and will fail otherwise. +This package works with both ethers and viem. By default it uses the ethers-based API; if you are using viem, import from `@openzeppelin/hardhat-upgrades/viem`. The options and validations documented below apply either way; what differs is how contracts are referenced and returned: + +* **Ethers (the default):** `deployProxy` and `upgradeProxy` return instances of [ethers.js contracts](https://docs.ethers.org/v6/api/contract/#Contract) and require [ethers.js contract factories](https://docs.ethers.org/v6/api/contract/#ContractFactory) as arguments. +* **If you are using viem:** the same functions identify contracts by name (a string) and return [viem contract instances](https://viem.sh/docs/contract/getContract); addresses use viem's `Address` type (a `` `0x${string}` `` template-literal type — any string beginning with `0x`). + +For beacons, `deployBeacon` and `upgradeBeacon` both return an upgradable beacon instance that can be used with a beacon proxy. + +All deploy and upgrade functions validate that the implementation contract is upgrade-safe, and will fail otherwise. ## Setup -In Hardhat 3, all of the functions below are accessed via an `upgradesApi` object created from the `upgrades` factory. Defender-specific functions (`defender.*`) are accessed via `defenderApi` from the `defender` factory. Both factories take a network connection as an argument; share one connection across your operations. (Or use `hre.network.getOrCreate()`, which reuses a connection per network instead of creating a new one each time.) +In Hardhat 3, all of the functions below are accessed via an `upgradesApi` object created from the `upgrades` factory, which takes a network connection as an argument; share one connection across your operations. (Or use `hre.network.getOrCreate()`, which reuses a connection per network instead of creating a new one each time.) + + + ```typescript import hre from 'hardhat'; @@ -24,7 +34,23 @@ const upgradesApi = await upgrades(hre, connection); const defenderApi = await defender(hre, connection); ``` -`upgradesApi` exposes every top-level function documented below (e.g. `upgradesApi.deployProxy(...)`). `defenderApi` exposes the same functions plus the `defender.*` functions (e.g. `defenderApi.deployContract(...)`, `defenderApi.proposeUpgradeWithApproval(...)`). The `admin`, `erc1967`, and `beacon` namespaces are accessed as `upgradesApi.admin.*`, `upgradesApi.erc1967.*`, and `upgradesApi.beacon.*`. + + + +```typescript +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +const connection = await hre.network.create(); +const upgradesApi = await upgrades(hre, connection); +``` + +Contracts are referenced by name and returned as viem contract instances. OpenZeppelin Defender (`defender.*`) is available only with ethers. Transactions are signed by the connection's first wallet client by default; pass a specific one with the `client` option. + + + + +`upgradesApi` exposes every top-level function documented below (e.g. `upgradesApi.deployProxy(...)`). With ethers, `defenderApi` exposes the same functions plus the `defender.*` functions (e.g. `defenderApi.deployContract(...)`, `defenderApi.proposeUpgradeWithApproval(...)`). The `admin`, `erc1967`, and `beacon` namespaces are accessed as `upgradesApi.admin.*`, `upgradesApi.erc1967.*`, and `upgradesApi.beacon.*`. ## Common Options @@ -57,7 +83,9 @@ The following options are common to some functions. * If set to `"always"`, the implementation contract is always redeployed even if it was previously deployed with the same bytecode. This can be used with the `salt` option when deploying a proxy through OpenZeppelin Defender to ensure that the implementation contract is deployed with the same salt as the proxy. * If set to `"never"`, the implementation contract is never redeployed. If the implementation contract was not previously deployed or is not found in the network file, an error will be thrown. * If set to `"onchange"`, the implementation contract is redeployed only if the bytecode has changed from previous deployments. -* `txOverrides`: (`ethers.Overrides`) An ethers.js [Overrides](https://docs.ethers.org/v6/api/contract/#Overrides) object to override transaction parameters, such as `gasLimit` and `gasPrice`. Applies to all transactions sent by a function with this option, even if the function sends multiple transactions. For OpenZeppelin Defender deployments, only the `gasLimit`, `gasPrice`, `maxFeePerGas`, and `maxPriorityFeePerGas` parameters are supported. +* `txOverrides`: (`ethers.Overrides`) An ethers.js [Overrides](https://docs.ethers.org/v6/api/contract/#Overrides) object to override transaction parameters, such as `gasLimit` and `gasPrice`. Applies to all transactions sent by a function with this option, even if the function sends multiple transactions. For OpenZeppelin Defender deployments, only the `gasLimit`, `gasPrice`, `maxFeePerGas`, and `maxPriorityFeePerGas` parameters are supported. This option applies to the default ethers-based API. + * If you are using viem, `txOverrides` does not apply. Instead, pass the transaction parameters `gas`, `gasPrice`, `maxFeePerGas`, `maxPriorityFeePerGas`, and `value` directly as options, and use the `client` option to select the wallet/public client. +* `client`: (`KeyedClient`) viem-based API only. The viem public and/or wallet clients to use, following `@nomicfoundation/hardhat-viem` conventions. `KeyedClient` is an object `{ public?: PublicClient; wallet?: WalletClient }` (with at least one of the two provided). The **wallet client** selects the account that signs the plugin's transactions — the viem counterpart of the ethers signer — and must be an account managed by the network connection. Defaults to the first wallet client from `connection.viem.getWalletClients()`. The `admin.*` functions instead take a wallet client as a positional argument, not via this option. * `useDefenderDeploy`: (`boolean`) Deploy contracts using OpenZeppelin Defender instead of ethers.js. See [Using with OpenZeppelin Defender](/upgrades-plugins/defender-deploy). * `verifySourceCode`: (`boolean`) When using OpenZeppelin Defender deployments, whether to verify source code on block explorers. Defaults to `true`. * `relayerId`: (`string`) When using OpenZeppelin Defender deployments, the ID of the relayer to use for the deployment. Defaults to the relayer configured for your deployment environment on Defender. @@ -74,15 +102,18 @@ The following options have been deprecated. * `unsafeAllowLinkedLibraries`: Equivalent to including `"external-library-linking"` in `unsafeAllow`. * `unsafeAllowCustomTypes`: Equivalent to including `"struct-definition"` and `"enum-definition"` in `unsafeAllow`. No longer necessary. -* `useDeployedImplementation`: (`boolean`) Equivalent to setting `redeployImplementation` to `"never"`. +* `useDeployedImplementation`: (`boolean`) Equivalent to setting `redeployImplementation` to `"never"`. Not supported when using viem; use `redeployImplementation` instead. ## deployProxy + + + ```ts async function deployProxy( Contract: ethers.ContractFactory, args: unknown[] = [], - opts?: + opts?: { initializer?: string | false, unsafeAllow?: ValidationError[], constructorArgs?: unknown[], @@ -96,34 +127,62 @@ async function deployProxy( useDefenderDeploy?: boolean, proxyFactory?: ethers.ContractFactory, deployFunction?: () => Promise, - , + }, ): Promise ``` -Creates a UUPS or Transparent proxy given an ethers contract factory to use as implementation, and returns a contract instance with the proxy address and the implementation interface. If `args` is set, will call an initializer function `initialize` with the supplied args during proxy deployment. + + + +```ts +async function deployProxy( + contractName: string, + args: unknown[] = [], + opts?: { + initializer?: string | false, + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + initialOwner?: Address, + unsafeSkipProxyAdminCheck?: boolean, + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + kind?: 'uups' | 'transparent', + client?: KeyedClient, + }, +): Promise +``` + + + + +Creates a UUPS or Transparent proxy from the given implementation, and returns a contract instance bound to the proxy address with the implementation interface. If `args` is set, will call an initializer function `initialize` with the supplied args during proxy deployment. If you call `deployProxy` several times for the same implementation contract, several proxies will be deployed, but only one implementation contract will be used. **Parameters:** -* `Contract` - an ethers contract factory to use as the implementation. +* the implementation — an ethers contract factory (`Contract`), or the contract name as a string when using viem (`contractName`). * `args` - arguments for the initializer function. * `opts` - an object with options: - * `initializer`: set a different initializer function to call (see [Specifying Fragments](https://docs.ethers.io/v5/api/utils/abi/interface/#Interface--specifying-fragments)), or specify `false` to disable initialization. + * `initializer`: the initializer function to call instead of the default `initialize` — a function name such as `initialize`, or a full signature such as `initialize(uint256)` to disambiguate overloads; or `false` to disable initialization. The accepted formats differ slightly between ethers and viem — see [Differences](/upgrades-plugins/hardhat-upgrades#differences-from-the-ethers-based-api). * additional options as described in [Common Options](#common-options). **Returns:** -* a contract instance with the proxy address and the implementation interface. +* a contract instance bound to the proxy address with the implementation interface — an ethers contract, or a viem contract instance when using viem. ## upgradeProxy + + + ```ts async function upgradeProxy( proxy: string | ethers.Contract, Contract: ethers.ContractFactory, - opts?: - call?: string | { fn: string; args?: unknown[] , + opts?: { + call?: string | { fn: string; args?: unknown[] }, unsafeAllow?: ValidationError[], unsafeAllowRenames?: boolean, unsafeSkipStorageCheck?: boolean, @@ -137,14 +196,39 @@ async function upgradeProxy( ): Promise ``` + + + +```ts +async function upgradeProxy( + proxy: Address | ContractReturnType, + contractName: string, + opts?: { + call?: string | { fn: string; args?: unknown[] }, + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + kind?: 'uups' | 'transparent', + client?: KeyedClient, + }, +): Promise +``` + + + + Upgrades a UUPS or Transparent proxy at a specified address to a new implementation contract, and returns a contract instance with the proxy address and the new implementation interface. **Parameters:** * `proxy` - the proxy address or proxy contract instance. -* `Contract` - an ethers contract factory to use as the new implementation. +* the new implementation — an ethers contract factory (`Contract`), or the contract name as a string when using viem (`contractName`). * `opts` - an object with options: - * `call`: enables the execution of an arbitrary function call during the upgrade process. This call is described using a function name, signature, or selector (see [Specifying Fragments](https://docs.ethers.io/v5/api/utils/abi/interface/#Interface--specifying-fragments)), and optional arguments. It is batched into the upgrade transaction, making it safe to call migration initializing functions. + * `call`: enables the execution of an arbitrary function call during the upgrade process. This call is described using a function name or signature, and optional arguments. It is batched into the upgrade transaction, making it safe to call migration initializing functions. The accepted formats differ slightly between ethers and viem — see [Differences](/upgrades-plugins/hardhat-upgrades#differences-from-the-ethers-based-api). * additional options as described in [Common Options](#common-options). **Returns:** @@ -153,10 +237,13 @@ Upgrades a UUPS or Transparent proxy at a specified address to a new implementat ## deployBeacon + + + ```ts async function deployBeacon( Contract: ethers.ContractFactory, - opts?: + opts?: { unsafeAllow?: ValidationError[], constructorArgs?: unknown[], initialOwner?: string, @@ -164,15 +251,36 @@ async function deployBeacon( pollingInterval?: number, redeployImplementation?: 'always' | 'never' | 'onchange', txOverrides?: ethers.Overrides, - , + }, ): Promise ``` -Creates an [upgradable beacon](/contracts/5.x/api/proxy#UpgradeableBeacon) given an ethers contract factory to use as implementation, and returns the beacon contract instance. + + + +```ts +async function deployBeacon( + contractName: string, + opts?: { + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + initialOwner?: Address, + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + client?: KeyedClient, + }, +): Promise +``` + + + + +Creates an [upgradable beacon](/contracts/5.x/api/proxy#UpgradeableBeacon) for the given implementation, and returns the beacon contract instance. **Parameters:** -* `Contract` - an ethers contract factory to use as the implementation. +* the implementation — an ethers contract factory (`Contract`), or the contract name as a string when using viem (`contractName`). * `opts` - an object with options: * additional options as described in [Common Options](#common-options). @@ -186,11 +294,14 @@ Creates an [upgradable beacon](/contracts/5.x/api/proxy#UpgradeableBeacon) given ## upgradeBeacon + + + ```ts async function upgradeBeacon( beacon: string | ethers.Contract, Contract: ethers.ContractFactory, - opts?: + opts?: { unsafeAllow?: ValidationError[], unsafeAllowRenames?: boolean, unsafeSkipStorageCheck?: boolean, @@ -199,16 +310,39 @@ async function upgradeBeacon( pollingInterval?: number, redeployImplementation?: 'always' | 'never' | 'onchange', txOverrides?: ethers.Overrides, - , + }, ): Promise ``` + + + +```ts +async function upgradeBeacon( + beacon: Address | UpgradeableBeaconContract, + contractName: string, + opts?: { + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + client?: KeyedClient, + }, +): Promise +``` + + + + Upgrades an [upgradable beacon](/contracts/5.x/api/proxy#UpgradeableBeacon) at a specified address to a new implementation contract, and returns the beacon contract instance. **Parameters:** * `beacon` - the beacon address or beacon contract instance. -* `Contract` - an ethers contract factory to use as the new implementation. +* the new implementation — an ethers contract factory (`Contract`), or the contract name as a string when using viem (`contractName`). * `opts` - an object with options: * additional options as described in [Common Options](#common-options). @@ -222,30 +356,51 @@ Upgrades an [upgradable beacon](/contracts/5.x/api/proxy#UpgradeableBeacon) at a ## deployBeaconProxy + + + ```ts async function deployBeaconProxy( beacon: string | ethers.Contract, attachTo: ethers.ContractFactory, args: unknown[] = [], - opts?: + opts?: { initializer?: string | false, txOverrides?: ethers.Overrides, useDefenderDeploy?: boolean, proxyFactory?: ethers.ContractFactory, deployFunction?: () => Promise, - , + }, ): Promise ``` -Creates a [Beacon proxy](/contracts/5.x/api/proxy#BeaconProxy) given an existing beacon contract address and an ethers contract factory corresponding to the beacon’s current implementation contract, and returns a contract instance with the beacon proxy address and the implementation interface. If `args` is set, will call an initializer function `initialize` with the supplied args during proxy deployment. + + + +```ts +async function deployBeaconProxy( + beacon: Address | UpgradeableBeaconContract, + contractName: string, + args: unknown[] = [], + opts?: { + initializer?: string | false, + client?: KeyedClient, + }, +): Promise +``` + + + + +Creates a [Beacon proxy](/contracts/5.x/api/proxy#BeaconProxy) given an existing beacon and the beacon's current implementation contract, and returns a contract instance with the beacon proxy address and the implementation interface. If `args` is set, will call an initializer function `initialize` with the supplied args during proxy deployment. **Parameters:** * `beacon` - the beacon address or beacon contract instance. -* `attachTo` - an ethers contract factory corresponding to the beacon’s current implementation contract. +* the beacon's current implementation — an ethers contract factory (`attachTo`), or the contract name as a string when using viem (`contractName`). * `args` - arguments for the initializer function. * `opts` - an object with options: - * `initializer`: set a different initializer function to call (see [Specifying Fragments](https://docs.ethers.io/v5/api/utils/abi/interface/#Interface--specifying-fragments)), or specify `false` to disable initialization. + * `initializer`: set a different initializer function to call (a function name, or a signature such as `initialize(uint256)`), or specify `false` to disable initialization. See [Differences](/upgrades-plugins/hardhat-upgrades#differences-from-the-ethers-based-api). * additional options as described in [Common Options](#common-options). **Returns:** @@ -258,28 +413,48 @@ Creates a [Beacon proxy](/contracts/5.x/api/proxy#BeaconProxy) given an existing ## forceImport + + + ```ts async function forceImport( address: string, deployedImpl: ethers.ContractFactory, - opts?: + opts?: { kind?: 'uups' | 'transparent' | 'beacon', - , + }, ): Promise ``` -Forces the import of an existing proxy, beacon, or implementation contract deployment to be used with this plugin. Provide the address of an existing proxy, beacon or implementation, along with the ethers contract factory of the implementation contract that was deployed. + + + +```ts +async function forceImport( + addressOrInstance: Address | ContractReturnType, + contractName: string, + opts?: { + kind?: 'uups' | 'transparent' | 'beacon', + client?: KeyedClient, + }, +): Promise +``` + + + + +Forces the import of an existing proxy, beacon, or implementation contract deployment to be used with this plugin. Provide the address of an existing proxy, beacon or implementation, along with its current implementation contract (an ethers contract factory, or the contract name as a string when using viem). -When importing a proxy or beacon, the `deployedImpl` argument must be the contract factory of the **current** implementation contract version that is being used, not the version that you are planning to upgrade to. +When importing a proxy or beacon, the implementation argument must be the **current** implementation contract version that is being used, not the version that you are planning to upgrade to. Use this function to recreate a lost [network file](/upgrades-plugins/network-files) by importing previous deployments, or to register proxies or beacons for upgrading even if they were not originally deployed by this plugin. Supported for UUPS, Transparent, and Beacon proxies, as well as beacons and implementation contracts. **Parameters:** -* `address` - the address of an existing proxy, beacon or implementation. -* `deployedImpl` - the ethers contract factory of the implementation contract that was deployed. +* the address of the proxy, beacon, or implementation to import (the viem API also accepts a contract instance). +* the current implementation — an ethers contract factory (`deployedImpl`), or the contract name as a string when using viem (`contractName`). * `opts` - an object with options: * `kind`: (`"uups" | "transparent" | "beacon"`) forces a proxy to be treated as a UUPS, Transparent, or Beacon proxy. If not provided, the proxy kind will be automatically detected. @@ -293,21 +468,40 @@ Use this function to recreate a lost [network file](/upgrades-plugins/network-fi ## validateImplementation + + + ```ts async function validateImplementation( Contract: ethers.ContractFactory, - opts?: + opts?: { unsafeAllow?: ValidationError[], kind?: 'uups' | 'transparent' | 'beacon', - , + }, ): Promise ``` + + + +```ts +async function validateImplementation( + contractName: string, + opts?: { + unsafeAllow?: ValidationError[], + kind?: 'uups' | 'transparent' | 'beacon', + }, +): Promise +``` + + + + Validates an implementation contract without deploying it. **Parameters:** -* `Contract` - the ethers contract factory of the implementation contract. +* the implementation — an ethers contract factory (`Contract`), or the contract name as a string when using viem. * `opts` - an object with options: * additional options as described in [Common Options](#common-options). @@ -317,10 +511,13 @@ Validates an implementation contract without deploying it. ## deployImplementation + + + ```ts async function deployImplementation( Contract: ethers.ContractFactory, - opts?: + opts?: { unsafeAllow?: ValidationError[], constructorArgs?: unknown[], timeout?: number, @@ -330,22 +527,43 @@ async function deployImplementation( getTxResponse?: boolean, kind?: 'uups' | 'transparent' | 'beacon', useDefenderDeploy?: boolean, - , + }, ): Promise ``` + + + +```ts +async function deployImplementation( + contractName: string, + opts?: { + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + kind?: 'uups' | 'transparent' | 'beacon', + client?: KeyedClient, + }, +): Promise
+``` + + + + Validates and deploys an implementation contract, and returns its address. **Parameters:** -* `Contract` - an ethers contract factory to use as the implementation. +* the implementation — an ethers contract factory (`Contract`), or the contract name as a string when using viem. * `opts` - an object with options: - * `getTxResponse`: if set to `true`, causes this function to return an ethers transaction response corresponding to the deployment of the new implementation contract instead of its address. Note that if the new implementation contract was originally imported as a result of `forceImport`, only the address will be returned. + * `getTxResponse`: (ethers-based API only) if set to `true`, causes this function to return an ethers transaction response corresponding to the deployment of the new implementation contract instead of its address. Note that if the new implementation contract was originally imported as a result of `forceImport`, only the address will be returned. * additional options as described in [Common Options](#common-options). **Returns:** -* the address or an ethers transaction response corresponding to the deployment of the implementation contract. +* the implementation contract address (or, with the ethers-based API, an ethers transaction response when `getTxResponse` is set). **Since:** @@ -353,25 +571,47 @@ Validates and deploys an implementation contract, and returns its address. ## validateUpgrade + + + ```ts async function validateUpgrade( referenceAddressOrContract: string | ethers.ContractFactory, newContract: ethers.ContractFactory, - opts?: + opts?: { + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + kind?: 'uups' | 'transparent' | 'beacon', + }, +): Promise +``` + + + + +```ts +async function validateUpgrade( + referenceAddressOrContractName: Address | string, + newContractName: string, + opts?: { unsafeAllow?: ValidationError[], unsafeAllowRenames?: boolean, unsafeSkipStorageCheck?: boolean, kind?: 'uups' | 'transparent' | 'beacon', - , + }, ): Promise ``` -Validates a new implementation contract without deploying it and without actually upgrading to it. Compares the current implementation contract to the new implementation contract to check for storage layout compatibility errors. If `referenceAddressOrContract` is the current implementation address, the `kind` option is required. + + + +Validates a new implementation contract without deploying it and without actually upgrading to it. Compares the current implementation contract to the new implementation contract to check for storage layout compatibility errors. If the reference is the current implementation address, the `kind` option is required. **Parameters:** -* `referenceAddressOrContract` - a proxy or beacon address that uses the current implementation, or an address or ethers contract factory corresponding to the current implementation. -* `newContract` - the new implementation contract. +* the current implementation — a proxy or beacon address that uses it, or the current implementation itself (an address or ethers contract factory, or the contract name as a string when using viem). +* the new implementation contract — an ethers contract factory, or the contract name as a string when using viem. * `opts` - an object with options: * additional options as described in [Common Options](#common-options). @@ -382,6 +622,10 @@ Validates a new implementation contract without deploying it and without actuall **Examples:** Validate upgrading an existing proxy to a new contract (replace `PROXY_ADDRESS` with the address of your proxy): + + + + ```ts import hre from 'hardhat'; import { upgrades } from '@openzeppelin/hardhat-upgrades'; @@ -394,7 +638,27 @@ const BoxV2 = await ethers.getContractFactory('BoxV2'); await upgradesApi.validateUpgrade(PROXY_ADDRESS, BoxV2); ``` + + + +```ts +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +const connection = await hre.network.create(); +const upgradesApi = await upgrades(hre, connection); + +await upgradesApi.validateUpgrade(PROXY_ADDRESS, 'BoxV2'); +``` + + + + Validate upgrading between two contract implementations: + + + + ```ts import hre from 'hardhat'; import { upgrades } from '@openzeppelin/hardhat-upgrades'; @@ -408,13 +672,32 @@ const BoxV2 = await ethers.getContractFactory('BoxV2'); await upgradesApi.validateUpgrade(Box, BoxV2); ``` + + + +```ts +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +const connection = await hre.network.create(); +const upgradesApi = await upgrades(hre, connection); + +await upgradesApi.validateUpgrade('Box', 'BoxV2'); +``` + + + + ## prepareUpgrade + + + ```ts async function prepareUpgrade( referenceAddressOrContract: string | ethers.Contract, Contract: ethers.ContractFactory, - opts?: + opts?: { unsafeAllow?: ValidationError[], unsafeAllowRenames?: boolean, unsafeSkipStorageCheck?: boolean, @@ -426,34 +709,62 @@ async function prepareUpgrade( getTxResponse?: boolean, kind?: 'uups' | 'transparent' | 'beacon', useDefenderDeploy?: boolean, - , + }, ): Promise ``` -Validates and deploys a new implementation contract, and returns its address. If `referenceAddressOrContract` is the current implementation address, the `kind` option is required. Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from Hardhat. + + + +```ts +async function prepareUpgrade( + referenceAddressOrContract: Address | ContractReturnType, + contractName: string, + opts?: { + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + kind?: 'uups' | 'transparent' | 'beacon', + client?: KeyedClient, + }, +): Promise
+``` + + + + +Validates and deploys a new implementation contract, and returns its address. If the reference is the current implementation address, the `kind` option is required. Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from Hardhat. **Parameters:** -* `referenceAddressOrContract` - the proxy or beacon or implementation address or contract instance. -* `Contract` - the new implementation contract. +* the proxy, beacon, or implementation — an address or contract instance. +* the new implementation contract — an ethers contract factory (`Contract`), or the contract name as a string when using viem (`contractName`). * `opts` - an object with options: - * `getTxResponse`: if set to `true`, causes this function to return an ethers transaction response corresponding to the deployment of the new implementation contract instead of its address. Note that if the new implementation contract was originally imported as a result of `forceImport`, only the address will be returned. + * `getTxResponse`: (ethers-based API only) if set to `true`, causes this function to return an ethers transaction response corresponding to the deployment of the new implementation contract instead of its address. Note that if the new implementation contract was originally imported as a result of `forceImport`, only the address will be returned. * additional options as described in [Common Options](#common-options). **Returns:** -* the address or an ethers transaction response corresponding to the deployment of the new implementation contract. +* the new implementation contract address (or, with the ethers-based API, an ethers transaction response when `getTxResponse` is set). ## defender.deployContract + +The `defender.*` functions are available only with the ethers-based API (accessed via `defender(hre, connection)`); they are not part of the viem-based API. + + ```ts async function deployContract( Contract: ethers.ContractFactory, args: unknown[] = [], - opts?: + opts?: { unsafeAllowDeployContract?: boolean, pollingInterval?: number, - , + }, ): Promise ``` @@ -526,7 +837,7 @@ Gets the default upgrade approval process configured for your deployment environ async function proposeUpgradeWithApproval( proxyAddress: string, ImplFactory: ContractFactory, - opts?: + opts?: { unsafeAllow?: ValidationError[], unsafeAllowRenames?: boolean, unsafeSkipStorageCheck?: boolean, @@ -537,7 +848,7 @@ async function proposeUpgradeWithApproval( kind?: 'uups' | 'transparent' | 'beacon', useDefenderDeploy?: boolean, approvalProcessId?: string, - , + }, ): Promise< proposalId: string, url: string, @@ -567,17 +878,41 @@ Similar to `prepareUpgrade`. This method validates and deploys the new implement ## admin.changeProxyAdmin + + + ```ts async function changeProxyAdmin( proxyAddress: string, newAdmin: string, signer?: ethers.Signer, - opts?: + opts?: { txOverrides?: ethers.Overrides, + }, +): Promise +``` + + + +```ts +async function changeProxyAdmin( + proxyAddress: Address, + newAdmin: Address, + walletClient?: WalletClient, + opts?: { + gas?: bigint, + gasPrice?: bigint, + maxFeePerGas?: bigint, + maxPriorityFeePerGas?: bigint, + value?: bigint, + }, ): Promise ``` + + + Changes the admin for a specific proxy. @@ -588,24 +923,49 @@ This function is not supported with admins or proxies from OpenZeppelin Contract * `proxyAddress` - the address of the proxy to change. * `newAdmin` - the new admin address. -* `signer` - the signer to use for the transaction. +* The account to send the transaction with — `signer` for ethers, `walletClient` for viem. Optional; defaults to the connection's default account. * `opts` - an object with options: * additional options as described in [Common Options](#common-options). ## admin.transferProxyAdminOwnership + + + ```ts async function transferProxyAdminOwnership( proxyAddress: string, newOwner: string, signer?: ethers.Signer, - opts?: + opts?: { silent?: boolean, txOverrides?: ethers.Overrides, + }, +): Promise +``` + + + +```ts +async function transferProxyAdminOwnership( + proxyAddress: Address, + newOwner: Address, + walletClient?: WalletClient, + opts?: { + silent?: boolean, + gas?: bigint, + gasPrice?: bigint, + maxFeePerGas?: bigint, + maxPriorityFeePerGas?: bigint, + value?: bigint, + }, ): Promise ``` + + + Changes the owner of the proxy admin contract for a specific proxy. @@ -616,7 +976,7 @@ The `proxyAddress` parameter is required since `@openzeppelin/hardhat-upgrades@3 * `proxyAddress` - the address of the proxy whose admin ownership is to be transferred. * `newOwner` - the new owner address for the proxy admin contract. -* `signer` - the signer to use for the transaction. +* The account to send the transaction with — `signer` for ethers, `walletClient` for viem. Optional; defaults to the connection's default account. * `opts` - an object with options: * `silent`: if set to `true`, silences console logging about each proxy affected by the admin ownership transfer. * additional options as described in [Common Options](#common-options). @@ -627,12 +987,27 @@ The `proxyAddress` parameter is required since `@openzeppelin/hardhat-upgrades@3 ## erc1967 + + + ```ts async function erc1967.getImplementationAddress(proxyAddress: string): Promise; async function erc1967.getBeaconAddress(proxyAddress: string): Promise; async function erc1967.getAdminAddress(proxyAddress: string): Promise; ``` + + + +```ts +async function erc1967.getImplementationAddress(proxyAddress: Address): Promise
; +async function erc1967.getBeaconAddress(proxyAddress: Address): Promise
; +async function erc1967.getAdminAddress(proxyAddress: Address): Promise
; +``` + + + + Functions in this module provide access to the [ERC1967](https://eips.ethereum.org/EIPS/eip-1967) variables of a proxy contract. **Parameters:** @@ -645,10 +1020,23 @@ Functions in this module provide access to the [ERC1967](https://eips.ethereum.o ## beacon + + + ```ts async function beacon.getImplementationAddress(beaconAddress: string): Promise; ``` + + + +```ts +async function beacon.getImplementationAddress(beaconAddress: Address): Promise
; +``` + + + + This module provides a convenience function to get the implementation address from a beacon contract. **Parameters:** @@ -700,6 +1088,9 @@ npm install --save-dev @nomicfoundation/hardhat-verify ``` Then add both `@nomicfoundation/hardhat-verify` and `@openzeppelin/hardhat-upgrades` to the `plugins` array in your `hardhat.config.ts`. The upgrades plugin detects hardhat-verify and automatically extends its `verify` task: + + + ```typescript import { configVariable, defineConfig } from 'hardhat/config'; import hardhatVerify from '@nomicfoundation/hardhat-verify'; @@ -715,6 +1106,27 @@ export default defineConfig({ }); ``` + + + +```typescript +import { configVariable, defineConfig } from 'hardhat/config'; +import hardhatVerify from '@nomicfoundation/hardhat-verify'; +import hardhatUpgrades from '@openzeppelin/hardhat-upgrades/viem'; + +export default defineConfig({ + plugins: [hardhatVerify, hardhatUpgrades], + verify: { + etherscan: { + apiKey: configVariable('ETHERSCAN_API_KEY'), + }, + }, +}); +``` + + + + Then run the `verify` task from the command line with the proxy address: ``` npx hardhat verify --network mainnet PROXY_ADDRESS diff --git a/content/upgrades-plugins/hardhat-upgrades.mdx b/content/upgrades-plugins/hardhat-upgrades.mdx index 1c044b26..a734a07e 100644 --- a/content/upgrades-plugins/hardhat-upgrades.mdx +++ b/content/upgrades-plugins/hardhat-upgrades.mdx @@ -6,7 +6,7 @@ title: Using with Hardhat This is the documentation for Hardhat 3. For Hardhat 2, see [Using with Hardhat (Hardhat 2)](/upgrades-plugins/hardhat-2/hardhat-upgrades). -This package adds functions to your Hardhat scripts so you can deploy and upgrade proxies for your contracts. Requires `@nomicfoundation/hardhat-ethers`. +This package adds functions to your Hardhat scripts so you can deploy and upgrade proxies for your contracts, using either ethers or viem. Migrating from Hardhat 2? See the [Migration Guide](/upgrades-plugins/migrate-from-hardhat-2). @@ -14,16 +14,33 @@ Migrating from Hardhat 2? See the [Migration Guide](/upgrades-plugins/migrate-fr ## Installation + + + +```console +$ npm install --save-dev @openzeppelin/hardhat-upgrades +$ npm install --save-dev @nomicfoundation/hardhat-ethers ethers +``` + + + + ```console $ npm install --save-dev @openzeppelin/hardhat-upgrades -$ npm install --save-dev @nomicfoundation/hardhat-ethers ethers # peer dependencies +$ npm install --save-dev @nomicfoundation/hardhat-viem viem ``` + + + -Hardhat 3 supports both ethers and viem. This plugin uses `@nomicfoundation/hardhat-ethers` internally and loads it automatically. If your project uses viem, install `@nomicfoundation/hardhat-viem` and add it to your config alongside this plugin: your own scripts and tests can use viem, while the plugin's functions take ethers contract factories from `connection.ethers` and return ethers contracts. +Use the **ethers / viem** toggle above depending on which library you use — your choice also switches the config and every code example on this page. When you install this plugin, you must also install the peer dependencies for your chosen library. See [Using with viem](#using-with-viem) for the behavior differences between the two. -Register the `@openzeppelin/hardhat-upgrades` plugin in your [`hardhat.config.ts`](https://hardhat.org/config/): +Register the plugin in your [`hardhat.config.ts`](https://hardhat.org/config/): + + + ```typescript import { defineConfig } from 'hardhat/config'; @@ -35,6 +52,23 @@ export default defineConfig({ }); ``` + + + +```typescript +import { defineConfig } from 'hardhat/config'; +import hardhatViem from '@nomicfoundation/hardhat-viem'; +import hardhatUpgrades from '@openzeppelin/hardhat-upgrades/viem'; + +export default defineConfig({ + plugins: [hardhatViem, hardhatUpgrades], + // ... rest of config +}); +``` + + + + If you use the `verify` task, also add `@nomicfoundation/hardhat-verify` to the `plugins` array and configure Hardhat's `verify.etherscan.apiKey` setting. ## Usage in scripts @@ -45,6 +79,9 @@ If you use the `verify` task, also add `@nomicfoundation/hardhat-verify` to the You can use this plugin in a [Hardhat script](https://hardhat.org/docs/guides/writing-scripts) to deploy an upgradeable instance of one of your contracts via the `deployProxy` function: + + + ```typescript // scripts/create-box.ts import hre from 'hardhat'; @@ -67,10 +104,38 @@ main().catch(err => { }); ``` + + + +```typescript +// scripts/create-box.ts +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +async function main() { + const connection = await hre.network.create(); + const upgradesApi = await upgrades(hre, connection); + + const box = await upgradesApi.deployProxy('Box', [42]); + console.log('Box deployed to:', box.address); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); +``` + + + + This will automatically check that the `Box` contract is upgrade-safe, deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy (along with a proxy admin if needed), and initialize it by calling `initialize(42)`. Then, in another script, you can use the `upgradeProxy` function to upgrade the deployed instance to a new version. The new version can be a different contract (such as `BoxV2`), or you can just modify the existing `Box` contract and recompile it — the plugin will note it changed. + + + ```typescript // scripts/upgrade-box.ts import hre from 'hardhat'; @@ -94,6 +159,33 @@ main().catch(err => { }); ``` + + + +```typescript +// scripts/upgrade-box.ts +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +const PROXY_ADDRESS = '0x...'; // Replace with your proxy address + +async function main() { + const connection = await hre.network.create(); + const upgradesApi = await upgrades(hre, connection); + + await upgradesApi.upgradeProxy(PROXY_ADDRESS, 'BoxV2'); + console.log('Box upgraded'); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); +``` + + + + > Note: While this plugin keeps track of all the implementation contracts you have deployed per network, in order to reuse them and validate storage compatibilities, it does _not_ keep track of the proxies you have deployed. This means that you will need to manually keep track of each deployment address, to supply those to the upgrade function when needed. The plugin will take care of comparing `BoxV2` to the previous one to ensure they are compatible for the upgrade, deploy the new `BoxV2` implementation contract (unless there is one already from a previous deployment), and upgrade the existing proxy to the new implementation. @@ -102,6 +194,9 @@ The plugin will take care of comparing `BoxV2` to the previous one to ensure the You can also use this plugin to deploy an upgradeable beacon for your contract with the `deployBeacon` function, then deploy one or more beacon proxies that point to it by using the `deployBeaconProxy` function. + + + ```typescript // scripts/create-box.ts import hre from 'hardhat'; @@ -129,8 +224,39 @@ main().catch(err => { }); ``` + + + +```typescript +// scripts/create-box.ts +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +async function main() { + const connection = await hre.network.create(); + const upgradesApi = await upgrades(hre, connection); + + const beacon = await upgradesApi.deployBeacon('Box'); + console.log('Beacon deployed to:', beacon.address); + + const box = await upgradesApi.deployBeaconProxy(beacon, 'Box', [42]); + console.log('Box deployed to:', box.address); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); +``` + + + + Then, in another script, you can use the `upgradeBeacon` function to upgrade the beacon to a new version. When the beacon is upgraded, all of the beacon proxies that point to it will use the new contract implementation. + + + ```typescript // scripts/upgrade-box.ts import hre from 'hardhat'; @@ -154,6 +280,33 @@ main().catch(err => { }); ``` + + + +```typescript +// scripts/upgrade-box.ts +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +const BEACON_ADDRESS = '0x...'; // Replace with your beacon address + +async function main() { + const connection = await hre.network.create(); + const upgradesApi = await upgrades(hre, connection); + + await upgradesApi.upgradeBeacon(BEACON_ADDRESS, 'BoxV2'); + console.log('Beacon upgraded'); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); +``` + + + + ## Usage in tests You can also use the plugin's functions from your Hardhat tests, in case you want to add tests for upgrading your contracts (which you should!). The API is the same as in scripts. @@ -162,6 +315,9 @@ You can also use the plugin's functions from your Hardhat tests, in case you wan ### Proxies + + + ```typescript import { expect } from 'chai'; import hre from 'hardhat'; @@ -190,8 +346,39 @@ describe('Box', function () { }); ``` + + + +```typescript +import { expect } from 'chai'; +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +describe('Box', function () { + let upgradesApi; + + before(async () => { + const connection = await hre.network.create(); + upgradesApi = await upgrades(hre, connection); + }); + + it('works', async () => { + const instance = await upgradesApi.deployProxy('Box', [42]); + const upgraded = await upgradesApi.upgradeProxy(instance.address, 'BoxV2'); + + expect(await upgraded.read.value()).to.equal(42n); + }); +}); +``` + + + + ### Beacon proxies + + + ```typescript import { expect } from 'chai'; import hre from 'hardhat'; @@ -223,6 +410,50 @@ describe('Box', function () { }); ``` + + + +```typescript +import { expect } from 'chai'; +import hre from 'hardhat'; +import { upgrades } from '@openzeppelin/hardhat-upgrades/viem'; + +describe('Box', function () { + let upgradesApi; + let connection; + + before(async () => { + connection = await hre.network.create(); + upgradesApi = await upgrades(hre, connection); + }); + + it('works', async () => { + const beacon = await upgradesApi.deployBeacon('Box'); + const instance = await upgradesApi.deployBeaconProxy(beacon, 'Box', [42]); + + await upgradesApi.upgradeBeacon(beacon, 'BoxV2'); + const upgraded = await connection.viem.getContractAt('BoxV2', instance.address); + + const value = await upgraded.read.value(); + expect(value).to.equal(42n); + }); +}); +``` + + + + +## Using with viem + +Every code example on this page has an **ethers / viem** toggle — switch the tab to see the viem version. The viem API imports from `@openzeppelin/hardhat-upgrades/viem`, identifies contracts by name (instead of passing an ethers contract factory), and returns [viem contract instances](https://viem.sh/docs/contract/getContract) with `read` and `write`. Your selected tab is remembered across the page. Addresses use viem's `Address` type, and transactions are signed by the connection's wallet client (or one you pass with the `client` option), so viem local accounts such as `privateKeyToAccount` are supported. + +### Differences from the ethers-based API + +Both accept the same options and run the same upgrade-safety validations. A couple of behaviors differ: + +- **Overloaded function names.** When you pass a bare function name (no parameter types) for the `initializer` option or an upgrade `call`, and the contract has multiple overloads of that name, viem resolves the overload from the argument types, while ethers requires the full function signature in that case. +- **Function reference formats.** For the `initializer` option and upgrade `call`, viem accepts a function name or a canonical signature such as `initialize(uint256)`. It does not accept a 4-byte selector or non-canonical signature spellings that ethers tolerates. + ## Solidity tests Hardhat 3 supports writing tests in Solidity. You can use Solidity to test deployments, upgrades, and upgrade safety via the [`@openzeppelin/foundry-upgrades`](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades) Solidity library. @@ -243,6 +474,9 @@ $ npm install --save-dev @openzeppelin/foundry-upgrades "github:foundry-rs/forge Configure your Hardhat config to enable Solidity tests and expose the artifacts directory to FFI: + + + ```typescript import { defineConfig } from 'hardhat/config'; import hardhatUpgrades, { proxyFilesToBuild } from '@openzeppelin/hardhat-upgrades'; @@ -268,6 +502,37 @@ export default defineConfig({ }); ``` + + + +```typescript +import { defineConfig } from 'hardhat/config'; +import hardhatUpgrades, { proxyFilesToBuild } from '@openzeppelin/hardhat-upgrades/viem'; + +if (!process.env.FOUNDRY_OUT) { + process.env.FOUNDRY_OUT = 'artifacts/contracts'; +} + +export default defineConfig({ + plugins: [hardhatUpgrades], + solidity: { + version: '0.8.28', + npmFilesToBuild: [...proxyFilesToBuild()], + }, + test: { + solidity: { + ffi: true, + fsPermissions: { + readDirectory: ['artifacts/contracts'], + }, + }, + }, +}); +``` + + + + Add a `remappings.txt` at your project root so the library's imports resolve to the npm package's source: ``` diff --git a/content/upgrades-plugins/migrate-from-hardhat-2.mdx b/content/upgrades-plugins/migrate-from-hardhat-2.mdx index ebfde92d..70d8d9c8 100644 --- a/content/upgrades-plugins/migrate-from-hardhat-2.mdx +++ b/content/upgrades-plugins/migrate-from-hardhat-2.mdx @@ -22,7 +22,7 @@ npm install --save-dev hardhat @nomicfoundation/hardhat-ethers ``` -**Using viem?** You can install both `@nomicfoundation/hardhat-ethers` and `@nomicfoundation/hardhat-viem`. The upgrades plugin uses ethers internally; your own scripts and tests can still use viem. Note that the plugin's functions take ethers contract factories and return ethers contracts, so use `connection.ethers` when calling them. +**Using viem?** The plugin now has a viem API. Install `@nomicfoundation/hardhat-viem` and `viem` instead of the ethers packages, register the plugin from `@openzeppelin/hardhat-upgrades/viem`, and import its `upgrades` factory from the same path. Contracts are then identified by name and the functions return viem contract instances — you no longer need `@nomicfoundation/hardhat-ethers`. See [Using with Hardhat](/upgrades-plugins/hardhat-upgrades) for viem usage examples. ## Migration @@ -214,7 +214,7 @@ Note that you do not need to include constructor arguments when verifying if you ## Checklist -- Install `@nomicfoundation/hardhat-ethers` — required even if your project uses viem (install both if needed) +- Install the peer dependencies for the API you use: `@nomicfoundation/hardhat-ethers` and `ethers` for the ethers-based API, or `@nomicfoundation/hardhat-viem` and `viem` if you are using viem - Add `hardhatUpgrades` to `plugins` in `hardhat.config.ts` - If using `verify`, add `hardhatVerify` to `plugins`, install `@nomicfoundation/hardhat-verify`, and configure Hardhat's `verify.etherscan.apiKey` setting - Replace `import '@openzeppelin/hardhat-upgrades'` → `import { upgrades, defender } from '@openzeppelin/hardhat-upgrades'` in scripts/tests diff --git a/src/mdx-components.tsx b/src/mdx-components.tsx index fd95b6b0..cb715000 100644 --- a/src/mdx-components.tsx +++ b/src/mdx-components.tsx @@ -1,4 +1,5 @@ import { APIPage } from "fumadocs-openapi/ui"; +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; import defaultMdxComponents from "fumadocs-ui/mdx"; import { AnchorIcon, @@ -28,6 +29,8 @@ import { APIItemCompact } from "./components/ui/api-reference/api-item-compact"; export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, + Tabs, + Tab, OZWizard, APIItemCompact, APIGithubLinkHeader,