From 91a85f0b661397dad38fe2fa716ed0ae79424ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Jakubowski?= Date: Tue, 1 Feb 2022 18:00:09 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=9A=20Add=20chain=20configuration=20(#?= =?UTF-8?q?90)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🌚 Add chain configuration * Squash config into one constant * Lowercase network variable * Remove hardcoded network names * Rename network to chains * Distinguish block explorer from contract verifier * Adjust typings * Adjust coverage * Move types to one model file * Split ethereum and arbitrum chain configs * Rename contrat verifier api to etherscan verifier api * Lowercase RPC * Change error message * Update changeset --- .changeset/kind-toys-kneel.md | 5 ++ docs/source/cli.rst | 3 +- packages/mars/.nycrc | 2 +- packages/mars/src/options/chain/arbitrum.ts | 21 +++++++++ packages/mars/src/options/chain/ethereum.ts | 51 +++++++++++++++++++++ packages/mars/src/options/chain/index.ts | 5 ++ packages/mars/src/options/chain/model.ts | 13 ++++++ packages/mars/src/options/checks.ts | 4 +- packages/mars/src/options/config.ts | 8 ++-- packages/mars/src/options/usage.ts | 4 +- packages/mars/src/verification.ts | 26 +++++++---- 11 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 .changeset/kind-toys-kneel.md create mode 100644 packages/mars/src/options/chain/arbitrum.ts create mode 100644 packages/mars/src/options/chain/ethereum.ts create mode 100644 packages/mars/src/options/chain/index.ts create mode 100644 packages/mars/src/options/chain/model.ts diff --git a/.changeset/kind-toys-kneel.md b/.changeset/kind-toys-kneel.md new file mode 100644 index 0000000..b77bef6 --- /dev/null +++ b/.changeset/kind-toys-kneel.md @@ -0,0 +1,5 @@ +--- +'ethereum-mars': major +--- + +This update extracts RPC setup to dedicated configuration files for every chain. diff --git a/docs/source/cli.rst b/docs/source/cli.rst index ec5317f..e01b76c 100644 --- a/docs/source/cli.rst +++ b/docs/source/cli.rst @@ -60,7 +60,8 @@ CLI flags +-----------------+-------+------+----------------+--------------------+-----------------------------------------------------------------+ | --network | -n | Str | No | mainnet | Network name. Should be one of: | | | | | | | | -| | | | | | mainnet, development, kovan, ropsten, goerli, rinkeby | +| | | | | | mainnet, development, kovan, ropsten, goerli, rinkeby, | +| | | | | | arbitrum, arbitrum_rinkeby | | | | | | | | | | | | | | or RPC URL e.g. ``https://infura.io/...`` | +-----------------+-------+------+----------------+--------------------+-----------------------------------------------------------------+ diff --git a/packages/mars/.nycrc b/packages/mars/.nycrc index 54e458b..0a63e92 100644 --- a/packages/mars/.nycrc +++ b/packages/mars/.nycrc @@ -10,6 +10,6 @@ "check-coverage": true, "branches": 63, "lines": 69, - "functions": 58, + "functions": 55, "statements": 69 } diff --git a/packages/mars/src/options/chain/arbitrum.ts b/packages/mars/src/options/chain/arbitrum.ts new file mode 100644 index 0000000..d8d16ea --- /dev/null +++ b/packages/mars/src/options/chain/arbitrum.ts @@ -0,0 +1,21 @@ +import { Chain } from './model' + +export const arbitrum: Chain = { + chainId: 42161, + chainName: 'Arbitrum', + getPublicRpc: () => 'https://arb1.arbitrum.io/rpc', + getInfuraRpc: (infuraApiKey) => `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`, + getAlchemyRpc: (alchemyApiKey) => `https://arb-mainnet.g.alchemy.com/v2/${alchemyApiKey}`, + getBlockExplorerContractAddress: (contractAddress) => `https://arbiscan.io/address/${contractAddress}`, + getEtherscanVerifierApi: () => 'https://api.arbiscan.io/api', +} + +export const arbitrum_rinkeby: Chain = { + chainId: 421611, + chainName: 'Arbitrum Testnet', + getPublicRpc: () => 'https://rinkeby.arbitrum.io/rpc', + getInfuraRpc: (infuraApiKey) => `https://arbitrum-rinkeby.infura.io/v3/${infuraApiKey}`, + getAlchemyRpc: (alchemyApiKey) => `https://arb-rinkeby.g.alchemy.com/v2/${alchemyApiKey}`, + getBlockExplorerContractAddress: (contractAddress) => `https://testnet.arbiscan.io/address/${contractAddress}`, + getEtherscanVerifierApi: () => 'https://api-testnet.arbiscan.io/api', +} diff --git a/packages/mars/src/options/chain/ethereum.ts b/packages/mars/src/options/chain/ethereum.ts new file mode 100644 index 0000000..885967e --- /dev/null +++ b/packages/mars/src/options/chain/ethereum.ts @@ -0,0 +1,51 @@ +import { Chain } from './model' + +export const mainnet: Chain = { + chainId: 1, + chainName: 'Mainnet', + getPublicRpc: () => 'https://main-light.eth.linkpool.io/', + getInfuraRpc: (infuraApiKey) => `https://mainnet.infura.io/v3/${infuraApiKey}`, + getAlchemyRpc: (alchemyApiKey) => `https://eth-mainnet.alchemyapi.io/v2/${alchemyApiKey}`, + getBlockExplorerContractAddress: (contractAddress) => `https://etherscan.io/address/${contractAddress}`, + getEtherscanVerifierApi: () => 'https://api.etherscan.io/api', +} + +export const ropsten: Chain = { + chainId: 3, + chainName: 'Ropsten', + getPublicRpc: () => 'https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', + getInfuraRpc: (infuraApiKey) => `https://ropsten.infura.io/v3/${infuraApiKey}`, + getAlchemyRpc: (alchemyApiKey) => `https://eth-ropsten.alchemyapi.io/v2/${alchemyApiKey}`, + getBlockExplorerContractAddress: (contractAddress) => `https://ropsten.etherscan.io/address/${contractAddress}`, + getEtherscanVerifierApi: () => 'https://api-ropsten.etherscan.io/api', +} + +export const rinkeby: Chain = { + chainId: 4, + chainName: 'Rinkeby', + getPublicRpc: () => 'https://rinkeby-light.eth.linkpool.io/', + getInfuraRpc: (infuraApiKey) => `https://rinkeby.infura.io/v3/${infuraApiKey}`, + getAlchemyRpc: (alchemyApiKey) => `https://eth-rinkeby.alchemyapi.io/v2/${alchemyApiKey}`, + getBlockExplorerContractAddress: (contractAddress) => `https://rinkeby.etherscan.io/address/${contractAddress}`, + getEtherscanVerifierApi: () => 'https://api-rinkeby.etherscan.io/api', +} + +export const goerli: Chain = { + chainId: 5, + chainName: 'Goerli', + getPublicRpc: () => 'https://goerli-light.eth.linkpool.io/', + getInfuraRpc: (infuraApiKey) => `https://goerli.infura.io/v3/${infuraApiKey}`, + getAlchemyRpc: (alchemyApiKey) => `https://eth-goerli.alchemyapi.io/v2/${alchemyApiKey}`, + getBlockExplorerContractAddress: (contractAddress) => `https://goerli.etherscan.io/address/${contractAddress}`, + getEtherscanVerifierApi: () => 'https://api-goerli.etherscan.io/api', +} + +export const kovan: Chain = { + chainId: 42, + chainName: 'Kovan', + getPublicRpc: () => 'https://kovan.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', + getInfuraRpc: (infuraApiKey) => `https://kovan.infura.io/v3/${infuraApiKey}`, + getAlchemyRpc: (alchemyApiKey) => `https://eth-kovan.alchemyapi.io/v2/${alchemyApiKey}`, + getBlockExplorerContractAddress: (contractAddress) => `https://kovan.etherscan.io/address/${contractAddress}`, + getEtherscanVerifierApi: () => 'https://api-kovan.etherscan.io/api', +} diff --git a/packages/mars/src/options/chain/index.ts b/packages/mars/src/options/chain/index.ts new file mode 100644 index 0000000..2264f77 --- /dev/null +++ b/packages/mars/src/options/chain/index.ts @@ -0,0 +1,5 @@ +import { ChainSet } from './model' +import * as ethereumChains from './ethereum' +import * as arbitrumChains from './arbitrum' + +export const chains = { ...ethereumChains, ...arbitrumChains } as ChainSet diff --git a/packages/mars/src/options/chain/model.ts b/packages/mars/src/options/chain/model.ts new file mode 100644 index 0000000..88026ab --- /dev/null +++ b/packages/mars/src/options/chain/model.ts @@ -0,0 +1,13 @@ +export type Chain = { + chainId: number + chainName: string + getPublicRpc: () => string + getInfuraRpc: (infuraApiKey: string) => string + getAlchemyRpc: (alchemyApiKey: string) => string + getBlockExplorerContractAddress: (contractAddress: string) => string + getEtherscanVerifierApi: () => string +} + +export type ChainSet = { + [chainName: string]: Chain +} diff --git a/packages/mars/src/options/checks.ts b/packages/mars/src/options/checks.ts index f957189..43c9275 100644 --- a/packages/mars/src/options/checks.ts +++ b/packages/mars/src/options/checks.ts @@ -1,3 +1,4 @@ +import { chains } from './chain' import { usage } from './usage' const PRIVATE_KEY_REGEX = /^0x[a-f\d]{64}$/i @@ -31,10 +32,9 @@ export function ensureBoolean(value: unknown, message: string): asserts value is ensure((value) => typeof value === 'boolean', value, message) } -const NETWORKS = ['development', 'kovan', 'ropsten', 'goerli', 'rinkeby', 'mainnet'] const URL_REGEX = /^https?:\/\/[^\s]+$/ function isProperNetwork(value: unknown) { - return typeof value === 'string' && (NETWORKS.includes(value) || URL_REGEX.test(value)) + return typeof value === 'string' && (value in chains || URL_REGEX.test(value)) } export function ensureNetwork(value: unknown, message: string): asserts value is string { ensure(isProperNetwork, value, message) diff --git a/packages/mars/src/options/config.ts b/packages/mars/src/options/config.ts index 8ae6dd9..46aa6dc 100644 --- a/packages/mars/src/options/config.ts +++ b/packages/mars/src/options/config.ts @@ -10,6 +10,7 @@ import { getEnvironmentOptions } from './environment' import { Options } from './Options' import { ensureMultisigConfig } from '../multisig/multisigConfig' import { logConfig } from '../logging' +import { chains } from './chain' export async function getConfig(options: Options): Promise { const merged = { @@ -76,6 +77,7 @@ async function getSigner(options: Options) { } let rpcUrl: string | undefined let provider: providers.JsonRpcProvider | undefined + if (isNetworkProvider(network)) { // this causes 'MaxListenersExceededWarning: Possible EventEmitter memory leak detected.' when many contracts in use // details at https://github.com/ChainSafe/web3.js/issues/1648 @@ -83,11 +85,11 @@ async function getSigner(options: Options) { } else if (network.startsWith('http')) { rpcUrl = network } else if (alchemyApiKey) { - rpcUrl = `https://eth-${network}.alchemyapi.io/v2/${alchemyApiKey}` + rpcUrl = chains[network].getAlchemyRpc(alchemyApiKey) } else if (infuraApiKey) { - rpcUrl = `https://${network}.infura.io/v3/${infuraApiKey}` + rpcUrl = chains[network].getInfuraRpc(infuraApiKey) } else { - throw new Error('Cannot construct rpc url. This should never happen.') + rpcUrl = chains[network].getPublicRpc() } let signer: Signer diff --git a/packages/mars/src/options/usage.ts b/packages/mars/src/options/usage.ts index efa70e0..aa897a8 100644 --- a/packages/mars/src/options/usage.ts +++ b/packages/mars/src/options/usage.ts @@ -9,8 +9,8 @@ Options: also use the env variable PRIVATE_KEY. -n, --network [string] The network to run the deployment against. Can be either an Ethereum JSON-RPC url or one of: - development, kovan, ropsten, goerli, rinkeby or - mainnet. Default: mainnet. + development, kovan, ropsten, goerli, rinkeby, + mainnet, arbitrum or arbitrum_rinkeby. Default: mainnet. -i, --infura-key [key] The Infura api key to use for JSON-RPC. You can also use the env variable INFURA_KEY. -a, --alchemy-key [key] The Alchemy api key to use for JSON-RPC. You can diff --git a/packages/mars/src/verification.ts b/packages/mars/src/verification.ts index fc5c806..a28b194 100644 --- a/packages/mars/src/verification.ts +++ b/packages/mars/src/verification.ts @@ -4,6 +4,7 @@ import path from 'path' import chalk from 'chalk' import axios from 'axios' import querystring from 'querystring' +import { chains } from './options/chain' const isDirectory = (directoryPath: string) => fs.existsSync(path.resolve(directoryPath)) && fs.statSync(path.resolve(directoryPath)).isDirectory() @@ -82,17 +83,22 @@ type Awaited = T extends Promise ? U : never export type JsonInputs = Awaited> const etherscanUrl = (network?: string) => { - if (!network || network === 'mainnet') { - return 'https://api.etherscan.io/api' + if (!network) { + return chains.mainnet.getEtherscanVerifierApi() as string + } + const url = chains[network].getEtherscanVerifierApi() + if (url) { + return url as string + } else { + throw new Error('Block explorer not supported for requested network') } - return `https://api-${network}.etherscan.io/api` } -function getEtherscanContractAddress(address: string, network?: string) { +function getBlockExplorerContractAddress(address: string, network?: string) { if (!network || network === 'mainnet') { - return `https://etherscan.io/address/${address}` + return chains.mainnet.getBlockExplorerContractAddress(address) } - return `https://${network}.etherscan.io/address/${address}` + return chains[network].getBlockExplorerContractAddress(address) } async function getCompilerOptions(waffleConfigPath: string) { @@ -265,7 +271,9 @@ export async function verifySingleFile( return } if (await waitForResult(etherscanApiKey, guid)) { - console.log(chalk.bold(chalk.green(`Contract verified at ${getEtherscanContractAddress(address, network)}\n`))) + console.log( + chalk.bold(chalk.green(`Contract verified at ${getBlockExplorerContractAddress(address, network)}\n`)) + ) } } catch (err) { console.log(chalk.bold(chalk.yellow(`Error during verification: ${err.message ?? err}. Skipping\n`))) @@ -306,7 +314,9 @@ export async function verify( return } if (await waitForResult(etherscanApiKey, guid)) { - console.log(chalk.bold(chalk.green(`Contract verified at ${getEtherscanContractAddress(address, network)}\n`))) + console.log( + chalk.bold(chalk.green(`Contract verified at ${getBlockExplorerContractAddress(address, network)}\n`)) + ) } } catch (err) { console.log(chalk.bold(chalk.yellow(`Error during verification: ${err.message ?? err}. Skipping\n`)))