Skip to content

gre: refactor to allow using L1 and L2 simultaneously #682

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

Merged
merged 9 commits into from
Aug 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"require": "ts-node/register/files",
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this? :)

Copy link
Member Author

Choose a reason for hiding this comment

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

I knew you would ask ;)

The plugin tests run with mocha (and not with hh test), this is needed for mocha to support typescript files.

On a more general note, I based the GRE folder on hardhat's plugin boilerplate https://github.com/NomicFoundation/hardhat-ts-plugin-boilerplate/, once GRE is mature I think it makes sense to fork it into it's own repository so each repo is nice and tidy and not polluted with too much stuff.

"ignore": ["test/fixture-projects/**/*"],
"timeout": 6000
}
2 changes: 1 addition & 1 deletion e2e/deployment/config/allocationExchange.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import hre from 'hardhat'
import { NamedAccounts } from '../../../tasks/type-extensions'
import { NamedAccounts } from '../../../gre/type-extensions'

describe('AllocationExchange configuration', () => {
const {
Expand Down
2 changes: 1 addition & 1 deletion e2e/deployment/config/controller.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import hre, { ethers } from 'hardhat'
import { NamedAccounts } from '../../../tasks/type-extensions'
import { NamedAccounts } from '../../../gre/type-extensions'

describe('Controller configuration', () => {
const { contracts, getNamedAccounts } = hre.graph()
Expand Down
2 changes: 1 addition & 1 deletion e2e/deployment/config/graphProxyAdmin.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import hre from 'hardhat'
import { NamedAccounts } from '../../../tasks/type-extensions'
import { NamedAccounts } from '../../../gre/type-extensions'

describe('GraphProxyAdmin configuration', () => {
const {
Expand Down
2 changes: 1 addition & 1 deletion e2e/deployment/config/graphToken.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import hre from 'hardhat'
import { NamedAccounts } from '../../../tasks/type-extensions'
import { NamedAccounts } from '../../../gre/type-extensions'

describe('GraphToken configuration', () => {
const {
Expand Down
2 changes: 1 addition & 1 deletion e2e/deployment/config/rewardsManager.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import hre from 'hardhat'
import { NamedAccounts } from '../../../tasks/type-extensions'
import { NamedAccounts } from '../../../gre/type-extensions'

describe('RewardsManager configuration', () => {
const {
Expand Down
2 changes: 1 addition & 1 deletion e2e/deployment/config/subgraphNFT.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import hre from 'hardhat'
import { NamedAccounts } from '../../../tasks/type-extensions'
import { NamedAccounts } from '../../../gre/type-extensions'

describe('SubgraphNFT configuration', () => {
const {
Expand Down
223 changes: 223 additions & 0 deletions gre/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# Graph Runtime Environment (GRE)

GRE is a hardhat plugin that extends hardhat's runtime environment to inject additional functionality related to the usage of the Graph Protocol.

### Features

- Provides a simple interface to interact with protocol contracts
- Exposes protocol configuration via graph config file and address book
- Provides account management methods for convenience
- Multichain! Supports both L1 and L2 layers of the protocol simultaneously

### Usage

#### Example
Import GRE using `import './gre/gre'` on your hardhat config file and then:

```js
// Use L2 governor account to set the L1 token address on the L2 gateway
const { l1, l2 } = hre.graph()

const { GraphToken } = l1.contracts

const { L2GraphTokenGateway } = l2.contracts
const { governor } = await l2.getNamedAccounts()

const tx = L2GraphTokenGateway.connect(governor).setL1TokenAddress(GraphToken.address)
```

#### Network selection

GRE supports both the L1 and L2 networks of the Graph Protocol by default. It will use hardhat's network defined via `--network` as the "main" network and then automatically detect which is the appropriate counterpart network in L1 or L2.

Example:

```bash
# L1: goerli and L2: arbitrum-goerli
hh console --network goerli

# L1: mainnet and L2: arbitrum-one
hh console --network arbitrum-one

# L1: mainnet and L2: arbitrum-one > same as previous
hh console --network mainnet
```

#### Configuration

To use GRE you'll need to configure the target networks. That is done via either hardhat's config file using the `networks` [config field](https://hardhat.org/hardhat-runner/docs/config#json-rpc-based-networks) or by passing the appropriate arguments to `hre.graph()` initializer.

__Note__: The "main" network, defined by hardhat's `--network` flag _MUST_ be properly configured for GRE to initialize successfully. It's not necessary to configure the counterpart network if you don't plan on using it.

**Hardhat: Network config**
```js
networks: {
goerli: {
chainId: 5,
url: `https://goerli.infura.io/v3/123456`
accounts: {
mnemonic: 'test test test test test test test test test test test test',
},
graphConfig: 'config/graph.goerli.yml'
},
}
```

Fields:
- **(_REQUIRED_) chainId**: the chainId of the network. This field is not required by hardhat but it's used by GRE to simplify the API.
- **(_REQUIRED_) url**: the RPC endpoint of the network.
- **(_OPTIONAL_) accounts**: the accounts to use on the network. These will be used by the account management functions on GRE.
- **(_OPTIONAL_) graphConfig**: the path to the graph config file for the network.

**Hardhat: Graph config**

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


```js
...
networks: {
...
},
graph: {
addressBook: 'addresses.json'
l1GraphConfig: 'config/graph.mainnet.yml'
l2GraphConfig: 'config/graph.arbitrum-one.yml'
}
...
```

Fields:
- **(_OPTIONAL_) addressBook**: the path to the address book.
- **(_REQUIRED_) l1GraphConfig**: default path to the graph config file for L1 networks. This will be used if the `graphConfig` field is not defined on the network config.
- **(_REQUIRED_) l2GraphConfig**: default path to the graph config file for L2 networks. This will be used if the `graphConfig` field is not defined on the network config.

**Options: Graph initializer**

The GRE initializer also allows you to set the address book and the graph config files like so:
```js
const graph = hre.graph({
addressBook: 'addresses.json',
l1GraphConfig: 'config/graph.mainnet.yml'
l2GraphConfig: 'config/graph.arbitrum-one.yml'
})

// Here graphConfig will apply only to the "main" network given by --network
const graph = hre.graph({
addressBook: 'addresses.json',
graphConfig: 'config/graph.mainnet.yml'
})
```

**Config priority**

The path to the graph config and the address book can be set in multiple ways. The plugin will use the following order to determine the path to the graph config file:

1) `hre.graph({ ... })` init parameters `l1GraphConfigPath` and `l2GraphConfigPath`
2) `hre.graph({ ...})` init parameter graphConfigPath (but only for the "main" network)
3) `networks.<NETWORK_NAME>.graphConfig` network config parameter `graphConfig` in hardhat config file
4) `graph.l<X>GraphConfig` graph config parameters `l1GraphConfig` and `l2GraphConfig` in hardhat config file

The priority for the address book is:
1) `hre.graph({ ... })` init parameter `addressBook`
2) `graph.addressBook` graph config parameter `addressBook` in hardhat config file

### API

GRE exposes functionality via a simple API:

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

// To access the L1 object
graph.l1

// To access the L2 object
graph.l2
```

The interface for both `l1` and `l2` objects looks like this:

```ts
export interface GraphNetworkEnvironment {
chainId: number
contracts: NetworkContracts
graphConfig: any
addressBook: AddressBook
getNamedAccounts: () => Promise<NamedAccounts>
getTestAccounts: () => Promise<SignerWithAddress[]>
getDeployer: () => Promise<SignerWithAddress>
}
```

**ChainId**

The chainId of the network.

**Contracts**

Returns an object with all the contracts available in the network. Connects using a provider created with the URL specified in hardhat's network configuration (it doesn't use the usual hardhat `hre.ethers.provider`).

```js
> const graph = hre.graph()

// Print curation default reserve ratio on L1
> await g.l1.contracts.Curation.defaultReserveRatio()
500000
```

**Graph Config**

Returns an object that grants raw access to the graph config file for the protocol. The graph config file is a YAML file that contains all the parameters with which the protocol was deployed.

> TODO: add better APIs to interact with the graph config file.

**Address Book**

Returns an object that allows interacting with the address book.

```js
> const graph = hre.graph()
> graph.l1.addressBook.getEntry('Curation')
{
address: '0xE59B4820dDE28D2c235Bd9A73aA4e8716Cb93E9B',
initArgs: [
'0x48eD7AfbaB432d1Fc6Ea84EEC70E745d9DAcaF3B',
'0x2DFDC3e11E035dD96A4aB30Ef67fab4Fb6EC01f2',
'0x8bEd0a89F18a801Da9dEA994D475DEa74f75A059',
'500000',
'10000',
'1000000000000000000'
],
creationCodeHash: '0x25a7b6cafcebb062169bc25fca9bcce8f23bd7411235859229ae3cc99b9a7d58',
runtimeCodeHash: '0xaf2d63813a0e5059f63ec46e1b280eb9d129d5ad548f0cdd1649d9798fde10b6',
txHash: '0xf1b1f0f28b80068bcc9fd6ef475be6324a8b23cbdb792f7344f05ce00aa997d7',
proxy: true,
implementation: {
address: '0xAeaA2B058539750b740E858f97159E6856948670',
creationCodeHash: '0x022576ab4b739ee17dab126ea7e5a6814bda724aa0e4c6735a051b38a76bd597',
runtimeCodeHash: '0xc7b1f9bef01ef92779aab0ae9be86376c47584118c508f5b4e612a694a4aab93',
txHash: '0x400bfb7b6c384363b859a66930590507ddca08ebedf64b20c4b5f6bc8e76e125'
}
}
```

**Account management: getNamedAccounts**
Returns an object with all the named accounts available in the network. Named accounts are accounts that have special roles in the protocol, they are defined in the graph config file.

```js
> const graph = hre.graph()
> const namedAccounts = await g.l1.getNamedAccounts()
> namedAccounts.governor.address
'0xf1135bFF22512FF2A585b8d4489426CE660f204c'
```

The accounts are initialized from the graph config file but if the correct mnemonic or private key is provided via hardhat network configuration then they will be fully capable of signing transactions.

**Account management: getTestAccounts**
Returns an object with accounts which can be used for testing/interacting with the protocol. These are obtained from hardhat's network configuration using the provided mnemonic or private key.

**Account management: getDeployer**
Returns an object with the would-be deployer account. The deployer is by convention the first (index 0) account derived from the mnemonic or private key provided via hardhat network configuration.

It's important to note that the deployer is not a named account as it's derived from the provided mnemonic so it won't necessarily match the actual deployer for a given deployment. It's the account that would be used to deploy the protocol with the current configuration. It's not possible at the moment to recover the actual deployer account from a deployed protocol.
55 changes: 55 additions & 0 deletions gre/accounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { EthersProviderWrapper } from '@nomiclabs/hardhat-ethers/internal/ethers-provider-wrapper'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { getItemValue, readConfig } from '../cli/config'
import { AccountNames, NamedAccounts } from './type-extensions'

const namedAccountList: AccountNames[] = [
'arbitrator',
'governor',
'authority',
'availabilityOracle',
'pauseGuardian',
'allocationExchangeOwner',
]

export async function getNamedAccounts(
provider: EthersProviderWrapper,
graphConfigPath: string,
): Promise<NamedAccounts> {
const namedAccounts = namedAccountList.reduce(async (accountsPromise, name) => {
const accounts = await accountsPromise
const address = getItemValue(readConfig(graphConfigPath, true), `general/${name}`)
accounts[name] = await SignerWithAddress.create(provider.getSigner(address))
return accounts
}, Promise.resolve({} as NamedAccounts))

return namedAccounts
}

export async function getDeployer(provider: EthersProviderWrapper): Promise<SignerWithAddress> {
const signer = provider.getSigner(0)
return SignerWithAddress.create(signer)
}

export async function getTestAccounts(
provider: EthersProviderWrapper,
graphConfigPath: string,
): Promise<SignerWithAddress[]> {
// Get list of privileged accounts we don't want as test accounts
const namedAccounts = await getNamedAccounts(provider, graphConfigPath)
const blacklist = namedAccountList.map((a) => {
const account = namedAccounts[a]
return account.address
})
blacklist.push((await getDeployer(provider)).address)

// Get signers and filter out blacklisted accounts
const accounts = await provider.listAccounts()
const signers = await Promise.all(
accounts.map(async (account) => await SignerWithAddress.create(provider.getSigner(account))),
)

return signers.filter((s) => {
return !blacklist.includes(s.address)
})
}
Loading