From 0ff9575b50b68aada2a317c4a9aea4216450b7fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Thu, 22 Sep 2022 14:29:05 -0400 Subject: [PATCH 1/3] feat(gre): add wallet methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/README.md | 14 +++- gre/accounts.ts | 31 +++++++ gre/config.ts | 2 +- gre/gre.ts | 20 ++++- gre/test/accounts.test.ts | 82 +++++++++++++++++++ .../graph-config/hardhat.config.ts | 3 + gre/type-extensions.d.ts | 3 + 7 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 gre/test/accounts.test.ts diff --git a/gre/README.md b/gre/README.md index c5e6369ce..2ebb2365a 100644 --- a/gre/README.md +++ b/gre/README.md @@ -212,12 +212,18 @@ Returns an object with all the named accounts available in the network. Named ac '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. +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. Accounts are already connected to the network provider. **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. +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. Accounts are already connected to the network provider. **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. +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. Deployer account is already connected to the network provider. -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 +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. + +**Account management: getWallets** +Returns an object with wallets derived from the mnemonic or private key provided via hardhat network configuration. These wallets are not connected to a provider. + +**Account management: getWallet** +Returns a wallet derived from the mnemonic or private key provided via hardhat network configuration that matches a given address. This wallet is not connected to a provider. \ No newline at end of file diff --git a/gre/accounts.ts b/gre/accounts.ts index 339606c46..6baf3da6a 100644 --- a/gre/accounts.ts +++ b/gre/accounts.ts @@ -1,7 +1,11 @@ import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +import { derivePrivateKeys } from 'hardhat/internal/core/providers/util' +import { Wallet } from 'ethers' import { getItemValue, readConfig } from '../cli/config' import { AccountNames, NamedAccounts } from './type-extensions' +import { getNetworkName } from './config' +import { HttpNetworkHDAccountsConfig, NetworksConfig } from 'hardhat/types' const namedAccountList: AccountNames[] = [ 'arbitrator', @@ -53,3 +57,30 @@ export async function getTestAccounts( return !blacklist.includes(s.address) }) } + +export async function getWallets( + networks: NetworksConfig, + chainId: number, + mainNetworkName: string, +): Promise { + const networkName = getNetworkName(networks, chainId, mainNetworkName) + const accounts = networks[networkName].accounts + const mnemonic = (accounts as HttpNetworkHDAccountsConfig).mnemonic + + if (mnemonic) { + const privateKeys = derivePrivateKeys(mnemonic, "m/44'/60'/0'/0/", 0, 20, '') + return privateKeys.map((privateKey) => new Wallet(privateKey)) + } + + return [] +} + +export async function getWallet( + networks: NetworksConfig, + chainId: number, + mainNetworkName: string, + address: string, +): Promise { + const wallets = await getWallets(networks, chainId, mainNetworkName) + return wallets.find((w) => w.address === address) +} diff --git a/gre/config.ts b/gre/config.ts index 14e4008ac..de960815f 100644 --- a/gre/config.ts +++ b/gre/config.ts @@ -242,7 +242,7 @@ function getNetworkConfig( } } -function getNetworkName( +export function getNetworkName( networks: NetworksConfig, chainId: number, mainNetworkName: string, diff --git a/gre/gre.ts b/gre/gre.ts index 97520a16a..aea6ee6f4 100644 --- a/gre/gre.ts +++ b/gre/gre.ts @@ -11,10 +11,11 @@ import { GraphRuntimeEnvironmentOptions, } from './type-extensions' import { getChains, getProviders, getAddressBookPath, getGraphConfigPaths } from './config' -import { getDeployer, getNamedAccounts, getTestAccounts } from './accounts' +import { getDeployer, getNamedAccounts, getTestAccounts, getWallet, getWallets } from './accounts' import { logDebug, logWarn } from './logger' import path from 'path' import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' +import { Wallet } from 'ethers' // Graph Runtime Environment (GRE) extensions for the HRE @@ -53,12 +54,23 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { isHHL1, ) + // Wallet functions + const l1GetWallets = () => getWallets(hre.config.networks, l1ChainId, hre.network.name) + const l1GetWallet = (address: string) => + getWallet(hre.config.networks, l1ChainId, hre.network.name, address) + const l2GetWallets = () => getWallets(hre.config.networks, l2ChainId, hre.network.name) + const l2GetWallet = (address: string) => + getWallet(hre.config.networks, l2ChainId, hre.network.name, address) + + // Build the Graph Runtime Environment (GRE) const l1Graph: GraphNetworkEnvironment | null = buildGraphNetworkEnvironment( l1ChainId, l1Provider, l1GraphConfigPath, addressBookPath, isHHL1, + l1GetWallets, + l1GetWallet, ) const l2Graph: GraphNetworkEnvironment | null = buildGraphNetworkEnvironment( @@ -67,6 +79,8 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => { l2GraphConfigPath, addressBookPath, isHHL1, + l2GetWallets, + l2GetWallet, ) const gre: GraphRuntimeEnvironment = { @@ -88,6 +102,8 @@ function buildGraphNetworkEnvironment( graphConfigPath: string | undefined, addressBookPath: string, isHHL1: boolean, + getWallets: () => Promise, + getWallet: (address: string) => Promise, ): GraphNetworkEnvironment | null { if (graphConfigPath === undefined) { logWarn( @@ -116,5 +132,7 @@ function buildGraphNetworkEnvironment( getDeployer: lazyFunction(() => () => getDeployer(provider)), getNamedAccounts: lazyFunction(() => () => getNamedAccounts(provider, graphConfigPath)), getTestAccounts: lazyFunction(() => () => getTestAccounts(provider, graphConfigPath)), + getWallets: lazyFunction(() => () => getWallets()), + getWallet: lazyFunction(() => (address: string) => getWallet(address)), } } diff --git a/gre/test/accounts.test.ts b/gre/test/accounts.test.ts new file mode 100644 index 000000000..a382aee44 --- /dev/null +++ b/gre/test/accounts.test.ts @@ -0,0 +1,82 @@ +import chai, { expect } from 'chai' +import chaiAsPromised from 'chai-as-promised' +import { ethers } from 'ethers' +import { GraphRuntimeEnvironment } from '../type-extensions' +import { useEnvironment } from './helpers' + +chai.use(chaiAsPromised) + +const mnemonic = 'pumpkin orient can short never warm truth legend cereal tourist craft skin' + +describe('GRE usage > account management', function () { + useEnvironment('graph-config', 'hardhat') + + let graph: GraphRuntimeEnvironment + + beforeEach(function () { + graph = this.hre.graph() + }) + + describe('getWallets', function () { + it('should return 20 wallets', async function () { + const wallets = await graph.getWallets() + expect(wallets.length).to.equal(20) + }) + + it('should derive wallets from hardhat config mnemonic', async function () { + const wallets = await graph.getWallets() + + for (let i = 0; i < wallets.length; i++) { + const derived = ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`) + expect(wallets[i].address).to.equal(derived.address) + } + }) + + it('should return wallets capable of signing messages', async function () { + const wallets = await graph.getWallets() + + for (const wallet of wallets) { + expect(wallet.signMessage('test')).to.eventually.be.fulfilled + } + }) + + it('should return wallets not connected to a provider', async function () { + const wallets = await graph.getWallets() + + for (const wallet of wallets) { + expect(wallet.provider).to.be.null + } + }) + }) + + describe('getWallet', function () { + it('should return wallet if provided address can be derived from mnemonic', async function () { + for (let i = 0; i < 20; i++) { + const derived = ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`) + const wallet = await graph.getWallet(derived.address) + expect(wallet.address).to.equal(derived.address) + } + }) + + it('should return wallet capable of signing messages', async function () { + for (let i = 0; i < 20; i++) { + const derived = ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`) + const wallet = await graph.getWallet(derived.address) + expect(wallet.signMessage('test')).to.eventually.be.fulfilled + } + }) + + it('should return wallet not connected to a provider', async function () { + for (let i = 0; i < 20; i++) { + const derived = ethers.Wallet.fromMnemonic(mnemonic, `m/44'/60'/0'/0/${i}`) + const wallet = await graph.getWallet(derived.address) + expect(wallet.provider).to.be.null + } + }) + + it('should return undefined if provided address cant be derived from mnemonic', async function () { + const wallet = await graph.getWallet('0x0000000000000000000000000000000000000000') + expect(wallet).to.be.undefined + }) + }) +}) diff --git a/gre/test/fixture-projects/graph-config/hardhat.config.ts b/gre/test/fixture-projects/graph-config/hardhat.config.ts index 5a4e5cdfc..1def7b415 100644 --- a/gre/test/fixture-projects/graph-config/hardhat.config.ts +++ b/gre/test/fixture-projects/graph-config/hardhat.config.ts @@ -9,6 +9,9 @@ module.exports = { networks: { hardhat: { chainId: 1337, + accounts: { + mnemonic: 'pumpkin orient can short never warm truth legend cereal tourist craft skin', + }, }, mainnet: { chainId: 1, diff --git a/gre/type-extensions.d.ts b/gre/type-extensions.d.ts index a2f253124..c20277927 100644 --- a/gre/type-extensions.d.ts +++ b/gre/type-extensions.d.ts @@ -3,6 +3,7 @@ import { AddressBook } from '../cli/address-book' import { NetworkContracts } from '../cli/contracts' import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper' +import { Wallet } from 'ethers' export interface GraphRuntimeEnvironmentOptions { addressBook?: string @@ -32,6 +33,8 @@ export interface GraphNetworkEnvironment { getNamedAccounts: () => Promise getTestAccounts: () => Promise getDeployer: () => Promise + getWallets: () => Promise + getWallet: (address: string) => Promise } export interface GraphRuntimeEnvironment extends GraphNetworkEnvironment { From 3ef5ec8d50599759556eada00e7c7456a439eb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Thu, 22 Sep 2022 14:38:24 -0400 Subject: [PATCH 2/3] fix(gre): add chai-as-promise dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- package.json | 2 ++ yarn.lock | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/package.json b/package.json index 2ca6cd1c3..93f52e23c 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@typechain/ethers-v5": "^7.0.0", "@typechain/hardhat": "^2.0.0", "@types/bs58": "^4.0.1", + "@types/chai-as-promised": "^7.1.5", "@types/dotenv": "^8.2.0", "@types/glob": "^7.2.0", "@types/inquirer": "^7.3.1", @@ -42,6 +43,7 @@ "@urql/core": "^2.1.3", "bignumber.js": "^9.0.0", "chai": "^4.3.4", + "chai-as-promised": "^7.1.1", "cli-table": "^0.3.6", "dotenv": "^9.0.0", "eslint": "^7.24.0", diff --git a/yarn.lock b/yarn.lock index 6201843bb..d82c896fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1477,6 +1477,13 @@ dependencies: base-x "^3.0.6" +"@types/chai-as-promised@^7.1.5": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz#6e016811f6c7a64f2eed823191c3a6955094e255" + integrity sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ== + dependencies: + "@types/chai" "*" + "@types/chai@*": version "4.3.1" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.1.tgz#e2c6e73e0bdeb2521d00756d099218e9f5d90a04" @@ -3320,6 +3327,13 @@ cbor@^8.0.0: dependencies: nofilter "^3.1.0" +chai-as-promised@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + chai@^4.3.4: version "4.3.6" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" From 9b91113638b213a52f9dae4afa4db0e2f7aae1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Thu, 22 Sep 2022 14:47:38 -0400 Subject: [PATCH 3/3] fix(gre): increase wallet tests timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- gre/test/accounts.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gre/test/accounts.test.ts b/gre/test/accounts.test.ts index a382aee44..f309aed95 100644 --- a/gre/test/accounts.test.ts +++ b/gre/test/accounts.test.ts @@ -9,6 +9,9 @@ chai.use(chaiAsPromised) const mnemonic = 'pumpkin orient can short never warm truth legend cereal tourist craft skin' describe('GRE usage > account management', function () { + // Tests that loop through all the wallets take more than the default timeout + this.timeout(10_000) + useEnvironment('graph-config', 'hardhat') let graph: GraphRuntimeEnvironment