From 8288202eb45b887a9d40c87cd24ea6610b657f70 Mon Sep 17 00:00:00 2001 From: Andres Adjimann Date: Thu, 14 Sep 2023 08:36:38 -0300 Subject: [PATCH] feat: add npm scripts using during live deployments --- packages/deploy/README.md | 73 ++++++++---- packages/deploy/hardhat.config.ts | 32 +++--- packages/deploy/package.json | 11 +- packages/deploy/utils/hardhatConfig.ts | 37 ++++++ packages/deploy/utils/npmScriptHelper.js | 139 +++++++++++++++++++++++ yarn.lock | 8 ++ 6 files changed, 262 insertions(+), 38 deletions(-) create mode 100644 packages/deploy/utils/npmScriptHelper.js diff --git a/packages/deploy/README.md b/packages/deploy/README.md index 684dabbe8a..27d3283aa1 100644 --- a/packages/deploy/README.md +++ b/packages/deploy/README.md @@ -39,26 +39,59 @@ where: ## Testing We assume that the imported contracts are well tested in their own package by having enough unit tests and more that 80% -coverage. This repo contains integrations tests and tests that verify the integrity of the system. For example in the -case of the `SignedMultiGiveaway` contract we check the roles and the users assigned to them are correctly configured. - -The tests can be run in different contexts: - -- on hardhat: - - run all the deployment scripts just to test them: `yarn deploy` - - run the integration tests using `hardhat-deploy` fixtures: `yarn test` -- on a real network `testnet` or `mainnet`, run the integration tests over contracts already deployed and fail if - something is wrong, for example: `yarn test --network mumbai` -- to run on a fork of some network the following environment variables must be set: - - HARDHAT_DEPLOY_FIXTURE=true - - HARDHAT_FORK=mumbai - - HARDHAT_DEPLOY_ACCOUNTS_NETWORK=mumbai - - and then `yarn test` can be executed for integration tests or `yarn deploy` - with or without tags to test just the deployment scripts. - -To simplify the execution of the integration tests over forks the following targets are added to the package.json -file: `fork:mainnet`, `fork:polygon`, `fork:goerli` and `fork:mumbai`. +coverage. This repo contains integrations tests, tests for the deployment process and tests that verify the integrity of +the system. For example in the case of the `SignedMultiGiveaway` contract we check the roles and the users assigned to +them are correctly configured. + +To test the deployment process: + +- run all the deployment scripts on the hardhat local node just to test them: `yarn deploy` +- run the deployments scripts over a forks of a live network: `fork:deploy NETWORK` where NETWORK is `mainnet`,`polygon` + ,`goerli`,`mumbai`, etc. + +The tests the integrity of the system: + +- using hardhat local node and `hardhat-deploy` deployment scripts: `yarn test` +- on a live network over contracts that are already deployed: `yarn test --network NETWORK`. Where NETWORK is `mainnet` + , `polygon`, `mumbai`, etc. +- on a fork of a live network and `hardhat-deploy` deployment scripts: `yarn fork:test NETWORK` where NETWORK + is `mainnet`,`polygon`,`goerli`,`mumbai`, etc. + +### Tweaking hardhat when forking a live network + +We are using some tricks to control the way we run `hardhat` and `hardhat-deploy` when forking live networks. We change +the `hardhat` configuration depending on some environment variables to be able to run the fork, avoid running some +deployment scripts or skip the deployment scripts at all. The following table describes the environment variables and +their use: + +| Environment variable | Origin | Description | +|----------------------------------------|-------------------|-----------------------------------------------------| +| HARDHAT_FORK=mumbai | hardhat.config.ts | configure the hardhat network with forking enabled | +| HARDHAT_DEPLOY_ACCOUNTS_NETWORK=mumbai | hardhat-deploy | use this network for [named accounts](https://github.com/wighawag/hardhat-deploy#1-namedaccounts-ability-to-name-addresses)| +| HARDHAT_DEPLOY_FIXTURE=true | hardhat-deploy | run the deployment scripts before running the tests | +| HARDHAT_DEPLOY_NO_IMPERSONATION=true | hardhat-deploy | Optional. Don't [impersonate unknown accounts](https://hardhat.org/hardhat-network/docs/reference#hardhat_impersonateaccount)| +| HARDHAT_FORK_INCLUDE_MOCKS=false | hardhat.config.ts | Optional. Include mock deploy scripts in the fork | +| HARDHAT_SKIP_FIXTURES=false | hardhat.config.ts | Optional. Skip all the deployment scripts | +| HARDHAT_FORK_NUMBER=9999999999 | hardhat.config.ts | Optional. Forking block number | + +There is a `npmScriptHlper` script used in the `package.json` file to simplify the execution of hardhat while setting +the right environment variables. Run `node utils/npmScriptHelper.js` to see the script usage. + +## Contract verification + +To verify the contracts we use +the [hardhat-deploy](https://github.com/wighawag/hardhat-deploy#4-hardhat-etherscan-verify) +plugin support for verification, check the manual for details. + +To do the verification on etherscan/polygonscan you must: + +- set the environment variable `ETHERSCAN_API_KEY` with the api-key obtained from the etherscan in the `.env` file +- run the following command: `yarn hardhat etherscan-verify --network NETWORK`, where NETWORK is mainnet, polygon, + mumbai, etc. + +To verify just one contract add the `--contract-name CONTRACT_NAME` argument where CONTRACT_NAME is the name of the +contract to verify, if you are using upgradable contract you must verify `CONTRACT_NAME_Proxy` +and `CONTRACT_NAME_Implementation` separately. # Adding contract from a new package diff --git a/packages/deploy/hardhat.config.ts b/packages/deploy/hardhat.config.ts index cf76b49fea..1fa3f6b72f 100644 --- a/packages/deploy/hardhat.config.ts +++ b/packages/deploy/hardhat.config.ts @@ -3,7 +3,11 @@ import '@nomicfoundation/hardhat-chai-matchers'; import '@nomicfoundation/hardhat-network-helpers'; import '@nomiclabs/hardhat-ethers'; import 'hardhat-deploy'; -import {addForkingSupport, addNodeAndMnemonic} from './utils/hardhatConfig'; +import { + addForkingSupport, + addNodeAndMnemonic, + skipDeploymentsOnLiveNetworks, +} from './utils/hardhatConfig'; import './tasks/importedPackages'; // Package name : solidity source code path @@ -265,16 +269,18 @@ const compilers = [ }, })); -const config = addForkingSupport({ - importedPackages, - namedAccounts, - networks: addNodeAndMnemonic(networks), - mocha: { - timeout: 0, - ...(!process.env.CI ? {} : {invert: true, grep: '@skip-on-ci'}), - }, - solidity: { - compilers, - }, -}); +const config = skipDeploymentsOnLiveNetworks( + addForkingSupport({ + importedPackages, + namedAccounts, + networks: addNodeAndMnemonic(networks), + mocha: { + timeout: 0, + ...(!process.env.CI ? {} : {invert: true, grep: '@skip-on-ci'}), + }, + solidity: { + compilers, + }, + }) +); export default config; diff --git a/packages/deploy/package.json b/packages/deploy/package.json index 05cb15f5d1..f03d043fdf 100644 --- a/packages/deploy/package.json +++ b/packages/deploy/package.json @@ -22,7 +22,7 @@ ], "scripts": { "compile": "hardhat compile", - "test": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true NODE_ENV=test hardhat test", + "test": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true hardhat test", "void:deploy": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192 --unhandled-rejections=strict\" hardhat deploy", "deploy": "npm run void:deploy", "hardhat": "hardhat", @@ -30,10 +30,10 @@ "lint:fix": "eslint --fix \"**/*.{js,ts}\"", "format": "prettier --check \"**/*.{ts,js}\"", "format:fix": "prettier --write \"**/*.{ts,js}\"", - "fork:mainnet": "cross-env HARDHAT_FORK=mainnet HARDHAT_DEPLOY_ACCOUNTS_NETWORK=mainnet HARDHAT_DEPLOY_FIXTURE=true NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true NODE_ENV=test hardhat test", - "fork:polygon": "cross-env HARDHAT_FORK=polygon HARDHAT_DEPLOY_ACCOUNTS_NETWORK=polygon HARDHAT_DEPLOY_FIXTURE=true NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true NODE_ENV=test hardhat test", - "fork:goerli": "cross-env HARDHAT_FORK=goerli HARDHAT_DEPLOY_ACCOUNTS_NETWORK=goerli HARDHAT_DEPLOY_FIXTURE=true NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true NODE_ENV=test hardhat test", - "fork:mumbai": "cross-env HARDHAT_FORK=mumbai HARDHAT_DEPLOY_ACCOUNTS_NETWORK=mumbai HARDHAT_DEPLOY_FIXTURE=true NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true NODE_ENV=test hardhat test" + "verify": "yarn hardhat etherscan-verify --network", + "live:deploy": "node ./utils/npmScriptHelper.js deploy", + "fork:deploy": "node ./utils/npmScriptHelper.js fork:deploy", + "fork:test": "node ./utils/npmScriptHelper.js fork:test" }, "devDependencies": { "@ethersproject/abi": "^5.7.0", @@ -51,6 +51,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.8", "@typescript-eslint/parser": "^5.59.8", "chai": "^4.3.7", + "child_process": "^1.0.2", "cross-env": "^7.0.3", "dotenv": "^16.1.4", "dotenv-cli": "^7.2.1", diff --git a/packages/deploy/utils/hardhatConfig.ts b/packages/deploy/utils/hardhatConfig.ts index acccceb17f..af75d33a08 100644 --- a/packages/deploy/utils/hardhatConfig.ts +++ b/packages/deploy/utils/hardhatConfig.ts @@ -57,16 +57,53 @@ export function addNodeAndMnemonic( return networks; } +export function skipDeploymentsOnLiveNetworks( + conf: HardhatUserConfig +): HardhatUserConfig { + // We want to run deployments on hardhat but not on live nets + // The problem is that hardhat-deploy always run the fixtures when testing + // maybe there is a better way to do it: + const testingLive = + process.argv.some((x) => x === 'test') && + process.argv.some((x) => x === '--network'); + if (!conf.networks || !testingLive) { + return conf; + } + const networks = conf.networks as NetworksUserConfig; + return { + ...conf, + networks: Object.keys(networks).reduce( + (acc, val) => ({ + ...acc, + [val]: {...networks[val], deploy: []}, + }), + {} + ), + }; +} + export function addForkingSupport(conf: HardhatUserConfig): HardhatUserConfig { if (!process.env.HARDHAT_FORK) { return conf; } + const d = + (conf.networks && + conf.networks['hardhat'] && + conf.networks['hardhat']?.deploy) || + []; + const deploy = typeof d === 'string' ? [d] : d; return { ...conf, networks: { ...conf.networks, hardhat: { ...(conf.networks ? conf.networks['hardhat'] : {}), + deploy: process.env.HARDHAT_FORK_INCLUDE_MOCKS + ? deploy + : deploy.filter((x) => !x.includes('mock')), + accounts: { + mnemonic: getMnemonic(process.env.HARDHAT_FORK), + }, forking: process.env.HARDHAT_FORK ? { enabled: true, diff --git a/packages/deploy/utils/npmScriptHelper.js b/packages/deploy/utils/npmScriptHelper.js new file mode 100644 index 0000000000..7b5fbad2ac --- /dev/null +++ b/packages/deploy/utils/npmScriptHelper.js @@ -0,0 +1,139 @@ +/* + This script set the environment variables needed to run hardhat in a fork + before executing it. + See the testing section of the `README.md` file. + Based on scripts.js in the core package!!! + */ +const path = require('path'); +const {spawnSync} = require('child_process'); +const dotenv = require('dotenv'); + +dotenv.config(); +const myName = path.basename(__filename); +const expectedOptions = { + blockNumber: 'string', + 'no-impersonation': 'boolean', + 'include-mocks': 'boolean', + debug: 'boolean', + skipFixtures: 'boolean', +}; + +function parseArgs() { + const rawArgs = process.argv.slice(process.argv.indexOf(__filename) + 1); + const [command, network, file] = rawArgs; + const numFixedArgs = ['run', 'export', 'fork:run'].includes(command) ? 3 : 2; + if (rawArgs.length < numFixedArgs) { + throw new Error('missing argument'); + } + const fixedArgs = []; + const options = {}; + const extra = []; + const alreadyCounted = {}; + for (let i = 0; i < rawArgs.length; i++) { + const rawArg = rawArgs[i]; + if (rawArg.startsWith('--')) { + const optionName = rawArg.slice(2); + const optionDetected = expectedOptions[optionName]; + if (!alreadyCounted[optionName] && optionDetected) { + alreadyCounted[optionName] = true; + if (optionDetected === 'boolean') { + options[optionName] = true; + } else { + i++; + options[optionName] = rawArgs[i]; + } + continue; + } + } + if (fixedArgs.length < numFixedArgs) { + if (Object.keys(alreadyCounted).length) { + throw new Error( + `expected ${numFixedArgs} fixed args, got only ${fixedArgs.length}` + ); + } + fixedArgs.push(rawArg); + } else { + extra.push(rawArg); + } + } + return {command, network, file, options, extra, fixedArgs}; +} + +function usage() { + console.log( + `Usage: node ${myName} command network [ file ] ` + + Object.keys(expectedOptions) + .map( + (x) => + `[ --${x} ${ + expectedOptions[x] === 'string' ? x.toUpperCase() : '' + } ]` + ) + .join(' ') + ); + console.log('\tfile is only used in the run, export and fork:run commands'); + console.log( + '\t--skip-test-deployments/ntd/--ntd is the default for this command, use --include-mocks if needed' + ); +} + +function getEnv({command, network, options}) { + const env = []; + if (options.debug) { + env.push(`DEBUG="*hardhat-deploy"`); + } + if (options.skipFixtures) { + env.push(`HARDHAT_SKIP_FIXTURES=true"`); + } + + // What follows only make sense on forked networks + if (!command.includes('fork')) { + return env; + } + if (options['include-mocks']) { + env.push(`HARDHAT_FORK_INCLUDE_MOCKS=true`); + } + if (options.blockNumber) { + env.push(`HARDHAT_FORK_NUMBER=${options.blockNumber}`); + } + if (options['no-impersonation']) { + env.push(`HARDHAT_DEPLOY_NO_IMPERSONATION=true`); + } + env.push( + `HARDHAT_DEPLOY_ACCOUNTS_NETWORK=${network}`, + `HARDHAT_FORK=${network}` + ); + return env; +} + +async function main() { + const args = parseArgs(); + const {network, file} = args; + + const env = getEnv(args).join(' '); + const extraArgs = args.extra.join(' '); + const commands = { + run: `${env} HARDHAT_NETWORK=${network} ts-node --files ${file} ${extraArgs}`, + deploy: `${env} hardhat --network ${network} deploy ${extraArgs}`, + test: `${env} hardhat --network ${network} test ${extraArgs}`, + export: `${env} hardhat --network ${network} export --export ${file}`, + 'fork:run': `${env} HARDHAT_NETWORK=${network} ts-node --files ${file} ${extraArgs}`, + 'fork:deploy': `${env} hardhat deploy ${extraArgs}`, + 'fork:test': `${env} HARDHAT_DEPLOY_FIXTURE=true HARDHAT_COMPILE=true hardhat test ${extraArgs}`, + 'fork:dev': `${env} hardhat node --watch --export contractsInfo.json ${extraArgs}`, + }; + const cmd = commands[args.command]; + if (!cmd) { + throw new Error(`invalid command ${args.command}`); + } + console.log(`${myName} executing:`, cmd); + await spawnSync('yarn', ['cross-env', ...cmd.split(' ')], { + stdio: 'inherit', + shell: true, + }); +} + +main().catch((err) => { + console.error(err.toString()); + usage(); +}); diff --git a/yarn.lock b/yarn.lock index dd48097446..f395ca285a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1978,6 +1978,7 @@ __metadata: "@typescript-eslint/eslint-plugin": ^5.59.8 "@typescript-eslint/parser": ^5.59.8 chai: ^4.3.7 + child_process: ^1.0.2 cross-env: ^7.0.3 dotenv: ^16.1.4 dotenv-cli: ^7.2.1 @@ -4062,6 +4063,13 @@ __metadata: languageName: node linkType: hard +"child_process@npm:^1.0.2": + version: 1.0.2 + resolution: "child_process@npm:1.0.2" + checksum: bd814d82bc8c6e85ed6fb157878978121cd03b5296c09f6135fa3d081fd9a6a617a6d509c50397711df713af403331241a9c0397a7fad30672051485e156c2a1 + languageName: node + linkType: hard + "chokidar@npm:3.3.0": version: 3.3.0 resolution: "chokidar@npm:3.3.0"