Skip to content

Horizon: post audit changes - no solidity! #1116

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

Open
wants to merge 62 commits into
base: horizon-oz2/l05-provision-params
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
f71ecbe
chore: afuera hardhat-storage-layout
tmigone Feb 21, 2025
38caf38
chore: silence lint ignored file warnings
tmigone Feb 21, 2025
00e181f
feat: add deployment test boilerplate and one example
tmigone Feb 21, 2025
02ec0ac
feat: add deployment tests
tmigone Feb 24, 2025
596c7c0
feat: deployment tests
tmigone Feb 25, 2025
60ed29f
chore: gitignore
tmigone Feb 25, 2025
6e59d7e
test: add tests to ensure subgraph service contracts are owned by the…
tmigone Feb 25, 2025
a296df7
chore: only run unit tests with yarn test
tmigone Apr 1, 2025
5580785
chore: add local network to hardhat base config, fix deploy script bug
tmigone Apr 1, 2025
b40dd0b
chore: add addressbooks for local network
tmigone Apr 1, 2025
b6d23f3
chore: service provider integration tests
Maikol Feb 12, 2025
5e32650
fix: import hardhat tasks after build/contracts exist
Maikol Feb 12, 2025
bdd8b9d
fix: run integration tests on CI
Maikol Feb 13, 2025
dfab1db
chore: added operator tests
Maikol Feb 19, 2025
8d6fe67
chore: add delegators integration tests
Maikol Feb 20, 2025
9a0bae7
chore: add to delegation pool integration tests
Maikol Feb 21, 2025
b1c26c4
chore: working on e2e scripts
Maikol Feb 21, 2025
a727158
chore: added pre horizon upgrade state setup scripts
Maikol Mar 10, 2025
b14100a
chore: added operator, permissionless and slasher integration tests
Maikol Mar 18, 2025
5ea9832
fix: ts-lint
Maikol Mar 18, 2025
b08f5d4
chore: cleanup
Maikol Mar 19, 2025
42cc449
fix: during transition period test fixes
Maikol Mar 19, 2025
48bbd4b
chore: rename e2e to integration and moved scripts to hh tasks
Maikol Mar 25, 2025
a1adeb2
wip: moving staking actions to sdk
Maikol Mar 25, 2025
912a10f
chore: reset yarn lock file due to shenanigans
tmigone Mar 25, 2025
420c236
chore: moving actions to SDK
Maikol Mar 26, 2025
59c78fd
fix: renamed signerIndex to accountIndex
Maikol Mar 26, 2025
a375b3a
fix: added comments, change GRT asset holder for a minting account
Maikol Mar 26, 2025
2bcd0d2
chore: added more tests for service provider during transition period
Maikol Mar 27, 2025
dea2561
chore: added more service provider and delegators tests
Maikol Mar 28, 2025
aa51cf1
chore: rebase cleanup
tmigone Apr 3, 2025
7016cf9
chore: add arbitrum sepolia addresses to horizon address book
tmigone Apr 3, 2025
a2ad15a
chore: couple fixes due to rebase
tmigone Apr 3, 2025
d41eec3
Merge pull request #1109 from graphprotocol/mde/integration-tests
tmigone Apr 3, 2025
30356b0
chore: migrate to pnpm
tmigone Apr 4, 2025
cff6e47
wip
tmigone Apr 5, 2025
7577f75
chore: migrate sdk, horizon working
tmigone Apr 8, 2025
ef1ca10
chore: make subgraph service compatible with toolshed
tmigone Apr 8, 2025
8cfe43b
chore: fix path management in toolshed
tmigone Apr 8, 2025
e5a1ed3
chore: remove deployment actions
tmigone Apr 8, 2025
8e8aae2
chore: refactor actions
tmigone Apr 9, 2025
af851a3
chore: remove unnecessary type assertion
tmigone Apr 9, 2025
27b6515
chore: move subgraph service tests to unit test folder
tmigone Apr 9, 2025
eb22d50
chore: split transition tasks
tmigone Apr 9, 2025
806a62b
chore: split horizon tasks, make integration tests idempotent
tmigone Apr 10, 2025
b7e0271
chore: use debug instead of console.log in toolshed
tmigone Apr 10, 2025
e24936c
fix: bug where deployments were loaded multiple times
tmigone Apr 10, 2025
7834589
chore: remove unnecessary assertions
tmigone Apr 10, 2025
efb75b3
chore: remove hardhat-graph-protocol sdk and refactor toolshed folders
tmigone Apr 10, 2025
52acbfa
chore: remove deps
tmigone Apr 10, 2025
82593b1
feat: add transaction logging and auto wait to toolshed deployments
tmigone Apr 10, 2025
663c3f6
chore: final touches to toolshed v1
tmigone Apr 11, 2025
4d87fb4
chore: deprecate sdk
tmigone Apr 11, 2025
23e3838
chore: remove sdk
tmigone Apr 11, 2025
fff226b
Merge pull request #1137 from graphprotocol/tmigone/pnpm
tmigone Apr 11, 2025
0f00bb1
Merge pull request #1139 from graphprotocol/tmigone/toolshed
tmigone Apr 14, 2025
051520e
chore: add addresses of deployed contracts to subgraph service
tmigone Apr 14, 2025
0f9690d
chore: remove unused env vars
tmigone Apr 14, 2025
9b05de4
feat: auto fund test accounts on local networks
tmigone Apr 15, 2025
cf9e754
fix: prevent mnemonic override for local network
tmigone Apr 15, 2025
835a820
fix: unit scaling
tmigone Apr 16, 2025
ad5e7f4
fix: type in comment
tmigone Apr 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
@@ -6,14 +6,17 @@ runs:
steps:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Enable corepack for modern yarn
- name: Enable Corepack
shell: bash
run: corepack enable
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 18
cache: 'yarn'
cache: 'pnpm'
- name: Set up pnpm via Corepack
shell: bash
run: corepack prepare pnpm@9.0.6 --activate
- name: Install dependencies
shell: bash
run: yarn --immutable
run: pnpm install --frozen-lockfile
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -22,4 +22,4 @@ jobs:
- name: Set up environment
uses: ./.github/actions/setup
- name: Build
run: yarn build || yarn build
run: pnpm build || pnpm build
4 changes: 2 additions & 2 deletions .github/workflows/ci-contracts.yml
Original file line number Diff line number Diff line change
@@ -27,11 +27,11 @@ jobs:
- name: Build
run: |
pushd packages/contracts
yarn build || yarn build
pnpm build || pnpm build
- name: Run tests
run: |
pushd packages/contracts
yarn test:coverage
pnpm test:coverage
- name: Upload coverage report
uses: codecov/codecov-action@v3
with:
4 changes: 2 additions & 2 deletions .github/workflows/ci-data-edge.yml
Original file line number Diff line number Diff line change
@@ -25,6 +25,6 @@ jobs:
- name: Build
run: |
pushd packages/data-edge
yarn build
pnpm build
- name: Run tests
run: yarn test
run: pnpm test
51 changes: 51 additions & 0 deletions .github/workflows/ci-hardhat-graph-protocol.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI - packages/toolshed

env:
CI: true

on:
push:
branches: "*"
paths:
- packages/toolshed/**
pull_request:
branches: "*"
paths:
- packages/toolshed/**
workflow_dispatch:

jobs:
test-ci:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up environment
uses: ./.github/actions/setup
- name: Build contracts
run: |
pushd packages/contracts
pnpm build
popd
- name: Build horizon
run: |
pushd packages/horizon
pnpm build
popd
- name: Build subgraph service
run: |
pushd packages/subgraph-service
pnpm build
popd
- name: Build toolshed
run: |
pushd packages/toolshed
pnpm build
popd
- name: Build hardhat-graph-protocol
run: |
pushd packages/hardhat-graph-protocol
pnpm build
popd
15 changes: 10 additions & 5 deletions .github/workflows/ci-horizon.yml
Original file line number Diff line number Diff line change
@@ -27,24 +27,29 @@ jobs:
- name: Build contracts
run: |
pushd packages/contracts
yarn build
pnpm build
popd
- name: Build horizon
run: |
pushd packages/horizon
yarn build
pnpm build
popd
- name: Build subgraph service
run: |
pushd packages/subgraph-service
yarn build
pnpm build
popd
- name: Build toolshed
run: |
pushd packages/toolshed
pnpm build
popd
- name: Build hardhat-graph-protocol
run: |
pushd packages/hardhat-graph-protocol
yarn build
pnpm build
popd
- name: Run tests
run: |
pushd packages/horizon
yarn test
pnpm test
15 changes: 10 additions & 5 deletions .github/workflows/ci-subgraph-service.yml
Original file line number Diff line number Diff line change
@@ -27,24 +27,29 @@ jobs:
- name: Build contracts
run: |
pushd packages/contracts
yarn build
pnpm build
popd
- name: Build horizon
run: |
pushd packages/horizon
yarn build
pnpm build
popd
- name: Build subgraph service
run: |
pushd packages/subgraph-service
yarn build
pnpm build
popd
- name: Build toolshed
run: |
pushd packages/toolshed
pnpm build
popd
- name: Build hardhat-graph-protocol
run: |
pushd packages/hardhat-graph-protocol
yarn build
pnpm build
popd
- name: Run tests
run: |
pushd packages/subgraph-service
yarn test
pnpm test
4 changes: 2 additions & 2 deletions .github/workflows/ci-token-dist.yml
Original file line number Diff line number Diff line change
@@ -28,8 +28,8 @@ jobs:
- name: Build
run: |
pushd packages/token-distribution
yarn build
pnpm build
- name: Run tests
run: |
pushd packages/token-distribution
yarn test
pnpm test
46 changes: 46 additions & 0 deletions .github/workflows/ci-toolshed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI - packages/toolshed

env:
CI: true

on:
push:
branches: "*"
paths:
- packages/toolshed/**
pull_request:
branches: "*"
paths:
- packages/toolshed/**
workflow_dispatch:

jobs:
test-ci:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up environment
uses: ./.github/actions/setup
- name: Build contracts
run: |
pushd packages/contracts
pnpm build
popd
- name: Build horizon
run: |
pushd packages/horizon
pnpm build
popd
- name: Build subgraph service
run: |
pushd packages/subgraph-service
pnpm build
popd
- name: Build toolshed
run: |
pushd packages/toolshed
pnpm build
popd
10 changes: 5 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -8,8 +8,8 @@ on:
required: true
type: choice
options:
- contracts
- sdk
- contracts
- sdk
tag:
description: 'Tag to publish'
required: true
@@ -27,10 +27,10 @@ jobs:
submodules: recursive
- name: Set up environment
uses: ./.github/actions/setup
- name: Set npm token for publishing
run: pnpm config set //registry.npmjs.org/:_authToken ${{ secrets.GRAPHPROTOCOL_NPM_TOKEN }}
- name: Publish 🚀
shell: bash
run: |
pushd packages/${{ inputs.package }}
yarn npm publish --tag ${{ inputs.tag }} --access public
env:
YARN_NPM_AUTH_TOKEN: ${{ secrets.GRAPHPROTOCOL_NPM_TOKEN }}
pnpm publish --tag ${{ inputs.tag }} --access public --no-git-checks
6 changes: 3 additions & 3 deletions .github/workflows/verifydeployed.yml
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ jobs:
- name: Build
run: |
pushd packages/contracts
yarn build || yarn build
pnpm build || pnpm build

- name: Save build artifacts
uses: actions/upload-artifact@v3
@@ -55,14 +55,14 @@ jobs:
- name: Build
run: |
pushd packages/contracts
yarn build || yarn build
pnpm build || pnpm build
- name: Get build artifacts
uses: actions/download-artifact@v3
with:
name: contract-artifacts

- name: Verify contracts on Defender
run: cd packages/contracts && yarn hardhat --network ${{ inputs.network }} verify-defender ${{ inputs.contracts }}
run: cd packages/contracts && pnpm hardhat --network ${{ inputs.network }} verify-defender ${{ inputs.contracts }}
env:
DEFENDER_API_KEY: "${{ secrets.DEFENDER_API_KEY }}"
DEFENDER_API_SECRET: "${{ secrets.DEFENDER_API_SECRET }}"
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Logs
yarn-debug.log*
yarn-error.log*
node.log

# Dependency directories
node_modules/
@@ -45,7 +46,9 @@ localNetwork.json
arbitrum-addresses-local.json
tx-*.log
addresses-fork.json

addresses-hardhat.json
addresses-localhost.json
addresses-local-network.json
# Keys
.keystore

2 changes: 0 additions & 2 deletions .yarnrc.yml

This file was deleted.

20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@

## Packages

This repository is a Yarn workspaces monorepo containing the following packages:
This repository is a pnpm workspaces monorepo containing the following packages:

| Package | Latest version | Description |
| --- | --- | --- |
@@ -46,26 +46,22 @@ This repository is a Yarn workspaces monorepo containing the following packages:
## Development

### Setup
To set up this project you'll need [git](https://git-scm.com) and [yarn](https://yarnpkg.com/) installed. Note that Yarn v4 is required to install the dependencies and build the project.
To set up this project you'll need [git](https://git-scm.com) and [pnpm](https://pnpm.io/) installed.

From your command line:

```bash
# Enable Yarn v4
corepack enable
yarn set version stable

# Clone this repository
$ git clone https://github.com/graphprotocol/contracts

# Go into the repository
$ cd contracts

# Install dependencies
$ yarn
$ pnpm install

# Build projects
$ yarn build
$ pnpm build
```

### Versioning and publishing packages
@@ -77,7 +73,7 @@ We use [changesets](https://github.com/changesets/changesets) to manage package
A changeset is a file that describes the changes that have been made to the packages in the repository. To create a changeset, run the following command from the root of the repository:

```bash
$ yarn changeset
$ pnpm changeset
```

Changeset files are stored in the `.changeset` directory until they are packaged into a release. You can commit these files and even merge them into your main branch without publishing a release.
@@ -87,7 +83,7 @@ Changeset files are stored in the `.changeset` directory until they are packaged
When you are ready to create a new package release, run the following command to package all changesets, this will also bump package versions and dependencies:

```bash
$ yarn changeset version
$ pnpm changeset version
```

### Step 3: Tagging the release
@@ -97,7 +93,7 @@ __Note__: this step is meant to be run on the main branch.
After creating a package release, you will need to tag the release commit with the version number. To do this, run the following command from the root of the repository:

```bash
$ yarn changeset tag
$ pnpm changeset tag
$ git push --follow-tags
```

@@ -109,7 +105,7 @@ Packages are published and distributed via NPM. To publish a package, run the fo

```bash
# Publish the package
$ yarn npm publish --access public --tag <tag>
$ pnpm npm publish --access public --tag <tag>
```

Alternatively, there is a GitHub action that can be manually triggered to publish a package.
22 changes: 4 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -5,26 +5,12 @@
"license": "GPL-2.0-or-later",
"repository": "git@github.com:graphprotocol/contracts.git",
"author": "The Graph team",
"packageManager": "yarn@4.0.2",
"workspaces": [
"packages/contracts",
"packages/data-edge",
"packages/eslint-graph-config",
"packages/hardhat-graph-protocol",
"packages/horizon",
"packages/sdk",
"packages/solhint-graph-config",
"packages/solhint-plugin-graph",
"packages/subgraph-service",
"packages/token-distribution"
],
"packageManager": "pnpm@9.0.6+sha1.648f6014eb363abb36618f2ba59282a9eeb3e879",
"scripts": {
"postinstall": "husky install",
"clean": "yarn workspaces foreach --all --parallel --verbose run clean",
"clean:all": "yarn clean && rm -rf node_modules packages/*/node_modules",
"build": "chmod +x ./scripts/build && ./scripts/build",
"lint": "yarn workspaces foreach --all --parallel --verbose run lint",
"test": "yarn workspaces foreach --all --parallel --verbose --interlaced run test"
"clean": "pnpm -r --parallel --reporter=append-only run clean",
"clean:all": "pnpm clean && rm -rf node_modules packages/*/node_modules",
"build": "chmod +x ./scripts/build && ./scripts/build"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
16 changes: 8 additions & 8 deletions packages/contracts/DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ cli/cli.ts --help
For convenience, the script can also be used as a hardhat command with `hardhat migrate` and it can be also run with:

