Skip to content

feat(e2e): deploy and configure both layers with the e2e script #717

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 15 commits into from
Nov 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
4 changes: 1 addition & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ jobs:
run: |
git clone https://github.com/edgeandnode/nitro
pushd nitro
git checkout ci
git submodule update --init --recursive
./test-node.bash --init --no-blockscout --detach
popd
NETWORK=localnitrol1 ADDRESS_BOOK=addresses.json GRAPH_CONFIG=config/graph.localhost.yml yarn test:e2e
NETWORK=localnitrol2 ADDRESS_BOOK=addresses.json GRAPH_CONFIG=config/graph.arbitrum-localhost.yml yarn test:e2e
L1_NETWORK=localnitrol1 L2_NETWORK=localnitrol2 yarn test:e2e
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ bin/
# Coverage and other reports
/reports
coverage.json

# Local test files
addresses-local.json
localNetwork.json
arbitrum-addresses-local.json
59 changes: 26 additions & 33 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,38 @@ There are several types of e2e tests which can be run separately:
- 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
### Hardhat local node (L1)

To run all e2e tests against a hardhat local node run:
It can be useful to run E2E tests against a fresh protocol deployment on L1, this can be done with the following:

```bash
yarn test:e2e
```

The command will perform the following actions:
The command will:
- start a hardhat local node
- deploy the L1 protocol
- configure the new L1 deployment
- Run all L1 e2e tests

- 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 deployment tests (config and init)
- Run `e2e:scenario` hardhat task to run a scenario
### Arbitrum Nitro testnodes (L1/L2)

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
```

In this case the command will:
- deploy the L1 protocol
- configure the new L1 deployment
- deploy the L2 protocol
- configure the new L2 deployment
- configure the L1/L2 bridge
- Run all L1 e2e tests
- Run all L2 e2e tests

Note that you'll need to setup the testnodes before running the tests. See [Quick Setup](https://github.com/edgeandnode/nitro#quick-setup) for details on how to do this.

### Other networks

Expand Down Expand Up @@ -82,26 +97,4 @@ Scenarios are defined by an optional script and a test file:
- They run before the test file.
- Test file
- Should be named e2e/scenarios/{scenario-name}.test.ts.
- Standard chai/mocha/hardhat/ethers test file.

## Setting up Arbitrum's testnodes

Arbitrum provides a quick way of setting up L1 and L2 testnodes for local development and testing. The following steps will guide you through the process of setting them up. Note that a local installation of Docker and Docker Compose is required.

```bash
git clone https://github.com/offchainlabs/nitro
cd nitro
git submodule update --init --recursive

