Commit f8531d88 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #2030 from mslipper/feat/nightly-itests

integration-tests: Changes to make it work with live networks again
parents a3b11da9 3710e8cd
---
'@eth-optimism/integration-tests': minor
---
Updates to work with a live network
......@@ -141,6 +141,32 @@ jobs:
kubectl rollout restart statefulset nightly-dtl --namespace nightly
kubectl rollout restart deployment nightly-gas-oracle --namespace nightly
kubectl rollout restart deployment edge-proxyd --namespace nightly
run-itests-nightly:
docker:
- image: cimg/base:2021.04
steps:
- setup_remote_docker:
version: 19.03.13
- run:
name: Run integration tests
command: |
docker run \
--env PRIVATE_KEY=$NIGHTLY_ITESTS_PRIVKEY \
--env L1_URL=https://nightly-l1.optimism-stacks.net \
--env L2_URL=https://nightly-l2.optimism-stacks.net \
--env ADDRESS_MANAGER=0x22D4E211ef8704f2ca2d6dfdB32125E2530ACE3e \
--env L2_CHAINID=69 \
--env MOCHA_BAIL=true \
--env MOCHA_TIMEOUT=300000 \
--env L1_GAS_PRICE=onchain \
--env L2_GAS_PRICE=onchain \
--env RUN_DEBUG_TRACE_TESTS=false \
--env RUN_REPLICA_TESTS=false \
--env RUN_STRESS_TESTS=false \
--env OVMCONTEXT_SPEC_NUM_TXS=1 \
--env DTL_ENQUEUE_CONFIRMATIONS=12 \
"$STACKMAN_REPO/integration-tests:nightly" \
yarn test:integration:live
notify:
docker:
- image: cimg/base:2021.04
......@@ -152,6 +178,18 @@ jobs:
workflows:
nightly-itests:
triggers:
- schedule:
cron: "0 1 * * * "
filters:
branches:
only:
- develop
jobs:
- run-itests-nightly:
context:
- optimism
nightly:
triggers:
- schedule:
......
......@@ -4,3 +4,14 @@ L1_URL=
L2_URL=
ADDRESS_MANAGER=
L2_CHAINID=
DTL_ENQUEUE_CONFIRMATIONS=
OVMCONTEXT_SPEC_NUM_TXS=1
# Can be set to true below if the withdrawal window is short enough
RUN_WITHDRAWAL_TESTS=false
RUN_DEBUG_TRACE_TESTS=false
RUN_REPLICA_TESTS=false
RUN_STRESS_TESTS=false
# Can be configured up or down as necessary
MOCHA_TIMEOUT=300000
# Set to true to make Mocha stop after the first failed test.
MOCHA_BAIL=false
\ No newline at end of file
......@@ -4,7 +4,7 @@ import { HardhatUserConfig } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
import 'hardhat-gas-reporter'
import { isLiveNetwork } from './test/shared/utils'
import { envConfig } from './test/shared/utils'
const enableGasReport = !!process.env.ENABLE_GAS_REPORT
......@@ -15,7 +15,8 @@ const config: HardhatUserConfig = {
},
},
mocha: {
timeout: isLiveNetwork() ? 300_000 : 75_000,
timeout: envConfig.MOCHA_TIMEOUT,
bail: envConfig.MOCHA_BAIL,
},
solidity: {
compilers: [
......
......@@ -41,7 +41,6 @@
"@types/chai-as-promised": "^7.1.4",
"@types/mocha": "^8.2.2",
"@types/rimraf": "^3.0.0",
"@types/shelljs": "^0.8.8",
"@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0",
"@uniswap/v3-core": "1.0.0",
......@@ -52,7 +51,6 @@
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"commander": "^8.3.0",
"docker-compose": "^0.23.8",
"dotenv": "^10.0.0",
"envalid": "^7.1.0",
"eslint": "^7.27.0",
......@@ -71,7 +69,6 @@
"mocha": "^8.4.0",
"prom-client": "^14.0.1",
"rimraf": "^3.0.2",
"shelljs": "^0.8.4",
"typescript": "^4.3.5",
"uniswap-v3-deploy-plugin": "^0.1.0"
}
......
......@@ -8,7 +8,13 @@ import { applyL1ToL2Alias, awaitCondition } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { Direction } from './shared/watcher-utils'
import { OptimismEnv } from './shared/env'
import { isMainnet } from './shared/utils'
import {
DEFAULT_TEST_GAS_L1,
DEFAULT_TEST_GAS_L2,
envConfig,
sleep,
withdrawalTest,
} from './shared/utils'
describe('Basic L1<>L2 Communication', async () => {
let Factory__L1SimpleStorage: ContractFactory
......@@ -37,28 +43,27 @@ describe('Basic L1<>L2 Communication', async () => {
beforeEach(async () => {
L1SimpleStorage = await Factory__L1SimpleStorage.deploy()
await L1SimpleStorage.deployTransaction.wait()
await L1SimpleStorage.deployed()
L2SimpleStorage = await Factory__L2SimpleStorage.deploy()
await L2SimpleStorage.deployTransaction.wait()
await L2SimpleStorage.deployed()
L2Reverter = await Factory__L2Reverter.deploy()
await L2Reverter.deployTransaction.wait()
await L2Reverter.deployed()
})
describe('L2 => L1', () => {
it('should be able to perform a withdrawal from L2 -> L1', async function () {
if (await isMainnet(env)) {
console.log('Skipping withdrawals test on mainnet.')
this.skip()
return
}
withdrawalTest(
'should be able to perform a withdrawal from L2 -> L1',
async () => {
const value = `0x${'77'.repeat(32)}`
// Send L2 -> L1 message.
const transaction = await env.l2Messenger.sendMessage(
L1SimpleStorage.address,
L1SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000
5000000,
{
gasLimit: DEFAULT_TEST_GAS_L2,
}
)
await transaction.wait()
await env.relayXDomainMessages(transaction)
......@@ -72,7 +77,8 @@ describe('Basic L1<>L2 Communication', async () => {
)
expect(await L1SimpleStorage.value()).to.equal(value)
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
}
)
})
describe('L1 => L2', () => {
......@@ -83,7 +89,10 @@ describe('Basic L1<>L2 Communication', async () => {
const transaction = await env.l1Messenger.sendMessage(
L2SimpleStorage.address,
L2SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000
5000000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
await env.waitForXDomainTransaction(transaction, Direction.L1ToL2)
......@@ -101,19 +110,41 @@ describe('Basic L1<>L2 Communication', async () => {
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(1)
})
it('should deposit from L1 -> L2 directly via enqueue', async () => {
it('should deposit from L1 -> L2 directly via enqueue', async function () {
this.timeout(
envConfig.MOCHA_TIMEOUT * 2 +
envConfig.DTL_ENQUEUE_CONFIRMATIONS * 15000
)
const value = `0x${'42'.repeat(32)}`
// Send L1 -> L2 message.
await env.ctc
const tx = await env.ctc
.connect(env.l1Wallet)
.enqueue(
L2SimpleStorage.address,
5000000,
L2SimpleStorage.interface.encodeFunctionData('setValueNotXDomain', [
value,
])
]),
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
const receipt = await tx.wait()
const waitUntilBlock =
receipt.blockNumber + envConfig.DTL_ENQUEUE_CONFIRMATIONS
let currBlock = await env.l1Provider.getBlockNumber()
while (currBlock <= waitUntilBlock) {
const progress =
envConfig.DTL_ENQUEUE_CONFIRMATIONS - (waitUntilBlock - currBlock)
console.log(
`Waiting for ${progress}/${envConfig.DTL_ENQUEUE_CONFIRMATIONS} confirmations.`
)
await sleep(5000)
currBlock = await env.l1Provider.getBlockNumber()
}
console.log('Enqueue should be confirmed.')
await awaitCondition(
async () => {
......@@ -138,8 +169,12 @@ describe('Basic L1<>L2 Communication', async () => {
const transaction = await env.l1Messenger.sendMessage(
L2SimpleStorage.address,
L2SimpleStorage.interface.encodeFunctionData('setValue', [value]),
5000000
5000000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
await transaction.wait()
const { remoteReceipt } = await env.waitForXDomainTransaction(
transaction,
......@@ -155,7 +190,10 @@ describe('Basic L1<>L2 Communication', async () => {
const transaction = await env.l1Messenger.sendMessage(
L2Reverter.address,
L2Reverter.interface.encodeFunctionData('doRevert', []),
5000000
5000000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
const { remoteReceipt } = await env.waitForXDomainTransaction(
......
......@@ -5,7 +5,7 @@ import { ethers } from 'hardhat'
import * as L2Artifact from '@eth-optimism/contracts/artifacts/contracts/standards/L2StandardERC20.sol/L2StandardERC20.json'
import { OptimismEnv } from './shared/env'
import { isLiveNetwork, isMainnet } from './shared/utils'
import { withdrawalTest } from './shared/utils'
import { Direction } from './shared/watcher-utils'
describe('Bridged tokens', () => {
......@@ -25,14 +25,16 @@ describe('Bridged tokens', () => {
const other = Wallet.createRandom()
otherWalletL1 = other.connect(env.l1Wallet.provider)
otherWalletL2 = other.connect(env.l2Wallet.provider)
await env.l1Wallet.sendTransaction({
let tx = await env.l1Wallet.sendTransaction({
to: otherWalletL1.address,
value: utils.parseEther('0.01'),
})
await env.l2Wallet.sendTransaction({
await tx.wait()
tx = await env.l2Wallet.sendTransaction({
to: otherWalletL2.address,
value: utils.parseEther('0.01'),
})
await tx.wait()
L1Factory__ERC20 = await ethers.getContractFactory('ERC20', env.l1Wallet)
L2Factory__ERC20 = new ethers.ContractFactory(
......@@ -77,7 +79,7 @@ describe('Bridged tokens', () => {
expect(await L2__ERC20.balanceOf(env.l2Wallet.address)).to.deep.equal(
BigNumber.from(1000)
)
}).timeout(isLiveNetwork() ? 300_000 : 120_000)
})
it('should transfer tokens on L2', async () => {
const tx = await L2__ERC20.transfer(otherWalletL1.address, 500)
......@@ -90,13 +92,9 @@ describe('Bridged tokens', () => {
)
})
it('should withdraw tokens from L2 to the depositor', async function () {
if (await isMainnet(env)) {
console.log('Skipping withdrawals test on mainnet.')
this.skip()
return
}
withdrawalTest(
'should withdraw tokens from L2 to the depositor',
async () => {
const tx = await env.l2Bridge.withdraw(
L2__ERC20.address,
500,
......@@ -111,15 +109,12 @@ describe('Bridged tokens', () => {
expect(await L2__ERC20.balanceOf(env.l2Wallet.address)).to.deep.equal(
BigNumber.from(0)
)
}).timeout(isLiveNetwork() ? 300_000 : 120_000)
it('should withdraw tokens from L2 to the transfer recipient', async function () {
if (await isMainnet(env)) {
console.log('Skipping withdrawals test on mainnet.')
this.skip()
return
}
)
withdrawalTest(
'should withdraw tokens from L2 to the transfer recipient',
async () => {
const tx = await env.l2Bridge
.connect(otherWalletL2)
.withdraw(L2__ERC20.address, 500, 2000000, '0x')
......@@ -131,5 +126,6 @@ describe('Bridged tokens', () => {
expect(await L2__ERC20.balanceOf(otherWalletL2.address)).to.deep.equal(
BigNumber.from(0)
)
}).timeout(isLiveNetwork() ? 300_000 : 120_000)
}
)
})
......@@ -6,14 +6,11 @@ import { serialize } from '@ethersproject/transactions'
import { predeploys, getContractFactory } from '@eth-optimism/contracts'
/* Imports: Internal */
import { isLiveNetwork } from './shared/utils'
import { hardhatTest } from './shared/utils'
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
const setPrices = async (env: OptimismEnv, value: number | BigNumber) => {
if (isLiveNetwork()) {
return
}
const gasPrice = await env.gasPriceOracle.setGasPrice(value)
await gasPrice.wait()
const baseFee = await env.gasPriceOracle.setL1BaseFee(value)
......@@ -28,24 +25,25 @@ describe('Fee Payment Integration Tests', async () => {
env = await OptimismEnv.new()
})
if (!isLiveNetwork()) {
it(`should return eth_gasPrice equal to OVM_GasPriceOracle.gasPrice`, async () => {
hardhatTest(
`should return eth_gasPrice equal to OVM_GasPriceOracle.gasPrice`,
async () => {
const assertGasPrice = async () => {
const gasPrice = await env.l2Wallet.getGasPrice()
const oracleGasPrice = await env.gasPriceOracle.gasPrice()
expect(gasPrice).to.deep.equal(oracleGasPrice)
}
assertGasPrice()
await assertGasPrice()
// update the gas price
const tx = await env.gasPriceOracle.setGasPrice(1000)
await tx.wait()
assertGasPrice()
})
await assertGasPrice()
}
)
it('Paying a nonzero but acceptable gasPrice fee', async () => {
hardhatTest('Paying a nonzero but acceptable gasPrice fee', async () => {
await setPrices(env, 1000)
const amount = utils.parseEther('0.0000001')
......@@ -97,7 +95,7 @@ describe('Fee Payment Integration Tests', async () => {
await setPrices(env, 1)
})
it('should compute correct fee', async () => {
hardhatTest('should compute correct fee', async () => {
await setPrices(env, 1000)
const preBalance = await env.l2Wallet.getBalance()
......@@ -149,15 +147,13 @@ describe('Fee Payment Integration Tests', async () => {
await expect(env.sequencerFeeVault.withdraw()).to.be.rejected
})
it('should be able to withdraw fees back to L1 once the minimum is met', async function () {
if (isLiveNetwork()) {
this.skip()
return
}
hardhatTest(
'should be able to withdraw fees back to L1 once the minimum is met',
async () => {
const l1FeeWallet = await env.sequencerFeeVault.l1FeeWallet()
const balanceBefore = await env.l1Wallet.provider.getBalance(l1FeeWallet)
const withdrawalAmount = await env.sequencerFeeVault.MIN_WITHDRAWAL_AMOUNT()
const withdrawalAmount =
await env.sequencerFeeVault.MIN_WITHDRAWAL_AMOUNT()
// Transfer the minimum required to withdraw.
const tx = await env.l2Wallet.sendTransaction({
......@@ -183,5 +179,6 @@ describe('Fee Payment Integration Tests', async () => {
expect(balanceAfter.sub(balanceBefore)).to.deep.equal(
BigNumber.from(vaultBalance)
)
})
}
)
})
......@@ -45,7 +45,7 @@ describe('Native ETH value integration tests', () => {
const there = await wallet.sendTransaction({
to: other.address,
value,
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
})
const thereReceipt = await there.wait()
const thereGas = thereReceipt.gasUsed.mul(there.gasPrice)
......@@ -63,7 +63,7 @@ describe('Native ETH value integration tests', () => {
const backAgain = await other.sendTransaction({
to: wallet.address,
value: backVal,
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
})
const backReceipt = await backAgain.wait()
const backGas = backReceipt.gasUsed.mul(backAgain.gasPrice)
......@@ -169,7 +169,7 @@ describe('Native ETH value integration tests', () => {
it('should allow ETH to be sent', async () => {
const sendAmount = 15
const tx = await ValueCalls0.simpleSend(ValueCalls1.address, sendAmount, {
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
})
await tx.wait()
......
......@@ -9,11 +9,15 @@ import { expectApprox } from '@eth-optimism/core-utils'
/* Imports: Internal */
import { Direction } from './shared/watcher-utils'
import { isMainnet, PROXY_SEQUENCER_ENTRYPOINT_ADDRESS } from './shared/utils'
import {
DEFAULT_TEST_GAS_L1,
DEFAULT_TEST_GAS_L2,
envConfig,
PROXY_SEQUENCER_ENTRYPOINT_ADDRESS,
withdrawalTest,
} from './shared/utils'
import { OptimismEnv } from './shared/env'
const DEFAULT_TEST_GAS_L1 = 330_000
const DEFAULT_TEST_GAS_L2 = 1_300_000
// TX size enforced by CTC:
const MAX_ROLLUP_TX_SIZE = 50_000
......@@ -183,13 +187,7 @@ describe('Native ETH Integration Tests', async () => {
).to.be.reverted
})
it('withdraw', async function () {
if (await isMainnet(env)) {
console.log('Skipping withdrawals test on mainnet.')
this.skip()
return
}
withdrawalTest('withdraw', async () => {
const withdrawAmount = BigNumber.from(3)
const preBalances = await getBalances(env)
expect(
......@@ -231,13 +229,7 @@ describe('Native ETH Integration Tests', async () => {
)
})
it('withdrawTo', async function () {
if (await isMainnet(env)) {
console.log('Skipping withdrawals test on mainnet.')
this.skip()
return
}
withdrawalTest('withdrawTo', async () => {
const withdrawAmount = BigNumber.from(3)
const preBalances = await getBalances(env)
......@@ -295,13 +287,9 @@ describe('Native ETH Integration Tests', async () => {
)
})
it('deposit, transfer, withdraw', async function () {
if (await isMainnet(env)) {
console.log('Skipping withdrawals test on mainnet.')
this.skip()
return
}
withdrawalTest(
'deposit, transfer, withdraw',
async () => {
// 1. deposit
const amount = utils.parseEther('1')
await env.waitForXDomainTransaction(
......@@ -363,5 +351,7 @@ describe('Native ETH Integration Tests', async () => {
const l2BalanceAfter = await other.getBalance()
expect(l1BalanceAfter).to.deep.eq(l1BalanceBefore.add(withdrawnAmount))
expect(l2BalanceAfter).to.deep.eq(amount.sub(withdrawnAmount).sub(fee))
})
},
envConfig.MOCHA_TIMEOUT * 3
)
})
......@@ -7,7 +7,12 @@ import { predeploys } from '@eth-optimism/contracts'
import { Contract, BigNumber } from 'ethers'
/* Imports: Internal */
import { l2Provider, l1Provider, IS_LIVE_NETWORK } from './shared/utils'
import {
l2Provider,
l1Provider,
envConfig,
DEFAULT_TEST_GAS_L1,
} from './shared/utils'
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
......@@ -41,11 +46,7 @@ describe('OVM Context: Layer 2 EVM Context', () => {
await Multicall.deployTransaction.wait()
})
let numTxs = 5
if (IS_LIVE_NETWORK) {
// Tests take way too long if we don't reduce the number of txs here.
numTxs = 1
}
const numTxs = envConfig.OVMCONTEXT_SPEC_NUM_TXS
it('enqueue: L1 contextual values are correctly set in L2', async () => {
for (let i = 0; i < numTxs; i++) {
......@@ -54,7 +55,10 @@ describe('OVM Context: Layer 2 EVM Context', () => {
const tx = await env.l1Messenger.sendMessage(
OVMContextStorage.address,
'0x',
2_000_000
2_000_000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
// Wait for the transaction to be sent over to L2.
......@@ -89,7 +93,7 @@ describe('OVM Context: Layer 2 EVM Context', () => {
const coinbase = await OVMContextStorage.coinbases(i)
expect(coinbase).to.equal(predeploys.OVM_SequencerFeeVault)
}
}).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 () => {
for (let i = 0; i < numTxs; i++) {
......
......@@ -7,7 +7,7 @@ import { injectL2Context, applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Imports: External */
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'
import { isLiveNetwork } from './shared/utils'
import { DEFAULT_TEST_GAS_L1, envConfig } from './shared/utils'
describe('Queue Ingestion', () => {
let env: OptimismEnv
......@@ -21,7 +21,7 @@ describe('Queue Ingestion', () => {
// 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 () => {
const numTxs = 5
const numTxs = envConfig.OVMCONTEXT_SPEC_NUM_TXS
// Enqueue some transactions by building the calldata and then sending
// the transaction to Layer 1
......@@ -30,7 +30,10 @@ describe('Queue Ingestion', () => {
const tx = await env.l1Messenger.sendMessage(
`0x${`${i}`.repeat(40)}`,
`0x0${i}`,
1_000_000
1_000_000,
{
gasLimit: DEFAULT_TEST_GAS_L1,
}
)
await tx.wait()
txs.push(tx)
......@@ -62,5 +65,5 @@ describe('Queue Ingestion', () => {
)
expect(l2Tx.l1BlockNumber).to.equal(l1TxReceipt.blockNumber)
}
}).timeout(isLiveNetwork() ? 300_000 : 100_000)
})
})
......@@ -4,26 +4,26 @@ import {
defaultTransactionFactory,
gasPriceForL2,
sleep,
isLiveNetwork,
envConfig,
} from './shared/utils'
import { TransactionReceipt } from '@ethersproject/abstract-provider'
describe('Replica Tests', () => {
let env: OptimismEnv
before(async () => {
before(async function () {
if (!envConfig.RUN_REPLICA_TESTS) {
this.skip()
return
}
env = await OptimismEnv.new()
})
describe('Matching blocks', () => {
if (isLiveNetwork()) {
console.log('Skipping replica tests on live network')
return
}
it('should sync a transaction', async () => {
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2(env)
tx.gasPrice = await gasPriceForL2()
const result = await env.l2Wallet.sendTransaction(tx)
let receipt: TransactionReceipt
......@@ -48,7 +48,7 @@ describe('Replica Tests', () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await env.l2Wallet.signTransaction(tx)
......@@ -76,7 +76,7 @@ describe('Replica Tests', () => {
const tx = {
...defaultTransactionFactory(),
nonce: await env.l2Wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
}
const signed = await env.l2Wallet.signTransaction(tx)
const result = await env.replicaProvider.sendTransaction(signed)
......
......@@ -10,8 +10,10 @@ import {
defaultTransactionFactory,
fundUser,
L2_CHAINID,
isLiveNetwork,
gasPriceForL2,
isHardhat,
hardhatTest,
envConfig,
} from './shared/utils'
import { OptimismEnv } from './shared/env'
import {
......@@ -56,7 +58,7 @@ describe('Basic RPC tests', () => {
describe('eth_sendRawTransaction', () => {
it('should correctly process a valid transaction', async () => {
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2(env)
tx.gasPrice = await gasPriceForL2()
const nonce = await wallet.getTransactionCount()
const result = await wallet.sendTransaction(tx)
......@@ -70,7 +72,7 @@ describe('Basic RPC tests', () => {
it('should not accept a transaction with the wrong chain ID', async () => {
const tx = {
...defaultTransactionFactory(),
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
chainId: (await wallet.getChainId()) + 1,
}
......@@ -83,7 +85,7 @@ describe('Basic RPC tests', () => {
const tx = {
...defaultTransactionFactory(),
nonce: await wallet.getTransactionCount(),
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
chainId: null, // Disables EIP155 transaction signing.
}
const signed = await wallet.signTransaction(tx)
......@@ -97,7 +99,7 @@ describe('Basic RPC tests', () => {
it('should accept a transaction with a value', async () => {
const tx = {
...defaultTransactionFactory(),
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
chainId: await env.l2Wallet.getChainId(),
data: '0x',
value: ethers.utils.parseEther('0.1'),
......@@ -117,7 +119,7 @@ describe('Basic RPC tests', () => {
const balance = await env.l2Wallet.getBalance()
const tx = {
...defaultTransactionFactory(),
gasPrice: await gasPriceForL2(env),
gasPrice: await gasPriceForL2(),
chainId: await env.l2Wallet.getChainId(),
data: '0x',
value: balance.add(ethers.utils.parseEther('1')),
......@@ -137,13 +139,12 @@ describe('Basic RPC tests', () => {
})
it('should reject a transaction with too low of a fee', async () => {
if (isLiveNetwork()) {
console.log('Skipping too low of a fee test on live network')
return
}
const gasPrice = await env.gasPriceOracle.gasPrice()
const isHH = await isHardhat()
let gasPrice
if (isHH) {
gasPrice = await env.gasPriceOracle.gasPrice()
await env.gasPriceOracle.setGasPrice(1000)
}
const tx = {
...defaultTransactionFactory(),
......@@ -151,18 +152,16 @@ describe('Basic RPC tests', () => {
}
await expect(env.l2Wallet.sendTransaction(tx)).to.be.rejectedWith(
`gas price too low: 1 wei, use at least tx.gasPrice = 1000 wei`
/gas price too low: 1 wei, use at least tx\.gasPrice = \d+ wei/
)
if (isHH) {
// Reset the gas price to its original price
await env.gasPriceOracle.setGasPrice(gasPrice)
}
})
it('should reject a transaction with too high of a fee', async () => {
if (isLiveNetwork()) {
console.log('Skpping too high of a fee test on live network')
return
}
const gasPrice = await env.gasPriceOracle.gasPrice()
const largeGasPrice = gasPrice.mul(10)
const tx = {
......@@ -332,7 +331,7 @@ describe('Basic RPC tests', () => {
it('includes L1 gas price and L1 gas used', async () => {
const tx = await env.l2Wallet.populateTransaction({
to: env.l2Wallet.address,
gasPrice: isLiveNetwork() ? 10000 : 1,
gasPrice: await gasPriceForL2(),
})
const raw = serialize({
......@@ -367,7 +366,7 @@ describe('Basic RPC tests', () => {
describe('eth_getTransactionByHash', () => {
it('should be able to get all relevant l1/l2 transaction data', async () => {
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2(env)
tx.gasPrice = await gasPriceForL2()
const result = await wallet.sendTransaction(tx)
await result.wait()
......@@ -382,7 +381,7 @@ describe('Basic RPC tests', () => {
it('should return the block and all included transactions', async () => {
// Send a transaction and wait for it to be mined.
const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2(env)
tx.gasPrice = await gasPriceForL2()
const result = await wallet.sendTransaction(tx)
const receipt = await result.wait()
......@@ -408,11 +407,9 @@ describe('Basic RPC tests', () => {
// 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 (isLiveNetwork()) {
this.skip()
}
hardhatTest(
'should return the same result when new transactions are not applied',
async () => {
// Get latest block once to start.
const prev = await provider.getBlockWithTransactions('latest')
// set wait to null to allow a deep object comparison
......@@ -433,7 +430,8 @@ describe('Basic RPC tests', () => {
expect(latest).to.deep.equal(prev)
await sleep(2000)
}
})
}
)
})
describe('eth_getBalance', () => {
......@@ -489,6 +487,12 @@ describe('Basic RPC tests', () => {
})
describe('debug_traceTransaction', () => {
before(async function () {
if (!envConfig.RUN_DEBUG_TRACE_TESTS) {
this.skip()
}
})
it('should match debug_traceBlock', async () => {
const storage = await ethers.getContractFactory(
'SimpleStorage',
......
import { DockerComposeNetwork } from './shared/docker-compose'
before(async () => {
if (!process.env.NO_NETWORK) {
await new DockerComposeNetwork().up()
}
})
import * as compose from 'docker-compose'
import * as shell from 'shelljs'
import * as path from 'path'
type ServiceNames =
| 'batch_submitter'
| 'dtl'
| 'l2geth'
| 'relayer'
| 'verifier'
| 'replica'
const OPS_DIRECTORY = path.join(process.cwd(), '../ops')
const DEFAULT_SERVICES: ServiceNames[] = [
'batch_submitter',
'dtl',
'l2geth',
'relayer',
]
export class DockerComposeNetwork {
constructor(private readonly services: ServiceNames[] = DEFAULT_SERVICES) {}
async up(options?: compose.IDockerComposeOptions) {
const out = await compose.upMany(this.services, {
cwd: OPS_DIRECTORY,
...options,
})
const { err, exitCode } = out
if (!err || exitCode) {
console.error(err)
throw new Error(
'Unexpected error when starting docker-compose network, dumping output'
)
}
if (err.includes('Creating')) {
console.info(
'🐳 Tests required starting containers. Waiting for sequencer to ready.'
)
shell.exec(`${OPS_DIRECTORY}/scripts/wait-for-sequencer.sh`, {
cwd: OPS_DIRECTORY,
})
}
return out
}
async logs() {
return compose.logs(this.services, { cwd: OPS_DIRECTORY })
}
async stop(service: ServiceNames) {
return compose.stopOne(service, { cwd: OPS_DIRECTORY })
}
async rm() {
return compose.rm({ cwd: OPS_DIRECTORY })
}
}
......@@ -19,6 +19,8 @@ import {
getL1Bridge,
getL2Bridge,
sleep,
envConfig,
DEFAULT_TEST_GAS_L1,
} from './utils'
import {
initWatcher,
......@@ -83,8 +85,10 @@ export class OptimismEnv {
// fund the user if needed
const balance = await l2Wallet.getBalance()
if (balance.lt(utils.parseEther('1'))) {
await fundUser(watcher, l1Bridge, utils.parseEther('1').sub(balance))
const min = envConfig.L2_WALLET_MIN_BALANCE_ETH.toString()
const topUp = envConfig.L2_WALLET_TOP_UP_AMOUNT_ETH.toString()
if (balance.lt(utils.parseEther(min))) {
await fundUser(watcher, l1Bridge, utils.parseEther(topUp))
}
const l1Messenger = getContractFactory('L1CrossDomainMessenger')
.connect(l1Wallet)
......@@ -156,6 +160,7 @@ export class OptimismEnv {
tx: Promise<TransactionResponse> | TransactionResponse
): Promise<void> {
tx = await tx
await tx.wait()
let messagePairs = []
while (true) {
......@@ -187,7 +192,10 @@ export class OptimismEnv {
message.sender,
message.message,
message.messageNonce,
proof
proof,
{
gasLimit: DEFAULT_TEST_GAS_L1 * 10,
}
)
await result.wait()
break
......
......@@ -23,7 +23,7 @@ export const fundRandomWallet = async (
const fundTx = await env.l1Wallet.sendTransaction({
gasLimit: 25_000,
to: wallet.address,
gasPrice: await gasPriceForL1(env),
gasPrice: await gasPriceForL1(),
value,
})
await fundTx.wait()
......@@ -47,7 +47,7 @@ export const executeL1ToL2Transaction = async (
),
MESSAGE_GAS,
{
gasPrice: await gasPriceForL1(env),
gasPrice: await gasPriceForL1(),
}
)
)
......@@ -71,7 +71,7 @@ export const executeL2ToL1Transaction = async (
),
MESSAGE_GAS,
{
gasPrice: gasPriceForL2(env),
gasPrice: gasPriceForL2(),
}
)
)
......@@ -90,7 +90,7 @@ export const executeL2Transaction = async (
tx.contract
.connect(signer)
.functions[tx.functionName](...tx.functionParams, {
gasPrice: gasPriceForL2(env),
gasPrice: gasPriceForL2(),
})
)
await result.wait()
......
......@@ -14,32 +14,54 @@ import {
predeploys,
} from '@eth-optimism/contracts'
import { injectL2Context, remove0x, Watcher } from '@eth-optimism/core-utils'
import { cleanEnv, str, num, bool } from 'envalid'
import { cleanEnv, str, num, bool, makeValidator } from 'envalid'
import dotenv from 'dotenv'
dotenv.config()
/* Imports: Internal */
import { Direction, waitForXDomainTransaction } from './watcher-utils'
import { OptimismEnv } from './env'
export const GWEI = BigNumber.from(1e9)
export const isLiveNetwork = () => {
return process.env.IS_LIVE_NETWORK === 'true'
}
if (isLiveNetwork()) {
dotenv.config()
}
export const HARDHAT_CHAIN_ID = 31337
export const DEFAULT_TEST_GAS_L1 = 330_000
export const DEFAULT_TEST_GAS_L2 = 1_300_000
export const ON_CHAIN_GAS_PRICE = 'onchain'
const gasPriceValidator = makeValidator((gasPrice) => {
if (gasPrice === 'onchain') {
return gasPrice
}
const env = cleanEnv(process.env, {
return num()._parse(gasPrice).toString()
})
const procEnv = cleanEnv(process.env, {
L1_GAS_PRICE: gasPriceValidator({
default: '0',
}),
L1_URL: str({ default: 'http://localhost:9545' }),
L2_URL: str({ default: 'http://localhost:8545' }),
VERIFIER_URL: str({ default: 'http://localhost:8547' }),
REPLICA_URL: str({ default: 'http://localhost:8549' }),
L1_POLLING_INTERVAL: num({ default: 10 }),
L2_CHAINID: num({ default: 420 }),
L2_GAS_PRICE: gasPriceValidator({
default: 'onchain',
}),
L2_URL: str({ default: 'http://localhost:8545' }),
L2_POLLING_INTERVAL: num({ default: 10 }),
VERIFIER_POLLING_INTERVAL: num({ default: 10 }),
L2_WALLET_MIN_BALANCE_ETH: num({
default: 2,
}),
L2_WALLET_TOP_UP_AMOUNT_ETH: num({
default: 3,
}),
REPLICA_URL: str({ default: 'http://localhost:8549' }),
REPLICA_POLLING_INTERVAL: num({ default: 10 }),
PRIVATE_KEY: str({
default:
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
......@@ -51,31 +73,53 @@ const env = cleanEnv(process.env, {
default:
'0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba',
}),
L2_CHAINID: num({ default: 420 }),
IS_LIVE_NETWORK: bool({ default: false }),
OVMCONTEXT_SPEC_NUM_TXS: num({
default: 5,
}),
DTL_ENQUEUE_CONFIRMATIONS: num({
default: 0,
}),
RUN_WITHDRAWAL_TESTS: bool({
default: true,
}),
RUN_REPLICA_TESTS: bool({
default: true,
}),
RUN_DEBUG_TRACE_TESTS: bool({
default: true,
}),
RUN_STRESS_TESTS: bool({
default: true,
}),
MOCHA_TIMEOUT: num({
default: 120_000,
}),
MOCHA_BAIL: bool({
default: false,
}),
})
export const envConfig = procEnv
// The hardhat instance
export const l1Provider = new providers.JsonRpcProvider(env.L1_URL)
l1Provider.pollingInterval = env.L1_POLLING_INTERVAL
export const l1Provider = new providers.JsonRpcProvider(procEnv.L1_URL)
l1Provider.pollingInterval = procEnv.L1_POLLING_INTERVAL
export const l2Provider = injectL2Context(
new providers.JsonRpcProvider(env.L2_URL)
new providers.JsonRpcProvider(procEnv.L2_URL)
)
l2Provider.pollingInterval = env.L2_POLLING_INTERVAL
export const verifierProvider = injectL2Context(
new providers.JsonRpcProvider(env.VERIFIER_URL)
)
verifierProvider.pollingInterval = env.VERIFIER_POLLING_INTERVAL
l2Provider.pollingInterval = procEnv.L2_POLLING_INTERVAL
export const replicaProvider = injectL2Context(
new providers.JsonRpcProvider(env.REPLICA_URL)
new providers.JsonRpcProvider(procEnv.REPLICA_URL)
)
replicaProvider.pollingInterval = env.REPLICA_POLLING_INTERVAL
replicaProvider.pollingInterval = procEnv.REPLICA_POLLING_INTERVAL
// The sequencer private key which is funded on L1
export const l1Wallet = new Wallet(env.PRIVATE_KEY, l1Provider)
export const l1Wallet = new Wallet(procEnv.PRIVATE_KEY, l1Provider)
// A random private key which should always be funded with deposits from L1 -> L2
// if it's using non-0 gas price
......@@ -83,7 +127,7 @@ export const l2Wallet = l1Wallet.connect(l2Provider)
// The owner of the GasPriceOracle on L2
export const gasPriceOracleWallet = new Wallet(
env.GAS_PRICE_ORACLE_PRIVATE_KEY,
procEnv.GAS_PRICE_ORACLE_PRIVATE_KEY,
l2Provider
)
......@@ -92,13 +136,12 @@ 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 L2_CHAINID = procEnv.L2_CHAINID
export const getAddressManager = (provider: any) => {
return getContractFactory('Lib_AddressManager')
.connect(provider)
.attach(env.ADDRESS_MANAGER)
.attach(procEnv.ADDRESS_MANAGER)
}
// Gets the bridge contract
......@@ -115,33 +158,17 @@ export const getL1Bridge = async (wallet: Wallet, AddressManager: Contract) => {
throw new Error('Proxy__OVM_L1StandardBridge not found')
}
const L1StandardBridge = new Contract(
ProxyBridgeAddress,
l1BridgeInterface,
wallet
)
return L1StandardBridge
return new Contract(ProxyBridgeAddress, l1BridgeInterface, wallet)
}
export const getL2Bridge = async (wallet: Wallet) => {
const L2BridgeInterface = getContractInterface('L2StandardBridge')
const L2StandardBridge = new Contract(
predeploys.L2StandardBridge,
L2BridgeInterface,
wallet
)
return L2StandardBridge
return new Contract(predeploys.L2StandardBridge, L2BridgeInterface, wallet)
}
export const getOvmEth = (wallet: Wallet) => {
const OVM_ETH = new Contract(
OVM_ETH_ADDRESS,
getContractInterface('OVM_ETH'),
wallet
)
return OVM_ETH
return new Contract(OVM_ETH_ADDRESS, getContractInterface('OVM_ETH'), wallet)
}
export const fundUser = async (
......@@ -152,12 +179,54 @@ export const fundUser = async (
) => {
const value = BigNumber.from(amount)
const tx = recipient
? bridge.depositETHTo(recipient, 1_300_000, '0x', { value })
: bridge.depositETH(1_300_000, '0x', { value })
? bridge.depositETHTo(recipient, DEFAULT_TEST_GAS_L2, '0x', {
value,
gasLimit: DEFAULT_TEST_GAS_L1,
})
: bridge.depositETH(DEFAULT_TEST_GAS_L2, '0x', {
value,
gasLimit: DEFAULT_TEST_GAS_L1,
})
await waitForXDomainTransaction(watcher, tx, Direction.L1ToL2)
}
export const conditionalTest = (
condition: (env?: OptimismEnv) => Promise<boolean>,
name,
fn,
message?: string,
timeout?: number
) => {
it(name, async function () {
const shouldRun = await condition()
if (!shouldRun) {
console.log(message)
this.skip()
return
}
await fn()
}).timeout(timeout || envConfig.MOCHA_TIMEOUT)
}
export const withdrawalTest = (name, fn, timeout?: number) =>
conditionalTest(
() => Promise.resolve(procEnv.RUN_WITHDRAWAL_TESTS),
name,
fn,
`Skipping withdrawal test.`,
timeout
)
export const hardhatTest = (name, fn) =>
conditionalTest(
isHardhat,
name,
fn,
'Skipping test on non-Hardhat environment.'
)
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
const abiCoder = new utils.AbiCoder()
......@@ -175,56 +244,23 @@ export const defaultTransactionFactory = () => {
}
}
export const waitForL2Geth = async (
provider: providers.JsonRpcProvider
): Promise<providers.JsonRpcProvider> => {
let ready: boolean = false
while (!ready) {
try {
await provider.getNetwork()
ready = true
} catch (error) {
await sleep(1000)
}
}
return injectL2Context(provider)
}
// eslint-disable-next-line @typescript-eslint/no-shadow
export const gasPriceForL2 = async (env: OptimismEnv) => {
// The integration tests enforce fees on L2
// which run against hardhat on L1. Update if
// geth --dev is adopted for L1
const chainId = await env.l1Wallet.getChainId()
if ((await isMainnet(env)) || chainId === 31337) {
return env.l2Wallet.getGasPrice()
export const gasPriceForL2 = async () => {
if (procEnv.L2_GAS_PRICE === ON_CHAIN_GAS_PRICE) {
return l2Wallet.getGasPrice()
}
if (isLiveNetwork()) {
return Promise.resolve(BigNumber.from(10000))
}
return Promise.resolve(BigNumber.from(0))
return utils.parseUnits(procEnv.L2_GAS_PRICE, 'wei')
}
// eslint-disable-next-line @typescript-eslint/no-shadow
export const gasPriceForL1 = async (env: OptimismEnv) => {
const chainId = await env.l1Wallet.getChainId()
switch (chainId) {
case 1:
return env.l1Wallet.getGasPrice()
case 3:
case 42:
return utils.parseUnits('10', 'gwei')
case 5:
return utils.parseUnits('2', 'gwei')
default:
return BigNumber.from(0)
export const gasPriceForL1 = async () => {
if (procEnv.L1_GAS_PRICE === ON_CHAIN_GAS_PRICE) {
return l1Wallet.getGasPrice()
}
return utils.parseUnits(procEnv.L1_GAS_PRICE, 'wei')
}
// eslint-disable-next-line @typescript-eslint/no-shadow
export const isMainnet = async (env: OptimismEnv) => {
const chainId = await env.l1Wallet.getChainId()
return chainId === 1
export const isHardhat = async () => {
const chainId = await l1Wallet.getChainId()
return chainId === HARDHAT_CHAIN_ID
}
......@@ -17,12 +17,12 @@ import {
} from './shared/stress-test-helpers'
/* Imports: Artifacts */
import { fundUser, isLiveNetwork, isMainnet } from './shared/utils'
import { envConfig, fundUser } from './shared/utils'
// Need a big timeout to allow for all transactions to be processed.
// For some reason I can't figure out how to set the timeout on a per-suite basis
// so I'm instead setting it for every test.
const STRESS_TEST_TIMEOUT = isLiveNetwork() ? 500_000 : 1_200_000
const STRESS_TEST_TIMEOUT = envConfig.MOCHA_TIMEOUT * 5
describe('stress tests', () => {
const numTransactions = 3
......@@ -32,13 +32,14 @@ describe('stress tests', () => {
const wallets: Wallet[] = []
before(async function () {
env = await OptimismEnv.new()
if (await isMainnet(env)) {
console.log('Skipping stress tests on mainnet.')
if (!envConfig.RUN_STRESS_TESTS) {
console.log('Skipping stress tests.')
this.skip()
return
}
env = await OptimismEnv.new()
for (let i = 0; i < numTransactions; i++) {
wallets.push(Wallet.createRandom())
}
......
......@@ -3210,14 +3210,6 @@
"@types/mime" "^1"
"@types/node" "*"
"@types/shelljs@^0.8.8":
version "0.8.9"
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.9.tgz#45dd8501aa9882976ca3610517dac3831c2fbbf4"
integrity sha512-flVe1dvlrCyQJN/SGrnBxqHG+RzXrVKsmjD8WS/qYHpq5UPjfq7UWFBENP0ZuOl0g6OpAlL6iBoLSvKYUUmyQw==
dependencies:
"@types/glob" "*"
"@types/node" "*"
"@types/sinon-chai@^3.2.3", "@types/sinon-chai@^3.2.5":
version "3.2.5"
resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.5.tgz#df21ae57b10757da0b26f512145c065f2ad45c48"
......@@ -6247,13 +6239,6 @@ directory-tree@^2.2.7:
resolved "https://registry.yarnpkg.com/directory-tree/-/directory-tree-2.3.1.tgz#78b8aa84878eb84dd29a51dcd664ded4cd0247c7"
integrity sha512-hxolIHCtQ/a56CUywaLzGD/V78zPwFihI+UK/4ZjOp7GoV4Mptmtv95yavOn/RlnTi7cCMjszvfcNrwCoWLH+Q==
docker-compose@^0.23.8:
version "0.23.13"
resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.13.tgz#77d37bd05b6a966345f631e6d05e961c79514f06"
integrity sha512-/9fYC4g3AO+qsqxIZhmbVnFvJJPcYEV2yJbAPPXH+6AytU3urIY8lUAXOlvY8sl4u25pdKu1JrOfAmWC7lJDJg==
dependencies:
yaml "^1.10.2"
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
......@@ -13963,7 +13948,7 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shelljs@^0.8.3, shelljs@^0.8.4:
shelljs@^0.8.3:
version "0.8.5"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c"
integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==
......@@ -16639,7 +16624,7 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.10.0, yaml@^1.10.2:
yaml@^1.10.0:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
......
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