-
Notifications
You must be signed in to change notification settings - Fork 161
feat(horizon): deploy horizon with Hardhat Ignition #1025
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
030106e
fa65cc4
eb5bfa3
bf02979
e2e3bda
84c3eab
4a8cc59
5b7c80a
3f13548
98f2fd4
b4a5fe4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. |
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 {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
{ | ||
"GraphProxyAdmin": { | ||
"governor": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0" | ||
}, | ||
"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" | ||
} | ||
} |
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 } | ||
}) |
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], | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 } | ||||||
}) |
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' }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this hashing is a custom pattern, is it? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 } | ||
}) |
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' }) | ||
|
||
|
||
return { HorizonStakingProxy, HorizonStakingImplementation, HorizonStaking } | ||
}) |
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 } | ||
}) |
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 } | ||
}) |
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 } | ||
}) |
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, | ||
} | ||
}) |
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, | ||
} | ||
}) |
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 } | ||
}) |
There was a problem hiding this comment.
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:
to this same json file, and then during execution we first do a lookup by module, and if not present in
$global
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes exactly!