diff --git a/.changeset/dull-hotels-trade.md b/.changeset/dull-hotels-trade.md new file mode 100644 index 0000000..aad1174 --- /dev/null +++ b/.changeset/dull-hotels-trade.md @@ -0,0 +1,6 @@ +--- +'@rosen-chains/evm': major +--- + +Replace getMaxFeePerGas and getMaxPriorityFeePerGas functions with getFeeData function in AbstractEvmNetwork +Support both type 0 and type 2 transactions diff --git a/.changeset/wet-terms-decide.md b/.changeset/wet-terms-decide.md new file mode 100644 index 0000000..946dcd8 --- /dev/null +++ b/.changeset/wet-terms-decide.md @@ -0,0 +1,5 @@ +--- +'@rosen-chains/evm-rpc': major +--- + +Replace getMaxFeePerGas and getMaxPriorityFeePerGas functions with getFeeData function in AbstractEvmNetwork diff --git a/packages/chains/binance/lib/BinanceChain.ts b/packages/chains/binance/lib/BinanceChain.ts index 6b9e6e5..1318889 100644 --- a/packages/chains/binance/lib/BinanceChain.ts +++ b/packages/chains/binance/lib/BinanceChain.ts @@ -29,6 +29,7 @@ class BinanceChain extends EvmChain { signFunction, BINANCE_CHAIN, BNB, + 0, logger ); } diff --git a/packages/chains/ethereum/lib/EthereumChain.ts b/packages/chains/ethereum/lib/EthereumChain.ts index 30b5967..7c63b49 100644 --- a/packages/chains/ethereum/lib/EthereumChain.ts +++ b/packages/chains/ethereum/lib/EthereumChain.ts @@ -29,6 +29,7 @@ class EthereumChain extends EvmChain { signFunction, ETHEREUM_CHAIN, ETH, + 2, logger ); } diff --git a/packages/chains/evm/lib/EvmChain.ts b/packages/chains/evm/lib/EvmChain.ts index 4cf9fdf..da354ad 100644 --- a/packages/chains/evm/lib/EvmChain.ts +++ b/packages/chains/evm/lib/EvmChain.ts @@ -31,6 +31,7 @@ abstract class EvmChain extends AbstractChain { declare network: AbstractEvmNetwork; declare configs: EvmConfigs; abstract CHAIN_ID: bigint; + evmTxType: number; extractor: EvmRosenExtractor | undefined; supportedTokens: Array; @@ -44,9 +45,11 @@ abstract class EvmChain extends AbstractChain { signFunction: TssSignFunction, CHAIN: string, NATIVE_TOKEN_ID: string, + evmTxType = 0, logger?: AbstractLogger ) { super(network, configs, tokens, logger); + this.evmTxType = evmTxType; this.supportedTokens = supportedTokens; this.signFunction = signFunction; this.extractor = new EvmRosenExtractor( @@ -128,7 +131,39 @@ abstract class EvmChain extends AbstractChain { nextNonce++; } - const gasPrice = await this.network.getMaxFeePerGas(); + const feeData = await this.network.getFeeData(); + let requiredNativeToken = 0n; + let txFeeData; + if (this.evmTxType === 2) { + if ( + feeData.maxFeePerGas === null || + feeData.maxPriorityFeePerGas === null + ) { + throw new ImpossibleBehavior( + `Tx type is 2 but max fee variables are null` + ); + } + requiredNativeToken = + this.configs.gasLimitCap * BigInt(orders.length) * feeData.maxFeePerGas; + txFeeData = { + maxFeePerGas: feeData.maxFeePerGas, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, + }; + } else if (this.evmTxType === 0) { + if (feeData.gasPrice === null) { + throw new ImpossibleBehavior(`Tx type is 0 but gas price is null`); + } else { + requiredNativeToken = + this.configs.gasLimitCap * BigInt(orders.length) * feeData.gasPrice; + txFeeData = { + gasPrice: feeData.gasPrice, + }; + } + } else { + throw new ImpossibleBehavior( + `Type ${this.evmTxType} transaction is not supported in EvmChain` + ); + } // check the balance in the lock address const requiredAssets: AssetBalance = ChainUtils.sumAssetBalance( @@ -136,7 +171,7 @@ abstract class EvmChain extends AbstractChain { { nativeToken: this.tokenMap.wrapAmount( this.NATIVE_TOKEN_ID, - this.configs.gasLimitCap * BigInt(orders.length) * gasPrice, + requiredNativeToken, this.CHAIN ).amount, tokens: [], @@ -156,7 +191,6 @@ abstract class EvmChain extends AbstractChain { // try to generate transactions let totalGas = 0n; - const maxPriorityFeePerGas = await this.network.getMaxPriorityFeePerGas(); const evmTrxs: Array = []; for (const singleOrder of orders) { let trx; @@ -167,14 +201,13 @@ abstract class EvmChain extends AbstractChain { this.CHAIN ).amount; trx = Transaction.from({ - type: 2, + type: this.evmTxType, to: singleOrder.address, nonce: nextNonce, - maxPriorityFeePerGas: maxPriorityFeePerGas, - maxFeePerGas: gasPrice, data: '0x' + eventId, value: value, chainId: this.CHAIN_ID, + ...txFeeData, }); } else { const token = singleOrder.assets.tokens[0]; @@ -190,14 +223,13 @@ abstract class EvmChain extends AbstractChain { ); trx = Transaction.from({ - type: 2, + type: this.evmTxType, to: token.id, nonce: nextNonce, - maxPriorityFeePerGas: maxPriorityFeePerGas, - maxFeePerGas: gasPrice, data: data + eventId, value: 0n, chainId: this.CHAIN_ID, + ...txFeeData, }); } let estimatedRequiredGas = await this.network.getGasRequired(trx); @@ -248,24 +280,29 @@ abstract class EvmChain extends AbstractChain { ); } - if (tx.type !== 2) { - throw new TransactionFormatError( - `Transaction [${transaction.txId}] is not of type 2` - ); - } - - if (tx.maxFeePerGas === null) { - throw new ImpossibleBehavior( - 'Type 2 transaction can not have null maxFeePerGas' - ); - } - const assets: AssetBalance = { nativeToken: 0n, tokens: [], }; - const networkFee = tx.maxFeePerGas * tx.gasLimit; + let networkFee = tx.gasLimit; + if (tx.type === 2) { + if (tx.maxFeePerGas === null) + throw new ImpossibleBehavior( + "Type 2 transaction doesn't have null maxFeePerGas or maxPriorityFeePerGas" + ); + networkFee *= tx.maxFeePerGas; + } else if (tx.type === 0) { + if (tx.gasPrice === null) + throw new ImpossibleBehavior( + "Type 0 transaction doesn't have null gasPrice" + ); + networkFee *= tx.gasPrice; + } else { + throw new ImpossibleBehavior( + `Type ${this.evmTxType} transaction is not supported in EvmChain` + ); + } assets.nativeToken = tx.value + networkFee; if (EvmUtils.isTransfer(tx.to, tx.data)) { @@ -356,10 +393,13 @@ abstract class EvmChain extends AbstractChain { /** * verifies transaction fee for a PaymentTransaction * - `to` shouldn't be null - * - transaction must of of type 2 + * - transaction type must be as expected * - gasLimit must be as expected - * - maxFeePerGas shouldn't be different than current network condition by more than slippage - * - maxPriorityFeePerGas shouldn't be different than current network condition by more than slippage + * - for type 2 transactions + * - maxFeePerGas shouldn't be different than current network condition by more than slippage + * - maxPriorityFeePerGas shouldn't be different than current network condition by more than slippage + * - for type 0 transactions + * - gasPrice shouldn't be different than current network condition by more than slippage * @param transaction the PaymentTransaction * @returns true if the transaction fee is verified */ @@ -367,6 +407,7 @@ abstract class EvmChain extends AbstractChain { transaction: PaymentTransaction ): Promise => { let tx: Transaction; + const baseError = `Tx [${transaction.txId}] is not verified: `; try { tx = Serializer.deserialize(transaction.txBytes); } catch (error) { @@ -383,25 +424,13 @@ abstract class EvmChain extends AbstractChain { return false; } - if (tx.type !== 2) { + if (tx.type !== this.evmTxType) { this.logger.warn( - `Tx [${transaction.txId}] is not verified: is not of type 2` + `Tx [${transaction.txId}] is not verified: expected type [${this.evmTxType}] found [${tx.type}]` ); return false; } - if (tx.maxFeePerGas === null) { - throw new ImpossibleBehavior( - "Type 2 transaction can't have null maxFeePerGas" - ); - } - - if (tx.maxPriorityFeePerGas === null) { - throw new ImpossibleBehavior( - "Type 2 transaction can't have null maxPriorityFeePerGas" - ); - } - // check gas limit let estimatedRequiredGas = await this.network.getGasRequired(tx); if (estimatedRequiredGas > this.configs.gasLimitCap) { @@ -420,40 +449,87 @@ abstract class EvmChain extends AbstractChain { if (gasDifference > gasLimitSlippage) { this.logger.warn( - `Tx [${transaction.txId}] is not verified: Transaction gas limit [${tx.gasLimit}] is too far from calculated gas limit [${gasRequired}]` + baseError + + `Transaction gas limit [${tx.gasLimit}] is too far from calculated gas limit [${gasRequired}]` ); return false; } // check fees - const networkMaxFee = await this.network.getMaxFeePerGas(); - const maxFeeSlippage = - (networkMaxFee * this.configs.gasPriceSlippage) / 100n; - const maxFeeDifference = - tx.maxFeePerGas >= networkMaxFee - ? tx.maxFeePerGas - networkMaxFee - : networkMaxFee - tx.maxFeePerGas; - - if (maxFeeDifference > maxFeeSlippage) { - this.logger.warn( - `Tx [${transaction.txId}] is not verified: Transaction max fee [${tx.maxFeePerGas}] is too far from network's max fee [${networkMaxFee}]` - ); - return false; - } + const feeData = await this.network.getFeeData(); + if (tx.type === 2) { + if (tx.maxFeePerGas === null || tx.maxPriorityFeePerGas === null) + throw new ImpossibleBehavior( + "Type 2 transaction can't have null maxFeePerGas or maxPriorityFeePerGas" + ); + if ( + feeData.maxFeePerGas === null || + feeData.maxPriorityFeePerGas === null + ) + throw new ImpossibleBehavior( + 'Chain is using type 2 transactions but network is replying with null maxFeePerGas or maxPriorityFeePerGas' + ); - const networkMaxPriorityFee = await this.network.getMaxPriorityFeePerGas(); - const priorityFeeSlippage = - (networkMaxPriorityFee * this.configs.gasPriceSlippage) / 100n; - const maxPriorityFeeDifference = - tx.maxPriorityFeePerGas >= networkMaxPriorityFee - ? tx.maxPriorityFeePerGas - networkMaxPriorityFee - : networkMaxPriorityFee - tx.maxPriorityFeePerGas; + const networkMaxFee = feeData.maxFeePerGas; + const maxFeeSlippage = + (networkMaxFee * this.configs.gasPriceSlippage) / 100n; + const maxFeeDifference = + tx.maxFeePerGas >= networkMaxFee + ? tx.maxFeePerGas - networkMaxFee + : networkMaxFee - tx.maxFeePerGas; - if (maxPriorityFeeDifference > priorityFeeSlippage) { - this.logger.warn( - `Tx [${transaction.txId}] is not verified: Transaction max priority fee [${tx.maxPriorityFeePerGas}] is too far from network's max priority fee [${networkMaxPriorityFee}]` + if (maxFeeDifference > maxFeeSlippage) { + this.logger.warn( + baseError + + `Transaction max fee [${tx.maxFeePerGas}] is too far from network's max fee [${networkMaxFee}]` + ); + return false; + } + + const networkMaxPriorityFee = feeData.maxPriorityFeePerGas; + const priorityFeeSlippage = + (networkMaxPriorityFee * this.configs.gasPriceSlippage) / 100n; + const maxPriorityFeeDifference = + tx.maxPriorityFeePerGas >= networkMaxPriorityFee + ? tx.maxPriorityFeePerGas - networkMaxPriorityFee + : networkMaxPriorityFee - tx.maxPriorityFeePerGas; + + if (maxPriorityFeeDifference > priorityFeeSlippage) { + this.logger.warn( + baseError + + `Transaction max priority fee [${tx.maxPriorityFeePerGas}] is too far from network's max priority fee [${networkMaxPriorityFee}]` + ); + return false; + } + } else if (tx.type === 0) { + if (tx.gasPrice === null) + throw new ImpossibleBehavior( + "Type 0 transaction can't have null gasPrice" + ); + if (feeData.gasPrice === null) + throw new ImpossibleBehavior( + 'Chain is using type 0 transactions but network is replying with null gasPrice' + ); + + const networkGasPrice = feeData.gasPrice; + const gasPriceSlippage = + (networkGasPrice * this.configs.gasPriceSlippage) / 100n; + const gasPriceDifference = + tx.gasPrice >= networkGasPrice + ? tx.gasPrice - networkGasPrice + : networkGasPrice - tx.gasPrice; + + if (gasPriceDifference > gasPriceSlippage) { + this.logger.warn( + baseError + + `Transaction gas price [${tx.gasPrice}] is too far from network's gas price [${networkGasPrice}]` + ); + return false; + } + } else { + throw new ImpossibleBehavior( + `Type ${this.evmTxType} transaction is not supported in EvmChain` ); - return false; } return true; }; @@ -576,7 +652,6 @@ abstract class EvmChain extends AbstractChain { /** * submits a transaction to the blockchain * checks the following conditions before: - * - transaction must of of type 2 * - fees are set appropriately according to the current network's condition * - lock address still have enough funds * @param transaction the transaction @@ -596,19 +671,11 @@ abstract class EvmChain extends AbstractChain { } try { - // check type - if (tx.type !== 2) { - this.logger.warn( - `Cannot submit transaction [${transaction.txId}]: Transaction is not of type 2` - ); - return; - } - // check fees const gasRequired = await this.network.getGasRequired(tx); if (gasRequired > tx.gasLimit) { this.logger.warn( - `Cannot submit transaction [${transaction.txId}]: Transaction gas limit [${tx.maxFeePerGas}] is less than the required gas [${gasRequired}]` + `Cannot submit transaction [${transaction.txId}]: Transaction gas limit [${tx.gasLimit}] is less than the required gas [${gasRequired}]` ); return; } @@ -685,7 +752,7 @@ abstract class EvmChain extends AbstractChain { /** * generates PaymentTransaction object from raw tx json string - * checks the transaction is of type 2 + * also checks the transaction type * @param rawTxJsonString * @returns PaymentTransaction object */ @@ -693,9 +760,9 @@ abstract class EvmChain extends AbstractChain { rawTxJsonString: string ): Promise => { const trx = Transaction.from(JSON.parse(rawTxJsonString)); - if (trx.type !== 2) { + if (trx.type !== this.evmTxType) { throw new TransactionFormatError( - `Only transaction of type 2 is supported while parsing raw transaction` + `Expected type [${this.evmTxType}] transaction while parsing raw transaction, found type [${trx.type}]` ); } const evmTx = new PaymentTransaction( @@ -716,7 +783,6 @@ abstract class EvmChain extends AbstractChain { * verifies additional conditions for a PaymentTransaction * - `to` shouldn't be null * - `data` shouldn't be null - * - transaction must be of type 2 * - `data` length must be either: * native-token transfer: 2 (0x) + eventId.length * erc-20 transfer: 2 (0x) + 136 (`transfer` data) + eventId.length @@ -754,14 +820,6 @@ abstract class EvmChain extends AbstractChain { const eidlen = transaction.eventId.length; - // only type 2 transactions are allowed - if (tx.type !== 2) { - this.logger.warn( - `Tx [${transaction.txId}] is not verified. It is not of type 2` - ); - return false; - } - // tx data must have correct length if (![eidlen + 2, eidlen + 2 + 136].includes(tx.data.length)) { this.logger.warn( diff --git a/packages/chains/evm/lib/network/AbstractEvmNetwork.ts b/packages/chains/evm/lib/network/AbstractEvmNetwork.ts index 64139fc..b1bbc06 100644 --- a/packages/chains/evm/lib/network/AbstractEvmNetwork.ts +++ b/packages/chains/evm/lib/network/AbstractEvmNetwork.ts @@ -1,5 +1,5 @@ import { AbstractChainNetwork } from '@rosen-chains/abstract-chain'; -import { Transaction, TransactionResponse } from 'ethers'; +import { FeeData, Transaction } from 'ethers'; import { EvmTxStatus, TransactionHashes } from '../types'; abstract class AbstractEvmNetwork extends AbstractChainNetwork { @@ -36,19 +36,16 @@ abstract class AbstractEvmNetwork extends AbstractChainNetwork { * @returns gas required in bigint */ abstract getGasRequired: (transaction: Transaction) => Promise; - /** - * gets the maximum wei we would pay to the miner according - * to the network's current condition - * @returns gas price as a bigint - */ - abstract getMaxPriorityFeePerGas: () => Promise; /** - * gets the maximum wei we would pay (miner + base fee) according - * to the network's current condition - * @returns gas price as a bigint + * gets fee-related values associated with the network + * - the legacy gas price + * - the maximum fee to pay per gas + * - the additional amount to pay per gas to miner + * it may or may not include all the values, depends on the instance implementation + * @returns fee-related values as bigint or null */ - abstract getMaxFeePerGas: () => Promise; + abstract getFeeData: () => Promise; /** * gets all transactions in mempool (returns empty list if the chain has no mempool) diff --git a/packages/chains/evm/tests/EvmChain.spec.ts b/packages/chains/evm/tests/EvmChain.spec.ts index 8eedafe..a919661 100644 --- a/packages/chains/evm/tests/EvmChain.spec.ts +++ b/packages/chains/evm/tests/EvmChain.spec.ts @@ -1,7 +1,6 @@ import { expect, vi } from 'vitest'; import { AssetNotSupportedError, - MaxParallelTxError, NotEnoughAssetsError, PaymentTransaction, SigningStatus, @@ -12,16 +11,12 @@ import TestEvmNetwork from './network/TestEvmNetwork'; import * as TestData from './testData'; import * as testUtils from './TestUtils'; import Serializer from '../lib/Serializer'; -import { Transaction, TransactionLike } from 'ethers'; +import { FeeData, Transaction, TransactionLike } from 'ethers'; import { mockGetAddressBalanceForNativeToken } from './TestUtils'; import { EvmTxStatus } from '../lib'; import TestChain from './TestChain'; describe('EvmChain', () => { - const network = new TestEvmNetwork(); - - const evmChain = testUtils.generateChainObject(network); - describe(`constructor`, () => { /** * @target EvmChain.constructor should initialize rosen-extractor successfully @@ -33,12 +28,14 @@ describe('EvmChain', () => { * - CHAIN variable should be the same as the one defined EvmChain */ it('should initialize rosen-extractor successfully', async () => { + const network = new TestEvmNetwork(); const chain = new TestChain( network, testUtils.configs, TestData.testTokenMap, TestData.supportedTokens, - testUtils.mockedSignFn + testUtils.mockedSignFn, + 2 ); expect(chain.extractor?.chain).toEqual(chain.CHAIN); }); @@ -50,9 +47,8 @@ describe('EvmChain', () => { * successfully for multiple orders * @dependencies * @scenario - * - mock hasLockAddressEnoughAssets, getMaxFeePerGas + * - mock hasLockAddressEnoughAssets, getFeeData * - mock getGasRequired, getAddressNextNonce - * - mock getMaxPriorityFeePerGas * - call the function * - check returned value * @expected @@ -86,14 +82,15 @@ describe('EvmChain', () => { Buffer.from(Serializer.signedSerialize(elem)).toString('hex') ); - // mock hasLockAddressEnoughAssets, getMaxFeePerGas, - // getGasRequired, getAddressNextNonce, getMaxPriorityFeePerGas + // mock hasLockAddressEnoughAssets, getFeeData, + // getGasRequired, getAddressNextNonce + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); const requiredGas = 100000n; testUtils.mockHasLockAddressEnoughAssets(evmChain, true); - testUtils.mockGetMaxFeePerGas(network, 10n); + testUtils.mockGetFeeData(network, new FeeData(10n, 10n, 10n)); testUtils.mockGetGasRequired(network, requiredGas); testUtils.mockGetAddressNextAvailableNonce(network, nonce); - testUtils.mockGetMaxPriorityFeePerGas(network, 10n); // run test const evmTxs = await evmChain.generateMultipleTransactions( @@ -146,9 +143,8 @@ describe('EvmChain', () => { * transaction successfully for single order * @dependencies * @scenario - * - mock hasLockAddressEnoughAssets, getMaxFeePerGas + * - mock hasLockAddressEnoughAssets, getFeeData * - mock getGasRequired, getAddressNextNonce - * - mock getMaxPriorityFeePerGas * - call the function * - check returned value * @expected @@ -161,6 +157,7 @@ describe('EvmChain', () => { * - transaction must be of type 2 and has no blobs * - nonce must be the same as the next available nonce * - gas limit should be as expected + * - maxFeePerGas and maxPriorityFeePerGas should be as expected */ it('should generate payment transaction successfully for single order', async () => { const order = TestData.nativePaymentOrder; @@ -168,14 +165,15 @@ describe('EvmChain', () => { const txType = TransactionType.payment; const nonce = 54; - // mock hasLockAddressEnoughAssets, getMaxFeePerGas, - // getGasRequired, getAddressNextNonce, getMaxPriorityFeePerGas + // mock hasLockAddressEnoughAssets, getFeeData, + // getGasRequired, getAddressNextNonce + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); const requiredGas = 21000n; testUtils.mockHasLockAddressEnoughAssets(evmChain, true); - testUtils.mockGetMaxFeePerGas(network, 10n); + testUtils.mockGetFeeData(network, new FeeData(10n, 10n, 10n)); testUtils.mockGetGasRequired(network, requiredGas); testUtils.mockGetAddressNextAvailableNonce(network, nonce); - testUtils.mockGetMaxPriorityFeePerGas(network, 10n); // run test const evmTx = await evmChain.generateMultipleTransactions( @@ -214,6 +212,10 @@ describe('EvmChain', () => { // check gas limit expect(tx.gasLimit).toEqual(requiredGas * 3n); // requiredGas * gasLimitMultiplier + + // check maxFeePerGas and maxPriorityFeePerGas + expect(tx.maxFeePerGas).toEqual(10n); + expect(tx.maxPriorityFeePerGas).toEqual(10n); }); /** @@ -221,9 +223,8 @@ describe('EvmChain', () => { * transaction with capped gas limit when required is too high * @dependencies * @scenario - * - mock hasLockAddressEnoughAssets, getMaxFeePerGas + * - mock hasLockAddressEnoughAssets, getFeeData * - mock getGasRequired, getAddressNextNonce - * - mock getMaxPriorityFeePerGas * - call the function * - check returned value * @expected @@ -243,13 +244,14 @@ describe('EvmChain', () => { const txType = TransactionType.payment; const nonce = 54; - // mock hasLockAddressEnoughAssets, getMaxFeePerGas, - // getGasRequired, getAddressNextNonce, getMaxPriorityFeePerGas + // mock hasLockAddressEnoughAssets, getFeeData, + // getGasRequired, getAddressNextNonce + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); testUtils.mockHasLockAddressEnoughAssets(evmChain, true); - testUtils.mockGetMaxFeePerGas(network, 10n); + testUtils.mockGetFeeData(network, new FeeData(10n, 10n, 10n)); testUtils.mockGetGasRequired(network, 200000n); testUtils.mockGetAddressNextAvailableNonce(network, nonce); - testUtils.mockGetMaxPriorityFeePerGas(network, 10n); // run test const evmTx = await evmChain.generateMultipleTransactions( @@ -307,6 +309,8 @@ describe('EvmChain', () => { const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; // run test and expect error + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); await expect(async () => { await evmChain.generateMultipleTransactions( eventId, @@ -323,7 +327,7 @@ describe('EvmChain', () => { * when lock address does not have enough assets * @dependencies * @scenario - * - mock hasLockAddressEnoughAssets + * - mock hasLockAddressEnoughAssets and getAddressNextNonce * - call the function * @expected * - throw NotEnoughAssetsError @@ -332,9 +336,14 @@ describe('EvmChain', () => { const order = TestData.multipleOrders; const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; + const nonce = 54; - // mock hasLockAddressEnoughAssets + // mock hasLockAddressEnoughAssets and getAddressNextNonce + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); testUtils.mockHasLockAddressEnoughAssets(evmChain, false); + testUtils.mockGetFeeData(network, new FeeData(10n, 10n, 10n)); + testUtils.mockGetAddressNextAvailableNonce(network, nonce); // run test and expect error await expect(async () => { @@ -388,14 +397,15 @@ describe('EvmChain', () => { ); const nonce = 53; - // mock hasLockAddressEnoughAssets, getMaxFeePerGas, - // getGasRequired, getAddressNextNonce, getMaxPriorityFeePerGas + // mock hasLockAddressEnoughAssets, getFeeData, + // getGasRequired, getAddressNextNonce + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); const requiredGas = 21000n; testUtils.mockHasLockAddressEnoughAssets(evmChain, true); - testUtils.mockGetMaxFeePerGas(network, 10n); + testUtils.mockGetFeeData(network, new FeeData(10n, 10n, 10n)); testUtils.mockGetGasRequired(network, requiredGas); testUtils.mockGetAddressNextAvailableNonce(network, nonce); - testUtils.mockGetMaxPriorityFeePerGas(network, 10n); // run test const evmTx = await evmChain.generateMultipleTransactions( @@ -467,7 +477,11 @@ describe('EvmChain', () => { ); // mock hasLockAddressEnoughAssets, getAddressNextNonce, + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); testUtils.mockHasLockAddressEnoughAssets(evmChain, true); + testUtils.mockGetFeeData(network, new FeeData(10n, 10n, 10n)); + testUtils.mockGetGasRequired(network, 200000n); testUtils.mockGetAddressNextAvailableNonce(network, 53); // run test and expect no error @@ -488,9 +502,8 @@ describe('EvmChain', () => { * transaction successfully for wrapped order * @dependencies * @scenario - * - mock hasLockAddressEnoughAssets, getMaxFeePerGas + * - mock hasLockAddressEnoughAssets, getFeeData * - mock getGasRequired, getAddressNextNonce - * - mock getMaxPriorityFeePerGas * - call the function * - check returned value * @expected @@ -504,6 +517,7 @@ describe('EvmChain', () => { * - nonce must be the same as the next available nonce */ it('should generate payment transaction successfully for wrapped order', async () => { + const network = new TestEvmNetwork(); const evmChain = testUtils.generateChainObjectWithMultiDecimalTokenMap(network); @@ -515,10 +529,9 @@ describe('EvmChain', () => { // mock hasLockAddressEnoughAssets, getMaxFeePerGas, // getGasRequired, getAddressNextNonce, getMaxPriorityFeePerGas testUtils.mockHasLockAddressEnoughAssets(evmChain, true); - testUtils.mockGetMaxFeePerGas(network, 10n); + testUtils.mockGetFeeData(network, new FeeData(10n, 10n, 10n)); testUtils.mockGetGasRequired(network, 21000n); testUtils.mockGetAddressNextAvailableNonce(network, nonce); - testUtils.mockGetMaxPriorityFeePerGas(network, 10n); // run test const evmTx = await evmChain.generateMultipleTransactions( @@ -555,9 +568,91 @@ describe('EvmChain', () => { // check nonce expect(tx.nonce).toEqual(nonce); }); + + /** + * @target EvmChain.generateMultipleTransactions should generate + * type 0 transaction successfully for single order + * @dependencies + * @scenario + * - mock hasLockAddressEnoughAssets, getFeeData + * - mock getGasRequired, getAddressNextNonce + * - call the function + * - check returned value + * @expected + * - PaymentTransaction txType, eventId and network should be as + * expected + * - extracted order of generated transaction should be the same as input + * order + * - eventId should be properly in the transaction data + * - no extra data should be found in the transaction data + * - transaction must be of type 0 and has no blobs + * - nonce must be the same as the next available nonce + * - gas limit should be as expected + * - gas price should be as expected + */ + it('should generate type 0 transaction successfully for single order', async () => { + const order = TestData.nativePaymentOrder; + const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; + const txType = TransactionType.payment; + const nonce = 54; + + // mock hasLockAddressEnoughAssets, getFeeData, + // getGasRequired, getAddressNextNonce + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network, undefined, 0); + const requiredGas = 21000n; + testUtils.mockHasLockAddressEnoughAssets(evmChain, true); + testUtils.mockGetFeeData(network, new FeeData(10n, null, null)); + testUtils.mockGetGasRequired(network, requiredGas); + testUtils.mockGetAddressNextAvailableNonce(network, nonce); + + // run test + const evmTx = await evmChain.generateMultipleTransactions( + eventId, + txType, + order, + [], + [] + ); + + // check returned value + expect(evmTx[0].txType).toEqual(txType); + expect(evmTx[0].eventId).toEqual(eventId); + expect(evmTx[0].network).toEqual(evmChain.CHAIN); + + // extracted order of generated transaction should be the same as input order + const extractedOrder = evmChain.extractTransactionOrder(evmTx[0]); + expect(extractedOrder).toEqual(order); + + const tx = Serializer.deserialize(evmTx[0].txBytes); + + // check eventId encoded at the end of the data + expect(tx.data.substring(2, 34)).toEqual(eventId); + + // check there is no more data + expect(tx.data.length).toEqual(34); + + // check transaction type + expect(tx.type).toEqual(0); + + // check blobs zero + expect(tx.maxFeePerBlobGas).toEqual(null); + + // check nonce + expect(tx.nonce).toEqual(nonce); + + // check gas limit + expect(tx.gasLimit).toEqual(requiredGas * 3n); // requiredGas * gasLimitMultiplier + + // check gas price + expect(tx.gasPrice).toEqual(10n); + }); }); describe('rawTxToPaymentTransaction', () => { + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); + /** * @target EvmChain.rawTxToPaymentTransaction should construct transaction successfully * @dependencies @@ -583,7 +678,7 @@ describe('EvmChain', () => { /** * @target EvmChain.rawTxToPaymentTransaction should throw error when transaction - * is not of type 2 + * type is unexpected * @dependencies * @scenario * - mock invalid transaction @@ -591,7 +686,8 @@ describe('EvmChain', () => { * @expected * - throw TransactionFormatError */ - it('should throw error when transaction is not of type 2', async () => { + it('should throw error when transaction type is unexpected', async () => { + const evmChain = testUtils.generateChainObject(network, undefined, 0); expect(async () => { await evmChain.rawTxToPaymentTransaction( TestData.transaction1JsonString @@ -601,6 +697,9 @@ describe('EvmChain', () => { }); describe('getTransactionAssets', () => { + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); + /** * @target EvmChain.getTransactionAssets should get transaction assets * successfully when there is ERC-20 token transfer. @@ -703,24 +802,26 @@ describe('EvmChain', () => { }); /** - * @target EvmChain.getTransactionAssets should throw error when transaction is not of type 2 + * @target EvmChain.getTransactionAssets should get transaction assets + * successfully for type 0 transactions * @dependencies * @scenario * - mock PaymentTransaction * - call the function + * - check returned value * @expected - * - throw error + * - it should return mocked transaction assets (both input and output assets) */ - it('should throw error when transaction is not of type 2', async () => { + it('should get transaction assets successfully for type 0 transactions', async () => { + const evmChain = testUtils.generateChainObject(network, undefined, 0); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; // mock PaymentTransaction - const trx = { ...TestData.transaction1Json }; + const trx = { ...TestData.transaction1WithType0Json }; trx.data = '0x'; const tx = Transaction.from(trx); const assets = { ...TestData.transaction1Assets }; assets.tokens = []; - tx.type = 3; const paymentTx = new PaymentTransaction( evmChain.CHAIN, tx.unsignedHash, @@ -729,10 +830,12 @@ describe('EvmChain', () => { txType ); - // run test function and expect error - expect(async () => { - await evmChain.getTransactionAssets(paymentTx); - }).rejects.toThrowError(TransactionFormatError); + // check returned value + const result = await evmChain.getTransactionAssets(paymentTx); + + // check returned value + expect(result.inputAssets).toEqual(assets); + expect(result.outputAssets).toEqual(assets); }); /** @@ -747,6 +850,7 @@ describe('EvmChain', () => { * - it should return mocked transaction assets (both input and output assets) */ it('should wrap transaction assets successfully', async () => { + const network = new TestEvmNetwork(); const evmChain = testUtils.generateChainObjectWithMultiDecimalTokenMap(network); @@ -782,7 +886,7 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * - check returned value * @expected @@ -790,11 +894,12 @@ describe('EvmChain', () => { */ it('should return true when both fees and gasLimit are set properly', async () => { // mock a config that has almost the same fee as the mocked transaction + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 100000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const tx = Transaction.from(TestData.transaction1Json); tx.gasLimit = 85000n * evmChain.configs.gasLimitMultiplier; tx.maxFeePerGas = 22n; @@ -823,18 +928,19 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * @expected * - return false */ it('should return false when `to` is null', async () => { // mock a config that has almost the same fee as the transaction fee + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 100000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const tx = Transaction.from(TestData.transaction1Json); tx.gasLimit = 100000n * evmChain.configs.gasLimitMultiplier; tx.maxFeePerGas = 22n; @@ -860,22 +966,23 @@ describe('EvmChain', () => { }); /** - * @target EvmChain.verifyTransactionFee should return false when transaction is not of type 2 + * @target EvmChain.verifyTransactionFee should return false when transaction type is unexpected * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * @expected * - return false */ - it('should return false when transaction is not of type 2', async () => { + it('should return false when transaction type is unexpected', async () => { // mock a config that has almost the same fee as the transaction fee + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 55000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network, undefined, 0); const tx = Transaction.from(TestData.transaction1Json); tx.gasLimit = 55000n * evmChain.configs.gasLimitMultiplier; tx.maxFeePerGas = 22n; @@ -906,7 +1013,7 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * - check returned value * @expected @@ -915,11 +1022,12 @@ describe('EvmChain', () => { it('should return false when gasLimit is wrong for erc-20 transfer', async () => { // mock a config that has more fee and wrong required gas // comparing to the mocked transaction + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 90000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.transaction1Json); @@ -949,7 +1057,7 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * - check returned value * @expected @@ -958,11 +1066,12 @@ describe('EvmChain', () => { it('should return false when gasLimit is over the cap for erc-20 transfer', async () => { // mock a config that has more fee and wrong required gas // comparing to the mocked transaction + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 90000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.transaction1Json); @@ -992,7 +1101,7 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * - check returned value * @expected @@ -1001,11 +1110,12 @@ describe('EvmChain', () => { it('should return false when gasLimit is wrong for native-token transfer', async () => { // mock a config that has more fee and wrong required gas // comparing to the mocked transaction + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 20000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.transaction1Json); @@ -1036,7 +1146,7 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * - check returned value * @expected @@ -1044,11 +1154,12 @@ describe('EvmChain', () => { */ it('should return false when maxFeePerGas is too much bigger than expected', async () => { // mock a config that has too much bigger max fee comparing to the mocked transaction + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 76000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.transaction1Json); @@ -1078,19 +1189,20 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * - check returned value * @expected * - it should return false */ - it('should return false when maxFeePerGas is too much smaller than exptected', async () => { + it('should return false when maxFeePerGas is too much smaller than expected', async () => { // mock a config that has too much smaller max fee comparing to the mocked transaction + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 76000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.transaction1Json); @@ -1120,7 +1232,7 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * - check returned value * @expected @@ -1128,11 +1240,12 @@ describe('EvmChain', () => { */ it('should return false when maxPriorityFeePerGas is too much bigger than expected', async () => { // mock a config that has too much bigger max fee comparing to the mocked transaction + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 76000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.transaction1Json); @@ -1162,7 +1275,7 @@ describe('EvmChain', () => { * @dependencies * @scenario * - mock mockGetGasRequired - * - mock mockGetMaxFeePerGas, mockGetMaxPriorityFeePerGas + * - mock getFeeData * - mock PaymentTransaction * - check returned value * @expected @@ -1170,11 +1283,12 @@ describe('EvmChain', () => { */ it('should return false when maxPriorityFeePerGas is too much smaller than expected', async () => { // mock a config that has too much bigger max fee comparing to the mocked transaction + const network = new TestEvmNetwork(); testUtils.mockGetGasRequired(network, 76000n); - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.transaction1Json); @@ -1197,9 +1311,138 @@ describe('EvmChain', () => { // check returned value expect(result).toEqual(false); }); + + /** + * @target EvmChain.verifyTransactionFee should return true when + * gasPrice and gasLimit are set properly for a type 0 transaction + * @dependencies + * @scenario + * - mock mockGetGasRequired + * - mock getFeeData + * - mock PaymentTransaction + * - check returned value + * @expected + * - it should return true + */ + it('should return true when gasPrice and gasLimit are set properly for a type 0 transaction', async () => { + // mock a config that has almost the same fee as the mocked transaction + const network = new TestEvmNetwork(); + testUtils.mockGetGasRequired(network, 100000n); + testUtils.mockGetFeeData(network, new FeeData(20n, null, null)); + + // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network, undefined, 0); + const tx = Transaction.from(TestData.transaction1WithType0Json); + tx.gasLimit = 85000n * evmChain.configs.gasLimitMultiplier; + tx.gasPrice = 22n; + tx.value = 2n; + + const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; + const txType = TransactionType.payment; + const paymentTx = new PaymentTransaction( + evmChain.CHAIN, + tx.unsignedHash, + eventId, + Serializer.serialize(tx), + txType + ); + + // run test + const result = await evmChain.verifyTransactionFee(paymentTx); + + // check returned value + expect(result).toEqual(true); + }); + + /** + * @target EvmChain.verifyTransactionFee should return false when gasPrice + * is too much bigger than expected + * @dependencies + * @scenario + * - mock mockGetGasRequired + * - mock getFeeData + * - mock PaymentTransaction + * - check returned value + * @expected + * - it should return false + */ + it('should return false when gasPrice is too much bigger than expected', async () => { + // mock a config that has too much bigger max fee comparing to the mocked transaction + const network = new TestEvmNetwork(); + testUtils.mockGetGasRequired(network, 76000n); + testUtils.mockGetFeeData(network, new FeeData(20n, null, null)); + + // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network, undefined, 0); + const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; + const txType = TransactionType.payment; + const tx = Transaction.from(TestData.transaction1WithType0Json); + tx.gasLimit = 76000n * evmChain.configs.gasLimitMultiplier; + tx.gasPrice = 25n; + tx.value = 10n; + + const paymentTx = new PaymentTransaction( + evmChain.CHAIN, + tx.unsignedHash, + eventId, + Serializer.serialize(tx), + txType + ); + + // run test + const result = await evmChain.verifyTransactionFee(paymentTx); + + // check returned value + expect(result).toEqual(false); + }); + + /** + * @target EvmChain.verifyTransactionFee should return false when gasPrice + * is too much smaller than expected + * @dependencies + * @scenario + * - mock mockGetGasRequired + * - mock getFeeData + * - mock PaymentTransaction + * - check returned value + * @expected + * - it should return false + */ + it('should return false when gasPrice is too much smaller than expected', async () => { + // mock a config that has too much smaller max fee comparing to the mocked transaction + const network = new TestEvmNetwork(); + testUtils.mockGetGasRequired(network, 76000n); + testUtils.mockGetFeeData(network, new FeeData(20n, null, null)); + + // mock PaymentTransaction + const evmChain = testUtils.generateChainObject(network, undefined, 0); + const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; + const txType = TransactionType.payment; + const tx = Transaction.from(TestData.transaction1WithType0Json); + tx.gasLimit = 76000n * evmChain.configs.gasLimitMultiplier; + tx.gasPrice = 16n; + tx.value = 10n; + + const paymentTx = new PaymentTransaction( + evmChain.CHAIN, + tx.unsignedHash, + eventId, + Serializer.serialize(tx), + txType + ); + + // run test + const result = await evmChain.verifyTransactionFee(paymentTx); + + // check returned value + expect(result).toEqual(false); + }); }); describe('extractTransactionOrder', () => { + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); + /** * @target EvmChain.extractTransactionOrder should extract transaction * order successfully for ERC-20 token transfer only @@ -1459,19 +1702,23 @@ describe('EvmChain', () => { * when transaction is of type 2, fees are set properly and lock address has enough assets * @dependencies * @scenario + * - mock network functions + * - getFeeData + * - hasLockAddressEnoughAssets + * - getGasRequired * - mock valid PaymentTransaction - * - mock getMaxFeePerGas, getMaxPriorityFeePerGas - * - mock hasLockAddressEnoughAssets * - run test * - check function is called * @expected * - it should call the function */ it('should submit the transaction when transaction is of type 2, fees are set properly and lock address has enough assets', async () => { - // mock getMaxFeePerGas, getMaxPriorityFeePerGas, and hasLockAddressEnoughAssets - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + // mock network functions + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); testUtils.mockHasLockAddressEnoughAssets(evmChain, true); + testUtils.mockGetGasRequired(network, 70000n); // mock PaymentTransaction const tx = Transaction.from(TestData.transaction1Json); @@ -1495,53 +1742,27 @@ describe('EvmChain', () => { expect(submitTransactionSpy).toHaveBeenCalled(); }); - /** - * @target EvmChain.submitTransaction should not submit the transaction - * when transaction is not of type 2 - * @dependencies - * @scenario - * - mock invalid PaymentTransaction - * - run test - * - check function is not called - * @expected - * - it should not call the function - */ - it('should submit the transaction when transaction is not of type 2', async () => { - // mock PaymentTransaction - const tx = Transaction.from(TestData.transaction1Json); - tx.gasLimit = 55000n + 21000n; - tx.type = 3; - - const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; - const txType = TransactionType.payment; - const paymentTx = new PaymentTransaction( - evmChain.CHAIN, - tx.unsignedHash, - eventId, - Serializer.serialize(tx), - txType - ); - const submitTransactionSpy = vi.spyOn(network, 'submitTransaction'); - submitTransactionSpy.mockImplementation(async () => undefined); - await evmChain.submitTransaction(paymentTx); - expect(submitTransactionSpy).not.toHaveBeenCalled(); - }); - /** * @target EvmChain.submitTransaction should not submit the transaction * when gasLimit is wrong * @dependencies * @scenario + * - mock network functions + * - getFeeData + * - hasLockAddressEnoughAssets + * - getGasRequired * - mock invalid PaymentTransaction - * - mock getGasRequired - * - mock hasLockAddressEnoughAssets * - run test * - check function is not called * @expected * - it should not call the function */ it('should not submit the transaction when gasLimit is wrong', async () => { - // mock getGasRequired, and hasLockAddressEnoughAssets + // mock network functions + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); + testUtils.mockHasLockAddressEnoughAssets(evmChain, true); testUtils.mockGetGasRequired(network, 77000n); // mock PaymentTransaction @@ -1571,19 +1792,23 @@ describe('EvmChain', () => { * when lock address does not have enough asset * @dependencies * @scenario + * - mock network functions + * - getFeeData + * - hasLockAddressEnoughAssets + * - getGasRequired * - mock invalid PaymentTransaction - * - mock getMaxFeePerGas, getMaxPriorityFeePerGas - * - mock hasLockAddressEnoughAssets * - run test * - check the function is not called * @expected * - it should not call the function */ it('should not submit the transaction when lock address does not have enough asset', async () => { - // mock getMaxFeePerGas, getMaxPriorityFeePerGas, and hasLockAddressEnoughAssets - testUtils.mockGetMaxFeePerGas(network, 20n); - testUtils.mockGetMaxPriorityFeePerGas(network, 7n); + // mock network functions + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); testUtils.mockHasLockAddressEnoughAssets(evmChain, false); + testUtils.mockGetGasRequired(network, 70000n); // mock PaymentTransaction const tx = Transaction.from(TestData.transaction1Json); @@ -1612,15 +1837,22 @@ describe('EvmChain', () => { * when checking failed due to error * @dependencies * @scenario + * - mock network functions + * - getFeeData + * - hasLockAddressEnoughAssets + * - getGasRequired to throw error * - mock invalid PaymentTransaction - * - mock getGasRequired to throw error * - run test * - check function is not called * @expected * - it should not call the function */ it('should not submit the transaction when checking failed due to error', async () => { - // mock getGasRequired + // mock network functions + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); + testUtils.mockGetFeeData(network, new FeeData(20n, 20n, 7n)); + testUtils.mockHasLockAddressEnoughAssets(evmChain, false); vi.spyOn(network, 'getGasRequired').mockImplementation(() => { throw Error(`test Error`); }); @@ -1649,6 +1881,9 @@ describe('EvmChain', () => { }); describe('verifyTransactionExtraConditions', () => { + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); + /** * @target EvmChain.verifyTransactionExtraConditions should return true * for erc-20 transfer when extra conditions are met and eventId is not empty @@ -1680,39 +1915,6 @@ describe('EvmChain', () => { expect(result).toEqual(true); }); - /** - * @target EvmChain.verifyTransactionExtraConditions should return false - * when transaction is not of type 2 - * @dependencies - * @scenario - * - mock valid PaymentTransaction - * - run test - * - check returned value - * @expected - * - it should return false - */ - it('should return false when transaction is not of type 2', async () => { - // mock PaymentTransaction - const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; - const txType = TransactionType.payment; - const tx = Transaction.from(TestData.erc20transaction as TransactionLike); - tx.type = 3; - - const paymentTx = new PaymentTransaction( - evmChain.CHAIN, - tx.unsignedHash, - eventId, - Serializer.serialize(tx), - txType - ); - - // run test - const result = evmChain.verifyTransactionExtraConditions(paymentTx); - - // check returned value - expect(result).toEqual(false); - }); - /** * @target EvmChain.verifyTransactionExtraConditions should return true * for erc-20 transfer when extra conditions are met and eventId is empty @@ -2070,6 +2272,8 @@ describe('EvmChain', () => { */ it('should return true when tx is not found and nonce is not used', async () => { // mock PaymentTransaction + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.erc20transaction as TransactionLike); @@ -2114,6 +2318,8 @@ describe('EvmChain', () => { */ it('should return false when nonce is already used by another transaction', async () => { // mock PaymentTransaction + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.erc20transaction as TransactionLike); @@ -2168,6 +2374,8 @@ describe('EvmChain', () => { */ it('should return true when nonce is used by current transaction', async () => { // mock PaymentTransaction + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.erc20transaction as TransactionLike); @@ -2217,6 +2425,8 @@ describe('EvmChain', () => { */ it('should return false when tx is failed', async () => { // mock PaymentTransaction + const network = new TestEvmNetwork(); + const evmChain = testUtils.generateChainObject(network); const eventId = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; const txType = TransactionType.payment; const tx = Transaction.from(TestData.erc20transaction as TransactionLike); @@ -2250,6 +2460,8 @@ describe('EvmChain', () => { }); describe('signTransaction', () => { + const network = new TestEvmNetwork(); + /** * @target EvmChain.signTransaction should return PaymentTransaction of the * signed transaction @@ -2360,7 +2572,8 @@ describe('EvmChain', () => { * - it should return mocked address assets (both input and output assets) */ it('should get address assets successfully', async () => { - mockGetAddressBalanceForNativeToken(evmChain.network, 1000n); + const network = new TestEvmNetwork(); + mockGetAddressBalanceForNativeToken(network, 1000n); vi.spyOn(network, 'getAddressBalanceForERC20Asset').mockImplementation( async (address, tokenId) => { if (tokenId === '0xedee4752e5a2f595151c94762fb38e5730357785') @@ -2376,6 +2589,7 @@ describe('EvmChain', () => { ); // run test + const evmChain = testUtils.generateChainObject(network); const result = await evmChain.getAddressAssets(TestData.lockAddress); // check returned value @@ -2401,6 +2615,7 @@ describe('EvmChain', () => { */ it('should return 0 balance with no token if address is empty', async () => { // run test + const evmChain = testUtils.generateChainObject(new TestEvmNetwork()); const result = await evmChain.getAddressAssets(''); // check returned value @@ -2419,6 +2634,7 @@ describe('EvmChain', () => { * - it should return mocked address assets (both input and output assets) */ it('should wrap address assets successfully', async () => { + const network = new TestEvmNetwork(); const evmChain = testUtils.generateChainObjectWithMultiDecimalTokenMap(network); @@ -2471,9 +2687,11 @@ describe('EvmChain', () => { const tx = Transaction.from(TestData.erc20transaction as TransactionLike); // mock getTransactionStatus + const network = new TestEvmNetwork(); testUtils.mockGetTransactionStatus(network, EvmTxStatus.succeed); // run test + const evmChain = testUtils.generateChainObject(network); const result = await evmChain.verifyLockTransactionExtraConditions( tx, {} as any @@ -2500,9 +2718,11 @@ describe('EvmChain', () => { const tx = Transaction.from(TestData.erc20transaction as TransactionLike); // mock getTransactionStatus + const network = new TestEvmNetwork(); testUtils.mockGetTransactionStatus(network, EvmTxStatus.failed); // run test + const evmChain = testUtils.generateChainObject(network); const result = await evmChain.verifyLockTransactionExtraConditions( tx, {} as any diff --git a/packages/chains/evm/tests/TestChain.ts b/packages/chains/evm/tests/TestChain.ts index 414d368..14d8b32 100644 --- a/packages/chains/evm/tests/TestChain.ts +++ b/packages/chains/evm/tests/TestChain.ts @@ -11,7 +11,8 @@ class TestChain extends EvmChain { configs: any, tokens: RosenTokens, supportedTokens: Array, - signFunction: TssSignFunction + signFunction: TssSignFunction, + evmTxType: number ) { super( network, @@ -20,7 +21,8 @@ class TestChain extends EvmChain { supportedTokens, signFunction, 'test', - 'test-native-token' + 'test-native-token', + evmTxType ); } } diff --git a/packages/chains/evm/tests/TestUtils.ts b/packages/chains/evm/tests/TestUtils.ts index 067c37e..8d81534 100644 --- a/packages/chains/evm/tests/TestUtils.ts +++ b/packages/chains/evm/tests/TestUtils.ts @@ -10,6 +10,7 @@ import { vi } from 'vitest'; import { AbstractEvmNetwork } from '../lib'; import TestEvmNetwork from './network/TestEvmNetwork'; import TestChain from './TestChain'; +import { FeeData } from 'ethers'; const spyOn = vi.spyOn; const observationTxConfirmation = 5; @@ -62,11 +63,8 @@ export const mockGetAddressBalanceForNativeToken = ( spyOn(network, 'getAddressBalanceForNativeToken').mockResolvedValue(value); }; -export const mockGetMaxFeePerGas = ( - network: AbstractEvmNetwork, - value: bigint -) => { - spyOn(network, 'getMaxFeePerGas').mockResolvedValue(value); +export const mockGetFeeData = (network: AbstractEvmNetwork, value: FeeData) => { + spyOn(network, 'getFeeData').mockResolvedValue(value); }; export const mockGetGasRequired = ( @@ -90,36 +88,33 @@ export const mockGetTransactionByNonce = ( spyOn(network, 'getTransactionByNonce').mockResolvedValue(value); }; -export const mockGetMaxPriorityFeePerGas = ( - network: AbstractEvmNetwork, - value: bigint -) => { - spyOn(network, 'getMaxPriorityFeePerGas').mockResolvedValue(value); -}; - export const generateChainObject = ( network: TestEvmNetwork, - signFn: TssSignFunction = mockedSignFn + signFn: TssSignFunction = mockedSignFn, + evmTxType = 2 ) => { return new TestChain( network, configs, testData.testTokenMap, testData.supportedTokens, - signFn + signFn, + evmTxType ); }; export const generateChainObjectWithMultiDecimalTokenMap = ( network: TestEvmNetwork, - signFn: TssSignFunction = mockedSignFn + signFn: TssSignFunction = mockedSignFn, + evmTxType = 2 ) => { return new TestChain( network, configs, testData.multiDecimalTokenMap, testData.supportedTokens, - signFn + signFn, + evmTxType ); }; diff --git a/packages/chains/evm/tests/network/TestEvmNetwork.ts b/packages/chains/evm/tests/network/TestEvmNetwork.ts index 763dd3d..54d2fed 100644 --- a/packages/chains/evm/tests/network/TestEvmNetwork.ts +++ b/packages/chains/evm/tests/network/TestEvmNetwork.ts @@ -1,4 +1,4 @@ -import { Transaction } from 'ethers'; +import { FeeData, Transaction } from 'ethers'; import { AbstractEvmNetwork, EvmTxStatus, TransactionHashes } from '../../lib'; import { BlockInfo, @@ -66,11 +66,7 @@ class TestEvmNetwork extends AbstractEvmNetwork { throw Error('Not mocked'); }; - getMaxPriorityFeePerGas = (): Promise => { - throw Error('Not mocked'); - }; - - getMaxFeePerGas = (): Promise => { + getFeeData = (): Promise => { throw Error('Not mocked'); }; diff --git a/packages/chains/evm/tests/testData.ts b/packages/chains/evm/tests/testData.ts index c6d3d47..af7a3bd 100644 --- a/packages/chains/evm/tests/testData.ts +++ b/packages/chains/evm/tests/testData.ts @@ -92,6 +92,18 @@ export const transaction1Json = { accessList: [], }; +export const transaction1WithType0Json = { + type: 0, + to: '0xeDee4752e5a2F595151c94762fB38e5730357785', + data: '0xa9059cbb0000000000000000000000004f0d2dde80b45e24ad4019a5aabd6c23aff2842b00000000000000000000000000000000000000000000000000000000e319aa30bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + nonce: 10, + gasLimit: '21000', + gasPrice: '48978500000', + value: 0, + chainId: 1, + sig: null, +}; + export const erc20transaction = { type: 2, to: '0xeDee4752e5a2F595151c94762fB38e5730357785', diff --git a/packages/networks/evm-rpc/lib/EvmRpcNetwork.ts b/packages/networks/evm-rpc/lib/EvmRpcNetwork.ts index 5a94517..3347141 100644 --- a/packages/networks/evm-rpc/lib/EvmRpcNetwork.ts +++ b/packages/networks/evm-rpc/lib/EvmRpcNetwork.ts @@ -370,6 +370,29 @@ class EvmRpcNetwork extends AbstractEvmNetwork { return feeData.maxFeePerGas; }; + /** + * gets fee-related values associated with the network + * - the legacy gas price + * - the maximum fee to pay per gas + * - the additional amount to pay per gas to miner + * it includes all the values + * @returns fee-related values as bigint or null + */ + getFeeData = async (): Promise => { + const baseError = `Failed to get fee data from ${this.chain} RPC: `; + try { + const feeData = await this.provider.getFeeData(); + this.logger.debug( + `requested 'getFeeData' of ${ + this.chain + } RPC. res: ${JsonBigInt.stringify(feeData)}` + ); + return feeData; + } catch (e: unknown) { + throw new UnexpectedApiError(baseError + `${e}`); + } + }; + /** * gets the transaction status (mempool, succeed, failed) * Note: this function considers the hash as unsigned hash diff --git a/packages/networks/evm-rpc/tests/EvmRpcNetwork.spec.ts b/packages/networks/evm-rpc/tests/EvmRpcNetwork.spec.ts index a3175d7..dcc5e32 100644 --- a/packages/networks/evm-rpc/tests/EvmRpcNetwork.spec.ts +++ b/packages/networks/evm-rpc/tests/EvmRpcNetwork.spec.ts @@ -457,9 +457,9 @@ describe('EvmRpcNetwork', () => { }); }); - describe('getMaxPriorityFeePerGas', () => { + describe('getFeeData', () => { /** - * @target `EvmRpcNetwork.getMaxPriorityFeePerGas` should return max priority fee per gas successfully + * @target `EvmRpcNetwork.getFeeData` should return fee data successfully * @dependencies * @scenario * - mock provider.`getFeeData` to return address tx count @@ -468,36 +468,14 @@ describe('EvmRpcNetwork', () => { * @expected * - it should be mocked nonce */ - it('should return max priority fee per gas successfully', async () => { + it('should return fee data successfully', async () => { vi.spyOn(network.getProvider(), 'getFeeData').mockResolvedValue( testData.feeDataResponse ); - const result = await network.getMaxPriorityFeePerGas(); + const result = await network.getFeeData(); - expect(result).toEqual(testData.maxPriorityFeePerGas); - }); - }); - - describe('getMaxFeePerGas', () => { - /** - * @target `EvmRpcNetwork.getMaxFeePerGas` should return max fee per gas successfully - * @dependencies - * @scenario - * - mock provider.`getFeeData` to return address tx count - * - run test - * - check returned value - * @expected - * - it should be mocked nonce - */ - it('should return max fee per gas successfully', async () => { - vi.spyOn(network.getProvider(), 'getFeeData').mockResolvedValue( - testData.feeDataResponse - ); - - const result = await network.getMaxFeePerGas(); - - expect(result).toEqual(testData.maxFeePerGas); + expect(result).toEqual(testData.feeDataResponse); }); });