Skip to content

Commit c75a0a4

Browse files
authored
Merge pull request #1095 from graphprotocol/tmigone/horizon-deployment-plan
2 parents 4799228 + e2ea28c commit c75a0a4

38 files changed

+673
-267
lines changed

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

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,13 @@ export abstract class AddressBook<
104104
* @returns the address book entry for the contract
105105
* Returns an empty address book entry if the contract is not found
106106
*/
107-
getEntry(name: ContractName): AddressBookEntry {
108-
try {
109-
const entry = this.addressBook[this.chainId][name]
110-
this._assertAddressBookEntry(entry)
111-
return entry
112-
} catch (_) {
113-
// TODO: should we throw instead?
114-
return { address: '0x0000000000000000000000000000000000000000' }
107+
getEntry(name: ContractName): { address: string } {
108+
const entry = this.addressBook[this.chainId][name]
109+
// Handle both object and string formats
110+
if (typeof entry === 'string') {
111+
return { address: entry }
115112
}
113+
return entry
116114
}
117115

118116
/**
@@ -215,13 +213,20 @@ export abstract class AddressBook<
215213
}
216214

217215
// Asserts the provided object is a valid address book entry
218-
_assertAddressBookEntry(json: unknown): asserts json is AddressBookEntry {
219-
assertObject(json)
220-
221-
if (typeof json.address !== 'string') throw new AssertionError({ message: 'Invalid address' })
222-
if (json.proxy && typeof json.proxy !== 'boolean')
223-
throw new AssertionError({ message: 'Invalid proxy' })
224-
if (json.implementation && typeof json.implementation !== 'object')
225-
throw new AssertionError({ message: 'Invalid implementation' })
216+
_assertAddressBookEntry(
217+
entry: unknown,
218+
): asserts entry is { address: string } {
219+
if (typeof entry === 'string') {
220+
// If it's a string, treat it as an address
221+
return
222+
}
223+
224+
assertObject(entry)
225+
if (!('address' in entry)) {
226+
throw new Error('Address book entry must have an address field')
227+
}
228+
if (typeof entry.address !== 'string') {
229+
throw new Error('Address book entry address must be a string')
230+
}
226231
}
227232
}

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ import type { EtherscanConfig } from '@nomicfoundation/hardhat-verify/types'
55

66
// This next import ensures secure accounts config is correctly typed
77
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8-
import type { SecureAccountsOptions } from 'hardhat-secure-accounts/src/type-extensions'
8+
import 'hardhat-secure-accounts'
99

1010
// Environment variables
1111
const ARBISCAN_API_KEY = vars.get('ARBISCAN_API_KEY', undefined)
1212

1313
// RPCs
1414
const VIRTUAL_ARBITRUM_SEPOLIA_RPC = vars.has('VIRTUAL_ARBITRUM_SEPOLIA_RPC') ? vars.get('VIRTUAL_ARBITRUM_SEPOLIA_RPC') : undefined
1515
const ARBITRUM_SEPOLIA_RPC = vars.get('ARBITRUM_SEPOLIA_RPC', 'https://sepolia-rollup.arbitrum.io/rpc')
16+
const VIRTUAL_ARBITRUM_ONE_RPC = vars.has('VIRTUAL_ARBITRUM_ONE_RPC') ? vars.get('VIRTUAL_ARBITRUM_ONE_RPC') : undefined
17+
18+
// Tenderly API Key
19+
const TENDERLY_API_KEY = vars.has('TENDERLY_API_KEY') ? vars.get('TENDERLY_API_KEY') : undefined
1620

1721
// Accounts
1822
const DEPLOYER_PRIVATE_KEY = vars.get('DEPLOYER_PRIVATE_KEY', undefined)
@@ -47,13 +51,33 @@ export const projectPathsUserConfig: ProjectPathsUserConfig = {
4751
export const etherscanUserConfig: Partial<EtherscanConfig> = {
4852
apiKey: {
4953
arbitrumSepolia: ARBISCAN_API_KEY,
54+
...(TENDERLY_API_KEY && {
55+
virtualArbitrumSepolia: TENDERLY_API_KEY,
56+
virtualArbitrumOne: TENDERLY_API_KEY,
57+
}),
5058
},
5159
customChains: [
5260
{
5361
network: 'arbitrumSepolia',
5462
chainId: 421614,
5563
urls: { apiURL: 'https://api-sepolia.arbiscan.io/api', browserURL: 'https://sepolia.arbiscan.io/' },
5664
},
65+
{
66+
network: 'virtualArbitrumSepolia',
67+
chainId: 421615,
68+
urls: {
69+
apiURL: VIRTUAL_ARBITRUM_SEPOLIA_RPC + '/verify/etherscan',
70+
browserURL: VIRTUAL_ARBITRUM_SEPOLIA_RPC || 'https://sepolia.arbiscan.io/',
71+
},
72+
},
73+
{
74+
network: 'virtualArbitrumOne',
75+
chainId: 42162,
76+
urls: {
77+
apiURL: VIRTUAL_ARBITRUM_ONE_RPC + '/verify/etherscan',
78+
browserURL: VIRTUAL_ARBITRUM_SEPOLIA_RPC || 'https://arbiscan.io/',
79+
},
80+
},
5781
],
5882
}
5983

@@ -85,6 +109,13 @@ export const networksUserConfig: NetworksUserConfig = {
85109
accounts: getTestnetAccounts(),
86110
},
87111
}),
112+
...(VIRTUAL_ARBITRUM_ONE_RPC && {
113+
virtualArbitrumOne: {
114+
chainId: 42162,
115+
url: VIRTUAL_ARBITRUM_ONE_RPC,
116+
accounts: getTestnetAccounts(),
117+
},
118+
}),
88119
}
89120

90121
export const hardhatBaseConfig: HardhatUserConfig & { etherscan: Partial<EtherscanConfig> } = {
@@ -96,9 +127,7 @@ export const hardhatBaseConfig: HardhatUserConfig & { etherscan: Partial<Ethersc
96127
networks: networksUserConfig,
97128
graph: {
98129
deployments: {
99-
horizon: {
100-
addressBook: 'addresses.json',
101-
},
130+
horizon: require.resolve('@graphprotocol/horizon/addresses.json'),
102131
},
103132
},
104133
etherscan: etherscanUserConfig,

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable no-prototype-builtins */
2+
/* eslint-disable @typescript-eslint/no-unsafe-return */
13
/* eslint-disable @typescript-eslint/no-explicit-any */
24
require('json5/lib/register')
35

@@ -20,6 +22,39 @@ export function loadConfig(configPath: string, prefix: string, networkName: stri
2022
return removeNFromBigInts(require(configFile))
2123
}
2224

25+
export function patchConfig(jsonData: any, patches: Record<string, any>) {
26+
function recursivePatch(obj: any) {
27+
if (typeof obj === 'object' && obj !== null) {
28+
for (const key in obj) {
29+
if (key in patches) {
30+
obj[key] = patches[key]
31+
} else {
32+
recursivePatch(obj[key])
33+
}
34+
}
35+
}
36+
}
37+
38+
recursivePatch(jsonData)
39+
return jsonData
40+
}
41+
42+
export function mergeConfigs(obj1: any, obj2: any) {
43+
const merged = { ...obj1 }
44+
45+
for (const key in obj2) {
46+
if (obj2.hasOwnProperty(key)) {
47+
if (typeof obj2[key] === 'object' && obj2[key] !== null && obj1[key]) {
48+
merged[key] = mergeConfigs(obj1[key], obj2[key])
49+
} else {
50+
merged[key] = obj2[key]
51+
}
52+
}
53+
}
54+
55+
return merged
56+
}
57+
2358
export function saveAddressBook(
2459
contracts: any,
2560
chainId: number | undefined,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { loadConfig, saveAddressBook } from './ignition/ignition'
1+
import { loadConfig, mergeConfigs, patchConfig, saveAddressBook } from './ignition/ignition'
22
import { hardhatBaseConfig } from './hardhat.base.config'
33

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

packages/horizon/addresses.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

packages/horizon/hardhat.config.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1+
import { HardhatUserConfig } from 'hardhat/config'
12
import { hardhatBaseConfig } from 'hardhat-graph-protocol/sdk'
23

34
// Hardhat plugins
45
import '@nomicfoundation/hardhat-foundry'
56
import '@nomicfoundation/hardhat-toolbox'
67
import '@nomicfoundation/hardhat-ignition-ethers'
8+
import "@tenderly/hardhat-tenderly"
79
import 'hardhat-storage-layout'
810
import 'hardhat-contract-sizer'
9-
import 'hardhat-secure-accounts'
1011

1112
if (process.env.BUILD_RUN !== 'true') {
1213
require('hardhat-graph-protocol')
1314
}
1415

15-
export default hardhatBaseConfig
16+
const config: HardhatUserConfig = {
17+
...hardhatBaseConfig,
18+
tenderly: {
19+
project: "graph-network",
20+
username: "graphprotocol",
21+
}
22+
}
23+
24+
export default config
Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
11
{
22
"$global": {
3+
// Accounts
4+
"governor": "0x72ee30d43Fb5A90B3FE983156C5d2fBE6F6d07B3",
5+
36
// Addresses for contracts deployed in the original Graph Protocol
47
"graphProxyAdminAddress": "0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C",
58
"controllerAddress": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695",
69
"horizonStakingAddress": "0x865365C425f3A593Ffe698D9c4E6707D14d51e08",
710
"epochManagerAddress": "0x88b3C7f37253bAA1A9b95feAd69bD5320585826D",
811
"graphTokenAddress": "0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04",
912
"graphTokenGatewayAddress": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb",
13+
"rewardsManagerAddress": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79",
14+
"curationAddress": "0xDe761f075200E75485F4358978FB4d1dC8644FD5",
1015

11-
// Placeholder address for a standalone Horizon deployment, see README.md for more details
12-
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000"
13-
},
14-
"RewardsManager": {
15-
"rewardsManagerAddress": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79"
16-
},
17-
"L2Curation": {
18-
"curationAddress": "0xDe761f075200E75485F4358978FB4d1dC8644FD5"
19-
},
20-
"HorizonStaking": {
16+
// Must be set for step 4 of the migration
17+
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000",
18+
19+
// Parameters
2120
"maxThawingPeriod": 2419200
2221
},
23-
"GraphPayments": {
22+
"GraphPayments": {
2423
"protocolPaymentCut": 10000
2524
},
2625
"PaymentsEscrow": {
@@ -30,5 +29,22 @@
3029
"eip712Name": "TAPCollector",
3130
"eip712Version": "1",
3231
"revokeSignerThawingPeriod": 10000
32+
},
33+
"HorizonProxiesGovernor": {
34+
// These addresses must be set for step 2 of the migration
35+
"graphPaymentsAddress": "0x0000000000000000000000000000000000000000",
36+
"paymentsEscrowAddress": "0x0000000000000000000000000000000000000000"
37+
},
38+
"HorizonStakingGovernor": {
39+
// Must be set for step 4 of the migration
40+
"horizonStakingImplementationAddress": "0x0000000000000000000000000000000000000000"
41+
},
42+
"L2CurationGovernor": {
43+
// Must be set for step 4 of the migration
44+
"curationImplementationAddress": "0x0000000000000000000000000000000000000000"
45+
},
46+
"RewardsManagerGovernor": {
47+
// Must be set for step 4 of the migration
48+
"rewardsManagerImplementationAddress": "0x0000000000000000000000000000000000000000"
3349
}
3450
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"$global": {
3+
// Accounts
4+
"governor": "0x72ee30d43Fb5A90B3FE983156C5d2fBE6F6d07B3",
5+
6+
// Addresses for contracts deployed in the original Graph Protocol
7+
"graphProxyAdminAddress": "0x2983936aC20202a6555993448E0d5654AC8Ca5fd",
8+
"controllerAddress": "0x0a8491544221dd212964fbb96487467291b2C97e",
9+
"horizonStakingAddress": "0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF03",
10+
"epochManagerAddress": "0x5A843145c43d328B9bB7a4401d94918f131bB281",
11+
"graphTokenAddress": "0x9623063377AD1B27544C965cCd7342f7EA7e88C7",
12+
"graphTokenGatewayAddress": "0x65E1a5e8946e7E87d9774f5288f41c30a99fD302",
13+
"rewardsManagerAddress": "0x971B9d3d0Ae3ECa029CAB5eA1fB0F72c85e6a525",
14+
"curationAddress": "0x22d78fb4bc72e191C765807f8891B5e1785C8014",
15+
16+
// Must be set for step 4 of the migration
17+
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000",
18+
19+
// Parameters
20+
"maxThawingPeriod": 2419200
21+
},
22+
"GraphPayments": {
23+
"protocolPaymentCut": 10000
24+
},
25+
"PaymentsEscrow": {
26+
"withdrawEscrowThawingPeriod": 10000
27+
},
28+
"TAPCollector": {
29+
"eip712Name": "TAPCollector",
30+
"eip712Version": "1",
31+
"revokeSignerThawingPeriod": 10000
32+
},
33+
"HorizonProxiesGovernor": {
34+
// These addresses must be set for step 2 of the migration
35+
"graphPaymentsAddress": "0x0000000000000000000000000000000000000000",
36+
"paymentsEscrowAddress": "0x0000000000000000000000000000000000000000"
37+
},
38+
"HorizonStakingGovernor": {
39+
// Must be set for step 4 of the migration
40+
"horizonStakingImplementationAddress": "0x0000000000000000000000000000000000000000"
41+
},
42+
"L2CurationGovernor": {
43+
// Must be set for step 4 of the migration
44+
"curationImplementationAddress": "0x0000000000000000000000000000000000000000"
45+
},
46+
"RewardsManagerGovernor": {
47+
// Must be set for step 4 of the migration
48+
"rewardsManagerImplementationAddress": "0x0000000000000000000000000000000000000000"
49+
}
50+
}

packages/horizon/ignition/configs/horizon.default.json5

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"$global": {
33
"pauseGuardian": "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC",
4+
45
// Placeholder address for a standalone Horizon deployment, see README.md for more details
56
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000"
67
},

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'
22
import { deployImplementation } from '../proxy/implementation'
3-
import { upgradeTransparentUpgradeableProxyNoLoad } from '../proxy/TransparentUpgradeableProxy'
3+
import { upgradeTransparentUpgradeableProxy } from '../proxy/TransparentUpgradeableProxy'
44

55
import GraphPeripheryModule, { MigratePeripheryModule } from '../periphery/periphery'
6-
import HorizonProxiesModule, { MigrateHorizonProxiesModule } from './HorizonProxies'
6+
import HorizonProxiesModule, { MigrateHorizonProxiesDeployerModule } from './HorizonProxies'
77

88
import GraphPaymentsArtifact from '../../../build/contracts/contracts/payments/GraphPayments.sol/GraphPayments.json'
99

@@ -22,7 +22,7 @@ export default buildModule('GraphPayments', (m) => {
2222
}, { after: [GraphPeripheryModule, HorizonProxiesModule] })
2323

2424
// Upgrade proxy to implementation contract
25-
const GraphPayments = upgradeTransparentUpgradeableProxyNoLoad(m,
25+
const GraphPayments = upgradeTransparentUpgradeableProxy(m,
2626
GraphPaymentsProxyAdmin,
2727
GraphPaymentsProxy,
2828
GraphPaymentsImplementation, {
@@ -36,22 +36,26 @@ export default buildModule('GraphPayments', (m) => {
3636
return { GraphPayments, GraphPaymentsProxyAdmin }
3737
})
3838

39+
// Note that this module requires MigrateHorizonProxiesGovernorModule to be executed first
40+
// The dependency is not made explicit to support the production workflow where the governor is a
41+
// multisig owned by the Graph Council.
42+
// For testnet, the dependency can be made explicit by having a parent module establish it.
3943
export const MigrateGraphPaymentsModule = buildModule('GraphPayments', (m) => {
40-
const { GraphPaymentsProxyAdmin, GraphPaymentsProxy } = m.useModule(MigrateHorizonProxiesModule)
44+
const { GraphPaymentsProxyAdmin, GraphPaymentsProxy } = m.useModule(MigrateHorizonProxiesDeployerModule)
4145
const { Controller } = m.useModule(MigratePeripheryModule)
4246

43-
const governor = m.getAccount(1)
47+
const governor = m.getParameter('governor')
4448
const protocolPaymentCut = m.getParameter('protocolPaymentCut')
4549

4650
// Deploy GraphPayments implementation
4751
const GraphPaymentsImplementation = deployImplementation(m, {
4852
name: 'GraphPayments',
4953
artifact: GraphPaymentsArtifact,
5054
constructorArgs: [Controller, protocolPaymentCut],
51-
}, { after: [MigrateHorizonProxiesModule] })
55+
})
5256

5357
// Upgrade proxy to implementation contract
54-
const GraphPayments = upgradeTransparentUpgradeableProxyNoLoad(m,
58+
const GraphPayments = upgradeTransparentUpgradeableProxy(m,
5559
GraphPaymentsProxyAdmin,
5660
GraphPaymentsProxy,
5761
GraphPaymentsImplementation, {

0 commit comments

Comments
 (0)