diff --git a/.changeset/mighty-jars-rest.md b/.changeset/mighty-jars-rest.md new file mode 100644 index 000000000000..3916ae5852dc --- /dev/null +++ b/.changeset/mighty-jars-rest.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/integration-tests': patch +--- + +Add updated fee scheme integration tests diff --git a/integration-tests/package.json b/integration-tests/package.json index 128311f3fb43..93f433928dd9 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -9,8 +9,7 @@ "lint": "yarn lint:fix && yarn lint:check", "lint:fix": "yarn lint:check --fix", "lint:check": "eslint .", - "build:integration": "yarn build:contracts", - "build:contracts": "hardhat compile", + "build": "hardhat compile", "test:integration": "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", @@ -21,6 +20,7 @@ "@eth-optimism/core-utils": "^0.6.0", "@eth-optimism/message-relayer": "^0.1.13", "@ethersproject/providers": "^5.4.4", + "@ethersproject/transactions": "^5.4.0", "@nomiclabs/hardhat-ethers": "^2.0.2", "@nomiclabs/hardhat-waffle": "^2.0.1", "@types/chai": "^4.2.17", @@ -30,19 +30,19 @@ "@types/shelljs": "^0.8.8", "@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/parser": "^4.26.0", + "babel-eslint": "^10.1.0", "chai": "^4.3.3", "chai-as-promised": "^7.1.1", "docker-compose": "^0.23.8", "dotenv": "^10.0.0", "envalid": "^7.1.0", - "babel-eslint": "^10.1.0", "eslint": "^7.27.0", - "eslint-plugin-prettier": "^3.4.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-ban": "^1.5.2", "eslint-plugin-import": "^2.23.4", "eslint-plugin-jsdoc": "^35.1.2", "eslint-plugin-prefer-arrow": "^1.2.3", + "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-react": "^7.24.0", "eslint-plugin-unicorn": "^32.0.1", "ethereum-waffle": "^3.3.0", diff --git a/integration-tests/test/fee-payment.spec.ts b/integration-tests/test/fee-payment.spec.ts index 539ca54de86b..bbed3757fe71 100644 --- a/integration-tests/test/fee-payment.spec.ts +++ b/integration-tests/test/fee-payment.spec.ts @@ -4,37 +4,157 @@ chai.use(chaiAsPromised) /* Imports: External */ import { ethers, BigNumber, Contract, utils } from 'ethers' -import { TxGasLimit, TxGasPrice } from '@eth-optimism/core-utils' -import { predeploys, getContractInterface } from '@eth-optimism/contracts' +import { sleep } from '@eth-optimism/core-utils' +import { serialize } from '@ethersproject/transactions' +import { + predeploys, + getContractInterface, + getContractFactory, +} from '@eth-optimism/contracts' /* Imports: Internal */ import { IS_LIVE_NETWORK } from './shared/utils' import { OptimismEnv } from './shared/env' 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 () => { let env: OptimismEnv + const other = '0x1234123412341234123412341234123412341234' + before(async () => { env = await OptimismEnv.new() }) - let ovmSequencerFeeVault: Contract - before(async () => { - ovmSequencerFeeVault = new Contract( - predeploys.OVM_SequencerFeeVault, - getContractInterface('OVM_SequencerFeeVault'), - env.l2Wallet + it(`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() + // 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 () => { - 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 () { - const l1FeeWallet = await ovmSequencerFeeVault.l1FeeWallet() + const l1FeeWallet = await env.sequencerFeeVault.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() if (IS_LIVE_NETWORK && l2WalletBalance.lt(withdrawalAmount)) { @@ -48,18 +168,18 @@ describe('Fee Payment Integration Tests', async () => { // Transfer the minimum required to withdraw. const tx = await env.l2Wallet.sendTransaction({ - to: ovmSequencerFeeVault.address, + to: env.sequencerFeeVault.address, value: withdrawalAmount, gasLimit: 500000, }) await tx.wait() const vaultBalance = await env.ovmEth.balanceOf( - ovmSequencerFeeVault.address + env.sequencerFeeVault.address ) // 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. }) diff --git a/integration-tests/test/native-eth.spec.ts b/integration-tests/test/native-eth.spec.ts index 46a81c27e7ed..526e41517ad7 100644 --- a/integration-tests/test/native-eth.spec.ts +++ b/integration-tests/test/native-eth.spec.ts @@ -2,6 +2,7 @@ import { expect } from 'chai' /* Imports: External */ import { Wallet, utils, BigNumber } from 'ethers' +import { serialize } from '@ethersproject/transactions' import { predeploys } from '@eth-optimism/contracts' /* Imports: Internal */ @@ -248,24 +249,44 @@ describe('Native ETH Integration Tests', async () => { DEFAULT_TEST_GAS_L2, '0xFFFF' ) + await transaction.wait() await env.relayXDomainMessages(transaction) const receipts = await env.waitForXDomainTransaction( transaction, Direction.L2ToL1 ) - const fee = receipts.tx.gasLimit.mul(receipts.tx.gasPrice) + + const 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) expect(postBalances.l1BridgeBalance).to.deep.eq( - preBalances.l1BridgeBalance.sub(withdrawAmount) + preBalances.l1BridgeBalance.sub(withdrawAmount), + 'L1 Bridge Balance Mismatch' ) + 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( - preBalances.l1BobBalance.add(withdrawAmount) + preBalances.l1BobBalance.add(withdrawAmount), + 'L1 User Balance Mismatch' ) }) @@ -282,7 +303,7 @@ describe('Native ETH Integration Tests', async () => { Direction.L1ToL2 ) - // 2. trnsfer to another address + // 2. transfer to another address const other = Wallet.createRandom().connect(env.l2Wallet.provider) const tx = await env.l2Wallet.sendTransaction({ to: other.address, @@ -311,8 +332,22 @@ describe('Native ETH Integration Tests', async () => { 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 - 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 .connect(env.l1Wallet.provider) .getBalance() diff --git a/integration-tests/test/rpc.spec.ts b/integration-tests/test/rpc.spec.ts index f39d4e79e127..12ca82056bdd 100644 --- a/integration-tests/test/rpc.spec.ts +++ b/integration-tests/test/rpc.spec.ts @@ -1,8 +1,4 @@ -import { - injectL2Context, - TxGasLimit, - TxGasPrice, -} from '@eth-optimism/core-utils' +import { injectL2Context } from '@eth-optimism/core-utils' import { Wallet, BigNumber, Contract, ContractFactory } from 'ethers' import { ethers } from 'hardhat' import chai, { expect } from 'chai' diff --git a/integration-tests/test/shared/env.ts b/integration-tests/test/shared/env.ts index 860bb3ad5cb1..62643efcff86 100644 --- a/integration-tests/test/shared/env.ts +++ b/integration-tests/test/shared/env.ts @@ -1,5 +1,5 @@ /* Imports: External */ -import { Contract, utils, Wallet } from 'ethers' +import { Contract, utils, Wallet, providers } from 'ethers' import { TransactionResponse } from '@ethersproject/providers' import { getContractFactory, predeploys } from '@eth-optimism/contracts' import { Watcher } from '@eth-optimism/core-utils' @@ -40,6 +40,7 @@ export class OptimismEnv { l2Bridge: Contract l2Messenger: Contract gasPriceOracle: Contract + sequencerFeeVault: Contract // The L1 <> L2 State watcher watcher: Watcher @@ -48,6 +49,10 @@ export class OptimismEnv { l1Wallet: Wallet l2Wallet: Wallet + // The providers + l1Provider: providers.JsonRpcProvider + l2Provider: providers.JsonRpcProvider + constructor(args: any) { this.addressManager = args.addressManager this.l1Bridge = args.l1Bridge @@ -56,9 +61,12 @@ export class OptimismEnv { this.l2Bridge = args.l2Bridge this.l2Messenger = args.l2Messenger this.gasPriceOracle = args.gasPriceOracle + this.sequencerFeeVault = args.sequencerFeeVault this.watcher = args.watcher this.l1Wallet = args.l1Wallet this.l2Wallet = args.l2Wallet + this.l1Provider = args.l1Provider + this.l2Provider = args.l2Provider this.ctc = args.ctc this.scc = args.scc } @@ -100,6 +108,10 @@ export class OptimismEnv { .connect(l1Wallet) .attach(sccAddress) + const sequencerFeeVault = getContractFactory('OVM_SequencerFeeVault') + .connect(l2Wallet) + .attach(predeploys.OVM_SequencerFeeVault) + return new OptimismEnv({ addressManager, l1Bridge, @@ -108,11 +120,14 @@ export class OptimismEnv { l1Messenger, ovmEth, gasPriceOracle, + sequencerFeeVault, l2Bridge, l2Messenger, watcher, l1Wallet, l2Wallet, + l1Provider, + l2Provider, }) } diff --git a/integration-tests/test/stress-tests.spec.ts b/integration-tests/test/stress-tests.spec.ts index efe35beb6027..6b7e73eb45a3 100644 --- a/integration-tests/test/stress-tests.spec.ts +++ b/integration-tests/test/stress-tests.spec.ts @@ -155,8 +155,7 @@ describe('stress tests', () => { }).timeout(STRESS_TEST_TIMEOUT) }) - // SKIP: needs message passing PR - describe.skip('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 () => {