Skip to content
Closed
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
27 changes: 26 additions & 1 deletion TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ The command will perform the following actions:
- Run `migrate` hardhat task to deploy the protocol
- Run `migrate:ownership` hardhat task to transfer ownership of governed contracts to the governor
- Run `migrate:unpause` to unpause the protocol
- Run `e2e` hardhat task to run all e2e tests, including scenarios
- Run `e2e` hardhat task to run all deployment tests (config and init)
- Run `e2e:scenario` hardhat task to run a scenario

### Other networks

Expand All @@ -70,6 +71,30 @@ npx hardhat e2e:scenario <scenario> --network <network> --graph-config config/gr

Note that this command will only run the tests so you need to be sure the protocol is already deployed and the graph config file and address book files are up to date.

### Scenarios

#### Typical protocol actions

Common protocol actions can be tested by running the scenarios:

```bash
# Set up subgraphs: publish and signal
npx hardhat e2e:scenario create-subgraph --network <network> --graph-config config/graph.<network>.yml

# Set up indexers: stake and open allocations
npx hardhat e2e:scenario open-allocations --network <network> --graph-config config/graph.<network>.yml

# Close some allocations
# Need to wait at least 1 epoch before running. On localhost, epoch is automatically advanced
npx hardhat e2e:scenario close-allocations --network <network> --graph-config config/graph.<network>.yml
```

These scenarios will setup the protocol to have at least:

- two indexers with stake
- each indexer has two or three allocations, one of which is open
- four subgraphs, with 0, 400, 10000 and 12500 tokens signalled, with signal coming from three different curators

### How to add scenarios

Scenarios are defined by an optional script and a test file:
Expand Down
48 changes: 48 additions & 0 deletions e2e/scenarios/close-allocations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { expect } from 'chai'
import hre from 'hardhat'
import { AllocationFixture, getIndexerFixtures, IndexerFixture } from './fixtures/indexers'

enum AllocationState {
Null,
Active,
Closed,
Finalized,
Claimed,
}

let indexerFixtures: IndexerFixture[]

describe('Close allocations', () => {
const { contracts, getTestAccounts } = hre.graph()
const { Staking } = contracts

before(async () => {
indexerFixtures = getIndexerFixtures(await getTestAccounts())
})

describe('Allocations', () => {
let allocations: AllocationFixture[] = []
let openAllocations: AllocationFixture[] = []
let closedAllocations: AllocationFixture[] = []

before(async () => {
allocations = indexerFixtures.map((i) => i.allocations).flat()
openAllocations = allocations.filter((a) => !a.close)
closedAllocations = allocations.filter((a) => a.close)
})

it(`some allocatons should be open`, async function () {
for (const allocation of openAllocations) {
const state = await Staking.getAllocationState(allocation.signer.address)
expect(state).eq(AllocationState.Active)
}
})

it(`some allocatons should be closed`, async function () {
for (const allocation of closedAllocations) {
const state = await Staking.getAllocationState(allocation.signer.address)
expect(state).eq(AllocationState.Closed)
}
})
})
})
42 changes: 42 additions & 0 deletions e2e/scenarios/close-allocations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import hre from 'hardhat'
import { closeAllocation } from './lib/staking'
import { advanceToNextEpoch } from '../../test/lib/testHelpers'
import { fundAccountsEth } from './lib/accounts'
import { getIndexerFixtures } from './fixtures/indexers'
import { fund } from './fixtures/funds'

async function main() {
const graph = hre.graph()
const indexerFixtures = getIndexerFixtures(await graph.getTestAccounts())

const deployer = await graph.getDeployer()
const indexers = indexerFixtures.map((i) => i.signer.address)

// == Fund participants
console.log('\n== Fund indexers')
await fundAccountsEth(deployer, indexers, fund.ethAmount)

// == Time travel on local networks, ensure allocations can be closed
if (['hardhat', 'localhost'].includes(hre.network.name)) {
console.log('\n== Advancing to next epoch')
await advanceToNextEpoch(graph.contracts.EpochManager)
}

// == Close allocations
console.log('\n== Close allocations')

for (const indexer of indexerFixtures) {
for (const allocation of indexer.allocations.filter((a) => a.close)) {
await closeAllocation(graph.contracts, indexer.signer, allocation.signer.address)
}
}
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exitCode = 1
})
73 changes: 73 additions & 0 deletions e2e/scenarios/create-subgraphs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect } from 'chai'
import hre from 'hardhat'
import { recreatePreviousSubgraphId } from './lib/subgraph'
import { BigNumber } from 'ethers'
import { CuratorFixture, getCuratorFixtures } from './fixtures/curators'
import { SubgraphFixture, getSubgraphFixtures, getSubgraphOwner } from './fixtures/subgraphs'
import { fund } from './fixtures/funds'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'

let curatorFixtures: CuratorFixture[]
let subgraphFixtures: SubgraphFixture[]
let subgraphOwnerFixture: SignerWithAddress