# Apply any changes you might want, see below for more info, and then start the testnodes
./test-node.bash --init
```

**Useful information**
- L1 RPC: [http://localhost:8545](http://localhost:8545/)
- L2 RPC: [http://localhost:8547](http://localhost:8547/)
- Blockscout explorer (L2 only): [http://localhost:4000/](http://localhost:4000/)
- Prefunded genesis key (L1 and L2): `e887f7d17d07cc7b8004053fb8826f6657084e88904bb61590e498ca04704cf2`

**Enable automine on L1**
In `docker-compose.yml` file edit the `geth` service command by removing the `--dev.period 1` flag.
- Standard chai/mocha/hardhat/ethers test file.
15 changes: 10 additions & 5 deletions cli/commands/bridge/to-l1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,16 @@ export const startSendToL1 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Prom
const l2Receipt = new L2TransactionReceipt(receipt)
const l2ToL1Message = (await l2Receipt.getL2ToL1Messages(cli.wallet))[0]

logger.info(`The transaction generated an L2 to L1 message in outbox with eth block number:`)
logger.info(l2ToL1Message.event.ethBlockNum.toString())
logger.info(
`After the dispute period is finalized (in ~1 week), you can finalize this by calling`,
)
const ethBlockNum = l2ToL1Message.getFirstExecutableBlock(l2Provider)
if (ethBlockNum === null) {
logger.info(`L2 to L1 message can or already has been executed. If not finalized call`)
} else {
logger.info(`The transaction generated an L2 to L1 message in outbox with eth block number:`)
logger.info(ethBlockNum.toString())
logger.info(
`After the dispute period is finalized (in ~1 week), you can finalize this by calling`,
)
}
logger.info(`finish-send-to-l1 with the following txhash:`)
logger.info(l2Receipt.transactionHash)
}
Expand Down
6 changes: 4 additions & 2 deletions cli/commands/bridge/to-l2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const checkAndRedeemMessage = async (l1ToL2Message: L1ToL2MessageWriter) => {
logger.warn('Funds were deposited on L2 but the retryable ticket was not redeemed')
logAutoRedeemReason(autoRedeemRec)
logger.info('Attempting to redeem...')
await l1ToL2Message.redeem()
await l1ToL2Message.redeem(process.env.CI ? { gasLimit: 2_000_000 } : {})
const redeemAttempt = await l1ToL2Message.getSuccessfulRedeem()
if (redeemAttempt.status == L1ToL2MessageStatus.REDEEMED) {
l2TxHash = redeemAttempt.l2TxReceipt ? redeemAttempt.l2TxReceipt.transactionHash : 'null'
Expand All @@ -45,7 +45,8 @@ export const sendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<v

// parse provider
const l1Provider = cli.wallet.provider
const l2Provider = getProvider(cliArgs.l2ProviderUrl)
// TODO: fix this hack for usage with hardhat
const l2Provider = cliArgs.l2Provider ? cliArgs.l2Provider : getProvider(cliArgs.l2ProviderUrl)
const l1ChainId = cli.chainId
const l2ChainId = (await l2Provider.getNetwork()).chainId
if (chainIdIsL2(l1ChainId) || !chainIdIsL2(l2ChainId)) {
Expand Down Expand Up @@ -100,6 +101,7 @@ export const sendToL2 = async (cli: CLIEnvironment, cliArgs: CLIArgs): Promise<v
const txReceipt = await sendTransaction(cli.wallet, l1Gateway, 'outboundTransfer', txParams, {
value: ethValue,
})

// get l2 ticket status
if (txReceipt.status == 1) {
logger.info('Waiting for message to propagate to L2...')
Expand Down
27 changes: 20 additions & 7 deletions cli/cross-chain.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { L1ToL2MessageGasEstimator } from '@arbitrum/sdk'
import { L1ToL2MessageNoGasParams } from '@arbitrum/sdk/dist/lib/message/L1ToL2MessageCreator'
import { GasOverrides } from '@arbitrum/sdk/dist/lib/message/L1ToL2MessageGasEstimator'
import { BigNumber, providers } from 'ethers'
import { parseEther } from 'ethers/lib/utils'

Expand Down Expand Up @@ -44,20 +46,31 @@ export const estimateRetryableTxGas = async (
logger.info('Estimating retryable ticket gas:')
const baseFee = (await l1Provider.getBlock('latest')).baseFeePerGas
const gasEstimator = new L1ToL2MessageGasEstimator(l2Provider)
const retryableEstimateData: L1ToL2MessageNoGasParams = {
from: gatewayAddress,
to: l2Dest,
data: depositCalldata,
l2CallValue: parseEther('0'),
excessFeeRefundAddress: gatewayAddress,
callValueRefundAddress: gatewayAddress,
}

const estimateOpts: GasOverrides = {}
if (opts.maxGas) estimateOpts.gasLimit = { base: opts.maxGas }
if (opts.maxSubmissionCost) estimateOpts.maxSubmissionFee = { base: opts.maxSubmissionCost }
if (opts.gasPriceBid) estimateOpts.maxFeePerGas = { base: opts.gasPriceBid }

const gasParams = await gasEstimator.estimateAll(
gatewayAddress,
l2Dest,
depositCalldata,
parseEther('0'),
retryableEstimateData,
baseFee as BigNumber,
gatewayAddress,
gatewayAddress,
l1Provider,
estimateOpts,
)

// override fixed values
return {
maxGas: opts.maxGas ?? gasParams.gasLimit,
gasPriceBid: opts.gasPriceBid ?? gasParams.maxFeePerGas,
maxSubmissionCost: opts.maxSubmissionCost ?? gasParams.maxSubmissionFee,
maxSubmissionCost: opts.maxSubmissionCost ?? gasParams.maxSubmissionCost,
}
}
12 changes: 6 additions & 6 deletions config/graph.arbitrum-localhost.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
general:
arbitrator: &arbitrator "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0" # Arbitration Council
governor: &governor "0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b" # Graph Council
authority: &authority "0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d" # Authority that signs payment vouchers
availabilityOracle: &availabilityOracle "0xd03ea8624C8C5987235048901fB614fDcA89b117" # Subgraph Availability Oracle
pauseGuardian: &pauseGuardian "0x95cED938F7991cd0dFcb48F0a06a40FA1aF46EBC" # Protocol pause guardian
allocationExchangeOwner: &allocationExchangeOwner "0x3E5e9111Ae8eB78Fe1CC3bb8915d5D461F3Ef9A9" # Allocation Exchange owner
arbitrator: &arbitrator "0x4237154FE0510FdE3575656B60c68a01B9dCDdF8" # Arbitration Council
governor: &governor "0x1257227a2ECA34834940110f7B5e341A5143A2c4" # Graph Council
authority: &authority "0x12B8D08b116E1E3cc29eE9Cf42bB0AA8129C3215" # Authority that signs payment vouchers
availabilityOracle: &availabilityOracle "0x7694a48065f063a767a962610C6717c59F36b445" # Subgraph Availability Oracle
pauseGuardian: &pauseGuardian "0x601060e0DC5349AA55EC73df5A58cB0FC1cD2e3C" # Protocol pause guardian
allocationExchangeOwner: &allocationExchangeOwner "0xbD38F7b67a591A5cc7D642e1026E5095B819d952" # Allocation Exchange owner

contracts:
Controller:
Expand Down
63 changes: 51 additions & 12 deletions e2e/deployment/config/l1/l1GraphTokenGateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect } from 'chai'
import hre from 'hardhat'
import GraphChain from '../../../../gre/helpers/network'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { getAddressBook } from '../../../../cli/address-book'

describe('[L1] L1GraphTokenGateway configuration', function () {
const graph = hre.graph()
Expand All @@ -13,16 +14,53 @@ describe('[L1] L1GraphTokenGateway configuration', function () {
unauthorized = (await graph.getTestAccounts())[0]
})

it('bridge should be paused', async function () {
it('bridge should not be paused', async function () {
const paused = await L1GraphTokenGateway.paused()
expect(paused).eq(true)
expect(paused).eq(false)
})

it('should be controlled by Controller', async function () {
const controller = await L1GraphTokenGateway.controller()
expect(controller).eq(Controller.address)
})

it('l2GRT should match the L2 GraphToken deployed address', async function () {
const l2GRT = await L1GraphTokenGateway.l2GRT()
expect(l2GRT).eq(graph.l2.contracts.GraphToken.address)
})

it('l2Counterpart should match the deployed L2 GraphTokenGateway address', async function () {
const l2Counterpart = await L1GraphTokenGateway.l2Counterpart()
expect(l2Counterpart).eq(graph.l2.contracts.L2GraphTokenGateway.address)
})

it('escrow should match the deployed L1 BridgeEscrow address', async function () {
const escrow = await L1GraphTokenGateway.escrow()
expect(escrow).eq(graph.l1.contracts.BridgeEscrow.address)
})

it("inbox should match Arbitrum's Inbox address", async function () {
const inbox = await L1GraphTokenGateway.inbox()

// TODO: is there a cleaner way to get the router address?
const arbitrumAddressBook = process.env.ARBITRUM_ADDRESS_BOOK ?? 'arbitrum-addresses-local.json'
const arbAddressBook = getAddressBook(arbitrumAddressBook, graph.l1.chainId.toString())
const arbIInbox = arbAddressBook.getEntry('IInbox')

expect(inbox.toLowerCase()).eq(arbIInbox.address.toLowerCase())
})

it("l1Router should match Arbitrum's router address", async function () {
const l1Router = await L1GraphTokenGateway.l1Router()

// TODO: is there a cleaner way to get the router address?
const arbitrumAddressBook = process.env.ARBITRUM_ADDRESS_BOOK ?? 'arbitrum-addresses-local.json'
const arbAddressBook = getAddressBook(arbitrumAddressBook, graph.l1.chainId.toString())
const arbL2Router = arbAddressBook.getEntry('L1GatewayRouter')

expect(l1Router).eq(arbL2Router.address)
})

describe('calls with unauthorized user', () => {
it('initialize should revert', async function () {
const tx = L1GraphTokenGateway.connect(unauthorized).initialize(unauthorized.address)
Expand Down Expand Up @@ -68,16 +106,17 @@ describe('[L1] L1GraphTokenGateway configuration', function () {
await expect(tx).revertedWith('Only Controller governor')
})

it('finalizeInboundTransfer should revert', async function () {
const tx = L1GraphTokenGateway.connect(unauthorized).finalizeInboundTransfer(
unauthorized.address,
unauthorized.address,
unauthorized.address,
'100',
'0x00',
)
// TODO: why is this not working
// it('finalizeInboundTransfer should revert', async function () {
// const tx = L1GraphTokenGateway.connect(unauthorized).finalizeInboundTransfer(
// unauthorized.address,
// unauthorized.address,
// unauthorized.address,
// '100',
// '0x00',
// )

await expect(tx).revertedWith('Paused (contract)')
})
// await expect(tx).revertedWith('NOT_FROM_BRIDGE')
// })
})
})
10 changes: 10 additions & 0 deletions e2e/deployment/config/l2/l2GraphToken.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ describe('[L2] L2GraphToken', () => {
unauthorized = (await graph.getTestAccounts())[0]
})

it('l1Address should match the L1 GraphToken deployed address', async function () {
const l1Address = await L2GraphToken.l1Address()
expect(l1Address).eq(graph.l1.contracts.GraphToken.address)
})

it('gateway should match the L2 GraphTokenGateway deployed address', async function () {
const gateway = await L2GraphToken.gateway()
expect(gateway).eq(graph.l2.contracts.L2GraphTokenGateway.address)
})

describe('calls with unauthorized user', () => {
it('mint should revert', async function () {
const tx = L2GraphToken.connect(unauthorized).mint(
Expand Down
28 changes: 25 additions & 3 deletions e2e/deployment/config/l2/l2GraphTokenGateway.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { expect } from 'chai'
import hre from 'hardhat'
import { getAddressBook } from '../../../../cli/address-book'
import GraphChain from '../../../../gre/helpers/network'

describe('[L2] L2GraphTokenGateway configuration', function () {
Expand All @@ -13,16 +14,37 @@ describe('[L2] L2GraphTokenGateway configuration', function () {
unauthorized = (await graph.getTestAccounts())[0]
})

it('bridge should be paused', async function () {
it('bridge should not be paused', async function () {
const paused = await L2GraphTokenGateway.paused()
expect(paused).eq(true)
expect(paused).eq(false)
})

it('should be controlled by Controller', async function () {
const controller = await L2GraphTokenGateway.controller()
expect(controller).eq(Controller.address)
})

it('l1GRT should match the L1 GraphToken deployed address', async function () {
const l1GRT = await L2GraphTokenGateway.l1GRT()
expect(l1GRT).eq(graph.l1.contracts.GraphToken.address)
})

it('l1Counterpart should match the deployed L1 GraphTokenGateway address', async function () {
const l1Counterpart = await L2GraphTokenGateway.l1Counterpart()
expect(l1Counterpart).eq(graph.l1.contracts.L1GraphTokenGateway.address)
})

it("l2Router should match Arbitrum's router address", async function () {
const l2Router = await L2GraphTokenGateway.l2Router()

// TODO: is there a cleaner way to get the router address?
const arbitrumAddressBook = process.env.ARBITRUM_ADDRESS_BOOK ?? 'arbitrum-addresses-local.json'
const arbAddressBook = getAddressBook(arbitrumAddressBook, graph.l2.chainId.toString())
const arbL2Router = arbAddressBook.getEntry('L2GatewayRouter')

expect(l2Router).eq(arbL2Router.address)
})

describe('calls with unauthorized user', () => {
it('initialize should revert', async function () {
const tx = L2GraphTokenGateway.connect(unauthorized).initialize(unauthorized.address)
Expand Down Expand Up @@ -55,7 +77,7 @@ describe('[L2] L2GraphTokenGateway configuration', function () {
'0x00',
)

await expect(tx).revertedWith('Paused (contract)')
await expect(tx).revertedWith('ONLY_COUNTERPART_GATEWAY')
})
})
})
17 changes: 17 additions & 0 deletions e2e/deployment/init/l1/bridgeEscrow.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { expect } from 'chai'
import hre from 'hardhat'
import GraphChain from '../../../../gre/helpers/network'

describe('BridgeEscrow initialization', () => {
const graph = hre.graph()
const { BridgeEscrow, GraphToken, L1GraphTokenGateway } = graph.contracts

before(async function () {
if (GraphChain.isL2(graph.chainId)) this.skip()
})

it("should allow L1GraphTokenGateway contract to spend MAX_UINT256 tokens on BridgeEscrow's behalf", async function () {
const allowance = await GraphToken.allowance(BridgeEscrow.address, L1GraphTokenGateway.address)
expect(allowance).eq(hre.ethers.constants.MaxUint256)
})
})
Loading