Skip to content

feat: add support for scenario testing #660

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 14 commits into from
Jul 29, 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
71 changes: 3 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,72 +76,11 @@ yarn build

# Testing

Testing is done with the following stack:
For testing details see [TESTING.md](./TESTING.md).

- [Waffle](https://getwaffle.io/)
- [Hardhat](https://hardhat.org/)
- [Typescript](https://www.typescriptlang.org/)
- [Ethers](https://docs.ethers.io/v5/)

## Contracts

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

## E2E Testing

End to end tests are also available and can be run against a local network or a live network. These can be useful to validate a protocol deployment is configured and working as expected.

There are several types of e2e tests which can be run separately:
- **deployment/config**
- Test the configuration of deployed contracts (parameters that don't change over time).
- Can be run against any network at any time and the tests should pass.
- Only read only interactions with the blockchain.
- Example: a test validating the curation default reserve ratio matches the value in the graph config file.
- **deployment/init**
- Test the initialization of deployed contracts (parameters that change with protocol usage).
- Can be run against a "fresh" protocol deployment. Running these tests against a protocol with pre-existing state will probably fail.
- Only read only interactions with the blockchain.
- Example: a test validating that the GRT total supply equals 10B, this is only true on a freshly deployed protocol until the first allocation is closed and protocol issuance kicks in.
- **scenarios**
- Test the execution of common protocol actions.
- Can be run against any network at any time and the tests should pass.
- Read and write interactions with the blockchain. _Requires an account with sufficient balance!_
- Example: a test validating that a user can add signal to a subgraph.

### Hardhat local node

To run all e2e tests against a hardhat local node run:

```bash
yarn test:e2e
```

The command will perform the following actions:

- Start a hardhat node (localhost)
- Run `migrate:accounts` hardhat task to create keys for all protocol roles (deployer, governor, arbiter, etc). This currently doesn't support multisig accounts.
- 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

### Other networks

To run tests against a live testnet or even mainnet run:

```bash
# All e2e tests
npx hardhat e2e --network <network> --graph-config config/graph.<network>.yml

# Only deployment config tests
npx hardhat e2e:config --network <network> --graph-config config/graph.<network>.yml

# Only deployment init tests
npx hardhat e2e:init --network <network> --graph-config config/graph.<network>.yml
```
# Deploying Contracts

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.
In order to run deployments, see [DEPLOYMENT.md](./DEPLOYMENT.md).

# Interacting with the contracts

Expand Down Expand Up @@ -187,10 +126,6 @@ Considerations when forking a chain:

- When running on the `localhost` network it will use by default a deterministic seed for testing purposes. If you want to connect to a local node that is forking while retaining the capability to impersonate accounts or use local accounts you need to set the FORK=true environment variable.

# Deploying Contracts

In order to run deployments, see [`./DEPLOYMENT.md`](./DEPLOYMENT.md).

# Contributing

Contributions are welcomed and encouraged! You can do so by:
Expand Down
84 changes: 84 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Testing

Testing is done with the following stack:

- [Waffle](https://getwaffle.io/)
- [Hardhat](https://hardhat.org/)
- [Typescript](https://www.typescriptlang.org/)
- [Ethers](https://docs.ethers.io/v5/)

## Unit testing

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

## E2E Testing

End to end tests are also available and can be run against a local network or a live network. These can be useful to validate that a protocol deployment is configured and working as expected.

There are several types of e2e tests which can be run separately:
- **deployment/config**
- Test the configuration of deployed contracts (parameters that don't change over time).
- Can be run against any network at any time and the tests should pass.
- Only read only interactions with the blockchain.
- Example: a test validating the curation default reserve ratio matches the value in the graph config file.
- **deployment/init**
- Test the initialization of deployed contracts (parameters that change with protocol usage).
- Can be run against a "fresh" protocol deployment. Running these tests against a protocol with pre-existing state will probably fail.
- Only read only interactions with the blockchain.
- Example: a test validating that the GRT total supply equals 10B, this is only true on a freshly deployed protocol until the first allocation is closed and protocol issuance kicks in.
- **scenarios**
- Test the execution of common protocol actions.
- Can be run against any network at any time and the tests should pass.
- Read and write interactions with the blockchain. _Requires an account with sufficient balance!_
- Example: a test validating that a user can add signal to a subgraph.

### Hardhat local node

To run all e2e tests against a hardhat local node run:

```bash
yarn test:e2e
```

The command will perform the following actions:

- Start a hardhat node (localhost)
- Run `migrate:accounts` hardhat task to create keys for all protocol roles (deployer, governor, arbiter, etc). This currently doesn't support multisig accounts.
- 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

### Other networks

To run tests against a live testnet or even mainnet run:

```bash
# All e2e tests
npx hardhat e2e --network <network> --graph-config config/graph.<network>.yml

# Only deployment config tests
npx hardhat e2e:config --network <network> --graph-config config/graph.<network>.yml

# Only deployment init tests
npx hardhat e2e:init --network <network> --graph-config config/graph.<network>.yml

# Only a specific scenario
npx hardhat e2e:scenario <scenario> --network <network> --graph-config config/graph.<network>.yml
```

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.

### How to add scenarios

Scenarios are defined by an optional script and a test file:

- Optional ts script
- The objective of this script is to perform actions on the protocol to advance it's state to the desired one.
- Should follow hardhat script convention.
- Should be named e2e/scenarios/{scenario-name}.ts.
- They run before the test file.
- Test file
- Should be named e2e/scenarios/{scenario-name}.test.ts.
- Standard chai/mocha/hardhat/ethers test file.
15 changes: 15 additions & 0 deletions e2e/scenarios/lib/curation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { BigNumberish, Signer } from 'ethers'
import { NetworkContracts } from '../../../cli/contracts'

export const signal = async (
contracts: NetworkContracts,
curator: Signer,
subgraphId: string,
amount: BigNumberish,
): Promise<void> => {
const { GNS } = contracts

// Add signal
const tx = await GNS.connect(curator).mintSignal(subgraphId, amount, 0)
await tx.wait()
}
19 changes: 12 additions & 7 deletions e2e/scenarios/lib/staking.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import hre from 'hardhat'
import { Staking } from '../../../build/types/Staking'
import { BigNumberish, Signer } from 'ethers'
import { NetworkContracts } from '../../../cli/contracts'

export const stake = async (amountWei: string): Promise<void> => {
const graph = hre.graph()
export const stake = async (
contracts: NetworkContracts,
indexer: Signer,
amount: BigNumberish,
): Promise<void> => {
const { GraphToken, Staking } = contracts

// Approve
const stakeAmountWei = hre.ethers.utils.parseEther(amountWei).toString()
await graph.contracts.GraphToken.approve(graph.contracts.Staking.address, stakeAmountWei)
const txApprove = await GraphToken.connect(indexer).approve(Staking.address, amount)
await txApprove.wait()

// Stake
await graph.contracts.Staking.stake(stakeAmountWei)
const txStake = await Staking.connect(indexer).stake(amount)
await txStake.wait()
}
17 changes: 17 additions & 0 deletions e2e/scenarios/lib/subgraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Signer, utils } from 'ethers'
import { NetworkContracts } from '../../../cli/contracts'

const { hexlify, randomBytes } = utils

export const publishNewSubgraph = async (
contracts: NetworkContracts,
publisher: Signer,
deploymentId: string,
): Promise<void> => {
const tx = await contracts.GNS.connect(publisher).publishNewSubgraph(
deploymentId,
hexlify(randomBytes(32)),
hexlify(randomBytes(32)),
)
await tx.wait()
}
19 changes: 19 additions & 0 deletions e2e/scenarios/lib/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { BigNumberish, ContractReceipt, Signer } from 'ethers'
import { NetworkContracts } from '../../../cli/contracts'

export const airdrop = async (
contracts: NetworkContracts,
sender: Signer,
beneficiaries: string[],
amount: BigNumberish,
): Promise<void> => {
const { GraphToken } = contracts

const txs: Promise<ContractReceipt>[] = []

for (const beneficiary of beneficiaries) {
const tx = await GraphToken.connect(sender).transfer(beneficiary, amount)
txs.push(tx.wait())
}
await Promise.all(txs)
}
72 changes: 72 additions & 0 deletions e2e/scenarios/scenario1.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { expect } from 'chai'
import { solidityKeccak256 } from 'ethers/lib/utils'
import hre from 'hardhat'
import { fixture } from './scenario1'

describe('Scenario 1', () => {
const {
contracts: { GraphToken, Staking, GNS },
getTestAccounts,
} = hre.graph()

let indexer1: SignerWithAddress
let indexer2: SignerWithAddress
let curator1: SignerWithAddress
let curator2: SignerWithAddress
let curator3: SignerWithAddress
let subgraphOwner: SignerWithAddress

let indexers: SignerWithAddress[] = []
let curators: SignerWithAddress[] = []

before(async () => {
;[indexer1, indexer2, subgraphOwner, curator1, curator2, curator3] = await getTestAccounts()
indexers = [indexer1, indexer2]
curators = [curator1, curator2, curator3]
})

describe('GRT balances', () => {
it('indexer1 should match airdropped amount minus staked', async function () {
const balance = await GraphToken.balanceOf(indexer1.address)
expect(balance).eq(fixture.grtAmount.sub(fixture.indexer1.stake))
})

it('indexer2 should match airdropped amount minus staked', async function () {
const balance = await GraphToken.balanceOf(indexer2.address)
expect(balance).eq(fixture.grtAmount.sub(fixture.indexer2.stake))
})

it('curator should match airdropped amount', async function () {
for (const account of curators) {
const balance = await GraphToken.balanceOf(account.address)
expect(balance).eq(fixture.grtAmount)
}
})
})

describe('Staking', () => {
it('indexer1 should have tokens staked', async function () {
const tokensStaked = (await Staking.stakes(indexer1.address)).tokensStaked
expect(tokensStaked).eq(fixture.indexer1.stake)
})
it('indexer2 should have tokens staked', async function () {
const tokensStaked = (await Staking.stakes(indexer2.address)).tokensStaked
expect(tokensStaked).eq(fixture.indexer2.stake)
})
})

// describe('Subgraphs', () => {
// for (const subgraphDeploymentId of fixture.subgraphs) {
// it(`${subgraphDeploymentId} is published`, async function () {
// const seqID = await GNS.nextAccountSeqID(subgraphOwner.address)
// const subgraphId = solidityKeccak256(['address', 'uint256'], [subgraphOwner.address, seqID])

// await GNS.subgraphs(subgraphDeploymentId)

// const isPublished = await GNS.isPublished(subgraphId)
// expect(isPublished).eq(true)
// })
// }
// })
})
Loading