Skip to content

Commit

Permalink
Support private networks and forked networks with Defender (#989)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericglau authored Mar 8, 2024
1 parent 3960e8f commit 6869419
Show file tree
Hide file tree
Showing 19 changed files with 383 additions and 34 deletions.
10 changes: 1 addition & 9 deletions docs/modules/ROOT/pages/api-hardhat-upgrades.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The following options are common to some functions.
** 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 https://docs.ethers.org/v6/api/contract/#Overrides[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.
* `useDefenderDeploy`: (`boolean`) Deploy contracts using OpenZeppelin Defender instead of ethers.js. See xref:defender-deploy.adoc[Using with OpenZeppelin Defender]. **Note**: OpenZeppelin Defender deployments is in beta and functionality related to it is subject to change.
* `useDefenderDeploy`: (`boolean`) Deploy contracts using OpenZeppelin Defender instead of ethers.js. See xref:defender-deploy.adoc[Using with OpenZeppelin Defender].
* `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.
* `salt`: (`string`) When using OpenZeppelin Defender deployments, if this is not set, deployments will be performed using the CREATE opcode. If this is set, deployments will be performed using the CREATE2 opcode with the provided salt. Note that deployments using a Safe are done using CREATE2 and require a salt. **Warning:** CREATE2 affects `msg.sender` behavior. See https://docs.openzeppelin.com/defender/v2/tutorial/deploy#deploy-caveat[Caveats] for more information.
Expand Down Expand Up @@ -431,8 +431,6 @@ async function deployContract(
): Promise<ethers.Contract>
----

**Note**: OpenZeppelin Defender deployments is in beta and this function is subject to change.

Deploys a non-upgradeable contract using OpenZeppelin Defender, and returns a contract instance. Throws an error if the contract looks like an implementation contract.

CAUTION: Do not use this function to deploy implementations of upgradeable contracts, because upgrade safety validations are not performed with this function. For implementation contracts, use <<deploy-implementation>> instead.
Expand Down Expand Up @@ -465,8 +463,6 @@ async function getDeployApprovalProcess(
}>
----

**Note**: OpenZeppelin Defender deployments is in beta and this function is subject to change.

Gets the default deploy approval process configured for your deployment environment on OpenZeppelin Defender.

*Returns:*
Expand All @@ -490,8 +486,6 @@ async function getUpgradeApprovalProcess(
}>
----

**Note**: OpenZeppelin Defender deployments is in beta and this function is subject to change.

Gets the default upgrade approval process configured for your deployment environment on OpenZeppelin Defender. For example, this is useful for determining the default multisig wallet that you can use in your scripts to assign as the owner of your proxy.

*Returns:*
Expand Down Expand Up @@ -529,8 +523,6 @@ async function proposeUpgradeWithApproval(
}>
----

**Note**: OpenZeppelin Defender deployments is in beta and this function is subject to change.

Proposes an upgrade using an upgrade approval process on OpenZeppelin Defender.

Similar to `prepareUpgrade`. This method validates and deploys the new implementation contract, but also proposes an upgrade using an upgrade approval process on OpenZeppelin Defender. Supported for UUPS or Transparent proxies. Not currently supported for beacon proxies or beacons. For beacons, use `prepareUpgrade` along with a transaction proposal on Defender to upgrade the beacon to the deployed implementation.
Expand Down
21 changes: 19 additions & 2 deletions docs/modules/ROOT/pages/defender-deploy.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

The Hardhat Upgrades package can use https://docs.openzeppelin.com/defender/[OpenZeppelin Defender] for deployments instead of ethers.js, which allows for features such as gas pricing estimation, resubmissions, and automated bytecode and source code verification.

NOTE: OpenZeppelin Defender deployments is in beta and the functionality described here is subject to change.

[[configuration]]
== Configuration

Expand All @@ -19,6 +17,25 @@ module.exports = {
}
----

[[network-selection]]
== Network Selection

The network that is used with OpenZeppelin Defender is determined by the network that Hardhat is connected to.
If you want to ensure that a specific network is used with Defender, set the `network` field in the `defender` section of your `hardhat.config.js` or `hardhat.config.ts` file:
[source,js]
----
module.exports = {
defender: {
apiKey: process.env.API_KEY,
apiSecret: process.env.API_SECRET,
network: "my-mainnet-fork",
}
}
----
If set, this must be the name of a public, private or forked network in Defender. If Hardhat is connected to a different network while this is set, the deployment will not occur and will throw an error instead.

NOTE: This is required if you have multiple forked networks in Defender with the same chainId, in which case the one with name matching the `network` field will be used.

[[usage]]
== Usage

Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-hardhat/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Changelog

## Unreleased
## 3.0.5 (2024-03-08)

- Simplify console logging for `admin.transferProxyAdminOwnership`. ([#978](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/978))
- Support private networks and forked networks with Defender. ([#989](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/989))

## 3.0.4 (2024-02-27)

Expand Down
7 changes: 4 additions & 3 deletions packages/plugin-hardhat/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openzeppelin/hardhat-upgrades",
"version": "3.0.4",
"version": "3.0.5",
"description": "",
"repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/plugin-hardhat",
"license": "MIT",
Expand Down Expand Up @@ -37,8 +37,9 @@
"dependencies": {
"@openzeppelin/defender-admin-client": "^1.52.0",
"@openzeppelin/defender-base-client": "^1.52.0",
"@openzeppelin/defender-sdk-base-client": "^1.9.0",
"@openzeppelin/defender-sdk-deploy-client": "^1.9.0",
"@openzeppelin/defender-sdk-base-client": "^1.10.0",
"@openzeppelin/defender-sdk-deploy-client": "^1.10.0",
"@openzeppelin/defender-sdk-network-client": "^1.10.0",
"@openzeppelin/upgrades-core": "^1.32.0",
"chalk": "^4.1.0",
"debug": "^4.1.1",
Expand Down
12 changes: 12 additions & 0 deletions packages/plugin-hardhat/src/defender/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { DeployClient } from '@openzeppelin/defender-sdk-deploy-client';
import { NetworkClient } from '@openzeppelin/defender-sdk-network-client';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { getDefenderApiKey } from './utils';

export function getNetworkClient(hre: HardhatRuntimeEnvironment): NetworkClient {
return new NetworkClient(getDefenderApiKey(hre));
}

export function getDeployClient(hre: HardhatRuntimeEnvironment): DeployClient {
return new DeployClient(getDefenderApiKey(hre));
}
3 changes: 2 additions & 1 deletion packages/plugin-hardhat/src/defender/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/con
import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json';
import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json';

import { getNetwork, getDeployClient, parseTxOverrides } from './utils';
import { getNetwork, parseTxOverrides } from './utils';
import { DeployTransaction, DefenderDeployOptions, UpgradeOptions, EthersDeployOptions } from '../utils';
import debug from '../utils/debug';
import { getDeployData } from '../utils/deploy-impl';
import { ContractSourceNotFoundError } from '@openzeppelin/upgrades-core';
import { getDeployClient } from './client';

const deployableProxyContracts = [ERC1967Proxy, BeaconProxy, UpgradeableBeacon, TransparentUpgradeableProxy];

Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-hardhat/src/defender/get-approval-process.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';

import { getNetwork, getDeployClient } from './utils';
import { getNetwork } from './utils';
import { getDeployClient } from './client';
import { ApprovalProcessResponse } from '@openzeppelin/defender-sdk-deploy-client';

export interface ApprovalProcess {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import {
import { ContractFactory, ethers } from 'ethers';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { DefenderDeployOptions, UpgradeOptions } from '../utils';
import { getNetwork, enableDefender, getDeployClient } from './utils';
import { getNetwork, enableDefender } from './utils';
import { deployImplForUpgrade } from '../prepare-upgrade';
import { getDeployClient } from './client';

export interface UpgradeProposalResponse {
proposalId: string;
Expand Down
72 changes: 63 additions & 9 deletions packages/plugin-hardhat/src/defender/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
} from '@openzeppelin/upgrades-core';

import { Network, fromChainId } from '@openzeppelin/defender-sdk-base-client';
import { DeployClient, TxOverrides } from '@openzeppelin/defender-sdk-deploy-client';
import { TxOverrides } from '@openzeppelin/defender-sdk-deploy-client';

import { HardhatDefenderConfig } from '../type-extensions';
import { DefenderDeploy } from '../utils';
import debug from '../utils/debug';
import { Overrides } from 'ethers';

import { promisify } from 'util';
import { getDeployClient, getNetworkClient } from './client';
const sleep = promisify(setTimeout);

export function getDefenderApiKey(hre: HardhatRuntimeEnvironment): HardhatDefenderConfig {
Expand All @@ -33,11 +34,68 @@ export function getDefenderApiKey(hre: HardhatRuntimeEnvironment): HardhatDefend
export async function getNetwork(hre: HardhatRuntimeEnvironment): Promise<Network> {
const { provider } = hre.network;
const chainId = hre.network.config.chainId ?? (await getChainId(provider));
const network = fromChainId(chainId);
if (network === undefined) {
throw new Error(`Network ${chainId} is not supported by OpenZeppelin Defender`);

const networkNames = await getNetworkNames(chainId, hre);

const userConfigNetwork = hre.config.defender?.network;
if (networkNames.length === 0) {
throw new UpgradesError(
`The current network with chainId ${chainId} is not supported by OpenZeppelin Defender`,
() => `If this is a private or forked network, add it in Defender from the Manage tab.`,
);
} else if (networkNames.length === 1) {
const network = networkNames[0];
if (userConfigNetwork !== undefined && network !== userConfigNetwork) {
throw new UpgradesError(
`Detected network ${network} does not match specified network: ${userConfigNetwork}`,
() =>
`The current chainId ${chainId} is detected as ${network} on OpenZeppelin Defender, but the hardhat config's 'defender' section specifies network: ${userConfigNetwork}.\nEnsure you are connected to the correct network.`,
);
}
return network;
} else {
if (userConfigNetwork === undefined) {
throw new UpgradesError(
`Detected multiple networks with the same chainId ${chainId} on OpenZeppelin Defender: ${Array.from(networkNames).join(', ')}`,
() =>
`Specify the network that you want to use in your hardhat config file as follows:\ndefender: { network: 'networkName' }`,
);
} else if (!networkNames.includes(userConfigNetwork)) {
throw new UpgradesError(
`Specified network ${userConfigNetwork} does not match any of the detected networks for chainId ${chainId}: ${Array.from(networkNames).join(', ')}`,
() =>
`Ensure you are connected to the correct network, or specify one of the detected networks in your hardhat config file.`,
);
}
return userConfigNetwork;
}
return network;
}