```
yarn deploy
pnpm deploy
```

The **migrate** command will:
@@ -25,22 +25,22 @@ The **migrate** command will:
The script accepts multiple parameters that allow to override default values, print the available options with:

```
yarn deploy -- --help
pnpm deploy -- --help
```

NOTE: Please run `yarn build` at least once before running migrate as this command relies on artifacts produced in the compilation process.
NOTE: Please run `pnpm build` at least once before running migrate as this command relies on artifacts produced in the compilation process.

### Networks

By default, `yarn deploy` will deploy the contracts to a localhost instance of a development network.
By default, `pnpm deploy` will deploy the contracts to a localhost instance of a development network.

To deploy to a different network execute:

```
yarn deploy -- --network {networkName}
pnpm deploy -- --network {networkName}
# Example
yarn deploy -- --network goerli
pnpm deploy -- --network goerli
```

The network must be configured in the `hardhat.config.ts` as explained in https://hardhat.org/config.
@@ -54,7 +54,7 @@ A configuration file called `graph.<networkName>.yml` located in the `config` fo
You can use a different set of configuration options by specifying the file location in the command line:

```
yarn deploy -- --graph-config another-graph.mainnet.yml
pnpm deploy -- --graph-config another-graph.mainnet.yml
```

Rules:
@@ -100,7 +100,7 @@ Some contracts require the address from previously deployed contracts. For that
### Deploying a new testnet

