Skip to content

Commit

Permalink
feat: add npm scripts using during live deployments
Browse files Browse the repository at this point in the history
  • Loading branch information
adjisb committed Sep 21, 2023
1 parent aaaec46 commit 8288202
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 38 deletions.
73 changes: 53 additions & 20 deletions packages/deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
32 changes: 19 additions & 13 deletions packages/deploy/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
11 changes: 6 additions & 5 deletions packages/deploy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@
],
"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",
"lint": "eslint --max-warnings 0 \"**/*.{js,ts}\"",
"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",
Expand All @@ -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",
Expand Down
37 changes: 37 additions & 0 deletions packages/deploy/utils/hardhatConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
139 changes: 139 additions & 0 deletions packages/deploy/utils/npmScriptHelper.js
Original file line number Diff line number Diff line change
@@ -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();
});
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 8288202

Please sign in to comment.