Commit 7b761af5 authored by Mark Tyneway's avatar Mark Tyneway Committed by Kelvin Fichter

integration-tests: fee tests

Add test coverage for new fee scheme
and refactor old tests to work with new scheme
parent 6d32d701
---
'@eth-optimism/integration-tests': patch
---
Add updated fee scheme integration tests
...@@ -9,8 +9,7 @@ ...@@ -9,8 +9,7 @@
"lint": "yarn lint:fix && yarn lint:check", "lint": "yarn lint:fix && yarn lint:check",
"lint:fix": "yarn lint:check --fix", "lint:fix": "yarn lint:check --fix",
"lint:check": "eslint .", "lint:check": "eslint .",
"build:integration": "yarn build:contracts", "build": "hardhat compile",
"build:contracts": "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": "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",
...@@ -21,6 +20,7 @@ ...@@ -21,6 +20,7 @@
"@eth-optimism/core-utils": "^0.6.1", "@eth-optimism/core-utils": "^0.6.1",
"@eth-optimism/message-relayer": "^0.1.14", "@eth-optimism/message-relayer": "^0.1.14",
"@ethersproject/providers": "^5.4.5", "@ethersproject/providers": "^5.4.5",
"@ethersproject/transactions": "^5.4.0",
"@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
"@types/chai": "^4.2.18", "@types/chai": "^4.2.18",
...@@ -30,19 +30,19 @@ ...@@ -30,19 +30,19 @@
"@types/shelljs": "^0.8.8", "@types/shelljs": "^0.8.8",
"@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0", "@typescript-eslint/parser": "^4.26.0",
"babel-eslint": "^10.1.0",
"chai": "^4.3.4", "chai": "^4.3.4",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"docker-compose": "^0.23.8", "docker-compose": "^0.23.8",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"envalid": "^7.1.0", "envalid": "^7.1.0",
"babel-eslint": "^10.1.0",
"eslint": "^7.27.0", "eslint": "^7.27.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-ban": "^1.5.2", "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2", "eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3", "eslint-plugin-prefer-arrow": "^1.2.3",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.24.0", "eslint-plugin-react": "^7.24.0",
"eslint-plugin-unicorn": "^32.0.1", "eslint-plugin-unicorn": "^32.0.1",
"ethereum-waffle": "^3.3.0", "ethereum-waffle": "^3.3.0",
......
...@@ -4,37 +4,157 @@ chai.use(chaiAsPromised) ...@@ -4,37 +4,157 @@ chai.use(chaiAsPromised)
/* Imports: External */ /* Imports: External */
import { ethers, BigNumber, Contract, utils } from 'ethers' import { ethers, BigNumber, Contract, utils } from 'ethers'
import { TxGasLimit, TxGasPrice } from '@eth-optimism/core-utils' import { sleep } from '@eth-optimism/core-utils'
import { predeploys, getContractInterface } from '@eth-optimism/contracts' import { serialize } from '@ethersproject/transactions'
import {
predeploys,
getContractInterface,
getContractFactory,
} from '@eth-optimism/contracts'
/* Imports: Internal */ /* Imports: Internal */
import { IS_LIVE_NETWORK } from './shared/utils' import { IS_LIVE_NETWORK } 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 gasPrice = await env.gasPriceOracle.setGasPrice(value)
await gasPrice.wait()
const baseFee = await env.gasPriceOracle.setL1BaseFee(value)
await baseFee.wait()
}
describe('Fee Payment Integration Tests', async () => { describe('Fee Payment Integration Tests', async () => {
let env: OptimismEnv let env: OptimismEnv
const other = '0x1234123412341234123412341234123412341234'
before(async () => { before(async () => {
env = await OptimismEnv.new() env = await OptimismEnv.new()
}) })
let ovmSequencerFeeVault: Contract it(`should return eth_gasPrice equal to OVM_GasPriceOracle.gasPrice`, async () => {
before(async () => { const assertGasPrice = async () => {
ovmSequencerFeeVault = new Contract( const gasPrice = await env.l2Wallet.getGasPrice()
predeploys.OVM_SequencerFeeVault, const oracleGasPrice = await env.gasPriceOracle.gasPrice()
getContractInterface('OVM_SequencerFeeVault'), expect(gasPrice).to.deep.equal(oracleGasPrice)
env.l2Wallet }
assertGasPrice()
// update the gas price
const tx = await env.gasPriceOracle.setGasPrice(1000)
await tx.wait()
assertGasPrice()
})
it('Paying a nonzero but acceptable gasPrice fee', async () => {
await setPrices(env, 1000)
const amount = utils.parseEther('0.0000001')
const balanceBefore = await env.l2Wallet.getBalance()
const feeVaultBalanceBefore = await env.l2Wallet.provider.getBalance(
env.sequencerFeeVault.address
)
expect(balanceBefore.gt(amount))
const unsigned = await env.l2Wallet.populateTransaction({
to: other,
value: amount,
gasLimit: 500000,
})
const raw = serialize({
nonce: parseInt(unsigned.nonce.toString(10), 10),
value: unsigned.value,
gasPrice: unsigned.gasPrice,
gasLimit: unsigned.gasLimit,
to: unsigned.to,
data: unsigned.data,
})
const l1Fee = await env.gasPriceOracle.getL1Fee(raw)
const tx = await env.l2Wallet.sendTransaction(unsigned)
const receipt = await tx.wait()
expect(receipt.status).to.eq(1)
const balanceAfter = await env.l2Wallet.getBalance()
const feeVaultBalanceAfter = await env.l2Wallet.provider.getBalance(
env.sequencerFeeVault.address
)
const l2Fee = receipt.gasUsed.mul(tx.gasPrice)
const expectedFeePaid = l1Fee.add(l2Fee)
expect(balanceBefore.sub(balanceAfter)).to.deep.equal(
expectedFeePaid.add(amount)
)
// Make sure the fee was transferred to the vault.
expect(feeVaultBalanceAfter.sub(feeVaultBalanceBefore)).to.deep.equal(
expectedFeePaid
)
await setPrices(env, 1)
})
it('should compute correct fee', async () => {
await setPrices(env, 1000)
const preBalance = await env.l2Wallet.getBalance()
const OVM_GasPriceOracle = getContractFactory('OVM_GasPriceOracle')
.attach(predeploys.OVM_GasPriceOracle)
.connect(env.l2Wallet)
const WETH = getContractFactory('OVM_ETH')
.attach(predeploys.OVM_ETH)
.connect(env.l2Wallet)
const feeVaultBefore = await WETH.balanceOf(
predeploys.OVM_SequencerFeeVault
) )
const unsigned = await env.l2Wallet.populateTransaction({
to: env.l2Wallet.address,
value: 0,
})
const raw = serialize({
nonce: parseInt(unsigned.nonce.toString(10), 10),
value: unsigned.value,
gasPrice: unsigned.gasPrice,
gasLimit: unsigned.gasLimit,
to: unsigned.to,
data: unsigned.data,
})
const l1Fee = await OVM_GasPriceOracle.getL1Fee(raw)
const tx = await env.l2Wallet.sendTransaction(unsigned)
const receipt = await tx.wait()
const l2Fee = receipt.gasUsed.mul(tx.gasPrice)
const postBalance = await env.l2Wallet.getBalance()
const feeVaultAfter = await WETH.balanceOf(predeploys.OVM_SequencerFeeVault)
const fee = l1Fee.add(l2Fee)
const balanceDiff = preBalance.sub(postBalance)
const feeReceived = feeVaultAfter.sub(feeVaultBefore)
expect(balanceDiff).to.deep.equal(fee)
// There is no inflation
expect(feeReceived).to.deep.equal(balanceDiff)
await setPrices(env, 1)
}) })
it('should not be able to withdraw fees before the minimum is met', async () => { it('should not be able to withdraw fees before the minimum is met', async () => {
await expect(ovmSequencerFeeVault.withdraw()).to.be.rejected 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 () { it('should be able to withdraw fees back to L1 once the minimum is met', async function () {
const l1FeeWallet = await ovmSequencerFeeVault.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 ovmSequencerFeeVault.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)) { if (IS_LIVE_NETWORK && l2WalletBalance.lt(withdrawalAmount)) {
...@@ -48,18 +168,18 @@ describe('Fee Payment Integration Tests', async () => { ...@@ -48,18 +168,18 @@ describe('Fee Payment Integration Tests', async () => {
// Transfer the minimum required to withdraw. // Transfer the minimum required to withdraw.
const tx = await env.l2Wallet.sendTransaction({ const tx = await env.l2Wallet.sendTransaction({
to: ovmSequencerFeeVault.address, to: env.sequencerFeeVault.address,
value: withdrawalAmount, value: withdrawalAmount,
gasLimit: 500000, gasLimit: 500000,
}) })
await tx.wait() await tx.wait()
const vaultBalance = await env.ovmEth.balanceOf( const vaultBalance = await env.ovmEth.balanceOf(
ovmSequencerFeeVault.address env.sequencerFeeVault.address
) )
// Submit the withdrawal. // Submit the withdrawal.
const withdrawTx = await ovmSequencerFeeVault.withdraw({ const withdrawTx = await env.sequencerFeeVault.withdraw({
gasPrice: 0, // Need a gasprice of 0 or the balances will include the fee paid during this tx. gasPrice: 0, // Need a gasprice of 0 or the balances will include the fee paid during this tx.
}) })
......
...@@ -2,6 +2,7 @@ import { expect } from 'chai' ...@@ -2,6 +2,7 @@ import { expect } from 'chai'
/* Imports: External */ /* Imports: External */
import { Wallet, utils, BigNumber } from 'ethers' import { Wallet, utils, BigNumber } from 'ethers'
import { serialize } from '@ethersproject/transactions'
import { predeploys } from '@eth-optimism/contracts' import { predeploys } from '@eth-optimism/contracts'
/* Imports: Internal */ /* Imports: Internal */
...@@ -248,24 +249,44 @@ describe('Native ETH Integration Tests', async () => { ...@@ -248,24 +249,44 @@ describe('Native ETH Integration Tests', async () => {
DEFAULT_TEST_GAS_L2, DEFAULT_TEST_GAS_L2,
'0xFFFF' '0xFFFF'
) )
await transaction.wait() await transaction.wait()
await env.relayXDomainMessages(transaction) await env.relayXDomainMessages(transaction)
const receipts = await env.waitForXDomainTransaction( const receipts = await env.waitForXDomainTransaction(
transaction, transaction,
Direction.L2ToL1 Direction.L2ToL1
) )
const fee = receipts.tx.gasLimit.mul(receipts.tx.gasPrice)
const l2Fee = receipts.tx.gasPrice.mul(receipts.receipt.gasUsed)
// Calculate the L1 portion of the fee
const raw = serialize({
nonce: transaction.nonce,
value: transaction.value,
gasPrice: transaction.gasPrice,
gasLimit: transaction.gasLimit,
to: transaction.to,
data: transaction.data,
})
const l1Fee = await env.gasPriceOracle.getL1Fee(raw)
const fee = l2Fee.add(l1Fee)
const postBalances = await getBalances(env) const postBalances = await getBalances(env)
expect(postBalances.l1BridgeBalance).to.deep.eq( expect(postBalances.l1BridgeBalance).to.deep.eq(
preBalances.l1BridgeBalance.sub(withdrawAmount) preBalances.l1BridgeBalance.sub(withdrawAmount),
'L1 Bridge Balance Mismatch'
) )
expect(postBalances.l2UserBalance).to.deep.eq( expect(postBalances.l2UserBalance).to.deep.eq(
preBalances.l2UserBalance.sub(withdrawAmount.add(fee)) preBalances.l2UserBalance.sub(withdrawAmount.add(fee)),
'L2 User Balance Mismatch'
) )
expect(postBalances.l1BobBalance).to.deep.eq( expect(postBalances.l1BobBalance).to.deep.eq(
preBalances.l1BobBalance.add(withdrawAmount) preBalances.l1BobBalance.add(withdrawAmount),
'L1 User Balance Mismatch'
) )
}) })
...@@ -282,7 +303,7 @@ describe('Native ETH Integration Tests', async () => { ...@@ -282,7 +303,7 @@ describe('Native ETH Integration Tests', async () => {
Direction.L1ToL2 Direction.L1ToL2
) )
// 2. trnsfer to another address // 2. transfer to another address
const other = Wallet.createRandom().connect(env.l2Wallet.provider) const other = Wallet.createRandom().connect(env.l2Wallet.provider)
const tx = await env.l2Wallet.sendTransaction({ const tx = await env.l2Wallet.sendTransaction({
to: other.address, to: other.address,
...@@ -311,8 +332,22 @@ describe('Native ETH Integration Tests', async () => { ...@@ -311,8 +332,22 @@ describe('Native ETH Integration Tests', async () => {
Direction.L2ToL1 Direction.L2ToL1
) )
// Compute the L1 portion of the fee
const l1Fee = await env.gasPriceOracle.getL1Fee(
serialize({
nonce: transaction.nonce,
value: transaction.value,
gasPrice: transaction.gasPrice,
gasLimit: transaction.gasLimit,
to: transaction.to,
data: transaction.data,
})
)
// check that correct amount was withdrawn and that fee was charged // check that correct amount was withdrawn and that fee was charged
const fee = receipts.tx.gasLimit.mul(receipts.tx.gasPrice) const l2Fee = receipts.tx.gasPrice.mul(receipts.receipt.gasUsed)
const fee = l1Fee.add(l2Fee)
const l1BalanceAfter = await other const l1BalanceAfter = await other
.connect(env.l1Wallet.provider) .connect(env.l1Wallet.provider)
.getBalance() .getBalance()
......
import { import { injectL2Context } from '@eth-optimism/core-utils'
injectL2Context,
TxGasLimit,
TxGasPrice,
} from '@eth-optimism/core-utils'
import { Wallet, BigNumber, Contract, ContractFactory } from 'ethers' import { Wallet, BigNumber, Contract, ContractFactory } from 'ethers'
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
import chai, { expect } from 'chai' import chai, { expect } from 'chai'
......
/* Imports: External */ /* Imports: External */
import { Contract, utils, Wallet } from 'ethers' import { Contract, utils, Wallet, providers } from 'ethers'
import { TransactionResponse } from '@ethersproject/providers' import { TransactionResponse } from '@ethersproject/providers'
import { getContractFactory, predeploys } from '@eth-optimism/contracts' import { getContractFactory, predeploys } from '@eth-optimism/contracts'
import { Watcher } from '@eth-optimism/core-utils' import { Watcher } from '@eth-optimism/core-utils'
...@@ -40,6 +40,7 @@ export class OptimismEnv { ...@@ -40,6 +40,7 @@ export class OptimismEnv {
l2Bridge: Contract l2Bridge: Contract
l2Messenger: Contract l2Messenger: Contract
gasPriceOracle: Contract gasPriceOracle: Contract
sequencerFeeVault: Contract
// The L1 <> L2 State watcher // The L1 <> L2 State watcher
watcher: Watcher watcher: Watcher
...@@ -48,6 +49,10 @@ export class OptimismEnv { ...@@ -48,6 +49,10 @@ export class OptimismEnv {
l1Wallet: Wallet l1Wallet: Wallet
l2Wallet: Wallet l2Wallet: Wallet
// The providers
l1Provider: providers.JsonRpcProvider
l2Provider: providers.JsonRpcProvider
constructor(args: any) { constructor(args: any) {
this.addressManager = args.addressManager this.addressManager = args.addressManager
this.l1Bridge = args.l1Bridge this.l1Bridge = args.l1Bridge
...@@ -56,9 +61,12 @@ export class OptimismEnv { ...@@ -56,9 +61,12 @@ export class OptimismEnv {
this.l2Bridge = args.l2Bridge this.l2Bridge = args.l2Bridge
this.l2Messenger = args.l2Messenger this.l2Messenger = args.l2Messenger
this.gasPriceOracle = args.gasPriceOracle this.gasPriceOracle = args.gasPriceOracle
this.sequencerFeeVault = args.sequencerFeeVault
this.watcher = args.watcher this.watcher = args.watcher
this.l1Wallet = args.l1Wallet this.l1Wallet = args.l1Wallet
this.l2Wallet = args.l2Wallet this.l2Wallet = args.l2Wallet
this.l1Provider = args.l1Provider
this.l2Provider = args.l2Provider
this.ctc = args.ctc this.ctc = args.ctc
this.scc = args.scc this.scc = args.scc
} }
...@@ -100,6 +108,10 @@ export class OptimismEnv { ...@@ -100,6 +108,10 @@ export class OptimismEnv {
.connect(l1Wallet) .connect(l1Wallet)
.attach(sccAddress) .attach(sccAddress)
const sequencerFeeVault = getContractFactory('OVM_SequencerFeeVault')
.connect(l2Wallet)
.attach(predeploys.OVM_SequencerFeeVault)
return new OptimismEnv({ return new OptimismEnv({
addressManager, addressManager,
l1Bridge, l1Bridge,
...@@ -108,11 +120,14 @@ export class OptimismEnv { ...@@ -108,11 +120,14 @@ export class OptimismEnv {
l1Messenger, l1Messenger,
ovmEth, ovmEth,
gasPriceOracle, gasPriceOracle,
sequencerFeeVault,
l2Bridge, l2Bridge,
l2Messenger, l2Messenger,
watcher, watcher,
l1Wallet, l1Wallet,
l2Wallet, l2Wallet,
l1Provider,
l2Provider,
}) })
} }
......
...@@ -155,8 +155,7 @@ describe('stress tests', () => { ...@@ -155,8 +155,7 @@ describe('stress tests', () => {
}).timeout(STRESS_TEST_TIMEOUT) }).timeout(STRESS_TEST_TIMEOUT)
}) })
// SKIP: needs message passing PR describe('C-C-C-Combo breakers', () => {
describe.skip('C-C-C-Combo breakers', () => {
const numTransactions = 10 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 () => {
......
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