From e2c378ea500b70604cfc1eb8b8e4f4d9dbf5e4f4 Mon Sep 17 00:00:00 2001 From: Boyuan-Chen <46272347+Boyuan-Chen@users.noreply.github.com> Date: Sun, 8 May 2022 14:49:34 -0700 Subject: [PATCH] Add Boba gas oracle tests (#150) * Add Boba gas oracle tests * Update Github Action Co-authored-by: CAPtheorem <79423264+CAPtheorem@users.noreply.github.com> --- .github/workflows/boba-gas-oracle.yml | 47 ++ packages/boba/gas-price-oracle/package.json | 3 +- .../boba/gas-price-oracle/src/exec/run.ts | 7 + packages/boba/gas-price-oracle/src/service.ts | 102 +-- .../test/gas-price-oracle.spec.ts | 665 ++++++++++++++++++ packages/boba/gas-price-oracle/test/setup.ts | 10 + 6 files changed, 746 insertions(+), 88 deletions(-) create mode 100644 .github/workflows/boba-gas-oracle.yml create mode 100644 packages/boba/gas-price-oracle/test/gas-price-oracle.spec.ts create mode 100644 packages/boba/gas-price-oracle/test/setup.ts diff --git a/.github/workflows/boba-gas-oracle.yml b/.github/workflows/boba-gas-oracle.yml new file mode 100644 index 0000000000..2ada5159c6 --- /dev/null +++ b/.github/workflows/boba-gas-oracle.yml @@ -0,0 +1,47 @@ +name: boba-gas-oracle unit tests + +on: + push: + paths: + - 'packages/boba/gas-price-oracle/**' + branches: + - 'master' + - 'develop' + - '*rc' + - 'regenesis/*' + pull_request: + paths: + - 'packages/boba/gas-price-oracle/**' + branches: + - 'master' + - 'develop' + - '*rc' + - 'regenesis/*' + workflow_dispatch: + +jobs: + tests: + runs-on: ubuntu-latest + steps: + # Monorepo tests + - uses: actions/checkout@v2 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Build + working-directory: ./ + run: yarn && yarn install && yarn build + + - name: Run Boba gas oracle unit tests + working-directory: ./packages/boba/gas-price-oracle + run: yarn test diff --git a/packages/boba/gas-price-oracle/package.json b/packages/boba/gas-price-oracle/package.json index a58a58f3b1..d4c8a28ab4 100644 --- a/packages/boba/gas-price-oracle/package.json +++ b/packages/boba/gas-price-oracle/package.json @@ -16,7 +16,8 @@ "clean": "rimraf dist/ ./tsconfig.build.tsbuildinfo", "lint": "yarn lint:fix && yarn lint:check", "lint:fix": "prettier --config .prettierrc.json --write \"{src,exec,test}/**/*.ts\"", - "lint:check": "tslint --format stylish --project ." + "lint:check": "tslint --format stylish --project .", + "test": "hardhat test --show-stack-traces" }, "keywords": [ "optimism", diff --git a/packages/boba/gas-price-oracle/src/exec/run.ts b/packages/boba/gas-price-oracle/src/exec/run.ts index 85d55b6a20..1be086bc7c 100644 --- a/packages/boba/gas-price-oracle/src/exec/run.ts +++ b/packages/boba/gas-price-oracle/src/exec/run.ts @@ -90,6 +90,12 @@ const main = async () => { parseFloat(env.BOBA_FEE_RATIO_MIN_PERCENT_CHANGE) || 0.05 ) + // disable gasPrice=0 for local testing + const BOBA_LOCAL_TESTNET_CHAINID = config.uint( + 'boba-local-testnet-chainid', + parseInt(env.BOBA_LOCAL_TESTNET_CHAINID, 10) || 31338 + ) + if (!GAS_PRICE_ORACLE_ADDRESS) { throw new Error('Must pass GAS_PRICE_ORACLE_ADDRESS') } @@ -157,6 +163,7 @@ const main = async () => { maxL1BaseFee: MAX_L1_BASE_FEE, bobaFeeRatio100X: BOBA_FEE_RATIO_100X, bobaFeeRatioMinPercentChange: BOBA_FEE_RATIO_MIN_PERCENT_CHANGE, + bobaLocalTestnetChainId: BOBA_LOCAL_TESTNET_CHAINID, }) await service.start() diff --git a/packages/boba/gas-price-oracle/src/service.ts b/packages/boba/gas-price-oracle/src/service.ts index 0a99dc5130..416c203400 100644 --- a/packages/boba/gas-price-oracle/src/service.ts +++ b/packages/boba/gas-price-oracle/src/service.ts @@ -3,21 +3,14 @@ import { Contract, Wallet, BigNumber, providers, utils } from 'ethers' import fs, { promises as fsPromise } from 'fs' import path from 'path' import { orderBy } from 'lodash' -import fetch from 'node-fetch' /* Imports: Internal */ import { sleep } from '@eth-optimism/core-utils' import { BaseService } from '@eth-optimism/common-ts' import { loadContract } from '@eth-optimism/contracts' -import L1StandardBridgeJson from '@eth-optimism/contracts/artifacts/contracts/L1/messaging/L1StandardBridge.sol/L1StandardBridge.json' import L2GovernanceERC20Json from '@eth-optimism/contracts/artifacts/contracts/standards/L2GovernanceERC20.sol/L2GovernanceERC20.json' import Boba_GasPriceOracleJson from '@eth-optimism/contracts/artifacts/contracts/L2/predeploys/Boba_GasPriceOracle.sol/Boba_GasPriceOracle.json' -import DiscretionaryExitBurnJson from '@boba/contracts/artifacts/contracts/DiscretionaryExitBurn.sol/DiscretionaryExitBurn.json' -import L1LiquidityPoolJson from '@boba/contracts/artifacts/contracts/LP/L1LiquidityPool.sol/L1LiquidityPool.json' -import L2LiquidityPoolJson from '@boba/contracts/artifacts/contracts/LP/L2LiquidityPool.sol/L2LiquidityPool.json' -import L1NFTBridgeJson from '@boba/contracts/artifacts/contracts/bridges/L1NFTBridge.sol/L1NFTBridge.json' -import L2NFTBridgeJson from '@boba/contracts/artifacts/contracts/bridges/L2NFTBridge.sol/L2NFTBridge.json' import FluxAggregatorJson from '@boba/contracts/artifacts/contracts/oracle/FluxAggregator.sol/FluxAggregator.json' interface GasPriceOracleOptions { @@ -64,6 +57,9 @@ interface GasPriceOracleOptions { // minimum percentage change for boba fee / eth fee bobaFeeRatioMinPercentChange: number + + // local testnet chain ID + bobaLocalTestnetChainId: number } const optionSettings = {} @@ -76,14 +72,8 @@ export class GasPriceOracleService extends BaseService { private state: { Lib_AddressManager: Contract OVM_GasPriceOracle: Contract - Proxy__L1StandardBridge: Contract - DiscretionaryExitBurn: Contract - Proxy__L1LiquidityPool: Contract - Proxy__L2LiquidityPool: Contract CanonicalTransactionChain: Contract StateCommitmentChain: Contract - Proxy__L1NFTBridge: Contract - Proxy__L2NFTBridge: Contract Boba_GasPriceOracle: Contract BobaBillingContractAddress: string L2BOBA: Contract @@ -101,6 +91,7 @@ export class GasPriceOracleService extends BaseService { L2BOBABillingCollectFee: BigNumber BOBAUSDPrice: number ETHUSDPrice: number + chainID: number } protected async _init(): Promise { @@ -119,6 +110,7 @@ export class GasPriceOracleService extends BaseService { minL1BaseFee: this.options.minL1BaseFee, bobaFeeRatio100X: this.options.bobaFeeRatio100X, bobaFeeRatioMinPercentChange: this.options.bobaFeeRatioMinPercentChange, + bobaLocalTestnetChainId: this.options.bobaLocalTestnetChainId, }) this.state = {} as any @@ -133,77 +125,6 @@ export class GasPriceOracleService extends BaseService { address: this.state.Lib_AddressManager.address, }) - this.logger.info('Connecting to Proxy__L1StandardBridge...') - const Proxy__L1StandardBridgeAddress = - await this.state.Lib_AddressManager.getAddress('Proxy__L1StandardBridge') - this.state.Proxy__L1StandardBridge = new Contract( - Proxy__L1StandardBridgeAddress, - L1StandardBridgeJson.abi, - this.options.l1RpcProvider - ) - this.logger.info('Connected to Proxy__L1StandardBridge', { - address: this.state.Proxy__L1StandardBridge.address, - }) - - this.logger.info('Connecting to DiscretionaryExitBurn...') - const DiscretionaryExitBurnAddress = - await this.state.Lib_AddressManager.getAddress('DiscretionaryExitBurn') - this.state.DiscretionaryExitBurn = new Contract( - DiscretionaryExitBurnAddress, - DiscretionaryExitBurnJson.abi, - this.options.gasPriceOracleOwnerWallet - ) - this.logger.info('Connected to DiscretionaryExitBurn', { - address: this.state.DiscretionaryExitBurn.address, - }) - - this.logger.info('Connecting to Proxy__L1LiquidityPool...') - const Proxy__L1LiquidityPoolAddress = - await this.state.Lib_AddressManager.getAddress('Proxy__L1LiquidityPool') - this.state.Proxy__L1LiquidityPool = new Contract( - Proxy__L1LiquidityPoolAddress, - L1LiquidityPoolJson.abi, - this.options.l1RpcProvider - ) - this.logger.info('Connected to Proxy__L1LiquidityPool', { - address: this.state.Proxy__L1LiquidityPool.address, - }) - - this.logger.info('Connecting to Proxy__L2LiquidityPool...') - const Proxy__L2LiquidityPoolAddress = - await this.state.Lib_AddressManager.getAddress('Proxy__L2LiquidityPool') - this.state.Proxy__L2LiquidityPool = new Contract( - Proxy__L2LiquidityPoolAddress, - L2LiquidityPoolJson.abi, - this.options.gasPriceOracleOwnerWallet - ) - this.logger.info('Connected to Proxy__L2LiquidityPool', { - address: this.state.Proxy__L2LiquidityPool.address, - }) - - this.logger.info('Connecting to Proxy__L1NFTBridge...') - const Proxy__L1NFTBridgeAddress = - await this.state.Lib_AddressManager.getAddress('Proxy__L1NFTBridge') - this.state.Proxy__L1NFTBridge = new Contract( - Proxy__L1NFTBridgeAddress, - L1NFTBridgeJson.abi, - this.options.gasPriceOracleOwnerWallet - ) - this.logger.info('Connected to Proxy__L1NFTBridge', { - address: this.state.Proxy__L1NFTBridge.address, - }) - - const Proxy__L2NFTBridgeAddress = - await this.state.Lib_AddressManager.getAddress('Proxy__L2NFTBridge') - this.state.Proxy__L2NFTBridge = new Contract( - Proxy__L2NFTBridgeAddress, - L2NFTBridgeJson.abi, - this.options.gasPriceOracleOwnerWallet - ) - this.logger.info('Connected to Proxy__L2NFTBridge', { - address: this.state.Proxy__L2NFTBridge.address, - }) - this.logger.info('Connecting to CanonicalTransactionChain...') const CanonicalTransactionChainAddress = await this.state.Lib_AddressManager.getAddress( @@ -314,6 +235,9 @@ export class GasPriceOracleService extends BaseService { // Load history await this._loadL1ETHFee() await this._loadL2FeeCost() + + // Get chain ID + this.state.chainID = (await this.options.l2RpcProvider.getNetwork()).chainId } protected async _start(): Promise { @@ -753,7 +677,9 @@ export class GasPriceOracleService extends BaseService { await this.state.Boba_GasPriceOracle.updatePriceRatio( targetUpdatedPriceRatio, targetMarketPriceRatio, - { gasPrice: 0 } + this.state.chainID === this.options.bobaLocalTestnetChainId + ? {} + : { gasPrice: 0 } ) await gasPriceTx.wait() this.logger.info('Updated price ratio', { @@ -876,7 +802,9 @@ export class GasPriceOracleService extends BaseService { ) { const tx = await this.state.OVM_GasPriceOracle.setL1BaseFee( l1GasPrice, - { gasPrice: 0 } + this.state.chainID === this.options.bobaLocalTestnetChainId + ? {} + : { gasPrice: 0 } ) await tx.wait() this.logger.info('Updated l1BaseFee', { @@ -896,7 +824,7 @@ export class GasPriceOracleService extends BaseService { } } - private async _queryTokenPrice(tokenPair): Promise { + private async _queryTokenPrice(tokenPair: string): Promise { if (tokenPair === 'ETH/USD') { const latestAnswer = await this.state.BobaStraw_ETHUSD.latestAnswer() const decimals = await this.state.BobaStraw_ETHUSD.decimals() diff --git a/packages/boba/gas-price-oracle/test/gas-price-oracle.spec.ts b/packages/boba/gas-price-oracle/test/gas-price-oracle.spec.ts new file mode 100644 index 0000000000..c8ce2d30af --- /dev/null +++ b/packages/boba/gas-price-oracle/test/gas-price-oracle.spec.ts @@ -0,0 +1,665 @@ +import { expect } from './setup' + +/* External Imports */ +import { ethers } from 'hardhat' +import { ContractFactory, Contract, Signer, BigNumber, Wallet } from 'ethers' +import { getContractFactory } from '@eth-optimism/contracts' +import { GasPriceOracleService } from '../dist/service' +import fs, { promises as fsPromise } from 'fs' +import path from 'path' +import { + AppendSequencerBatchParams, + encodeAppendSequencerBatch, +} from '@eth-optimism/core-utils' +import { TransactionResponse } from '@ethersproject/abstract-provider' +import { keccak256 } from 'ethers/lib/utils' + +describe('gas-price-oracle', () => { + let signer1: Signer + let signer2: Signer + let signer3: Signer + // sequencerAddress + let wallet1: Wallet + let address1: string + // proposerAddress + let wallet2: Wallet + let address2: string + // relayerAddress + let wallet3: Wallet + let address3: string + // fastRelayerAddress + let wallet4: Wallet + let address4: string + // OVM_SequencerFeeVault + let wallet5: Wallet + let address5: string + // BOBA_GasPriceOracle + let wallet6: Wallet + let address6: string + // BOBA_billingContract + let wallet7: Wallet + let address7: string + + // deployer + let wallet8: Wallet + + let gasPriceOracleService: any + let tempGasPriceOracleService: any + + before(async () => { + ;[signer1, signer2] = await ethers.getSigners() + wallet1 = ethers.Wallet.createRandom().connect(ethers.provider) + address1 = wallet1.address + wallet2 = ethers.Wallet.createRandom().connect(ethers.provider) + address2 = wallet2.address + wallet3 = ethers.Wallet.createRandom().connect(ethers.provider) + address3 = wallet3.address + wallet4 = ethers.Wallet.createRandom().connect(ethers.provider) + address4 = wallet4.address + wallet5 = ethers.Wallet.createRandom().connect(ethers.provider) + address5 = wallet5.address + wallet6 = ethers.Wallet.createRandom().connect(ethers.provider) + address6 = wallet6.address + wallet7 = ethers.Wallet.createRandom().connect(ethers.provider) + address7 = wallet7.address + wallet8 = ethers.Wallet.createRandom().connect(ethers.provider) + await signer1.sendTransaction({ + to: wallet8.address, + value: ethers.utils.parseEther('20'), + }) + }) + + let Factory__GasPriceOracle: ContractFactory + let gasPriceOracle: Contract + + let Factory__Lib_AddressManager: ContractFactory + let Lib_AddressManager: Contract + + let Factory__StateCommitmentChain: ContractFactory + let StateCommitmentChain: Contract + + let Factory__CanonicalTransactionChain: ContractFactory + let CanonicalTransactionChain: Contract + + let Factory__L2BOBA: ContractFactory + let L2BOBA: Contract + + let Factory__ChainStorageContainer: ContractFactory + let batches: Contract + let queue: Contract + + before(async () => { + Factory__Lib_AddressManager = getContractFactory( + 'Lib_AddressManager', + signer1 + ) + Lib_AddressManager = await Factory__Lib_AddressManager.deploy() + await Lib_AddressManager.deployTransaction.wait() + + Factory__GasPriceOracle = getContractFactory('OVM_GasPriceOracle', wallet8) + gasPriceOracle = await Factory__GasPriceOracle.deploy(wallet8.address) + await gasPriceOracle.deployTransaction.wait() + + await Lib_AddressManager.setAddress( + 'OVM_GasPriceOracle', + gasPriceOracle.address + ) + + Factory__StateCommitmentChain = getContractFactory( + 'StateCommitmentChain', + signer1 + ) + StateCommitmentChain = await Factory__StateCommitmentChain.deploy( + Lib_AddressManager.address, + 0, + 0 + ) + await StateCommitmentChain.deployTransaction.wait() + + await Lib_AddressManager.setAddress( + 'StateCommitmentChain', + StateCommitmentChain.address + ) + + Factory__CanonicalTransactionChain = getContractFactory( + 'CanonicalTransactionChain', + wallet8 + ) + CanonicalTransactionChain = await Factory__CanonicalTransactionChain.deploy( + Lib_AddressManager.address, + 8_000_000, + 32, + 60_000 + ) + await CanonicalTransactionChain.deployTransaction.wait() + + await Lib_AddressManager.setAddress( + 'CanonicalTransactionChain', + CanonicalTransactionChain.address + ) + + Factory__L2BOBA = getContractFactory('BOBA', signer1) + L2BOBA = await Factory__L2BOBA.deploy() + await L2BOBA.deployTransaction.wait() + + await Lib_AddressManager.setAddress('TK_L2BOBA', L2BOBA.address) + + await Lib_AddressManager.setAddress('Proxy__Boba_GasPriceOracle', address6) + + await Lib_AddressManager.setAddress('Proxy__BobaBillingContract', address7) + }) + + it('should create GasPriceOracleService', async () => { + // Remove file if it exists + const l2DumpsPath = path.resolve(__dirname, '../data/l2History.json') + if (fs.existsSync(l2DumpsPath)) { + fs.unlinkSync(l2DumpsPath) + } + const l1DumpsPath = path.resolve(__dirname, '../data/l1History.json') + if (fs.existsSync(l1DumpsPath)) { + fs.unlinkSync(l1DumpsPath) + } + // Initialize GasPriceOracleService + gasPriceOracleService = new GasPriceOracleService({ + l1RpcProvider: ethers.provider, + l2RpcProvider: ethers.provider, + + addressManagerAddress: Lib_AddressManager.address, + gasPriceOracleAddress: gasPriceOracle.address, + + OVM_SequencerFeeVault: address5, + + gasPriceOracleOwnerWallet: wallet8, + + sequencerAddress: address1, + proposerAddress: address2, + relayerAddress: address3, + fastRelayerAddress: address4, + + pollingInterval: 0, + overheadRatio1000X: 10, + overheadMinPercentChange: 10, + minOverhead: 2000, + minL1BaseFee: 0, + maxL1BaseFee: 1, + bobaFeeRatio100X: 800, + bobaFeeRatioMinPercentChange: 3000, + bobaLocalTestnetChainId: 31338, + }) + + await gasPriceOracleService.init() + }) + + it('should set history values to 0', async () => { + expect(gasPriceOracleService.state.L1ETHBalance).to.be.eq( + BigNumber.from('0') + ) + expect(gasPriceOracleService.state.L1ETHCostFee).to.be.eq( + BigNumber.from('0') + ) + expect(gasPriceOracleService.state.L1RelayerBalance).to.be.eq( + BigNumber.from('0') + ) + expect(gasPriceOracleService.state.L1RelayerCostFee).to.be.eq( + BigNumber.from('0') + ) + expect(gasPriceOracleService.state.L2ETHCollectFee).to.be.eq( + BigNumber.from('0') + ) + expect(gasPriceOracleService.state.L2BOBACollectFee).to.be.eq( + BigNumber.from('0') + ) + expect(gasPriceOracleService.state.L2BOBABillingCollectFee).to.be.eq( + BigNumber.from('0') + ) + }) + + it('should write history values', async () => { + // Write two files + await gasPriceOracleService._writeL1ETHFee() + await gasPriceOracleService._writeL2FeeCollect() + + // Verify them + const l2DumpsPath = path.resolve(__dirname, '../data/l2History.json') + expect(fs.existsSync(l2DumpsPath)).to.be.true + + const l2HistoryJsonRaw = await fsPromise.readFile(l2DumpsPath) + const l2HistoryJSON = JSON.parse(l2HistoryJsonRaw.toString()) + + expect(l2HistoryJSON.L2BOBACollectFee).to.be.eq('0') + expect(l2HistoryJSON.L2ETHCollectFee).to.be.eq('0') + expect(l2HistoryJSON.L2BOBABillingCollectFee).to.be.eq('0') + + const l1DumpsPath = path.resolve(__dirname, '../data/l1History.json') + expect(fs.existsSync(l1DumpsPath)).to.be.true + + const l1HistoryJsonRaw = await fsPromise.readFile(l1DumpsPath) + const l1HistoryJSON = JSON.parse(l1HistoryJsonRaw.toString()) + + expect(l1HistoryJSON.L1ETHBalance).to.be.eq('0') + expect(l1HistoryJSON.L1ETHCostFee).to.be.eq('0') + expect(l1HistoryJSON.L1RelayerBalance).to.be.eq('0') + expect(l1HistoryJSON.L1RelayerCostFee).to.be.eq('0') + }) + + it('should update and store history values', async () => { + gasPriceOracleService.state.L1ETHBalance = BigNumber.from('1') + gasPriceOracleService.state.L1ETHCostFee = BigNumber.from('2') + gasPriceOracleService.state.L1RelayerBalance = BigNumber.from('3') + gasPriceOracleService.state.L1RelayerCostFee = BigNumber.from('4') + gasPriceOracleService.state.L2BOBACollectFee = BigNumber.from('5') + gasPriceOracleService.state.L2ETHCollectFee = BigNumber.from('6') + gasPriceOracleService.state.L2BOBABillingCollectFee = BigNumber.from('7') + + // Write two files + await gasPriceOracleService._writeL1ETHFee() + await gasPriceOracleService._writeL2FeeCollect() + + // Verify them + const l2DumpsPath = path.resolve(__dirname, '../data/l2History.json') + expect(fs.existsSync(l2DumpsPath)).to.be.true + + const l2HistoryJsonRaw = await fsPromise.readFile(l2DumpsPath) + const l2HistoryJSON = JSON.parse(l2HistoryJsonRaw.toString()) + + expect(l2HistoryJSON.L2BOBACollectFee).to.be.eq('5') + expect(l2HistoryJSON.L2ETHCollectFee).to.be.eq('6') + expect(l2HistoryJSON.L2BOBABillingCollectFee).to.be.eq('7') + + const l1DumpsPath = path.resolve(__dirname, '../data/l1History.json') + expect(fs.existsSync(l1DumpsPath)).to.be.true + + const l1HistoryJsonRaw = await fsPromise.readFile(l1DumpsPath) + const l1HistoryJSON = JSON.parse(l1HistoryJsonRaw.toString()) + + expect(l1HistoryJSON.L1ETHBalance).to.be.eq('1') + expect(l1HistoryJSON.L1ETHCostFee).to.be.eq('2') + expect(l1HistoryJSON.L1RelayerBalance).to.be.eq('3') + expect(l1HistoryJSON.L1RelayerCostFee).to.be.eq('4') + + // Reset history values + gasPriceOracleService.state.L1ETHBalance = BigNumber.from('0') + gasPriceOracleService.state.L1ETHCostFee = BigNumber.from('0') + gasPriceOracleService.state.L1RelayerBalance = BigNumber.from('0') + gasPriceOracleService.state.L1RelayerCostFee = BigNumber.from('0') + gasPriceOracleService.state.L2BOBACollectFee = BigNumber.from('0') + gasPriceOracleService.state.L2ETHCollectFee = BigNumber.from('0') + gasPriceOracleService.state.L2BOBABillingCollectFee = BigNumber.from('0') + + // Write two files + await gasPriceOracleService._writeL1ETHFee() + await gasPriceOracleService._writeL2FeeCollect() + }) + + it('should get l1 balance correctly', async () => { + await signer1.sendTransaction({ + to: address1, + value: ethers.utils.parseEther('1'), + }) + await signer1.sendTransaction({ + to: address2, + value: ethers.utils.parseEther('1'), + }) + await signer1.sendTransaction({ + to: address3, + value: ethers.utils.parseEther('1'), + }) + await signer1.sendTransaction({ + to: address4, + value: ethers.utils.parseEther('1'), + }) + await gasPriceOracleService._getL1Balance() + + expect(gasPriceOracleService.state.L1ETHBalance).to.be.eq( + ethers.utils.parseEther('4') + ) + expect(gasPriceOracleService.state.L1ETHCostFee).to.be.eq( + BigNumber.from('0') + ) + console.log(await signer1.provider.getBalance(address1)) + }) + + it('should record l1 cost correctly', async () => { + // send some funds back + const signer1Address = await signer1.getAddress() + await wallet1.sendTransaction({ + to: signer1Address, + value: ethers.utils.parseEther('0.5'), + }) + await wallet2.sendTransaction({ + to: signer1Address, + value: ethers.utils.parseEther('0.5'), + }) + await wallet3.sendTransaction({ + to: signer1Address, + value: ethers.utils.parseEther('0.5'), + }) + await wallet4.sendTransaction({ + to: signer1Address, + value: ethers.utils.parseEther('0.5'), + }) + + const sequencerBalance = await wallet1.getBalance() + const proposerBalance = await wallet2.getBalance() + const relayerBalance = await wallet3.getBalance() + const fastRelayerBalance = await wallet4.getBalance() + + await gasPriceOracleService._getL1Balance() + + const costFee = ethers.utils + .parseEther('4') + .sub( + sequencerBalance + .add(proposerBalance) + .add(relayerBalance) + .add(fastRelayerBalance) + ) + const relayerCostFee = ethers.utils + .parseEther('2') + .sub(relayerBalance.add(fastRelayerBalance)) + expect(costFee).to.be.equal(gasPriceOracleService.state.L1ETHCostFee) + expect(gasPriceOracleService.state.L1ETHBalance).to.be.equal( + sequencerBalance + .add(proposerBalance) + .add(relayerBalance) + .add(fastRelayerBalance) + ) + expect(gasPriceOracleService.state.L1RelayerBalance).to.be.equal( + relayerBalance.add(fastRelayerBalance) + ) + expect(gasPriceOracleService.state.L1RelayerCostFee).to.be.equal( + relayerCostFee + ) + }) + + it('should record l1 cost correctly after adding more funds to l1 wallets', async () => { + const preL1ETHCostFee = gasPriceOracleService.state.L1ETHCostFee + const preL1RelayerCostFee = gasPriceOracleService.state.L1RelayerCostFee + + await signer1.sendTransaction({ + to: address1, + value: ethers.utils.parseEther('1'), + }) + await signer1.sendTransaction({ + to: address2, + value: ethers.utils.parseEther('1'), + }) + await signer1.sendTransaction({ + to: address3, + value: ethers.utils.parseEther('1'), + }) + await signer1.sendTransaction({ + to: address4, + value: ethers.utils.parseEther('1'), + }) + + const postSequencerBalance = await wallet1.getBalance() + const postProposerBalance = await wallet2.getBalance() + const postRelayerBalance = await wallet3.getBalance() + const postFastRelayerBalance = await wallet4.getBalance() + + // Update L1ETHBalance and keep L1ETHCostFee + // Update L1RelayerBalance and keep L1RelayerCostFee + await gasPriceOracleService._getL1Balance() + expect(gasPriceOracleService.state.L1ETHBalance).to.be.equal( + postSequencerBalance + .add(postProposerBalance) + .add(postRelayerBalance) + .add(postFastRelayerBalance) + ) + expect(preL1ETHCostFee).to.be.eq(gasPriceOracleService.state.L1ETHCostFee) + + expect(gasPriceOracleService.state.L1RelayerBalance).to.be.equal( + postRelayerBalance.add(postFastRelayerBalance) + ) + expect(preL1RelayerCostFee).to.be.eq( + gasPriceOracleService.state.L1RelayerCostFee + ) + }) + + it('should get l2 revenue correctly', async () => { + await signer1.sendTransaction({ + to: address5, + value: ethers.utils.parseEther('1'), + }) + await signer1.sendTransaction({ + to: address6, + value: ethers.utils.parseEther('1'), + }) + await signer1.sendTransaction({ + to: address7, + value: ethers.utils.parseEther('1'), + }) + await L2BOBA.transfer(address6, ethers.utils.parseEther('1')) + await L2BOBA.transfer(address7, ethers.utils.parseEther('1')) + + await gasPriceOracleService._getL2GasCost() + + expect(gasPriceOracleService.state.L2ETHCollectFee).to.be.eq( + ethers.utils.parseEther('1') + ) + expect(gasPriceOracleService.state.L2ETHVaultBalance).to.be.eq( + ethers.utils.parseEther('1') + ) + expect(gasPriceOracleService.state.L2BOBACollectFee).to.be.eq( + ethers.utils.parseEther('1') + ) + expect(gasPriceOracleService.state.L2BOBAVaultBalance).to.be.eq( + ethers.utils.parseEther('1') + ) + expect(gasPriceOracleService.state.L2BOBABillingBalance).to.be.eq( + ethers.utils.parseEther('1') + ) + expect(gasPriceOracleService.state.L2BOBABillingCollectFee).to.be.eq( + ethers.utils.parseEther('1') + ) + }) + + it('should record l2 revenue correctly after withdrawing fees', async () => { + const preL2ETHCollectFee = gasPriceOracleService.state.L2ETHCollectFee + const preL2BOBACollectFee = gasPriceOracleService.state.L2BOBACollectFee + const preL2BOBABillingCollectFee = + gasPriceOracleService.state.L2BOBABillingCollectFee + + // send some funds back + const signer1Address = await signer1.getAddress() + await wallet5.sendTransaction({ + to: signer1Address, + value: ethers.utils.parseEther('0.5'), + }) + await L2BOBA.connect(wallet6).transfer( + signer1Address, + ethers.utils.parseEther('0.5') + ) + await L2BOBA.connect(wallet7).transfer( + signer1Address, + ethers.utils.parseEther('0.5') + ) + + const ETHVaultBalance = await wallet5.getBalance() + const BobaVaultBalance = await L2BOBA.balanceOf(address6) + const BobaBillingBalance = await L2BOBA.balanceOf(address7) + + await gasPriceOracleService._getL2GasCost() + + expect(gasPriceOracleService.state.L2ETHCollectFee).to.be.equal( + preL2ETHCollectFee + ) + expect(gasPriceOracleService.state.L2BOBACollectFee).to.be.equal( + preL2BOBACollectFee + ) + expect(gasPriceOracleService.state.L2BOBABillingCollectFee).to.be.equal( + preL2BOBABillingCollectFee + ) + expect(gasPriceOracleService.state.L2ETHVaultBalance).to.be.equal( + ETHVaultBalance + ) + expect(gasPriceOracleService.state.L2BOBAVaultBalance).to.be.equal( + BobaVaultBalance + ) + expect(gasPriceOracleService.state.L2BOBABillingBalance).to.be.equal( + BobaBillingBalance + ) + }) + + it('should update l1 base fee', async () => { + // Initialize GasPriceOracleService + tempGasPriceOracleService = new GasPriceOracleService({ + l1RpcProvider: ethers.provider, + l2RpcProvider: ethers.provider, + + addressManagerAddress: Lib_AddressManager.address, + gasPriceOracleAddress: gasPriceOracle.address, + + OVM_SequencerFeeVault: address5, + + gasPriceOracleOwnerWallet: wallet8, + + sequencerAddress: address1, + proposerAddress: address2, + relayerAddress: address3, + fastRelayerAddress: address4, + + pollingInterval: 0, + overheadRatio1000X: 10, + overheadMinPercentChange: 10, + minOverhead: 2000, + minL1BaseFee: 1_000_000_000, + maxL1BaseFee: 2_000_000_000, + bobaFeeRatio100X: 800, + bobaFeeRatioMinPercentChange: 3000, + bobaLocalTestnetChainId: 31337, + }) + + await tempGasPriceOracleService.init() + + await gasPriceOracle.connect(wallet8).setL1BaseFee(0) + console.log('SET L1 BASE FEE TO 0') + await tempGasPriceOracleService._upateL1BaseFee() + + const postL1BaseFee = await gasPriceOracle.l1BaseFee() + expect(postL1BaseFee).to.not.be.equal(BigNumber.from('0')) + + await gasPriceOracle.connect(wallet8).setL1BaseFee(0) + }) + + it('should not update l1 base fee if the gas price is higher than maxL1BaseFee', async () => { + const preL1BaseFee = await gasPriceOracle.l1BaseFee() + expect(preL1BaseFee).to.be.equal(ethers.utils.parseEther('0')) + + await gasPriceOracleService._upateL1BaseFee() + + const postL1BaseFee = await gasPriceOracle.l1BaseFee() + expect(postL1BaseFee).to.be.equal(ethers.utils.parseEther('0')) + }) + + it('should not update l1 base fee if the gas price is smaller than minL1BaseFee', async () => { + // Initialize GasPriceOracleService + tempGasPriceOracleService = new GasPriceOracleService({ + l1RpcProvider: ethers.provider, + l2RpcProvider: ethers.provider, + + addressManagerAddress: Lib_AddressManager.address, + gasPriceOracleAddress: gasPriceOracle.address, + + OVM_SequencerFeeVault: address5, + + gasPriceOracleOwnerWallet: wallet8, + + sequencerAddress: address1, + proposerAddress: address2, + relayerAddress: address3, + fastRelayerAddress: address4, + + pollingInterval: 0, + overheadRatio1000X: 10, + overheadMinPercentChange: 10, + minOverhead: 2000, + minL1BaseFee: 50_000_000_000, + maxL1BaseFee: 100_000_000_000, + bobaFeeRatio100X: 800, + bobaFeeRatioMinPercentChange: 3000, + bobaLocalTestnetChainId: 31337, + }) + + await tempGasPriceOracleService.init() + + const preL1BaseFee = await gasPriceOracle.l1BaseFee() + expect(preL1BaseFee).to.be.equal(ethers.utils.parseEther('0')) + + await tempGasPriceOracleService._upateL1BaseFee() + + const postL1BaseFee = await gasPriceOracle.l1BaseFee() + expect(postL1BaseFee).to.be.equal(ethers.utils.parseEther('0')) + }) + + it('should update price ratio', async () => { + const Factory__Boba_GasPriceOracle = getContractFactory( + 'Boba_GasPriceOracle', + wallet8 + ) + const Boba_GasPriceOracle = await Factory__Boba_GasPriceOracle.deploy() + await Boba_GasPriceOracle.deployTransaction.wait() + + await Boba_GasPriceOracle.initialize(address1, address2) + + const registerBoba_GasPriceOralceTx = await Lib_AddressManager.setAddress( + 'Proxy__Boba_GasPriceOracle', + Boba_GasPriceOracle.address + ) + await registerBoba_GasPriceOralceTx.wait() + + // Initialize GasPriceOracleService + tempGasPriceOracleService = new GasPriceOracleService({ + l1RpcProvider: ethers.provider, + l2RpcProvider: ethers.provider, + + addressManagerAddress: Lib_AddressManager.address, + gasPriceOracleAddress: gasPriceOracle.address, + + OVM_SequencerFeeVault: address5, + + gasPriceOracleOwnerWallet: wallet8, + + sequencerAddress: address1, + proposerAddress: address2, + relayerAddress: address3, + fastRelayerAddress: address4, + + pollingInterval: 0, + overheadRatio1000X: 10, + overheadMinPercentChange: 10, + minOverhead: 2000, + minL1BaseFee: 50_000_000_000, + maxL1BaseFee: 100_000_000_000, + bobaFeeRatio100X: 100, + bobaFeeRatioMinPercentChange: 10, + bobaLocalTestnetChainId: 31337, + }) + + await tempGasPriceOracleService.init() + + tempGasPriceOracleService.state.ETHUSDPrice = 10 + tempGasPriceOracleService.state.BOBAUSDPrice = 1 + + const prePriceRatio = await Boba_GasPriceOracle.priceRatio() + + await tempGasPriceOracleService._updatePriceRatio() + + const postPriceRatio = await Boba_GasPriceOracle.priceRatio() + expect(postPriceRatio).to.be.equal(prePriceRatio) + + tempGasPriceOracleService.state.ETHUSDPrice = 2500 + tempGasPriceOracleService.state.BOBAUSDPrice = 1 + + console.log({ + ETHUSDPrice: tempGasPriceOracleService.state.ETHUSDPrice, + BOBAUSDPrice: tempGasPriceOracleService.state.BOBAUSDPrice, + }) + + await tempGasPriceOracleService._updatePriceRatio() + + const updatedPriceRatio = await Boba_GasPriceOracle.priceRatio() + expect(updatedPriceRatio).to.be.equal(BigNumber.from('2500')) + }) +}) diff --git a/packages/boba/gas-price-oracle/test/setup.ts b/packages/boba/gas-price-oracle/test/setup.ts new file mode 100644 index 0000000000..3b89f57894 --- /dev/null +++ b/packages/boba/gas-price-oracle/test/setup.ts @@ -0,0 +1,10 @@ +/* External Imports */ +import chai = require('chai') +import Mocha from 'mocha' +import { solidity } from 'ethereum-waffle' + +chai.use(solidity) +const should = chai.should() +const expect = chai.expect + +export { should, expect, Mocha }