Skip to content

Commit 7e5ac05

Browse files
authored
feat(gre): add hardhat-secure-accounts to GRE (#696)
* chore: update GRE to use secure accounts Signed-off-by: Tomás Migone <[email protected]>
1 parent 3f16e5a commit 7e5ac05

28 files changed

+565
-219
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ bin/
2020
/reports
2121
coverage.json
2222

23-
tx-*.log
23+
tx-*.log
24+
.keystore

e2e/scenarios/lib/helpers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
export function getGraphOptsFromArgv(): {
22
graphConfig: string | undefined
33
addressBook: string | undefined
4+
disableSecureAccounts?: boolean | undefined
45
} {
56
const argv = process.argv.slice(2)
67

7-
const getArgv = (index: number) =>
8+
const getArgv: any = (index: number) =>
89
argv[index] && argv[index] !== 'undefined' ? argv[index] : undefined
910

1011
return {
1112
graphConfig: getArgv(0),
1213
addressBook: getArgv(1),
14+
disableSecureAccounts: getArgv(2),
1315
}
1416
}

gre/README.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ GRE is a hardhat plugin that extends hardhat's runtime environment to inject add
88
- Exposes protocol configuration via graph config file and address book
99
- Provides account management methods for convenience
1010
- Multichain! Supports both L1 and L2 layers of the protocol simultaneously
11+
- Integrates seamlessly with [hardhat-secure-accounts](https://www.npmjs.com/package/hardhat-secure-accounts)
1112

12-
### Usage
13+
## Usage
1314

1415
#### Example
1516
Import GRE using `import './gre/gre'` on your hardhat config file and then:
@@ -80,7 +81,7 @@ networks: {
8081
...
8182
},
8283
graph: {
83-
addressBook: 'addresses.json'
84+
addressBook: 'addresses.json'
8485
l1GraphConfig: 'config/graph.mainnet.yml'
8586
l2GraphConfig: 'config/graph.arbitrum-one.yml'
8687
}
@@ -122,7 +123,7 @@ The priority for the address book is:
122123
1) `hre.graph({ ... })` init parameter `addressBook`
123124
2) `graph.addressBook` graph config parameter `addressBook` in hardhat config file
124125

125-
### API
126+
## API
126127

127128
GRE exposes functionality via a simple API:
128129

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

169171
**Graph Config**
170172

171-
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.
173+
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.
172174

173175
> TODO: add better APIs to interact with the graph config file.
174176
@@ -226,4 +228,26 @@ It's important to note that the deployer is not a named account as it's derived
226228
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.
227229

