Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions packages/horizon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

Graph Horizon is the next evolution of the Graph Protocol.

## Configuration

The following environment variables might be required:

- `ETHERSCAN_API_KEY`: Etherscan API key

You can set them using Hardhat:

```bash
npx hardhat vars set ETHERSCAN_API_KEY
```

## Deployment

We use Hardhat Ignition to deploy the contracts. To build and deploy Graph Horizon run the following commands:
Expand Down
7 changes: 7 additions & 0 deletions packages/horizon/contracts/mocks/imports.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

// We import these here to force Hardhat to compile them.
// This ensures that their artifacts are available for Hardhat Ignition to use.
pragma solidity 0.8.27;

import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
15 changes: 9 additions & 6 deletions packages/horizon/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { vars } from 'hardhat/config'
import type { HardhatUserConfig } from 'hardhat/config'

// Hardhat plugins
import '@nomicfoundation/hardhat-foundry'
import '@nomicfoundation/hardhat-toolbox'
import '@nomicfoundation/hardhat-ignition-ethers'
import '@tenderly/hardhat-tenderly'
import 'hardhat-storage-layout'
import 'hardhat-contract-sizer'
import 'hardhat-secure-accounts'
import * as dotenv from 'dotenv'

import type { HardhatUserConfig } from 'hardhat/config'

dotenv.config()
// Environment variables
const ETHERSCAN_API_KEY = vars.get('ETHERSCAN_API_KEY', '')
const ARBITRUM_VIRTUAL_TESTNET_URL = vars.get('ARBITRUM_VIRTUAL_TESTNET_URL', '')

const getNetworkAccounts = () => {
const accounts: string[] = []
Expand Down Expand Up @@ -57,7 +60,7 @@ const config: HardhatUserConfig = {
enabled: false,
},
chainId: 421615,
url: process.env.ARBITRUM_VIRTUAL_TESTNET_URL || '',
url: ARBITRUM_VIRTUAL_TESTNET_URL,
accounts: getNetworkAccounts(),
},
},
Expand All @@ -74,7 +77,7 @@ const config: HardhatUserConfig = {
},
etherscan: {
apiKey: {
arbitrumSepolia: process.env.ETHERSCAN_API_KEY ?? '',
arbitrumSepolia: ETHERSCAN_API_KEY,
},
customChains: [
{
Expand Down
4 changes: 2 additions & 2 deletions packages/horizon/ignition/configs/horizon.hardhat.json5
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
"EpochManager": {
"epochLength": 60
},
"Curation": {
"L2Curation": {
"curationTaxPercentage": 10000,
"minimumCurationDeposit": 1
},
"GraphToken": {
"L2GraphToken": {
"initialSupply": "10000000000000000000000000000n"
},
"HorizonStaking": {
Expand Down
6 changes: 3 additions & 3 deletions packages/horizon/ignition/modules/core/GraphPayments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import GraphPaymentsArtifact from '../../../build/contracts/contracts/payments/G

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

const protocolPaymentCut = m.getParameter('protocolPaymentCut')

Expand All @@ -17,7 +17,7 @@ export default buildModule('GraphPayments', (m) => {
GraphPaymentsArtifact,
[Controller, protocolPaymentCut],
{
after: [PeripheryRegistered, HorizonRegistered],
after: [GraphPeripheryModule, HorizonProxiesModule],
},
)

Expand Down
28 changes: 8 additions & 20 deletions packages/horizon/ignition/modules/core/HorizonProxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,26 @@ import { ethers } from 'ethers'
import GraphPeripheryModule from '../periphery'
import GraphProxyAdminModule from '../periphery/GraphProxyAdmin'

import DummyArtifact from '../../../build/contracts/contracts/mocks/Dummy.sol/Dummy.json'
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 { Controller } = m.useModule(GraphPeripheryModule)
const { GraphProxyAdmin } = m.useModule(GraphProxyAdminModule)

const isMigrate = m.getParameter('isMigrate', false)

// Deploy HorizonStaking proxy without an implementation
let HorizonStakingProxy, setProxyHorizonStaking
let HorizonStakingProxy
if (isMigrate) {
const horizonStakingProxyAddress = m.getParameter('horizonStakingProxyAddress')
HorizonStakingProxy = m.contractAt('GraphProxy', GraphProxyArtifact, horizonStakingProxyAddress, { id: 'GraphProxy_HorizonStaking' })
setProxyHorizonStaking = HorizonStakingProxy
} else {
HorizonStakingProxy = m.contract('GraphProxy', GraphProxyArtifact, [ZERO_ADDRESS, GraphProxyAdmin], { after: [PeripheryRegistered], id: 'GraphProxy_HorizonStaking' })
setProxyHorizonStaking = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('Staking')), HorizonStakingProxy], { id: 'setContractProxy_HorizonStaking' })
HorizonStakingProxy = m.contract('GraphProxy', GraphProxyArtifact, [ZERO_ADDRESS, GraphProxyAdmin], { id: 'GraphProxy_HorizonStaking' })
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('Staking')), HorizonStakingProxy], { id: 'setContractProxy_HorizonStaking' })
}

// Deploy proxies for payments contracts using OZ TransparentUpgradeableProxy
Expand All @@ -36,18 +34,8 @@ export default buildModule('HorizonProxies', (m) => {
// Register the proxies in the controller
// if isMigrate then use from: governor
const options = isMigrate ? { from: m.getAccount(1) } : {}
const setProxyGraphPayments = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphPayments')), GraphPaymentsProxy], { ...options, id: 'setContractProxy_GraphPayments' })
const setProxyPaymentsEscrow = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('PaymentsEscrow')), PaymentsEscrowProxy], { ...options, id: 'setContractProxy_PaymentsEscrow' })

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

