Commit cee2a464 authored by Matthew Slipper's avatar Matthew Slipper Committed by Kelvin Fichter

feat: integration-tests core-utils Kovan integration tests

parent 499d8736
---
'@eth-optimism/core-utils': patch
---
Add awaitCondition to core utils
...@@ -13,19 +13,23 @@ yarn build ...@@ -13,19 +13,23 @@ yarn build
### Testing a live network ### Testing a live network
Create an `.env` file and fill it out. Testing on a live network is a bit more complicated than testing locally. You'll need the following in order to do so:
Look at `.env.example` to know which variables to include.
Once you have your environment set up, run: 1. A pre-funded wallet with at least 40 ETH.
2. URLs to an L1 and L2 node.
3. The address of the address manager contract.
4. The chain ID of the L2.
Once you have all the necessary info, create a `.env` file like the one in `.env.example` and fill it in with the values above. Then, run:
```bash ```bash
yarn test:integration:live yarn test:integration:live
``` ```
This will take quite a long time. Kovan, for example, takes about 30 minutes to complete.
You can also set environment variables on the command line instead of inside `.env` if you want: You can also set environment variables on the command line instead of inside `.env` if you want:
```bash ```bash
L1_URL=whatever L2_URL=whatever yarn test:integration:live L1_URL=whatever L2_URL=whatever yarn test:integration:live
``` ```
Note that this can take an extremely long time (~1hr).
...@@ -4,6 +4,7 @@ import { HardhatUserConfig } from 'hardhat/types' ...@@ -4,6 +4,7 @@ import { HardhatUserConfig } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers' import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle' import '@nomiclabs/hardhat-waffle'
import 'hardhat-gas-reporter' import 'hardhat-gas-reporter'
import { isLiveNetwork } from './test/shared/utils'
const enableGasReport = !!process.env.ENABLE_GAS_REPORT const enableGasReport = !!process.env.ENABLE_GAS_REPORT
...@@ -14,7 +15,7 @@ const config: HardhatUserConfig = { ...@@ -14,7 +15,7 @@ const config: HardhatUserConfig = {
}, },
}, },
mocha: { mocha: {
timeout: 75_000, timeout: isLiveNetwork() ? 300_000 : 75_000,
}, },
solidity: { solidity: {
version: '0.8.9', version: '0.8.9',
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"lint:check": "eslint .", "lint:check": "eslint .",
"build": "hardhat compile", "build": "hardhat compile",
"test:integration": "hardhat --network optimism test", "test:integration": "hardhat --network optimism test",
"test:integration:live": "IS_LIVE_NETWORK=true hardhat --network optimism test", "test:integration:live": "NO_NETWORK=true IS_LIVE_NETWORK=true hardhat --network optimism test",
"test:sync": "hardhat --network optimism test sync-tests/*.spec.ts --no-compile", "test:sync": "hardhat --network optimism test sync-tests/*.spec.ts --no-compile",
"clean": "rimraf cache artifacts" "clean": "rimraf cache artifacts"
}, },
......
...@@ -2,13 +2,14 @@ import { expect } from 'chai' ...@@ -2,13 +2,14 @@ import { expect } from 'chai'
/* Imports: External */ /* Imports: External */
import { Contract, ContractFactory } from 'ethers' import { Contract, ContractFactory } from 'ethers'
import { applyL1ToL2Alias, sleep } from '@eth-optimism/core-utils' import { applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Imports: Internal */ /* Imports: Internal */
import simpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json' import simpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json'
import l2ReverterJson from '../artifacts/contracts/Reverter.sol/Reverter.json' import l2ReverterJson from '../artifacts/contracts/Reverter.sol/Reverter.json'
import { Direction } from './shared/watcher-utils' import { Direction } from './shared/watcher-utils'
import { OptimismEnv, useDynamicTimeoutForWithdrawals } from './shared/env' import { OptimismEnv } from './shared/env'
import { awaitCondition } from '@eth-optimism/core-utils'
describe('Basic L1<>L2 Communication', async () => { describe('Basic L1<>L2 Communication', async () => {
let Factory__L1SimpleStorage: ContractFactory let Factory__L1SimpleStorage: ContractFactory
...@@ -48,9 +49,7 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -48,9 +49,7 @@ describe('Basic L1<>L2 Communication', async () => {
}) })
describe('L2 => L1', () => { describe('L2 => L1', () => {
it('should be able to perform a withdrawal from L2 -> L1', async function () { it('should be able to perform a withdrawal from L2 -> L1', async () => {
await useDynamicTimeoutForWithdrawals(this, env)
const value = `0x${'77'.repeat(32)}` const value = `0x${'77'.repeat(32)}`
// Send L2 -> L1 message. // Send L2 -> L1 message.
...@@ -114,8 +113,14 @@ describe('Basic L1<>L2 Communication', async () => { ...@@ -114,8 +113,14 @@ describe('Basic L1<>L2 Communication', async () => {
]) ])
) )
// TODO: We need to have a function that can wait for enqueued txs. await awaitCondition(
await sleep(10000) async () => {
const sender = await L2SimpleStorage.msgSender()
return sender === env.l1Wallet.address
},
2000,
60
)
// No aliasing when an EOA goes directly to L2. // No aliasing when an EOA goes directly to L2.
expect(await L2SimpleStorage.msgSender()).to.equal(env.l1Wallet.address) expect(await L2SimpleStorage.msgSender()).to.equal(env.l1Wallet.address)
......
...@@ -13,11 +13,14 @@ import { ...@@ -13,11 +13,14 @@ import {
} from '@eth-optimism/contracts' } from '@eth-optimism/contracts'
/* Imports: Internal */ /* Imports: Internal */
import { IS_LIVE_NETWORK } from './shared/utils' import { isLiveNetwork } from './shared/utils'
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils' import { Direction } from './shared/watcher-utils'
const setPrices = async (env: OptimismEnv, value: number | BigNumber) => { const setPrices = async (env: OptimismEnv, value: number | BigNumber) => {
if (isLiveNetwork()) {
return
}
const gasPrice = await env.gasPriceOracle.setGasPrice(value) const gasPrice = await env.gasPriceOracle.setGasPrice(value)
await gasPrice.wait() await gasPrice.wait()
const baseFee = await env.gasPriceOracle.setL1BaseFee(value) const baseFee = await env.gasPriceOracle.setL1BaseFee(value)
...@@ -32,20 +35,22 @@ describe('Fee Payment Integration Tests', async () => { ...@@ -32,20 +35,22 @@ describe('Fee Payment Integration Tests', async () => {
env = await OptimismEnv.new() env = await OptimismEnv.new()
}) })
it(`should return eth_gasPrice equal to OVM_GasPriceOracle.gasPrice`, async () => { if (!isLiveNetwork()) {
const assertGasPrice = async () => { it(`should return eth_gasPrice equal to OVM_GasPriceOracle.gasPrice`, async () => {
const gasPrice = await env.l2Wallet.getGasPrice() const assertGasPrice = async () => {
const oracleGasPrice = await env.gasPriceOracle.gasPrice() const gasPrice = await env.l2Wallet.getGasPrice()
expect(gasPrice).to.deep.equal(oracleGasPrice) const oracleGasPrice = await env.gasPriceOracle.gasPrice()
} expect(gasPrice).to.deep.equal(oracleGasPrice)
}
assertGasPrice() assertGasPrice()
// update the gas price // update the gas price
const tx = await env.gasPriceOracle.setGasPrice(1000) const tx = await env.gasPriceOracle.setGasPrice(1000)
await tx.wait() await tx.wait()
assertGasPrice() assertGasPrice()
}) })
}
it('Paying a nonzero but acceptable gasPrice fee', async () => { it('Paying a nonzero but acceptable gasPrice fee', async () => {
await setPrices(env, 1000) await setPrices(env, 1000)
...@@ -152,19 +157,16 @@ describe('Fee Payment Integration Tests', async () => { ...@@ -152,19 +157,16 @@ describe('Fee Payment Integration Tests', async () => {
}) })
it('should be able to withdraw fees back to L1 once the minimum is met', async function () { it('should be able to withdraw fees back to L1 once the minimum is met', async function () {
if (isLiveNetwork()) {
this.skip()
return
}
const l1FeeWallet = await env.sequencerFeeVault.l1FeeWallet() const l1FeeWallet = await env.sequencerFeeVault.l1FeeWallet()
const balanceBefore = await env.l1Wallet.provider.getBalance(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()
const l2WalletBalance = await env.l2Wallet.getBalance() 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. // Transfer the minimum required to withdraw.
const tx = await env.l2Wallet.sendTransaction({ const tx = await env.l2Wallet.sendTransaction({
......
import { BigNumber, Contract, ContractFactory, Wallet } from 'ethers' import {
BigNumber,
BigNumberish,
Contract,
ContractFactory,
Wallet,
} from 'ethers'
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
import chai, { expect } from 'chai' import chai, { expect } from 'chai'
import { GWEI, fundUser, encodeSolidityRevertMessage } from './shared/utils' import {
fundUser,
encodeSolidityRevertMessage,
gasPriceForL2,
} from './shared/utils'
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
import { solidity } from 'ethereum-waffle' import { solidity } from 'ethereum-waffle'
import { sleep } from '../../packages/core-utils/dist'
import {
getContractFactory,
getContractInterface,
} from '../../packages/contracts/dist'
import { Interface } from 'ethers/lib/utils'
chai.use(solidity) chai.use(solidity)
...@@ -32,15 +36,16 @@ describe('Native ETH value integration tests', () => { ...@@ -32,15 +36,16 @@ describe('Native ETH value integration tests', () => {
] ]
} }
const checkBalances = async ( const expectBalancesWithinRange = (
expectedBalances: BigNumber[] bal: BigNumber,
): Promise<void> => { lte: BigNumber,
const realBalances = await getBalances() gte: BigNumber
expect(realBalances[0]).to.deep.eq(expectedBalances[0]) ) => {
expect(realBalances[1]).to.deep.eq(expectedBalances[1]) expect(bal.lte(lte)).to.be.true
expect(bal.gte(gte)).to.be.true
} }
const value = 10 const value = ethers.utils.parseEther('0.01')
await fundUser(env.watcher, env.l1Bridge, value, wallet.address) await fundUser(env.watcher, env.l1Bridge, value, wallet.address)
const initialBalances = await getBalances() const initialBalances = await getBalances()
...@@ -48,23 +53,40 @@ describe('Native ETH value integration tests', () => { ...@@ -48,23 +53,40 @@ describe('Native ETH value integration tests', () => {
const there = await wallet.sendTransaction({ const there = await wallet.sendTransaction({
to: other.address, to: other.address,
value, value,
gasPrice: 0, gasPrice: await gasPriceForL2(),
}) })
await there.wait() const thereReceipt = await there.wait()
const thereGas = thereReceipt.gasUsed.mul(there.gasPrice)
await checkBalances([ const thereBalances = await getBalances()
const thereWithGas = initialBalances[0].sub(value).sub(thereGas).sub(100000)
expectBalancesWithinRange(
thereBalances[0],
initialBalances[0].sub(value), initialBalances[0].sub(value),
initialBalances[1].add(value), thereWithGas
]) )
expect(initialBalances[1].add(value).eq(thereBalances[1]))
const backVal = ethers.utils.parseEther('0.005')
const backAgain = await other.sendTransaction({ const backAgain = await other.sendTransaction({
to: wallet.address, to: wallet.address,
value, value: backVal,
gasPrice: 0, gasPrice: await gasPriceForL2(),
}) })
await backAgain.wait() const backReceipt = await backAgain.wait()
const backGas = backReceipt.gasUsed.mul(backAgain.gasPrice)
await checkBalances(initialBalances)
const backBalances = await getBalances()
expectBalancesWithinRange(
backBalances[0],
initialBalances[0].sub(thereGas).sub(backVal),
initialBalances[0].sub(thereGas).sub(backVal).sub(200000)
)
expectBalancesWithinRange(
backBalances[1],
initialBalances[1].add(backVal).sub(backGas),
initialBalances[1].add(backVal).sub(backGas).sub(200000)
)
}) })
describe(`calls between OVM contracts with native ETH value and relevant opcodes`, async () => { describe(`calls between OVM contracts with native ETH value and relevant opcodes`, async () => {
...@@ -155,7 +177,7 @@ describe('Native ETH value integration tests', () => { ...@@ -155,7 +177,7 @@ describe('Native ETH value integration tests', () => {
it('should allow ETH to be sent', async () => { it('should allow ETH to be sent', async () => {
const sendAmount = 15 const sendAmount = 15
const tx = await ValueCalls0.simpleSend(ValueCalls1.address, sendAmount, { const tx = await ValueCalls0.simpleSend(ValueCalls1.address, sendAmount, {
gasPrice: 0, gasPrice: await gasPriceForL2(),
}) })
await tx.wait() await tx.wait()
......
...@@ -183,9 +183,7 @@ describe('Native ETH Integration Tests', async () => { ...@@ -183,9 +183,7 @@ describe('Native ETH Integration Tests', async () => {
).to.be.reverted ).to.be.reverted
}) })
it('withdraw', async function () { it('withdraw', async () => {
await useDynamicTimeoutForWithdrawals(this, env)
const withdrawAmount = BigNumber.from(3) const withdrawAmount = BigNumber.from(3)
const preBalances = await getBalances(env) const preBalances = await getBalances(env)
expect( expect(
...@@ -227,9 +225,7 @@ describe('Native ETH Integration Tests', async () => { ...@@ -227,9 +225,7 @@ describe('Native ETH Integration Tests', async () => {
) )
}) })
it('withdrawTo', async function () { it('withdrawTo', async () => {
await useDynamicTimeoutForWithdrawals(this, env)
const withdrawAmount = BigNumber.from(3) const withdrawAmount = BigNumber.from(3)
const preBalances = await getBalances(env) const preBalances = await getBalances(env)
...@@ -287,9 +283,7 @@ describe('Native ETH Integration Tests', async () => { ...@@ -287,9 +283,7 @@ describe('Native ETH Integration Tests', async () => {
) )
}) })
it('deposit, transfer, withdraw', async function () { it('deposit, transfer, withdraw', async () => {
await useDynamicTimeoutForWithdrawals(this, env)
// 1. deposit // 1. deposit
const amount = utils.parseEther('1') const amount = utils.parseEther('1')
await env.waitForXDomainTransaction( await env.waitForXDomainTransaction(
......
...@@ -7,6 +7,7 @@ import { injectL2Context, applyL1ToL2Alias } from '@eth-optimism/core-utils' ...@@ -7,6 +7,7 @@ import { injectL2Context, applyL1ToL2Alias } from '@eth-optimism/core-utils'
/* Imports: External */ /* Imports: External */
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils' import { Direction } from './shared/watcher-utils'
import { isLiveNetwork } from './shared/utils'
describe('Queue Ingestion', () => { describe('Queue Ingestion', () => {
let env: OptimismEnv let env: OptimismEnv
...@@ -61,5 +62,5 @@ describe('Queue Ingestion', () => { ...@@ -61,5 +62,5 @@ describe('Queue Ingestion', () => {
) )
expect(l2Tx.l1BlockNumber).to.equal(l1TxReceipt.blockNumber) expect(l2Tx.l1BlockNumber).to.equal(l1TxReceipt.blockNumber)
} }
}).timeout(100_000) }).timeout(isLiveNetwork() ? 300_000 : 100_000)
}) })
...@@ -6,10 +6,12 @@ import chai, { expect } from 'chai' ...@@ -6,10 +6,12 @@ import chai, { expect } from 'chai'
import { import {
sleep, sleep,
l2Provider, l2Provider,
DEFAULT_TRANSACTION, defaultTransactionFactory,
fundUser, fundUser,
L2_CHAINID, L2_CHAINID,
IS_LIVE_NETWORK, IS_LIVE_NETWORK,
isLiveNetwork,
gasPriceForL2,
} from './shared/utils' } from './shared/utils'
import chaiAsPromised from 'chai-as-promised' import chaiAsPromised from 'chai-as-promised'
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
...@@ -57,7 +59,8 @@ describe('Basic RPC tests', () => { ...@@ -57,7 +59,8 @@ describe('Basic RPC tests', () => {
describe('eth_sendRawTransaction', () => { describe('eth_sendRawTransaction', () => {
it('should correctly process a valid transaction', async () => { it('should correctly process a valid transaction', async () => {
const tx = DEFAULT_TRANSACTION const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2()
const nonce = await wallet.getTransactionCount() const nonce = await wallet.getTransactionCount()
const result = await wallet.sendTransaction(tx) const result = await wallet.sendTransaction(tx)
...@@ -70,7 +73,8 @@ describe('Basic RPC tests', () => { ...@@ -70,7 +73,8 @@ describe('Basic RPC tests', () => {
it('should not accept a transaction with the wrong chain ID', async () => { it('should not accept a transaction with the wrong chain ID', async () => {
const tx = { const tx = {
...DEFAULT_TRANSACTION, ...defaultTransactionFactory(),
gasPrice: await gasPriceForL2(),
chainId: (await wallet.getChainId()) + 1, chainId: (await wallet.getChainId()) + 1,
} }
...@@ -81,7 +85,8 @@ describe('Basic RPC tests', () => { ...@@ -81,7 +85,8 @@ describe('Basic RPC tests', () => {
it('should not accept a transaction without a chain ID', async () => { it('should not accept a transaction without a chain ID', async () => {
const tx = { const tx = {
...DEFAULT_TRANSACTION, ...defaultTransactionFactory(),
gasPrice: await gasPriceForL2(),
chainId: null, // Disables EIP155 transaction signing. chainId: null, // Disables EIP155 transaction signing.
} }
...@@ -92,7 +97,8 @@ describe('Basic RPC tests', () => { ...@@ -92,7 +97,8 @@ describe('Basic RPC tests', () => {
it('should accept a transaction with a value', async () => { it('should accept a transaction with a value', async () => {
const tx = { const tx = {
...DEFAULT_TRANSACTION, ...defaultTransactionFactory(),
gasPrice: await gasPriceForL2(),
chainId: await env.l2Wallet.getChainId(), chainId: await env.l2Wallet.getChainId(),
data: '0x', data: '0x',
value: ethers.utils.parseEther('0.1'), value: ethers.utils.parseEther('0.1'),
...@@ -103,15 +109,16 @@ describe('Basic RPC tests', () => { ...@@ -103,15 +109,16 @@ describe('Basic RPC tests', () => {
const receipt = await result.wait() const receipt = await result.wait()
expect(receipt.status).to.deep.equal(1) expect(receipt.status).to.deep.equal(1)
expect(await provider.getBalance(env.l2Wallet.address)).to.deep.equal( const balAfter = await provider.getBalance(env.l2Wallet.address)
balanceBefore.sub(ethers.utils.parseEther('0.1')) expect(balAfter.lte(balanceBefore.sub(ethers.utils.parseEther('0.1')))).to
) .be.true
}) })
it('should reject a transaction with higher value than user balance', async () => { it('should reject a transaction with higher value than user balance', async () => {
const balance = await env.l2Wallet.getBalance() const balance = await env.l2Wallet.getBalance()
const tx = { const tx = {
...DEFAULT_TRANSACTION, ...defaultTransactionFactory(),
gasPrice: await gasPriceForL2(),
chainId: await env.l2Wallet.getChainId(), chainId: await env.l2Wallet.getChainId(),
data: '0x', data: '0x',
value: balance.add(ethers.utils.parseEther('1')), value: balance.add(ethers.utils.parseEther('1')),
...@@ -240,7 +247,7 @@ describe('Basic RPC tests', () => { ...@@ -240,7 +247,7 @@ describe('Basic RPC tests', () => {
it('includes L1 gas price and L1 gas used', async () => { it('includes L1 gas price and L1 gas used', async () => {
const tx = await env.l2Wallet.populateTransaction({ const tx = await env.l2Wallet.populateTransaction({
to: env.l2Wallet.address, to: env.l2Wallet.address,
gasPrice: 1, gasPrice: isLiveNetwork() ? 10000 : 1,
}) })
const raw = serialize({ const raw = serialize({
...@@ -274,7 +281,8 @@ describe('Basic RPC tests', () => { ...@@ -274,7 +281,8 @@ describe('Basic RPC tests', () => {
describe('eth_getTransactionByHash', () => { describe('eth_getTransactionByHash', () => {
it('should be able to get all relevant l1/l2 transaction data', async () => { it('should be able to get all relevant l1/l2 transaction data', async () => {
const tx = DEFAULT_TRANSACTION const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2()
const result = await wallet.sendTransaction(tx) const result = await wallet.sendTransaction(tx)
await result.wait() await result.wait()
...@@ -288,7 +296,8 @@ describe('Basic RPC tests', () => { ...@@ -288,7 +296,8 @@ describe('Basic RPC tests', () => {
describe('eth_getBlockByHash', () => { describe('eth_getBlockByHash', () => {
it('should return the block and all included transactions', async () => { it('should return the block and all included transactions', async () => {
// Send a transaction and wait for it to be mined. // Send a transaction and wait for it to be mined.
const tx = DEFAULT_TRANSACTION const tx = defaultTransactionFactory()
tx.gasPrice = await gasPriceForL2()
const result = await wallet.sendTransaction(tx) const result = await wallet.sendTransaction(tx)
const receipt = await result.wait() const receipt = await result.wait()
...@@ -362,7 +371,7 @@ describe('Basic RPC tests', () => { ...@@ -362,7 +371,7 @@ describe('Basic RPC tests', () => {
let lastEstimate: BigNumber let lastEstimate: BigNumber
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
to: DEFAULT_TRANSACTION.to, to: defaultTransactionFactory().to,
value: 0, value: 0,
}) })
...@@ -376,7 +385,7 @@ describe('Basic RPC tests', () => { ...@@ -376,7 +385,7 @@ describe('Basic RPC tests', () => {
it('should return a gas estimate for txs with empty data', async () => { it('should return a gas estimate for txs with empty data', async () => {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
to: DEFAULT_TRANSACTION.to, to: defaultTransactionFactory().to,
value: 0, value: 0,
}) })
// Expect gas to be less than or equal to the target plus 1% // Expect gas to be less than or equal to the target plus 1%
......
...@@ -185,6 +185,16 @@ export class OptimismEnv { ...@@ -185,6 +185,16 @@ export class OptimismEnv {
await sleep(5000) await sleep(5000)
} else if (err.message.includes('Nonce too low')) { } else if (err.message.includes('Nonce too low')) {
await sleep(5000) await sleep(5000)
} else if (err.message.includes('transaction was replaced')) {
// this happens when we run tests in parallel
await sleep(5000)
} else if (
err.message.includes(
'another transaction with same nonce in the queue'
)
) {
// this happens when we run tests in parallel
await sleep(5000)
} else if ( } else if (
err.message.includes('message has already been received') err.message.includes('message has already been received')
) { ) {
......
...@@ -4,6 +4,7 @@ import { ethers } from 'ethers' ...@@ -4,6 +4,7 @@ import { ethers } from 'ethers'
/* Imports: Internal */ /* Imports: Internal */
import { OptimismEnv } from './env' import { OptimismEnv } from './env'
import { Direction } from './watcher-utils' import { Direction } from './watcher-utils'
import { gasPriceForL1, gasPriceForL2, sleep } from './utils'
interface TransactionParams { interface TransactionParams {
contract: ethers.Contract contract: ethers.Contract
...@@ -14,13 +15,29 @@ interface TransactionParams { ...@@ -14,13 +15,29 @@ interface TransactionParams {
// Arbitrary big amount of gas for the L1<>L2 messages. // Arbitrary big amount of gas for the L1<>L2 messages.
const MESSAGE_GAS = 8_000_000 const MESSAGE_GAS = 8_000_000
export const executeL1ToL2Transactions = async ( export const fundRandomWallet = async (
env: OptimismEnv, env: OptimismEnv,
txs: TransactionParams[] wallet: ethers.Wallet,
value: ethers.BigNumber
): Promise<ethers.Wallet> => {
const fundTx = await env.l1Wallet.sendTransaction({
gasLimit: 25_000,
to: wallet.address,
gasPrice: await gasPriceForL1(env),
value,
})
await fundTx.wait()
return wallet
}
export const executeL1ToL2Transaction = async (
env: OptimismEnv,
wallet: ethers.Wallet,
tx: TransactionParams
) => { ) => {
for (const tx of txs) { const signer = wallet.connect(env.l1Wallet.provider)
const signer = ethers.Wallet.createRandom().connect(env.l1Wallet.provider) const receipt = await retryOnNonceError(async () =>
const receipt = await env.l1Messenger env.l1Messenger
.connect(signer) .connect(signer)
.sendMessage( .sendMessage(
tx.contract.address, tx.contract.address,
...@@ -30,21 +47,21 @@ export const executeL1ToL2Transactions = async ( ...@@ -30,21 +47,21 @@ export const executeL1ToL2Transactions = async (
), ),
MESSAGE_GAS, MESSAGE_GAS,
{ {
gasPrice: 0, gasPrice: await gasPriceForL1(env),
} }
) )
)
await env.waitForXDomainTransaction(receipt, Direction.L1ToL2) await env.waitForXDomainTransaction(receipt, Direction.L1ToL2)
}
} }
export const executeL2ToL1Transactions = async ( export const executeL2ToL1Transaction = async (
env: OptimismEnv, env: OptimismEnv,
txs: TransactionParams[] wallet: ethers.Wallet,
tx: TransactionParams
) => { ) => {
for (const tx of txs) { const signer = wallet.connect(env.l2Wallet.provider)
const signer = ethers.Wallet.createRandom().connect(env.l2Wallet.provider) const receipt = await retryOnNonceError(() =>
const receipt = await env.l2Messenger env.l2Messenger
.connect(signer) .connect(signer)
.sendMessage( .sendMessage(
tx.contract.address, tx.contract.address,
...@@ -54,162 +71,105 @@ export const executeL2ToL1Transactions = async ( ...@@ -54,162 +71,105 @@ export const executeL2ToL1Transactions = async (
), ),
MESSAGE_GAS, MESSAGE_GAS,
{ {
gasPrice: 0, gasPrice: gasPriceForL2(),
} }
) )
)
await env.relayXDomainMessages(receipt) await env.relayXDomainMessages(receipt)
await env.waitForXDomainTransaction(receipt, Direction.L2ToL1) await env.waitForXDomainTransaction(receipt, Direction.L2ToL1)
}
} }
export const executeL2Transactions = async ( export const executeL2Transaction = async (
env: OptimismEnv, env: OptimismEnv,
txs: TransactionParams[] wallet: ethers.Wallet,
tx: TransactionParams
) => { ) => {
for (const tx of txs) { const signer = wallet.connect(env.l2Wallet.provider)
const signer = ethers.Wallet.createRandom().connect(env.l2Wallet.provider) const result = await retryOnNonceError(() =>
const result = await tx.contract tx.contract
.connect(signer) .connect(signer)
.functions[tx.functionName](...tx.functionParams, { .functions[tx.functionName](...tx.functionParams, {
gasPrice: 0, gasPrice: gasPriceForL2(),
}) })
await result.wait() )
} await result.wait()
} }
export const executeRepeatedL1ToL2Transactions = async ( export const executeRepeatedL1ToL2Transactions = async (
env: OptimismEnv, env: OptimismEnv,
tx: TransactionParams, wallets: ethers.Wallet[],
count: number tx: TransactionParams
) => { ) => {
await executeL1ToL2Transactions( for (const wallet of wallets) {
env, await executeL1ToL2Transaction(env, wallet, tx)
[...Array(count).keys()].map(() => tx) }
)
} }
export const executeRepeatedL2ToL1Transactions = async ( export const executeRepeatedL2ToL1Transactions = async (
env: OptimismEnv, env: OptimismEnv,
tx: TransactionParams, wallets: ethers.Wallet[],
count: number tx: TransactionParams
) => { ) => {
await executeL2ToL1Transactions( for (const wallet of wallets) {
env, await executeL2ToL1Transaction(env, wallet, tx)
[...Array(count).keys()].map(() => tx) }
)
} }
export const executeRepeatedL2Transactions = async ( export const executeRepeatedL2Transactions = async (
env: OptimismEnv, env: OptimismEnv,
tx: TransactionParams, wallets: ethers.Wallet[],
count: number tx: TransactionParams
) => { ) => {
await executeL2Transactions( for (const wallet of wallets) {
env, await executeL2Transaction(env, wallet, tx)
[...Array(count).keys()].map(() => tx) }
)
} }
export const executeL1ToL2TransactionsParallel = async ( export const executeL1ToL2TransactionsParallel = async (
env: OptimismEnv, env: OptimismEnv,
txs: TransactionParams[] wallets: ethers.Wallet[],
tx: TransactionParams
) => { ) => {
await Promise.all( await Promise.all(wallets.map((w) => executeL1ToL2Transaction(env, w, tx)))
txs.map(async (tx) => {
const signer = ethers.Wallet.createRandom().connect(env.l1Wallet.provider)
const receipt = await env.l1Messenger
.connect(signer)
.sendMessage(
tx.contract.address,
tx.contract.interface.encodeFunctionData(
tx.functionName,
tx.functionParams
),
MESSAGE_GAS,
{
gasPrice: 0,
}
)
await env.waitForXDomainTransaction(receipt, Direction.L1ToL2)
})
)
} }
export const executeL2ToL1TransactionsParallel = async ( export const executeL2ToL1TransactionsParallel = async (
env: OptimismEnv, env: OptimismEnv,
txs: TransactionParams[] wallets: ethers.Wallet[],
tx: TransactionParams
) => { ) => {
await Promise.all( await Promise.all(wallets.map((w) => executeL2ToL1Transaction(env, w, tx)))
txs.map(async (tx) => {
const signer = ethers.Wallet.createRandom().connect(env.l2Wallet.provider)
const receipt = await env.l2Messenger
.connect(signer)
.sendMessage(
tx.contract.address,
tx.contract.interface.encodeFunctionData(
tx.functionName,
tx.functionParams
),
MESSAGE_GAS,
{
gasPrice: 0,
}
)
await env.relayXDomainMessages(receipt)
await env.waitForXDomainTransaction(receipt, Direction.L2ToL1)
})
)
} }
export const executeL2TransactionsParallel = async ( export const executeL2TransactionsParallel = async (
env: OptimismEnv, env: OptimismEnv,
txs: TransactionParams[] wallets: ethers.Wallet[],
) => { tx: TransactionParams
await Promise.all(
txs.map(async (tx) => {
const signer = ethers.Wallet.createRandom().connect(env.l2Wallet.provider)
const result = await tx.contract
.connect(signer)
.functions[tx.functionName](...tx.functionParams, {
gasPrice: 0,
})
await result.wait()
})
)
}
export const executeRepeatedL1ToL2TransactionsParallel = async (
env: OptimismEnv,
tx: TransactionParams,
count: number
) => {
await executeL1ToL2TransactionsParallel(
env,
[...Array(count).keys()].map(() => tx)
)
}
export const executeRepeatedL2ToL1TransactionsParallel = async (
env: OptimismEnv,
tx: TransactionParams,
count: number
) => { ) => {
await executeL2ToL1TransactionsParallel( await Promise.all(wallets.map((w) => executeL2Transaction(env, w, tx)))
env,
[...Array(count).keys()].map(() => tx)
)
} }
export const executeRepeatedL2TransactionsParallel = async ( const retryOnNonceError = async (cb: () => Promise<any>): Promise<any> => {
env: OptimismEnv, while (true) {
tx: TransactionParams, try {
count: number return await cb()
) => { } catch (err) {
await executeL2TransactionsParallel( const msg = err.message.toLowerCase()
env,
[...Array(count).keys()].map(() => tx) if (
) msg.includes('nonce too low') ||
msg.includes('nonce has already been used') ||
msg.includes('transaction was replaced') ||
msg.includes('another transaction with same nonce in the queue') ||
msg.includes('reverted without a reason')
) {
console.warn('Retrying transaction after nonce error.')
await sleep(5000)
continue
}
throw err
}
}
} }
...@@ -19,10 +19,15 @@ import dotenv from 'dotenv' ...@@ -19,10 +19,15 @@ import dotenv from 'dotenv'
/* Imports: Internal */ /* Imports: Internal */
import { Direction, waitForXDomainTransaction } from './watcher-utils' import { Direction, waitForXDomainTransaction } from './watcher-utils'
import { OptimismEnv } from './env'
export const GWEI = BigNumber.from(1e9) export const GWEI = BigNumber.from(1e9)
if (process.env.IS_LIVE_NETWORK === 'true') { export const isLiveNetwork = () => {
return process.env.IS_LIVE_NETWORK === 'true'
}
if (isLiveNetwork()) {
dotenv.config() dotenv.config()
} }
...@@ -144,12 +149,14 @@ export const encodeSolidityRevertMessage = (_reason: string): string => { ...@@ -144,12 +149,14 @@ export const encodeSolidityRevertMessage = (_reason: string): string => {
return '0x08c379a0' + remove0x(abiCoder.encode(['string'], [_reason])) return '0x08c379a0' + remove0x(abiCoder.encode(['string'], [_reason]))
} }
export const DEFAULT_TRANSACTION = { export const defaultTransactionFactory = () => {
to: '0x' + '1234'.repeat(10), return {
gasLimit: 8_000_000, to: '0x' + '1234'.repeat(10),
gasPrice: 0, gasLimit: 8_000_000,
data: '0x', gasPrice: BigNumber.from(0),
value: 0, data: '0x',
value: 0,
}
} }
export const waitForL2Geth = async ( export const waitForL2Geth = async (
...@@ -166,3 +173,44 @@ export const waitForL2Geth = async ( ...@@ -166,3 +173,44 @@ export const waitForL2Geth = async (
} }
return injectL2Context(provider) return injectL2Context(provider)
} }
export const awaitCondition = async (
cond: () => Promise<boolean>,
rate = 1000,
attempts = 10
) => {
for (let i = 0; i < attempts; i++) {
const ok = await cond()
if (ok) {
return
}
await sleep(rate)
}
throw new Error('Timed out.')
}
export const gasPriceForL2 = async () => {
if (isLiveNetwork()) {
return Promise.resolve(BigNumber.from(10000))
}
return Promise.resolve(BigNumber.from(0))
}
// 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)
}
}
...@@ -60,6 +60,11 @@ export const waitForXDomainTransaction = async ( ...@@ -60,6 +60,11 @@ export const waitForXDomainTransaction = async (
// get the message hash which was created on the SentMessage // get the message hash which was created on the SentMessage
const [xDomainMsgHash] = await watcher.getMessageHashesFromTx(src, tx.hash) const [xDomainMsgHash] = await watcher.getMessageHashesFromTx(src, tx.hash)
if (!xDomainMsgHash) {
throw new Error(`No x-domain message hash for tx hash ${tx.hash}, bailing.`)
}
// Get the transaction and receipt on the remote layer // Get the transaction and receipt on the remote layer
const remoteReceipt = await watcher.getTransactionReceipt( const remoteReceipt = await watcher.getTransactionReceipt(
dest, dest,
......
import { expect } from 'chai' import { expect } from 'chai'
/* Imports: External */ /* Imports: External */
import { Contract, ContractFactory } from 'ethers' import { Contract, ContractFactory, Wallet, utils } from 'ethers'
/* Imports: Internal */ /* Imports: Internal */
import { OptimismEnv } from './shared/env' import { OptimismEnv } from './shared/env'
import { import {
executeL1ToL2TransactionsParallel,
executeL2ToL1TransactionsParallel,
executeL2TransactionsParallel,
executeRepeatedL1ToL2Transactions, executeRepeatedL1ToL2Transactions,
executeRepeatedL2ToL1Transactions, executeRepeatedL2ToL1Transactions,
executeRepeatedL2Transactions, executeRepeatedL2Transactions,
executeRepeatedL1ToL2TransactionsParallel, fundRandomWallet,
executeRepeatedL2ToL1TransactionsParallel,
executeRepeatedL2TransactionsParallel,
} from './shared/stress-test-helpers' } from './shared/stress-test-helpers'
/* Imports: Artifacts */ /* Imports: Artifacts */
import simpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json' import simpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json'
import { fundUser, isLiveNetwork } from './shared/utils'
// Need a big timeout to allow for all transactions to be processed. // 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 // 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. // so I'm instead setting it for every test.
const STRESS_TEST_TIMEOUT = 500_000 const STRESS_TEST_TIMEOUT = isLiveNetwork() ? 500_000 : 1_200_000
describe('stress tests', () => { describe('stress tests', () => {
const numTransactions = 3
let env: OptimismEnv let env: OptimismEnv
const wallets: Wallet[] = []
before(async () => { before(async () => {
env = await OptimismEnv.new() env = await OptimismEnv.new()
for (let i = 0; i < numTransactions; i++) {
wallets.push(Wallet.createRandom())
}
for (const wallet of wallets) {
await fundRandomWallet(env, wallet, utils.parseEther('0.1'))
}
for (const wallet of wallets) {
await fundUser(
env.watcher,
env.l1Bridge,
utils.parseEther('0.1'),
wallet.address
)
}
}) })
let L2SimpleStorage: Contract let L2SimpleStorage: Contract
...@@ -48,106 +72,76 @@ describe('stress tests', () => { ...@@ -48,106 +72,76 @@ describe('stress tests', () => {
}) })
describe('L1 => L2 stress tests', () => { describe('L1 => L2 stress tests', () => {
const numTransactions = 10
it(`${numTransactions} L1 => L2 transactions (serial)`, async () => { it(`${numTransactions} L1 => L2 transactions (serial)`, async () => {
await executeRepeatedL1ToL2Transactions( await executeRepeatedL1ToL2Transactions(env, wallets, {
env, contract: L2SimpleStorage,
{ functionName: 'setValue',
contract: L2SimpleStorage, functionParams: [`0x${'42'.repeat(32)}`],
functionName: 'setValue', })
functionParams: [`0x${'42'.repeat(32)}`],
},
numTransactions
)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions wallets.length
) )
}).timeout(STRESS_TEST_TIMEOUT) }).timeout(STRESS_TEST_TIMEOUT)
it(`${numTransactions} L1 => L2 transactions (parallel)`, async () => { it(`${numTransactions} L1 => L2 transactions (parallel)`, async () => {
await executeRepeatedL1ToL2TransactionsParallel( await executeL1ToL2TransactionsParallel(env, wallets, {
env, contract: L2SimpleStorage,
{ functionName: 'setValue',
contract: L2SimpleStorage, functionParams: [`0x${'42'.repeat(32)}`],
functionName: 'setValue', })
functionParams: [`0x${'42'.repeat(32)}`],
},
numTransactions
)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions wallets.length
) )
}).timeout(STRESS_TEST_TIMEOUT) }).timeout(STRESS_TEST_TIMEOUT)
}) })
describe('L2 => L1 stress tests', () => { describe('L2 => L1 stress tests', () => {
const numTransactions = 10
it(`${numTransactions} L2 => L1 transactions (serial)`, async () => { it(`${numTransactions} L2 => L1 transactions (serial)`, async () => {
await executeRepeatedL2ToL1Transactions( await executeRepeatedL2ToL1Transactions(env, wallets, {
env, contract: L1SimpleStorage,
{ functionName: 'setValue',
contract: L1SimpleStorage, functionParams: [`0x${'42'.repeat(32)}`],
functionName: 'setValue', })
functionParams: [`0x${'42'.repeat(32)}`],
},
numTransactions
)
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions wallets.length
) )
}).timeout(STRESS_TEST_TIMEOUT) }).timeout(STRESS_TEST_TIMEOUT)
it(`${numTransactions} L2 => L1 transactions (parallel)`, async () => { it(`${numTransactions} L2 => L1 transactions (parallel)`, async () => {
await executeRepeatedL2ToL1TransactionsParallel( await executeL2ToL1TransactionsParallel(env, wallets, {
env, contract: L1SimpleStorage,
{ functionName: 'setValue',
contract: L1SimpleStorage, functionParams: [`0x${'42'.repeat(32)}`],
functionName: 'setValue', })
functionParams: [`0x${'42'.repeat(32)}`],
},
numTransactions
)
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions wallets.length
) )
}).timeout(STRESS_TEST_TIMEOUT) }).timeout(STRESS_TEST_TIMEOUT)
}) })
describe('L2 transaction stress tests', () => { describe('L2 transaction stress tests', () => {
const numTransactions = 10
it(`${numTransactions} L2 transactions (serial)`, async () => { it(`${numTransactions} L2 transactions (serial)`, async () => {
await executeRepeatedL2Transactions( await executeRepeatedL2Transactions(env, wallets, {
env, contract: L2SimpleStorage,
{ functionName: 'setValueNotXDomain',
contract: L2SimpleStorage, functionParams: [`0x${'42'.repeat(32)}`],
functionName: 'setValueNotXDomain', })
functionParams: [`0x${'42'.repeat(32)}`],
},
numTransactions
)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions wallets.length
) )
}).timeout(STRESS_TEST_TIMEOUT) }).timeout(STRESS_TEST_TIMEOUT)
it(`${numTransactions} L2 transactions (parallel)`, async () => { it(`${numTransactions} L2 transactions (parallel)`, async () => {
await executeRepeatedL2TransactionsParallel( await executeL2TransactionsParallel(env, wallets, {
env, contract: L2SimpleStorage,
{ functionName: 'setValueNotXDomain',
contract: L2SimpleStorage, functionParams: [`0x${'42'.repeat(32)}`],
functionName: 'setValueNotXDomain', })
functionParams: [`0x${'42'.repeat(32)}`],
},
numTransactions
)
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions numTransactions
...@@ -156,85 +150,59 @@ describe('stress tests', () => { ...@@ -156,85 +150,59 @@ describe('stress tests', () => {
}) })
describe('C-C-C-Combo breakers', () => { describe('C-C-C-Combo breakers', () => {
const numTransactions = 10
it(`${numTransactions} L2 transactions, L1 => L2 transactions, L2 => L1 transactions (txs serial, suites parallel)`, async () => { it(`${numTransactions} L2 transactions, L1 => L2 transactions, L2 => L1 transactions (txs serial, suites parallel)`, async () => {
await Promise.all([ await Promise.all([
executeRepeatedL1ToL2Transactions( executeRepeatedL1ToL2Transactions(env, wallets, {
env, contract: L2SimpleStorage,
{ functionName: 'setValue',
contract: L2SimpleStorage, functionParams: [`0x${'42'.repeat(32)}`],
functionName: 'setValue', }),
functionParams: [`0x${'42'.repeat(32)}`], executeRepeatedL2ToL1Transactions(env, wallets, {
}, contract: L1SimpleStorage,
numTransactions functionName: 'setValue',
), functionParams: [`0x${'42'.repeat(32)}`],
executeRepeatedL2ToL1Transactions( }),
env, executeRepeatedL2Transactions(env, wallets, {
{ contract: L2SimpleStorage,
contract: L1SimpleStorage, functionName: 'setValueNotXDomain',
functionName: 'setValue', functionParams: [`0x${'42'.repeat(32)}`],
functionParams: [`0x${'42'.repeat(32)}`], }),
},
numTransactions
),
executeRepeatedL2Transactions(
env,
{
contract: L2SimpleStorage,
functionName: 'setValueNotXDomain',
functionParams: [`0x${'42'.repeat(32)}`],
},
numTransactions
),
]) ])
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions * 2 wallets.length * 2
) )
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions wallets.length
) )
}).timeout(STRESS_TEST_TIMEOUT) }).timeout(STRESS_TEST_TIMEOUT)
it(`${numTransactions} L2 transactions, L1 => L2 transactions, L2 => L1 transactions (all parallel)`, async () => { it(`${numTransactions} L2 transactions, L1 => L2 transactions, L2 => L1 transactions (all parallel)`, async () => {
await Promise.all([ await Promise.all([
executeRepeatedL1ToL2TransactionsParallel( executeL1ToL2TransactionsParallel(env, wallets, {
env, contract: L2SimpleStorage,
{ functionName: 'setValue',
contract: L2SimpleStorage, functionParams: [`0x${'42'.repeat(32)}`],
functionName: 'setValue', }),
functionParams: [`0x${'42'.repeat(32)}`], executeL2ToL1TransactionsParallel(env, wallets, {
}, contract: L1SimpleStorage,
numTransactions functionName: 'setValue',
), functionParams: [`0x${'42'.repeat(32)}`],
executeRepeatedL2ToL1TransactionsParallel( }),
env, executeL2TransactionsParallel(env, wallets, {
{ contract: L2SimpleStorage,
contract: L1SimpleStorage, functionName: 'setValueNotXDomain',
functionName: 'setValue', functionParams: [`0x${'42'.repeat(32)}`],
functionParams: [`0x${'42'.repeat(32)}`], }),
},
numTransactions
),
executeRepeatedL2TransactionsParallel(
env,
{
contract: L2SimpleStorage,
functionName: 'setValueNotXDomain',
functionParams: [`0x${'42'.repeat(32)}`],
},
numTransactions
),
]) ])
expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L2SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions * 2 wallets.length * 2
) )
expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal( expect((await L1SimpleStorage.totalCount()).toNumber()).to.equal(
numTransactions wallets.length
) )
}).timeout(STRESS_TEST_TIMEOUT) }).timeout(STRESS_TEST_TIMEOUT)
}) })
......
...@@ -5,9 +5,9 @@ import { ...@@ -5,9 +5,9 @@ import {
getContractFromArtifact, getContractFromArtifact,
fundAccount, fundAccount,
sendImpersonatedTx, sendImpersonatedTx,
waitUntilTrue,
BIG_BALANCE, BIG_BALANCE,
} from '../src/hardhat-deploy-ethers' } from '../src/hardhat-deploy-ethers'
import { awaitCondition } from '@eth-optimism/core-utils'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
if ((hre as any).deployConfig.forked !== 'true') { if ((hre as any).deployConfig.forked !== 'true') {
...@@ -39,9 +39,13 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -39,9 +39,13 @@ const deployFn: DeployFunction = async (hre) => {
}) })
console.log(`Waiting for owner to be correctly set...`) console.log(`Waiting for owner to be correctly set...`)
await waitUntilTrue(async () => { await awaitCondition(
return (await Lib_AddressManager.owner()) === deployer async () => {
}) return (await Lib_AddressManager.owner()) === deployer
},
5000,
100
)
// Get a reference to the L1StandardBridge contract. // Get a reference to the L1StandardBridge contract.
const Proxy__OVM_L1StandardBridge = await getContractFromArtifact( const Proxy__OVM_L1StandardBridge = await getContractFromArtifact(
...@@ -63,13 +67,17 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -63,13 +67,17 @@ const deployFn: DeployFunction = async (hre) => {
}) })
console.log(`Waiting for owner to be correctly set...`) console.log(`Waiting for owner to be correctly set...`)
await waitUntilTrue(async () => { await awaitCondition(
return ( async () => {
(await Proxy__OVM_L1StandardBridge.callStatic.getOwner({ return (
from: hre.ethers.constants.AddressZero, (await Proxy__OVM_L1StandardBridge.callStatic.getOwner({
})) === deployer from: hre.ethers.constants.AddressZero,
) })) === deployer
}) )
},
5000,
100
)
} }
deployFn.tags = ['hardhat', 'upgrade'] deployFn.tags = ['hardhat', 'upgrade']
......
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { hexStringEquals } from '@eth-optimism/core-utils' import { hexStringEquals, awaitCondition } from '@eth-optimism/core-utils'
/* Imports: Internal */ /* Imports: Internal */
import { import {
deployAndPostDeploy, deployAndPostDeploy,
getContractFromArtifact, getContractFromArtifact,
waitUntilTrue,
} from '../src/hardhat-deploy-ethers' } from '../src/hardhat-deploy-ethers'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
...@@ -29,12 +28,16 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -29,12 +28,16 @@ const deployFn: DeployFunction = async (hre) => {
await contract.initialize(Lib_AddressManager.address) await contract.initialize(Lib_AddressManager.address)
console.log(`Checking that contract was correctly initialized...`) console.log(`Checking that contract was correctly initialized...`)
await waitUntilTrue(async () => { await awaitCondition(
return hexStringEquals( async () => {
await contract.libAddressManager(), return hexStringEquals(
Lib_AddressManager.address await contract.libAddressManager(),
) Lib_AddressManager.address
}) )
},
5000,
100
)
}, },
}) })
} }
......
...@@ -6,7 +6,6 @@ import { hexStringEquals } from '@eth-optimism/core-utils' ...@@ -6,7 +6,6 @@ import { hexStringEquals } from '@eth-optimism/core-utils'
import { import {
deployAndPostDeploy, deployAndPostDeploy,
getContractFromArtifact, getContractFromArtifact,
waitUntilTrue,
} from '../src/hardhat-deploy-ethers' } from '../src/hardhat-deploy-ethers'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
......
/* Imports: External */ /* Imports: External */
import { hexStringEquals } from '@eth-optimism/core-utils' import { hexStringEquals, awaitCondition } from '@eth-optimism/core-utils'
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
/* Imports: Internal */ /* Imports: Internal */
import { import { getContractFromArtifact } from '../src/hardhat-deploy-ethers'
getContractFromArtifact,
waitUntilTrue,
} from '../src/hardhat-deploy-ethers'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
...@@ -92,18 +89,16 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -92,18 +89,16 @@ const deployFn: DeployFunction = async (hre) => {
} }
// Wait for ownership to be transferred to the AddressDictator contract. // Wait for ownership to be transferred to the AddressDictator contract.
await waitUntilTrue( await awaitCondition(
async () => { async () => {
return hexStringEquals( return hexStringEquals(
await Lib_AddressManager.owner(), await Lib_AddressManager.owner(),
AddressDictator.address AddressDictator.address
) )
}, },
{ // Try every 30 seconds for 500 minutes.
// Try every 30 seconds for 500 minutes. 30000,
delay: 30_000, 1000
retries: 1000,
}
) )
// Set the addresses! // Set the addresses!
...@@ -112,9 +107,13 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -112,9 +107,13 @@ const deployFn: DeployFunction = async (hre) => {
// Make sure ownership has been correctly sent back to the original owner. // Make sure ownership has been correctly sent back to the original owner.
console.log('Verifying final ownership of Lib_AddressManager...') console.log('Verifying final ownership of Lib_AddressManager...')
await waitUntilTrue(async () => { await awaitCondition(
return hexStringEquals(await Lib_AddressManager.owner(), finalOwner) async () => {
}) return hexStringEquals(await Lib_AddressManager.owner(), finalOwner)
},
500,
1000
)
} }
deployFn.tags = ['set-addresses', 'upgrade'] deployFn.tags = ['set-addresses', 'upgrade']
......
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { hexStringEquals } from '@eth-optimism/core-utils' import { hexStringEquals, awaitCondition } from '@eth-optimism/core-utils'
/* Imports: Internal */ /* Imports: Internal */
import { import { getContractFromArtifact } from '../src/hardhat-deploy-ethers'
getContractFromArtifact,
waitUntilTrue,
} from '../src/hardhat-deploy-ethers'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
...@@ -34,12 +31,16 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -34,12 +31,16 @@ const deployFn: DeployFunction = async (hre) => {
await Proxy__OVM_L1CrossDomainMessenger.initialize(Lib_AddressManager.address) await Proxy__OVM_L1CrossDomainMessenger.initialize(Lib_AddressManager.address)
console.log(`Checking that contract was correctly initialized...`) console.log(`Checking that contract was correctly initialized...`)
await waitUntilTrue(async () => { await awaitCondition(
return hexStringEquals( async () => {
await Proxy__OVM_L1CrossDomainMessenger.libAddressManager(), return hexStringEquals(
Lib_AddressManager.address await Proxy__OVM_L1CrossDomainMessenger.libAddressManager(),
) Lib_AddressManager.address
}) )
},
5000,
100
)
} }
deployFn.tags = ['finalize'] deployFn.tags = ['finalize']
......
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { hexStringEquals } from '@eth-optimism/core-utils' import { hexStringEquals, awaitCondition } from '@eth-optimism/core-utils'
/* Imports: Internal */ /* Imports: Internal */
import { getContractDefinition } from '../src/contract-defs' import { getContractDefinition } from '../src/contract-defs'
import { import {
getContractFromArtifact, getContractFromArtifact,
waitUntilTrue,
deployAndPostDeploy, deployAndPostDeploy,
} from '../src/hardhat-deploy-ethers' } from '../src/hardhat-deploy-ethers'
...@@ -100,7 +99,7 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -100,7 +99,7 @@ const deployFn: DeployFunction = async (hre) => {
} }
// Wait for ownership to be transferred to the AddressDictator contract. // Wait for ownership to be transferred to the AddressDictator contract.
await waitUntilTrue( await awaitCondition(
async () => { async () => {
return hexStringEquals( return hexStringEquals(
await Proxy__OVM_L1StandardBridge.connect( await Proxy__OVM_L1StandardBridge.connect(
...@@ -111,11 +110,8 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -111,11 +110,8 @@ const deployFn: DeployFunction = async (hre) => {
ChugSplashDictator.address ChugSplashDictator.address
) )
}, },
{ 30000,
// Try every 30 seconds for 500 minutes. 1000
delay: 30_000,
retries: 1000,
}
) )
// Set the addresses! // Set the addresses!
...@@ -123,16 +119,20 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -123,16 +119,20 @@ const deployFn: DeployFunction = async (hre) => {
await ChugSplashDictator.doActions(bridgeCode) await ChugSplashDictator.doActions(bridgeCode)
console.log(`Confirming that owner address was correctly set...`) console.log(`Confirming that owner address was correctly set...`)
await waitUntilTrue(async () => { await awaitCondition(
return hexStringEquals( async () => {
await Proxy__OVM_L1StandardBridge.connect( return hexStringEquals(
Proxy__OVM_L1StandardBridge.signer.provider await Proxy__OVM_L1StandardBridge.connect(
).callStatic.getOwner({ Proxy__OVM_L1StandardBridge.signer.provider
from: ethers.constants.AddressZero, ).callStatic.getOwner({
}), from: ethers.constants.AddressZero,
finalOwner }),
) finalOwner
}) )
},
5000,
100
)
// Deploy a copy of the implementation so it can be successfully verified on Etherscan. // Deploy a copy of the implementation so it can be successfully verified on Etherscan.
console.log(`Deploying a copy of the bridge for Etherscan verification...`) console.log(`Deploying a copy of the bridge for Etherscan verification...`)
......
/* Imports: External */ /* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types' import { DeployFunction } from 'hardhat-deploy/dist/types'
import { hexStringEquals } from '@eth-optimism/core-utils' import { hexStringEquals, awaitCondition } from '@eth-optimism/core-utils'
/* Imports: Internal */ /* Imports: Internal */
import { import { getContractFromArtifact } from '../src/hardhat-deploy-ethers'
getContractFromArtifact,
waitUntilTrue,
} from '../src/hardhat-deploy-ethers'
const deployFn: DeployFunction = async (hre) => { const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts() const { deployer } = await hre.getNamedAccounts()
...@@ -31,9 +28,13 @@ const deployFn: DeployFunction = async (hre) => { ...@@ -31,9 +28,13 @@ const deployFn: DeployFunction = async (hre) => {
await Lib_AddressManager.transferOwnership(owner) await Lib_AddressManager.transferOwnership(owner)
console.log(`Confirming transfer was successful...`) console.log(`Confirming transfer was successful...`)
await waitUntilTrue(async () => { await awaitCondition(
return hexStringEquals(await Lib_AddressManager.owner(), owner) async () => {
}) return hexStringEquals(await Lib_AddressManager.owner(), owner)
},
5000,
100
)
console.log(`✓ Set owner of Lib_AddressManager to: ${owner}`) console.log(`✓ Set owner of Lib_AddressManager to: ${owner}`)
} }
......
...@@ -2,29 +2,13 @@ ...@@ -2,29 +2,13 @@
import { ethers, Contract } from 'ethers' import { ethers, Contract } from 'ethers'
import { Provider } from '@ethersproject/abstract-provider' import { Provider } from '@ethersproject/abstract-provider'
import { Signer } from '@ethersproject/abstract-signer' import { Signer } from '@ethersproject/abstract-signer'
import { sleep, hexStringEquals } from '@eth-optimism/core-utils' import {
sleep,
hexStringEquals,
awaitCondition,
} from '@eth-optimism/core-utils'
import { HttpNetworkConfig } from 'hardhat/types' import { HttpNetworkConfig } from 'hardhat/types'
export const waitUntilTrue = async (
check: () => Promise<boolean>,
opts: {
retries?: number
delay?: number
} = {}
) => {
opts.retries = opts.retries || 100
opts.delay = opts.delay || 5000
let retries = 0
while (!(await check())) {
if (retries > opts.retries) {
throw new Error(`check failed after ${opts.retries} attempts`)
}
retries++
await sleep(opts.delay)
}
}
export const deployAndPostDeploy = async ({ export const deployAndPostDeploy = async ({
hre, hre,
name, name,
...@@ -152,10 +136,14 @@ export const fundAccount = async ( ...@@ -152,10 +136,14 @@ export const fundAccount = async (
]) ])
console.log(`Waiting for balance to reflect...`) console.log(`Waiting for balance to reflect...`)
await waitUntilTrue(async () => { await awaitCondition(
const balance = await hre.ethers.provider.getBalance(address) async () => {
return balance.gte(amount) const balance = await hre.ethers.provider.getBalance(address)
}) return balance.gte(amount)
},
5000,
100
)
console.log(`Account successfully funded.`) console.log(`Account successfully funded.`)
} }
......
import { expect } from 'chai' import { expect } from 'chai'
import { BigNumber } from 'ethers' import { BigNumber } from 'ethers'
import { sleep } from './misc'
interface deviationRanges { interface deviationRanges {
percentUpperDeviation?: number percentUpperDeviation?: number
...@@ -8,6 +9,23 @@ interface deviationRanges { ...@@ -8,6 +9,23 @@ interface deviationRanges {
absoluteLowerDeviation?: number absoluteLowerDeviation?: number
} }
export const awaitCondition = async (
cond: () => Promise<boolean>,
rate = 1000,
attempts = 10
) => {
for (let i = 0; i < attempts; i++) {
const ok = await cond()
if (ok) {
return
}
await sleep(rate)
}
throw new Error('Timed out.')
}
/** /**
* Assert that a number lies within a custom defined range of the target. * Assert that a number lies within a custom defined range of the target.
*/ */
......
import { expect } from '../setup' import { expect } from '../setup'
/* Imports: Internal */ /* Imports: Internal */
import { expectApprox } from '../../src' import { expectApprox, awaitCondition } from '../../src'
import { assert } from 'chai' import { assert } from 'chai'
describe('awaitCondition', () => {
it('should try the condition fn until it returns true', async () => {
let i = 0
const condFn = async () => {
i++
return Promise.resolve(i === 2)
}
await awaitCondition(condFn, 50, 3);
expect(i).to.equal(2)
})
it('should only try the configured number of attempts', async () => {
let i = 0
const condFn = async () => {
i++
return Promise.resolve(i === 2)
}
try {
await awaitCondition(condFn, 50, 1);
} catch (e) {
return;
}
assert.fail('Condition never failed, but it should have.');
})
})
describe('expectApprox', () => { describe('expectApprox', () => {
it('should pass when the actual number is higher, but within the expected range of the target', async () => { it('should pass when the actual number is higher, but within the expected range of the target', async () => {
expectApprox(119, 100, { expectApprox(119, 100, {
......
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