diff --git a/DEPLOYMENT_CHECKLIST.md b/DEPLOYMENT_CHECKLIST.md index a17077ca5..898368826 100644 --- a/DEPLOYMENT_CHECKLIST.md +++ b/DEPLOYMENT_CHECKLIST.md @@ -32,6 +32,7 @@ This checklist is seen as a guide to deploy the stack to a new chain. - [ ] Set the amount of minimum approvals the management DAO needs to `MANAGEMENT_DAO_MULTISIG_MINAPPROVALS` in `.env` - [ ] If new plugin builds are released - [ ] Double-check that the build- and release-metadata is published correctly by the deploy script and contracts +- [ ] Check all the tags and `func.dependencies` to ensure the `00-env-check.ts` file is executed at the beginning of the deployment. ## Deployment diff --git a/UPDATE_CHECKLIST.md b/UPDATE_CHECKLIST.md index 16cdb25eb..34124a891 100644 --- a/UPDATE_CHECKLIST.md +++ b/UPDATE_CHECKLIST.md @@ -30,6 +30,7 @@ This checklist is seen as a guide to update the existing deployment. - [ ] If new plugin builds are released - [ ] Double-check that the build- and release-metadata is published and updated correctly by the deploy script and contracts - [ ] If the plugin is used by the management DAO and the new build includes security relevant changes it must be applied immediately +- [ ] Check all the tags and `func.dependencies` to ensure the `00-env-check.ts` file is executed at the beginning of the deployment. ## Update diff --git a/packages/contracts/README.md b/packages/contracts/README.md index c0047b5c5..63831b008 100644 --- a/packages/contracts/README.md +++ b/packages/contracts/README.md @@ -16,35 +16,27 @@ yarn add @aragon/osx-artifacts To get started running your repository locally: -Copy `.env.example` into a file called `.env` or create a new one with these 3 keys defined: +1. Install packages from the root folder with `yarn` +2. Change directory into this package (`/pacakages/contracts`) +3. Run `yarn build` to compile the contracts +4. Run `yarn test` to execute the test suite (this can take a while, so see [performance optimizations](#performance-optimizations) for ways to speed up the tests). -```sh -# keys used for running tests -HARDHAT_DAO_ENS_DOMAIN=dao.eth -HARDHAT_PLUGIN_ENS_DOMAIN=plugin.eth -MANAGEMENT_DAO_SUBDOMAIN=management -``` +## Deployment -Run these commands on the project's root folder in your terminal: +Deployments use [hardhat-deploy](https://github.com/wighawag/hardhat-deploy), and follow most of the conventions in the HH deploy docs. -```shell -npx hardhat accounts -npx hardhat compile -npx hardhat clean -npx hardhat test -npx hardhat node -npx hardhat help -REPORT_GAS=true npx hardhat test -npx hardhat coverage -npx hardhat run scripts/deploy.ts -TS_NODE_FILES=true npx ts-node scripts/deploy.ts -npx eslint '**/*.{js,ts}' -npx eslint '**/*.{js,ts}' --fix -npx prettier '**/*.{json,sol,md}' --check -npx prettier '**/*.{json,sol,md}' --write -npx solhint 'contracts/**/*.sol' -npx solhint 'contracts/**/*.sol' --fix -``` +See the [deployment checklist](../../DEPLOYMENT_CHECKLIST.md) for full details on how to deploy the contracts to a proper network. + +When testing locally: + +1. `yarn deploy` will run the test scripts against the local hardhat node. +2. `yarn dev` will spin up a persistent hardhat node and execute the deploy script against it automatically. +3. `yarn deploy:reset` will clear any prior deploy state stored by hardhat deploy. +4. `yarn deploy:local` will deploy a against a persistent localhost fork, clearing the deploy state between runs + +Default values for all required environment variables are provided when running against hardhat, check the [`.env.example`](./.env.example) for details of what these are and what they mean. + +The private key provided by default is a hardhat publically known key for `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`. Don't use it outside of a local development context. > Tests can be sped up if needed. See [the test performance optimization](#performance-optimizations) section for more info. diff --git a/packages/contracts/deploy/env/00-env-check.ts b/packages/contracts/deploy/env/00-env-check.ts new file mode 100644 index 000000000..35d298ebe --- /dev/null +++ b/packages/contracts/deploy/env/00-env-check.ts @@ -0,0 +1,53 @@ +import { + daoDomainEnv, + ethKeyEnv, + managementDaoMultisigApproversEnv, + managementDaoMultisigListedOnlyEnv, + managementDaoMultisigMinApprovalsEnv, + managementDaoSubdomainEnv, + pluginDomainEnv, +} from '../../utils/environment'; +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +/** + * Pre-deployment check for required environment variables + * Although fetching these variables throws during execution, it's nicer + * to fail early and provide a more descriptive error message and avoid submitting + * redundant transactions. + */ +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + console.log('\nChecking Required Env Vars'); + + const {network} = hre; + + // fetch env values: in localhost or hardhat network, these have defaults + const daoDomain = daoDomainEnv(network); + const pluginDomain = pluginDomainEnv(network); + const managementDaoSubdomain = managementDaoSubdomainEnv(network); + const managementDaoMultisigApprovers = + managementDaoMultisigApproversEnv(network); + const managementDaoMultisigMinApprovals = + managementDaoMultisigMinApprovalsEnv(network); + const managementDaoMultisigListedOnly = + managementDaoMultisigListedOnlyEnv(network); + const ethKey = ethKeyEnv(network); + + // technically redundant as the above functions throw if the env var is missing + if ( + !daoDomain || + !pluginDomain || + !managementDaoSubdomain || + !managementDaoMultisigApprovers || + !managementDaoMultisigMinApprovals || + !managementDaoMultisigListedOnly || + !ethKey + ) { + throw new Error('Missing required env vars'); + } + + console.log('✅ All required env vars are set'); +}; +export default func; +// set the dependencies of other functions to `Env` to ensure this check runs first +func.tags = ['Env']; diff --git a/packages/contracts/deploy/helpers.ts b/packages/contracts/deploy/helpers.ts index 2256e9eba..912853157 100644 --- a/packages/contracts/deploy/helpers.ts +++ b/packages/contracts/deploy/helpers.ts @@ -7,6 +7,7 @@ import { } from '../typechain'; import {VersionCreatedEvent} from '../typechain/PluginRepo'; import {PluginRepoRegisteredEvent} from '../typechain/PluginRepoRegistry'; +import {isLocal, pluginDomainEnv} from '../utils/environment'; import { getNetworkNameByAlias, getLatestNetworkDeployment, @@ -129,7 +130,7 @@ export function getLatestContractAddress( const osxNetworkName = getNetworkNameByAlias(networkName); if (!osxNetworkName) { - if (networkName === 'hardhat') { + if (isLocal(hre.network)) { return ''; } throw new Error(`Failed to find network ${networkName}`); @@ -165,8 +166,7 @@ export async function createPluginRepo( const {network} = hre; const signers = await ethers.getSigners(); - const pluginDomain = - process.env[`${network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`] || ''; + const pluginDomain = pluginDomainEnv(network); if ( await isENSDomainRegistered( `${subdomain}.${pluginDomain}`, @@ -587,3 +587,6 @@ export function getManagementDAOMultisigAddress( } return address; } + +// hh-deploy cannot process files without default exports +export default async () => {}; diff --git a/packages/contracts/deploy/new/00_management-dao/00_management-dao.ts b/packages/contracts/deploy/new/00_management-dao/00_management-dao.ts index 3c3b12f03..9ccef0ca0 100644 --- a/packages/contracts/deploy/new/00_management-dao/00_management-dao.ts +++ b/packages/contracts/deploy/new/00_management-dao/00_management-dao.ts @@ -52,3 +52,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }; export default func; func.tags = ['New', 'ManagementDao']; +func.dependencies = ['Env']; diff --git a/packages/contracts/deploy/new/10_framework/00_ens_registry.ts b/packages/contracts/deploy/new/10_framework/00_ens_registry.ts index fb1a4867a..987e71e22 100644 --- a/packages/contracts/deploy/new/10_framework/00_ens_registry.ts +++ b/packages/contracts/deploy/new/10_framework/00_ens_registry.ts @@ -1,4 +1,5 @@ import {setupENS} from '../../../utils/ens'; +import {daoDomainEnv, pluginDomainEnv} from '../../../utils/environment'; import {ENS_ADDRESSES} from '../../helpers'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -9,15 +10,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {network} = hre; - // Prepare ENS. - const daoDomain = - process.env[`${network.name.toUpperCase()}_DAO_ENS_DOMAIN`] || ''; - const pluginDomain = - process.env[`${network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`] || ''; - - if (!daoDomain || !pluginDomain) { - throw new Error('DAO or Plugin ENS domains have not been set in .env'); - } + const daoDomain = daoDomainEnv(network); + const pluginDomain = pluginDomainEnv(network); const officialEnsRegistryAddress = ENS_ADDRESSES[network.name]; diff --git a/packages/contracts/deploy/new/10_framework/01_ens_subdomains.ts b/packages/contracts/deploy/new/10_framework/01_ens_subdomains.ts index e16dda9b6..21bee6e62 100644 --- a/packages/contracts/deploy/new/10_framework/01_ens_subdomains.ts +++ b/packages/contracts/deploy/new/10_framework/01_ens_subdomains.ts @@ -1,4 +1,5 @@ import {ENSRegistry__factory} from '../../../typechain'; +import {daoDomainEnv, pluginDomainEnv} from '../../../utils/environment'; import { getContractAddress, getENSAddress, @@ -14,14 +15,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const [deployer] = await ethers.getSigners(); // Get ENS subdomains - const daoDomain = - process.env[`${network.name.toUpperCase()}_DAO_ENS_DOMAIN`] || ''; - const pluginDomain = - process.env[`${network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`] || ''; - - if (!daoDomain || !pluginDomain) { - throw new Error('DAO or Plugin ENS domains have not been set in .env'); - } + const daoDomain = daoDomainEnv(network); + const pluginDomain = pluginDomainEnv(network); const ensRegistryAddress = await getENSAddress(hre); const ensRegistryContract = ENSRegistry__factory.connect( diff --git a/packages/contracts/deploy/new/10_framework/02_ens_subdomain_registrars.ts b/packages/contracts/deploy/new/10_framework/02_ens_subdomain_registrars.ts index 462f6f12a..003b5018d 100644 --- a/packages/contracts/deploy/new/10_framework/02_ens_subdomain_registrars.ts +++ b/packages/contracts/deploy/new/10_framework/02_ens_subdomain_registrars.ts @@ -1,5 +1,6 @@ import ensSubdomainRegistrarArtifact from '../../../artifacts/src/framework/utils/ens/ENSSubdomainRegistrar.sol/ENSSubdomainRegistrar.json'; import {DAO__factory, ENSRegistry__factory} from '../../../typechain'; +import {daoDomainEnv, pluginDomainEnv} from '../../../utils/environment'; import {getContractAddress, getENSAddress} from '../../helpers'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -19,17 +20,12 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const ensRegistryAddress = await getENSAddress(hre); - const daoDomain = - process.env[`${network.name.toUpperCase()}_DAO_ENS_DOMAIN`] || ''; - const pluginDomain = - process.env[`${network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`] || ''; + const daoDomain = daoDomainEnv(network); + const pluginDomain = pluginDomainEnv(network); + const daoNode = ethers.utils.namehash(daoDomain); const pluginNode = ethers.utils.namehash(pluginDomain); - if (!daoDomain || !pluginDomain) { - throw new Error('DAO or Plugin ENS domains have not been set in .env'); - } - await deploy('DAOENSSubdomainRegistrarProxy', { contract: ensSubdomainRegistrarArtifact, from: deployer.address, diff --git a/packages/contracts/deploy/new/10_framework/99_verifiy_step.ts b/packages/contracts/deploy/new/10_framework/99_verifiy_step.ts index 7a3b8b363..30a961aaf 100644 --- a/packages/contracts/deploy/new/10_framework/99_verifiy_step.ts +++ b/packages/contracts/deploy/new/10_framework/99_verifiy_step.ts @@ -7,6 +7,7 @@ import { PluginRepoRegistry__factory, PluginSetupProcessor__factory, } from '../../../typechain'; +import {daoDomainEnv, pluginDomainEnv} from '../../../utils/environment'; import {checkSetManagementDao, getContractAddress} from '../../helpers'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -48,9 +49,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } const node = await DAOENSSubdomainRegistrar.node(); - const expectedNode = ethers.utils.namehash( - process.env[`${hre.network.name.toUpperCase()}_DAO_ENS_DOMAIN`] || '' - ); + const expectedNode = ethers.utils.namehash(daoDomainEnv(hre.network)); if (node !== expectedNode) { throw new Error( `DAOENSSubdomainRegistrar node (${node}) doesn't match expected node (${expectedNode})` @@ -86,9 +85,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } const node = await PluginENSSubdomainRegistrar.node(); - const expectedNode = ethers.utils.namehash( - process.env[`${hre.network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`] || '' - ); + const expectedNode = ethers.utils.namehash(pluginDomainEnv(hre.network)); if (node !== expectedNode) { throw new Error( `PluginENSSubdomainRegistrar node (${node}) doesn't match expected node (${expectedNode})` diff --git a/packages/contracts/deploy/new/40_finalize-management-dao/20_register-management-dao-on-dao-registry.ts b/packages/contracts/deploy/new/40_finalize-management-dao/20_register-management-dao-on-dao-registry.ts index 4e30a486e..38d56eed5 100644 --- a/packages/contracts/deploy/new/40_finalize-management-dao/20_register-management-dao-on-dao-registry.ts +++ b/packages/contracts/deploy/new/40_finalize-management-dao/20_register-management-dao-on-dao-registry.ts @@ -1,4 +1,8 @@ import {DAO__factory, DAORegistry__factory} from '../../../typechain'; +import { + daoDomainEnv, + managementDaoSubdomainEnv, +} from '../../../utils/environment'; import { getContractAddress, getENSAddress, @@ -14,9 +18,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const [deployer] = await ethers.getSigners(); // Get info from .env - const daoSubdomain = process.env.MANAGEMENT_DAO_SUBDOMAIN || ''; - const daoDomain = - process.env[`${network.name.toUpperCase()}_DAO_ENS_DOMAIN`] || ''; + const daoSubdomain = managementDaoSubdomainEnv(network); + const daoDomain = daoDomainEnv(network); if (!daoSubdomain) throw new Error('ManagementDAO subdomain has not been set in .env'); diff --git a/packages/contracts/deploy/new/40_finalize-management-dao/30_install-multisig-on-management-dao.ts b/packages/contracts/deploy/new/40_finalize-management-dao/30_install-multisig-on-management-dao.ts index 896f4a6d8..055042f68 100644 --- a/packages/contracts/deploy/new/40_finalize-management-dao/30_install-multisig-on-management-dao.ts +++ b/packages/contracts/deploy/new/40_finalize-management-dao/30_install-multisig-on-management-dao.ts @@ -6,6 +6,11 @@ import { PluginSetupProcessor__factory, } from '../../../typechain'; import {InstallationPreparedEvent} from '../../../typechain/PluginSetupProcessor'; +import { + isLocal, + managementDaoMultisigApproversEnv, + managementDaoMultisigMinApprovalsEnv, +} from '../../../utils/environment'; import {hashHelpers} from '../../../utils/psp'; import {checkPermission, getContractAddress} from '../../helpers'; import {findEvent} from '@aragon/osx-commons-sdk'; @@ -18,7 +23,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {ethers, network} = hre; const [deployer] = await ethers.getSigners(); - if (network.name !== 'localhost' && network.name !== 'hardhat') { + if (!isLocal(network)) { if ( !('MANAGEMENT_DAO_MULTISIG_LISTEDONLY' in process.env) || !('MANAGEMENT_DAO_MULTISIG_MINAPPROVALS' in process.env) || @@ -28,11 +33,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } } - const approvers = process.env.MANAGEMENT_DAO_MULTISIG_APPROVERS?.split( - ',' - ) || [deployer.address]; + const approvers = managementDaoMultisigApproversEnv(network).split(','); const minApprovals = parseInt( - process.env.MANAGEMENT_DAO_MULTISIG_MINAPPROVALS || '1' + managementDaoMultisigMinApprovalsEnv(hre.network) ); // In case `MANAGEMENT_DAO_MULTISIG_LISTEDONLY` not present in .env // which applies only hardhat/localhost, use `true` setting for extra safety for tests. diff --git a/packages/contracts/deploy/new/40_finalize-management-dao/40_revoke-permissions.ts b/packages/contracts/deploy/new/40_finalize-management-dao/40_revoke-permissions.ts index 3231b835e..248f2b5c3 100644 --- a/packages/contracts/deploy/new/40_finalize-management-dao/40_revoke-permissions.ts +++ b/packages/contracts/deploy/new/40_finalize-management-dao/40_revoke-permissions.ts @@ -1,4 +1,5 @@ import {DAO__factory, PluginRepo__factory} from '../../../typechain'; +import {managementDaoSubdomainEnv} from '../../../utils/environment'; import {getContractAddress, managePermissions, Permission} from '../../helpers'; import {Operation} from '@aragon/osx-commons-sdk'; import {DeployFunction} from 'hardhat-deploy/types'; @@ -9,7 +10,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const [deployer] = await ethers.getSigners(); // Get info from .env - const daoSubdomain = process.env.MANAGEMENT_DAO_SUBDOMAIN || ''; + const daoSubdomain = managementDaoSubdomainEnv(hre.network); if (!daoSubdomain) throw new Error('ManagementDAO subdomain has not been set in .env'); diff --git a/packages/contracts/deploy/update/to_v1.3.0/10_DAOFactory.ts b/packages/contracts/deploy/update/to_v1.3.0/10_DAOFactory.ts index b2245abeb..fc577ac9e 100644 --- a/packages/contracts/deploy/update/to_v1.3.0/10_DAOFactory.ts +++ b/packages/contracts/deploy/update/to_v1.3.0/10_DAOFactory.ts @@ -65,3 +65,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }; export default func; func.tags = ['DAOFactory', 'v1.3.0']; +func.dependencies = ['Env']; diff --git a/packages/contracts/deploy/verification/99_conclude/10_verify-contracts.ts b/packages/contracts/deploy/verification/99_conclude/10_verify-contracts.ts index 779d98715..76e4745b2 100644 --- a/packages/contracts/deploy/verification/99_conclude/10_verify-contracts.ts +++ b/packages/contracts/deploy/verification/99_conclude/10_verify-contracts.ts @@ -1,3 +1,4 @@ +import {isLocal} from '../../../utils/environment'; import {verifyContract} from '../../../utils/etherscan'; import {DeployFunction} from 'hardhat-deploy/types'; import {HardhatRuntimeEnvironment} from 'hardhat/types'; @@ -29,8 +30,4 @@ export default func; func.tags = ['New', 'Verify']; func.runAtTheEnd = true; func.skip = (hre: HardhatRuntimeEnvironment) => - Promise.resolve( - hre.network.name === 'localhost' || - hre.network.name === 'hardhat' || - hre.network.name === 'coverage' - ); + Promise.resolve(isLocal(hre.network)); diff --git a/packages/contracts/docs/developer-portal/02-how-to-guides/02-plugin-development/07-publication/index.md b/packages/contracts/docs/developer-portal/02-how-to-guides/02-plugin-development/07-publication/index.md index 0b6bf0975..649bdb4bd 100644 --- a/packages/contracts/docs/developer-portal/02-how-to-guides/02-plugin-development/07-publication/index.md +++ b/packages/contracts/docs/developer-portal/02-how-to-guides/02-plugin-development/07-publication/index.md @@ -63,7 +63,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Get the plugin factory address let pluginRepoFactoryAddr: string; - if (network.name === 'localhost' || network.name === 'hardhat' || network.name === 'coverage') { + if (isLocal(network)) { const hardhatForkNetwork = process.env.HARDHAT_FORK_NETWORK ? process.env.HARDHAT_FORK_NETWORK : 'mainnet'; diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index eb612bf32..a600b4f29 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -78,7 +78,12 @@ const config: HardhatUserConfig = { gasPrice: 80000000000, deploy: ENABLE_DEPLOY_TEST ? ['./deploy'] - : ['./deploy/new', './deploy/verification'], + : ['./deploy/env', './deploy/new', './deploy/verification'], + }, + localhost: { + deploy: ENABLE_DEPLOY_TEST + ? ['./deploy'] + : ['./deploy/env', './deploy/new', './deploy/verification'], }, ...hardhatNetworks, }, diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 4a3d4db25..a4cfa18c7 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -24,6 +24,7 @@ "flatten": "hardhat flatten", "analyze": "mythx analyze", "deploy": "hardhat deploy", + "deploy:local": "yarn deploy --network localhost --reset", "dev": "yarn hardhat node --hostname 0.0.0.0", "prepublishOnly": "yarn build && yarn build:npm", "docgen": "hardhat docgen", diff --git a/packages/contracts/src/README.md b/packages/contracts/src/README.md index cc433e9be..cc151ef84 100644 --- a/packages/contracts/src/README.md +++ b/packages/contracts/src/README.md @@ -14,38 +14,21 @@ yarn add @aragon/osx-artifacts ## Get Started -To get started running your repository locally: - -Copy `.env.example` into a file called `.env` or create a new one with these 3 keys defined: +To get started running your repository locally, run these commands on the project's root folder in your terminal: ```sh -# keys used for running tests -HARDHAT_DAO_ENS_DOMAIN=dao.eth -HARDHAT_PLUGIN_ENS_DOMAIN=plugin.eth -MANAGEMENT_DAO_SUBDOMAIN=management -``` +# compile contracts +yarn build -Run these commands on the project's root folder in your terminal: +# run tests +yarn test -```shell -npx hardhat accounts -npx hardhat compile -npx hardhat clean -npx hardhat test -npx hardhat node -npx hardhat help -REPORT_GAS=true npx hardhat test -npx hardhat coverage -npx hardhat run scripts/deploy.ts -TS_NODE_FILES=true npx ts-node scripts/deploy.ts -npx eslint '**/*.{js,ts}' -npx eslint '**/*.{js,ts}' --fix -npx prettier '**/*.{json,sol,md}' --check -npx prettier '**/*.{json,sol,md}' --write -npx solhint 'contracts/**/*.sol' -npx solhint 'contracts/**/*.sol' --fix +# deploy locally +yarn deploy ``` +See the [package.json](./package.json) for all available scripts. Alternatively, consult the hardhat documentation for additional tasks and commands. + ## Documentation You can find all documentation regarding how to use this protocol in [Aragon's Developer Portal here](https://devs.aragon.org). diff --git a/packages/contracts/test/deploy/default-env.ts b/packages/contracts/test/deploy/default-env.ts new file mode 100644 index 000000000..f244c67a0 --- /dev/null +++ b/packages/contracts/test/deploy/default-env.ts @@ -0,0 +1,70 @@ +import { + HARDHAT_ACCOUNTS, + daoDomainEnv, + env, + ethKeyEnv, + managementDaoMultisigApproversEnv, + managementDaoMultisigListedOnlyEnv, + managementDaoMultisigMinApprovalsEnv, + managementDaoSubdomainEnv, + pluginDomainEnv, +} from '../../utils/environment'; +import {expect} from 'chai'; +import {network} from 'hardhat'; +import {Network} from 'hardhat/types'; + +describe('detect network', () => { + beforeEach(() => { + process.env = {}; + }); + + it('should detect the hardhat network', () => { + expect(network.name).to.equal('hardhat'); + }); + + it('provides default values for env vars if using the hardhat network', () => { + const daoDomain = env(network, 'DAO_ENS_DOMAIN', 'dao.eth'); + expect(daoDomain).to.equal('dao.eth'); + }); + + it('uses the environment variable if set', () => { + process.env['DAO_ENS_DOMAIN'] = 'mydao.eth'; + const daoDomain = env(network, 'DAO_ENS_DOMAIN', 'dao.eth'); + expect(daoDomain).to.equal('mydao.eth'); + }); + + it("Throws if env vars aren't set for the network other than hardhat", () => { + const network = {name: 'mainnet'} as unknown as Network; + delete process.env['DAO_ENS_DOMAIN']; + expect(() => env(network, 'DAO_ENS_DOMAIN', 'dao.eth')).to.throw( + 'Missing env var: DAO_ENS_DOMAIN' + ); + }); + + it("Doesn't throw if env vars are set for the network other than hardhat", () => { + const network: Network = {name: 'mainnet'} as unknown as Network; + process.env['DAO_ENS_DOMAIN'] = 'mydao.eth'; + const daoDomain = env(network, 'DAO_ENS_DOMAIN', 'dao.eth'); + expect(daoDomain).to.equal('mydao.eth'); + }); + + it('sets the correct fallbacks for each environment variable', () => { + expect(daoDomainEnv(network)).to.equal('dao.eth'); + expect(pluginDomainEnv(network)).to.equal('plugin.eth'); + expect(managementDaoSubdomainEnv(network)).to.equal('management'); + expect(managementDaoMultisigApproversEnv(network)).to.equal( + HARDHAT_ACCOUNTS[0].ADDRESS + ); + expect(managementDaoMultisigMinApprovalsEnv(network)).to.equal('1'); + expect(managementDaoMultisigListedOnlyEnv(network)).to.equal('true'); + expect(ethKeyEnv(network)).to.equal(HARDHAT_ACCOUNTS[1].KEY); + }); + + it('string interpolates the ENS subdomains', () => { + const network: Network = {name: 'FakeNet'} as unknown as Network; + process.env['FAKENET_DAO_ENS_DOMAIN'] = 'mydao.eth'; + process.env['FAKENET_PLUGIN_ENS_DOMAIN'] = 'myplugin.eth'; + expect(daoDomainEnv(network)).to.equal('mydao.eth'); + expect(pluginDomainEnv(network)).to.equal('myplugin.eth'); + }); +}); diff --git a/packages/contracts/test/deploy/managing-dao.ts b/packages/contracts/test/deploy/managing-dao.ts index e042109bb..a82fdcbc5 100644 --- a/packages/contracts/test/deploy/managing-dao.ts +++ b/packages/contracts/test/deploy/managing-dao.ts @@ -12,6 +12,11 @@ import { PluginRepoRegistry__factory, PluginRepo__factory, } from '../../typechain'; +import { + managementDaoMultisigApproversEnv, + managementDaoMultisigListedOnlyEnv, + managementDaoMultisigMinApprovalsEnv, +} from '../../utils/environment'; import {initializeDeploymentFixture} from '../test-utils/fixture'; import { DAO_PERMISSIONS, @@ -63,21 +68,19 @@ describe('Management DAO', function () { // deploy framework await deployAll(); - if ( - process.env.MANAGEMENT_DAO_MULTISIG_APPROVERS === undefined || - process.env.MANAGEMENT_DAO_MULTISIG_MINAPPROVALS === undefined || - process.env.MANAGEMENT_DAO_MULTISIG_LISTEDONLY === undefined - ) { - throw new Error('Management DAO Multisig settings not set in .env'); - } + const approversEnv = managementDaoMultisigApproversEnv(hre.network); - listedOnly = process.env.MANAGEMENT_DAO_MULTISIG_LISTEDONLY === 'true'; + minApprovals = parseInt(managementDaoMultisigMinApprovalsEnv(hre.network)); + listedOnly = managementDaoMultisigListedOnlyEnv(hre.network) === 'true'; - minApprovals = parseInt(process.env.MANAGEMENT_DAO_MULTISIG_MINAPPROVALS); + if (!approversEnv || !minApprovals || !listedOnly) { + throw new Error( + 'Management DAO Multisig settings not set in .env or fallbacks' + ); + } // Get approver addresses - const approverAddresses = - process.env.MANAGEMENT_DAO_MULTISIG_APPROVERS.split(','); + const approverAddresses = approversEnv.split(','); // Impersonate them as signers approvers = await Promise.all( diff --git a/packages/contracts/utils/environment.ts b/packages/contracts/utils/environment.ts new file mode 100644 index 000000000..8d1602328 --- /dev/null +++ b/packages/contracts/utils/environment.ts @@ -0,0 +1,65 @@ +import {HARDHAT_NETWORK_NAME} from 'hardhat/plugins'; +import {Network} from 'hardhat/types'; + +export const isLocal = (network: Network): boolean => + [HARDHAT_NETWORK_NAME, 'localhost', 'coverage'].includes(network.name); + +// known hardhat accounts and private keys unlocked by default in the HH node +export const HARDHAT_ACCOUNTS: Array<{KEY: string; ADDRESS: string}> = [ + { + KEY: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', + ADDRESS: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', + }, + { + KEY: '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d', + ADDRESS: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', + }, +] as const; + +/** + * Provides default values for environment variables if running in local + * else requires that the environment variables are set + * @param network The hardhat network object. + * @param name The name of the environment variable. + * @param defaultValue The fallback value to be used if not set and in development mode. + * @returns The value of the environment variable, or a fallback if possible. + */ +export function env( + network: Network, + name: string, + defaultValue: string +): string { + const value = process.env[name]; + if (!isLocal(network) && !value) { + throw new Error(`Missing env var: ${name}`); + } + return process.env[name] || defaultValue; +} + +/// Specific environment variables with hardcoded defaults in local environments + +export const daoDomainEnv = (network: Network): string => + env(network, `${network.name.toUpperCase()}_DAO_ENS_DOMAIN`, 'dao.eth'); + +export const pluginDomainEnv = (network: Network): string => + env(network, `${network.name.toUpperCase()}_PLUGIN_ENS_DOMAIN`, 'plugin.eth'); + +export const managementDaoSubdomainEnv = (network: Network): string => + env(network, 'MANAGEMENT_DAO_SUBDOMAIN', 'management'); + +export const managementDaoMultisigApproversEnv = (network: Network): string => + env( + network, + 'MANAGEMENT_DAO_MULTISIG_APPROVERS', + HARDHAT_ACCOUNTS[0].ADDRESS + ); + +export const managementDaoMultisigMinApprovalsEnv = ( + network: Network +): string => env(network, 'MANAGEMENT_DAO_MULTISIG_MINAPPROVALS', '1'); + +export const managementDaoMultisigListedOnlyEnv = (network: Network): string => + env(network, 'MANAGEMENT_DAO_MULTISIG_LISTEDONLY', 'true'); + +export const ethKeyEnv = (network: Network): string => + env(network, 'ETH_KEY', HARDHAT_ACCOUNTS[1].KEY);