return { HorizonStakingProxy, GraphPaymentsProxy, PaymentsEscrowProxy, HorizonRegistered, GraphPaymentsProxyAdmin, PaymentsEscrowProxyAdmin }
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphPayments')), GraphPaymentsProxy], { ...options, id: 'setContractProxy_GraphPayments' })
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('PaymentsEscrow')), PaymentsEscrowProxy], { ...options, id: 'setContractProxy_PaymentsEscrow' })

return { HorizonStakingProxy, GraphPaymentsProxy, PaymentsEscrowProxy, GraphPaymentsProxyAdmin, PaymentsEscrowProxyAdmin }
})
6 changes: 3 additions & 3 deletions packages/horizon/ignition/modules/core/HorizonStaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import HorizonStakingExtensionModule from './HorizonStakingExtension'
import HorizonStakingArtifact from '../../../build/contracts/contracts/staking/HorizonStaking.sol/HorizonStaking.json'

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

const subgraphServiceAddress = m.getParameter('subgraphServiceAddress')
Expand All @@ -22,7 +22,7 @@ export default buildModule('HorizonStaking', (m) => {
subgraphServiceAddress,
],
{
after: [PeripheryRegistered, HorizonRegistered],
after: [GraphPeripheryModule, HorizonProxiesModule],
},
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import ExponentialRebatesArtifact from '../../../build/contracts/contracts/staki
import HorizonStakingExtensionArtifact from '../../../build/contracts/contracts/staking/HorizonStakingExtension.sol/HorizonStakingExtension.json'

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

const subgraphServiceAddress = m.getParameter('subgraphServiceAddress')

Expand All @@ -19,7 +18,7 @@ export default buildModule('HorizonStakingExtension', (m) => {
libraries: {
ExponentialRebates: ExponentialRebates,
},
after: [PeripheryRegistered, HorizonRegistered],
after: [GraphPeripheryModule, HorizonProxiesModule],
})

return { HorizonStakingExtension }
Expand Down
6 changes: 3 additions & 3 deletions packages/horizon/ignition/modules/core/PaymentsEscrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import PaymentsEscrowArtifact from '../../../build/contracts/contracts/payments/

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

const withdrawEscrowThawingPeriod = m.getParameter('withdrawEscrowThawingPeriod')

Expand All @@ -17,7 +17,7 @@ export default buildModule('PaymentsEscrow', (m) => {
PaymentsEscrowArtifact,
[Controller, withdrawEscrowThawingPeriod],
{
after: [PeripheryRegistered, HorizonRegistered],
after: [GraphPeripheryModule, HorizonProxiesModule],
},
)

Expand Down
10 changes: 7 additions & 3 deletions packages/horizon/ignition/modules/core/TAPCollector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import HorizonProxiesModule from './HorizonProxies'
import TAPCollectorArtifact from '../../../build/contracts/contracts/payments/collectors/TAPCollector.sol/TAPCollector.json'

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

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

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

return { TAPCollector }
})
31 changes: 7 additions & 24 deletions packages/horizon/ignition/modules/periphery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import GraphTokenGatewayModule from './periphery/GraphTokenGateway'
import GraphTokenModule from './periphery/GraphToken'
import RewardsManagerModule from './periphery/RewardsManager'

import DummyArtifact from '../../build/contracts/contracts/mocks/Dummy.sol/Dummy.json'

export default buildModule('GraphHorizon_Periphery', (m) => {
const { BridgeEscrow } = m.useModule(BridgeEscrowModule)
const { Controller } = m.useModule(ControllerModule)
Expand All @@ -25,34 +23,20 @@ export default buildModule('GraphHorizon_Periphery', (m) => {

const isMigrate = m.getParameter('isMigrate', false)

let PeripheryRegistered
if (!isMigrate) {
// 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' })
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('EpochManager')), EpochManager], { id: 'setContractProxy_EpochManager' })
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('RewardsManager')), RewardsManager], { id: 'setContractProxy_RewardsManager' })
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphToken')), GraphToken], { id: 'setContractProxy_GraphToken' })
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
PeripheryRegistered = m.contract('Dummy', DummyArtifact, [], {
after: [
setProxyEpochManager,
setProxyRewardsManager,
setProxyGraphToken,
setProxyGraphTokenGateway,
setProxyGraphProxyAdmin,
setProxyCuration,
],
})
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphProxyAdmin')), GraphProxyAdmin], { id: 'setContractProxy_GraphProxyAdmin' })
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('Curation')), Curation], { id: 'setContractProxy_Curation' })
} else {
// TODO: Remove if not needed
const governor = m.getAccount(1)
// eslint-disable-next-line no-secrets/no-secrets
const setProxyGraphProxyAdmin = m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphProxyAdmin')), GraphProxyAdmin], { id: 'setContractProxy_GraphProxyAdmin', from: governor })
PeripheryRegistered = m.contract('Dummy', DummyArtifact, [], { after: [setProxyGraphProxyAdmin] })
m.call(Controller, 'setContractProxy', [ethers.keccak256(ethers.toUtf8Bytes('GraphProxyAdmin')), GraphProxyAdmin], { id: 'setContractProxy_GraphProxyAdmin', from: governor })
}