1. Make sure contracts are up to date as you please.
2. `yarn deploy-goerli` to deploy to Goerli. This will create new contracts with new addresses in `addresses.json`.
2. `pnpm deploy-goerli` to deploy to Goerli. This will create new contracts with new addresses in `addresses.json`.
3. Update the `package.json` and `package-lock.json` files with the new package version and publish a new npm package with `npm publish`. You can dry-run the files to be uploaded by running `npm publish --dry-run`.
4. Merge this update into master, branch off and save for whatever version of the testnet is going on, and then tag this on the github repo, pointing to your branch (ex. at `testnet-phase-1` branch). This way we can always get the contract code for testnet, while continuing to do work on mainnet.
5. Pull the updated package into the subgraph, and other apps that depend on the package.json.
6 changes: 3 additions & 3 deletions packages/contracts/README.md
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@ The contracts are upgradable, following the [Open Zeppelin Proxy Upgrade Pattern
The [NPM package](https://www.npmjs.com/package/@graphprotocol/contracts) contains contract interfaces and addresses for the testnet and mainnet. It also contains [typechain](https://github.com/ethereum-ts/TypeChain) generated objects to easily interact with the contracts. This allows for anyone to install the package in their repository and interact with the protocol. It is updated and released whenever a change to the contracts occurs.

```
yarn add @graphprotocol/contracts
pnpm add @graphprotocol/contracts
```

# Contract Addresses
@@ -68,8 +68,8 @@ The testnet runs on Goerli, while mainnet is on Ethereum Mainnet. The addresses
To setup the contracts locally, checkout the `dev` branch, then run:

```bash
yarn
yarn build
pnpm
pnpm build
```

# Testing
6 changes: 3 additions & 3 deletions packages/contracts/TESTING.md
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ Testing is done with the following stack:

## Unit testing

To test all the smart contracts, use `yarn test`.
To test all the smart contracts, use `pnpm test`.
To test a single file run: `npx hardhat test test/<FILE_NAME>.ts`

## E2E Testing
@@ -37,7 +37,7 @@ There are several types of e2e tests which can be run separately:
It can be useful to run E2E tests against a fresh protocol deployment on L1, this can be done with the following:

```bash
L1_NETWORK=localhost yarn test:e2e
L1_NETWORK=localhost pnpm test:e2e
```

The command will:
@@ -51,7 +51,7 @@ The command will:
If you want to test the protocol on an L1/L2 setup, you can run:

```bash
L1_NETWORK=localnitrol1 L2_NETWORK=localnitrol2 yarn test:e2e
L1_NETWORK=localnitrol1 L2_NETWORK=localnitrol2 pnpm test:e2e
```

In this case the command will:
2 changes: 1 addition & 1 deletion packages/contracts/contracts/base/IMulticall.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;
pragma solidity ^0.7.6 || 0.8.27;
pragma abicoder v2;

/**
2 changes: 1 addition & 1 deletion packages/contracts/contracts/l2/curation/IL2Curation.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;
pragma solidity ^0.7.6 || 0.8.27;

/**
* @title Interface of the L2 Curation contract.
2 changes: 1 addition & 1 deletion packages/contracts/contracts/l2/discovery/IL2GNS.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6;
pragma solidity ^0.7.6 || 0.8.27;

import { ICallhookReceiver } from "../../gateway/ICallhookReceiver.sol";

3 changes: 2 additions & 1 deletion packages/contracts/contracts/l2/staking/L2Staking.sol
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import { Staking } from "../../staking/Staking.sol";
import { IL2StakingBase } from "./IL2StakingBase.sol";
import { IL2Staking } from "./IL2Staking.sol";
import { Stakes } from "../../staking/libs/Stakes.sol";
import { IStakes } from "../../staking/libs/IStakes.sol";
import { IL2StakingTypes } from "./IL2StakingTypes.sol";

/**
@@ -18,7 +19,7 @@ import { IL2StakingTypes } from "./IL2StakingTypes.sol";
*/
contract L2Staking is Staking, IL2StakingBase {
using SafeMath for uint256;
using Stakes for Stakes.Indexer;
using Stakes for IStakes.Indexer;

/// @dev Minimum amount of tokens that can be delegated
uint256 private constant MINIMUM_DELEGATION = 1e18;
2 changes: 1 addition & 1 deletion packages/contracts/contracts/staking/IStaking.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity >=0.6.12 <0.8.0;
pragma solidity >=0.6.12 <0.8.0 || 0.8.27;
pragma abicoder v2;

import { IStakingBase } from "./IStakingBase.sol";
2 changes: 1 addition & 1 deletion packages/contracts/contracts/staking/IStakingBase.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity >=0.6.12 <0.8.0;
pragma solidity >=0.6.12 <0.8.0 || 0.8.27;
pragma abicoder v2;

import { IStakingData } from "./IStakingData.sol";
2 changes: 1 addition & 1 deletion packages/contracts/contracts/staking/IStakingData.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity >=0.6.12 <0.8.0;
pragma solidity >=0.6.12 <0.8.0 || 0.8.27;

/**
* @title Staking Data interface
10 changes: 5 additions & 5 deletions packages/contracts/contracts/staking/IStakingExtension.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity >=0.6.12 <0.8.0;
pragma solidity >=0.6.12 <0.8.0 || 0.8.27;
pragma abicoder v2;

import { IStakingData } from "./IStakingData.sol";
import { Stakes } from "./libs/Stakes.sol";
import { IStakes } from "./libs/IStakes.sol";

/**
* @title Interface for the StakingExtension contract
@@ -276,11 +276,11 @@ interface IStakingExtension is IStakingData {

/**
* @notice Getter for stakes[_indexer]:
* gets the stake information for an indexer as a Stakes.Indexer struct.
* gets the stake information for an indexer as a IStakes.Indexer struct.
* @param _indexer Indexer address for which to query the stake information
* @return Stake information for the specified indexer, as a Stakes.Indexer struct
* @return Stake information for the specified indexer, as a IStakes.Indexer struct
*/
function stakes(address _indexer) external view returns (Stakes.Indexer memory);
function stakes(address _indexer) external view returns (IStakes.Indexer memory);

/**
* @notice Getter for allocations[_allocationID]:
5 changes: 3 additions & 2 deletions packages/contracts/contracts/staking/L1Staking.sol
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { ITokenGateway } from "../arbitrum/ITokenGateway.sol";
import { Staking } from "./Staking.sol";
import { Stakes } from "./libs/Stakes.sol";
import { IStakes } from "./libs/IStakes.sol";
import { IStakingData } from "./IStakingData.sol";
import { L1StakingV1Storage } from "./L1StakingStorage.sol";
import { IGraphToken } from "../token/IGraphToken.sol";
@@ -22,7 +23,7 @@ import { IL2StakingTypes } from "../l2/staking/IL2StakingTypes.sol";
* to send an indexer's stake to L2, and to send delegation to L2 as well.
*/
contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase {
using Stakes for Stakes.Indexer;
using Stakes for IStakes.Indexer;
using SafeMath for uint256;

/**
@@ -231,7 +232,7 @@ contract L1Staking is Staking, L1StakingV1Storage, IL1StakingBase {
uint256 _maxSubmissionCost,
uint256 _ethAmount
) internal {
Stakes.Indexer storage indexerStake = __stakes[_indexer];
IStakes.Indexer storage indexerStake = __stakes[_indexer];
require(indexerStake.tokensStaked != 0, "tokensStaked == 0");
// Indexers shouldn't be trying to withdraw tokens before transferring to L2.
// Allowing this would complicate our accounting so we require that they have no
7 changes: 4 additions & 3 deletions packages/contracts/contracts/staking/Staking.sol
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import { IStakingBase } from "./IStakingBase.sol";
import { StakingV4Storage } from "./StakingStorage.sol";
import { MathUtils } from "./libs/MathUtils.sol";
import { Stakes } from "./libs/Stakes.sol";
import { IStakes } from "./libs/IStakes.sol";
import { Managed } from "../governance/Managed.sol";
import { ICuration } from "../curation/ICuration.sol";
import { IRewardsManager } from "../rewards/IRewardsManager.sol";
@@ -32,7 +33,7 @@ import { LibExponential } from "./libs/Exponential.sol";
*/
abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, Multicall {
using SafeMath for uint256;
using Stakes for Stakes.Indexer;
using Stakes for IStakes.Indexer;

/// @dev 100% in parts per million
uint32 internal constant MAX_PPM = 1000000;
@@ -245,7 +246,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M
*/
function unstake(uint256 _tokens) external override notPartialPaused {
address indexer = msg.sender;
Stakes.Indexer storage indexerStake = __stakes[indexer];
IStakes.Indexer storage indexerStake = __stakes[indexer];

require(indexerStake.tokensStaked > 0, "!stake");

@@ -556,7 +557,7 @@ abstract contract Staking is StakingV4Storage, GraphUpgradeable, IStakingBase, M
* @return Amount of tokens available to allocate including delegation
*/
function getIndexerCapacity(address _indexer) public view override returns (uint256) {
Stakes.Indexer memory indexerStake = __stakes[_indexer];
IStakes.Indexer memory indexerStake = __stakes[_indexer];
uint256 tokensDelegated = __delegationPools[_indexer].tokens;

uint256 tokensDelegatedCap = indexerStake.tokensSecureStake().mul(uint256(__delegationRatio));
11 changes: 6 additions & 5 deletions packages/contracts/contracts/staking/StakingExtension.sol
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import { IStakingExtension } from "./IStakingExtension.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";
import { IGraphToken } from "../token/IGraphToken.sol";
import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol";
import { IStakes } from "./libs/IStakes.sol";
import { Stakes } from "./libs/Stakes.sol";
import { IStakingData } from "./IStakingData.sol";
import { MathUtils } from "./libs/MathUtils.sol";
@@ -22,7 +23,7 @@ import { MathUtils } from "./libs/MathUtils.sol";
*/
contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtension {
using SafeMath for uint256;
using Stakes for Stakes.Indexer;
using Stakes for IStakes.Indexer;

/// @dev 100% in parts per million
uint32 private constant MAX_PPM = 1000000;
@@ -147,7 +148,7 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi
uint256 _reward,
address _beneficiary
) external override onlySlasher notPartialPaused {
Stakes.Indexer storage indexerStake = __stakes[_indexer];
IStakes.Indexer storage indexerStake = __stakes[_indexer];

// Only able to slash a non-zero number of tokens
require(_tokens > 0, "!tokens");
@@ -365,11 +366,11 @@ contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtensi

/**
* @notice Getter for stakes[_indexer]:
* gets the stake information for an indexer as a Stakes.Indexer struct.
* gets the stake information for an indexer as an IStakes.Indexer struct.
* @param _indexer Indexer address for which to query the stake information
* @return Stake information for the specified indexer, as a Stakes.Indexer struct
* @return Stake information for the specified indexer, as an IStakes.Indexer struct
*/
function stakes(address _indexer) external view override returns (Stakes.Indexer memory) {
function stakes(address _indexer) external view override returns (IStakes.Indexer memory) {
return __stakes[_indexer];
}

4 changes: 2 additions & 2 deletions packages/contracts/contracts/staking/StakingStorage.sol
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ pragma solidity ^0.7.6;
import { Managed } from "../governance/Managed.sol";

import { IStakingData } from "./IStakingData.sol";
import { Stakes } from "./libs/Stakes.sol";
import { IStakes } from "./libs/IStakes.sol";

/**
* @title StakingV1Storage
@@ -46,7 +46,7 @@ contract StakingV1Storage is Managed {
uint32 internal __alphaDenominator;

/// @dev Indexer stakes : indexer => Stake
mapping(address => Stakes.Indexer) internal __stakes;
mapping(address => IStakes.Indexer) internal __stakes;

/// @dev Allocations : allocationID => Allocation
mapping(address => IStakingData.Allocation) internal __allocations;
13 changes: 13 additions & 0 deletions packages/contracts/contracts/staking/libs/IStakes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later

pragma solidity ^0.7.6 || 0.8.27;
pragma abicoder v2;

interface IStakes {
struct Indexer {
uint256 tokensStaked; // Tokens on the indexer stake (staked by the indexer)
uint256 tokensAllocated; // Tokens used in allocations
uint256 tokensLocked; // Tokens locked for withdrawal subject to thawing period
uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn
}
}
34 changes: 14 additions & 20 deletions packages/contracts/contracts/staking/libs/Stakes.sol
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ pragma abicoder v2;
import "@openzeppelin/contracts/math/SafeMath.sol";

import "./MathUtils.sol";
import "./IStakes.sol";

/**
* @title A collection of data structures and functions to manage the Indexer Stake state.
@@ -14,21 +15,14 @@ import "./MathUtils.sol";
*/
library Stakes {
using SafeMath for uint256;
using Stakes for Stakes.Indexer;

struct Indexer {
uint256 tokensStaked; // Tokens on the indexer stake (staked by the indexer)
uint256 tokensAllocated; // Tokens used in allocations
uint256 tokensLocked; // Tokens locked for withdrawal subject to thawing period
uint256 tokensLockedUntil; // Block when locked tokens can be withdrawn
}
using Stakes for IStakes.Indexer;

/**
* @dev Deposit tokens to the indexer stake.
* @param stake Stake data
* @param _tokens Amount of tokens to deposit
*/
function deposit(Stakes.Indexer storage stake, uint256 _tokens) internal {
function deposit(IStakes.Indexer storage stake, uint256 _tokens) internal {
stake.tokensStaked = stake.tokensStaked.add(_tokens);
}

@@ -37,7 +31,7 @@ library Stakes {
* @param stake Stake data
* @param _tokens Amount of tokens to release
*/
function release(Stakes.Indexer storage stake, uint256 _tokens) internal {
function release(IStakes.Indexer storage stake, uint256 _tokens) internal {
stake.tokensStaked = stake.tokensStaked.sub(_tokens);
}

@@ -46,7 +40,7 @@ library Stakes {
* @param stake Stake data
* @param _tokens Amount of tokens to allocate
*/
function allocate(Stakes.Indexer storage stake, uint256 _tokens) internal {
function allocate(IStakes.Indexer storage stake, uint256 _tokens) internal {
stake.tokensAllocated = stake.tokensAllocated.add(_tokens);
}

@@ -55,7 +49,7 @@ library Stakes {
* @param stake Stake data
* @param _tokens Amount of tokens to unallocate
*/
function unallocate(Stakes.Indexer storage stake, uint256 _tokens) internal {
function unallocate(IStakes.Indexer storage stake, uint256 _tokens) internal {
stake.tokensAllocated = stake.tokensAllocated.sub(_tokens);
}

@@ -65,7 +59,7 @@ library Stakes {
* @param _tokens Amount of tokens to unstake
* @param _period Period in blocks that need to pass before withdrawal
*/
function lockTokens(Stakes.Indexer storage stake, uint256 _tokens, uint256 _period) internal {
function lockTokens(IStakes.Indexer storage stake, uint256 _tokens, uint256 _period) internal {
// Take into account period averaging for multiple unstake requests
uint256 lockingPeriod = _period;
if (stake.tokensLocked > 0) {
@@ -87,7 +81,7 @@ library Stakes {
* @param stake Stake data
* @param _tokens Amount of tokens to unlock
*/
function unlockTokens(Stakes.Indexer storage stake, uint256 _tokens) internal {
function unlockTokens(IStakes.Indexer storage stake, uint256 _tokens) internal {
stake.tokensLocked = stake.tokensLocked.sub(_tokens);
if (stake.tokensLocked == 0) {
stake.tokensLockedUntil = 0;
@@ -99,7 +93,7 @@ library Stakes {
* @param stake Stake data
* @return Amount of tokens being withdrawn
*/
function withdrawTokens(Stakes.Indexer storage stake) internal returns (uint256) {
function withdrawTokens(IStakes.Indexer storage stake) internal returns (uint256) {
// Calculate tokens that can be released
uint256 tokensToWithdraw = stake.tokensWithdrawable();

@@ -119,7 +113,7 @@ library Stakes {
* @param stake Stake data
* @return Token amount
*/
function tokensUsed(Stakes.Indexer memory stake) internal pure returns (uint256) {
function tokensUsed(IStakes.Indexer memory stake) internal pure returns (uint256) {
return stake.tokensAllocated.add(stake.tokensLocked);
}

@@ -130,7 +124,7 @@ library Stakes {
* @param stake Stake data
* @return Token amount
*/
function tokensSecureStake(Stakes.Indexer memory stake) internal pure returns (uint256) {
function tokensSecureStake(IStakes.Indexer memory stake) internal pure returns (uint256) {
return stake.tokensStaked.sub(stake.tokensLocked);
}

@@ -142,7 +136,7 @@ library Stakes {
* @param stake Stake data
* @return Token amount
*/
function tokensAvailable(Stakes.Indexer memory stake) internal pure returns (uint256) {
function tokensAvailable(IStakes.Indexer memory stake) internal pure returns (uint256) {
return stake.tokensAvailableWithDelegation(0);
}

@@ -155,7 +149,7 @@ library Stakes {
* @return Token amount
*/
function tokensAvailableWithDelegation(
Stakes.Indexer memory stake,
IStakes.Indexer memory stake,
uint256 _delegatedCapacity
) internal pure returns (uint256) {
uint256 tokensCapacity = stake.tokensStaked.add(_delegatedCapacity);
@@ -181,7 +175,7 @@ library Stakes {
* @param stake Stake data
* @return Token amount
*/
function tokensWithdrawable(Stakes.Indexer memory stake) internal view returns (uint256) {
function tokensWithdrawable(IStakes.Indexer memory stake) internal view returns (uint256) {
// No tokens to withdraw before locking period
if (stake.tokensLockedUntil == 0 || block.number < stake.tokensLockedUntil) {
return 0;
2 changes: 1 addition & 1 deletion packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ function loadTasks() {
if (fs.existsSync(path.join(__dirname, 'build', 'types'))) {
loadTasks()
} else if (!SKIP_LOAD) {
execSync('yarn build', { stdio: 'inherit' })
execSync('pnpm build', { stdio: 'inherit' })
loadTasks()
}

27 changes: 16 additions & 11 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -16,9 +16,13 @@
"devDependencies": {
"@arbitrum/sdk": "~3.1.13",
"@defi-wonderland/smock": "~2.3.0",
"@ethersproject/abi": "^5.8.0",
"@ethersproject/abstract-provider": "^5.8.0",
"@ethersproject/bytes": "^5.8.0",
"@ethersproject/experimental": "^5.6.0",
"@ethersproject/providers": "^5.8.0",
"@graphprotocol/common-ts": "^1.8.3",
"@graphprotocol/sdk": "workspace:^0.5.0",
"@graphprotocol/sdk": "^0.5.2",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@nomiclabs/hardhat-waffle": "2.0.3",
@@ -30,8 +34,8 @@
"@typechain/ethers-v5": "^7.0.0",
"@typechain/hardhat": "^2.0.0",
"@types/bs58": "^4.0.1",
"@types/chai": "4.3.4",
"@types/chai-as-promised": "^7.1.5",
"@types/dotenv": "^8.2.0",
"@types/glob": "^7.2.0",
"@types/inquirer": "^7.3.1",
"@types/minimist": "^1.2.1",
@@ -42,8 +46,9 @@
"@types/yargs": "^16.0.0",
"@urql/core": "^2.1.3",
"arbos-precompiles": "^1.0.2",
"bignumber.js": "^9.0.0",
"chai": "^4.3.4",
"axios": "^1.8.4",
"bignumber.js": "9.1.2",
"chai": "4.3.4",
"chai-as-promised": "^7.1.1",
"cli-table": "^0.3.6",
"console-table-printer": "^2.11.1",
@@ -85,15 +90,15 @@
"build": "SKIP_LOAD=true scripts/build",
"clean": "rm -rf build/ cache/ dist/",
"compile": "hardhat compile",
"deploy": "yarn predeploy && yarn build && hardhat migrate",
"deploy-localhost": "yarn build && hardhat migrate --force --skip-confirmation --disable-secure-accounts --network localhost --graph-config config/graph.localhost.yml --address-book addresses-local.json",
"deploy": "pnpm predeploy && pnpm build && hardhat migrate",
"deploy-localhost": "pnpm build && hardhat migrate --force --skip-confirmation --disable-secure-accounts --network localhost --graph-config config/graph.localhost.yml --address-book addresses-local.json",
"predeploy": "scripts/predeploy",
"test": "scripts/test",
"test:e2e": "scripts/e2e",
"test:gas": "RUN_EVM=true REPORT_GAS=true scripts/test",
"test:coverage": "scripts/coverage",
"test:upgrade": "scripts/upgrade",
"lint": "yarn lint:ts && yarn lint:sol",
"lint": "pnpm lint:ts && pnpm lint:sol",
"lint:ts": "eslint '**/*.{js,ts}' --fix",
"lint:sol": "prettier --write 'contracts/**/*.sol' && solhint --fix --noPrompt contracts/**/*.sol --config solhint.config.js",
"analyze": "scripts/analyze",
@@ -105,16 +110,16 @@
},
"lint-staged": {
"contracts/**/*.sol": [
"yarn lint:sol"
"pnpm lint:sol"
],
"**/*.ts": [
"yarn lint:ts"
"pnpm lint:ts"
],
"**/*.js": [
"yarn lint:ts"
"pnpm lint:ts"
],
"**/*.json": [
"yarn lint:ts"
"pnpm lint:ts"
]
},
"repository": {
2 changes: 1 addition & 1 deletion packages/contracts/scripts/analyze
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@
mkdir -p reports

pip3 install --user slither-analyzer && \
yarn build && \
pnpm build && \

echo "Analyzing contracts..."
slither . \
4 changes: 2 additions & 2 deletions packages/contracts/scripts/build
Original file line number Diff line number Diff line change
@@ -5,8 +5,8 @@ TYPECHAIN_DIR=dist/types
set -eo pipefail

# Build contracts
yarn clean
yarn compile
pnpm clean
pnpm compile
tsc

# Copy types and abis to distribution folder
2 changes: 1 addition & 1 deletion packages/contracts/scripts/coverage
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

set -eo pipefail

yarn build
pnpm build

echo {} > addresses-local.json

2 changes: 1 addition & 1 deletion packages/contracts/scripts/e2e
Original file line number Diff line number Diff line change
@@ -126,7 +126,7 @@ function test_e2e_scenarios_bridge () {
### > SCRIPT START < ###
## SETUP
# Compile contracts
yarn build
pnpm build

# Start evm
if [[ "$L1_NETWORK" == "localhost" || "$L2_NETWORK" == "localhost" ]]; then
2 changes: 1 addition & 1 deletion packages/contracts/scripts/myth
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
# - https://github.com/ConsenSys/mythril#usage

pip3 install --user mythril && \
yarn build && \
pnpm build && \
mkdir -p reports/myth

echo "Myth Analysis..."
2 changes: 1 addition & 1 deletion packages/contracts/scripts/test
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ source $(pwd)/scripts/evm
### Setup EVM

# Ensure we compiled sources
yarn build
pnpm build

### Cleanup
function cleanup() {
2 changes: 1 addition & 1 deletion packages/contracts/scripts/upgrade
Original file line number Diff line number Diff line change
@@ -55,7 +55,7 @@ echo "- Upgrade name: $UPGRADE_NAME"
## SETUP
# Compile contracts
print_separator "Building contracts"
yarn build
pnpm build

# Build fork address book with actual contract addresses from the forked chain
jq "{\"$CHAIN_ID\"} + {"\"1337\"": .\"$CHAIN_ID\"} | del(.\"$CHAIN_ID\")" $ADDRESS_BOOK > addresses-fork.json
Original file line number Diff line number Diff line change
@@ -3,5 +3,5 @@
1) Upgrade the GNS contract, add a `uint256 public test;` storage variable
2) Run the upgrade script:
```
CHAIN_ID=1 FORK_URL=<RPC_URL> CONTRACT_NAME=GNS UPGRADE_NAME=example yarn test:upgrade
CHAIN_ID=1 FORK_URL=<RPC_URL> CONTRACT_NAME=GNS UPGRADE_NAME=example pnpm test:upgrade
```
Original file line number Diff line number Diff line change
@@ -8,5 +8,5 @@ FORK_URL=https://mainnet.infura.io/v3/<INFURA_KEY> \
FORK_BLOCK_NUMBER=17324022 \
CONTRACT_NAME=L1Staking \
UPGRADE_NAME=exponential-rebates \
yarn test:upgrade
pnpm test:upgrade
```
2 changes: 1 addition & 1 deletion packages/data-edge/README.md
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ The DataEdge accepts any function call by using a fallback function that will no
# Deploying

Setup a `.env` file with the keys you want to use for deployments. You can use `.env.sample` as a guide.
Deploy a `DataEdge` contract by running `yarn deploy -- --network <network-name>`
Deploy a `DataEdge` contract by running `pnpm deploy -- --network <network-name>`

# Copyright

4 changes: 2 additions & 2 deletions packages/data-edge/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -15,8 +15,8 @@ import 'hardhat-contract-sizer'
import '@openzeppelin/hardhat-upgrades'
import '@typechain/hardhat'

import * as tdly from "@tenderly/hardhat-tenderly";
tdly.setup();
import "@tenderly/hardhat-tenderly";


// Tasks

5 changes: 3 additions & 2 deletions packages/data-edge/package.json
Original file line number Diff line number Diff line change
@@ -12,10 +12,10 @@
"test": "scripts/test",
"test:gas": "RUN_EVM=true REPORT_GAS=true scripts/test",
"test:coverage": "scripts/coverage",
"lint": "yarn lint:ts && yarn lint:sol",
"lint": "pnpm lint:ts && pnpm lint:sol",
"lint:ts": "eslint '**/*.{js,ts}' --fix",
"lint:sol": "prettier --write 'contracts/**/*.sol' && solhint --fix --noPrompt contracts/**/*.sol --config node_modules/solhint-graph-config/index.js",
"prettier": "yarn prettier:ts && yarn prettier:sol",
"prettier": "pnpm prettier:ts && pnpm prettier:sol",
"prettier:ts": "prettier --write 'test/**/*.ts'",
"prettier:sol": "prettier --write 'contracts/**/*.sol'",
"security": "scripts/security",
@@ -48,6 +48,7 @@
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "^4.5.0",
"@openzeppelin/hardhat-upgrades": "^1.8.2",
"@tenderly/api-client": "^1.0.13",
"@tenderly/hardhat-tenderly": "^1.0.13",
"@typechain/ethers-v5": "^9.0.0",
"@typechain/hardhat": "^4.0.0",
2 changes: 1 addition & 1 deletion packages/data-edge/scripts/build
Original file line number Diff line number Diff line change
@@ -3,4 +3,4 @@
set -eo pipefail

# Build
yarn compile
pnpm compile
2 changes: 1 addition & 1 deletion packages/data-edge/scripts/coverage
Original file line number Diff line number Diff line change
@@ -2,5 +2,5 @@

set -eo pipefail

yarn compile
pnpm compile
npx hardhat coverage $@
4 changes: 2 additions & 2 deletions packages/data-edge/scripts/prepublish
Original file line number Diff line number Diff line change
@@ -5,8 +5,8 @@ TYPECHAIN_DIR=dist/types
set -eo pipefail

# Build contracts
yarn clean
yarn build
pnpm clean
pnpm build

# Refresh distribution folder
rm -rf dist && mkdir -p dist/types/_src
2 changes: 1 addition & 1 deletion packages/data-edge/scripts/security
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
mkdir -p reports

pip3 install --user slither-analyzer && \
yarn build && \
pnpm build && \

echo "Analyzing contracts..."
slither . &> reports/analyzer-report.log && \
2 changes: 1 addition & 1 deletion packages/data-edge/scripts/test
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ evm_kill() {

# Ensure we compiled sources

yarn build
pnpm build

# Gas reporter needs to run in its own evm instance
if [ "$RUN_EVM" = true ]; then
4 changes: 2 additions & 2 deletions packages/eslint-graph-config/README.md
Original file line number Diff line number Diff line change
@@ -5,13 +5,13 @@ This repository contains shared linting and formatting rules for TypeScript proj
## Installation

```bash
yarn add --dev eslint@^8.56.0 eslint-graph-config
pnpm add --dev eslint@^8.56.0 eslint-graph-config
```

For projects on this monorepo, you can use the following command to install the package:

```bash
yarn add --dev eslint@^8.56.0 eslint-graph-config@workspace:^x.y.z
pnpm add --dev eslint@^8.56.0 eslint-graph-config@workspace:^x.y.z
```

To enable the rules, you need to create an `eslint.config.js` file in the root of your project with the following content:
1 change: 1 addition & 0 deletions packages/eslint-graph-config/index.ts
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ export default [
},
rules: {
'prefer-const': 'warn',
'@typescript-eslint/no-redundant-type-constituents': 'warn',
'@typescript-eslint/no-inferrable-types': 'warn',
'@typescript-eslint/no-empty-function': 'warn',
'no-only-tests/no-only-tests': 'error',
3 changes: 2 additions & 1 deletion packages/eslint-graph-config/package.json
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
"scripts": {
"clean": "rm -rf index.js",
"lint": "eslint '**/*.{js,ts}' --ignore-pattern index.js --fix",
"build": "yarn clean && tsc"
"build": "pnpm clean && tsc"
},
"dependencies": {
"@stylistic/eslint-plugin": "^1.6.2",
@@ -20,6 +20,7 @@
"devDependencies": {
"@types/eslint__js": "^8.42.3",
"@types/node": "^20.11.19",
"globals": "^16.0.0",
"typescript": "^5.3.3"
},
"peerDependencies": {
221 changes: 209 additions & 12 deletions packages/hardhat-graph-protocol/README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,229 @@
# hardhat-graph-protocol

A Hardhat plugin for integrating with The Graph Protocol, providing easy access to deployment addresses and configuration for Graph Protocol contracts.

## Usage
### Features

- **Protocol deployments** - Provides a simple interface to interact with protocol contracts without having to configure contract addresses or ABIs.
- **Transaction logging** - Transactions made via the plugin are automatically awaited and logged.
- **Accounts** - Provides account management methods for convenience, following protocol conventions for account derivation
- **Secure accounts** - Integrates seamlessly with [hardhat-secure-accounts](https://www.npmjs.com/package/hardhat-secure-accounts)

Install with yarn
## Installation

```bash
yarn add --dev hardhat-graph-protocol
# Install as a dev dependency
pnpm add --dev hardhat-graph-protocol
```

## Configuration

Add the plugin to your `hardhat.config.ts`:

# From the monorepo
yarn add --dev hardhat-graph-protocol@workspace:^x.y.z
```ts
import "hardhat-graph-protocol";
```

And add it to your `hardhat.config.ts`:
### Using @graphprotocol/toolshed
To use the plugin you'll need to configure the target networks. We recommend using our base hardhat configuration which can be imported from `@graphprotocol/toolshed`:

```ts
import { hardhatBaseConfig, networksUserConfig } from '@graphprotocol/toolshed/hardhat'
import "hardhat-graph-protocol";

export default {
const config: HardhatUserConfig = {
...networksUserConfig,
// rest of config
}

export default config // or just "export default hardhatBaseConfig"
```

### Manual configuration
To manually configure target networks:

**Hardhat: Network config**
```ts
networks: {
arbitrumOne: {
chainId: 42161,
url: `https://arbitrum-one.infura.io/v3/123456`
deployments: {
horizon: '/path/to/horizon/addresses.json,
subgraphService: 'path/to/subgraph-service/addresses.json'
}
},
}
```
**Hardhat: Graph config**

Additionally, the plugin adds a new config field to hardhat's config file: `graph`. This can be used used to define defaults for all networks:

```ts
...
networks: {
...
},
graph: {
deployments: {
horizon: require.resolve('@graphprotocol/horizon/addresses.json'),
subgraphService: require.resolve('@graphprotocol/subgraph-service/addresses.json'),
}
horizon: '/path/to/horizon/addresses.json,
subgraphService: 'path/to/subgraph-service/addresses.json'
},
},
...
```

## Usage

This plugin exposes functionality via a simple API:

```ts
const graph = hre.graph()
```

The interface for the graph object can be found [here](src/types.ts), it's expanded version lookg like this:

```ts
export type GraphRuntimeEnvironment = {
[deploymentName]: {
contracts: DeploymentContractsType,
addressBook: DeploymentAddressBookType,
actions: DeplyomentActionsType
},
provider: HardhatEthersProvider
chainId: number
accounts: {
getAccounts: () => Promise<GraphAccounts>
getDeployer: (accountIndex?: number) => Promise<HardhatEthersSigner>
getGovernor: (accountIndex?: number) => Promise<HardhatEthersSigner>
getArbitrator: (accountIndex?: number) => Promise<HardhatEthersSigner>
getPauseGuardian: (accountIndex?: number) => Promise<HardhatEthersSigner>
getSubgraphAvailabilityOracle: (accountIndex?: number) => Promise<HardhatEthersSigner>
getGateway: (accountIndex?: number) => Promise<HardhatEthersSigner>
getTestAccounts: () => Promise<HardhatEthersSigner[]>
}
}
```
### Deployments
The plugin provides one object for each configured deployment, this object allows easily interacting with the associated deployment with a few additional features. The current deployments that are supported: `horizon` and `subgraphService`.
Each deployment will be of the form:
```ts
[deploymentName]: {
contracts: DeploymentContractsType,
addressBook: DeploymentAddressBookType,
actions: DeplyomentActionsType
},
```
Where:
- `contracts`: an object with all the contracts available in the deployment, already instanced, fully typed and ready to go.
- `addressBook`: an object allowing read and write access to the deployment's address book.
- `actions`: (optional) an object with helper functions to perform common actions in the associated deployment.
**Transaction logging**
Any transactions made using the `contracts` object will be automatically logged both to the console and to a file:
- `file`, in the project's root directory: `tx-YYYY-MM-DD.log`
- `console`, not shown by default. Run with `DEBUG=toolshed:tx` to enable them.
Note that this does not apply to getter functions (`view` or `pure`) as those are not state modifying calls.
An example log output:
```
[2025-04-10T20:32:37.182Z] > Sending transaction: HorizonStaking.addToProvision
[2025-04-10T20:32:37.182Z] = Sender: 0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E
[2025-04-10T20:32:37.182Z] = Contract: 0x865365C425f3A593Ffe698D9c4E6707D14d51e08
[2025-04-10T20:32:37.182Z] = Params: [ 0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E, 0x1afb3ce06A1b3Cfb065DA4821c6Fa33b8CfC3485, 100000000000000000000 ]
[2025-04-10T20:32:37.182Z] = TxHash: 0x0e76c384a80f9f0402eb74de40c0456ef808d7afb4de68d451f5ed95b4be5c8a
[2025-04-10T20:32:37.183Z] ✔ Transaction succeeded!
[2025-04-10T20:32:40.936Z] > Sending transaction: HorizonStaking.thaw
[2025-04-10T20:32:40.936Z] = Sender: 0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E
[2025-04-10T20:32:40.936Z] = Contract: 0x865365C425f3A593Ffe698D9c4E6707D14d51e08
[2025-04-10T20:32:40.936Z] = Params: [ 0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E, 0x1afb3ce06A1b3Cfb065DA4821c6Fa33b8CfC3485, 100000000000000000000 ]
[2025-04-10T20:32:40.936Z] = TxHash: 0x5422a1e975688952e13a455498c4f652a090d619bec414662775fc9d8cbd0af6
[2025-04-10T20:32:40.946Z] ✔ Transaction succeeded!
```
**Transaction auto-awaiting**
Any transactions made using the `contracts` object will be automatically awaited:
```ts
const graph = hre.graph()

// The transaction is automatically awaited, no need to await tx.wait() it
const tx = await graph.horizon.contracts.GraphToken.approve('0xDEADBEEF', 100)

// But you can still do it if you need more confirmations for example
await tx.wait(10)
```

**Examples**
```js
const graph = hre.graph()
const { GraphPayments, HorizonStaking, GraphToken } = graph.horizon.contracts
const { provision } = graph.horizon.actions

// Print GraphPayment's protocol cut
await GraphPayments.PROTOCOL_PAYMENT_CUT()
10000n

// Provision some GRT to a data service
await GraphToken.connect(signer).approve(HorizonStaking.target, 100_000_000)
await HorizonStaking.connect(signer).stake(100_000_000)
await HorizonStaking.connect(signer).provision(signer.address, dataService.address, 100_000_000, 10_000, 42_690)

// Do the same but using actions - in this case the `provision` helper also approves and stakes
await provision(signer, [signer.address, dataService.address, 100_000_000, 10_000, 42_690])

// Read the address book
const entry = graph.horizon.addressBook.getEntry('HorizonStaking')
console.log(entry.address) // HorizonStaking proxy address
console.log(entry.implementation) // HorizonStaking implementation address
```

### Accounts

The plugin provides helper functions to derive signers from the configured accounts in hardhat config:
```ts
hardhat: {
chainId: 31337,
accounts: {
mnemonic: 'myth like bonus scare over problem client lizard pioneer submit female collect',
},
...
};
},
```

_Note_: When using the plugin from within this monorepo TypeScript fails to properly apply the type extension typings. This is a known issue and can be worked around by adding a `types/hardhat-graph-protocol.d.ts` file with the same content as the `type-extensions.ts` file in this repository.
| Function | Description | Default account derivation index |
|----------|-------------|-------------|
| `getAccounts()` | Returns all the accounts listed below | - |
| `getDeployer()` | Returns the deployer signer | 0 |
| `getGovernor()` | Returns the governor signer | 1 |
| `getArbitrator()` | Returns the arbitrator signer | 2 |
| `getPauseGuardian()` | Returns the pause guardian signer | 3 |
| `getSubgraphAvailabilityOracle()` | Returns a service provider signer | 4 |
| `getGateway()` | Returns the gateway signer | 5 |
| `getTestAccounts()` | Returns the test signers | 6-20 |

Note that these are just helper functions to enforce a convention on which index to use for each account. These might not match what is configured in the target protocol deployment.

For any of the accounts listed above these are equivalents:

```ts
const graph = hre.graph()

// These two should match
const governor = await graph.accounts.getGovernor() // By default governor uses derivation index 1
const governorFromEthers = (await hre.ethers.getSigners())[1]
```

## Development: TypeScript support

When using the plugin from within this monorepo, TypeScript may fail to properly apply the type extension typings. To work around this issue:

1. Create a file at `types/hardhat-graph-protocol.d.ts`
2. Copy the contents from the `type-extensions.ts` file in this repository into the new file

This will ensure proper TypeScript type support for the plugin.
8 changes: 1 addition & 7 deletions packages/hardhat-graph-protocol/package.json
Original file line number Diff line number Diff line change
@@ -18,10 +18,6 @@
".": {
"types": "./src/types.ts",
"default": "./src/index.ts"
},
"./sdk": {
"types": "./src/sdk/index.ts",
"default": "./src/sdk/index.ts"
}
},
"types": "./dist/src/index.d.ts",
@@ -39,9 +35,7 @@
"README.md"
],
"dependencies": {
"@graphprotocol/contracts": "workspace:^7.0.0",
"@graphprotocol/horizon": "workspace:^0.0.1",
"@graphprotocol/subgraph-service": "workspace:^0.0.1",
"@graphprotocol/toolshed": "workspace:^",
"@nomicfoundation/hardhat-ethers": "^3.0.8",
"debug": "^4.3.7",
"json5": "^2.2.3"
17 changes: 12 additions & 5 deletions packages/hardhat-graph-protocol/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import fs from 'fs'
import path from 'path'

import { GraphPluginError } from './sdk/utils/error'
import { GraphPluginError } from './error'
import { logDebug } from './logger'
import { normalizePath } from './sdk/utils/path'

import type { GraphDeployment, GraphRuntimeEnvironmentOptions } from './types'
import type { GraphDeploymentName } from '@graphprotocol/toolshed/deployments'
import type { GraphRuntimeEnvironmentOptions } from './types'
import type { HardhatRuntimeEnvironment } from 'hardhat/types'

export function getAddressBookPath(
deployment: GraphDeployment,
deployment: GraphDeploymentName,
hre: HardhatRuntimeEnvironment,
opts: GraphRuntimeEnvironmentOptions,
): string {
@@ -32,6 +32,13 @@ export function getAddressBookPath(
return normalizedAddressBookPath
}

function normalizePath(_path: string, graphPath?: string): string {
if (!path.isAbsolute(_path) && graphPath !== undefined) {
_path = path.join(graphPath, _path)
}
return _path
}

function getPath(value: string | {
addressBook: string
} | undefined): string | undefined {
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { HardhatPluginError } from 'hardhat/plugins'
import { logError } from '../../logger'
import { logError } from './logger'

export class GraphPluginError extends HardhatPluginError {
constructor(message: string) {
63 changes: 39 additions & 24 deletions packages/hardhat-graph-protocol/src/gre.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
/* eslint-disable no-case-declarations */
import path from 'path'

import {
getAccounts,
getArbitrator,
getDeployer,
getGateway,
getGovernor,
getPauseGuardian,
getSubgraphAvailabilityOracle,
getTestAccounts,
} from '@graphprotocol/toolshed'
import { loadGraphHorizon, loadSubgraphService } from '@graphprotocol/toolshed/deployments'
import { getAddressBookPath } from './config'
import { GraphPluginError } from './error'
import { HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider'
import { isGraphDeployment } from './types'
import { lazyFunction } from 'hardhat/plugins'
import { logDebug } from './logger'

import { GraphHorizonAddressBook } from './sdk/deployments/horizon'
import { SubgraphServiceAddressBook } from './sdk/deployments/subgraph-service'

import { assertGraphRuntimeEnvironment, type GraphRuntimeEnvironmentOptions, isGraphDeployment } from './types'
import type { HardhatConfig, HardhatRuntimeEnvironment, HardhatUserConfig } from 'hardhat/types'
import type { GraphDeployments } from '@graphprotocol/toolshed/deployments'
import type { GraphRuntimeEnvironmentOptions } from './types'

export const greExtendConfig = (config: HardhatConfig, userConfig: Readonly<HardhatUserConfig>) => {
const userPath = userConfig.paths?.graph
@@ -34,52 +46,55 @@ export const greExtendEnvironment = (hre: HardhatRuntimeEnvironment) => {
logDebug(`Main network: ${hre.network.name}`)
const chainId = hre.network.config.chainId
if (chainId === undefined) {
throw new Error('Please define chainId in your Hardhat network configuration')
throw new GraphPluginError('Please define chainId in your Hardhat network configuration')
}
logDebug(`Chain Id: ${chainId}`)

const deployments = [
const deployments = [...new Set([
...Object.keys(opts.deployments ?? {}),
...Object.keys(hre.network.config.deployments ?? {}),
...Object.keys(hre.config.graph?.deployments ?? {}),
].filter(v => isGraphDeployment(v))
].filter(v => isGraphDeployment(v)))]
logDebug(`Detected deployments: ${deployments.join(', ')}`)

// Build the Graph Runtime Environment (GRE) for each deployment
const provider = new HardhatEthersProvider(hre.network.provider, hre.network.name)
const greDeployments: Record<string, unknown> = {}
const greDeployments: GraphDeployments = {} as GraphDeployments

for (const deployment of deployments) {
logDebug(`== Initializing deployment: ${deployment} ==`)
const addressBookPath = getAddressBookPath(deployment, hre, opts)
let addressBook

switch (deployment) {
case 'horizon':
addressBook = new GraphHorizonAddressBook(addressBookPath, chainId)
greDeployments.horizon = {
addressBook: addressBook,
contracts: addressBook.loadContracts(provider),
}
greDeployments.horizon = loadGraphHorizon(addressBookPath, chainId, provider)
break
case 'subgraphService':
addressBook = new SubgraphServiceAddressBook(addressBookPath, chainId)
greDeployments.subgraphService = {
addressBook: addressBook,
contracts: addressBook.loadContracts(provider),
}
greDeployments.subgraphService = loadSubgraphService(addressBookPath, chainId, provider)
break
default:
break
}
}

const gre = {
// Accounts
const grtTokenAddress = greDeployments?.horizon?.contracts?.GraphToken?.target
const accounts = {
getAccounts: async () => getAccounts(provider, grtTokenAddress),
getDeployer: async (accountIndex?: number) => getDeployer(provider, accountIndex, grtTokenAddress),
getGovernor: async (accountIndex?: number) => getGovernor(provider, accountIndex, grtTokenAddress),
getArbitrator: async (accountIndex?: number) => getArbitrator(provider, accountIndex, grtTokenAddress),
getPauseGuardian: async (accountIndex?: number) => getPauseGuardian(provider, accountIndex, grtTokenAddress),
getSubgraphAvailabilityOracle: async (accountIndex?: number) => getSubgraphAvailabilityOracle(provider, accountIndex, grtTokenAddress),
getGateway: async (accountIndex?: number) => getGateway(provider, accountIndex, grtTokenAddress),
getTestAccounts: async () => getTestAccounts(provider, grtTokenAddress),
}

logDebug('GRE initialized successfully!')
return {
...greDeployments,
provider,
chainId,
accounts: accounts,
}
assertGraphRuntimeEnvironment(gre)
logDebug('GRE initialized successfully!')
return gre
})
}

This file was deleted.

This file was deleted.

5 changes: 0 additions & 5 deletions packages/hardhat-graph-protocol/src/sdk/index.ts

This file was deleted.

8 changes: 0 additions & 8 deletions packages/hardhat-graph-protocol/src/sdk/utils/abi.ts

This file was deleted.

8 changes: 0 additions & 8 deletions packages/hardhat-graph-protocol/src/sdk/utils/path.ts

This file was deleted.

11 changes: 6 additions & 5 deletions packages/hardhat-graph-protocol/src/type-extensions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// To extend one of Hardhat's types, you need to import the module where it has been defined, and redeclare it.
import 'hardhat/types/config'
import 'hardhat/types/runtime'
import type { GraphDeployments, GraphRuntimeEnvironment, GraphRuntimeEnvironmentOptions } from './types'

import type { GraphDeploymentOptions, GraphRuntimeEnvironment, GraphRuntimeEnvironmentOptions } from './types'

declare module 'hardhat/types/runtime' {
interface HardhatRuntimeEnvironment {
@@ -19,19 +20,19 @@ declare module 'hardhat/types/config' {
}

interface HardhatNetworkConfig {
deployments?: GraphDeployments
deployments?: GraphDeploymentOptions
}

interface HardhatNetworkUserConfig {
deployments?: GraphDeployments
deployments?: GraphDeploymentOptions
}

interface HttpNetworkConfig {
deployments?: GraphDeployments
deployments?: GraphDeploymentOptions
}

interface HttpNetworkUserConfig {
deployments?: GraphDeployments
deployments?: GraphDeploymentOptions
}

interface ProjectPathsConfig {
67 changes: 21 additions & 46 deletions packages/hardhat-graph-protocol/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,32 @@
import { type GraphDeploymentRuntimeEnvironmentMap, GraphDeploymentsList } from './deployment-list'
import type { HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider'

export type GraphDeployment = (typeof GraphDeploymentsList)[number]
import { GraphDeploymentsList } from '@graphprotocol/toolshed/deployments'

export type GraphDeployments = {
[deployment in GraphDeployment]?: string | {
addressBook: string
}
import type { GraphDeploymentName, GraphDeployments } from '@graphprotocol/toolshed/deployments'
import type { GraphAccounts } from '@graphprotocol/toolshed'
import type { HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider'
import type { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'
export type GraphDeploymentOptions = {
[deployment in GraphDeploymentName]?: string
}

export type GraphRuntimeEnvironmentOptions = {
deployments?: {
[deployment in GraphDeployment]?: string | {
addressBook: string
}
}
deployments?: GraphDeploymentOptions
}

export type GraphRuntimeEnvironment = {
[deployment in keyof GraphDeploymentRuntimeEnvironmentMap]?: GraphDeploymentRuntimeEnvironmentMap[deployment]
} & {
export type GraphRuntimeEnvironment = GraphDeployments & {
provider: HardhatEthersProvider
chainId: number
}

export function isGraphDeployment(deployment: unknown): deployment is GraphDeployment {
return (
typeof deployment === 'string'
&& GraphDeploymentsList.includes(deployment as GraphDeployment)
)
}

export function assertGraphRuntimeEnvironment(
obj: unknown,
): obj is GraphRuntimeEnvironment {
if (typeof obj !== 'object' || obj === null) return false

const deployments = obj as Partial<GraphDeploymentRuntimeEnvironmentMap>

for (const deployment in deployments) {
const environment = deployments[deployment as keyof GraphDeploymentRuntimeEnvironmentMap]
if (!environment || typeof environment !== 'object') {
return false
}
}

if (typeof (obj as GraphRuntimeEnvironment).provider !== 'object') {
return false
}

if (typeof (obj as GraphRuntimeEnvironment).chainId !== 'function') {
return false
accounts: {
getAccounts: () => Promise<GraphAccounts>
getDeployer: (accountIndex?: number) => Promise<HardhatEthersSigner>
getGovernor: (accountIndex?: number) => Promise<HardhatEthersSigner>
getArbitrator: (accountIndex?: number) => Promise<HardhatEthersSigner>
getPauseGuardian: (accountIndex?: number) => Promise<HardhatEthersSigner>
getSubgraphAvailabilityOracle: (accountIndex?: number) => Promise<HardhatEthersSigner>
getGateway: (accountIndex?: number) => Promise<HardhatEthersSigner>
getTestAccounts: () => Promise<HardhatEthersSigner[]>
}
}

return true
export function isGraphDeployment(deployment: unknown): deployment is GraphDeploymentName {
return typeof deployment === 'string' && GraphDeploymentsList.includes(deployment as GraphDeploymentName)
}
4 changes: 1 addition & 3 deletions packages/hardhat-graph-protocol/test/config.test.ts
Original file line number Diff line number Diff line change
@@ -22,9 +22,7 @@ describe('GRE init functions', function () {
this.hre = loadHardhatContext('network-address-book', 'mainnet')
const addressBook = getAddressBookPath('horizon', this.hre, {
deployments: {
horizon: {
addressBook: 'addresses-opt.json',
},
horizon: 'addresses-opt.json',
},
})
expect(path.basename(addressBook)).to.equal('addresses-opt.json')
Original file line number Diff line number Diff line change
@@ -42,9 +42,7 @@ const config: HardhatUserConfig = {
},
graph: {
deployments: {
horizon: {
addressBook: 'addresses-global.json',
},
horizon: 'addresses-global.json',
},
},
}
Original file line number Diff line number Diff line change
@@ -42,9 +42,7 @@ const config: HardhatUserConfig = {
},
graph: {
deployments: {
horizon: {
addressBook: 'addresses-invalid.json',
},
horizon: 'addresses-invalid.json',
},
},
}
Original file line number Diff line number Diff line change
@@ -19,9 +19,7 @@ const config: HardhatUserConfig = {
chainId: 1,
url: `https://mainnet.infura.io/v3/123456`,
deployments: {
horizon: {
addressBook: 'addresses-network.json',
},
horizon: 'addresses-network.json',
},
},
'arbitrum-one': {
@@ -47,9 +45,7 @@ const config: HardhatUserConfig = {
},
graph: {
deployments: {
horizon: {
addressBook: 'addresses-global.json',
},
horizon: 'addresses-global.json',
},
},
}
Original file line number Diff line number Diff line change
@@ -39,9 +39,7 @@ const config: HardhatUserConfig = {
},
graph: {
deployments: {
horizon: {
addressBook: 'addresses-hre.json',
},
horizon: 'addresses-hre.json',
},
},
}
Original file line number Diff line number Diff line change
@@ -49,9 +49,7 @@ const config: HardhatUserConfig = {
},
graph: {
deployments: {
horizon: {
addressBook: 'addresses-hre.json',
},
horizon: 'addresses-hre.json',
},
},
}
2 changes: 1 addition & 1 deletion packages/hardhat-graph-protocol/test/gre.test.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import path from 'path'

import { assert, expect } from 'chai'
import { loadHardhatContext, useHardhatProject } from './helpers'
import { GraphHorizonAddressBook } from '../src/sdk/deployments/horizon'
import { GraphHorizonAddressBook } from '@graphprotocol/toolshed/deployments'

describe('GRE usage', function () {
describe('Project not using GRE', function () {
2 changes: 1 addition & 1 deletion packages/hardhat-graph-protocol/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"module": "node16",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
26 changes: 21 additions & 5 deletions packages/horizon/README.md
Original file line number Diff line number Diff line change
@@ -12,8 +12,6 @@ The following environment variables might be required:
| `ARBITRUM_ONE_RPC` | Arbitrum One RPC URL - defaults to `https://arb1.arbitrum.io/rpc` |
| `ARBITRUM_SEPOLIA_RPC` | Arbitrum Sepolia RPC URL - defaults to `https://sepolia-rollup.arbitrum.io/rpc` |
| `LOCALHOST_RPC` | Localhost RPC URL - defaults to `http://localhost:8545` |
| `LOCALHOST_CHAIN_ID` | Localhost chain ID - defaults to `31337` |
| `LOCALHOST_ACCOUNTS_MNEMONIC` | Localhost accounts mnemonic - no default value. Note that setting this will override any secure accounts configuration. |

You can set them using Hardhat:

@@ -24,8 +22,8 @@ npx hardhat vars set <variable>
## Build

```bash
yarn install
yarn build
pnpm install
pnpm build
```

## Deployment
@@ -49,4 +47,22 @@ npx hardhat deploy:migrate --network hardhat --step 3 # Optionally add --patch-c
npx hardhat deploy:migrate --network hardhat --step 4 # Run with governor. Optionally add --patch-config
```

Steps 2, 3 and 4 require patching the configuration file with addresses from previous steps. The files are located in the `ignition/configs` directory and need to be manually edited. You can also pass `--patch-config` flag to the deploy command to automatically patch the configuration reading values from the address book. Note that this will NOT update the configuration file.
Steps 2, 3 and 4 require patching the configuration file with addresses from previous steps. The files are located in the `ignition/configs` directory and need to be manually edited. You can also pass `--patch-config` flag to the deploy command to automatically patch the configuration reading values from the address book. Note that this will NOT update the configuration file.

## Testing
- **unit**: Unit tests can be run with `pnpm test`
- **integration**: Integration tests can be run with `pnpm test:integration`
- Need to set `BLOCKCHAIN_RPC` for a chain where The Graph is already deployed
- If no `BLOCKCHAIN_RPC` is detected it will try using `ARBITRUM_SEPOLIA_RPC`
- **deployment**: Deployment tests can be run with `pnpm test:deployment --network <network>`
- By default, deployment tests assume a `migrate` deployment type. This can be overridden by setting the `IGNITION_DEPLOYMENT_TYPE` environment variable to `protocol`, however the tests only cover state transitions
originated by a `migrate` deployment.

## Verification

To verify contracts on a network, run the following commands:

```bash
./scripts/pre-verify <ignition-deployment-id>
npx hardhat ignition verify --network <network> --include-unrelated-contracts <ignition-deployment-id>
```
41 changes: 40 additions & 1 deletion packages/horizon/addresses.json
Original file line number Diff line number Diff line change
@@ -1 +1,40 @@
{}
{
"421614": {
"Controller": {
"address": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695"
},
"EpochManager": {
"address": "0x88b3C7f37253bAA1A9b95feAd69bD5320585826D",
"proxy": "graph",
"implementation": "0x646627fa39ec6f6E757Cb4189bC54c92FFBb71da"
},
"L2Curation": {
"address": "0xDe761f075200E75485F4358978FB4d1dC8644FD5",
"proxy": "graph",
"implementation": "0xd90022aB67920212D0F902F5c427DE82732DE136"
},
"RewardsManager": {
"address": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79",
"proxy": "graph",
"implementation": "0x714B54e5249C90414fecA240e2F5B618C243F0aE"
},
"L2GraphTokenGateway": {
"address": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb",
"proxy": "graph",
"implementation": "0x3C2eB5E561f70c0573E5f6c92358e988E32cb5eC"
},
"L2GraphToken": {
"address": "0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04",
"proxy": "graph",
"implementation": "0x4cf968bA38b43dd10be114daa7959C1b369479e5"
},
"GraphProxyAdmin": {
"address": "0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C"
},
"HorizonStaking": {
"address": "0x865365C425f3A593Ffe698D9c4E6707D14d51e08",
"proxy": "graph",
"implementation": "0x64Ed77b164d3B22339DA4DB6d56a1C1d8A051c0A"
}
}
}
23 changes: 21 additions & 2 deletions packages/horizon/contracts/mocks/imports.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6 || 0.8.27;

// 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";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";

// These are needed to get artifacts for toolshed
import "@graphprotocol/contracts/contracts/governance/Controller.sol";
import "@graphprotocol/contracts/contracts/upgrades/GraphProxyAdmin.sol";
import "@graphprotocol/contracts/contracts/l2/curation/IL2Curation.sol";
import "@graphprotocol/contracts/contracts/staking/IStaking.sol";

// Also for toolshed, solidity version in @graphprotocol/contracts does not support overriding public getters
// in interface file, so we need to amend them here.
import { IRewardsManager } from "@graphprotocol/contracts/contracts/rewards/IRewardsManager.sol";
import { IL2Curation } from "@graphprotocol/contracts/contracts/l2/curation/IL2Curation.sol";

interface IRewardsManagerToolshed is IRewardsManager {
function subgraphService() external view returns (address);
}

interface IL2CurationToolshed is IL2Curation {
function subgraphService() external view returns (address);
}
22 changes: 17 additions & 5 deletions packages/horizon/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { hardhatBaseConfig } from 'hardhat-graph-protocol/sdk'
import { hardhatBaseConfig, isProjectBuilt, loadTasks } from '@graphprotocol/toolshed/hardhat'
import type { HardhatUserConfig } from 'hardhat/types'

// Hardhat plugins
import '@nomicfoundation/hardhat-foundry'
import '@nomicfoundation/hardhat-toolbox'
import '@nomicfoundation/hardhat-ignition-ethers'
import 'hardhat-storage-layout'
import 'hardhat-contract-sizer'
import 'hardhat-secure-accounts'
import { HardhatUserConfig } from 'hardhat/types'

// Skip importing hardhat-graph-protocol when building the project, it has circular dependency
if (process.env.BUILD_RUN !== 'true') {
if (isProjectBuilt(__dirname)) {
require('hardhat-graph-protocol')
require('./tasks/deploy')
loadTasks(__dirname)
}

const config: HardhatUserConfig = {
@@ -26,6 +25,19 @@ const config: HardhatUserConfig = {
},
},
},
etherscan: {
...hardhatBaseConfig.etherscan,
customChains: [
{
network: 'arbitrumSepolia',
chainId: 421614,
urls: {
apiURL: 'https://api-sepolia.arbiscan.io/api',
browserURL: 'https://sepolia.arbiscan.io/',
},
},
],
},
}

export default config
32 changes: 12 additions & 20 deletions packages/horizon/ignition/configs/migrate.default.json5
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"$global": {
// Accounts
// Accounts already configured in the original Graph Protocol - Arbitrum Sepolia values
"governor": "0x72ee30d43Fb5A90B3FE983156C5d2fBE6F6d07B3",

// Addresses for contracts deployed in the original Graph Protocol
// Addresses for contracts deployed in the original Graph Protocol - Arbitrum Sepolia values
"graphProxyAdminAddress": "0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C",
"controllerAddress": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695",
"horizonStakingAddress": "0x865365C425f3A593Ffe698D9c4E6707D14d51e08",
@@ -13,10 +13,19 @@
"rewardsManagerAddress": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79",
"curationAddress": "0xDe761f075200E75485F4358978FB4d1dC8644FD5",

// Must be set for step 2 of the migration
"graphPaymentsAddress": "",
"paymentsEscrowAddress": "",

// Must be set for step 3 and 4 of the migration
"subgraphServiceAddress": "",

// Parameters
// Must be set for step 4 of the migration
"horizonStakingImplementationAddress": "",
"curationImplementationAddress": "",
"rewardsManagerImplementationAddress": "",

// Global parameters
"maxThawingPeriod": 2419200
},
"GraphPayments": {
@@ -29,22 +38,5 @@
"eip712Name": "GraphTallyCollector",
"eip712Version": "1",
"revokeSignerThawingPeriod": 10000
},
"HorizonProxiesGovernor": {
// Must be set for step 2 of the migration
"graphPaymentsAddress": "",
"paymentsEscrowAddress": ""
},
"HorizonStakingGovernor": {
// Must be set for step 4 of the migration
"horizonStakingImplementationAddress": ""
},
"L2CurationGovernor": {
// Must be set for step 4 of the migration
"curationImplementationAddress": ""
},
"RewardsManagerGovernor": {
// Must be set for step 4 of the migration
"rewardsManagerImplementationAddress": ""
}
}
42 changes: 42 additions & 0 deletions packages/horizon/ignition/configs/migrate.integration.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"$global": {
// Accounts
"governor": "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0",

// Addresses for contracts deployed in the original Graph Protocol
"graphProxyAdminAddress": "0x7474a6cc5fAeDEc620Db0fa8E4da6eD58477042C",
"controllerAddress": "0x9DB3ee191681f092607035d9BDA6e59FbEaCa695",
"horizonStakingAddress": "0x865365C425f3A593Ffe698D9c4E6707D14d51e08",
"epochManagerAddress": "0x88b3C7f37253bAA1A9b95feAd69bD5320585826D",
"graphTokenAddress": "0xf8c05dCF59E8B28BFD5eed176C562bEbcfc7Ac04",
"graphTokenGatewayAddress": "0xB24Ce0f8c18c4DdDa584A7EeC132F49C966813bb",
"rewardsManagerAddress": "0x1F49caE7669086c8ba53CC35d1E9f80176d67E79",
"curationAddress": "0xDe761f075200E75485F4358978FB4d1dC8644FD5",

// Must be set for step 2 of the migration
"graphPaymentsAddress": "",
"paymentsEscrowAddress": "",

// Must be set for step 3 and 4 of the migration
"subgraphServiceAddress": "",

// Must be set for step 4 of the migration
"horizonStakingImplementationAddress": "",
"curationImplementationAddress": "",
"rewardsManagerImplementationAddress": "",

// Global parameters
"maxThawingPeriod": 2419200
},
"GraphPayments": {
"protocolPaymentCut": 10000
},
"PaymentsEscrow": {
"withdrawEscrowThawingPeriod": 10000
},
"GraphTallyCollector": {
"eip712Name": "GraphTallyCollector",
"eip712Version": "1",
"revokeSignerThawingPeriod": 10000
}
}
37 changes: 20 additions & 17 deletions packages/horizon/ignition/configs/protocol.default.json5
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
{
"$global": {
"pauseGuardian": "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC",
// Accounts for new deployment - derived from hardhat default mnemonic
"pauseGuardian": "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d", // index 3
"subgraphAvailabilityOracle": "0xd03ea8624C8C5987235048901fB614fDcA89b117", // index 4

// Placeholder address for a standalone Horizon deployment, see README.md for more details
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000"
"subgraphServiceAddress": "0x0000000000000000000000000000000000000000",

// Global parameters
"maxThawingPeriod": 2419200
},
"GraphPayments": {
"protocolPaymentCut": 10000
},
"PaymentsEscrow": {
"withdrawEscrowThawingPeriod": 10000
},
"GraphTallyCollector": {
"eip712Name": "GraphTallyCollector",
"eip712Version": "1",
"revokeSignerThawingPeriod": 10000
},
"RewardsManager": {
"subgraphAvailabilityOracle": "0xd03ea8624C8C5987235048901fB614fDcA89b117",
"issuancePerBlock": "114155251141552511415n"
},
"EpochManager": {
@@ -19,18 +34,6 @@
"L2GraphToken": {
"initialSupply": "10000000000000000000000000000n"
},
"HorizonStaking": {
"maxThawingPeriod": 2419200
},
"GraphPayments": {
"protocolPaymentCut": 10000
},
"PaymentsEscrow": {
"withdrawEscrowThawingPeriod": 10000
},
"GraphTallyCollector": {
"eip712Name": "GraphTallyCollector",
"eip712Version": "1",
"revokeSignerThawingPeriod": 10000
}


}
6 changes: 4 additions & 2 deletions packages/horizon/ignition/modules/core/core.ts
Original file line number Diff line number Diff line change
@@ -7,15 +7,17 @@ import PaymentsEscrowModule, { MigratePaymentsEscrowModule } from './PaymentsEsc

export default buildModule('GraphHorizon_Core', (m) => {
const { HorizonStaking, HorizonStakingImplementation } = m.useModule(HorizonStakingModule)
const { GraphPayments, GraphPaymentsImplementation } = m.useModule(GraphPaymentsModule)
const { PaymentsEscrow, PaymentsEscrowImplementation } = m.useModule(PaymentsEscrowModule)
const { GraphPaymentsProxyAdmin, GraphPayments, GraphPaymentsImplementation } = m.useModule(GraphPaymentsModule)
const { PaymentsEscrowProxyAdmin, PaymentsEscrow, PaymentsEscrowImplementation } = m.useModule(PaymentsEscrowModule)
const { GraphTallyCollector } = m.useModule(GraphTallyCollectorModule)

return {
HorizonStaking,
HorizonStakingImplementation,
GraphPaymentsProxyAdmin,
GraphPayments,
GraphPaymentsImplementation,
PaymentsEscrowProxyAdmin,
PaymentsEscrow,
PaymentsEscrowImplementation,
GraphTallyCollector,
4 changes: 4 additions & 0 deletions packages/horizon/ignition/modules/deploy.ts
Original file line number Diff line number Diff line change
@@ -21,8 +21,10 @@ export default buildModule('GraphHorizon_Deploy', (m) => {
const {
HorizonStaking,
HorizonStakingImplementation,
GraphPaymentsProxyAdmin,
GraphPayments,
GraphPaymentsImplementation,
PaymentsEscrowProxyAdmin,
PaymentsEscrow,
PaymentsEscrowImplementation,
GraphTallyCollector,
@@ -53,8 +55,10 @@ export default buildModule('GraphHorizon_Deploy', (m) => {
GraphProxyAdmin,
Graph_Proxy_HorizonStaking: HorizonStaking,
Implementation_HorizonStaking: HorizonStakingImplementation,
Transparent_ProxyAdmin_GraphPayments: GraphPaymentsProxyAdmin,
Transparent_Proxy_GraphPayments: GraphPayments,
Implementation_GraphPayments: GraphPaymentsImplementation,
Transparent_ProxyAdmin_PaymentsEscrow: PaymentsEscrowProxyAdmin,
Transparent_Proxy_PaymentsEscrow: PaymentsEscrow,
Implementation_PaymentsEscrow: PaymentsEscrowImplementation,
GraphTallyCollector,
23 changes: 13 additions & 10 deletions packages/horizon/package.json
Original file line number Diff line number Diff line change
@@ -11,19 +11,22 @@
"addresses.json"
],
"scripts": {
"lint": "yarn lint:ts && yarn lint:sol",
"lint:ts": "eslint '**/*.{js,ts}' --fix",
"lint:sol": "yarn lint:sol:prettier && yarn lint:sol:solhint",
"lint": "pnpm lint:ts && pnpm lint:sol",
"lint:ts": "eslint '**/*.{js,ts}' --fix --no-warn-ignored",
"lint:sol": "prettier --write contracts/**/*.sol test/**/*.sol && solhint --noPrompt --fix contracts/**/*.sol --config node_modules/solhint-graph-config/index.js",
"lint:sol:prettier": "prettier --write contracts/**/*.sol test/**/*.sol",
"lint:sol:solhint": "solhint --noPrompt --fix contracts/**/*.sol --config node_modules/solhint-graph-config/index.js",
"lint:sol:natspec": "natspec-smells --config natspec-smells.config.js",
"clean": "rm -rf build dist cache cache_forge typechain-types",
"build": "BUILD_RUN=true hardhat compile",
"test": "forge test && hardhat test"
"build": "hardhat compile",
"test": "forge test",
"test:deployment": "SECURE_ACCOUNTS_DISABLE_PROVIDER=true hardhat test",
"test:integration": "./scripts/integration"
},
"devDependencies": {
"@defi-wonderland/natspec-smells": "^1.1.6",
"@graphprotocol/contracts": "workspace:^7.0.0",
"@graphprotocol/toolshed": "workspace:^",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.8",
"@nomicfoundation/hardhat-foundry": "^1.1.1",
@@ -44,12 +47,12 @@
"eslint": "^8.56.0",
"eslint-graph-config": "workspace:^0.0.1",
"ethers": "^6.13.4",
"glob": "^11.0.1",
"hardhat": "^2.22.18",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-gas-reporter": "^1.0.8",
"hardhat-graph-protocol": "workspace:^0.0.1",
"hardhat-secure-accounts": "^1.0.5",
"hardhat-storage-layout": "^0.1.7",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"prettier-plugin-solidity": "^1.3.1",
@@ -63,16 +66,16 @@
},
"lint-staged": {
"contracts/**/*.sol": [
"yarn lint:sol"
"pnpm lint:sol"
],
"**/*.ts": [
"yarn lint:ts"
"pnpm lint:ts"
],
"**/*.js": [
"yarn lint:ts"
"pnpm lint:ts"
],
"**/*.json": [
"yarn lint:ts"
"pnpm lint:ts"
]
}
}
125 changes: 125 additions & 0 deletions packages/horizon/scripts/integration
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/bash

set -eo pipefail

NON_INTERACTIVE=${NON_INTERACTIVE:-false}

# Set environment variables for this script
export SECURE_ACCOUNTS_DISABLE_PROVIDER=true
export FORK_FROM_CHAIN_ID=${FORK_FROM_CHAIN_ID:-421614}

# Function to cleanup resources
cleanup() {
# Kill hardhat node only if we started it
if [ ! -z "$NODE_PID" ] && [ "$STARTED_NODE" = true ]; then
echo "Cleaning up node process..."
kill $NODE_PID 2>/dev/null || true
fi
}

# Set trap to call cleanup function on script exit (normal or error)
trap cleanup EXIT

# Check if ignition deployment folder exists and prompt before proceeding
if [ -d "ignition/deployments/horizon-localhost" ]; then
if [ "$NON_INTERACTIVE" = true ]; then
confirm=y
else
read -p "Ignition deployment files already exist. These must be removed for the tests to work properly. Remove them? (y/n) [y]: " confirm
confirm=${confirm:-y}
fi
if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then
echo "Removing existing ignition deployment files..."
rm -rf ignition/deployments/horizon-localhost
else
echo "Operation cancelled. Exiting..."
exit 1
fi
fi

if [ -f "addresses-localhost.json" ] || [ -f "../subgraph-service/addresses-localhost.json" ]; then
if [ "$NON_INTERACTIVE" = true ]; then
confirm=y
else
read -p "Address books for horizon and subgraph service will be deleted. Continue? (y/n) [y]: " confirm
confirm=${confirm:-y}
fi
if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then
echo "Deleting addresses-localhost.json..."
rm -f addresses-localhost.json
echo "Deleting ../subgraph-service/addresses-localhost.json..."
rm -f ../subgraph-service/addresses-localhost.json
else
echo "Operation cancelled. Exiting..."
exit 1
fi
fi

# Check required env variables
BLOCKCHAIN_RPC=${BLOCKCHAIN_RPC:-$(npx hardhat vars get ARBITRUM_SEPOLIA_RPC)}
if [ -z "$BLOCKCHAIN_RPC" ]; then
echo "BLOCKCHAIN_RPC environment variable is required"
exit 1
fi

echo "Starting integration tests..."

# Check if hardhat node is already running on port 8545
STARTED_NODE=false
if lsof -i:8545 > /dev/null 2>&1; then
echo "Hardhat node already running on port 8545, using existing node"
# Get the PID of the process using port 8545
NODE_PID=$(lsof -t -i:8545)
else
# Start local hardhat node forked from Arbitrum Sepolia
echo "Starting local hardhat node..."
npx hardhat node --fork $BLOCKCHAIN_RPC > node.log 2>&1 &
NODE_PID=$!
STARTED_NODE=true

# Wait for node to start
sleep 10
fi

# Setup horizon address book
jq '{"31337": ."'"$FORK_FROM_CHAIN_ID"'"}' addresses.json > addresses-localhost.json

# Setup pre horizon migration state needed for the integration tests
npx hardhat test:seed --network localhost

# Transfer ownership of protocol to hardhat signer 1
npx hardhat test:transfer-ownership --network localhost

# Step 1 - Deployer
npx hardhat deploy:migrate --network localhost --horizon-config integration --step 1

# Step 2 - Governor
npx hardhat deploy:migrate --network localhost --horizon-config integration --step 2 --patch-config --account-index 1 --hide-banner

# Step 3 - Deployer
npx hardhat deploy:migrate --network localhost --horizon-config integration --step 3 --patch-config --hide-banner

# Step 4 - Governor
npx hardhat deploy:migrate --network localhost --horizon-config integration --step 4 --patch-config --account-index 1 --hide-banner

# Unset subgraph service
npx hardhat transition:unset-subgraph-service --network localhost

# Run integration tests - During transition period
npx hardhat test:integration --phase during-transition-period --network localhost

# Clear thawing period
npx hardhat transition:clear-thawing --network localhost

# Run integration tests - After transition period
npx hardhat test:integration --phase after-transition-period --network localhost

# Enable delegation slashing
npx hardhat transition:enable-delegation-slashing --network localhost

# Run integration tests - After delegation slashing enabled
npx hardhat test:integration --phase after-delegation-slashing-enabled --network localhost

echo ""
echo "🎉 ✨ 🚀 ✅ Integration tests completed successfully! 🎉 ✨ 🚀 ✅"
echo ""
4 changes: 3 additions & 1 deletion packages/horizon/scripts/pre-verify
Original file line number Diff line number Diff line change
@@ -54,4 +54,6 @@ cp "$DBG_DIR_DEST/GraphProxy#GraphProxy.dbg.json" "$DBG_DIR_DEST/BridgeEscrow#Gr
cp "$DBG_DIR_DEST/GraphProxy#GraphProxy.dbg.json" "$DBG_DIR_DEST/EpochManager#GraphProxy.dbg.json"

cp "$DBG_DIR_DEST/TransparentUpgradeableProxy#TransparentUpgradeableProxy.dbg.json" "$DBG_DIR_DEST/HorizonProxiesDeployer#TransparentUpgradeableProxy_GraphPayments.dbg.json"
cp "$DBG_DIR_DEST/TransparentUpgradeableProxy#TransparentUpgradeableProxy.dbg.json" "$DBG_DIR_DEST/HorizonProxiesDeployer#TransparentUpgradeableProxy_PaymentsEscrow.dbg.json"
cp "$DBG_DIR_DEST/TransparentUpgradeableProxy#TransparentUpgradeableProxy.dbg.json" "$DBG_DIR_DEST/HorizonProxies#TransparentUpgradeableProxy_GraphPayments.dbg.json"
cp "$DBG_DIR_DEST/TransparentUpgradeableProxy#TransparentUpgradeableProxy.dbg.json" "$DBG_DIR_DEST/HorizonProxiesDeployer#TransparentUpgradeableProxy_PaymentsEscrow.dbg.json"
cp "$DBG_DIR_DEST/TransparentUpgradeableProxy#TransparentUpgradeableProxy.dbg.json" "$DBG_DIR_DEST/HorizonProxies#TransparentUpgradeableProxy_PaymentsEscrow.dbg.json"
88 changes: 37 additions & 51 deletions packages/horizon/tasks/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
/* eslint-disable no-case-declarations */
import { loadConfig, patchConfig, saveToAddressBook } from '@graphprotocol/toolshed/hardhat'
import { task, types } from 'hardhat/config'
import { IgnitionHelper } from 'hardhat-graph-protocol/sdk'
import { ZERO_ADDRESS } from '@graphprotocol/toolshed'

import type { AddressBook } from '../../hardhat-graph-protocol/src/sdk/address-book'
import type { AddressBook } from '@graphprotocol/toolshed/deployments'
import type { HardhatRuntimeEnvironment } from 'hardhat/types'

import DeployModule from '../ignition/modules/deploy'
import { printHorizonBanner } from '@graphprotocol/toolshed/utils'

task('deploy:protocol', 'Deploy a new version of the Graph Protocol Horizon contracts - no data services deployed')
.addOptionalParam('horizonConfig', 'Name of the Horizon configuration file to use. Format is "protocol.<name>.json5", file must be in the "ignition/configs/" directory. Defaults to network name.', undefined, types.string)
.addOptionalParam('accountIndex', 'Derivation path index for the account to use', 0, types.int)
.setAction(async (args, hre: HardhatRuntimeEnvironment) => {
const graph = hre.graph()

// Load configuration for the deployment
console.log('\n========== ⚙️ Deployment configuration ==========')
const { config: HorizonConfig, file } = IgnitionHelper.loadConfig('./ignition/configs/', 'protocol', args.horizonConfig ?? hre.network.name)
const { config: HorizonConfig, file } = loadConfig('./ignition/configs/', 'protocol', args.horizonConfig ?? hre.network.name)
console.log(`Loaded migration configuration from ${file}`)

// Display the deployer -- this also triggers the secure accounts prompt if being used
console.log('\n========== 🔑 Deployer account ==========')
const signers = await hre.ethers.getSigners()
const deployer = signers[0]
const deployer = await graph.accounts.getDeployer(args.accountIndex)
console.log('Using deployer account:', deployer.address)
const balance = await hre.ethers.provider.getBalance(deployer.address)
console.log('Deployer balance:', hre.ethers.formatEther(balance), 'ETH')
@@ -34,27 +36,32 @@ task('deploy:protocol', 'Deploy a new version of the Graph Protocol Horizon cont
const deployment = await hre.ignition.deploy(DeployModule, {
displayUi: true,
parameters: HorizonConfig,
defaultSender: deployer.address,
})

// Save the addresses to the address book
console.log('\n========== 📖 Updating address book ==========')
IgnitionHelper.saveToAddressBook(deployment, hre.network.config.chainId, graph.horizon!.addressBook)
console.log(`Address book at ${graph.horizon!.addressBook.file} updated!`)
saveToAddressBook(deployment, graph.horizon.addressBook)
console.log(`Address book at ${graph.horizon.addressBook.file} updated!`)

console.log('\n\n🎉 ✨ 🚀 ✅ Deployment complete! 🎉 ✨ 🚀 ✅')
})

task('deploy:migrate', 'Upgrade an existing version of the Graph Protocol v1 to Horizon - no data services deployed')
.addOptionalParam('horizonConfig', 'Name of the Horizon configuration file to use. Format is "migrate.<name>.json5", file must be in the "ignition/configs/" directory. Defaults to network name.', undefined, types.string)
.addOptionalParam('step', 'Migration step to run (1, 2, 3 or 4)', undefined, types.int)
.addOptionalParam('accountIndex', 'Derivation path index for the account to use', 0, types.int)
.addFlag('patchConfig', 'Patch configuration file using address book values - does not save changes')
.addFlag('hideBanner', 'Hide the banner display')
.setAction(async (args, hre: HardhatRuntimeEnvironment) => {
// Task parameters
const step: number = args.step ?? 0
const patchConfig: boolean = args.patchConfig ?? false

const graph = hre.graph()
console.log(getHorizonBanner())
if (!args.hideBanner) {
printHorizonBanner()
}

// Migration step to run
console.log('\n========== 🏗️ Migration steps ==========')
@@ -68,13 +75,12 @@ task('deploy:migrate', 'Upgrade an existing version of the Graph Protocol v1 to

// Load configuration for the migration
console.log('\n========== ⚙️ Deployment configuration ==========')
const { config: HorizonMigrateConfig, file } = IgnitionHelper.loadConfig('./ignition/configs/', 'migrate', args.horizonConfig ?? hre.network.name)
const { config: HorizonMigrateConfig, file } = loadConfig('./ignition/configs/', 'migrate', args.horizonConfig ?? hre.network.name)
console.log(`Loaded migration configuration from ${file}`)

// Display the deployer -- this also triggers the secure accounts prompt if being used
console.log('\n========== 🔑 Deployer account ==========')
const signers = await hre.ethers.getSigners()
const deployer = signers[0]
const deployer = await graph.accounts.getDeployer(args.accountIndex)
console.log('Using deployer account:', deployer.address)
const balance = await hre.ethers.provider.getBalance(deployer.address)
console.log('Deployer balance:', hre.ethers.formatEther(balance), 'ETH')
@@ -90,16 +96,18 @@ task('deploy:migrate', 'Upgrade an existing version of the Graph Protocol v1 to
MigrationModule,
{
displayUi: true,
parameters: patchConfig ? _patchStepConfig(step, HorizonMigrateConfig, graph.horizon!.addressBook, graph.subgraphService!.addressBook) : HorizonMigrateConfig,
parameters: patchConfig ? _patchStepConfig(step, HorizonMigrateConfig, graph.horizon.addressBook, graph.subgraphService.addressBook) : HorizonMigrateConfig,
deploymentId: `horizon-${hre.network.name}`,
})
defaultSender: deployer.address,
},
)

// Update address book
console.log('\n========== 📖 Updating address book ==========')
IgnitionHelper.saveToAddressBook(deployment, hre.network.config.chainId, graph.horizon!.addressBook)
console.log(`Address book at ${graph.horizon!.addressBook.file} updated!`)
saveToAddressBook(deployment, graph.horizon.addressBook)
console.log(`Address book at ${graph.horizon.addressBook.file} updated!`)

console.log('\n\n🎉 ✨ 🚀 ✅ Migration complete! 🎉 ✨ 🚀 ✅')
console.log(`\n\n🎉 ✨ 🚀 ✅ Migration step ${step} complete! 🎉 ✨ 🚀 ✅\n`)
})

// This function patches the Ignition configuration object using an address book to fill in the gaps
@@ -119,60 +127,38 @@ function _patchStepConfig<ChainId extends number, ContractName extends string, H
case 2:
const GraphPayments = horizonAddressBook.getEntry('GraphPayments')
const PaymentsEscrow = horizonAddressBook.getEntry('PaymentsEscrow')
patchedConfig = IgnitionHelper.patchConfig(config, {
HorizonProxiesGovernor: {
patchedConfig = patchConfig(config, {
$global: {
graphPaymentsAddress: GraphPayments.address,
paymentsEscrowAddress: PaymentsEscrow.address,
},
})
break
case 3:
const SubgraphService3 = subgraphServiceAddressBook.getEntry('SubgraphService')
patchedConfig = IgnitionHelper.patchConfig(patchedConfig, {
patchedConfig = patchConfig(patchedConfig, {
$global: {
subgraphServiceAddress: SubgraphService3.address,
subgraphServiceAddress: subgraphServiceAddressBook.entryExists('SubgraphService')
? subgraphServiceAddressBook.getEntry('SubgraphService').address
: ZERO_ADDRESS,
},
})
break
case 4:
const HorizonStaking = horizonAddressBook.getEntry('HorizonStaking')
const L2Curation = horizonAddressBook.getEntry('L2Curation')
const RewardsManager = horizonAddressBook.getEntry('RewardsManager')
const SubgraphService4 = subgraphServiceAddressBook.getEntry('SubgraphService')
patchedConfig = IgnitionHelper.patchConfig(patchedConfig, {
patchedConfig = patchConfig(patchedConfig, {
$global: {
subgraphServiceAddress: SubgraphService4.address,
},
HorizonStakingGovernor: {
horizonStakingImplementationAddress: HorizonStaking.implementation,
},
L2CurationGovernor: {
curationImplementationAddress: L2Curation.implementation,
},
RewardsManagerGovernor: {
rewardsManagerImplementationAddress: RewardsManager.implementation,
subgraphServiceAddress: subgraphServiceAddressBook.entryExists('SubgraphService')
? subgraphServiceAddressBook.getEntry('SubgraphService').address
: ZERO_ADDRESS,
horizonStakingImplementationAddress: HorizonStaking.implementation ?? ZERO_ADDRESS,
curationImplementationAddress: L2Curation.implementation ?? ZERO_ADDRESS,
rewardsManagerImplementationAddress: RewardsManager.implementation ?? ZERO_ADDRESS,
},
})
break
}

return patchedConfig
}

function getHorizonBanner(): string {
return `
██╗ ██╗ ██████╗ ██████╗ ██╗███████╗ ██████╗ ███╗ ██╗
██║ ██║██╔═══██╗██╔══██╗██║╚══███╔╝██╔═══██╗████╗ ██║
███████║██║ ██║██████╔╝██║ ███╔╝ ██║ ██║██╔██╗ ██║
██╔══██║██║ ██║██╔══██╗██║ ███╔╝ ██║ ██║██║╚██╗██║
██║ ██║╚██████╔╝██║ ██║██║███████╗╚██████╔╝██║ ╚████║
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═══╝

██╗ ██╗██████╗ ██████╗ ██████╗ █████╗ ██████╗ ███████╗
██║ ██║██╔══██╗██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██╔════╝
██║ ██║██████╔╝██║ ███╗██████╔╝███████║██║ ██║█████╗
██║ ██║██╔═══╝ ██║ ██║██╔══██╗██╔══██║██║ ██║██╔══╝
╚██████╔╝██║ ╚██████╔╝██║ ██║██║ ██║██████╔╝███████╗
╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝
`
}
58 changes: 58 additions & 0 deletions packages/horizon/tasks/test/fixtures/delegators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { indexers } from './indexers'
import { parseEther } from 'ethers'

export interface Delegator {
address: string
delegations: {
indexerAddress: string
tokens: bigint
}[]
undelegate: boolean // Whether this delegator should undelegate at the end
}

export const delegators: Delegator[] = [
{
address: '0x610Bb1573d1046FCb8A70Bbbd395754cD57C2b60', // Hardhat account #10
delegations: [
{
indexerAddress: indexers[0].address,
tokens: parseEther('50000'),
},
{
indexerAddress: indexers[1].address,
tokens: parseEther('25000'),
},
],
undelegate: false,
},
{
address: '0x855FA758c77D68a04990E992aA4dcdeF899F654A', // Hardhat account #11
delegations: [
{
indexerAddress: indexers[1].address,
tokens: parseEther('75000'),
},
],
undelegate: false,
},
{
address: '0xfA2435Eacf10Ca62ae6787ba2fB044f8733Ee843', // Hardhat account #12
delegations: [
{
indexerAddress: indexers[0].address,
tokens: parseEther('100000'),
},
],
undelegate: true, // This delegator will undelegate
},
{
address: '0x64E078A8Aa15A41B85890265648e965De686bAE6', // Hardhat account #13
delegations: [],
undelegate: false,
},
{
address: '0x2F560290FEF1B3Ada194b6aA9c40aa71f8e95598', // Hardhat account #14
delegations: [],
undelegate: false,
},
]
104 changes: 104 additions & 0 deletions packages/horizon/tasks/test/fixtures/indexers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { parseEther } from 'ethers'

// Indexer interface
export interface Indexer {
address: string
stake: bigint
tokensToUnstake?: bigint
indexingRewardCut: number
queryFeeCut: number
rewardsDestination?: string
allocations: Allocation[]
}

// Allocation interface
export interface Allocation {
allocationID: string
allocationPrivateKey: string
subgraphDeploymentID: string
tokens: bigint
}

// Indexer one data
const INDEXER_ONE_ADDRESS = '0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC' // Hardhat account #5
const INDEXER_ONE_FIRST_ALLOCATION_ID = '0x70043e424171076D74a1f6a6a56087Bb4c7A61AA'
const INDEXER_ONE_FIRST_ALLOCATION_PRIVATE_KEY = '0x9c41bca4eb319bdf4cac23ae3366eed5f9fa12eb05c0ef29319afcfaa3fc2d79'
const INDEXER_ONE_SECOND_ALLOCATION_ID = '0xd67CE7F6A2eCa6fD78A7E2A5C5e56Fb821BEdE0c'
const INDEXER_ONE_SECOND_ALLOCATION_PRIVATE_KEY = '0x827a0b66fbeb3fefb4a99b6ba0b4bea3b8dd590b97fa7a1bbe74e5b33c935f16'
const INDEXER_ONE_THIRD_ALLOCATION_ID = '0x212e51125e4Ed4C2041614b139eC6cb8FA6d561C'
const INDEXER_ONE_THIRD_ALLOCATION_PRIVATE_KEY = '0x434f1d4435e978299ec64841153c25af2f611a145da3e8539c65b9bd5d9c08b5'

// Indexer two data
const INDEXER_TWO_ADDRESS = '0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9' // Hardhat account #6
const INDEXER_TWO_REWARDS_DESTINATION = '0x227A35f9912693240E842FaAB6cf5e4E6371ff63'
const INDEXER_TWO_FIRST_ALLOCATION_ID = '0xD0EAc83b0bf328bbf68F4f1a1480e17A38BFb192'
const INDEXER_TWO_FIRST_ALLOCATION_PRIVATE_KEY = '0x80ff89a67cf4b41ea3ece2574b7212b5fee43c0fa370bf3e188a645b561ac810'
const INDEXER_TWO_SECOND_ALLOCATION_ID = '0x63280ec9EA63859b7e2041f07a549F311C86B3bd'
const INDEXER_TWO_SECOND_ALLOCATION_PRIVATE_KEY = '0xab6cb9dbb3646a856e6cac2c0e2a59615634e93cde11385eb6c6ba58e2873a46'

// Indexer three data
const INDEXER_THREE_ADDRESS = '0x28a8746e75304c0780E011BEd21C72cD78cd535E' // Hardhat account #6

// Subgraph deployment IDs
const SUBGRAPH_DEPLOYMENT_ID_ONE = '0x02cd85012c1f075fd58fad178fd23ab841d3b5ddcf5cd3377c30118da97cb2a4'
const SUBGRAPH_DEPLOYMENT_ID_TWO = '0x03ca89485a59894f1acfa34660c69024b6b90ce45171dece7662b0886bc375c7'
const SUBGRAPH_DEPLOYMENT_ID_THREE = '0x0472e8c46f728adb65a22187c6740532f82c2ebadaeabbbe59a2bb4a1bdde197'

export const indexers: Indexer[] = [
{
address: INDEXER_ONE_ADDRESS,
stake: parseEther('1000000'),
tokensToUnstake: parseEther('10000'),
indexingRewardCut: 900000, // 90%
queryFeeCut: 900000, // 90%
allocations: [
{
allocationID: INDEXER_ONE_FIRST_ALLOCATION_ID,
allocationPrivateKey: INDEXER_ONE_FIRST_ALLOCATION_PRIVATE_KEY,
subgraphDeploymentID: SUBGRAPH_DEPLOYMENT_ID_ONE,
tokens: parseEther('400000'),
},
{
allocationID: INDEXER_ONE_SECOND_ALLOCATION_ID,
allocationPrivateKey: INDEXER_ONE_SECOND_ALLOCATION_PRIVATE_KEY,
subgraphDeploymentID: SUBGRAPH_DEPLOYMENT_ID_TWO,
tokens: parseEther('300000'),
},
{
allocationID: INDEXER_ONE_THIRD_ALLOCATION_ID,
allocationPrivateKey: INDEXER_ONE_THIRD_ALLOCATION_PRIVATE_KEY,
subgraphDeploymentID: SUBGRAPH_DEPLOYMENT_ID_THREE,
tokens: parseEther('250000'),
},
],
},
{
address: INDEXER_TWO_ADDRESS,
stake: parseEther('1000000'),
tokensToUnstake: parseEther('1000000'),
indexingRewardCut: 850000, // 85%
queryFeeCut: 850000, // 85%
rewardsDestination: INDEXER_TWO_REWARDS_DESTINATION,
allocations: [
{
allocationID: INDEXER_TWO_FIRST_ALLOCATION_ID,
allocationPrivateKey: INDEXER_TWO_FIRST_ALLOCATION_PRIVATE_KEY,
subgraphDeploymentID: SUBGRAPH_DEPLOYMENT_ID_ONE,
tokens: parseEther('400000'),
},
{
allocationID: INDEXER_TWO_SECOND_ALLOCATION_ID,
allocationPrivateKey: INDEXER_TWO_SECOND_ALLOCATION_PRIVATE_KEY,
subgraphDeploymentID: SUBGRAPH_DEPLOYMENT_ID_TWO,
tokens: parseEther('200000'),
},
],
},
{
address: INDEXER_THREE_ADDRESS,
stake: parseEther('1000000'),
indexingRewardCut: 800000, // 80%
queryFeeCut: 800000, // 80%
allocations: [],
},
]
36 changes: 36 additions & 0 deletions packages/horizon/tasks/test/integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { glob } from 'glob'
import { task } from 'hardhat/config'
import { TASK_TEST } from 'hardhat/builtin-tasks/task-names'

import { printBanner } from '@graphprotocol/toolshed/utils'

task('test:integration', 'Runs all integration tests')
.addParam(
'phase',
'Test phase to run: "during-transition-period", "after-transition-period", "after-delegation-slashing-enabled"',
)
.setAction(async (taskArgs, hre) => {
// Get test files for each phase
const duringTransitionPeriodFiles = await glob('test/integration/during-transition-period/**/*.{js,ts}')
const afterTransitionPeriodFiles = await glob('test/integration/after-transition-period/**/*.{js,ts}')
const afterDelegationSlashingEnabledFiles = await glob('test/integration/after-delegation-slashing-enabled/**/*.{js,ts}')

// Display banner for the current test phase
printBanner(taskArgs.phase, 'INTEGRATION TESTS: ')

switch (taskArgs.phase) {
case 'during-transition-period':
await hre.run(TASK_TEST, { testFiles: duringTransitionPeriodFiles })
break
case 'after-transition-period':
await hre.run(TASK_TEST, { testFiles: afterTransitionPeriodFiles })
break
case 'after-delegation-slashing-enabled':
await hre.run(TASK_TEST, { testFiles: afterDelegationSlashingEnabledFiles })
break
default:
throw new Error(
'Invalid phase. Must be "during-transition-period", "after-transition-period", "after-delegation-slashing-enabled", or "all"',
)
}
})
72 changes: 72 additions & 0 deletions packages/horizon/tasks/test/ownership.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { task, types } from 'hardhat/config'
import { printBanner } from '@graphprotocol/toolshed/utils'
import { requireLocalNetwork } from '@graphprotocol/toolshed/hardhat'

// This is required because we cannot impersonate Ignition accounts
// so we impersonate current governor and transfer ownership to accounts that Ignition can control
task('test:transfer-ownership', 'Transfer ownership of protocol contracts to a new governor')
.addOptionalParam('governorIndex', 'Derivation path index for the new governor account', 1, types.int)
.addOptionalParam('slasherIndex', 'Derivation path index for the new slasher account', 2, types.int)
.setAction(async (taskArgs, hre) => {
printBanner('TRANSFER OWNERSHIP')

const graph = hre.graph()

// this task uses impersonation so we NEED a local network
requireLocalNetwork(hre)

console.log('\n--- STEP 0: Setup ---')

// Get signers
const newGovernor = await graph.accounts.getGovernor(taskArgs.governorIndex)
const newSlasher = await graph.accounts.getArbitrator(taskArgs.slasherIndex)

console.log(`New governor will be: ${newGovernor.address}`)

// Get contracts
const staking = graph.horizon.contracts.LegacyStaking
const controller = graph.horizon.contracts.Controller
const graphProxyAdmin = graph.horizon.contracts.GraphProxyAdmin

// Get current owners
const controllerGovernor = await controller.governor()
const proxyAdminGovernor = await graphProxyAdmin.governor()

console.log(`Current Controller governor: ${controllerGovernor}`)
console.log(`Current GraphProxyAdmin governor: ${proxyAdminGovernor}`)

// Get impersonated signers
const controllerSigner = await hre.ethers.getImpersonatedSigner(controllerGovernor)
const proxyAdminSigner = await hre.ethers.getImpersonatedSigner(proxyAdminGovernor)

console.log('\n--- STEP 1: Transfer ownership of Controller ---')

// Transfer Controller ownership
console.log('Transferring Controller ownership...')
await controller.connect(controllerSigner).transferOwnership(newGovernor.address)
console.log('Accepting Controller ownership...')

// Accept ownership of Controller
await controller.connect(newGovernor).acceptOwnership()
console.log(`New Controller governor: ${await controller.governor()}`)

console.log('\n--- STEP 2: Transfer ownership of GraphProxyAdmin ---')

// Transfer GraphProxyAdmin ownership
console.log('Transferring GraphProxyAdmin ownership...')
await graphProxyAdmin.connect(proxyAdminSigner).transferOwnership(newGovernor.address)
console.log('Accepting GraphProxyAdmin ownership...')

// Accept ownership of GraphProxyAdmin
await graphProxyAdmin.connect(newGovernor).acceptOwnership()
console.log(`New GraphProxyAdmin governor: ${await graphProxyAdmin.governor()}`)

console.log('\n--- STEP 3: Assign new slasher ---')

// Assign new slasher
console.log('Assigning new slasher...')
await staking.connect(newGovernor).setSlasher(newSlasher.address, true)
console.log(`New slasher: ${newSlasher.address}, allowed: ${await staking.slashers(newSlasher.address)}`)

console.log('\n\n🎉 ✨ 🚀 ✅ Transfer ownership complete! 🎉 ✨ 🚀 ✅\n')
})
Loading