diff --git a/.env.template b/.env.template new file mode 100644 index 00000000..1177d0e4 --- /dev/null +++ b/.env.template @@ -0,0 +1,20 @@ +# The mnemonic phrase used to derive the wallet's private keys +MNEMONIC=... + +# Index of the account generated from the mnemonic to be used as the Voucher Manager +IEXEC_VOUCHER_MANAGER_ACCOUNT_INDEX=... + +# Index of the account generated from the mnemonic to be used as the Voucher Minter +IEXEC_VOUCHER_MINTER_ACCOUNT_INDEX=... + +# The private key for accessing the production account +PROD_PRIVATE_KEY=... + +# Flag to indicate whether to use a local blockchain fork (set to 'true' for local fork development) +IS_LOCAL_FORK=... + +# Override the default iExec PoCo (Proof-of-Contribution) contract address +IEXEC_POCO_ADDRESS=... + +# Indicates whether the deployment involves a factory contract +USE_FACTORY=... diff --git a/CHANGELOG.md b/CHANGELOG.md index e0f31e92..4f168355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## vNEXT +- Better handling for env variables. (#55) - Remove references to blockscout v5. (#49) - Override the decimals function of ERC-20 to set the token's decimal precision to 9, aligning with RLC standards. (#50) - Verify VoucherProxy contracts. (#51) diff --git a/config/env.ts b/config/env.ts new file mode 100644 index 00000000..079ffe81 --- /dev/null +++ b/config/env.ts @@ -0,0 +1,35 @@ +import 'dotenv/config'; +import { z } from 'zod'; + +const addressRegex = /^0x[a-fA-F0-9]{40}$/; +const numericRegex = /^\d+$/; +const privateKeyRegex = /^([a-fA-F0-9]{64})$/; + +const envSchema = z.object({ + IEXEC_VOUCHER_MANAGER_ACCOUNT_INDEX: z + .string() + .refine((val) => numericRegex.test(val), 'Must be a numeric index if provided') + .transform((val) => Number(val)) + .optional(), + IEXEC_VOUCHER_MINTER_ACCOUNT_INDEX: z + .string() + .refine((val) => numericRegex.test(val), 'Must be a numeric index if provided') + .transform((val) => Number(val)) + .optional(), + IS_LOCAL_FORK: z.preprocess( + (val) => typeof val === 'string' && val.toLowerCase() === 'true', + z.boolean().default(false), + ), + MNEMONIC: z.string().optional(), + PROD_PRIVATE_KEY: z.string().regex(privateKeyRegex, 'Invalid private key format').optional(), + IEXEC_POCO_ADDRESS: z + .string() + .regex(addressRegex, 'Invalid Ethereum address if provided') + .optional(), + USE_FACTORY: z.preprocess( + (val) => typeof val === 'string' && val.toLowerCase() === 'true', + z.boolean().default(false), + ), +}); + +export const env = envSchema.parse(process.env); diff --git a/deploy/deploy.ts b/deploy/deploy.ts index 8a4405e0..fa6dc55d 100644 --- a/deploy/deploy.ts +++ b/deploy/deploy.ts @@ -6,7 +6,7 @@ import { ContractFactory } from 'ethers'; import { deployments, ethers, upgrades } from 'hardhat'; import { HardhatRuntimeEnvironment } from 'hardhat/types'; import deploymentConfig from '../config/deployment'; -import { isLocalFork } from '../hardhat.config'; +import { env } from '../config/env'; import * as voucherHubUtils from '../scripts/voucherHubUtils'; import * as voucherUtils from '../scripts/voucherUtils'; import { @@ -19,7 +19,7 @@ import { } from '../typechain-types'; export default async function (hre: HardhatRuntimeEnvironment) { - if (isLocalFork) { + if (env.IS_LOCAL_FORK) { /** * This fixes following issue when deploying to a local Bellecour fork: * `ProviderError: No known hardfork for execution on historical block [...] in chain with id 134.` @@ -182,15 +182,15 @@ export async function getDeploymentConfig(chainId: number) { // Read default config of the target chain. const config = deploymentConfig[chainId]; // Override config if required. - if (process.env.IEXEC_POCO_ADDRESS) { - config.pocoAddress = process.env.IEXEC_POCO_ADDRESS; + if (env.IEXEC_POCO_ADDRESS) { + config.pocoAddress = env.IEXEC_POCO_ADDRESS; } // Check final config. if (!ethers.isAddress(config.pocoAddress)) { throw new Error('Valid PoCo address must be provided'); } - if (process.env.FACTORY) { - config.factory = process.env.FACTORY == 'true'; + if (env.USE_FACTORY) { + config.factory = env.USE_FACTORY; } return config; } diff --git a/hardhat.config.ts b/hardhat.config.ts index def44235..57bf9fc5 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -12,11 +12,11 @@ import { defaultLocalhostNetworkParams, } from 'hardhat/internal/core/config/default-config'; import 'solidity-docgen'; +import { env } from './config/env'; import { forceZeroGasPriceWithSolidityCoverage } from './scripts/utils/modify-solidity-coverage-lib-api-js'; -const managerAccount = Number(process.env.IEXEC_VOUCHER_MANAGER_ACCOUNT_INDEX) || null; -const minterAccount = Number(process.env.IEXEC_VOUCHER_MINTER_ACCOUNT_INDEX) || null; -export const isLocalFork = process.env.LOCAL_FORK == 'true'; +const managerAccount = env.IEXEC_VOUCHER_MANAGER_ACCOUNT_INDEX || null; +const minterAccount = env.IEXEC_VOUCHER_MINTER_ACCOUNT_INDEX || null; const bellecourBlockscoutUrl = 'https://blockscout.bellecour.iex.ec'; const config: HardhatUserConfig = { @@ -50,9 +50,9 @@ const config: HardhatUserConfig = { hardhat: { hardfork: 'berlin', // No EIP-1559 before London fork accounts: { - mnemonic: process.env.MNEMONIC || HARDHAT_NETWORK_MNEMONIC, + mnemonic: env.MNEMONIC || HARDHAT_NETWORK_MNEMONIC, }, - ...(isLocalFork && { + ...(env.IS_LOCAL_FORK && { forking: { url: 'https://bellecour.iex.ec', }, @@ -65,7 +65,7 @@ const config: HardhatUserConfig = { ...defaultHardhatNetworkParams, ...defaultLocalhostNetworkParams, accounts: 'remote', // will use accounts set in hardhat network config - ...(isLocalFork && { + ...(env.IS_LOCAL_FORK && { chainId: 134, }), gasPrice: 0, @@ -74,7 +74,7 @@ const config: HardhatUserConfig = { chainId: 65535, url: 'http://localhost:8545', accounts: { - mnemonic: process.env.MNEMONIC || '', + mnemonic: env.MNEMONIC || '', }, gasPrice: 0, // Get closer to Bellecour network }, @@ -82,7 +82,7 @@ const config: HardhatUserConfig = { chainId: 134, url: 'https://bellecour.iex.ec', accounts: [ - process.env.PROD_PRIVATE_KEY || + env.PROD_PRIVATE_KEY || '0x0000000000000000000000000000000000000000000000000000000000000000', ], gasPrice: 0, diff --git a/package-lock.json b/package-lock.json index 63ca1fe3..515e4dcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@amxx/factory": "^1.0.0", "@nomicfoundation/hardhat-toolbox": "^5.0.0", "@openzeppelin/hardhat-upgrades": "^3.2.1", + "dotenv": "^16.4.7", "ethers": "^6.13.2", "hardhat": "^2.22.10", "hardhat-dependency-compiler": "^1.2.1", @@ -26,7 +27,8 @@ "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-solidity": "^1.4.1", "sol2uml": "^2.5.19", - "solidity-docgen": "^0.6.0-beta.36" + "solidity-docgen": "^0.6.0-beta.36", + "zod": "^3.24.1" } }, "node_modules/@adraffy/ens-normalize": { @@ -4665,6 +4667,19 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -11190,6 +11205,16 @@ "@ethersproject/web": "5.7.1", "@ethersproject/wordlists": "5.7.0" } + }, + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index f565a5c5..0954303f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "prepare": "husky", "build": "npx hardhat compile", "test": "npx hardhat test", - "test-ci": "npm run test && FACTORY=false npm run test", + "test-ci": "npm run test && USE_FACTORY=false npm run test", "coverage": "npx hardhat coverage", "format": "npx prettier --write", "doc": "npx hardhat docgen", @@ -49,7 +49,9 @@ "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-solidity": "^1.4.1", "sol2uml": "^2.5.19", - "solidity-docgen": "^0.6.0-beta.36" + "solidity-docgen": "^0.6.0-beta.36", + "dotenv": "^16.4.7", + "zod": "^3.24.1" }, "devDependencies_comment": { "@sol2uml": "UML generation does not work properly after v2.5.19. Try before bump."