Skip to content

Commit 504d85b

Browse files
committed
feat: refactor deployment tooling, use task instead of script
Signed-off-by: Tomás Migone <[email protected]>
1 parent 0df54e3 commit 504d85b

30 files changed

+392
-247
lines changed

packages/hardhat-graph-protocol/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
"main": "./dist/src/index.js",
1717
"exports": {
1818
".": {
19-
"types": "./dist/src/index.d.ts",
20-
"default": "./dist/src/index.js"
19+
"types": "./src/types.ts",
20+
"default": "./src/index.ts"
2121
},
2222
"./sdk": {
2323
"types": "./src/sdk/index.ts",
@@ -47,7 +47,9 @@
4747
"json5": "^2.2.3"
4848
},
4949
"devDependencies": {
50+
"@nomicfoundation/hardhat-ignition": "^0.15.9",
5051
"@nomicfoundation/hardhat-verify": "^2.0.12",
52+
"@nomicfoundation/ignition-core": "^0.15.9",
5153
"@types/chai": "^4.0.0",
5254
"@types/debug": "^4.1.12",
5355
"@types/mocha": "^10.0.9",

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import path from 'path'
22

33
import { getAddressBookPath } from './config'
44
import { HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider'
5+
import { lazyFunction } from 'hardhat/plugins'
56
import { logDebug } from './logger'
67

78
import { GraphHorizonAddressBook } from './sdk/deployments/horizon'
@@ -28,7 +29,7 @@ export const greExtendConfig = (config: HardhatConfig, userConfig: Readonly<Hard
2829
}
2930

3031
export const greExtendEnvironment = (hre: HardhatRuntimeEnvironment) => {
31-
hre.graph = (opts: GraphRuntimeEnvironmentOptions = { deployments: {} }) => {
32+
hre.graph = lazyFunction(() => (opts: GraphRuntimeEnvironmentOptions = { deployments: {} }) => {
3233
logDebug('*** Initializing Graph Runtime Environment (GRE) ***')
3334
logDebug(`Main network: ${hre.network.name}`)
3435
const chainId = hre.network.config.chainId
@@ -80,5 +81,5 @@ export const greExtendEnvironment = (hre: HardhatRuntimeEnvironment) => {
8081
assertGraphRuntimeEnvironment(gre)
8182
logDebug('GRE initialized successfully!')
8283
return gre
83-
}
84+
})
8485
}

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

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ export type AddressBookJson<
1313

1414
export type AddressBookEntry = {
1515
address: string
16-
proxy?: boolean
17-
implementation?: AddressBookEntry
16+
proxy?: 'graph' | 'transparent'
17+
proxyAdmin?: string
18+
implementation?: string
1819
}
1920

2021
/**
@@ -24,8 +25,9 @@ export type AddressBookEntry = {
2425
* "<CHAIN_ID>": {
2526
* "<CONTRACT_NAME>": {
2627
* "address": "<ADDRESS>",
27-
* "proxy": true, // optional
28-
* "implementation": { ... } // optional, nested contract structure
28+
* "proxy": "<graph|transparent>", // optional
29+
* "proxyAdmin": "<ADDRESS>", // optional
30+
* "implementation": "<ADDRESS>", // optional
2931
* ...
3032
* }
3133
* }
@@ -80,7 +82,7 @@ export abstract class AddressBook<
8082
// If it's empty, initialize it with an empty object
8183
const fileContents = JSON.parse(fs.readFileSync(this.file, 'utf8') || '{}')
8284
if (!fileContents[this.chainId]) {
83-
fileContents[this.chainId] = {} as Record<ContractName, AddressBookEntry>
85+
fileContents[this.chainId] = {}
8486
}
8587
this.assertAddressBookJson(fileContents)
8688
this.addressBook = fileContents
@@ -100,25 +102,29 @@ export abstract class AddressBook<
100102
* Get an entry from the address book
101103
*
102104
* @param name the name of the contract to get
105+
* @param strict if true it will throw an error if the contract is not found
103106
* @returns the address book entry for the contract
104107
* Returns an empty address book entry if the contract is not found
105108
*/
106-
getEntry(name: ContractName): { address: string } {
107-
const entry = this.addressBook[this.chainId][name]
108-
// Handle both object and string formats
109-
if (typeof entry === 'string') {
110-
return { address: entry }
109+
getEntry(name: string): AddressBookEntry {
110+
if (!this.isContractName(name)) {
111+
throw new Error(`Contract name ${name} is not a valid contract name`)
111112
}
113+
const entry = this.addressBook[this.chainId][name]
114+
this._assertAddressBookEntry(entry)
112115
return entry
113116
}
114117

115118
/**
116119
* Save an entry to the address book
117-
*
120+
* Allows partial address book entries to be saved
118121
* @param name the name of the contract to save
119122
* @param entry the address book entry for the contract
120123
*/
121-
setEntry(name: ContractName, entry: AddressBookEntry): void {
124+
setEntry(name: ContractName, entry: Partial<AddressBookEntry>): void {
125+
if (entry.address === undefined) {
126+
entry.address = '0x0000000000000000000000000000000000000000'
127+
}
122128
this._assertAddressBookEntry(entry)
123129
this.addressBook[this.chainId][name] = entry
124130
try {
@@ -162,7 +168,8 @@ export abstract class AddressBook<
162168
): ContractList<ContractName> {
163169
const contracts = {} as ContractList<ContractName>
164170
if (this.listEntries().length == 0) {
165-
throw Error('No valid contracts found in address book')
171+
logError('No valid contracts found in address book')
172+
return contracts
166173
}
167174
for (const contractName of this.listEntries()) {
168175
const artifactPath = typeof artifactsPath === 'object' && !Array.isArray(artifactsPath)
@@ -214,18 +221,17 @@ export abstract class AddressBook<
214221
// Asserts the provided object is a valid address book entry
215222
_assertAddressBookEntry(
216223
entry: unknown,
217-
): asserts entry is { address: string } {
218-
if (typeof entry === 'string') {
219-
// If it's a string, treat it as an address
220-
return
221-
}
222-
224+
): asserts entry is AddressBookEntry {
223225
assertObject(entry)
224226
if (!('address' in entry)) {
225227
throw new Error('Address book entry must have an address field')
226228
}
227-
if (typeof entry.address !== 'string') {
228-
throw new Error('Address book entry address must be a string')
229+
230+
const allowedFields = ['address', 'implementation', 'proxyAdmin', 'proxy']
231+
const entryFields = Object.keys(entry)
232+
const invalidFields = entryFields.filter(field => !allowedFields.includes(field))
233+
if (invalidFields.length > 0) {
234+
throw new Error(`Address book entry contains invalid fields: ${invalidFields.join(', ')}`)
229235
}
230236
}
231237
}

packages/hardhat-graph-protocol/src/sdk/deployments/horizon/contracts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const GraphHorizonContractNameList = [
2424
'RewardsManager',
2525
'L2GraphToken',
2626
'L2GraphTokenGateway',
27+
'L2Curation',
2728

2829
// @graphprotocol/horizon
2930
'HorizonStaking',
@@ -32,7 +33,7 @@ export const GraphHorizonContractNameList = [
3233
'GraphTallyCollector',
3334
] as const
3435

35-
const root = path.resolve(__dirname, '../../../../..') // hardhat-graph-protocol root
36+
const root = path.resolve(__dirname, '../../../..') // hardhat-graph-protocol root
3637
export const CONTRACTS_ARTIFACTS_PATH = path.resolve(root, 'node_modules', '@graphprotocol/contracts/build/contracts')
3738
export const HORIZON_ARTIFACTS_PATH = path.resolve(root, 'node_modules', '@graphprotocol/horizon/build/contracts')
3839

@@ -44,6 +45,7 @@ export const GraphHorizonArtifactsMap = {
4445
RewardsManager: CONTRACTS_ARTIFACTS_PATH,
4546
L2GraphToken: CONTRACTS_ARTIFACTS_PATH,
4647
L2GraphTokenGateway: CONTRACTS_ARTIFACTS_PATH,
48+
L2Curation: CONTRACTS_ARTIFACTS_PATH,
4749

4850
// @graphprotocol/horizon
4951
HorizonStaking: HORIZON_ARTIFACTS_PATH,

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

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,6 @@ interface SecureAccountsOptions {
1313
const ARBITRUM_ONE_RPC = vars.get('ARBITRUM_ONE_RPC', 'https://arb1.arbitrum.io/rpc')
1414
const ARBITRUM_SEPOLIA_RPC = vars.get('ARBITRUM_SEPOLIA_RPC', 'https://sepolia-rollup.arbitrum.io/rpc')
1515

16-
// Accounts
17-
const getTestnetAccounts = () => {
18-
const accounts: string[] = []
19-
if (vars.has('DEPLOYER_PRIVATE_KEY')) accounts.push(vars.get('DEPLOYER_PRIVATE_KEY'))
20-
if (vars.has('GOVERNOR_PRIVATE_KEY')) accounts.push(vars.get('GOVERNOR_PRIVATE_KEY'))
21-
return accounts
22-
}
23-
const getHardhatAccounts = () => {
24-
return {
25-
mnemonic: 'myth like bonus scare over problem client lizard pioneer submit female collect',
26-
}
27-
}
28-
2916
export const solidityUserConfig: SolidityUserConfig = {
3017
version: '0.8.27',
3118
settings: {
@@ -57,15 +44,19 @@ type BaseNetworksUserConfig = NetworksUserConfig &
5744
export const networksUserConfig: BaseNetworksUserConfig = {
5845
hardhat: {
5946
chainId: 31337,
60-
accounts: getHardhatAccounts(),
47+
accounts: {
48+
mnemonic: 'myth like bonus scare over problem client lizard pioneer submit female collect',
49+
},
6150
deployments: {
6251
horizon: require.resolve('@graphprotocol/horizon/addresses-local.json'),
6352
},
6453
},
6554
localhost: {
6655
chainId: 31337,
6756
url: 'http://localhost:8545',
68-
accounts: getTestnetAccounts(),
57+
secureAccounts: {
58+
enabled: true,
59+
},
6960
deployments: {
7061
horizon: require.resolve('@graphprotocol/horizon/addresses-local.json'),
7162
},

packages/hardhat-graph-protocol/src/sdk/ignition/ignition.ts

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ require('json5/lib/register')
55

66
import fs from 'fs'
77
import path from 'path'
8+
import { AddressBook } from '../address-book'
89

910
export function loadConfig(configPath: string, prefix: string, networkName: string): any {
1011
const configFileCandidates = [
@@ -19,7 +20,7 @@ export function loadConfig(configPath: string, prefix: string, networkName: stri
1920
)
2021
}
2122

22-
return removeNFromBigInts(require(configFile))
23+
return { config: removeNFromBigInts(require(configFile)), file: configFile }
2324
}
2425

2526
export function patchConfig(jsonData: any, patches: Record<string, any>) {
@@ -55,40 +56,62 @@ export function mergeConfigs(obj1: any, obj2: any) {
5556
return merged
5657
}
5758

58-
export function saveAddressBook(
59+
export function saveToAddressBook<ChainId extends number, ContractName extends string>(
5960
contracts: any,
6061
chainId: number | undefined,
61-
addressBook = 'addresses.json',
62-
): Record<string, Record<string, string>> {
62+
addressBook: AddressBook<ChainId, ContractName>,
63+
): AddressBook<ChainId, ContractName> {
6364
if (!chainId) {
6465
throw new Error('Chain ID is required')
6566
}
6667

67-
// Use different address book for local networks - this one can be gitignored
68-
if ([1377, 31337].includes(chainId)) {
69-
addressBook = 'addresses-local.json'
70-
}
68+
// Extract contract names and addresses
69+
for (const [ignitionContractName, contract] of Object.entries(contracts)) {
70+
// Proxy contracts
71+
if (ignitionContractName.includes('_Proxy_')) {
72+
const contractName = ignitionContractName.replace(/(Transparent_Proxy_|Graph_Proxy_)/, '') as ContractName
73+
const proxy = ignitionContractName.includes('Transparent_Proxy_') ? 'transparent' : 'graph'
74+
const entry = addressBook.getEntry(contractName)
75+
addressBook.setEntry(contractName, {
76+
...entry,
77+
address: (contract as any).target,
78+
proxy,
79+
})
80+
}
7181

72-
const output = fs.existsSync(addressBook)
73-
? JSON.parse(fs.readFileSync(addressBook, 'utf8'))
74-
: {}
82+
// Proxy admin contracts
83+
if (ignitionContractName.includes('_ProxyAdmin_')) {
84+
const contractName = ignitionContractName.replace(/(Transparent_ProxyAdmin_|Graph_ProxyAdmin_)/, '') as ContractName
85+
const proxy = ignitionContractName.includes('Transparent_ProxyAdmin_') ? 'transparent' : 'graph'
86+
const entry = addressBook.getEntry(contractName)
87+
addressBook.setEntry(contractName, {
88+
...entry,
89+
proxy,
90+
proxyAdmin: (contract as any).target,
91+
})
92+
}
7593

76-
output[chainId] = output[chainId] || {}
94+
// Implementation contracts
95+
if (ignitionContractName.startsWith('Implementation_')) {
96+
const contractName = ignitionContractName.replace('Implementation_', '') as ContractName
97+
const entry = addressBook.getEntry(contractName)
98+
addressBook.setEntry(contractName, {
99+
...entry,
100+
implementation: (contract as any).target,
101+
})
102+
}
77103

78-
// Extract contract names and addresses
79-
Object.entries(contracts).forEach(([contractName, contract]: [string, any]) => {
80-
output[chainId][contractName] = contract.target
81-
})
82-
83-
// Write to output file
84-
const outputDir = path.dirname(addressBook)
85-
if (!fs.existsSync(outputDir)) {
86-
fs.mkdirSync(outputDir, { recursive: true })
104+
// Non proxied contracts
105+
if (addressBook.isContractName(ignitionContractName)) {
106+
const entry = addressBook.getEntry(ignitionContractName)
107+
addressBook.setEntry(ignitionContractName, {
108+
...entry,
109+
address: (contract as any).target,
110+
})
111+
}
87112
}
88113

89-
fs.writeFileSync(addressBook, JSON.stringify(output, null, 2))
90-
91-
return output as Record<string, Record<string, string>>
114+
return addressBook
92115
}
93116

94117
// Ignition requires "n" suffix for bigints, but not here
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { loadConfig, mergeConfigs, patchConfig, saveAddressBook } from './ignition/ignition'
1+
import { loadConfig, mergeConfigs, patchConfig, saveToAddressBook } from './ignition/ignition'
22
import { hardhatBaseConfig } from './hardhat.base.config'
33

4-
const IgnitionHelper = { saveAddressBook, loadConfig, patchConfig, mergeConfigs }
4+
const IgnitionHelper = { saveToAddressBook, loadConfig, patchConfig, mergeConfigs }
55
export { hardhatBaseConfig, IgnitionHelper }

packages/horizon/hardhat.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import 'hardhat-storage-layout'
88
import 'hardhat-contract-sizer'
99
import 'hardhat-secure-accounts'
1010

11+
import './tasks/deploy'
12+
1113
// Skip importing hardhat-graph-protocol when building the project, it has circular dependency
1214
if (process.env.BUILD_RUN !== 'true') {
1315
require('hardhat-graph-protocol')

packages/horizon/ignition/modules/core/GraphPayments.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export default buildModule('GraphPayments', (m) => {
3333

3434
m.call(GraphPaymentsProxyAdmin, 'transferOwnership', [governor], { after: [GraphPayments] })
3535

36-
return { GraphPayments, GraphPaymentsProxyAdmin }
36+
return { GraphPayments, GraphPaymentsProxyAdmin, GraphPaymentsImplementation }
3737
})
3838

3939
// Note that this module requires MigrateHorizonProxiesGovernorModule to be executed first
@@ -66,5 +66,5 @@ export const MigrateGraphPaymentsModule = buildModule('GraphPayments', (m) => {
6666

6767
m.call(GraphPaymentsProxyAdmin, 'transferOwnership', [governor], { after: [GraphPayments] })
6868

69-
return { GraphPayments, GraphPaymentsProxyAdmin }
69+
return { GraphPayments, GraphPaymentsProxyAdmin, GraphPaymentsImplementation }
7070
})

packages/horizon/ignition/modules/core/HorizonStaking.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default buildModule('HorizonStaking', (m) => {
4343
})
4444
m.call(HorizonStaking, 'setMaxThawingPeriod', [maxThawingPeriod])
4545

46-
return { HorizonStaking }
46+
return { HorizonStaking, HorizonStakingImplementation }
4747
})
4848

4949
// Note that this module requires MigrateHorizonProxiesGovernorModule to be executed first
@@ -95,5 +95,5 @@ export const MigrateHorizonStakingGovernorModule = buildModule('HorizonStakingGo
9595
})
9696
m.call(HorizonStaking, 'setMaxThawingPeriod', [maxThawingPeriod])
9797

98-
return { HorizonStaking }
98+
return { HorizonStaking, HorizonStakingImplementation }
9999
})

0 commit comments

Comments
 (0)