diff --git a/src/utils/validateAvaxBurnedAmountEtna.test.ts b/src/utils/validateAvaxBurnedAmountEtna.test.ts index 4909c62cc..dc202ee4e 100644 --- a/src/utils/validateAvaxBurnedAmountEtna.test.ts +++ b/src/utils/validateAvaxBurnedAmountEtna.test.ts @@ -24,14 +24,10 @@ import { } from '../vms/pvm'; import { TransferableOutput } from '../serializable'; import { nodeId } from '../fixtures/common'; -import { feeState as testFeeState } from '../fixtures/pvm'; import { testSubnetId } from '../fixtures/transactions'; import { blsPublicKeyBytes, blsSignatureBytes } from '../fixtures/primitives'; import { validateAvaxBurnedAmountEtna } from './validateAvaxBurnedAmountEtna'; -const incorrectBurnedAmount = 1n; -const correctBurnedAmount = 1000000n; - const utxoMock = new Utxo( utxoId(), Id.fromString(testContext.avaxAssetID), @@ -91,14 +87,12 @@ describe('validateAvaxBurnedAmountEtna', () => { try { validateAvaxBurnedAmountEtna({ unsignedTx, - context: testContext, - burnedAmount: correctBurnedAmount, - feeState: testFeeState(), + burnedAmount: 1000000n, + baseFee: 1n, + feeTolerance: 20, }); } catch (error) { - expect((error as Error).message).toEqual( - 'Unsupported transaction type.', - ); + expect((error as Error).message).toEqual('tx type is not supported'); } }); }); @@ -113,6 +107,8 @@ describe('validateAvaxBurnedAmountEtna', () => { [utxoMock], [outputMock], ), + baseFee: 3830000n, + burnedAmount: 3840000n, }, { name: 'export from P', @@ -123,6 +119,8 @@ describe('validateAvaxBurnedAmountEtna', () => { [utxoMock], [outputMock], ), + baseFee: 4190000n, + burnedAmount: 4390000n, }, { name: 'import to P', @@ -133,6 +131,8 @@ describe('validateAvaxBurnedAmountEtna', () => { [testAddress2], [testAddress1], ), + baseFee: 3380000n, + burnedAmount: 3980000n, }, { name: 'create subnet', @@ -142,6 +142,8 @@ describe('validateAvaxBurnedAmountEtna', () => { [testAddress1], [testAddress1], ), + baseFee: 3430000n, + burnedAmount: 3930000n, }, { name: 'create blockchain', @@ -156,6 +158,8 @@ describe('validateAvaxBurnedAmountEtna', () => { {}, [0], ), + baseFee: 5340000n, + burnedAmount: 5660000n, }, { name: 'add subnet validator', @@ -170,6 +174,8 @@ describe('validateAvaxBurnedAmountEtna', () => { 'subnet', [0], ), + baseFee: 4660000n, + burnedAmount: 4960000n, }, { name: 'remove subnet validator', @@ -181,6 +187,8 @@ describe('validateAvaxBurnedAmountEtna', () => { Id.fromHex(testSubnetId).toString(), [0], ), + baseFee: 4420000n, + burnedAmount: 4420000n, }, { name: 'add permissionless validator (subnet)', @@ -202,6 +210,8 @@ describe('validateAvaxBurnedAmountEtna', () => { blsPublicKeyBytes(), blsSignatureBytes(), ), + baseFee: 6570000n, + burnedAmount: 7570000n, }, { name: 'add permissionless delegator (subnet)', @@ -219,6 +229,8 @@ describe('validateAvaxBurnedAmountEtna', () => { 1, 0n, ), + baseFee: 4850000n, + burnedAmount: 4900000n, }, { name: 'transfer subnet ownership', @@ -230,35 +242,37 @@ describe('validateAvaxBurnedAmountEtna', () => { [0, 2], [testAddress2], ), + baseFee: 5300000n, + burnedAmount: 5900000n, }, ]; - describe.each(testData)('$name', ({ unsignedTx }) => { + describe.each(testData)('$name', ({ unsignedTx, baseFee, burnedAmount }) => { it('returns true if burned amount is correct', () => { const result = validateAvaxBurnedAmountEtna({ unsignedTx, - context: testContext, - burnedAmount: correctBurnedAmount, - feeState: testFeeState(), + burnedAmount, + baseFee, + feeTolerance: 20, }); expect(result).toStrictEqual({ isValid: true, - txFee: correctBurnedAmount, + txFee: burnedAmount, }); }); it('returns false if burned amount is not correct', () => { const result = validateAvaxBurnedAmountEtna({ unsignedTx, - context: testContext, - burnedAmount: incorrectBurnedAmount, - feeState: { ...testFeeState(), price: 10_000n }, + burnedAmount: burnedAmount * 30n, + feeTolerance: 20, + baseFee, }); expect(result).toStrictEqual({ isValid: false, - txFee: incorrectBurnedAmount, + txFee: burnedAmount * 30n, }); }); }); diff --git a/src/utils/validateAvaxBurnedAmountEtna.ts b/src/utils/validateAvaxBurnedAmountEtna.ts index e2dff6bac..6ed017c83 100644 --- a/src/utils/validateAvaxBurnedAmountEtna.ts +++ b/src/utils/validateAvaxBurnedAmountEtna.ts @@ -1,4 +1,3 @@ -import type { Context } from '../vms/context/model'; import { isAddPermissionlessDelegatorTx, isAddPermissionlessValidatorTx, @@ -12,29 +11,28 @@ import { isTransferSubnetOwnershipTx, } from '../serializable/pvm'; import type { UnsignedTx } from '../vms/common'; -import type { FeeState } from '../vms/pvm'; -import { calculateFee } from '../vms/pvm/txs/fee/calculator'; export const validateAvaxBurnedAmountEtna = ({ unsignedTx, - context, burnedAmount, - feeState, + baseFee, + feeTolerance, }: { unsignedTx: UnsignedTx; - context: Context; burnedAmount: bigint; - feeState: FeeState; + baseFee: bigint; // pvm dynamic fee caculator: @see https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/txs/fee/dynamic_calculator.go + feeTolerance: number; // tolerance percentage range where the burned amount is considered valid. e.g.: with FeeTolerance = 20% -> (expectedFee <= burnedAmount <= expectedFee * 1.2) }): { isValid: boolean; txFee: bigint } => { const tx = unsignedTx.getTx(); - const expectedFee = calculateFee( - unsignedTx.getTx(), - context.platformFeeConfig.weights, - feeState.price < context.platformFeeConfig.minPrice - ? context.platformFeeConfig.minPrice - : feeState.price, - ); + const feeToleranceInt = Math.floor(feeTolerance); + + if (feeToleranceInt < 1 || feeToleranceInt > 100) { + throw new Error('feeTolerance must be [1,100]'); + } + + const min = baseFee; + const max = (baseFee * (100n + BigInt(feeToleranceInt))) / 100n; if ( isPvmBaseTx(tx) || @@ -49,7 +47,7 @@ export const validateAvaxBurnedAmountEtna = ({ isTransferSubnetOwnershipTx(tx) ) { return { - isValid: burnedAmount >= expectedFee, + isValid: burnedAmount >= min && burnedAmount <= max, txFee: burnedAmount, }; } diff --git a/src/utils/validateBurnedAmount.ts b/src/utils/validateBurnedAmount.ts index 563412b5e..c0f9e8321 100644 --- a/src/utils/validateBurnedAmount.ts +++ b/src/utils/validateBurnedAmount.ts @@ -4,7 +4,6 @@ import type { EVMTx } from '../serializable/evm'; import { isImportExportTx as isEvmImportExportTx } from '../serializable/evm'; import { getBurnedAmountByTx } from './getBurnedAmountByTx'; import type { AvaxTx } from '../serializable/avax'; -import type { FeeState } from '../vms/pvm'; import { validateEvmBurnedAmount } from './validateEvmBurnedAmount'; import type { GetUpgradesInfoResponse } from '../info/model'; import { isEtnaEnabled } from './isEtnaEnabled'; @@ -39,22 +38,25 @@ const isPreEtnaTx = (tx: Transaction) => { ); }; +/** + * baseFee: + * - evm fee: fetched from the network and converted into nAvax (https://docs.avax.network/quickstart/transaction-fees#c-chain-fees) + * - pvm dynamic fee caculator: @see https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/txs/fee/dynamic_calculator.go + */ export const validateBurnedAmount = ({ unsignedTx, context, - feeState, upgradesInfo, burnedAmount, - evmBaseFee, - evmFeeTolerance, + baseFee, + feeTolerance, }: { unsignedTx: UnsignedTx; context: Context; - feeState: FeeState; upgradesInfo: GetUpgradesInfoResponse; burnedAmount?: bigint; - evmBaseFee?: bigint; // fetched from the network and converted into nAvax (https://docs.avax.network/quickstart/transaction-fees#c-chain-fees) - evmFeeTolerance?: number; // tolerance percentage range where the burned amount is considered valid. e.g.: with evmFeeTolerance = 20% -> (evmBaseFee * 0.8 <= burnedAmount <= evmBaseFee * 1.2) + baseFee: bigint; + feeTolerance: number; // tolerance percentage range where the burned amount is considered valid. }): { isValid: boolean; txFee: bigint } => { const tx = unsignedTx.getTx(); const burned = burnedAmount ?? _getBurnedAmount(tx, context); @@ -63,16 +65,16 @@ export const validateBurnedAmount = ({ return validateEvmBurnedAmount({ unsignedTx, burnedAmount: burned, - evmBaseFee, - evmFeeTolerance, + baseFee, + feeTolerance, }); } if (isEtnaEnabled(upgradesInfo) || !isPreEtnaTx(tx)) { return validateAvaxBurnedAmountEtna({ unsignedTx, - context, + baseFee, burnedAmount: burned, - feeState, + feeTolerance, }); } return validateAvaxBurnedAmountPreEtna({ diff --git a/src/utils/validateEvmBurnedAmount.test.ts b/src/utils/validateEvmBurnedAmount.test.ts index 644ce6b54..7ad15e538 100644 --- a/src/utils/validateEvmBurnedAmount.test.ts +++ b/src/utils/validateEvmBurnedAmount.test.ts @@ -30,39 +30,39 @@ describe('validateEvmBurnedAmount', () => { [testAddress1], 1n, ); - it('throws if evmFeeTolerance is incorrect', () => { + it('throws if feeTolerance is incorrect', () => { expect(() => validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 75n) / 100n, // 25% lower, - evmBaseFee: 25n, - evmFeeTolerance: 0.5, + baseFee: 25n, + feeTolerance: 0.5, }), - ).toThrowError('evmFeeTolerance must be [1,100]'); + ).toThrowError('feeTolerance must be [1,100]'); expect(() => validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 75n) / 100n, // 25% lower, - evmBaseFee: 25n, - evmFeeTolerance: 101, + baseFee: 25n, + feeTolerance: 101, }), - ).toThrowError('evmFeeTolerance must be [1,100]'); + ).toThrowError('feeTolerance must be [1,100]'); }); it('returns true if burned amount is in the tolerance range', () => { const resultLower = validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 75n) / 100n, // 25% lower - evmBaseFee: 25n, - evmFeeTolerance: 50.9, + baseFee: 25n, + feeTolerance: 50.9, }); const resultHigher = validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 125n) / 100n, // 25% higher - evmBaseFee: 25n, - evmFeeTolerance: 50.9, + baseFee: 25n, + feeTolerance: 50.9, }); expect(resultLower).toStrictEqual({ @@ -79,15 +79,15 @@ describe('validateEvmBurnedAmount', () => { const resultLower = validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 49n) / 100n, // 51% lower - evmBaseFee: 25n, - evmFeeTolerance: 50.9, + baseFee: 25n, + feeTolerance: 50.9, }); const resultHigher = validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 151n) / 100n, // 51% higher - evmBaseFee: 25n, - evmFeeTolerance: 50.9, + baseFee: 25n, + feeTolerance: 50.9, }); expect(resultLower).toStrictEqual({ @@ -115,15 +115,15 @@ describe('validateEvmBurnedAmount', () => { const resultLower = validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 75n) / 100n, // 25% lower - evmBaseFee: 25n, - evmFeeTolerance: 50.9, + baseFee: 25n, + feeTolerance: 50.9, }); const resultHigher = validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 125n) / 100n, // 25% higher - evmBaseFee: 25n, - evmFeeTolerance: 50.9, + baseFee: 25n, + feeTolerance: 50.9, }); expect(resultLower).toStrictEqual({ @@ -140,15 +140,15 @@ describe('validateEvmBurnedAmount', () => { const resultLower = validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 49n) / 100n, // 51% lower - evmBaseFee: 25n, - evmFeeTolerance: 50.9, + baseFee: 25n, + feeTolerance: 50.9, }); const resultHigher = validateEvmBurnedAmount({ unsignedTx, burnedAmount: (280750n * 151n) / 100n, // 51% higher - evmBaseFee: 25n, - evmFeeTolerance: 50.9, + baseFee: 25n, + feeTolerance: 50.9, }); expect(resultLower).toStrictEqual({ diff --git a/src/utils/validateEvmBurnedAmount.ts b/src/utils/validateEvmBurnedAmount.ts index b206708c5..ec443f8ff 100644 --- a/src/utils/validateEvmBurnedAmount.ts +++ b/src/utils/validateEvmBurnedAmount.ts @@ -5,30 +5,27 @@ import { costCorethTx } from './costs'; export const validateEvmBurnedAmount = ({ unsignedTx, burnedAmount, - evmBaseFee, - evmFeeTolerance, + baseFee, + feeTolerance, }: { unsignedTx: UnsignedTx; burnedAmount: bigint; - evmBaseFee?: bigint; // fetched from the network and converted into nAvax (https://docs.avax.network/quickstart/transaction-fees#c-chain-fees) - evmFeeTolerance?: number; // tolerance percentage range where the burned amount is considered valid. e.g.: with evmFeeTolerance = 20% -> (evmBaseFee * 0.8 <= burnedAmount <= evmBaseFee * 1.2) + baseFee: bigint; // fetched from the network and converted into nAvax (https://docs.avax.network/quickstart/transaction-fees#c-chain-fees) + feeTolerance: number; // tolerance percentage range where the burned amount is considered valid. e.g.: with feeTolerance = 20% -> (baseFee * 0.8 <= burnedAmount <= baseFee * 1.2) }): { isValid: boolean; txFee: bigint } => { const tx = unsignedTx.getTx(); if (!isEvmImportExportTx(tx)) { throw new Error(`tx type is not supported`); } - if (!evmBaseFee || !evmFeeTolerance) { - throw new Error('missing evm fee data'); - } - const feeToleranceInt = Math.floor(evmFeeTolerance); + const feeToleranceInt = Math.floor(feeTolerance); if (feeToleranceInt < 1 || feeToleranceInt > 100) { - throw new Error('evmFeeTolerance must be [1,100]'); + throw new Error('feeTolerance must be [1,100]'); } - const feeAmount = evmBaseFee * costCorethTx(unsignedTx); + const feeAmount = baseFee * costCorethTx(unsignedTx); const min = (feeAmount * (100n - BigInt(feeToleranceInt))) / 100n; const max = (feeAmount * (100n + BigInt(feeToleranceInt))) / 100n;