describe('Publish subgraphs', () => {
const { contracts, getTestAccounts } = hre.graph()
const { GNS, GraphToken, Curation } = contracts

before(async () => {
const testAccounts = await getTestAccounts()
curatorFixtures = getCuratorFixtures(testAccounts)
subgraphFixtures = getSubgraphFixtures()
subgraphOwnerFixture = getSubgraphOwner(testAccounts)
})

describe('GRT balances', () => {
it(`curator balances should match airdropped amount minus signalled`, async function () {
for (const curator of curatorFixtures) {
const address = curator.signer.address
const balance = await GraphToken.balanceOf(address)
expect(balance).gte(fund.grtAmount.sub(curator.signalled))
}
})
})

describe('Subgraphs', () => {
it(`should be published`, async function () {
for (let i = 0; i < subgraphFixtures.length; i++) {
const subgraphId = await recreatePreviousSubgraphId(
contracts,
subgraphOwnerFixture.address,
subgraphFixtures.length - i,
)
const isPublished = await GNS.isPublished(subgraphId)
expect(isPublished).eq(true)
}
})

it(`should have signal`, async function () {
for (let i = 0; i < subgraphFixtures.length; i++) {
const subgraph = subgraphFixtures[i]
const subgraphId = await recreatePreviousSubgraphId(
contracts,
subgraphOwnerFixture.address,
subgraphFixtures.length - i,
)

let totalSignal: BigNumber = BigNumber.from(0)
for (const curator of curatorFixtures) {
const _subgraph = curator.subgraphs.find((s) => s.deploymentId === subgraph.deploymentId)
if (_subgraph) {
totalSignal = totalSignal.add(_subgraph.signal)
}
}

const tokens = await GNS.subgraphTokens(subgraphId)
const MAX_PPM = 1000000
const curationTax = await Curation.curationTaxPercentage()
const tax = totalSignal.mul(curationTax).div(MAX_PPM)
expect(tokens).eq(totalSignal.sub(tax))
}
})
})
})
57 changes: 57 additions & 0 deletions e2e/scenarios/create-subgraphs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import hre from 'hardhat'
import { publishNewSubgraph } from './lib/subgraph'
import { fundAccountsEth, fundAccountsGRT } from './lib/accounts'
import { signal } from './lib/curation'
import { getSubgraphFixtures, getSubgraphOwner } from './fixtures/subgraphs'
import { getCuratorFixtures } from './fixtures/curators'
import { fund } from './fixtures/funds'

async function main() {
const graph = hre.graph()
const testAccounts = await graph.getTestAccounts()

const subgraphFixtures = getSubgraphFixtures()
const subgraphOwnerFixture = getSubgraphOwner(testAccounts)
const curatorFixtures = getCuratorFixtures(testAccounts)

const deployer = await graph.getDeployer()
const subgraphOwners = [subgraphOwnerFixture.address]
const curators = curatorFixtures.map((c) => c.signer.address)

// == Fund participants
console.log('\n== Fund subgraph owners and curators')
await fundAccountsEth(deployer, [...subgraphOwners, ...curators], fund.ethAmount)
await fundAccountsGRT(deployer, curators, fund.grtAmount, graph.contracts.GraphToken)

// == Publish subgraphs
console.log('\n== Publishing subgraphs')

for (const subgraph of subgraphFixtures) {
const id = await publishNewSubgraph(
graph.contracts,
subgraphOwnerFixture,
subgraph.deploymentId,
)
const subgraphData = subgraphFixtures.find((s) => s.deploymentId === subgraph.deploymentId)
if (subgraphData) subgraphData.subgraphId = id
}

// == Signal subgraphs
console.log('\n== Signaling subgraphs')
for (const curator of curatorFixtures) {
for (const subgraph of curator.subgraphs) {
const subgraphData = subgraphFixtures.find((s) => s.deploymentId === subgraph.deploymentId)
if (subgraphData)
await signal(graph.contracts, curator.signer, subgraphData.subgraphId, subgraph.signal)
}
}
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exitCode = 1
})
73 changes: 73 additions & 0 deletions e2e/scenarios/fixtures/curators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { BigNumber } from 'ethers'
import { toGRT } from '../../../cli/network'

export interface CuratorFixture {
signer: SignerWithAddress
signalled: BigNumber
subgraphs: SubgraphFixture[]
}

export interface SubgraphFixture {
deploymentId: string
signal: BigNumber
}

// Test account indexes
// 3: curator1
// 4: curator2
// 5: curator3

export const getCuratorFixtures = (signers: SignerWithAddress[]): CuratorFixture[] => {
return [
// curator1
{
signer: signers[3],
signalled: toGRT(10_400),
subgraphs: [
{
deploymentId: '0x0653445635cc1d06bd2370d2a9a072406a420d86e7fa13ea5cde100e2108b527',
signal: toGRT(400),
},
{
deploymentId: '0x3093dadafd593b5c2d10c16bf830e96fc41ea7b91d7dabd032b44331fb2a7e51',
signal: toGRT(4_000),
},
{
deploymentId: '0xb3fc2abc303c70a16ab9d5fc38d7e8aeae66593a87a3d971b024dd34b97e94b1',
signal: toGRT(6_000),
},
],
},
// curator2
{
signer: signers[4],
signalled: toGRT(4_500),
subgraphs: [
{
deploymentId: '0x3093dadafd593b5c2d10c16bf830e96fc41ea7b91d7dabd032b44331fb2a7e51',
signal: toGRT(2_000),
},
{
deploymentId: '0xb3fc2abc303c70a16ab9d5fc38d7e8aeae66593a87a3d971b024dd34b97e94b1',
signal: toGRT(2_500),
},
],
},
// curator3
{
signer: signers[5],
signalled: toGRT(8_000),
subgraphs: [
{
deploymentId: '0x3093dadafd593b5c2d10c16bf830e96fc41ea7b91d7dabd032b44331fb2a7e51',
signal: toGRT(4_000),
},
{
deploymentId: '0xb3fc2abc303c70a16ab9d5fc38d7e8aeae66593a87a3d971b024dd34b97e94b1',
signal: toGRT(4_000),
},
],
},
]
}
6 changes: 6 additions & 0 deletions e2e/scenarios/fixtures/funds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { toGRT } from '../../../cli/network'

export const fund = {
ethAmount: toGRT(0.01),
grtAmount: toGRT(100_000),
}
Loading