228230
**Account management: getWallet**
229-
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.
231+
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.
232+
233+
#### Integration with hardhat-secure-accounts
234+
235+
[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:
236+
237+
```js
238+
// Without secure accounts
239+
> const graph = hre.graph({ disableSecureAccounts: true })
240+
> const deployer = await g.l1.getDeployer()
241+
> deployer.address
242+
'0xBc7f4d3a85B820fDB1058FD93073Eb6bc9AAF59b'
243+
244+
// With secure accounts
245+
> const graph = hre.graph()
246+
> const deployer = await g.l1.getDeployer()
247+
== Using secure accounts, please unlock an account for L1(goerli)
248+
Available accounts: goerli-deployer, arbitrum-goerli-deployer, rinkeby-deployer, test-mnemonic
249+
Choose an account to unlock (use tab to autocomplete): test-mnemonic
250+
Enter the password for this account: ************
251+
> deployer.address
252+
'0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1'
253+
```

gre/accounts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { derivePrivateKeys } from 'hardhat/internal/core/providers/util'
44
import { Wallet } from 'ethers'
55
import { getItemValue, readConfig } from '../cli/config'
66
import { AccountNames, NamedAccounts } from './type-extensions'
7-
import { getNetworkName } from './config'
7+
import { getNetworkName } from './helpers/network'
88
import { HttpNetworkHDAccountsConfig, NetworksConfig } from 'hardhat/types'
99

1010
const namedAccountList: AccountNames[] = [

gre/config.ts

Lines changed: 20 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import fs from 'fs'
2-
import path from 'path'
32

4-
import { NetworkConfig, NetworksConfig } from 'hardhat/types/config'
53
import { HardhatRuntimeEnvironment } from 'hardhat/types/runtime'
6-
import { HttpNetworkConfig } from 'hardhat/types/config'
74

85
import { GraphRuntimeEnvironmentOptions } from './type-extensions'
96
import { GREPluginError } from './helpers/error'
10-
import GraphNetwork, { counterpartName } from './helpers/network'
11-
12-
import { createProvider } from 'hardhat/internal/core/providers/construction'
7+
import GraphNetwork from './helpers/chain'
138
import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper'
149

15-
import { logDebug, logWarn } from './logger'
16-
import { HARDHAT_NETWORK_NAME } from 'hardhat/plugins'
10+
import { logDebug } from './helpers/logger'
11+
import { normalizePath } from './helpers/utils'
12+
import { getNetworkConfig } from './helpers/network'
13+
import { getDefaultProvider } from './providers'
1714

1815
interface GREChains {
1916
l1ChainId: number
@@ -89,49 +86,28 @@ export function getChains(mainChainId: number | undefined): GREChains {
8986
}
9087
}
9188

92-
export function getProviders(
89+
export function getDefaultProviders(
9390
hre: HardhatRuntimeEnvironment,
9491
l1ChainId: number,
9592
l2ChainId: number,
9693
isHHL1: boolean,
9794
): GREProviders {
9895
logDebug('== Getting providers')
9996

100-
const getProvider = (
101-
networks: NetworksConfig,
102-
chainId: number,
103-
mainNetworkName: string,
104-
isMainProvider: boolean,
105-
chainLabel: string,
106-
): EthersProviderWrapper | undefined => {
107-
const network = getNetworkConfig(networks, chainId, mainNetworkName) as HttpNetworkConfig
108-
const networkName = getNetworkName(networks, chainId, mainNetworkName)
109-
110-
logDebug(`Provider url for ${chainLabel}(${networkName}): ${network?.url}`)
111-
112-
// Ensure at least main provider is configured
113-
// For Hardhat network we don't need url to create a provider
114-
if (
115-
isMainProvider &&
116-
(network === undefined || network.url === undefined) &&
117-
networkName !== HARDHAT_NETWORK_NAME
118-
) {
119-
throw new GREPluginError(`Must set a provider url for chain: ${chainId}!`)
120-
}
121-
122-
if (network === undefined || networkName === undefined) {
123-
return undefined
124-
}
125-
126-
// Build provider as EthersProviderWrapper instead of JsonRpcProvider
127-
// This allows us to use hardhat's account management methods for free
128-
const ethereumProvider = createProvider(networkName, network)
129-
const ethersProviderWrapper = new EthersProviderWrapper(ethereumProvider)
130-
return ethersProviderWrapper
131-
}
132-
133-
const l1Provider = getProvider(hre.config.networks, l1ChainId, hre.network.name, isHHL1, 'L1')
134-
const l2Provider = getProvider(hre.config.networks, l2ChainId, hre.network.name, !isHHL1, 'L2')
97+
const l1Provider = getDefaultProvider(
98+
hre.config.networks,
99+
l1ChainId,
100+
hre.network.name,
101+
isHHL1,
102+
'L1',
103+
)
104+
const l2Provider = getDefaultProvider(
105+
hre.config.networks,
106+
l2ChainId,
107+
hre.network.name,
108+
!isHHL1,
109+
'L2',
110+
)
135111

136112
return {
137113
l1Provider,
@@ -211,61 +187,3 @@ export function getGraphConfigPaths(
211187
l2GraphConfigPath: l2GraphConfigPath,
212188
}
213189
}
214-
215-
function getNetworkConfig(
216-
networks: NetworksConfig,
217-
chainId: number,
218-
mainNetworkName: string,
219-
): (NetworkConfig & { name: string }) | undefined {
220-
const candidateNetworks = Object.keys(networks)
221-
.map((n) => ({ ...networks[n], name: n }))
222-
.filter((n) => n.chainId === chainId)
223-
224-
if (candidateNetworks.length > 1) {
225-
logWarn(
226-
`Found multiple networks with chainId ${chainId}, trying to use main network name to desambiguate`,
227-
)
228-
229-
const filteredByMainNetworkName = candidateNetworks.filter((n) => n.name === mainNetworkName)
230-
231-
if (filteredByMainNetworkName.length === 1) {
232-
logDebug(`Found network with chainId ${chainId} and name ${mainNetworkName}`)
233-
return filteredByMainNetworkName[0]
234-
} else {
235-
logWarn(`Could not desambiguate with main network name, trying secondary network name`)
236-
const secondaryNetworkName = counterpartName(mainNetworkName)
237-
const filteredBySecondaryNetworkName = candidateNetworks.filter(
238-
(n) => n.name === secondaryNetworkName,
239-
)
240-
241-
if (filteredBySecondaryNetworkName.length === 1) {
242-
logDebug(`Found network with chainId ${chainId} and name ${mainNetworkName}`)
243-
return filteredBySecondaryNetworkName[0]
244-
} else {
245-
throw new GREPluginError(
246-
`Could not desambiguate network with chainID ${chainId}. Use case not supported!`,
247-
)
248-
}
249-
}
250-
} else if (candidateNetworks.length === 1) {
251-
return candidateNetworks[0]
252-
} else {
253-
return undefined
254-
}
255-
}
256-
257-
export function getNetworkName(
258-
networks: NetworksConfig,
259-
chainId: number,
260-
mainNetworkName: string,
261-
): string | undefined {
262-
const network = getNetworkConfig(networks, chainId, mainNetworkName)
263-
return network?.name
264-
}
265-
266-
function normalizePath(_path: string, graphPath: string) {
267-
if (!path.isAbsolute(_path)) {
268-
_path = path.join(graphPath, _path)
269-
}
270-
return _path
271-
}

gre/gre.ts

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import {
1010
GraphRuntimeEnvironment,
1111
GraphRuntimeEnvironmentOptions,
1212
} from './type-extensions'
13-
import { getChains, getProviders, getAddressBookPath, getGraphConfigPaths } from './config'
13+
import { getChains, getDefaultProviders, getAddressBookPath, getGraphConfigPaths } from './config'
1414
import { getDeployer, getNamedAccounts, getTestAccounts, getWallet, getWallets } from './accounts'
15-
import { logDebug, logWarn } from './logger'
15+
import { logDebug, logWarn } from './helpers/logger'
1616
import path from 'path'
1717
import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper'
1818
import { Wallet } from 'ethers'
1919

20+
import 'hardhat-secure-accounts'
21+
import { getSecureAccountsProvider } from './providers'
22+
2023
// Graph Runtime Environment (GRE) extensions for the HRE
2124

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

52+
const secureAccounts = !(
53+
opts.disableSecureAccounts ??
54+
hre.config.graph.disableSecureAccounts ??
55+
false
56+
)
57+
logDebug(`Secure accounts: ${secureAccounts ? 'enabled' : 'disabled'}`)
58+
4959
const { l1ChainId, l2ChainId, isHHL1 } = getChains(hre.network.config.chainId)
50-
const { l1Provider, l2Provider } = getProviders(hre, l1ChainId, l2ChainId, isHHL1)
60+
61+
// Default providers for L1 and L2
62+
const { l1Provider, l2Provider } = getDefaultProviders(hre, l1ChainId, l2ChainId, isHHL1)
63+
64+
// Getters to unlock secure account providers for L1 and L2
65+
const l1UnlockProvider = () =>
66+
getSecureAccountsProvider(
67+
hre.accounts,
68+
hre.config.networks,
69+
l1ChainId,
70+
hre.network.name,
71+
isHHL1,
72+
'L1',
73+
opts.l1AccountName,
74+
opts.l1AccountPassword,
75+
)
76+
77+
const l2UnlockProvider = () =>
78+
getSecureAccountsProvider(
79+
hre.accounts,
80+
hre.config.networks,
81+
l2ChainId,
82+
hre.network.name,
83+
!isHHL1,
84+
'L2',
85+
opts.l2AccountName,
86+
opts.l2AccountPassword,
87+
)
88+
5189
const addressBookPath = getAddressBookPath(hre, opts)
5290
const { l1GraphConfigPath, l2GraphConfigPath } = getGraphConfigPaths(
5391
hre,
@@ -73,8 +111,10 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => {
73111
addressBookPath,
74112
isHHL1,
75113
enableTxLogging,
114+
secureAccounts,
76115
l1GetWallets,
77116
l1GetWallet,
117+
l1UnlockProvider,
78118
)
79119

80120
const l2Graph: GraphNetworkEnvironment | null = buildGraphNetworkEnvironment(
@@ -84,8 +124,10 @@ extendEnvironment((hre: HardhatRuntimeEnvironment) => {
84124
addressBookPath,
85125
isHHL1,
86126
enableTxLogging,
127+
secureAccounts,
87128
l2GetWallets,
88129
l2GetWallet,
130+
l2UnlockProvider,
89131
)
90132

91133
const gre: GraphRuntimeEnvironment = {
@@ -108,8 +150,10 @@ function buildGraphNetworkEnvironment(
108150
addressBookPath: string,
109151
isHHL1: boolean,
110152
enableTxLogging: boolean,
153+
secureAccounts: boolean,
111154
getWallets: () => Promise<Wallet[]>,
112155
getWallet: (address: string) => Promise<Wallet>,
156+
unlockProvider: () => Promise<EthersProviderWrapper | undefined>,
113157
): GraphNetworkEnvironment | null {
114158
if (graphConfigPath === undefined) {
115159
logWarn(
@@ -127,6 +171,9 @@ function buildGraphNetworkEnvironment(
127171
return null
128172
}
129173

174+
// Upgrade provider to secure accounts if feature is enabled
175+
const getUpdatedProvider = async () => (secureAccounts ? await unlockProvider() : provider)
176+
130177
return {
131178
chainId: chainId,
132179
provider: provider,
@@ -135,10 +182,14 @@ function buildGraphNetworkEnvironment(
135182
contracts: lazyObject(() =>
136183
loadContracts(getAddressBook(addressBookPath, chainId.toString()), provider, enableTxLogging),
137184
),
138-
getDeployer: lazyFunction(() => () => getDeployer(provider)),
139-
getNamedAccounts: lazyFunction(() => () => getNamedAccounts(provider, graphConfigPath)),
140-
getTestAccounts: lazyFunction(() => () => getTestAccounts(provider, graphConfigPath)),
141185
getWallets: lazyFunction(() => () => getWallets()),
142186
getWallet: lazyFunction(() => (address: string) => getWallet(address)),
187+
getDeployer: lazyFunction(() => async () => getDeployer(await getUpdatedProvider())),
188+
getNamedAccounts: lazyFunction(
189+
() => async () => getNamedAccounts(await getUpdatedProvider(), graphConfigPath),
190+
),
191+
getTestAccounts: lazyFunction(
192+
() => async () => getTestAccounts(await getUpdatedProvider(), graphConfigPath),
193+
),
143194
}
144195
}

0 commit comments

Comments
 (0)