Commit 685c8f25 authored by Kelvin Fichter's avatar Kelvin Fichter Committed by Kelvin Fichter

feat[integration-tests]: make tests work for prod networks

parent 9ed1e38b
# only need to fill these out if you want to test against a prod network
PRIVATE_KEY=
L1_URL=
L2_URL=
ADDRESS_MANAGER=
L2_CHAINID=
# @eth-optimism/integration-tests
## Setup
Follow installation + build instructions in the [primary README](../README.md).
Then, run:
```bash
yarn build:integration
```
## Running tests
### Testing a live network
Create an `.env` file and fill it out.
Look at `.env.example` to know which variables to include.
Once you have your environment set up, run:
```bash
yarn test:integration:live
```
You can also set environment variables on the command line instead of inside `.env` if you want:
```bash
L1_URL=whatever L2_URL=whatever yarn test:integration:live
```
Note that this can take an extremely long time (~1hr).
......@@ -9,14 +9,19 @@ import 'hardhat-gas-reporter'
const enableGasReport = !!process.env.ENABLE_GAS_REPORT
const config: HardhatUserConfig = {
mocha: {
timeout: 20000,
},
networks: {
optimism: {
url: process.env.L2_URL || 'http://localhost:8545',
ovm: true,
},
'optimism-live': {
url: process.env.L2_URL || 'http://localhost:8545',
ovm: true,
timeout: 150000,
},
},
mocha: {
timeout: 50000,
},
solidity: '0.7.6',
ovm: {
......
......@@ -13,6 +13,7 @@
"build:contracts": "hardhat compile",
"build:contracts:ovm": "hardhat compile --network optimism",
"test:integration": "hardhat --network optimism test",
"test:integration:live": "IS_LIVE_NETWORK=true hardhat --network optimism-live test",
"test:sync": "hardhat --network optimism test sync-tests/*.spec.ts --no-compile",
"clean": "rimraf cache artifacts artifacts-ovm cache-ovm"
},
......@@ -20,6 +21,7 @@
"@eth-optimism/contracts": "^0.4.2",
"@eth-optimism/core-utils": "^0.5.0",
"@eth-optimism/hardhat-ovm": "^0.2.2",
"@eth-optimism/message-relayer": "^0.1.6",
"@ethersproject/providers": "^5.0.24",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
......@@ -33,6 +35,7 @@
"chai": "^4.3.3",
"chai-as-promised": "^7.1.1",
"docker-compose": "^0.23.8",
"dotenv": "^10.0.0",
"envalid": "^7.1.0",
"babel-eslint": "^10.1.0",
"eslint": "^7.27.0",
......
......@@ -3,13 +3,13 @@ import { expect } from 'chai'
/* Imports: External */
import { Contract, ContractFactory } from 'ethers'
import { predeploys, getContractInterface } from '@eth-optimism/contracts'
import { Direction } from './shared/watcher-utils'
/* Imports: Internal */
import l1SimpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json'
import l2SimpleStorageJson from '../artifacts-ovm/contracts/SimpleStorage.sol/SimpleStorage.json'
import l2ReverterJson from '../artifacts-ovm/contracts/Reverter.sol/Reverter.json'
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
import { OptimismEnv, useDynamicTimeoutForWithdrawals } from './shared/env'
describe('Basic L1<>L2 Communication', async () => {
let Factory__L1SimpleStorage: ContractFactory
......@@ -49,7 +49,9 @@ describe('Basic L1<>L2 Communication', async () => {
})
describe('L2 => L1', () => {
it('should be able to perform a withdrawal from L2 -> L1', async () => {
it('should be able to perform a withdrawal from L2 -> L1', async function () {
await useDynamicTimeoutForWithdrawals(this, env)
const value = `0x${'77'.repeat(32)}`
// Send L2 -> L1 message.
......@@ -58,7 +60,8 @@ describe('Basic L1<>L2 Communication', async () => {
L1SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000
)
await transaction.wait()
await env.relayXDomainMessages(transaction)
await env.waitForXDomainTransaction(transaction, Direction.L2ToL1)
expect(await L1SimpleStorage.msgSender()).to.equal(
......
......@@ -3,11 +3,12 @@ import chaiAsPromised from 'chai-as-promised'
chai.use(chaiAsPromised)
/* Imports: External */
import { BigNumber, Contract, utils } from 'ethers'
import { ethers, BigNumber, Contract, utils } from 'ethers'
import { TxGasLimit, TxGasPrice } from '@eth-optimism/core-utils'
import { predeploys, getContractInterface } from '@eth-optimism/contracts'
/* Imports: Internal */
import { IS_LIVE_NETWORK } from './shared/utils'
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
......@@ -36,11 +37,11 @@ describe('Fee Payment Integration Tests', async () => {
it('Should estimateGas with recoverable L2 gasLimit', async () => {
const gas = await env.ovmEth.estimateGas.transfer(
other,
utils.parseEther('0.5')
utils.parseEther('0.0000001')
)
const tx = await env.ovmEth.populateTransaction.transfer(
other,
utils.parseEther('0.5')
utils.parseEther('0.0000001')
)
const executionGas = await (env.ovmEth.provider as any).send(
'eth_estimateExecutionGas',
......@@ -51,7 +52,7 @@ describe('Fee Payment Integration Tests', async () => {
})
it('Paying a nonzero but acceptable gasPrice fee', async () => {
const amount = utils.parseEther('0.5')
const amount = utils.parseEther('0.0000001')
const balanceBefore = await env.l2Wallet.getBalance()
const feeVaultBalanceBefore = await env.l2Wallet.provider.getBalance(
ovmSequencerFeeVault.address
......@@ -84,15 +85,23 @@ describe('Fee Payment Integration Tests', async () => {
await expect(ovmSequencerFeeVault.withdraw()).to.be.rejected
})
it('should be able to withdraw fees back to L1 once the minimum is met', async () => {
it('should be able to withdraw fees back to L1 once the minimum is met', async function () {
const l1FeeWallet = await ovmSequencerFeeVault.l1FeeWallet()
const balanceBefore = await env.l1Wallet.provider.getBalance(l1FeeWallet)
const withdrawalAmount = await ovmSequencerFeeVault.MIN_WITHDRAWAL_AMOUNT()
const l2WalletBalance = await env.l2Wallet.getBalance()
if (IS_LIVE_NETWORK && l2WalletBalance.lt(withdrawalAmount)) {
console.log(
`NOTICE: must have at least ${ethers.utils.formatEther(
withdrawalAmount
)} ETH on L2 to execute this test, skipping`
)
this.skip()
}
// Transfer the minimum required to withdraw.
await env.ovmEth.transfer(
ovmSequencerFeeVault.address,
await ovmSequencerFeeVault.MIN_WITHDRAWAL_AMOUNT()
)
await env.ovmEth.transfer(ovmSequencerFeeVault.address, withdrawalAmount)
const vaultBalance = await env.ovmEth.balanceOf(
ovmSequencerFeeVault.address
......
import { predeploys } from '@eth-optimism/contracts'
import { expect } from 'chai'
/* Imports: External */
import { Wallet, utils, BigNumber } from 'ethers'
import { predeploys } from '@eth-optimism/contracts'
/* Imports: Internal */
import { Direction } from './shared/watcher-utils'
import {
......@@ -9,7 +12,7 @@ import {
fundUser,
PROXY_SEQUENCER_ENTRYPOINT_ADDRESS,
} from './shared/utils'
import { OptimismEnv } from './shared/env'
import { OptimismEnv, useDynamicTimeoutForWithdrawals } from './shared/env'
const DEFAULT_TEST_GAS_L1 = 330_000
const DEFAULT_TEST_GAS_L2 = 1_300_000
......@@ -53,7 +56,7 @@ describe('Native ETH Integration Tests', async () => {
describe('estimateGas', () => {
it('Should estimate gas for ETH transfer', async () => {
const amount = utils.parseEther('0.5')
const amount = utils.parseEther('0.0000001')
const addr = '0x' + '1234'.repeat(10)
const gas = await env.ovmEth.estimateGas.transfer(addr, amount)
// Expect gas to be less than or equal to the target plus 1%
......@@ -61,7 +64,7 @@ describe('Native ETH Integration Tests', async () => {
})
it('Should estimate gas for ETH withdraw', async () => {
const amount = utils.parseEther('0.5')
const amount = utils.parseEther('0.0000001')
const gas = await env.l2Bridge.estimateGas.withdraw(
predeploys.OVM_ETH,
amount,
......@@ -186,14 +189,13 @@ describe('Native ETH Integration Tests', async () => {
await expect(
env.l1Bridge.depositETH(DEFAULT_TEST_GAS_L2, data, {
value: depositAmount,
gasLimit: 4_000_000,
})
).to.be.revertedWith(
'Transaction data size exceeds maximum for rollup transaction.'
)
).to.be.reverted
})
it('withdraw', async () => {
it('withdraw', async function () {
await useDynamicTimeoutForWithdrawals(this, env)
const withdrawAmount = BigNumber.from(3)
const preBalances = await getBalances(env)
expect(
......@@ -201,31 +203,43 @@ describe('Native ETH Integration Tests', async () => {
'Cannot run withdrawal test before any deposits...'
)
const receipts = await env.waitForXDomainTransaction(
env.l2Bridge.withdraw(
const transaction = await env.l2Bridge.withdraw(
predeploys.OVM_ETH,
withdrawAmount,
DEFAULT_TEST_GAS_L2,
'0xFFFF'
),
)
await transaction.wait()
await env.relayXDomainMessages(transaction)
const receipts = await env.waitForXDomainTransaction(
transaction,
Direction.L2ToL1
)
const fee = receipts.tx.gasLimit.mul(receipts.tx.gasPrice)
const postBalances = await getBalances(env)
expect(postBalances.l1BridgeBalance).to.deep.eq(
preBalances.l1BridgeBalance.sub(withdrawAmount)
// Approximate because there's a fee related to relaying the L2 => L1 message and it throws off the math.
expectApprox(
postBalances.l1BridgeBalance,
preBalances.l1BridgeBalance.sub(withdrawAmount),
{ upperPercentDeviation: 1 }
)
expect(postBalances.l2UserBalance).to.deep.eq(
preBalances.l2UserBalance.sub(withdrawAmount.add(fee))
expectApprox(
postBalances.l2UserBalance,
preBalances.l2UserBalance.sub(withdrawAmount.add(fee)),
{ upperPercentDeviation: 1 }
)
expect(postBalances.l1UserBalance).to.deep.eq(
preBalances.l1UserBalance.add(withdrawAmount)
expectApprox(
postBalances.l1UserBalance,
preBalances.l1UserBalance.add(withdrawAmount),
{ upperPercentDeviation: 1 }
)
})
it('withdrawTo', async () => {
it('withdrawTo', async function () {
await useDynamicTimeoutForWithdrawals(this, env)
const withdrawAmount = BigNumber.from(3)
const preBalances = await getBalances(env)
......@@ -235,14 +249,17 @@ describe('Native ETH Integration Tests', async () => {
'Cannot run withdrawal test before any deposits...'
)
const receipts = await env.waitForXDomainTransaction(
env.l2Bridge.withdrawTo(
const transaction = await env.l2Bridge.withdrawTo(
predeploys.OVM_ETH,
l1Bob.address,
withdrawAmount,
DEFAULT_TEST_GAS_L2,
'0xFFFF'
),
)
await transaction.wait()
await env.relayXDomainMessages(transaction)
const receipts = await env.waitForXDomainTransaction(
transaction,
Direction.L2ToL1
)
const fee = receipts.tx.gasLimit.mul(receipts.tx.gasPrice)
......@@ -260,7 +277,9 @@ describe('Native ETH Integration Tests', async () => {
)
})
it('deposit, transfer, withdraw', async () => {
it('deposit, transfer, withdraw', async function () {
await useDynamicTimeoutForWithdrawals(this, env)
// 1. deposit
const amount = utils.parseEther('1')
await env.waitForXDomainTransaction(
......@@ -282,15 +301,18 @@ describe('Native ETH Integration Tests', async () => {
// 3. do withdrawal
const withdrawnAmount = utils.parseEther('0.95')
const receipts = await env.waitForXDomainTransaction(
env.l2Bridge
const transaction = await env.l2Bridge
.connect(other)
.withdraw(
predeploys.OVM_ETH,
withdrawnAmount,
DEFAULT_TEST_GAS_L1,
'0xFFFF'
),
)
await transaction.wait()
await env.relayXDomainMessages(transaction)
const receipts = await env.waitForXDomainTransaction(
transaction,
Direction.L2ToL1
)
......
import { expect } from 'chai'
/* Imports: External */
import { ethers } from 'hardhat'
import { injectL2Context } from '@eth-optimism/core-utils'
import { expect } from 'chai'
import {
sleep,
l2Provider,
l1Provider,
getAddressManager,
} from './shared/utils'
import { Contract, BigNumber } from 'ethers'
/* Imports: Internal */
import { l2Provider, l1Provider, IS_LIVE_NETWORK } from './shared/utils'
import { OptimismEnv } from './shared/env'
import { getContractFactory } from '@eth-optimism/contracts'
import { Contract, ContractFactory, Wallet, BigNumber } from 'ethers'
import { Direction } from './shared/watcher-utils'
/**
* These tests cover the OVM execution contexts. In the OVM execution
......@@ -17,73 +16,51 @@ import { Contract, ContractFactory, Wallet, BigNumber } from 'ethers'
* must be equal to the blocknumber/timestamp of the L1 transaction.
*/
describe('OVM Context: Layer 2 EVM Context', () => {
let address: string
let CanonicalTransactionChain: Contract
let OVMMulticall: Contract
let OVMContextStorage: Contract
const L1Provider = l1Provider
const L2Provider = injectL2Context(l2Provider)
let env: OptimismEnv
before(async () => {
const env = await OptimismEnv.new()
// Create providers and signers
const l1Wallet = env.l1Wallet
const l2Wallet = env.l2Wallet
const addressManager = env.addressManager
env = await OptimismEnv.new()
})
// deploy the contract
let OVMMulticall: Contract
let OVMContextStorage: Contract
beforeEach(async () => {
const OVMContextStorageFactory = await ethers.getContractFactory(
'OVMContextStorage',
l2Wallet
)
OVMContextStorage = await OVMContextStorageFactory.deploy()
const receipt = await OVMContextStorage.deployTransaction.wait()
address = OVMContextStorage.address
const ctcAddress = await addressManager.getAddress(
'OVM_CanonicalTransactionChain'
env.l2Wallet
)
const CanonicalTransactionChainFactory = getContractFactory(
'OVM_CanonicalTransactionChain'
)
CanonicalTransactionChain =
CanonicalTransactionChainFactory.connect(l1Wallet).attach(ctcAddress)
const OVMMulticallFactory = await ethers.getContractFactory(
'OVMMulticall',
l2Wallet
env.l2Wallet
)
OVMContextStorage = await OVMContextStorageFactory.deploy()
await OVMContextStorage.deployTransaction.wait()
OVMMulticall = await OVMMulticallFactory.deploy()
await OVMMulticall.deployTransaction.wait()
})
it('Enqueue: `block.number` and `block.timestamp` have L1 values', async () => {
for (let i = 0; i < 5; i++) {
const l2Tip = await L2Provider.getBlock('latest')
const tx = await CanonicalTransactionChain.enqueue(
let numTxs = 5
if (IS_LIVE_NETWORK) {
// Tests take way too long if we don't reduce the number of txs here.
numTxs = 1
}
it('enqueue: `block.number` and `block.timestamp` have L1 values', async () => {
for (let i = 0; i < numTxs; i++) {
const tx = await env.l1Messenger.sendMessage(
OVMContextStorage.address,
500_000,
'0x'
'0x',
2_000_000
)
// Wait for the enqueue to be ingested
while (true) {
const tip = await L2Provider.getBlock('latest')
if (tip.number === l2Tip.number + 1) {
break
}
await sleep(500)
}
const receipt = await tx.wait()
// Get the receipt
const receipt = await tx.wait()
// The transaction did not revert
expect(receipt.status).to.equal(1)
await env.waitForXDomainTransaction(tx, Direction.L1ToL2)
// Get the L1 block that the enqueue transaction was in so that
// the timestamp can be compared against the layer two contract
const block = await l1Provider.getBlock(receipt.blockNumber)
......@@ -96,14 +73,18 @@ describe('OVM Context: Layer 2 EVM Context', () => {
const timestamp = await OVMContextStorage.timestamps(i)
expect(block.timestamp).to.deep.equal(timestamp.toNumber())
}
})
}).timeout(150000) // this specific test takes a while because it involves L1 to L2 txs
it('should set correct OVM Context for `eth_call`', async () => {
const tip = await L2Provider.getBlockWithTransactions('latest')
const start = Math.max(0, tip.number - 5)
for (let i = 0; i < numTxs; i++) {
// Make an empty transaction to bump the latest block number.
const dummyTx = await env.l2Wallet.sendTransaction({
to: `0x${'11'.repeat(20)}`,
data: '0x',
})
await dummyTx.wait()
for (let i = start; i < tip.number; i++) {
const block = await L2Provider.getBlockWithTransactions(i)
const block = await L2Provider.getBlockWithTransactions('latest')
const [, returnData] = await OVMMulticall.callStatic.aggregate(
[
[
......@@ -117,7 +98,7 @@ describe('OVM Context: Layer 2 EVM Context', () => {
OVMMulticall.interface.encodeFunctionData('getCurrentBlockNumber'),
],
],
{ blockTag: i }
{ blockTag: block.number }
)
const timestamp = BigNumber.from(returnData[0])
......
import { expect } from 'chai'
/* Imports: Internal */
import { providers } from 'ethers'
import { injectL2Context } from '@eth-optimism/core-utils'
import { sleep } from './shared/utils'
import { OptimismEnv } from './shared/env'
/* Imports: External */
import { providers } from 'ethers'
import { expect } from 'chai'
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
// This test ensures that the transactions which get `enqueue`d get
// added to the L2 blocks by the Sync Service (which queries the DTL)
describe('Queue Ingestion', () => {
const RETRIES = 20
const numTxs = 5
let startBlock: number
let endBlock: number
let env: OptimismEnv
let l2Provider: providers.JsonRpcProvider
const receipts = []
before(async () => {
env = await OptimismEnv.new()
l2Provider = injectL2Context(env.l2Wallet.provider as any)
})
// The transactions are enqueue'd with a `to` address of i.repeat(40)
// meaning that the `to` value is different each iteration in a deterministic
// way. They need to be inserted into the L2 chain in an ascending order.
// Keep track of the receipts so that the blockNumber can be compared
// against the `L1BlockNumber` on the tx objects.
before(async () => {
// Keep track of the L2 tip before submitting any transactions so that
// the subsequent transactions can be queried for in the next test
startBlock = (await l2Provider.getBlockNumber()) + 1
endBlock = startBlock + numTxs - 1
// Enqueue some transactions by building the calldata and then sending
// the transaction to Layer 1
for (let i = 0; i < numTxs; i++) {
const input = ['0x' + `${i}`.repeat(40), 500_000, `0x0${i}`]
const calldata = env.ctc.interface.encodeFunctionData('enqueue', input)
const txResponse = await env.l1Wallet.sendTransaction({
data: calldata,
to: env.ctc.address,
})
const receipt = await txResponse.wait()
receipts.push(receipt)
}
})
// The batch submitter will notice that there are transactions
// that are in the queue and submit them. L2 will pick up the
// sequencer batch appended event and play the transactions.
it('should order transactions correctly', async () => {
// Wait until each tx from the previous test has
// been executed
let i: number
for (i = 0; i < RETRIES; i++) {
const tip = await l2Provider.getBlockNumber()
if (tip >= endBlock) {
break
}
await sleep(1000)
}
const numTxs = 5
if (i === RETRIES) {
throw new Error(
'timed out waiting for queued transactions to be inserted'
// Enqueue some transactions by building the calldata and then sending
// the transaction to Layer 1
const txs = []
for (let i = 0; i < numTxs; i++) {
const tx = await env.l1Messenger.sendMessage(
`0x${`${i}`.repeat(40)}`,
`0x0${i}`,
1_000_000
)
await tx.wait()
txs.push(tx)
}
const from = await env.l1Wallet.getAddress()
// Keep track of an index into the receipts list and
// increment it for each block fetched.
let receiptIndex = 0
// Fetch blocks
for (i = 0; i < numTxs; i++) {
const block = await l2Provider.getBlock(startBlock + i)
const hash = block.transactions[0]
// Use as any hack because additional properties are
// added to the transaction response
const tx = await (l2Provider.getTransaction(hash) as any)
for (let i = 0; i < numTxs; i++) {
const l1Tx = txs[i]
const l1TxReceipt = await txs[i].wait()
const receipt = await env.waitForXDomainTransaction(
l1Tx,
Direction.L1ToL2
)
const l2Tx = (await l2Provider.getTransaction(
receipt.remoteTx.hash
)) as any
const params = env.l2Messenger.interface.decodeFunctionData(
'relayMessage',
l2Tx.data
)
// The `to` addresses are defined in the previous test and
// increment sequentially.
expect(tx.to).to.be.equal('0x' + `${i}`.repeat(40))
// The queue origin is Layer 1
expect(tx.queueOrigin).to.be.equal('l1')
// the L1TxOrigin is equal to the Layer one from
expect(tx.l1TxOrigin).to.be.equal(from.toLowerCase())
expect(typeof tx.l1BlockNumber).to.be.equal('number')
// Get the receipt and increment the recept index
const receipt = receipts[receiptIndex++]
expect(tx.l1BlockNumber).to.be.equal(receipt.blockNumber)
expect(params._sender.toLowerCase()).to.equal(
env.l1Wallet.address.toLowerCase()
)
expect(params._target).to.equal('0x' + `${i}`.repeat(40))
expect(l2Tx.queueOrigin).to.equal('l1')
expect(l2Tx.l1TxOrigin.toLowerCase()).to.equal(
env.l1Messenger.address.toLowerCase()
)
expect(l2Tx.l1BlockNumber).to.equal(l1TxReceipt.blockNumber)
}
})
}).timeout(100_000)
})
......@@ -2,7 +2,6 @@ import {
injectL2Context,
TxGasLimit,
TxGasPrice,
toRpcHexString,
} from '@eth-optimism/core-utils'
import { Wallet, BigNumber, Contract, ContractFactory } from 'ethers'
import { ethers } from 'hardhat'
......@@ -13,6 +12,8 @@ import {
DEFAULT_TRANSACTION,
fundUser,
expectApprox,
L2_CHAINID,
IS_LIVE_NETWORK,
} from './shared/utils'
import chaiAsPromised from 'chai-as-promised'
import { OptimismEnv } from './shared/env'
......@@ -132,10 +133,9 @@ describe('Basic RPC tests', () => {
gasPrice: TxGasPrice,
}
const fee = tx.gasPrice.mul(tx.gasLimit)
const gasLimit = 5920001
await expect(env.l2Wallet.sendTransaction(tx)).to.be.rejectedWith(
`fee too low: ${fee}, use at least tx.gasLimit = ${gasLimit} and tx.gasPrice = ${TxGasPrice.toString()}`
`fee too low: ${fee}, use at least tx.gasLimit =`
)
})
......@@ -317,7 +317,15 @@ describe('Basic RPC tests', () => {
// canonical transaction chain. This test catches this by
// querying for the latest block and then waits and then queries
// the latest block again and then asserts that they are the same.
it('should return the same result when new transactions are not applied', async () => {
//
// Needs to be skipped on Prod networks because this test doesn't work when
// other people are sending transactions to the Sequencer at the same time
// as this test is running.
it('should return the same result when new transactions are not applied', async function () {
if (IS_LIVE_NETWORK) {
this.skip()
}
// Get latest block once to start.
const prev = await provider.getBlockWithTransactions('latest')
......@@ -341,7 +349,7 @@ describe('Basic RPC tests', () => {
describe('eth_chainId', () => {
it('should get the correct chainid', async () => {
const { chainId } = await provider.getNetwork()
expect(chainId).to.be.eq(420)
expect(chainId).to.be.eq(L2_CHAINID)
})
})
......
/* Imports: External */
import { Contract, utils, Wallet } from 'ethers'
import { TransactionResponse } from '@ethersproject/providers'
import { getContractFactory, predeploys } from '@eth-optimism/contracts'
import { Watcher } from '@eth-optimism/core-utils'
import { Contract, utils, Wallet } from 'ethers'
import { getMessagesAndProofsForL2Transaction } from '@eth-optimism/message-relayer'
/* Imports: Internal */
import {
getAddressManager,
l1Provider,
......@@ -11,6 +16,8 @@ import {
getOvmEth,
getL1Bridge,
getL2Bridge,
IS_LIVE_NETWORK,
sleep,
} from './utils'
import {
initWatcher,
......@@ -18,7 +25,6 @@ import {
Direction,
waitForXDomainTransaction,
} from './watcher-utils'
import { TransactionResponse } from '@ethersproject/providers'
/// Helper class for instantiating a test environment with a funded account
export class OptimismEnv {
......@@ -27,6 +33,7 @@ export class OptimismEnv {
l1Bridge: Contract
l1Messenger: Contract
ctc: Contract
scc: Contract
// L2 Contracts
ovmEth: Contract
......@@ -53,6 +60,7 @@ export class OptimismEnv {
this.l1Wallet = args.l1Wallet
this.l2Wallet = args.l2Wallet
this.ctc = args.ctc
this.scc = args.scc
}
static async new(): Promise<OptimismEnv> {
......@@ -85,10 +93,18 @@ export class OptimismEnv {
.connect(l2Wallet)
.attach(predeploys.OVM_GasPriceOracle)
const sccAddress = await addressManager.getAddress(
'OVM_StateCommitmentChain'
)
const scc = getContractFactory('OVM_StateCommitmentChain')
.connect(l1Wallet)
.attach(sccAddress)
return new OptimismEnv({
addressManager,
l1Bridge,
ctc,
scc,
l1Messenger,
ovmEth,
gasPriceOracle,
......@@ -106,4 +122,94 @@ export class OptimismEnv {
): Promise<CrossDomainMessagePair> {
return waitForXDomainTransaction(this.watcher, tx, direction)
}
/**
* Relays all L2 => L1 messages found in a given L2 transaction.
*
* @param tx Transaction to find messages in.
*/
async relayXDomainMessages(
tx: Promise<TransactionResponse> | TransactionResponse
): Promise<void> {
tx = await tx
let messagePairs = []
while (true) {
try {
messagePairs = await getMessagesAndProofsForL2Transaction(
l1Provider,
l2Provider,
this.scc.address,
predeploys.OVM_L2CrossDomainMessenger,
tx.hash
)
break
} catch (err) {
if (err.message.includes('unable to find state root batch for tx')) {
await sleep(5000)
} else {
throw err
}
}
}
for (const { message, proof } of messagePairs) {
while (true) {
try {
const result = await this.l1Messenger
.connect(this.l1Wallet)
.relayMessage(
message.target,
message.sender,
message.message,
message.messageNonce,
proof
)
await result.wait()
break
} catch (err) {
if (err.message.includes('execution failed due to an exception')) {
await sleep(5000)
} else if (
err.message.includes('message has already been received')
) {
break
} else {
throw err
}
}
}
}
}
}
/**
* Sets the timeout of a test based on the challenge period of the current network. If the
* challenge period is greater than 60s (e.g., on Mainnet) then we skip this test entirely.
*
* @param testctx Function context of the test to modify (i.e. `this` when inside a test).
* @param env Optimism environment used to resolve the StateCommitmentChain.
*/
export const useDynamicTimeoutForWithdrawals = async (
testctx: any,
env: OptimismEnv
) => {
if (!IS_LIVE_NETWORK) {
return
}
const challengePeriod = await env.scc.FRAUD_PROOF_WINDOW()
if (challengePeriod.gt(60)) {
console.log(
`WARNING: challenge period is greater than 60s (${challengePeriod.toString()}s), skipping test`
)
testctx.skip()
}
// 60s for state root batch to be published + (challenge period x 4)
const timeoutMs = 60000 + challengePeriod.toNumber() * 1000 * 4
console.log(
`NOTICE: inside a withdrawal test on a prod network, dynamically setting timeout to ${timeoutMs}ms`
)
testctx.timeout(timeoutMs)
}
import { expect } from 'chai'
import { Direction, waitForXDomainTransaction } from './watcher-utils'
import {
getContractFactory,
getContractInterface,
predeploys,
} from '@eth-optimism/contracts'
import { injectL2Context, remove0x, Watcher } from '@eth-optimism/core-utils'
/* Imports: External */
import {
Contract,
Wallet,
......@@ -17,10 +10,24 @@ import {
BigNumber,
utils,
} from 'ethers'
import { cleanEnv, str, num } from 'envalid'
import {
getContractFactory,
getContractInterface,
predeploys,
} from '@eth-optimism/contracts'
import { injectL2Context, remove0x, Watcher } from '@eth-optimism/core-utils'
import { cleanEnv, str, num, bool } from 'envalid'
import dotenv from 'dotenv'
/* Imports: Internal */
import { Direction, waitForXDomainTransaction } from './watcher-utils'
export const GWEI = BigNumber.from(1e9)
if (process.env.IS_LIVE_NETWORK === 'true') {
dotenv.config()
}
const env = cleanEnv(process.env, {
L1_URL: str({ default: 'http://localhost:9545' }),
L2_URL: str({ default: 'http://localhost:8545' }),
......@@ -37,6 +44,8 @@ const env = cleanEnv(process.env, {
ADDRESS_MANAGER: str({
default: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
}),
L2_CHAINID: num({ default: 420 }),
IS_LIVE_NETWORK: bool({ default: false }),
})
// The hardhat instance
......@@ -64,6 +73,9 @@ export const PROXY_SEQUENCER_ENTRYPOINT_ADDRESS =
'0x4200000000000000000000000000000000000004'
export const OVM_ETH_ADDRESS = predeploys.OVM_ETH
export const L2_CHAINID = env.L2_CHAINID
export const IS_LIVE_NETWORK = env.IS_LIVE_NETWORK
export const getAddressManager = (provider: any) => {
return getContractFactory('Lib_AddressManager')
.connect(provider)
......
......@@ -13,6 +13,9 @@ COPY --from=builder /optimism/node_modules ./node_modules
COPY --from=builder /optimism/packages/core-utils/package.json ./packages/core-utils/package.json
COPY --from=builder /optimism/packages/core-utils/dist ./packages/core-utils/dist
COPY --from=builder /optimism/packages/message-relayer/package.json ./packages/message-relayer/package.json
COPY --from=builder /optimism/packages/message-relayer/dist ./packages/message-relayer/dist
COPY --from=builder /optimism/packages/hardhat-ovm/package.json ./packages/hardhat-ovm/package.json
COPY --from=builder /optimism/packages/hardhat-ovm/dist ./packages/hardhat-ovm/dist
......
......@@ -5636,6 +5636,11 @@ dot-prop@^6.0.1:
dependencies:
is-obj "^2.0.0"
dotenv@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dotenv@^8.2.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment