Skip to content

Commit

Permalink
feat(gre): add hardhat-secure-accounts to GRE (#696)
Browse files Browse the repository at this point in the history
* chore: update GRE to use secure accounts

Signed-off-by: Tomás Migone <[email protected]>
  • Loading branch information
tmigone authored Oct 28, 2022
1 parent 3f16e5a commit 7e5ac05
Show file tree
Hide file tree
Showing 28 changed files with 565 additions and 219 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ bin/
/reports
coverage.json

tx-*.log
tx-*.log
.keystore
4 changes: 3 additions & 1 deletion e2e/scenarios/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
export function getGraphOptsFromArgv(): {
graphConfig: string | undefined
addressBook: string | undefined
disableSecureAccounts?: boolean | undefined
} {
const argv = process.argv.slice(2)

const getArgv = (index: number) =>
const getArgv: any = (index: number) =>
argv[index] && argv[index] !== 'undefined' ? argv[index] : undefined

return {
graphConfig: getArgv(0),
addressBook: getArgv(1),
disableSecureAccounts: getArgv(2),
}
}
34 changes: 29 additions & 5 deletions gre/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ GRE is a hardhat plugin that extends hardhat's runtime environment to inject add
- 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
- Integrates seamlessly with [hardhat-secure-accounts](https://www.npmjs.com/package/hardhat-secure-accounts)

### Usage
## Usage

#### Example
Import GRE using `import './gre/gre'` on your hardhat config file and then:
Expand Down Expand Up @@ -80,7 +81,7 @@ networks: {
...
},
graph: {
addressBook: 'addresses.json'
addressBook: 'addresses.json'
l1GraphConfig: 'config/graph.mainnet.yml'
l2GraphConfig: 'config/graph.arbitrum-one.yml'
}
Expand Down Expand Up @@ -122,7 +123,7 @@ 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
## API

GRE exposes functionality via a simple API:

Expand All @@ -142,6 +143,7 @@ The interface for both `l1` and `l2` objects looks like this:
export interface GraphNetworkEnvironment {
chainId: number
contracts: NetworkContracts
provider: EthersProviderWrapper
graphConfig: any
addressBook: AddressBook
getNamedAccounts: () => Promise<NamedAccounts>
Expand All @@ -168,7 +170,7 @@ Returns an object with all the contracts available in the network. Connects usin

**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.
Returns an object that grants raw access to the YAML parse of 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.
Expand Down Expand Up @@ -226,4 +228,26 @@ It's important to note that the deployer is not a named account as it's derived
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.
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.

#### Integration with hardhat-secure-accounts

[hardhat-secure-accounts](https://www.npmjs.com/package/hardhat-secure-accounts) is a hardhat plugin that allows you to use encrypted keystore files to store your private keys. GRE has built-in support to use this plugin. By default is enabled but can be disabled by setting the `disableSecureAccounts` option to `true` when instantiating the GRE object. When enabled, each time you call any of the account management methods you will be prompted for an account name and password to unlock:

```js
// Without secure accounts
> const graph = hre.graph({ disableSecureAccounts: true })
> const deployer = await g.l1.getDeployer()
> deployer.address
'0xBc7f4d3a85B820fDB1058FD93073Eb6bc9AAF59b'

// With secure accounts
> const graph = hre.graph()
> const deployer = await g.l1.getDeployer()
== Using secure accounts, please unlock an account for L1(goerli)
Available accounts: goerli-deployer, arbitrum-goerli-deployer, rinkeby-deployer, test-mnemonic
Choose an account to unlock (use tab to autocomplete): test-mnemonic
Enter the password for this account: ************
> deployer.address
'0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
```
2 changes: 1 addition & 1 deletion gre/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 { getNetworkName } from './helpers/network'
import { HttpNetworkHDAccountsConfig, NetworksConfig } from 'hardhat/types'

const namedAccountList: AccountNames[] = [
Expand Down
122 changes: 20 additions & 102 deletions gre/config.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import fs from 'fs'
import path from 'path'

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/error'
import GraphNetwork, { counterpartName } from './helpers/network'

import { createProvider } from 'hardhat/internal/core/providers/construction'
import GraphNetwork from './helpers/chain'
import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper'

import { logDebug, logWarn } from './logger'
import { HARDHAT_NETWORK_NAME } from 'hardhat/plugins'
import { logDebug } from './helpers/logger'
import { normalizePath } from './helpers/utils'
import { getNetworkConfig } from './helpers/network'
import { getDefaultProvider } from './providers'

interface GREChains {
l1ChainId: number
Expand Down Expand Up @@ -89,49 +86,28 @@ export function getChains(mainChainId: number | undefined): GREChains {
}
}

export function getProviders(
export function getDefaultProviders(
hre: HardhatRuntimeEnvironment,
l1ChainId: number,
l2ChainId: number,
isHHL1: boolean,
): GREProviders {
logDebug('== Getting providers')

const getProvider = (
networks: NetworksConfig,
chainId: number,
mainNetworkName: string,
isMainProvider: boolean,
chainLabel: string,
): EthersProviderWrapper | undefined => {
const network = getNetworkConfig(networks, chainId, mainNetworkName) as HttpNetworkConfig
const networkName = getNetworkName(networks, chainId, mainNetworkName)

logDebug(`Provider url for ${chainLabel}(${networkName}): ${network?.url}`)

// Ensure at least main provider is configured
// For Hardhat network we don't need url to create a provider
if (
isMainProvider &&
(network === undefined || network.url === undefined) &&
networkName !== HARDHAT_NETWORK_NAME
) {
throw new GREPluginError(`Must set a provider url for chain: ${chainId}!`)
}

if (network === undefined || networkName === undefined) {
return undefined
}

// Build provider as EthersProviderWrapper instead of JsonRpcProvider
// This allows us to use hardhat's account management methods for free
const ethereumProvider = createProvider(networkName, network)
const ethersProviderWrapper = new EthersProviderWrapper(ethereumProvider)
return ethersProviderWrapper
}

const l1Provider = getProvider(hre.config.networks, l1ChainId, hre.network.name, isHHL1, 'L1')
const l2Provider = getProvider(hre.config.networks, l2ChainId, hre.network.name, !isHHL1, 'L2')
const l1Provider = getDefaultProvider(
hre.config.networks,
l1ChainId,
hre.network.name,
isHHL1,
'L1',
)
const l2Provider = getDefaultProvider(
hre.config.networks,
l2ChainId,
hre.network.name,
!isHHL1,
'L2',
)

return {
l1Provider,
Expand Down Expand Up @@ -211,61 +187,3 @@ export function getGraphConfigPaths(
l2GraphConfigPath: l2GraphConfigPath,
}
}

function getNetworkConfig(
networks: NetworksConfig,
chainId: number,
mainNetworkName: string,
): (NetworkConfig & { name: string }) | undefined {
const candidateNetworks = Object.keys(networks)
.map((n) => ({ ...networks[n], name: n }))
.filter((n) => n.chainId === chainId)

if (candidateNetworks.length > 1) {
logWarn(
`Found multiple networks with chainId ${chainId}, trying to use main network name to desambiguate`,
)

const filteredByMainNetworkName = candidateNetworks.filter((n) => n.name === mainNetworkName)

if (filteredByMainNetworkName.length === 1) {
logDebug(`Found network with chainId ${chainId} and name ${mainNetworkName}`)
return filteredByMainNetworkName[0]
} else {
logWarn(`Could not desambiguate with main network name, trying secondary network name`)
const secondaryNetworkName = counterpartName(mainNetworkName)
const filteredBySecondaryNetworkName = candidateNetworks.filter(
(n) => n.name === secondaryNetworkName,
)

if (filteredBySecondaryNetworkName.length === 1) {
logDebug(`Found network with chainId ${chainId} and name ${mainNetworkName}`)
return filteredBySecondaryNetworkName[0]
} else {
throw new GREPluginError(
`Could not desambiguate network with chainID ${chainId}. Use case not supported!`,
)
}
}
} else if (candidateNetworks.length === 1) {
return candidateNetworks[0]
} else {
return undefined
}
}

export function getNetworkName(
networks: NetworksConfig,
chainId: number,
mainNetworkName: string,
): string | undefined {
const network = getNetworkConfig(networks, chainId, mainNetworkName)
return network?.name
}

function normalizePath(_path: string, graphPath: string) {
if (!path.isAbsolute(_path)) {
_path = path.join(graphPath, _path)
}
return _path
}
63 changes: 57 additions & 6 deletions gre/gre.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import {
GraphRuntimeEnvironment,
GraphRuntimeEnvironmentOptions,
} from './type-extensions'
import { getChains, getProviders, getAddressBookPath, getGraphConfigPaths } from './config'
import { getChains, getDefaultProviders, getAddressBookPath, getGraphConfigPaths } from './config'
import { getDeployer, getNamedAccounts, getTestAccounts, getWallet, getWallets } from './accounts'
import { logDebug, logWarn } from './logger'
import { logDebug, logWarn } from './helpers/logger'
import path from 'path'
import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper'
import { Wallet } from 'ethers'

import 'hardhat-secure-accounts'
import { getSecureAccountsProvider } from './providers'

// Graph Runtime Environment (GRE) extensions for the HRE

extendConfig((config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
Expand Down Expand Up @@ -46,8 +49,43 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => {
const enableTxLogging = opts.enableTxLogging ?? false
logDebug(`Tx logging: ${enableTxLogging ? 'enabled' : 'disabled'}`)

const secureAccounts = !(
opts.disableSecureAccounts ??
hre.config.graph.disableSecureAccounts ??
false
)
logDebug(`Secure accounts: ${secureAccounts ? 'enabled' : 'disabled'}`)

const { l1ChainId, l2ChainId, isHHL1 } = getChains(hre.network.config.chainId)
const { l1Provider, l2Provider } = getProviders(hre, l1ChainId, l2ChainId, isHHL1)

// Default providers for L1 and L2
const { l1Provider, l2Provider } = getDefaultProviders(hre, l1ChainId, l2ChainId, isHHL1)

// Getters to unlock secure account providers for L1 and L2
const l1UnlockProvider = () =>
getSecureAccountsProvider(
hre.accounts,
hre.config.networks,
l1ChainId,
hre.network.name,
isHHL1,
'L1',
opts.l1AccountName,
opts.l1AccountPassword,
)

const l2UnlockProvider = () =>
getSecureAccountsProvider(
hre.accounts,
hre.config.networks,
l2ChainId,
hre.network.name,
!isHHL1,
'L2',
opts.l2AccountName,
opts.l2AccountPassword,
)

const addressBookPath = getAddressBookPath(hre, opts)
const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths(
hre,
Expand All @@ -73,8 +111,10 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => {
addressBookPath,
isHHL1,
enableTxLogging,
secureAccounts,
l1GetWallets,
l1GetWallet,
l1UnlockProvider,
)

const l2Graph: GraphNetworkEnvironment | null = buildGraphNetworkEnvironment(
Expand All @@ -84,8 +124,10 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => {
addressBookPath,
isHHL1,
enableTxLogging,
secureAccounts,
l2GetWallets,
l2GetWallet,
l2UnlockProvider,
)

const gre: GraphRuntimeEnvironment = {
Expand All @@ -108,8 +150,10 @@ function buildGraphNetworkEnvironment(
addressBookPath: string,
isHHL1: boolean,
enableTxLogging: boolean,
secureAccounts: boolean,
getWallets: () => Promise<Wallet[]>,
getWallet: (address: string) => Promise<Wallet>,
unlockProvider: () => Promise<EthersProviderWrapper | undefined>,
): GraphNetworkEnvironment | null {
if (graphConfigPath === undefined) {
logWarn(
Expand All @@ -127,6 +171,9 @@ function buildGraphNetworkEnvironment(
return null
}

// Upgrade provider to secure accounts if feature is enabled
const getUpdatedProvider = async () => (secureAccounts ? await unlockProvider() : provider)

return {
chainId: chainId,
provider: provider,
Expand All @@ -135,10 +182,14 @@ function buildGraphNetworkEnvironment(
contracts: lazyObject(() =>
loadContracts(getAddressBook(addressBookPath, chainId.toString()), provider, enableTxLogging),
),
getDeployer: lazyFunction(() => () => getDeployer(provider)),
getNamedAccounts: lazyFunction(() => () => getNamedAccounts(provider, graphConfigPath)),
getTestAccounts: lazyFunction(() => () => getTestAccounts(provider, graphConfigPath)),
getWallets: lazyFunction(() => () => getWallets()),
getWallet: lazyFunction(() => (address: string) => getWallet(address)),
getDeployer: lazyFunction(() => async () => getDeployer(await getUpdatedProvider())),
getNamedAccounts: lazyFunction(
() => async () => getNamedAccounts(await getUpdatedProvider(), graphConfigPath),
),
getTestAccounts: lazyFunction(
() => async () => getTestAccounts(await getUpdatedProvider(), graphConfigPath),
),
}
}
Loading

0 comments on commit 7e5ac05

Please sign in to comment.