async function getNetworkNames(chainId: number, hre: HardhatRuntimeEnvironment) {
const matchingNetworks = [];

const knownNetwork = fromChainId(chainId);
if (knownNetwork !== undefined) {
matchingNetworks.push(knownNetwork);
}

const networkClient = getNetworkClient(hre);

const forkedNetworks = await networkClient.listForkedNetworks();
for (const network of forkedNetworks) {
if (network.chainId === chainId) {
matchingNetworks.push(network.name);
}
}

const privateNetworks = await networkClient.listPrivateNetworks();
for (const network of privateNetworks) {
if (network.chainId === chainId) {
matchingNetworks.push(network.name);
}
}

return matchingNetworks;
}

export function enableDefender<T extends DefenderDeploy>(
Expand Down Expand Up @@ -87,10 +145,6 @@ export function disableDefender(
}
}

export function getDeployClient(hre: HardhatRuntimeEnvironment): DeployClient {
return new DeployClient(getDefenderApiKey(hre));
}

/**
* Gets the remote deployment response for the given id.
*
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-hardhat/src/type-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface HardhatDefenderConfig {
apiKey: string;
apiSecret: string;
useDefenderDeploy?: boolean;
network?: string;
}

declare module 'hardhat/types/config' {
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-hardhat/test/defender-deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ test.beforeEach(async t => {
'./utils': {
...require('../dist/defender/utils'),
getNetwork: () => t.context.fakeChainId,
},
'./client': {
getDeployClient: () => t.context.fakeDefenderClient,
},
'../utils/etherscan-api': {
Expand Down Expand Up @@ -582,6 +584,8 @@ async function testGetDeployedContractPolling(t, getDeployedContractStub, expect
'./utils': {
...require('../dist/defender/utils'),
getNetwork: () => fakeChainId,
},
'./client': {
getDeployClient: () => defenderClientWaits,
},
'../utils/etherscan-api': {
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-hardhat/test/defender-get-approval-process.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ test.beforeEach(async t => {
'./utils': {
...require('../dist/defender/utils'),
getNetwork: () => t.context.fakeChainId,
},
'./client': {
getDeployClient: () => t.context.fakeDefenderClient,
},
});
Expand Down
Loading

0 comments on commit 6869419

Please sign in to comment.