Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions packages/horizon/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Sample Hardhat Project
# 🌅 Graph Horizon 🌅

This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract.
Graph Horizon is the next evolution of the Graph Protocol.

Try running some of the following tasks:
## Deployment

```shell
npx hardhat help
npx hardhat test
REPORT_GAS=true npx hardhat test
npx hardhat node
npx hardhat run scripts/deploy.ts
We use Hardhat Ignition to deploy the contracts. To build and deploy the contracts run the following commands:

```bash
yarn install
yarn build
npx hardhat ignition deploy ./ignition/modules/horizon.ts \
--parameters ./ignition/configs/graph.hardhat.json \
--network hardhat
```

You can use any network defined in `hardhat.config.ts` by replacing `hardhat` with the network name.
5 changes: 5 additions & 0 deletions packages/horizon/contracts/mocks/Dummy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity 0.8.27;

contract Dummy {}
8 changes: 8 additions & 0 deletions packages/horizon/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@nomicfoundation/hardhat-foundry'
import '@nomicfoundation/hardhat-toolbox'
import '@nomicfoundation/hardhat-ignition-ethers'
import 'hardhat-storage-layout'
import 'hardhat-contract-sizer'

Expand All @@ -19,6 +20,13 @@ const config: HardhatUserConfig = {
artifacts: './build/contracts',
sources: './contracts',
},
networks: {
hardhat: {
accounts: {
mnemonic: 'myth like bonus scare over problem client lizard pioneer submit female collect',
},
},
},
}

export default config
45 changes: 45 additions & 0 deletions packages/horizon/ignition/configs/graph.hardhat.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"GraphProxyAdmin": {
"governor": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an example of a repeated value that could have been global, right?

I think we could add something like:

"$global": {
   "governor": "...."
}

to this same json file, and then during execution we first do a lookup by module, and if not present in $global.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly!

},
"Controller": {
"governor": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",
"pauseGuardian": "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC"
},
"RewardsManager": {
"subgraphAvailabilityOracle": "0xd03ea8624C8C5987235048901fB614fDcA89b117",
"issuancePerBlock": "114155251141552511415n",
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000"
},
"EpochManager": {
"epochLength": 60
},
"GraphTokenGateway": {
"pauseGuardian": "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC"
},
"Curation": {
"curationTaxPercentage": 10000,
"minimumCurationDeposit": 1
},
"GraphToken": {
"initialSupply": "10000000000000000000000000000n",
"governor": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0"
},
"HorizonStaking": {
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000"
},
"HorizonStakingExtension": {
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000"
},
"GraphPayments": {
"protocolPaymentCut": 10000
},
"PaymentsEscrow": {
"revokeCollectorThawingPeriod": 10000,
"withdrawEscrowThawingPeriod": 10000
},
"TAPCollector": {
"eip712Name": "TAPCollector",
"eip712Version": "1"
}
}
15 changes: 15 additions & 0 deletions packages/horizon/ignition/modules/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'

import GraphPaymentsModule from './core/GraphPayments'
import HorizonStakingModule from './core/HorizonStaking'
import PaymentsEscrowModule from './core/PaymentsEscrow'
import TAPCollectorModule from './core/TAPCollector'

export default buildModule('GraphHorizon_Core', (m) => {
const { HorizonStaking } = m.useModule(HorizonStakingModule)
const { GraphPayments } = m.useModule(GraphPaymentsModule)
const { PaymentsEscrow } = m.useModule(PaymentsEscrowModule)
const { TAPCollector } = m.useModule(TAPCollectorModule)

return { HorizonStaking, GraphPayments, PaymentsEscrow, TAPCollector }
})
28 changes: 28 additions & 0 deletions packages/horizon/ignition/modules/core/GraphPayments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'

import GraphPeripheryModule from '../periphery'
import HorizonProxiesModule from './HorizonProxies'

// TODO: transfer ownership of ProxyAdmin???
export default buildModule('GraphPayments', (m) => {
const { Controller, PeripheryRegistered } = m.useModule(GraphPeripheryModule)
const { GraphPaymentsProxyAdmin, GraphPaymentsProxy, HorizonRegistered } = m.useModule(HorizonProxiesModule)

const protocolPaymentCut = m.getParameter('protocolPaymentCut')

// Deploy GraphPayments implementation
const GraphPaymentsImplementation = m.contract('GraphPayments',
[Controller, protocolPaymentCut],
{
after: [PeripheryRegistered, HorizonRegistered],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be (after a small change in Ignition)

Suggested change
after: [PeripheryRegistered, HorizonRegistered],
after: [GraphPeripheryModule, HorizonProxiesModule],

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be sweet 🙏

},
)

// Upgrade proxy to implementation contract
m.call(GraphPaymentsProxyAdmin, 'upgradeAndCall', [GraphPaymentsProxy, GraphPaymentsImplementation, m.encodeFunctionCall(GraphPaymentsImplementation, 'initialize', [])])

// Load contract with implementation ABI
const GraphPayments = m.contractAt('GraphPayments', GraphPaymentsProxy, { id: 'GraphPayments_Instance' })

return { GraphPayments }
})
40 changes: 40 additions & 0 deletions packages/horizon/ignition/modules/core/HorizonProxies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'
import { deployWithOZProxy } from '../proxy/TransparentUpgradeableProxy'
import { ethers } from 'ethers'

import GraphPeripheryModule from '../periphery'
import GraphProxyAdminModule from '../periphery/GraphProxyAdmin'
import GraphProxyArtifact from '@graphprotocol/contracts/build/contracts/contracts/upgrades/GraphProxy.sol/GraphProxy.json'

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'

// HorizonStaking, GraphPayments and PaymentsEscrow use GraphDirectory but they also in the directory.
// So we need to deploy their proxies, register them in the controller before being able to deploy the implementations
export default buildModule('HorizonProxies', (m) => {
const { Controller, PeripheryRegistered } = m.useModule(GraphPeripheryModule)
const { GraphProxyAdmin } = m.useModule(GraphProxyAdminModule)

// Deploy HorizonStaking proxy without an implementation
const HorizonStakingProxy = m.contract('GraphProxy', GraphProxyArtifact, [ZERO_ADDRESS, GraphProxyAdmin], { after: [PeripheryRegistered], id: 'GraphProxy_HorizonStaking' })

// Deploy proxies for payments contracts using OZ TransparentUpgradeableProxy
const { Proxy: GraphPaymentsProxy, ProxyAdmin: GraphPaymentsProxyAdmin } = deployWithOZProxy(m, 'GraphPayments')
const { Proxy: PaymentsEscrowProxy, ProxyAdmin: PaymentsEscrowProxyAdmin } = deployWithOZProxy(m, 'PaymentsEscrow')

// Register the proxies in the controller
const setProxyHorizonStaking = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('Staking')), HorizonStakingProxy], { id: 'setContractProxy_HorizonStaking' })

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this hashing is a custom pattern, is it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes!

const setProxyGraphPayments = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphPayments')), GraphPaymentsProxy], { id: 'setContractProxy_GraphPayments' })
const setProxyPaymentsEscrow = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('PaymentsEscrow')), PaymentsEscrowProxy], { id: 'setContractProxy_PaymentsEscrow' })

// Deploy dummy contract to signal that all periphery contracts are registered
const HorizonRegistered = m.contract('Dummy', [], {
id: 'RegisteredDummy',
after: [
setProxyHorizonStaking,
setProxyGraphPayments,
setProxyPaymentsEscrow,
],
})

return { HorizonStakingProxy, GraphPaymentsProxy, PaymentsEscrowProxy, HorizonRegistered, GraphPaymentsProxyAdmin, PaymentsEscrowProxyAdmin }
})
34 changes: 34 additions & 0 deletions packages/horizon/ignition/modules/core/HorizonStaking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'

import GraphPeripheryModule from '../periphery'
import HorizonProxiesModule from './HorizonProxies'
import HorizonStakingExtensionModule from './HorizonStakingExtension'

export default buildModule('HorizonStaking', (m) => {
const { Controller, GraphProxyAdmin, PeripheryRegistered } = m.useModule(GraphPeripheryModule)
const { HorizonStakingProxy, HorizonRegistered } = m.useModule(HorizonProxiesModule)
const { HorizonStakingExtension } = m.useModule(HorizonStakingExtensionModule)

const subgraphServiceAddress = m.getParameter('subgraphServiceAddress')

// Deploy HorizonStaking implementation
const HorizonStakingImplementation = m.contract('HorizonStaking',
[
Controller,
HorizonStakingExtension,
subgraphServiceAddress,
],
{
after: [PeripheryRegistered, HorizonRegistered],
},
)

// Upgrade proxy to implementation contract
const upgradeCall = m.call(GraphProxyAdmin, 'upgrade', [HorizonStakingProxy, HorizonStakingImplementation])
const acceptCall = m.call(GraphProxyAdmin, 'acceptProxy', [HorizonStakingImplementation, HorizonStakingProxy], { after: [upgradeCall] })

// Load contract with implementation ABI
const HorizonStaking = m.contractAt('HorizonStaking', HorizonStakingProxy, { after: [acceptCall], id: 'HorizonStaking_Instance' })

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is contractAt forcing you to use explicit ids too often? Or are you setting it here for a stylistic preference?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I guess this happens when you do "deploy implementation", "contractAt implementation". I'll think about how to improve this.

Maybe with something like what you did here: #1025 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah had to set it because of that!

Perhaps Ignition could detect this pattern:

const proxy = m.contract('Proxy')
const implementation = m.contract('ContractName')
const proxyWithImplementationABI = m.contractAt('ContractName', proxy)

And automatically add something to avoid id collision on the contractAt. But it's also not a big deal I think.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for confirming, I created an issue to keep track of this: https://github.com/NomicFoundation/hardhat-ignition/issues/814


return { HorizonStakingProxy, HorizonStakingImplementation, HorizonStaking }
})
22 changes: 22 additions & 0 deletions packages/horizon/ignition/modules/core/HorizonStakingExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'

import GraphPeripheryModule from '../periphery'
import HorizonProxiesModule from './HorizonProxies'

export default buildModule('HorizonStakingExtension', (m) => {
const { Controller, PeripheryRegistered } = m.useModule(GraphPeripheryModule)
const { HorizonRegistered } = m.useModule(HorizonProxiesModule)

const subgraphServiceAddress = m.getParameter('subgraphServiceAddress')

const ExponentialRebates = m.library('ExponentialRebates')
const HorizonStakingExtension = m.contract('HorizonStakingExtension',
[Controller, subgraphServiceAddress], {
libraries: {
ExponentialRebates: ExponentialRebates,
},
after: [PeripheryRegistered, HorizonRegistered],
})

return { HorizonStakingExtension }
})
29 changes: 29 additions & 0 deletions packages/horizon/ignition/modules/core/PaymentsEscrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'

import GraphPeripheryModule from '../periphery'
import HorizonProxiesModule from './HorizonProxies'

// TODO: transfer ownership of ProxyAdmin???
export default buildModule('PaymentsEscrow', (m) => {
const { Controller, PeripheryRegistered } = m.useModule(GraphPeripheryModule)
const { PaymentsEscrowProxyAdmin, PaymentsEscrowProxy, HorizonRegistered } = m.useModule(HorizonProxiesModule)

const revokeCollectorThawingPeriod = m.getParameter('revokeCollectorThawingPeriod')
const withdrawEscrowThawingPeriod = m.getParameter('withdrawEscrowThawingPeriod')

// Deploy PaymentsEscrow implementation
const PaymentsEscrowImplementation = m.contract('PaymentsEscrow',
[Controller, revokeCollectorThawingPeriod, withdrawEscrowThawingPeriod],
{
after: [PeripheryRegistered, HorizonRegistered],
},
)

// Upgrade proxy to implementation contract
m.call(PaymentsEscrowProxyAdmin, 'upgradeAndCall', [PaymentsEscrowProxy, PaymentsEscrowImplementation, m.encodeFunctionCall(PaymentsEscrowImplementation, 'initialize', [])])

// Load contract with implementation ABI
const PaymentsEscrow = m.contractAt('PaymentsEscrow', PaymentsEscrowProxy, { id: 'PaymentsEscrow_Instance' })

return { PaymentsEscrow }
})
16 changes: 16 additions & 0 deletions packages/horizon/ignition/modules/core/TAPCollector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'

import GraphPeripheryModule from '../periphery'
import HorizonProxiesModule from './HorizonProxies'

export default buildModule('TAPCollector', (m) => {
const { Controller, PeripheryRegistered } = m.useModule(GraphPeripheryModule)
const { HorizonRegistered } = m.useModule(HorizonProxiesModule)

const name = m.getParameter('eip712Name')
const version = m.getParameter('eip712Version')

const TAPCollector = m.contract('TAPCollector', [name, version, Controller], { after: [PeripheryRegistered, HorizonRegistered] })

return { TAPCollector }
})
31 changes: 31 additions & 0 deletions packages/horizon/ignition/modules/horizon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'

import GraphHorizonCoreModule from './core'
import GraphPeripheryModule from './periphery'

export default buildModule('GraphHorizon', (m) => {
const {
BridgeEscrow,
Controller,
EpochManager,
GraphProxyAdmin,
GraphTokenGateway,
RewardsManager,
Curation,
} = m.useModule(GraphPeripheryModule)
const { HorizonStaking, GraphPayments, PaymentsEscrow, TAPCollector } = m.useModule(GraphHorizonCoreModule)

return {
BridgeEscrow,
Controller,
Curation,
EpochManager,
GraphProxyAdmin,
GraphTokenGateway,
RewardsManager,
HorizonStaking,
GraphPayments,
PaymentsEscrow,
TAPCollector,
}
})
55 changes: 55 additions & 0 deletions packages/horizon/ignition/modules/periphery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { buildModule } from '@nomicfoundation/hardhat-ignition/modules'
import { ethers } from 'ethers'

import BridgeEscrowModule from './periphery/BridgeEscrow'
import ControllerModule from './periphery/Controller'
import CurationModule from './periphery/Curation'
import EpochManagerModule from './periphery/EpochManager'
import GraphProxyAdminModule from './periphery/GraphProxyAdmin'
import GraphTokenGatewayModule from './periphery/GraphTokenGateway'
import GraphTokenModule from './periphery/GraphToken'
import RewardsManagerModule from './periphery/RewardsManager'

export default buildModule('GraphHorizon_Periphery', (m) => {
const { BridgeEscrow } = m.useModule(BridgeEscrowModule)
const { Controller } = m.useModule(ControllerModule)
const { EpochManager } = m.useModule(EpochManagerModule)
const { GraphProxyAdmin } = m.useModule(GraphProxyAdminModule)
const { GraphTokenGateway } = m.useModule(GraphTokenGatewayModule)
const { RewardsManager } = m.useModule(RewardsManagerModule)
const { GraphToken } = m.useModule(GraphTokenModule)
const { Curation } = m.useModule(CurationModule)

// Register contracts in the Controller
const setProxyEpochManager = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('EpochManager')), EpochManager], { id: 'setContractProxy_EpochManager' })
const setProxyRewardsManager = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('RewardsManager')), RewardsManager], { id: 'setContractProxy_RewardsManager' })
const setProxyGraphToken = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphToken')), GraphToken], { id: 'setContractProxy_GraphToken' })
const setProxyGraphTokenGateway = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphTokenGateway')), GraphTokenGateway], { id: 'setContractProxy_GraphTokenGateway' })
// eslint-disable-next-line no-secrets/no-secrets
const setProxyGraphProxyAdmin = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphProxyAdmin')), GraphProxyAdmin], { id: 'setContractProxy_GraphProxyAdmin' })
const setProxyCuration = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('Curation')), Curation], { id: 'setContractProxy_Curation' })

// Deploy dummy contract to signal that all periphery contracts are registered
const PeripheryRegistered = m.contract('Dummy', [], {
after: [
setProxyEpochManager,
setProxyRewardsManager,
setProxyGraphToken,
setProxyGraphTokenGateway,
setProxyGraphProxyAdmin,
setProxyCuration,
],
})

return {
BridgeEscrow,
Controller,
Curation,
EpochManager,
GraphProxyAdmin,
GraphToken,
GraphTokenGateway,
RewardsManager,
PeripheryRegistered,
}
})
18 changes: 18 additions & 0 deletions packages/horizon/ignition/modules/periphery/BridgeEscrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { buildModule } from '@nomicfoundation/ignition-core'

import { deployWithGraphProxy } from '../proxy/GraphProxy'

import BridgeEscrowArtifact from '@graphprotocol/contracts/build/contracts/contracts/gateway/BridgeEscrow.sol/BridgeEscrow.json'
import ControllerModule from '../periphery/Controller'

export default buildModule('BridgeEscrow', (m) => {
const { Controller } = m.useModule(ControllerModule)

const { instance: BridgeEscrow } = deployWithGraphProxy(m, {
name: 'BridgeEscrow',
artifact: BridgeEscrowArtifact,
args: [Controller],
})

return { BridgeEscrow }
})
Loading