Skip to content

Commit 42b43b6

Browse files
committed
feat: gre now loads all horizon contracts
Signed-off-by: Tomás Migone <[email protected]>
1 parent 4111600 commit 42b43b6

File tree

15 files changed

+254
-217
lines changed

15 files changed

+254
-217
lines changed

packages/hardhat-graph-protocol/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
],
1414
"author": "Tomás Migone <[email protected]>",
1515
"license": "MIT",
16-
"main": "dist/index.js",
17-
"types": "dist/index.d.ts",
16+
"main": "dist/src/index.js",
17+
"types": "dist/src/index.d.ts",
1818
"scripts": {
1919
"build": "tsc",
2020
"lint": "eslint '**/*.{js,ts}' --fix",

packages/hardhat-graph-protocol/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function getAddressBookPath(
1717
const networkPath = getPath(hre.network.config.deployments?.[deployment])
1818
const globalPath = getPath(hre.config.graph?.deployments?.[deployment])
1919

20-
logDebug(`== ${deployment} - Getting address book path`)
20+
logDebug(`Getting address book path...`)
2121
logDebug(`Graph base dir: ${hre.config.paths.graph}`)
2222
logDebug(`1) opts: ${optsPath}`)
2323
logDebug(`2) network: ${networkPath}`)

packages/hardhat-graph-protocol/src/gre.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ export const greExtendEnvironment = (hre: HardhatRuntimeEnvironment) => {
3131
hre.graph = (opts: GraphRuntimeEnvironmentOptions = { deployments: {} }) => {
3232
logDebug('*** Initializing Graph Runtime Environment (GRE) ***')
3333
logDebug(`Main network: ${hre.network.name}`)
34+
const chainId = hre.network.config.chainId
35+
if (chainId === undefined) {
36+
throw new Error('Please define chainId in your Hardhat network configuration')
37+
}
38+
logDebug(`Chain Id: ${chainId}`)
3439

3540
const deployments = [
3641
...Object.keys(opts.deployments ?? {}),
@@ -43,16 +48,16 @@ export const greExtendEnvironment = (hre: HardhatRuntimeEnvironment) => {
4348
const provider = new HardhatEthersProvider(hre.network.provider, hre.network.name)
4449
const greDeployments: Record<string, unknown> = {}
4550
for (const deployment of deployments) {
46-
logDebug(`Initializing ${deployment} deployment...`)
51+
logDebug(`== Initializing deployment: ${deployment} ==`)
4752
const addressBookPath = getAddressBookPath(deployment, hre, opts)
4853
let addressBook
4954

5055
switch (deployment) {
5156
case 'horizon':
52-
addressBook = new GraphHorizonAddressBook(addressBookPath, hre.network.config.chainId!)
57+
addressBook = new GraphHorizonAddressBook(addressBookPath, chainId)
5358
greDeployments.horizon = {
5459
addressBook: addressBook,
55-
contracts: addressBook.loadContracts(hre.network.config.chainId!, provider),
60+
contracts: addressBook.loadContracts(provider),
5661
}
5762
break
5863
default:
@@ -63,7 +68,7 @@ export const greExtendEnvironment = (hre: HardhatRuntimeEnvironment) => {
6368
const gre = {
6469
...greDeployments,
6570
provider,
66-
chainId: async () => (await provider.getNetwork()).chainId,
71+
chainId,
6772
}
6873
assertGraphRuntimeEnvironment(gre)
6974
logDebug('GRE initialized successfully!')

packages/hardhat-graph-protocol/src/sdk/address-book.ts

Lines changed: 119 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,9 @@ import { AssertionError } from 'assert'
44
import { assertObject } from './utils/assertion'
55

66
import { ContractList, loadContract } from './deployments/lib/contract'
7-
import { logDebug, logError } from '../logger'
7+
import { logDebug, logError, logWarn } from '../logger'
88
import { Provider, Signer } from 'ethers'
99

10-
// JSON format:
11-
// {
12-
// "<CHAIN_ID>": {
13-
// "<CONTRACT_NAME>": {
14-
// "address": "<ADDRESS>",
15-
// "proxy": true,
16-
// "implementation": { ... }
17-
// ...
18-
// }
19-
// }
2010
export type AddressBookJson<
2111
ChainId extends number = number,
2212
ContractName extends string = string,
@@ -29,7 +19,21 @@ export type AddressBookEntry = {
2919
}
3020

3121
/**
32-
* An abstract class to manage the address book
22+
* An abstract class to manage an address book
23+
* The address book must be a JSON file with the following structure:
24+
* {
25+
* "<CHAIN_ID>": {
26+
* "<CONTRACT_NAME>": {
27+
* "address": "<ADDRESS>",
28+
* "proxy": true, // optional
29+
* "implementation": { ... } // optional, nested contract structure
30+
* ...
31+
* }
32+
* }
33+
* Uses generics to allow specifying a ContractName type to indicate which contracts should be loaded from the address book
34+
* Implementation should provide:
35+
* - `isContractName(name: string): name is ContractName`, a type predicate to check if a given string is a ContractName
36+
* - `loadContracts(signerOrProvider?: Signer | Provider): ContractList<ContractName>` to load contracts from the address book
3337
*/
3438
export abstract class AddressBook<
3539
ChainId extends number = number,
@@ -44,105 +48,54 @@ export abstract class AddressBook<
4448
// The raw contents of the address book file
4549
public addressBook: AddressBookJson<ChainId, ContractName>
4650

47-
public strictAssert: boolean
51+
// Contracts in the address book of type ContractName
52+
private validContracts: ContractName[] = []
53+
54+
// Contracts in the address book that are not of type ContractName, these are ignored
55+
private invalidContracts: string[] = []
56+
57+
// Type predicate to check if a given string is a ContractName
58+
abstract isContractName(name: string): name is ContractName
59+
60+
// Method to load valid contracts from the address book
61+
abstract loadContracts(signerOrProvider?: Signer | Provider): ContractList<ContractName>
4862

4963
/**
5064
* Constructor for the `AddressBook` class
5165
*
5266
* @param _file the path to the address book file
5367
* @param _chainId the chain id of the network the address book should be loaded for
68+
* @param _strictAssert
5469
*
5570
* @throws AssertionError if the target file is not a valid address book
5671
* @throws Error if the target file does not exist
5772
*/
58-
constructor(_file: string, _chainId: number, _strictAssert = false) {
59-
this.strictAssert = _strictAssert
73+
constructor(_file: string, _chainId: ChainId, _strictAssert = false) {
6074
this.file = _file
61-
if (!fs.existsSync(this.file)) throw new Error(`Address book path provided does not exist!`)
62-
63-
logDebug(`Loading address book for chainId ${_chainId} from ${this.file}`)
64-
this.assertChainId(_chainId)
6575
this.chainId = _chainId
6676

67-
// Ensure file is a valid address book
68-
this.addressBook = JSON.parse(fs.readFileSync(this.file, 'utf8') || '{}') as AddressBookJson<ChainId, ContractName>
69-
this.assertAddressBookJson(this.addressBook)
77+
logDebug(`Loading address book from ${this.file}.`)
78+
if (!fs.existsSync(this.file)) throw new Error(`Address book path provided does not exist!`)
79+
80+
// Load address book and validate its shape
81+
const fileContents = JSON.parse(fs.readFileSync(this.file, 'utf8') || '{}')
82+
this.assertAddressBookJson(fileContents)
83+
this.addressBook = fileContents
84+
this._parseAddressBook()
7085

7186
// If the address book is empty for this chain id, initialize it with an empty object
7287
if (!this.addressBook[this.chainId]) {
7388
this.addressBook[this.chainId] = {} as Record<ContractName, AddressBookEntry>
7489
}
7590
}
7691

77-
abstract isValidContractName(name: string): boolean
78-
79-
abstract loadContracts(chainId: number, signerOrProvider?: Signer | Provider): ContractList<ContractName>
80-
81-
// TODO: implement chain id validation?
82-
assertChainId(chainId: string | number): asserts chainId is ChainId {}
83-
84-
// Asserts the provided object is a valid address book
85-
// Logs warnings for unsupported chain ids or invalid contract names
86-
assertAddressBookJson(
87-
json: unknown,
88-
): asserts json is AddressBookJson<ChainId, ContractName> {
89-
this._assertAddressBookJson(json)
90-
91-
// // Validate contract names
92-
const contractList = json[this.chainId]
93-
94-
const contractNames = contractList ? Object.keys(contractList) : []
95-
for (const contract of contractNames) {
96-
if (!this.isValidContractName(contract)) {
97-
const message = `Detected invalid contract in address book: ${contract}, for chainId ${this.chainId}`
98-
if (this.strictAssert) {
99-
throw new Error(message)
100-
} else {
101-
logError(message)
102-
}
103-
}
104-
}
105-
}
106-
107-
_assertAddressBookJson(json: unknown): asserts json is AddressBookJson {
108-
assertObject(json, 'Assertion failed: address book is not an object')
109-
110-
const contractList = json[this.chainId]
111-
try {
112-
assertObject(contractList, 'Assertion failed: chain contract list is not an object')
113-
} catch (error) {
114-
if (this.strictAssert) throw error
115-
else return
116-
}
117-
118-
const contractNames = Object.keys(contractList)
119-
for (const contractName of contractNames) {
120-
this._assertAddressBookEntry(contractList[contractName])
121-
}
122-
}
123-
124-
_assertAddressBookEntry(json: unknown): asserts json is AddressBookEntry {
125-
assertObject(json)
126-
127-
try {
128-
if (typeof json.address !== 'string') throw new AssertionError({ message: 'Invalid address' })
129-
if (json.proxy && typeof json.proxy !== 'boolean')
130-
throw new AssertionError({ message: 'Invalid proxy' })
131-
if (json.implementation && typeof json.implementation !== 'object')
132-
throw new AssertionError({ message: 'Invalid implementation' })
133-
} catch (error) {
134-
if (this.strictAssert) throw error
135-
else return
136-
}
137-
}
138-
13992
/**
14093
* List entry names in the address book
14194
*
14295
* @returns a list with all the names of the entries in the address book
14396
*/
14497
listEntries(): ContractName[] {
145-
return Object.keys(this.addressBook[this.chainId]) as ContractName[]
98+
return this.validContracts
14699
}
147100

148101
/**
@@ -154,7 +107,9 @@ export abstract class AddressBook<
154107
*/
155108
getEntry(name: ContractName): AddressBookEntry {
156109
try {
157-
return this.addressBook[this.chainId][name]
110+
const entry = this.addressBook[this.chainId][name]
111+
this._assertAddressBookEntry(entry)
112+
return entry
158113
} catch (_) {
159114
// TODO: should we throw instead?
160115
return { address: '0x0000000000000000000000000000000000000000' }
@@ -179,36 +134,92 @@ export abstract class AddressBook<
179134
}
180135

181136
/**
182-
* Loads all contracts from an address book
183-
*
184-
* @param addressBook Address book to use
185-
* @param signerOrProvider Signer or provider to use
186-
* @param enableTxLogging Enable transaction logging to console and output file. Defaults to `true`
187-
* @returns the loaded contracts
188-
*/
137+
* Parse address book and separate valid and invalid contracts
138+
*/
139+
_parseAddressBook() {
140+
const contractList = this.addressBook[this.chainId]
141+
142+
const contractNames = contractList ? Object.keys(contractList) : []
143+
for (const contract of contractNames) {
144+
if (!this.isContractName(contract)) {
145+
this.invalidContracts.push(contract)
146+
} else {
147+
this.validContracts.push(contract)
148+
}
149+
}
150+
151+
if (this.invalidContracts.length > 0) {
152+
logWarn(`Detected invalid contracts in address book - these will not be loaded: ${this.invalidContracts.join(', ')}`)
153+
}
154+
}
155+
156+
/**
157+
* Loads all valid contracts from an address book
158+
*
159+
* @param addressBook Address book to use
160+
* @param signerOrProvider Signer or provider to use
161+
* @returns the loaded contracts
162+
*/
189163
_loadContracts(
190-
artifactsPath: string | string[],
164+
artifactsPath: string | string[] | Record<ContractName, string>,
191165
signerOrProvider?: Signer | Provider,
192166
): ContractList<ContractName> {
193167
const contracts = {} as ContractList<ContractName>
194168
for (const contractName of this.listEntries()) {
195-
try {
196-
const contract = loadContract(
197-
contractName,
198-
this.getEntry(contractName).address,
199-
artifactsPath,
200-
signerOrProvider,
201-
)
202-
contracts[contractName] = contract
203-
} catch (error) {
204-
if (error instanceof Error) {
205-
throw new Error(`Could not load contracts - ${error.message}`)
206-
} else {
207-
throw new Error(`Could not load contracts`)
208-
}
169+
const artifactPath = typeof artifactsPath === 'object' && !Array.isArray(artifactsPath)
170+
? artifactsPath[contractName]
171+
: artifactsPath
172+
173+
if (Array.isArray(artifactPath)
174+
? !artifactPath.some(fs.existsSync)
175+
: !fs.existsSync(artifactPath)) {
176+
logWarn(`Could not load contract ${contractName} - artifact not found`)
177+
logWarn(artifactPath)
178+
continue
209179
}
180+
logDebug(`Loading contract ${contractName}`)
181+
182+
const contract = loadContract(
183+
contractName,
184+
this.getEntry(contractName).address,
185+
artifactPath,
186+
signerOrProvider,
187+
)
188+
contracts[contractName] = contract
210189
}
211190

212191
return contracts
213192
}
193+
194+
// Asserts the provided object has the correct JSON format shape for an address book
195+
// This method can be overridden by subclasses to provide custom validation
196+
assertAddressBookJson(
197+
json: unknown,
198+
): asserts json is AddressBookJson<ChainId, ContractName> {
199+
this._assertAddressBookJson(json)
200+
}
201+
202+
// Asserts the provided object is a valid address book
203+
_assertAddressBookJson(json: unknown): asserts json is AddressBookJson {
204+
assertObject(json, 'Assertion failed: address book is not an object')
205+
206+
const contractList = json[this.chainId]
207+
assertObject(contractList, 'Assertion failed: chain contract list is not an object')
208+
209+
const contractNames = Object.keys(contractList)
210+
for (const contractName of contractNames) {
211+
this._assertAddressBookEntry(contractList[contractName])
212+
}
213+
}
214+
215+
// Asserts the provided object is a valid address book entry
216+
_assertAddressBookEntry(json: unknown): asserts json is AddressBookEntry {
217+
assertObject(json)
218+
219+
if (typeof json.address !== 'string') throw new AssertionError({ message: 'Invalid address' })
220+
if (json.proxy && typeof json.proxy !== 'boolean')
221+
throw new AssertionError({ message: 'Invalid proxy' })
222+
if (json.implementation && typeof json.implementation !== 'object')
223+
throw new AssertionError({ message: 'Invalid implementation' })
224+
}
214225
}

0 commit comments

Comments
 (0)