From c9c4f722ee23482cfa67e9afb29a0015f80f125f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Fri, 12 Aug 2022 14:46:45 +0200 Subject: [PATCH 1/9] feat: refactor GRE to support L1 and L2 simultaneously MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/accounts.ts | 55 +++++++++++++++ gre/config.ts | 138 +++++++++++++++++++++++++++++++++++++ gre/gre.ts | 70 +++++++++++++++++++ gre/helpers/chains.ts | 45 ++++++++++++ gre/helpers/errors.ts | 7 ++ gre/type-extensions.d.ts | 75 ++++++++++++++++++++ hardhat.config.ts | 14 +++- tasks/gre.ts | 81 ---------------------- tasks/type-extensions.d.ts | 30 -------- tsconfig.json | 3 +- 10 files changed, 404 insertions(+), 114 deletions(-) create mode 100644 gre/accounts.ts create mode 100644 gre/config.ts create mode 100644 gre/gre.ts create mode 100644 gre/helpers/chains.ts create mode 100644 gre/helpers/errors.ts create mode 100644 gre/type-extensions.d.ts delete mode 100644 tasks/gre.ts delete mode 100644 tasks/type-extensions.d.ts diff --git a/gre/accounts.ts b/gre/accounts.ts new file mode 100644 index 000000000..c195d5703 --- /dev/null +++ b/gre/accounts.ts @@ -0,0 +1,55 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { providers } from 'ethers' +import { getItemValue, readConfig } from '../cli/config' +import { AccountNames, NamedAccounts } from './type-extensions' + +const namedAccountList: AccountNames[] = [ + 'arbitrator', + 'governor', + 'authority', + 'availabilityOracle', + 'pauseGuardian', + 'allocationExchangeOwner', +] + +export async function getNamedAccounts( + provider: providers.JsonRpcProvider, + graphConfigPath: string, +): Promise { + const namedAccounts = namedAccountList.reduce(async (accountsPromise, name) => { + const accounts = await accountsPromise + const address = getItemValue(readConfig(graphConfigPath, true), `general/${name}`) + accounts[name] = await SignerWithAddress.create(provider.getSigner(address)) + return accounts + }, Promise.resolve({} as NamedAccounts)) + + return namedAccounts +} + +export async function getDeployer(provider: providers.JsonRpcProvider): Promise { + const signer = provider.getSigner(0) + return SignerWithAddress.create(signer) +} + +export async function getTestAccounts( + provider: providers.JsonRpcProvider, + graphConfigPath: string, +): Promise { + // Get list of privileged accounts we don't want as test accounts + const namedAccounts = await getNamedAccounts(provider, graphConfigPath) + const blacklist = namedAccountList.map((a) => { + const account = namedAccounts[a] + return account.address + }) + blacklist.push((await getDeployer(provider)).address) + + // Get signers and filter out blacklisted accounts + const accounts = await provider.listAccounts() + const signers = await Promise.all( + accounts.map(async (account) => await SignerWithAddress.create(provider.getSigner(account))), + ) + + return signers.filter((s) => { + return !blacklist.includes(s.address) + }) +} diff --git a/gre/config.ts b/gre/config.ts new file mode 100644 index 000000000..982ba58f2 --- /dev/null +++ b/gre/config.ts @@ -0,0 +1,138 @@ +import fs from 'fs' +import { providers } from 'ethers' + +import { NetworkConfig, NetworksConfig } from 'hardhat/types/config' +import { HardhatRuntimeEnvironment } from 'hardhat/types/runtime' +import { HttpNetworkConfig } from 'hardhat/types/config' + +import { GraphRuntimeEnvironmentOptions } from './type-extensions' +import { GREPluginError } from './helpers/errors' +import GraphChains from './helpers/chains' + +interface GREChainData { + l1ChainId: number + l2ChainId: number + isHHL1: boolean + isHHL2: boolean +} + +interface GREProviderData { + l1Provider: providers.JsonRpcProvider + l2Provider: providers.JsonRpcProvider +} + +interface GREGraphConfigData { + l1GraphConfigPath: string + l2GraphConfigPath: string +} + +export function getAddressBookPath( + hre: HardhatRuntimeEnvironment, + opts: GraphRuntimeEnvironmentOptions, +): string { + const addressBookPath = opts.addressBook ?? hre.config.graph.addressBook + + if (addressBookPath === undefined) { + throw new GREPluginError(`Must set a an addressBook path!`) + } + + if (!fs.existsSync(addressBookPath)) { + throw new GREPluginError(`Address book not found: ${addressBookPath}`) + } + + return addressBookPath +} + +export function getChains(mainChainId: number | undefined): GREChainData { + if (!GraphChains.isSupported(mainChainId)) { + const supportedChains = GraphChains.chains.join(',') + throw new GREPluginError( + `Chain ${mainChainId} is not supported! Supported chainIds: ${supportedChains}.`, + ) + } + + // If mainChainId is supported there is a supported counterpart chainId so both chains are not undefined + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + mainChainId = mainChainId! + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const secondaryChainId = GraphChains.counterpart(mainChainId!)! + + const isHHL1 = GraphChains.isL1(mainChainId) + const isHHL2 = GraphChains.isL2(mainChainId) + const l1ChainId = isHHL1 ? mainChainId : secondaryChainId + const l2ChainId = isHHL2 ? mainChainId : secondaryChainId + + return { + l1ChainId, + l2ChainId, + isHHL1, + isHHL2, + } +} + +export function getProviders( + hre: HardhatRuntimeEnvironment, + l1ChainId: number, + l2ChainId: number, +): GREProviderData { + const l1Network = getNetworkByChainId(hre.config.networks, l1ChainId) as HttpNetworkConfig + const l2Network = getNetworkByChainId(hre.config.networks, l2ChainId) as HttpNetworkConfig + + for (const network of [l1Network, l2Network]) { + if (network === undefined || network.url === undefined) { + throw new GREPluginError(`Must set a provider url for chain ${l1ChainId}!`) + } + } + + const l1Provider = new providers.JsonRpcProvider(l1Network.url) + const l2Provider = new providers.JsonRpcProvider(l2Network.url) + + return { + l1Provider, + l2Provider, + } +} + +export function getGraphConfigPaths( + hre: HardhatRuntimeEnvironment, + opts: GraphRuntimeEnvironmentOptions, + l1ChainId: number, + l2ChainId: number, +): GREGraphConfigData { + const l1Network = getNetworkByChainId(hre.config.networks, l1ChainId) + const l2Network = getNetworkByChainId(hre.config.networks, l2ChainId) + + // Priority is as follows: + // - hre.graph() init parameter + // - hh network config + // - hh graph config (layer specific: l1GraphConfig, l2GraphConfig) + const l1GraphConfigPath = + opts.l1GraphConfig ?? l1Network?.graphConfig ?? hre.config.graph.l1GraphConfig + const l2GraphConfigPath = + opts.l2GraphConfig ?? l2Network?.graphConfig ?? hre.config.graph.l2GraphConfig + + for (const configPath of [l1GraphConfigPath, l2GraphConfigPath]) { + if (configPath === undefined) { + throw new GREPluginError(`Must set a graphConfig path!`) + } + if (!fs.existsSync(configPath)) { + throw new GREPluginError(`Graph config file not found: ${configPath}`) + } + } + + return { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + l1GraphConfigPath: l1GraphConfigPath!, + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + l2GraphConfigPath: l2GraphConfigPath!, + } +} + +function getNetworkByChainId(networks: NetworksConfig, chainId: number): NetworkConfig | undefined { + return Object.keys(networks) + .map((n) => networks[n]) + .find((n) => n.chainId === chainId) +} diff --git a/gre/gre.ts b/gre/gre.ts new file mode 100644 index 000000000..93ac319ae --- /dev/null +++ b/gre/gre.ts @@ -0,0 +1,70 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import { extendEnvironment } from 'hardhat/config' +import { lazyFunction, lazyObject } from 'hardhat/plugins' + +import { getAddressBook } from '../cli/address-book' +import { loadContracts } from '../cli/contracts' +import { readConfig } from '../cli/config' +import { + GraphNetworkEnvironment, + GraphRuntimeEnvironment, + GraphRuntimeEnvironmentOptions, +} from './type-extensions' +import { providers } from 'ethers' +import { getChains, getProviders, getAddressBookPath, getGraphConfigPaths } from './config' +import { getDeployer, getNamedAccounts, getTestAccounts } from './accounts' + +// Graph Runtime Environment (GRE) extensions for the HRE +extendEnvironment((hre: HardhatRuntimeEnvironment) => { + hre.graph = (opts: GraphRuntimeEnvironmentOptions = {}) => { + const { l1ChainId, l2ChainId, isHHL1 } = getChains(hre.network.config.chainId) + const { l1Provider, l2Provider } = getProviders(hre, l1ChainId, l2ChainId) + const addressBookPath = getAddressBookPath(hre, opts) + const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( + hre, + opts, + l1ChainId, + l2ChainId, + ) + + const l1Graph: GraphNetworkEnvironment = buildGraphNetworkEnvironment( + l1ChainId, + l1Provider, + l1GraphConfigPath, + addressBookPath, + ) + + const l2Graph: GraphNetworkEnvironment = buildGraphNetworkEnvironment( + l2ChainId, + l2Provider, + l2GraphConfigPath, + addressBookPath, + ) + + const gre: GraphRuntimeEnvironment = { + ...(isHHL1 ? l1Graph : l2Graph), + l1: l1Graph, + l2: l2Graph, + } + + return gre + } +}) + +function buildGraphNetworkEnvironment( + chainId: number, + provider: providers.JsonRpcProvider, + graphConfigPath: string, + addressBookPath: string, +): GraphNetworkEnvironment { + return { + addressBook: lazyObject(() => getAddressBook(addressBookPath, chainId.toString())), + graphConfig: lazyObject(() => readConfig(graphConfigPath, true)), + contracts: lazyObject(() => + loadContracts(getAddressBook(addressBookPath, chainId.toString()), provider), + ), + getDeployer: lazyFunction(() => () => getDeployer(provider)), + getNamedAccounts: lazyFunction(() => () => getNamedAccounts(provider, graphConfigPath)), + getTestAccounts: lazyFunction(() => () => getTestAccounts(provider, graphConfigPath)), + } +} diff --git a/gre/helpers/chains.ts b/gre/helpers/chains.ts new file mode 100644 index 000000000..c011662d0 --- /dev/null +++ b/gre/helpers/chains.ts @@ -0,0 +1,45 @@ +class MapWithGetKey extends Map { + getKey(value: V): K | undefined { + for (const [k, v] of this.entries()) { + if (v === value) { + return k + } + } + return + } +} + +const chainMap = new MapWithGetKey([ + [1, 42161], // Ethereum Mainnet - Arbitrum One + [4, 421611], // Ethereum Rinkeby - Arbitrum Rinkeby + [5, 421613], // Ethereum Goerli - Arbitrum Goerli + [1337, 412346], // Localhost - Arbitrum Localhost +]) + +export const l1Chains = Array.from(chainMap.keys()) +export const l2Chains = Array.from(chainMap.values()) +export const chains = [...l1Chains, ...l2Chains] + +export const isL1 = (chainId: number): boolean => l1Chains.includes(chainId) +export const isL2 = (chainId: number): boolean => l2Chains.includes(chainId) +export const isSupported = (chainId: number | undefined): boolean => + chainId !== undefined && chains.includes(chainId) + +export const l1ToL2 = (chainId: number): number | undefined => chainMap.get(chainId) +export const l2ToL1 = (chainId: number): number | undefined => chainMap.getKey(chainId) +export const counterpart = (chainId: number): number | undefined => { + if (!isSupported(chainId)) return + return isL1(chainId) ? l2ToL1(chainId) : l1ToL2(chainId) +} + +export default { + l1Chains, + l2Chains, + chains, + isL1, + isL2, + isSupported, + l1ToL2, + l2ToL1, + counterpart, +} diff --git a/gre/helpers/errors.ts b/gre/helpers/errors.ts new file mode 100644 index 000000000..dbe70d0c5 --- /dev/null +++ b/gre/helpers/errors.ts @@ -0,0 +1,7 @@ +import { HardhatPluginError } from 'hardhat/plugins' + +export class GREPluginError extends HardhatPluginError { + constructor(message: string) { + super('GraphRuntimeEnvironment', message) + } +} diff --git a/gre/type-extensions.d.ts b/gre/type-extensions.d.ts new file mode 100644 index 000000000..c72b3a3e0 --- /dev/null +++ b/gre/type-extensions.d.ts @@ -0,0 +1,75 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { AddressBook } from '../cli/address-book' +import { NetworkContracts } from '../cli/contracts' + +export interface GraphRuntimeEnvironmentOptions { + addressBook?: string + l1GraphConfig?: string + l2GraphConfig?: string +} + +export type AccountNames = + | 'arbitrator' + | 'governor' + | 'authority' + | 'availabilityOracle' + | 'pauseGuardian' + | 'allocationExchangeOwner' + +export type NamedAccounts = { + [name in AccountNames]: SignerWithAddress +} + +export interface GraphNetworkEnvironment { + contracts: NetworkContracts + graphConfig: any + addressBook: AddressBook + getNamedAccounts: () => Promise + getTestAccounts: () => Promise + getDeployer: () => Promise +} + +export interface GraphRuntimeEnvironment extends GraphNetworkEnvironment { + l1: GraphNetworkEnvironment + l2: GraphNetworkEnvironment +} + +declare module 'hardhat/types/runtime' { + export interface HardhatRuntimeEnvironment { + graph: (opts?: GraphRuntimeEnvironmentOptions) => GraphRuntimeEnvironment + } +} + +declare module 'hardhat/types/config' { + export interface HardhatConfig { + graph: GraphRuntimeEnvironmentOptions + } + + export interface HardhatUserConfig { + graph: GraphRuntimeEnvironmentOptions + } + + export interface HardhatNetworkConfig { + graphConfig?: string + } + + export interface HardhatNetworkUserConfig { + graphConfig?: string + } + + export interface HttpNetworkConfig { + graphConfig?: string + } + + export interface HttpNetworkUserConfig { + graphConfig?: string + } + + export interface ProjectPathsConfig { + graphConfigs?: string + } + + export interface ProjectPathsUserConfig { + graphConfigs?: string + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 3dc5a3936..e85ff718d 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -22,13 +22,13 @@ import '@openzeppelin/hardhat-upgrades' import '@typechain/hardhat' import 'solidity-coverage' import 'hardhat-storage-layout' +import './gre/gre' // Tasks const SKIP_LOAD = process.env.SKIP_LOAD === 'true' function loadTasks() { - require('./tasks/gre.ts') ;['contracts', 'misc', 'deployment', 'actions', 'verify', 'e2e'].forEach((folder) => { const tasksPath = path.join(__dirname, 'tasks', folder) fs.readdirSync(tasksPath) @@ -54,10 +54,11 @@ interface NetworkConfig { url?: string gas?: number | 'auto' gasPrice?: number | 'auto' + graphConfig?: string } const networkConfigs: NetworkConfig[] = [ - { network: 'mainnet', chainId: 1 }, + { network: 'mainnet', chainId: 1, graphConfig: 'config/graph.mainnet.yml' }, { network: 'rinkeby', chainId: 4 }, { network: 'goerli', chainId: 5 }, { network: 'kovan', chainId: 42 }, @@ -82,6 +83,9 @@ function setupNetworkProviders(hardhatConfig) { gasPrice: netConfig.gasPrice || 'auto', accounts: getAccountsKeys(), } + if (netConfig.graphConfig) { + hardhatConfig.networks[netConfig.network].graphConfig = netConfig.graphConfig + } } } @@ -95,6 +99,7 @@ const config: HardhatUserConfig = { sources: './contracts', tests: './test', artifacts: './build/contracts', + graphConfigs: './config', }, solidity: { compilers: [ @@ -140,6 +145,11 @@ const config: HardhatUserConfig = { process.env.FORK === 'true' ? getAccountsKeys() : { mnemonic: DEFAULT_TEST_MNEMONIC }, }, }, + graph: { + addressBook: process.env.ADDRESS_BOOK, + l1GraphConfig: process.env.GRAPH_CONFIG, + l2GraphConfig: process.env.L2_GRAPH_CONFIG, + }, etherscan: { apiKey: process.env.ETHERSCAN_API_KEY, }, diff --git a/tasks/gre.ts b/tasks/gre.ts deleted file mode 100644 index 41028e958..000000000 --- a/tasks/gre.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { HardhatRuntimeEnvironment } from 'hardhat/types' -import { extendEnvironment } from 'hardhat/config' -import { lazyFunction, lazyObject } from 'hardhat/plugins' - -import { getAddressBook } from '../cli/address-book' -import { loadContracts } from '../cli/contracts' -import { getItemValue, readConfig } from '../cli/config' -import { GREOptions, NamedAccounts } from './type-extensions' -import fs from 'fs' -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' - -// Graph Runtime Environment (GRE) extensions for the HRE -extendEnvironment((hre: HardhatRuntimeEnvironment) => { - hre.graph = (opts: GREOptions = {}) => { - const chainId = hre.network.config.chainId?.toString() ?? '1337' - const addressBookPath = opts.addressBook ?? process.env.ADDRESS_BOOK - const graphConfigPath = opts.graphConfig ?? process.env.GRAPH_CONFIG - - if (!fs.existsSync(addressBookPath)) { - throw new Error(`Address book not found: ${addressBookPath}`) - } - - if (!fs.existsSync(graphConfigPath)) { - throw new Error(`Graph config not found: ${graphConfigPath}`) - } - - const namedAccountList = [ - 'arbitrator', - 'governor', - 'authority', - 'availabilityOracle', - 'pauseGuardian', - 'allocationExchangeOwner', - ] - - const getTestAccounts = async (): Promise => { - // Get list of privileged accounts we don't want as test accounts - const namedAccounts = await getNamedAccounts() - const blacklist = namedAccountList.map((a) => { - const account = namedAccounts[a] as SignerWithAddress - return account.address - }) - blacklist.push((await getDeployer()).address) - - // Get signers and filter out blacklisted accounts - let signers: SignerWithAddress[] = await hre.ethers.getSigners() - signers = signers.filter((s) => { - return !blacklist.includes(s.address) - }) - - return signers - } - - const getNamedAccounts = async (): Promise => { - const namedAccounts = namedAccountList.reduce(async (accountsPromise, name) => { - const accounts = await accountsPromise - const address = getItemValue(readConfig(graphConfigPath, true), `general/${name}`) - accounts[name] = await hre.ethers.getSigner(address) - return accounts - }, Promise.resolve({} as NamedAccounts)) - - return namedAccounts - } - - const getDeployer = async () => { - const signer = hre.ethers.provider.getSigner(0) - return hre.ethers.getSigner(await signer.getAddress()) - } - - return { - addressBook: lazyObject(() => getAddressBook(addressBookPath, chainId)), - graphConfig: lazyObject(() => readConfig(graphConfigPath, true)), - contracts: lazyObject(() => - loadContracts(getAddressBook(addressBookPath, chainId), hre.ethers.provider), - ), - getNamedAccounts: lazyFunction(() => getNamedAccounts), - getTestAccounts: lazyFunction(() => getTestAccounts), - getDeployer: lazyFunction(() => getDeployer), - } - } -}) diff --git a/tasks/type-extensions.d.ts b/tasks/type-extensions.d.ts deleted file mode 100644 index 5dbaa09ab..000000000 --- a/tasks/type-extensions.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { AddressBook } from '../cli/address-book' -import { NetworkContracts } from '../cli/contracts' - -export interface GREOptions { - addressBook?: string - graphConfig?: string -} - -export interface NamedAccounts { - arbitrator: SignerWithAddress - governor: SignerWithAddress - authority: SignerWithAddress - availabilityOracle: SignerWithAddress - pauseGuardian: SignerWithAddress - allocationExchangeOwner: SignerWithAddress -} - -declare module 'hardhat/types/runtime' { - export interface HardhatRuntimeEnvironment { - graph: (opts?: GREOptions) => { - contracts: NetworkContracts - graphConfig: any - addressBook: AddressBook - getNamedAccounts: () => Promise - getTestAccounts: () => Promise - getDeployer: () => Promise - } - } -} diff --git a/tsconfig.json b/tsconfig.json index 58be04eb9..e38e8e832 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "scripts/**/*.ts", "test/**/*.ts", "tasks/**/*.ts", - "e2e/**/*.ts" + "e2e/**/*.ts", + "gre/**/*.ts" ] } From 39e03e87154b6660fdc283c71b71891c24622aad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Fri, 12 Aug 2022 15:47:49 +0200 Subject: [PATCH 2/9] feat: allow using graphConfig for hh network MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/config.ts | 40 +++++++++++++++++++++++++--------------- gre/gre.ts | 23 ++++++++++++++++++----- gre/helpers/chains.ts | 9 +++++---- gre/logger.ts | 6 ++++++ gre/type-extensions.d.ts | 9 +++++---- 5 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 gre/logger.ts diff --git a/gre/config.ts b/gre/config.ts index 982ba58f2..865732ea3 100644 --- a/gre/config.ts +++ b/gre/config.ts @@ -9,6 +9,9 @@ import { GraphRuntimeEnvironmentOptions } from './type-extensions' import { GREPluginError } from './helpers/errors' import GraphChains from './helpers/chains' +import debug from 'debug' +const log = debug('hardhat:graphprotocol:gre') + interface GREChainData { l1ChainId: number l2ChainId: number @@ -22,8 +25,8 @@ interface GREProviderData { } interface GREGraphConfigData { - l1GraphConfigPath: string - l2GraphConfigPath: string + l1GraphConfigPath: string | undefined + l2GraphConfigPath: string | undefined } export function getAddressBookPath( @@ -57,13 +60,17 @@ export function getChains(mainChainId: number | undefined): GREChainData { mainChainId = mainChainId! // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const secondaryChainId = GraphChains.counterpart(mainChainId!)! + const secondaryChainId = GraphChains.counterpart(mainChainId)! const isHHL1 = GraphChains.isL1(mainChainId) const isHHL2 = GraphChains.isL2(mainChainId) const l1ChainId = isHHL1 ? mainChainId : secondaryChainId const l2ChainId = isHHL2 ? mainChainId : secondaryChainId + log(`Hardhat chain id: ${mainChainId}`) + log(`L1 chain id: ${l1ChainId} - Is HHL1: ${isHHL1}`) + log(`L2 chain id: ${l2ChainId} - Is HHL2: ${isHHL2}`) + return { l1ChainId, l2ChainId, @@ -100,34 +107,37 @@ export function getGraphConfigPaths( opts: GraphRuntimeEnvironmentOptions, l1ChainId: number, l2ChainId: number, + isHHL1: boolean, ): GREGraphConfigData { const l1Network = getNetworkByChainId(hre.config.networks, l1ChainId) const l2Network = getNetworkByChainId(hre.config.networks, l2ChainId) // Priority is as follows: - // - hre.graph() init parameter + // - hre.graph() init parameter l1GraphConfigPath/l2GraphConfigPath + // - hre.graph() init parameter graphConfigPath (only for layer corresponding to hh network) // - hh network config // - hh graph config (layer specific: l1GraphConfig, l2GraphConfig) const l1GraphConfigPath = - opts.l1GraphConfig ?? l1Network?.graphConfig ?? hre.config.graph.l1GraphConfig + opts.l1GraphConfig ?? + (isHHL1 ? opts.graphConfig : undefined) ?? + l1Network?.graphConfig ?? + hre.config.graph.l1GraphConfig + const l2GraphConfigPath = - opts.l2GraphConfig ?? l2Network?.graphConfig ?? hre.config.graph.l2GraphConfig + opts.l2GraphConfig ?? + (!isHHL1 ? opts.graphConfig : undefined) ?? + l2Network?.graphConfig ?? + hre.config.graph.l2GraphConfig for (const configPath of [l1GraphConfigPath, l2GraphConfigPath]) { - if (configPath === undefined) { - throw new GREPluginError(`Must set a graphConfig path!`) - } - if (!fs.existsSync(configPath)) { + if (configPath !== undefined && !fs.existsSync(configPath)) { throw new GREPluginError(`Graph config file not found: ${configPath}`) } } return { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - l1GraphConfigPath: l1GraphConfigPath!, - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - l2GraphConfigPath: l2GraphConfigPath!, + l1GraphConfigPath: l1GraphConfigPath, + l2GraphConfigPath: l2GraphConfigPath, } } diff --git a/gre/gre.ts b/gre/gre.ts index 93ac319ae..9be6954f1 100644 --- a/gre/gre.ts +++ b/gre/gre.ts @@ -25,24 +25,27 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { opts, l1ChainId, l2ChainId, + isHHL1, ) - const l1Graph: GraphNetworkEnvironment = buildGraphNetworkEnvironment( + const l1Graph: GraphNetworkEnvironment | null = buildGraphNetworkEnvironment( l1ChainId, l1Provider, l1GraphConfigPath, addressBookPath, + isHHL1, ) - const l2Graph: GraphNetworkEnvironment = buildGraphNetworkEnvironment( + const l2Graph: GraphNetworkEnvironment | null = buildGraphNetworkEnvironment( l2ChainId, l2Provider, l2GraphConfigPath, addressBookPath, + isHHL1, ) const gre: GraphRuntimeEnvironment = { - ...(isHHL1 ? l1Graph : l2Graph), + ...(isHHL1 ? (l1Graph as GraphNetworkEnvironment) : (l2Graph as GraphNetworkEnvironment)), l1: l1Graph, l2: l2Graph, } @@ -54,9 +57,19 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { function buildGraphNetworkEnvironment( chainId: number, provider: providers.JsonRpcProvider, - graphConfigPath: string, + graphConfigPath: string | undefined, addressBookPath: string, -): GraphNetworkEnvironment { + isHHL1: boolean, +): GraphNetworkEnvironment | null { + if (graphConfigPath === undefined) { + console.warn( + `No graph config file provided for chain: ${chainId}. L${ + isHHL1 ? '2' : '1' + } graph object will not be initialized.`, + ) + return null + } + return { addressBook: lazyObject(() => getAddressBook(addressBookPath, chainId.toString())), graphConfig: lazyObject(() => readConfig(graphConfigPath, true)), diff --git a/gre/helpers/chains.ts b/gre/helpers/chains.ts index c011662d0..1d9700349 100644 --- a/gre/helpers/chains.ts +++ b/gre/helpers/chains.ts @@ -1,7 +1,8 @@ -class MapWithGetKey extends Map { - getKey(value: V): K | undefined { +class MapWithGetKey extends Map { + getKey(value: K): K | undefined { for (const [k, v] of this.entries()) { - if (v === value) { + console.log(k, v, value) + if (k === value) { return k } } @@ -9,7 +10,7 @@ class MapWithGetKey extends Map { } } -const chainMap = new MapWithGetKey([ +const chainMap = new MapWithGetKey([ [1, 42161], // Ethereum Mainnet - Arbitrum One [4, 421611], // Ethereum Rinkeby - Arbitrum Rinkeby [5, 421613], // Ethereum Goerli - Arbitrum Goerli diff --git a/gre/logger.ts b/gre/logger.ts new file mode 100644 index 000000000..90b95936f --- /dev/null +++ b/gre/logger.ts @@ -0,0 +1,6 @@ +import debug from 'debug' + +const LOG_BASE = 'hardhat:gre' + +export const logDebug = debug(`${LOG_BASE}:debug`) +export const logWarn = debug(`${LOG_BASE}:warn`) diff --git a/gre/type-extensions.d.ts b/gre/type-extensions.d.ts index c72b3a3e0..34000bb92 100644 --- a/gre/type-extensions.d.ts +++ b/gre/type-extensions.d.ts @@ -6,6 +6,7 @@ export interface GraphRuntimeEnvironmentOptions { addressBook?: string l1GraphConfig?: string l2GraphConfig?: string + graphConfig?: string } export type AccountNames = @@ -30,8 +31,8 @@ export interface GraphNetworkEnvironment { } export interface GraphRuntimeEnvironment extends GraphNetworkEnvironment { - l1: GraphNetworkEnvironment - l2: GraphNetworkEnvironment + l1: GraphNetworkEnvironment | null + l2: GraphNetworkEnvironment | null } declare module 'hardhat/types/runtime' { @@ -42,11 +43,11 @@ declare module 'hardhat/types/runtime' { declare module 'hardhat/types/config' { export interface HardhatConfig { - graph: GraphRuntimeEnvironmentOptions + graph: Omit } export interface HardhatUserConfig { - graph: GraphRuntimeEnvironmentOptions + graph: Omit } export interface HardhatNetworkConfig { From 1343eac3968e08c125795c879971b12a11c77d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Mon, 15 Aug 2022 14:52:06 +0200 Subject: [PATCH 3/9] chore: add debug based logger to GRE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/config.ts | 82 ++++++++++++++++++--------- gre/gre.ts | 12 ++-- gre/helpers/{errors.ts => error.ts} | 2 + gre/helpers/{chains.ts => network.ts} | 5 +- gre/logger.ts | 1 + 5 files changed, 69 insertions(+), 33 deletions(-) rename gre/helpers/{errors.ts => error.ts} (76%) rename gre/helpers/{chains.ts => network.ts} (90%) diff --git a/gre/config.ts b/gre/config.ts index 865732ea3..008b65568 100644 --- a/gre/config.ts +++ b/gre/config.ts @@ -6,25 +6,24 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types/runtime' import { HttpNetworkConfig } from 'hardhat/types/config' import { GraphRuntimeEnvironmentOptions } from './type-extensions' -import { GREPluginError } from './helpers/errors' -import GraphChains from './helpers/chains' +import { GREPluginError } from './helpers/error' +import GraphNetwork from './helpers/network' -import debug from 'debug' -const log = debug('hardhat:graphprotocol:gre') +import { logDebug } from './logger' -interface GREChainData { +interface GREChains { l1ChainId: number l2ChainId: number isHHL1: boolean isHHL2: boolean } -interface GREProviderData { +interface GREProviders { l1Provider: providers.JsonRpcProvider l2Provider: providers.JsonRpcProvider } -interface GREGraphConfigData { +interface GREGraphConfigs { l1GraphConfigPath: string | undefined l2GraphConfigPath: string | undefined } @@ -33,6 +32,10 @@ export function getAddressBookPath( hre: HardhatRuntimeEnvironment, opts: GraphRuntimeEnvironmentOptions, ): string { + logDebug('== Getting address book path') + logDebug(`1) opts.addressBookPath: ${opts.addressBook}`) + logDebug(`2) hre.config.graph.addressBook: ${hre.config.graph.addressBook}`) + const addressBookPath = opts.addressBook ?? hre.config.graph.addressBook if (addressBookPath === undefined) { @@ -43,33 +46,36 @@ export function getAddressBookPath( throw new GREPluginError(`Address book not found: ${addressBookPath}`) } + logDebug(`Address book path found: ${addressBookPath}`) return addressBookPath } -export function getChains(mainChainId: number | undefined): GREChainData { - if (!GraphChains.isSupported(mainChainId)) { - const supportedChains = GraphChains.chains.join(',') +export function getChains(mainChainId: number | undefined): GREChains { + logDebug('== Getting chain ids') + logDebug(`Hardhat chain id: ${mainChainId}`) + + if (!GraphNetwork.isSupported(mainChainId)) { + const supportedChains = GraphNetwork.chains.join(',') throw new GREPluginError( `Chain ${mainChainId} is not supported! Supported chainIds: ${supportedChains}.`, ) } // If mainChainId is supported there is a supported counterpart chainId so both chains are not undefined - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion mainChainId = mainChainId! // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const secondaryChainId = GraphChains.counterpart(mainChainId)! + const secondaryChainId = GraphNetwork.counterpart(mainChainId)! + logDebug(`Secondary chain id: ${secondaryChainId}`) - const isHHL1 = GraphChains.isL1(mainChainId) - const isHHL2 = GraphChains.isL2(mainChainId) + const isHHL1 = GraphNetwork.isL1(mainChainId) + const isHHL2 = GraphNetwork.isL2(mainChainId) const l1ChainId = isHHL1 ? mainChainId : secondaryChainId const l2ChainId = isHHL2 ? mainChainId : secondaryChainId - log(`Hardhat chain id: ${mainChainId}`) - log(`L1 chain id: ${l1ChainId} - Is HHL1: ${isHHL1}`) - log(`L2 chain id: ${l2ChainId} - Is HHL2: ${isHHL2}`) + logDebug(`L1 chain id: ${l1ChainId} - Is HHL1: ${isHHL1}`) + logDebug(`L2 chain id: ${l2ChainId} - Is HHL2: ${isHHL2}`) return { l1ChainId, @@ -83,19 +89,26 @@ export function getProviders( hre: HardhatRuntimeEnvironment, l1ChainId: number, l2ChainId: number, -): GREProviderData { - const l1Network = getNetworkByChainId(hre.config.networks, l1ChainId) as HttpNetworkConfig - const l2Network = getNetworkByChainId(hre.config.networks, l2ChainId) as HttpNetworkConfig +): GREProviders { + logDebug('== Getting providers') + + const l1Network = getNetworkConfig(hre.config.networks, l1ChainId) as HttpNetworkConfig + const l2Network = getNetworkConfig(hre.config.networks, l2ChainId) as HttpNetworkConfig for (const network of [l1Network, l2Network]) { - if (network === undefined || network.url === undefined) { - throw new GREPluginError(`Must set a provider url for chain ${l1ChainId}!`) + if (network === undefined) { + throw new GREPluginError(`L1 or L2 network not found in hardhat config!`) + } else if (network.url === undefined) { + throw new GREPluginError(`Must set a provider url for chain ${network.chainId}!`) } } const l1Provider = new providers.JsonRpcProvider(l1Network.url) const l2Provider = new providers.JsonRpcProvider(l2Network.url) + logDebug(`L1 provider url: ${l1Network.url}`) + logDebug(`L2 provider url: ${l2Network.url}`) + return { l1Provider, l2Provider, @@ -108,9 +121,11 @@ export function getGraphConfigPaths( l1ChainId: number, l2ChainId: number, isHHL1: boolean, -): GREGraphConfigData { - const l1Network = getNetworkByChainId(hre.config.networks, l1ChainId) - const l2Network = getNetworkByChainId(hre.config.networks, l2ChainId) +): GREGraphConfigs { + logDebug('== Getting graph config paths') + + const l1Network = getNetworkConfig(hre.config.networks, l1ChainId) + const l2Network = getNetworkConfig(hre.config.networks, l2ChainId) // Priority is as follows: // - hre.graph() init parameter l1GraphConfigPath/l2GraphConfigPath @@ -123,25 +138,40 @@ export function getGraphConfigPaths( l1Network?.graphConfig ?? hre.config.graph.l1GraphConfig + logDebug(`> L1 graph config`) + logDebug(`1) opts.l1GraphConfig: ${opts.l1GraphConfig}`) + logDebug(`2) opts.graphConfig: ${isHHL1 ? opts.graphConfig : undefined}`) + logDebug(`3) l1Network.graphConfig: ${l1Network?.graphConfig}`) + logDebug(`4) hre.config.graph.l1GraphConfig: ${hre.config.graph.l1GraphConfig}`) + const l2GraphConfigPath = opts.l2GraphConfig ?? (!isHHL1 ? opts.graphConfig : undefined) ?? l2Network?.graphConfig ?? hre.config.graph.l2GraphConfig + logDebug(`> L2 graph config`) + logDebug(`1) opts.l2GraphConfig: ${opts.l2GraphConfig}`) + logDebug(`2) opts.graphConfig: ${!isHHL1 ? opts.graphConfig : undefined}`) + logDebug(`3) l2Network.graphConfig: ${l2Network?.graphConfig}`) + logDebug(`4) hre.config.graph.l2GraphConfig: ${hre.config.graph.l2GraphConfig}`) + for (const configPath of [l1GraphConfigPath, l2GraphConfigPath]) { if (configPath !== undefined && !fs.existsSync(configPath)) { throw new GREPluginError(`Graph config file not found: ${configPath}`) } } + logDebug(`L1 graph config path: ${l1GraphConfigPath}`) + logDebug(`L2 graph config path: ${l2GraphConfigPath}`) + return { l1GraphConfigPath: l1GraphConfigPath, l2GraphConfigPath: l2GraphConfigPath, } } -function getNetworkByChainId(networks: NetworksConfig, chainId: number): NetworkConfig | undefined { +function getNetworkConfig(networks: NetworksConfig, chainId: number): NetworkConfig | undefined { return Object.keys(networks) .map((n) => networks[n]) .find((n) => n.chainId === chainId) diff --git a/gre/gre.ts b/gre/gre.ts index 9be6954f1..2475513fd 100644 --- a/gre/gre.ts +++ b/gre/gre.ts @@ -13,6 +13,7 @@ import { import { providers } from 'ethers' import { getChains, getProviders, getAddressBookPath, getGraphConfigPaths } from './config' import { getDeployer, getNamedAccounts, getTestAccounts } from './accounts' +import { logDebug, logWarn } from './logger' // Graph Runtime Environment (GRE) extensions for the HRE extendEnvironment((hre: HardhatRuntimeEnvironment) => { @@ -50,6 +51,9 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { l2: l2Graph, } + logDebug('GRE initialized successfully!') + logDebug(`Main network: L${isHHL1 ? '1' : '2'}`) + logDebug(`Secondary network: ${gre.l2 !== null ? (isHHL1 ? 'L2' : 'L1') : 'not initialized'}`) return gre } }) @@ -62,10 +66,10 @@ function buildGraphNetworkEnvironment( isHHL1: boolean, ): GraphNetworkEnvironment | null { if (graphConfigPath === undefined) { - console.warn( - `No graph config file provided for chain: ${chainId}. L${ - isHHL1 ? '2' : '1' - } graph object will not be initialized.`, + logWarn( + `No graph config file provided for chain: ${chainId}. ${ + isHHL1 ? 'L2' : 'L1' + } will not be initialized.`, ) return null } diff --git a/gre/helpers/errors.ts b/gre/helpers/error.ts similarity index 76% rename from gre/helpers/errors.ts rename to gre/helpers/error.ts index dbe70d0c5..f9aafa748 100644 --- a/gre/helpers/errors.ts +++ b/gre/helpers/error.ts @@ -1,7 +1,9 @@ import { HardhatPluginError } from 'hardhat/plugins' +import { logError } from '../logger' export class GREPluginError extends HardhatPluginError { constructor(message: string) { super('GraphRuntimeEnvironment', message) + logError(message) } } diff --git a/gre/helpers/chains.ts b/gre/helpers/network.ts similarity index 90% rename from gre/helpers/chains.ts rename to gre/helpers/network.ts index 1d9700349..85ee07313 100644 --- a/gre/helpers/chains.ts +++ b/gre/helpers/network.ts @@ -1,7 +1,6 @@ class MapWithGetKey extends Map { getKey(value: K): K | undefined { - for (const [k, v] of this.entries()) { - console.log(k, v, value) + for (const [k] of this.entries()) { if (k === value) { return k } @@ -30,7 +29,7 @@ export const l1ToL2 = (chainId: number): number | undefined => chainMap.get(chai export const l2ToL1 = (chainId: number): number | undefined => chainMap.getKey(chainId) export const counterpart = (chainId: number): number | undefined => { if (!isSupported(chainId)) return - return isL1(chainId) ? l2ToL1(chainId) : l1ToL2(chainId) + return isL1(chainId) ? l1ToL2(chainId) : l2ToL1(chainId) } export default { diff --git a/gre/logger.ts b/gre/logger.ts index 90b95936f..a768d6b04 100644 --- a/gre/logger.ts +++ b/gre/logger.ts @@ -4,3 +4,4 @@ const LOG_BASE = 'hardhat:gre' export const logDebug = debug(`${LOG_BASE}:debug`) export const logWarn = debug(`${LOG_BASE}:warn`) +export const logError = debug(`${LOG_BASE}:error`) From 632e1848f7317f390b0acb0fd313fa7e00542ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Tue, 16 Aug 2022 15:12:15 +0200 Subject: [PATCH 4/9] chore: add tests to GRE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- .mocharc.json | 5 + gre/config.ts | 78 ++++-- gre/gre.ts | 37 ++- gre/helpers/network.ts | 4 +- gre/test/config.test.ts | 224 ++++++++++++++++++ gre/test/files/addresses-hre.json | 0 gre/test/files/addresses-opts.json | 0 .../files/config/graph.arbitrum-goerli.yml | 0 gre/test/files/config/graph.arbitrum-hre.yml | 0 gre/test/files/config/graph.arbitrum-opts.yml | 0 gre/test/files/config/graph.goerli.yml | 0 gre/test/files/config/graph.hre.yml | 0 gre/test/files/config/graph.mainnet.yml | 0 gre/test/files/config/graph.opts.yml | 0 gre/test/files/config/graph.opts2.yml | 0 .../default-config/hardhat.config.js | 1 + .../graph-config-bad/hardhat.config.ts | 43 ++++ .../graph-config-full/hardhat.config.ts | 47 ++++ .../graph-config/hardhat.config.ts | 44 ++++ gre/test/gre.test.ts | 70 ++++++ gre/test/helpers.ts | 24 ++ gre/type-extensions.d.ts | 5 +- hardhat.config.ts | 1 - package.json | 1 + 24 files changed, 553 insertions(+), 31 deletions(-) create mode 100644 .mocharc.json create mode 100644 gre/test/config.test.ts create mode 100644 gre/test/files/addresses-hre.json create mode 100644 gre/test/files/addresses-opts.json create mode 100644 gre/test/files/config/graph.arbitrum-goerli.yml create mode 100644 gre/test/files/config/graph.arbitrum-hre.yml create mode 100644 gre/test/files/config/graph.arbitrum-opts.yml create mode 100644 gre/test/files/config/graph.goerli.yml create mode 100644 gre/test/files/config/graph.hre.yml create mode 100644 gre/test/files/config/graph.mainnet.yml create mode 100644 gre/test/files/config/graph.opts.yml create mode 100644 gre/test/files/config/graph.opts2.yml create mode 100644 gre/test/fixture-projects/default-config/hardhat.config.js create mode 100644 gre/test/fixture-projects/graph-config-bad/hardhat.config.ts create mode 100644 gre/test/fixture-projects/graph-config-full/hardhat.config.ts create mode 100644 gre/test/fixture-projects/graph-config/hardhat.config.ts create mode 100644 gre/test/gre.test.ts create mode 100644 gre/test/helpers.ts diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 000000000..79ba35ddf --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,5 @@ +{ + "require": "ts-node/register/files", + "ignore": ["test/fixture-projects/**/*"], + "timeout": 6000 +} diff --git a/gre/config.ts b/gre/config.ts index 008b65568..49e7651ab 100644 --- a/gre/config.ts +++ b/gre/config.ts @@ -1,4 +1,5 @@ import fs from 'fs' +import path from 'path' import { providers } from 'ethers' import { NetworkConfig, NetworksConfig } from 'hardhat/types/config' @@ -19,8 +20,8 @@ interface GREChains { } interface GREProviders { - l1Provider: providers.JsonRpcProvider - l2Provider: providers.JsonRpcProvider + l1Provider: providers.JsonRpcProvider | undefined + l2Provider: providers.JsonRpcProvider | undefined } interface GREGraphConfigs { @@ -33,15 +34,18 @@ export function getAddressBookPath( opts: GraphRuntimeEnvironmentOptions, ): string { logDebug('== Getting address book path') + logDebug(`Graph base dir: ${hre.config.paths.graph}`) logDebug(`1) opts.addressBookPath: ${opts.addressBook}`) - logDebug(`2) hre.config.graph.addressBook: ${hre.config.graph.addressBook}`) + logDebug(`2) hre.config.graph.addressBook: ${hre.config.graph?.addressBook}`) - const addressBookPath = opts.addressBook ?? hre.config.graph.addressBook + let addressBookPath = opts.addressBook ?? hre.config.graph?.addressBook if (addressBookPath === undefined) { - throw new GREPluginError(`Must set a an addressBook path!`) + throw new GREPluginError('Must set a an addressBook path!') } + addressBookPath = normalizePath(addressBookPath, hre.config.paths.graph) + if (!fs.existsSync(addressBookPath)) { throw new GREPluginError(`Address book not found: ${addressBookPath}`) } @@ -55,10 +59,7 @@ export function getChains(mainChainId: number | undefined): GREChains { logDebug(`Hardhat chain id: ${mainChainId}`) if (!GraphNetwork.isSupported(mainChainId)) { - const supportedChains = GraphNetwork.chains.join(',') - throw new GREPluginError( - `Chain ${mainChainId} is not supported! Supported chainIds: ${supportedChains}.`, - ) + throw new GREPluginError(`Chain ${mainChainId} is not supported!`) } // If mainChainId is supported there is a supported counterpart chainId so both chains are not undefined @@ -89,25 +90,34 @@ export function getProviders( hre: HardhatRuntimeEnvironment, l1ChainId: number, l2ChainId: number, + isHHL1: boolean, ): GREProviders { logDebug('== Getting providers') - const l1Network = getNetworkConfig(hre.config.networks, l1ChainId) as HttpNetworkConfig - const l2Network = getNetworkConfig(hre.config.networks, l2ChainId) as HttpNetworkConfig + const getProvider = ( + networks: NetworksConfig, + chainId: number, + isMainProvider: boolean, + chainLabel: string, + ): providers.JsonRpcProvider | undefined => { + const network = getNetworkConfig(networks, chainId) as HttpNetworkConfig + + // Ensure at least main provider is configured + if (isMainProvider && network === undefined) { + throw new GREPluginError(`Must set a provider url for chain: ${chainId}!`) + } + + logDebug(`Provider url for ${chainLabel}: ${network?.url}`) - for (const network of [l1Network, l2Network]) { if (network === undefined) { - throw new GREPluginError(`L1 or L2 network not found in hardhat config!`) - } else if (network.url === undefined) { - throw new GREPluginError(`Must set a provider url for chain ${network.chainId}!`) + return undefined } - } - const l1Provider = new providers.JsonRpcProvider(l1Network.url) - const l2Provider = new providers.JsonRpcProvider(l2Network.url) + return new providers.JsonRpcProvider(network.url) + } - logDebug(`L1 provider url: ${l1Network.url}`) - logDebug(`L2 provider url: ${l2Network.url}`) + const l1Provider = getProvider(hre.config.networks, l1ChainId, isHHL1, 'L1') + const l2Provider = getProvider(hre.config.networks, l2ChainId, !isHHL1, 'L2') return { l1Provider, @@ -123,6 +133,7 @@ export function getGraphConfigPaths( isHHL1: boolean, ): GREGraphConfigs { logDebug('== Getting graph config paths') + logDebug(`Graph base dir: ${hre.config.paths.graph}`) const l1Network = getNetworkConfig(hre.config.networks, l1ChainId) const l2Network = getNetworkConfig(hre.config.networks, l2ChainId) @@ -132,7 +143,7 @@ export function getGraphConfigPaths( // - hre.graph() init parameter graphConfigPath (only for layer corresponding to hh network) // - hh network config // - hh graph config (layer specific: l1GraphConfig, l2GraphConfig) - const l1GraphConfigPath = + let l1GraphConfigPath = opts.l1GraphConfig ?? (isHHL1 ? opts.graphConfig : undefined) ?? l1Network?.graphConfig ?? @@ -144,7 +155,15 @@ export function getGraphConfigPaths( logDebug(`3) l1Network.graphConfig: ${l1Network?.graphConfig}`) logDebug(`4) hre.config.graph.l1GraphConfig: ${hre.config.graph.l1GraphConfig}`) - const l2GraphConfigPath = + if (isHHL1 && l1GraphConfigPath === undefined) { + throw new GREPluginError('Must specify a graph config file for L1!') + } + + if (l1GraphConfigPath !== undefined) { + l1GraphConfigPath = normalizePath(l1GraphConfigPath, hre.config.paths.graph) + } + + let l2GraphConfigPath = opts.l2GraphConfig ?? (!isHHL1 ? opts.graphConfig : undefined) ?? l2Network?.graphConfig ?? @@ -156,6 +175,14 @@ export function getGraphConfigPaths( logDebug(`3) l2Network.graphConfig: ${l2Network?.graphConfig}`) logDebug(`4) hre.config.graph.l2GraphConfig: ${hre.config.graph.l2GraphConfig}`) + if (!isHHL1 && l2GraphConfigPath === undefined) { + throw new GREPluginError('Must specify a graph config file for L2!') + } + + if (l2GraphConfigPath !== undefined) { + l2GraphConfigPath = normalizePath(l2GraphConfigPath, hre.config.paths.graph) + } + for (const configPath of [l1GraphConfigPath, l2GraphConfigPath]) { if (configPath !== undefined && !fs.existsSync(configPath)) { throw new GREPluginError(`Graph config file not found: ${configPath}`) @@ -176,3 +203,10 @@ function getNetworkConfig(networks: NetworksConfig, chainId: number): NetworkCon .map((n) => networks[n]) .find((n) => n.chainId === chainId) } + +function normalizePath(_path: string, graphPath: string) { + if (!path.isAbsolute(_path)) { + _path = path.join(graphPath, _path) + } + return _path +} diff --git a/gre/gre.ts b/gre/gre.ts index 2475513fd..99d081f7c 100644 --- a/gre/gre.ts +++ b/gre/gre.ts @@ -1,5 +1,5 @@ -import { HardhatRuntimeEnvironment } from 'hardhat/types' -import { extendEnvironment } from 'hardhat/config' +import { HardhatConfig, HardhatRuntimeEnvironment, HardhatUserConfig } from 'hardhat/types' +import { extendConfig, extendEnvironment } from 'hardhat/config' import { lazyFunction, lazyObject } from 'hardhat/plugins' import { getAddressBook } from '../cli/address-book' @@ -14,12 +14,33 @@ import { providers } from 'ethers' import { getChains, getProviders, getAddressBookPath, getGraphConfigPaths } from './config' import { getDeployer, getNamedAccounts, getTestAccounts } from './accounts' import { logDebug, logWarn } from './logger' +import path from 'path' // Graph Runtime Environment (GRE) extensions for the HRE + +extendConfig((config: HardhatConfig, userConfig: Readonly) => { + // Source for the path convention: + // https://github.com/NomicFoundation/hardhat-ts-plugin-boilerplate/blob/d450d89f4b6ed5d26a8ae32b136b9c55d2aadab5/src/index.ts + const userPath = userConfig.paths?.graph + + let newPath: string + if (userPath === undefined) { + newPath = config.paths.root + } else { + if (path.isAbsolute(userPath)) { + newPath = userPath + } else { + newPath = path.normalize(path.join(config.paths.root, userPath)) + } + } + + config.paths.graph = newPath +}) + extendEnvironment((hre: HardhatRuntimeEnvironment) => { hre.graph = (opts: GraphRuntimeEnvironmentOptions = {}) => { const { l1ChainId, l2ChainId, isHHL1 } = getChains(hre.network.config.chainId) - const { l1Provider, l2Provider } = getProviders(hre, l1ChainId, l2ChainId) + const { l1Provider, l2Provider } = getProviders(hre, l1ChainId, l2ChainId, isHHL1) const addressBookPath = getAddressBookPath(hre, opts) const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( hre, @@ -60,7 +81,7 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { function buildGraphNetworkEnvironment( chainId: number, - provider: providers.JsonRpcProvider, + provider: providers.JsonRpcProvider | undefined, graphConfigPath: string | undefined, addressBookPath: string, isHHL1: boolean, @@ -74,7 +95,15 @@ function buildGraphNetworkEnvironment( return null } + if (provider === undefined) { + logWarn( + `No provider URL found for: ${chainId}. ${isHHL1 ? 'L2' : 'L1'} will not be initialized.`, + ) + return null + } + return { + chainId: chainId, addressBook: lazyObject(() => getAddressBook(addressBookPath, chainId.toString())), graphConfig: lazyObject(() => readConfig(graphConfigPath, true)), contracts: lazyObject(() => diff --git a/gre/helpers/network.ts b/gre/helpers/network.ts index 85ee07313..5fc2829ff 100644 --- a/gre/helpers/network.ts +++ b/gre/helpers/network.ts @@ -1,7 +1,7 @@ class MapWithGetKey extends Map { getKey(value: K): K | undefined { - for (const [k] of this.entries()) { - if (k === value) { + for (const [k, v] of this.entries()) { + if (v === value) { return k } } diff --git a/gre/test/config.test.ts b/gre/test/config.test.ts new file mode 100644 index 000000000..d206220e6 --- /dev/null +++ b/gre/test/config.test.ts @@ -0,0 +1,224 @@ +import { expect } from 'chai' +import { useEnvironment } from './helpers' + +import { getAddressBookPath, getChains, getGraphConfigPaths, getProviders } from '../config' +import path from 'path' + +describe('GRE init functions', function () { + describe('getAddressBookPath with graph-config project', function () { + useEnvironment('graph-config') + + it('should use opts parameter if available', function () { + const addressBook = getAddressBookPath(this.hre, { + addressBook: 'addresses-opts.json', + }) + expect(path.basename(addressBook)).to.equal('addresses-opts.json') + }) + + it('should use HH graph config if opts parameter not available ', function () { + const addressBook = getAddressBookPath(this.hre, {}) + expect(path.basename(addressBook)).to.equal('addresses-hre.json') + }) + }) + + describe('getAddressBookPath with default-config project', function () { + useEnvironment('default-config') + + it('should throw if no address book is specified', function () { + expect(() => getAddressBookPath(this.hre, {})).to.throw('Must set a an addressBook path!') + }) + }) + + describe('getAddressBookPath with graph-config-bad project', function () { + useEnvironment('graph-config-bad') + + it("should throw if address book doesn't exist", function () { + expect(() => getAddressBookPath(this.hre, {})).to.throw(/Address book not found: /) + }) + }) + + describe('getChains', function () { + it('should return L1 and L2 chain ids for a supported L1 chain', function () { + const { l1ChainId, l2ChainId, isHHL1, isHHL2 } = getChains(5) // Goerli + + expect(l1ChainId).to.equal(5) + expect(l2ChainId).to.equal(421613) + expect(isHHL1).to.equal(true) + expect(isHHL2).to.equal(false) + }) + it('should return L1 and L2 chain ids for a supported L2 chain', function () { + const { l1ChainId, l2ChainId, isHHL1, isHHL2 } = getChains(42161) // Arbitrum One + + expect(l1ChainId).to.equal(1) + expect(l2ChainId).to.equal(42161) + expect(isHHL1).to.equal(false) + expect(isHHL2).to.equal(true) + }) + it('should throw if provided chain is not supported', function () { + const badChainId = 999 + expect(() => getChains(badChainId)).to.throw(`Chain ${badChainId} is not supported!`) + }) + }) + + describe('getProviders with graph-config project', function () { + useEnvironment('graph-config') + + it('should return L1 and L2 providers for supported networks (HH L1)', function () { + const { l1Provider, l2Provider } = getProviders(this.hre, 5, 421613, true) + expect(l1Provider).to.be.an('object') + expect(l2Provider).to.be.an('object') + }) + + it('should return L1 and L2 providers for supported networks (HH L2)', function () { + const { l1Provider, l2Provider } = getProviders(this.hre, 5, 421613, false) + expect(l1Provider).to.be.an('object') + expect(l2Provider).to.be.an('object') + }) + + it('should return only L1 provider if L2 is not supported (HH L1)', function () { + const { l1Provider, l2Provider } = getProviders(this.hre, 5, 123456, true) + expect(l1Provider).to.be.an('object') + expect(l2Provider).to.be.undefined + }) + + it('should return only L2 provider if L1 is not supported (HH L2)', function () { + const { l1Provider, l2Provider } = getProviders(this.hre, 123456, 421613, false) + expect(l1Provider).to.be.undefined + expect(l2Provider).to.be.an('object') + }) + }) + + describe('getProviders with graph-config-bad project', function () { + useEnvironment('graph-config-bad') + + it('should throw if main network is not defined in hardhat config (HH L1)', function () { + expect(() => getProviders(this.hre, 4, 421611, true)).to.throw( + /Must set a provider url for chain: /, + ) + }) + + it('should throw if main network is not defined in hardhat config (HH L2)', function () { + expect(() => getProviders(this.hre, 5, 421613, false)).to.throw( + /Must set a provider url for chain: /, + ) + }) + }) + + describe('getGraphConfigPaths with graph-config-full project', function () { + useEnvironment('graph-config-full') + + it('should use opts parameters if available', function () { + const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( + this.hre, + { l1GraphConfig: 'config/graph.opts.yml', l2GraphConfig: 'config/graph.arbitrum-opts.yml' }, + 5, + 421613, + true, + ) + expect(path.basename(l1GraphConfigPath)).to.equal('graph.opts.yml') + expect(path.basename(l2GraphConfigPath)).to.equal('graph.arbitrum-opts.yml') + }) + + it('should use opts graphConfig parameter only for main network if available (HH L1)', function () { + const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( + this.hre, + { graphConfig: 'config/graph.opts.yml' }, + 4, + 421611, + true, + ) + expect(path.basename(l1GraphConfigPath)).to.equal('graph.opts.yml') + expect(path.basename(l2GraphConfigPath)).to.equal('graph.arbitrum-hre.yml') + }) + + it('should use opts graphConfig parameter only for main network if available (HH L2)', function () { + const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( + this.hre, + { graphConfig: 'config/graph.arbitrum-opts.yml' }, + 4, + 421611, + false, + ) + expect(path.basename(l1GraphConfigPath)).to.equal('graph.hre.yml') + expect(path.basename(l2GraphConfigPath)).to.equal('graph.arbitrum-opts.yml') + }) + + it('should ignore graphConfig parameter if both config paths are provided (HH L1)', function () { + const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( + this.hre, + { + graphConfig: 'config/graph.opts2.yml', + l1GraphConfig: 'config/graph.opts.yml', + l2GraphConfig: 'config/graph.arbitrum-opts.yml', + }, + 5, + 421613, + true, + ) + expect(path.basename(l1GraphConfigPath)).to.equal('graph.opts.yml') + expect(path.basename(l2GraphConfigPath)).to.equal('graph.arbitrum-opts.yml') + }) + + it('should ignore graphConfig parameter if both config paths are provided (HH L2)', function () { + const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( + this.hre, + { + graphConfig: 'config/graph.opts2.yml', + l1GraphConfig: 'config/graph.opts.yml', + l2GraphConfig: 'config/graph.arbitrum-opts.yml', + }, + 5, + 421613, + false, + ) + expect(path.basename(l1GraphConfigPath)).to.equal('graph.opts.yml') + expect(path.basename(l2GraphConfigPath)).to.equal('graph.arbitrum-opts.yml') + }) + + it('should use network specific config if no opts given', function () { + const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( + this.hre, + {}, + 1, + 42161, + false, + ) + expect(path.basename(l1GraphConfigPath)).to.equal('graph.mainnet.yml') + expect(path.basename(l2GraphConfigPath)).to.equal('graph.arbitrum-goerli.yml') + }) + + it('should use graph generic config if nothing else given', function () { + const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths( + this.hre, + {}, + 4, + 421611, + false, + ) + expect(path.basename(l1GraphConfigPath)).to.equal('graph.hre.yml') + expect(path.basename(l2GraphConfigPath)).to.equal('graph.arbitrum-hre.yml') + }) + }) + + describe('getGraphConfigPaths with graph-config-bad project', function () { + useEnvironment('graph-config-bad') + + it('should throw if no config file for main network (HH L1)', function () { + expect(() => getGraphConfigPaths(this.hre, {}, 5, 421611, true)).to.throw( + 'Must specify a graph config file for L1!', + ) + }) + + it('should throw if no config file for main network (HH L2)', function () { + expect(() => getGraphConfigPaths(this.hre, {}, 5, 421611, false)).to.throw( + 'Must specify a graph config file for L2!', + ) + }) + + it('should throw if config file does not exist', function () { + expect(() => getGraphConfigPaths(this.hre, {}, 1, 421611, true)).to.throw( + /Graph config file not found: /, + ) + }) + }) +}) diff --git a/gre/test/files/addresses-hre.json b/gre/test/files/addresses-hre.json new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/addresses-opts.json b/gre/test/files/addresses-opts.json new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/config/graph.arbitrum-goerli.yml b/gre/test/files/config/graph.arbitrum-goerli.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/config/graph.arbitrum-hre.yml b/gre/test/files/config/graph.arbitrum-hre.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/config/graph.arbitrum-opts.yml b/gre/test/files/config/graph.arbitrum-opts.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/config/graph.goerli.yml b/gre/test/files/config/graph.goerli.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/config/graph.hre.yml b/gre/test/files/config/graph.hre.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/config/graph.mainnet.yml b/gre/test/files/config/graph.mainnet.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/config/graph.opts.yml b/gre/test/files/config/graph.opts.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/files/config/graph.opts2.yml b/gre/test/files/config/graph.opts2.yml new file mode 100644 index 000000000..e69de29bb diff --git a/gre/test/fixture-projects/default-config/hardhat.config.js b/gre/test/fixture-projects/default-config/hardhat.config.js new file mode 100644 index 000000000..4ba52ba2c --- /dev/null +++ b/gre/test/fixture-projects/default-config/hardhat.config.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/gre/test/fixture-projects/graph-config-bad/hardhat.config.ts b/gre/test/fixture-projects/graph-config-bad/hardhat.config.ts new file mode 100644 index 000000000..405d5d86c --- /dev/null +++ b/gre/test/fixture-projects/graph-config-bad/hardhat.config.ts @@ -0,0 +1,43 @@ +import '../../../gre' + +module.exports = { + paths: { + graph: '../../files', + }, + solidity: '0.8.9', + defaultNetwork: 'hardhat', + networks: { + hardhat: { + chainId: 1337, + }, + mainnet: { + chainId: 1, + graphConfig: 'config/graph.mainnet-does-not-exist.yml', + url: `https://mainnet.infura.io/v3/123456`, + }, + 'arbitrum-one': { + chainId: 42161, + url: 'https://arb1.arbitrum.io/rpc', + graphConfig: 'config/graph.arbitrum-does-not-exist.yml', + }, + goerli: { + chainId: 5, + url: `https://goerli.infura.io/v3/123456`, + }, + // 'arbitrum-goerli': { + // chainId: 421613, + // url: 'https://goerli-rollup.arbitrum.io/rpc', + // }, + // rinkeby: { + // chainId: 4, + // url: `https://goerli.infura.io/v3/123456`, + // }, + 'arbitrum-rinkeby': { + chainId: 421611, + url: `https://goerli.infura.io/v3/123456`, + }, + }, + graph: { + addressBook: 'addresses-does-not-exist.json', + }, +} diff --git a/gre/test/fixture-projects/graph-config-full/hardhat.config.ts b/gre/test/fixture-projects/graph-config-full/hardhat.config.ts new file mode 100644 index 000000000..c0d52dda1 --- /dev/null +++ b/gre/test/fixture-projects/graph-config-full/hardhat.config.ts @@ -0,0 +1,47 @@ +import '../../../gre' + +module.exports = { + paths: { + graph: '../../files', + }, + solidity: '0.8.9', + defaultNetwork: 'hardhat', + networks: { + hardhat: { + chainId: 1337, + }, + mainnet: { + chainId: 1, + graphConfig: 'config/graph.mainnet.yml', + url: `https://mainnet.infura.io/v3/123456`, + }, + 'arbitrum-one': { + chainId: 42161, + url: 'https://arb1.arbitrum.io/rpc', + graphConfig: 'config/graph.arbitrum-goerli.yml', + }, + goerli: { + chainId: 5, + url: `https://goerli.infura.io/v3/123456`, + graphConfig: 'config/graph.goerli.yml', + }, + 'arbitrum-goerli': { + chainId: 421613, + url: 'https://goerli-rollup.arbitrum.io/rpc', + graphConfig: 'config/graph.arbitrum-goerli.yml', + }, + rinkeby: { + chainId: 4, + url: `https://goerli.infura.io/v3/123456`, + }, + 'arbitrum-rinkeby': { + chainId: 421611, + url: `https://goerli.infura.io/v3/123456`, + }, + }, + graph: { + addressBook: 'addresses-hre.json', + l1GraphConfig: 'config/graph.hre.yml', + l2GraphConfig: 'config/graph.arbitrum-hre.yml', + }, +} diff --git a/gre/test/fixture-projects/graph-config/hardhat.config.ts b/gre/test/fixture-projects/graph-config/hardhat.config.ts new file mode 100644 index 000000000..5a4e5cdfc --- /dev/null +++ b/gre/test/fixture-projects/graph-config/hardhat.config.ts @@ -0,0 +1,44 @@ +import '../../../gre' + +module.exports = { + paths: { + graph: '../../files', + }, + solidity: '0.8.9', + defaultNetwork: 'hardhat', + networks: { + hardhat: { + chainId: 1337, + }, + mainnet: { + chainId: 1, + graphConfig: 'config/graph.mainnet.yml', + url: `https://mainnet.infura.io/v3/123456`, + }, + 'arbitrum-one': { + chainId: 42161, + url: 'https://arb1.arbitrum.io/rpc', + }, + goerli: { + chainId: 5, + url: `https://goerli.infura.io/v3/123456`, + }, + 'arbitrum-goerli': { + chainId: 421613, + url: 'https://goerli-rollup.arbitrum.io/rpc', + }, + localhost: { + chainId: 1337, + url: 'http://localhost:8545', + }, + 'arbitrum-rinkeby': { + chainId: 421611, + url: 'http://localhost:8545', + }, + }, + graph: { + addressBook: 'addresses-hre.json', + l1GraphConfig: 'config/graph.goerli.yml', + l2GraphConfig: 'config/graph.arbitrum-goerli.yml', + }, +} diff --git a/gre/test/gre.test.ts b/gre/test/gre.test.ts new file mode 100644 index 000000000..486e01d79 --- /dev/null +++ b/gre/test/gre.test.ts @@ -0,0 +1,70 @@ +import { expect } from 'chai' +import { useEnvironment } from './helpers' + +describe('GRE usage', function () { + describe('graph-config project setting --network to an L1', function () { + useEnvironment('graph-config', 'mainnet') + + it('should return L1 and L2 configured objects', function () { + const g = this.hre.graph() + + expect(g).to.be.an('object') + expect(g.l1).to.be.an('object') + expect(g.l2).to.be.an('object') + expect(g.l1.chainId).to.equal(1) + expect(g.l2.chainId).to.equal(42161) + expect(g.chainId).to.equal(1) + }) + }) + + describe('graph-config project setting --network to an L2', function () { + useEnvironment('graph-config', 'arbitrum-goerli') + + it('should return L1 and L2 configured objects ', function () { + const g = this.hre.graph() + + expect(g).to.be.an('object') + expect(g.l1).to.be.an('object') + expect(g.l2).to.be.an('object') + expect(g.l1.chainId).to.equal(5) + expect(g.l2.chainId).to.equal(421613) + expect(g.chainId).to.equal(421613) + }) + }) + + describe('graph-config project setting --network to an L1 with no configured counterpart', function () { + useEnvironment('graph-config', 'localhost') + + it('should return L1 configured object and L2 unconfigured', function () { + const g = this.hre.graph() + + expect(g).to.be.an('object') + expect(g.l1).to.be.an('object') + expect(g.l2).to.be.null + expect(g.l1.chainId).to.equal(1337) + expect(g.chainId).to.equal(1337) + }) + }) + + describe('graph-config project setting --network to an L2 with no configured counterpart', function () { + useEnvironment('graph-config', 'arbitrum-rinkeby') + + it('should return L2 configured object and L1 unconfigured', function () { + const g = this.hre.graph() + + expect(g).to.be.an('object') + expect(g.l1).to.be.null + expect(g.l2).to.be.an('object') + expect(g.l2.chainId).to.equal(421611) + expect(g.chainId).to.equal(421611) + }) + }) + + describe('default-config project', function () { + useEnvironment('default-config', 'mainnet') + + it('should throw', function () { + expect(() => this.hre.graph()).to.throw() + }) + }) +}) diff --git a/gre/test/helpers.ts b/gre/test/helpers.ts new file mode 100644 index 000000000..f0875a002 --- /dev/null +++ b/gre/test/helpers.ts @@ -0,0 +1,24 @@ +import { resetHardhatContext } from 'hardhat/plugins-testing' +import { HardhatRuntimeEnvironment } from 'hardhat/types' +import path from 'path' + +declare module 'mocha' { + interface Context { + hre: HardhatRuntimeEnvironment + } +} + +export function useEnvironment(fixtureProjectName: string, network?: string): void { + beforeEach('Loading hardhat environment', function () { + process.chdir(path.join(__dirname, 'fixture-projects', fixtureProjectName)) + + if (network !== undefined) { + process.env.HARDHAT_NETWORK = network + } + this.hre = require('hardhat') + }) + + afterEach('Resetting hardhat', function () { + resetHardhatContext() + }) +} diff --git a/gre/type-extensions.d.ts b/gre/type-extensions.d.ts index 34000bb92..9a1832a78 100644 --- a/gre/type-extensions.d.ts +++ b/gre/type-extensions.d.ts @@ -22,6 +22,7 @@ export type NamedAccounts = { } export interface GraphNetworkEnvironment { + chainId: number contracts: NetworkContracts graphConfig: any addressBook: AddressBook @@ -67,10 +68,10 @@ declare module 'hardhat/types/config' { } export interface ProjectPathsConfig { - graphConfigs?: string + graph?: string } export interface ProjectPathsUserConfig { - graphConfigs?: string + graph?: string } } diff --git a/hardhat.config.ts b/hardhat.config.ts index e85ff718d..782f4958e 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -99,7 +99,6 @@ const config: HardhatUserConfig = { sources: './contracts', tests: './test', artifacts: './build/contracts', - graphConfigs: './config', }, solidity: { compilers: [ diff --git a/package.json b/package.json index 852fd78f7..2ca6cd1c3 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "test:e2e": "scripts/e2e", "test:gas": "RUN_EVM=true REPORT_GAS=true scripts/test", "test:coverage": "scripts/coverage", + "test:gre": "cd gre && mocha --exit --recursive 'test/**/*.test.ts' && cd ..", "lint": "yarn lint:ts && yarn lint:sol", "lint:fix": "yarn lint:ts:fix && yarn lint:sol:fix", "lint:ts": "eslint '**/*.{js,ts}'", From 3a9d38d99417a1d27fab6c4964e973aaa832576e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Tue, 16 Aug 2022 17:38:11 +0200 Subject: [PATCH 5/9] fix: use ethers provider for GRE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/config.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/gre/config.ts b/gre/config.ts index 49e7651ab..5a2cdfd00 100644 --- a/gre/config.ts +++ b/gre/config.ts @@ -10,6 +10,9 @@ import { GraphRuntimeEnvironmentOptions } from './type-extensions' import { GREPluginError } from './helpers/error' import GraphNetwork from './helpers/network' +import { createProvider } from '../node_modules/hardhat/internal/core/providers/construction' +import { EthersProviderWrapper } from '../node_modules/@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' + import { logDebug } from './logger' interface GREChains { @@ -20,8 +23,8 @@ interface GREChains { } interface GREProviders { - l1Provider: providers.JsonRpcProvider | undefined - l2Provider: providers.JsonRpcProvider | undefined + l1Provider: EthersProviderWrapper | undefined + l2Provider: EthersProviderWrapper | undefined } interface GREGraphConfigs { @@ -99,7 +102,7 @@ export function getProviders( chainId: number, isMainProvider: boolean, chainLabel: string, - ): providers.JsonRpcProvider | undefined => { + ): EthersProviderWrapper | undefined => { const network = getNetworkConfig(networks, chainId) as HttpNetworkConfig // Ensure at least main provider is configured @@ -113,7 +116,12 @@ export function getProviders( return undefined } - return new providers.JsonRpcProvider(network.url) + // Build provider as EthersProviderWrapper instead of JsonRpcProvider + // This let's us use hardhat's account management methods + const networkName = getNetworkName(networks, chainId) + const ethereumProvider = createProvider(networkName, network) + const ethersProviderWrapper = new EthersProviderWrapper(ethereumProvider) + return ethersProviderWrapper } const l1Provider = getProvider(hre.config.networks, l1ChainId, isHHL1, 'L1') @@ -204,6 +212,10 @@ function getNetworkConfig(networks: NetworksConfig, chainId: number): NetworkCon .find((n) => n.chainId === chainId) } +function getNetworkName(networks: NetworksConfig, chainId: number): string | undefined { + return Object.keys(networks).find((n) => networks[n].chainId === chainId) +} + function normalizePath(_path: string, graphPath: string) { if (!path.isAbsolute(_path)) { _path = path.join(graphPath, _path) From a440a1dffc7bec7ef58c4cdccb20c4372c1c723d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Tue, 16 Aug 2022 17:46:14 +0200 Subject: [PATCH 6/9] chore: cleanup provider types in GRE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/accounts.ts | 8 ++++---- gre/config.ts | 9 ++++----- gre/gre.ts | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/gre/accounts.ts b/gre/accounts.ts index c195d5703..339606c46 100644 --- a/gre/accounts.ts +++ b/gre/accounts.ts @@ -1,5 +1,5 @@ +import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { providers } from 'ethers' import { getItemValue, readConfig } from '../cli/config' import { AccountNames, NamedAccounts } from './type-extensions' @@ -13,7 +13,7 @@ const namedAccountList: AccountNames[] = [ ] export async function getNamedAccounts( - provider: providers.JsonRpcProvider, + provider: EthersProviderWrapper, graphConfigPath: string, ): Promise { const namedAccounts = namedAccountList.reduce(async (accountsPromise, name) => { @@ -26,13 +26,13 @@ export async function getNamedAccounts( return namedAccounts } -export async function getDeployer(provider: providers.JsonRpcProvider): Promise { +export async function getDeployer(provider: EthersProviderWrapper): Promise { const signer = provider.getSigner(0) return SignerWithAddress.create(signer) } export async function getTestAccounts( - provider: providers.JsonRpcProvider, + provider: EthersProviderWrapper, graphConfigPath: string, ): Promise { // Get list of privileged accounts we don't want as test accounts diff --git a/gre/config.ts b/gre/config.ts index 5a2cdfd00..d69095565 100644 --- a/gre/config.ts +++ b/gre/config.ts @@ -1,6 +1,5 @@ import fs from 'fs' import path from 'path' -import { providers } from 'ethers' import { NetworkConfig, NetworksConfig } from 'hardhat/types/config' import { HardhatRuntimeEnvironment } from 'hardhat/types/runtime' @@ -10,8 +9,8 @@ import { GraphRuntimeEnvironmentOptions } from './type-extensions' import { GREPluginError } from './helpers/error' import GraphNetwork from './helpers/network' -import { createProvider } from '../node_modules/hardhat/internal/core/providers/construction' -import { EthersProviderWrapper } from '../node_modules/@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' +import { createProvider } from 'hardhat/internal/core/providers/construction' +import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' import { logDebug } from './logger' @@ -104,6 +103,7 @@ export function getProviders( chainLabel: string, ): EthersProviderWrapper | undefined => { const network = getNetworkConfig(networks, chainId) as HttpNetworkConfig + const networkName = getNetworkName(networks, chainId) // Ensure at least main provider is configured if (isMainProvider && network === undefined) { @@ -112,13 +112,12 @@ export function getProviders( logDebug(`Provider url for ${chainLabel}: ${network?.url}`) - if (network === undefined) { + if (network === undefined || networkName === undefined) { return undefined } // Build provider as EthersProviderWrapper instead of JsonRpcProvider // This let's us use hardhat's account management methods - const networkName = getNetworkName(networks, chainId) const ethereumProvider = createProvider(networkName, network) const ethersProviderWrapper = new EthersProviderWrapper(ethereumProvider) return ethersProviderWrapper diff --git a/gre/gre.ts b/gre/gre.ts index 99d081f7c..6e61ee790 100644 --- a/gre/gre.ts +++ b/gre/gre.ts @@ -10,11 +10,11 @@ import { GraphRuntimeEnvironment, GraphRuntimeEnvironmentOptions, } from './type-extensions' -import { providers } from 'ethers' import { getChains, getProviders, getAddressBookPath, getGraphConfigPaths } from './config' import { getDeployer, getNamedAccounts, getTestAccounts } from './accounts' import { logDebug, logWarn } from './logger' import path from 'path' +import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' // Graph Runtime Environment (GRE) extensions for the HRE @@ -81,7 +81,7 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { function buildGraphNetworkEnvironment( chainId: number, - provider: providers.JsonRpcProvider | undefined, + provider: EthersProviderWrapper | undefined, graphConfigPath: string | undefined, addressBookPath: string, isHHL1: boolean, From 5ac8a3f77fc07f17b641a1a1dd7337b02b8dbe84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Tue, 16 Aug 2022 17:59:06 +0200 Subject: [PATCH 7/9] fix: e2e deployment types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- e2e/deployment/config/allocationExchange.test.ts | 2 +- e2e/deployment/config/controller.test.ts | 2 +- e2e/deployment/config/graphProxyAdmin.test.ts | 2 +- e2e/deployment/config/graphToken.test.ts | 2 +- e2e/deployment/config/rewardsManager.test.ts | 2 +- e2e/deployment/config/subgraphNFT.test.ts | 2 +- hardhat.config.ts | 1 + 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/e2e/deployment/config/allocationExchange.test.ts b/e2e/deployment/config/allocationExchange.test.ts index ee023ce58..66e43074b 100644 --- a/e2e/deployment/config/allocationExchange.test.ts +++ b/e2e/deployment/config/allocationExchange.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import { NamedAccounts } from '../../../tasks/type-extensions' +import { NamedAccounts } from '../../../gre/type-extensions' describe('AllocationExchange configuration', () => { const { diff --git a/e2e/deployment/config/controller.test.ts b/e2e/deployment/config/controller.test.ts index 00caf2130..9bde5d85e 100644 --- a/e2e/deployment/config/controller.test.ts +++ b/e2e/deployment/config/controller.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre, { ethers } from 'hardhat' -import { NamedAccounts } from '../../../tasks/type-extensions' +import { NamedAccounts } from '../../../gre/type-extensions' describe('Controller configuration', () => { const { contracts, getNamedAccounts } = hre.graph() diff --git a/e2e/deployment/config/graphProxyAdmin.test.ts b/e2e/deployment/config/graphProxyAdmin.test.ts index 0d43984de..5c57b731f 100644 --- a/e2e/deployment/config/graphProxyAdmin.test.ts +++ b/e2e/deployment/config/graphProxyAdmin.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import { NamedAccounts } from '../../../tasks/type-extensions' +import { NamedAccounts } from '../../../gre/type-extensions' describe('GraphProxyAdmin configuration', () => { const { diff --git a/e2e/deployment/config/graphToken.test.ts b/e2e/deployment/config/graphToken.test.ts index b0e9a53c8..cc5e60618 100644 --- a/e2e/deployment/config/graphToken.test.ts +++ b/e2e/deployment/config/graphToken.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import { NamedAccounts } from '../../../tasks/type-extensions' +import { NamedAccounts } from '../../../gre/type-extensions' describe('GraphToken configuration', () => { const { diff --git a/e2e/deployment/config/rewardsManager.test.ts b/e2e/deployment/config/rewardsManager.test.ts index a5dff8a2e..475abd9f4 100644 --- a/e2e/deployment/config/rewardsManager.test.ts +++ b/e2e/deployment/config/rewardsManager.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import { NamedAccounts } from '../../../tasks/type-extensions' +import { NamedAccounts } from '../../../gre/type-extensions' describe('RewardsManager configuration', () => { const { diff --git a/e2e/deployment/config/subgraphNFT.test.ts b/e2e/deployment/config/subgraphNFT.test.ts index 2220a1e1b..d0bd2e10d 100644 --- a/e2e/deployment/config/subgraphNFT.test.ts +++ b/e2e/deployment/config/subgraphNFT.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import hre from 'hardhat' -import { NamedAccounts } from '../../../tasks/type-extensions' +import { NamedAccounts } from '../../../gre/type-extensions' describe('SubgraphNFT configuration', () => { const { diff --git a/hardhat.config.ts b/hardhat.config.ts index 782f4958e..91c44a685 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -140,6 +140,7 @@ const config: HardhatUserConfig = { hardfork: 'london', }, localhost: { + chainId: 1337, accounts: process.env.FORK === 'true' ? getAccountsKeys() : { mnemonic: DEFAULT_TEST_MNEMONIC }, }, From d003545fd021f9dd190e107111543e881c2ff0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Tue, 16 Aug 2022 19:32:04 +0200 Subject: [PATCH 8/9] chore: add readme to GRE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/README.md | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 gre/README.md diff --git a/gre/README.md b/gre/README.md new file mode 100644 index 000000000..c5e6369ce --- /dev/null +++ b/gre/README.md @@ -0,0 +1,223 @@ +# Graph Runtime Environment (GRE) + +GRE is a hardhat plugin that extends hardhat's runtime environment to inject additional functionality related to the usage of the Graph Protocol. + +### Features + +- Provides a simple interface to interact with protocol contracts +- Exposes protocol configuration via graph config file and address book +- Provides account management methods for convenience +- Multichain! Supports both L1 and L2 layers of the protocol simultaneously + +### Usage + +#### Example +Import GRE using `import './gre/gre'` on your hardhat config file and then: + +```js +// Use L2 governor account to set the L1 token address on the L2 gateway +const { l1, l2 } = hre.graph() + +const { GraphToken } = l1.contracts + +const { L2GraphTokenGateway } = l2.contracts +const { governor } = await l2.getNamedAccounts() + +const tx = L2GraphTokenGateway.connect(governor).setL1TokenAddress(GraphToken.address) +``` + +#### Network selection + +GRE supports both the L1 and L2 networks of the Graph Protocol by default. It will use hardhat's network defined via `--network` as the "main" network and then automatically detect which is the appropriate counterpart network in L1 or L2. + +Example: + +```bash +# L1: goerli and L2: arbitrum-goerli +hh console --network goerli + +# L1: mainnet and L2: arbitrum-one +hh console --network arbitrum-one + +# L1: mainnet and L2: arbitrum-one > same as previous +hh console --network mainnet +``` + +#### Configuration + +To use GRE you'll need to configure the target networks. That is done via either hardhat's config file using the `networks` [config field](https://hardhat.org/hardhat-runner/docs/config#json-rpc-based-networks) or by passing the appropriate arguments to `hre.graph()` initializer. + +__Note__: The "main" network, defined by hardhat's `--network` flag _MUST_ be properly configured for GRE to initialize successfully. It's not necessary to configure the counterpart network if you don't plan on using it. + +**Hardhat: Network config** +```js +networks: { + goerli: { + chainId: 5, + url: `https://goerli.infura.io/v3/123456` + accounts: { + mnemonic: 'test test test test test test test test test test test test', + }, + graphConfig: 'config/graph.goerli.yml' + }, +} +``` + +Fields: +- **(_REQUIRED_) chainId**: the chainId of the network. This field is not required by hardhat but it's used by GRE to simplify the API. +- **(_REQUIRED_) url**: the RPC endpoint of the network. +- **(_OPTIONAL_) accounts**: the accounts to use on the network. These will be used by the account management functions on GRE. +- **(_OPTIONAL_) graphConfig**: the path to the graph config file for the network. + +**Hardhat: Graph config** + +Additionally, the plugin adds a new config field to hardhat's config file: `graphConfig`. This can be used used to define defaults for the graph config file. + + +```js +... +networks: { +... +}, +graph: { + addressBook: 'addresses.json' + l1GraphConfig: 'config/graph.mainnet.yml' + l2GraphConfig: 'config/graph.arbitrum-one.yml' +} +... +``` + +Fields: +- **(_OPTIONAL_) addressBook**: the path to the address book. +- **(_REQUIRED_) l1GraphConfig**: default path to the graph config file for L1 networks. This will be used if the `graphConfig` field is not defined on the network config. +- **(_REQUIRED_) l2GraphConfig**: default path to the graph config file for L2 networks. This will be used if the `graphConfig` field is not defined on the network config. + +**Options: Graph initializer** + +The GRE initializer also allows you to set the address book and the graph config files like so: +```js +const graph = hre.graph({ + addressBook: 'addresses.json', + l1GraphConfig: 'config/graph.mainnet.yml' + l2GraphConfig: 'config/graph.arbitrum-one.yml' +}) + +// Here graphConfig will apply only to the "main" network given by --network +const graph = hre.graph({ + addressBook: 'addresses.json', + graphConfig: 'config/graph.mainnet.yml' +}) +``` + +**Config priority** + +The path to the graph config and the address book can be set in multiple ways. The plugin will use the following order to determine the path to the graph config file: + +1) `hre.graph({ ... })` init parameters `l1GraphConfigPath` and `l2GraphConfigPath` +2) `hre.graph({ ...})` init parameter graphConfigPath (but only for the "main" network) +3) `networks..graphConfig` network config parameter `graphConfig` in hardhat config file +4) `graph.lGraphConfig` graph config parameters `l1GraphConfig` and `l2GraphConfig` in hardhat config file + +The priority for the address book is: +1) `hre.graph({ ... })` init parameter `addressBook` +2) `graph.addressBook` graph config parameter `addressBook` in hardhat config file + +### API + +GRE exposes functionality via a simple API: + +```js +const graph = hre.graph() + +// To access the L1 object +graph.l1 + +// To access the L2 object +graph.l2 +``` + +The interface for both `l1` and `l2` objects looks like this: + +```ts +export interface GraphNetworkEnvironment { + chainId: number + contracts: NetworkContracts + graphConfig: any + addressBook: AddressBook + getNamedAccounts: () => Promise + getTestAccounts: () => Promise + getDeployer: () => Promise +} +``` + +**ChainId** + +The chainId of the network. + +**Contracts** + +Returns an object with all the contracts available in the network. Connects using a provider created with the URL specified in hardhat's network configuration (it doesn't use the usual hardhat `hre.ethers.provider`). + +```js +> const graph = hre.graph() + +// Print curation default reserve ratio on L1 +> await g.l1.contracts.Curation.defaultReserveRatio() +500000 +``` + +**Graph Config** + +Returns an object that grants raw access to the graph config file for the protocol. The graph config file is a YAML file that contains all the parameters with which the protocol was deployed. + +> TODO: add better APIs to interact with the graph config file. + +**Address Book** + +Returns an object that allows interacting with the address book. + +```js +> const graph = hre.graph() +> graph.l1.addressBook.getEntry('Curation') +{ + address: '0xE59B4820dDE28D2c235Bd9A73aA4e8716Cb93E9B', + initArgs: [ + '0x48eD7AfbaB432d1Fc6Ea84EEC70E745d9DAcaF3B', + '0x2DFDC3e11E035dD96A4aB30Ef67fab4Fb6EC01f2', + '0x8bEd0a89F18a801Da9dEA994D475DEa74f75A059', + '500000', + '10000', + '1000000000000000000' + ], + creationCodeHash: '0x25a7b6cafcebb062169bc25fca9bcce8f23bd7411235859229ae3cc99b9a7d58', + runtimeCodeHash: '0xaf2d63813a0e5059f63ec46e1b280eb9d129d5ad548f0cdd1649d9798fde10b6', + txHash: '0xf1b1f0f28b80068bcc9fd6ef475be6324a8b23cbdb792f7344f05ce00aa997d7', + proxy: true, + implementation: { + address: '0xAeaA2B058539750b740E858f97159E6856948670', + creationCodeHash: '0x022576ab4b739ee17dab126ea7e5a6814bda724aa0e4c6735a051b38a76bd597', + runtimeCodeHash: '0xc7b1f9bef01ef92779aab0ae9be86376c47584118c508f5b4e612a694a4aab93', + txHash: '0x400bfb7b6c384363b859a66930590507ddca08ebedf64b20c4b5f6bc8e76e125' + } +} +``` + +**Account management: getNamedAccounts** +Returns an object with all the named accounts available in the network. Named accounts are accounts that have special roles in the protocol, they are defined in the graph config file. + +```js +> const graph = hre.graph() +> const namedAccounts = await g.l1.getNamedAccounts() +> namedAccounts.governor.address +'0xf1135bFF22512FF2A585b8d4489426CE660f204c' +``` + +The accounts are initialized from the graph config file but if the correct mnemonic or private key is provided via hardhat network configuration then they will be fully capable of signing transactions. + +**Account management: getTestAccounts** +Returns an object with accounts which can be used for testing/interacting with the protocol. These are obtained from hardhat's network configuration using the provided mnemonic or private key. + +**Account management: getDeployer** +Returns an object with the would-be deployer account. The deployer is by convention the first (index 0) account derived from the mnemonic or private key provided via hardhat network configuration. + +It's important to note that the deployer is not a named account as it's derived from the provided mnemonic so it won't necessarily match the actual deployer for a given deployment. It's the account that would be used to deploy the protocol with the current configuration. It's not possible at the moment to recover the actual deployer account from a deployed protocol. \ No newline at end of file From 37cddd33b55d7cd2fe7634980e99ae1790e53ca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Wed, 17 Aug 2022 15:39:24 +0200 Subject: [PATCH 9/9] chore: add provider to GRE objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/config.ts | 2 +- gre/gre.ts | 1 + gre/type-extensions.d.ts | 3 +++ hardhat.config.ts | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gre/config.ts b/gre/config.ts index d69095565..68b624b82 100644 --- a/gre/config.ts +++ b/gre/config.ts @@ -117,7 +117,7 @@ export function getProviders( } // Build provider as EthersProviderWrapper instead of JsonRpcProvider - // This let's us use hardhat's account management methods + // This allows us to use hardhat's account management methods for free const ethereumProvider = createProvider(networkName, network) const ethersProviderWrapper = new EthersProviderWrapper(ethereumProvider) return ethersProviderWrapper diff --git a/gre/gre.ts b/gre/gre.ts index 6e61ee790..221557ba4 100644 --- a/gre/gre.ts +++ b/gre/gre.ts @@ -104,6 +104,7 @@ function buildGraphNetworkEnvironment( return { chainId: chainId, + provider: provider, addressBook: lazyObject(() => getAddressBook(addressBookPath, chainId.toString())), graphConfig: lazyObject(() => readConfig(graphConfigPath, true)), contracts: lazyObject(() => diff --git a/gre/type-extensions.d.ts b/gre/type-extensions.d.ts index 9a1832a78..a2f253124 100644 --- a/gre/type-extensions.d.ts +++ b/gre/type-extensions.d.ts @@ -2,6 +2,8 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { AddressBook } from '../cli/address-book' import { NetworkContracts } from '../cli/contracts' +import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' + export interface GraphRuntimeEnvironmentOptions { addressBook?: string l1GraphConfig?: string @@ -23,6 +25,7 @@ export type NamedAccounts = { export interface GraphNetworkEnvironment { chainId: number + provider: EthersProviderWrapper contracts: NetworkContracts graphConfig: any addressBook: AddressBook diff --git a/hardhat.config.ts b/hardhat.config.ts index 91c44a685..003e7d3ee 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -22,13 +22,13 @@ import '@openzeppelin/hardhat-upgrades' import '@typechain/hardhat' import 'solidity-coverage' import 'hardhat-storage-layout' -import './gre/gre' // Tasks const SKIP_LOAD = process.env.SKIP_LOAD === 'true' function loadTasks() { + require('./gre/gre') ;['contracts', 'misc', 'deployment', 'actions', 'verify', 'e2e'].forEach((folder) => { const tasksPath = path.join(__dirname, 'tasks', folder) fs.readdirSync(tasksPath)