return {
Expand All @@ -64,6 +48,5 @@ export default buildModule('GraphHorizon_Periphery', (m) => {
GraphToken,
GraphTokenGateway,
RewardsManager,
PeripheryRegistered,
}
})
6 changes: 3 additions & 3 deletions packages/horizon/ignition/modules/periphery/Curation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import CurationArtifact from '@graphprotocol/contracts/build/contracts/contracts
import GraphCurationTokenArtifact from '@graphprotocol/contracts/build/contracts/contracts/curation/GraphCurationToken.sol/GraphCurationToken.json'
import GraphProxyArtifact from '@graphprotocol/contracts/build/contracts/contracts/upgrades/GraphProxy.sol/GraphProxy.json'

export default buildModule('Curation', (m) => {
export default buildModule('L2Curation', (m) => {
const isMigrate = m.getParameter('isMigrate')

if (isMigrate) {
Expand All @@ -25,7 +25,7 @@ function upgradeCuration(m: IgnitionModuleBuilder) {

const GraphProxy = m.contractAt('GraphProxy', GraphProxyArtifact, graphCurationProxyAddress)
const { instance: Curation, implementation: CurationImplementation } = upgradeWithGraphProxy(m, {
name: 'Curation',
name: 'L2Curation',
artifact: CurationArtifact,
proxyContract: GraphProxy,
}, { from: governor })
Expand All @@ -43,7 +43,7 @@ function deployCuration(m: IgnitionModuleBuilder) {
const GraphCurationToken = m.contract('GraphCurationToken', GraphCurationTokenArtifact, [])

const { instance: Curation, implementation: CurationImplementation } = deployWithGraphProxy(m, {
name: 'Curation',
name: 'L2Curation',
artifact: CurationArtifact,
args: [Controller, GraphCurationToken, curationTaxPercentage, minimumCurationDeposit],
})
Expand Down
4 changes: 2 additions & 2 deletions packages/horizon/ignition/modules/periphery/GraphToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import RewardsManagerModule from '../periphery/RewardsManager'
import GraphTokenArtifact from '@graphprotocol/contracts/build/contracts/contracts/l2/token/L2GraphToken.sol/L2GraphToken.json'

// TODO: Ownership transfer is a two step process, the new owner needs to accept it by calling acceptOwnership
export default buildModule('GraphToken', (m) => {
export default buildModule('L2GraphToken', (m) => {
const isMigrate = m.getParameter('isMigrate', false)

let GraphToken
Expand All @@ -23,7 +23,7 @@ export default buildModule('GraphToken', (m) => {
const initialSupply = m.getParameter('initialSupply')

GraphToken = deployWithGraphProxy(m, {
name: 'GraphToken',
name: 'L2GraphToken',
artifact: GraphTokenArtifact,
args: [deployer],
}).instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { deployWithGraphProxy } from '../proxy/GraphProxy'
import ControllerModule from '../periphery/Controller'
import GraphTokenGatewayArtifact from '@graphprotocol/contracts/build/contracts/contracts/l2/gateway/L2GraphTokenGateway.sol/L2GraphTokenGateway.json'

export default buildModule('GraphTokenGateway', (m) => {
export default buildModule('L2GraphTokenGateway', (m) => {
const isMigrate = m.getParameter('isMigrate', false)

let GraphTokenGateway
Expand All @@ -18,7 +18,7 @@ export default buildModule('GraphTokenGateway', (m) => {
const pauseGuardian = m.getParameter('pauseGuardian')

GraphTokenGateway = deployWithGraphProxy(m, {
name: 'GraphTokenGateway',
name: 'L2GraphTokenGateway',
artifact: GraphTokenGatewayArtifact,
args: [Controller],
}).instance
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { IgnitionModuleBuilder } from '@nomicfoundation/ignition-core'

import DummyArtifact from '../../../build/contracts/contracts/mocks/Dummy.sol/Dummy.json'
import ProxyAdminArtifact from '@openzeppelin/contracts/build/contracts/ProxyAdmin.json'
import TransparentUpgradeableProxyArtifact from '@openzeppelin/contracts/build/contracts/TransparentUpgradeableProxy.json'
import ProxyAdminArtifact from '../../../build/contracts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'
import TransparentUpgradeableProxyArtifact from '../../../build/contracts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'

// Deploy a TransparentUpgradeableProxy
// Note that this module uses a dummy contract as the implementation as the proxy requires a valid contract
Expand Down
Loading
Loading