From 47f216ec385ae7de5136d588c42a997d2aaba6d7 Mon Sep 17 00:00:00 2001 From: Eric Taylor Date: Tue, 8 Oct 2024 09:10:47 -0600 Subject: [PATCH] feat: add platform.getFeeState and update etna builder (#879) --- examples/p-chain/etna/base.ts | 6 +- examples/p-chain/etna/delegate.ts | 6 +- examples/p-chain/etna/export.ts | 2 + examples/p-chain/etna/import.ts | 2 + examples/p-chain/etna/utils/etna-context.ts | 24 +++++- examples/p-chain/etna/validate.ts | 6 +- src/fixtures/context.ts | 14 +++ src/fixtures/feeConfig.ts | 16 ---- src/fixtures/pvm.ts | 8 ++ src/vms/context/context.ts | 5 ++ src/vms/context/model.ts | 5 ++ src/vms/pvm/api.ts | 13 +++ src/vms/pvm/etna-builder/builder.test.ts | 86 ++++++++++--------- src/vms/pvm/etna-builder/builder.ts | 51 +++++------ .../spend-reducers/fixtures/reducers.ts | 14 ++- .../pvm/etna-builder/spend-reducers/types.ts | 2 +- src/vms/pvm/etna-builder/spend.test.ts | 31 ++----- src/vms/pvm/etna-builder/spend.ts | 15 ++-- src/vms/pvm/etna-builder/spendHelper.test.ts | 8 +- src/vms/pvm/etna-builder/spendHelper.ts | 11 +-- src/vms/pvm/models.ts | 24 +++++- src/vms/pvm/txs/fee/calculator.test.ts | 17 ++-- src/vms/pvm/txs/fee/calculator.ts | 11 ++- 23 files changed, 224 insertions(+), 153 deletions(-) delete mode 100644 src/fixtures/feeConfig.ts diff --git a/examples/p-chain/etna/base.ts b/examples/p-chain/etna/base.ts index 78d40c0c5..9583acc2e 100644 --- a/examples/p-chain/etna/base.ts +++ b/examples/p-chain/etna/base.ts @@ -10,14 +10,16 @@ const SEND_AVAX_AMOUNT: number = 0.001; const main = async () => { const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars(); - const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL); - const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL); + const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL); + const feeState = await pvmApi.getFeeState(); + const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] }); const tx = pvm.e.newBaseTx( { + feeState, fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], outputs: [ TransferableOutput.fromNative( diff --git a/examples/p-chain/etna/delegate.ts b/examples/p-chain/etna/delegate.ts index 673a7319e..7748b3c11 100644 --- a/examples/p-chain/etna/delegate.ts +++ b/examples/p-chain/etna/delegate.ts @@ -8,10 +8,11 @@ const DAYS_TO_DELEGATE: number = 14; const main = async () => { const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars(); - const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL); - const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL); + const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL); + const feeState = await pvmApi.getFeeState(); + const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] }); const startTime = await pvmApi.getTimestamp(); @@ -28,6 +29,7 @@ const main = async () => { const tx = pvm.e.newAddPermissionlessDelegatorTx( { end, + feeState, fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], nodeId, rewardAddresses: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], diff --git a/examples/p-chain/etna/export.ts b/examples/p-chain/etna/export.ts index 233560361..d1b2163dd 100644 --- a/examples/p-chain/etna/export.ts +++ b/examples/p-chain/etna/export.ts @@ -11,6 +11,7 @@ const main = async () => { const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL); const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL); + const feeState = await pvmApi.getFeeState(); const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS], @@ -19,6 +20,7 @@ const main = async () => { const exportTx = pvm.e.newExportTx( { destinationChainId: context.xBlockchainID, + feeState, fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], outputs: [ TransferableOutput.fromNative( diff --git a/examples/p-chain/etna/import.ts b/examples/p-chain/etna/import.ts index 82bb6299c..724afb9b7 100644 --- a/examples/p-chain/etna/import.ts +++ b/examples/p-chain/etna/import.ts @@ -9,6 +9,7 @@ const main = async () => { const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL); const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL); + const feeState = await pvmApi.getFeeState(); const { utxos } = await pvmApi.getUTXOs({ sourceChain: 'X', @@ -17,6 +18,7 @@ const main = async () => { const importTx = pvm.e.newImportTx( { + feeState, fromAddressesBytes: [utils.bech32ToBytes(X_CHAIN_ADDRESS)], sourceChainId: context.xBlockchainID, toAddresses: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], diff --git a/examples/p-chain/etna/utils/etna-context.ts b/examples/p-chain/etna/utils/etna-context.ts index b25afa284..705cd3323 100644 --- a/examples/p-chain/etna/utils/etna-context.ts +++ b/examples/p-chain/etna/utils/etna-context.ts @@ -1,5 +1,4 @@ -import { Context } from '../../../../src'; - +import { Context, Info } from '../../../../src'; /** * Gets the context from URI and then modifies the context * to be used for testing example Etna transactions until Etna is enabled. @@ -9,5 +8,24 @@ export const getEtnaContextFromURI = async ( ): Promise => { const context = await Context.getContextFromURI(uri); - return context; + const info = new Info(uri); + + const { etnaTime } = await info.getUpgradesInfo(); + + const etnaDateTime = new Date(etnaTime); + const now = new Date(); + + if (etnaDateTime < now) { + return context; + } + + // If Etna is not enabled, we need to override the minPrice of 1n that is returned. + // This is because the minPrice of 1n is not enough to calculate a fee that exceeds the current static fees. + return { + ...context, + platformFeeConfig: { + ...context.platformFeeConfig, + minPrice: 10_000n, + }, + }; }; diff --git a/examples/p-chain/etna/validate.ts b/examples/p-chain/etna/validate.ts index 29b446487..f5a6ef7b9 100644 --- a/examples/p-chain/etna/validate.ts +++ b/examples/p-chain/etna/validate.ts @@ -11,10 +11,11 @@ const nodeId = getRandomNodeId(); const main = async () => { const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars(); - const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL); - const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL); + const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL); + const feeState = await pvmApi.getFeeState(); + const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] }); const startTime = await pvmApi.getTimestamp(); @@ -37,6 +38,7 @@ const main = async () => { { end, delegatorRewardsOwner: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], + feeState, fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)], nodeId, publicKey, diff --git a/src/fixtures/context.ts b/src/fixtures/context.ts index 2e6aff484..362805fb2 100644 --- a/src/fixtures/context.ts +++ b/src/fixtures/context.ts @@ -1,3 +1,4 @@ +import { createDimensions } from '../vms/common/fees/dimensions'; import type { Context } from '../vms/context'; export const testContext: Context = { @@ -16,4 +17,17 @@ export const testContext: Context = { addSubnetDelegatorFee: 1000000n, networkID: 1, hrp: 'avax', + platformFeeConfig: { + weights: createDimensions({ + bandwidth: 1, + dbRead: 1, + dbWrite: 1, + compute: 1, + }), + maxCapacity: 1_000_000n, + maxPerSecond: 1_000n, + targetPerSecond: 500n, + minPrice: 1n, + excessConversionConstant: 5_000n, + }, }; diff --git a/src/fixtures/feeConfig.ts b/src/fixtures/feeConfig.ts deleted file mode 100644 index f3fe5deda..000000000 --- a/src/fixtures/feeConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createDimensions } from '../vms/common/fees/dimensions'; -import type { FeeConfig } from '../vms/pvm'; - -export const testFeeConfig: FeeConfig = { - weights: createDimensions({ - bandwidth: 1, - dbRead: 1, - dbWrite: 1, - compute: 1, - }), - maxCapacity: 1000000n, // Maximum amount of gas the chain is allowed to consume. - maxPerSecond: 1000n, // Maximum amount of gas the chain is allowed to consume per second. - targetPerSecond: 500n, // Target amount of gas the chain should consume per second to keep the fees stable. - minPrice: 1n, // Minimum gas price. - excessConversionConstant: 5000n, // Excess conversion constant. -}; diff --git a/src/fixtures/pvm.ts b/src/fixtures/pvm.ts index 54ce32b31..a38360414 100644 --- a/src/fixtures/pvm.ts +++ b/src/fixtures/pvm.ts @@ -55,6 +55,7 @@ import { } from './secp256k1'; import { bytesForInt } from './utils/bytesFor'; import { makeList, makeListBytes } from './utils/makeList'; +import type { FeeState } from '../vms/pvm'; export const validator = () => new Validator(nodeId(), bigIntPr(), bigIntPr(), bigIntPr()); @@ -290,3 +291,10 @@ export const transformSubnetTxBytes = () => bytesForInt(10), inputBytes(), ); + +export const feeState = (): FeeState => ({ + capacity: 1n, + excess: 1n, + price: 1n, + timestamp: new Date().toISOString(), +}); diff --git a/src/vms/context/context.ts b/src/vms/context/context.ts index 9f999b5b9..9e36416cc 100644 --- a/src/vms/context/context.ts +++ b/src/vms/context/context.ts @@ -1,6 +1,7 @@ import { getHRP } from '../../constants/networkIDs'; import { Info } from '../../info/info'; import { AVMApi } from '../avm/api'; +import { PVMApi } from '../pvm'; import type { Context } from './model'; /* @@ -10,6 +11,7 @@ export const getContextFromURI = async ( baseURL?: string, assetDescription = 'AVAX', ): Promise => { + const pChainApi = new PVMApi(baseURL); const xChainApi = new AVMApi(baseURL); const { assetID: avaxAssetID } = await xChainApi.getAssetDescription( assetDescription, @@ -33,6 +35,8 @@ export const getContextFromURI = async ( const { networkID: networkIDstring } = await info.getNetworkId(); const networkID = Number(networkIDstring); + const platformFeeConfig = await pChainApi.getFeeConfig(); + return Object.freeze({ xBlockchainID, pBlockchainID, @@ -49,5 +53,6 @@ export const getContextFromURI = async ( addSubnetDelegatorFee, networkID, hrp: getHRP(networkID), + platformFeeConfig, }); }; diff --git a/src/vms/context/model.ts b/src/vms/context/model.ts index 1280a8a65..bb807ff7a 100644 --- a/src/vms/context/model.ts +++ b/src/vms/context/model.ts @@ -1,3 +1,5 @@ +import type { FeeConfig } from '../pvm'; + export type Context = { readonly networkID: number; readonly hrp: string; @@ -14,4 +16,7 @@ export type Context = { readonly addPrimaryNetworkDelegatorFee: bigint; readonly addSubnetValidatorFee: bigint; readonly addSubnetDelegatorFee: bigint; + + // Post Etna + readonly platformFeeConfig: FeeConfig; }; diff --git a/src/vms/pvm/api.ts b/src/vms/pvm/api.ts index 10d3b070d..a93aa5201 100644 --- a/src/vms/pvm/api.ts +++ b/src/vms/pvm/api.ts @@ -8,6 +8,8 @@ import { createDimensions } from '../common/fees/dimensions'; import type { FeeConfig, FeeConfigResponse, + FeeState, + FeeStateResponse, GetBalanceParams, GetBalanceResponse, GetBlockchainsResponse, @@ -247,4 +249,15 @@ export class PVMApi extends AvaxApi { excessConversionConstant: BigInt(excessConversionConstant), }; } + + async getFeeState(): Promise { + const resp = await this.callRpc('getFeeState'); + + return { + capacity: BigInt(resp.capacity), + excess: BigInt(resp.excess), + price: BigInt(resp.price), + timestamp: resp.timestamp, + }; + } } diff --git a/src/vms/pvm/etna-builder/builder.test.ts b/src/vms/pvm/etna-builder/builder.test.ts index 9a69332e2..54b296773 100644 --- a/src/vms/pvm/etna-builder/builder.test.ts +++ b/src/vms/pvm/etna-builder/builder.test.ts @@ -62,9 +62,11 @@ import { blsPublicKeyBytes, blsSignatureBytes, } from '../../../fixtures/primitives'; -import { proofOfPossession } from '../../../fixtures/pvm'; -import type { FeeConfig } from '../models'; -import { testFeeConfig } from '../../../fixtures/feeConfig'; +import { + feeState as testFeeState, + proofOfPossession, +} from '../../../fixtures/pvm'; +import type { FeeState } from '../models'; const addTransferableAmounts = ( transferableItems: @@ -117,14 +119,14 @@ const checkFeeIsCorrect = ({ unsignedTx, inputs, outputs, - feeConfig, + feeState, additionalInputs = [], additionalOutputs = [], }: { unsignedTx: UnsignedTx; inputs: readonly TransferableInput[]; outputs: readonly TransferableOutput[]; - feeConfig: FeeConfig; + feeState: FeeState; additionalInputs?: readonly TransferableInput[]; additionalOutputs?: readonly TransferableOutput[]; }): [ @@ -141,7 +143,13 @@ const checkFeeIsCorrect = ({ ...additionalOutputs, ]); - const expectedFee = calculateFee(unsignedTx.getTx(), feeConfig); + const expectedFee = calculateFee( + unsignedTx.getTx(), + testContext.platformFeeConfig.weights, + feeState.price < testContext.platformFeeConfig.minPrice + ? testContext.platformFeeConfig.minPrice + : feeState.price, + ); const expectedAmountBurned = addAmounts( new Map([[testAvaxAssetID.toString(), expectedFee]]), @@ -170,6 +178,7 @@ const checkFeeIsCorrect = ({ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const nodeId = 'NodeID-2m38qc95mhHXtrhjyGbe7r2NhniqHHJRB'; const toAddress = hexToBuffer('0x5432112345123451234512'); + const feeState = testFeeState(); const fromAddressesBytes = [testOwnerXAddress.toBytes()]; const getRewardsOwners = () => OutputOwners.fromNative([toAddress]); @@ -177,14 +186,12 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { { name: 'no memo', memo: undefined, - feeConfig: testFeeConfig, }, { name: 'with memo', memo: Buffer.from('memo'), - feeConfig: testFeeConfig, }, - ])('$name', ({ memo, feeConfig }) => { + ])('$name', ({ memo }) => { test('newBaseTx', () => { const utxos = testUtxos(); @@ -197,12 +204,12 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const utx = newBaseTx( { fromAddressesBytes, + feeState, outputs: [transferableOutput], options: { memo, }, utxos, - feeConfig, }, testContext, ); @@ -221,7 +228,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { unsignedTx: utx, inputs, outputs, - feeConfig, + feeState, }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -238,13 +245,13 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newImportTx( { fromAddressesBytes, + feeState, options: { memo, }, sourceChainId: testContext.cBlockchainID, toAddresses: [testAddress1], utxos, - feeConfig, }, testContext, ); @@ -260,7 +267,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalInputs: importedIns, - feeConfig, + feeState, }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -303,13 +310,13 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newExportTx( { destinationChainId: testContext.cBlockchainID, + feeState, fromAddressesBytes, options: { memo, }, outputs: [tnsOut], utxos, - feeConfig, }, testContext, ); @@ -325,7 +332,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalOutputs: exportedOuts, - feeConfig, + feeState, }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -357,12 +364,12 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newCreateSubnetTx( { fromAddressesBytes, + feeState, options: { memo, }, subnetOwners: [toAddress], utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], - feeConfig, }, testContext, ); @@ -373,7 +380,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { expect(txMemo.toString()).toEqual(memo ? 'memo' : ''); const [amountConsumed, expectedAmountConsumed, expectedFee] = - checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeConfig }); + checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -397,6 +404,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newCreateChainTx( { chainName: 'Random Chain Name', + feeState, fromAddressesBytes, fxIds: [], genesisData: testGenesisData, @@ -407,7 +415,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { subnetId: Id.fromHex(testSubnetId).toString(), utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], vmId: Id.fromHex(testVMId).toString(), - feeConfig, }, testContext, ); @@ -418,7 +425,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { expect(txMemo.toString()).toEqual(memo ? 'memo' : ''); const [amountConsumed, expectedAmountConsumed, expectedFee] = - checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeConfig }); + checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -447,6 +454,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newAddSubnetValidatorTx( { end: 190_000_000n, + feeState, fromAddressesBytes, nodeId, options: { @@ -457,7 +465,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { start: 100n, utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], weight: 1_800_000n, - feeConfig, }, testContext, ); @@ -468,7 +475,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { expect(txMemo.toString()).toEqual(memo ? 'memo' : ''); const [amountConsumed, expectedAmountConsumed, expectedFee] = - checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeConfig }); + checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -499,6 +506,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newRemoveSubnetValidatorTx( { fromAddressesBytes, + feeState, nodeId, options: { memo, @@ -506,7 +514,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { subnetAuth: [0], subnetId: Id.fromHex(testSubnetId).toString(), utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], - feeConfig, }, testContext, ); @@ -517,7 +524,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { expect(txMemo.toString()).toEqual(memo ? 'memo' : ''); const [amountConsumed, expectedAmountConsumed, expectedFee] = - checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeConfig }); + checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -545,6 +552,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { { delegatorRewardsOwner: [], end: 120n, + feeState, fromAddressesBytes, nodeId, options: { @@ -558,7 +566,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { subnetId: PrimaryNetworkID.toString(), utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], weight: stakeAmount, - feeConfig, }, testContext, ); @@ -575,7 +582,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalOutputs: stake, - feeConfig, + feeState, }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -613,6 +620,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { { delegatorRewardsOwner: [], end: 120n, + feeState, fromAddressesBytes, nodeId, options: { @@ -626,7 +634,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { subnetId: Id.fromHex(testSubnetId).toString(), utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], weight: stakeAmount, - feeConfig, }, testContext, ); @@ -643,7 +650,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalOutputs: stake, - feeConfig, + feeState, }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -682,6 +689,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { { delegatorRewardsOwner: [], end: 120n, + feeState, fromAddressesBytes, nodeId, options: { @@ -699,7 +707,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { getValidUtxo(new BigIntPr(2n * stakeAmount), stakingAssetId), ], weight: stakeAmount, - feeConfig, }, testContext, ); @@ -716,7 +723,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalOutputs: stake, - feeConfig, + feeState, }); expect(stake.length).toEqual(1); @@ -742,6 +749,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newAddPermissionlessDelegatorTx( { end: 120n, + feeState, fromAddressesBytes, nodeId, options: { @@ -752,7 +760,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { subnetId: PrimaryNetworkID.toString(), utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], weight: stakeAmount, - feeConfig, }, testContext, ); @@ -769,7 +776,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalOutputs: stake, - feeConfig, + feeState, }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -803,6 +810,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newAddPermissionlessDelegatorTx( { end: 120n, + feeState, fromAddressesBytes, nodeId, options: { @@ -813,7 +821,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { subnetId: Id.fromHex(testSubnetId).toString(), utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], weight: stakeAmount, - feeConfig, }, testContext, ); @@ -830,7 +837,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalOutputs: stake, - feeConfig, + feeState, }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -865,6 +872,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newAddPermissionlessDelegatorTx( { end: 120n, + feeState, fromAddressesBytes, nodeId, options: { @@ -879,7 +887,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { getValidUtxo(new BigIntPr(2n * stakeAmount), stakingAssetId), ], weight: stakeAmount, - feeConfig, }, testContext, ); @@ -898,7 +905,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalOutputs: stake, - feeConfig, + feeState, }); expect(stake.length).toEqual(1); @@ -924,6 +931,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newTransferSubnetOwnershipTx( { fromAddressesBytes, + feeState, options: { memo, }, @@ -931,7 +939,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { subnetId: Id.fromHex(testSubnetId).toString(), subnetOwners: [toAddress], utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))], - feeConfig, }, testContext, ); @@ -942,7 +949,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { expect(txMemo.toString()).toEqual(memo ? 'memo' : ''); const [amountConsumed, expectedAmountConsumed, expectedFee] = - checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeConfig }); + checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState }); expect(amountConsumed).toEqual(expectedAmountConsumed); @@ -965,7 +972,6 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { describe('ImportTx', () => { it('should create an ImportTx with only AVAX and not non-AVAX assets', () => { - const feeConfig = testFeeConfig; const utxos = [ getLockedUTXO(), // Locked and should be ignored. getNotTransferOutput(), // Invalid and should be ignored. @@ -982,10 +988,10 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { const unsignedTx = newImportTx( { fromAddressesBytes, + feeState, sourceChainId: testContext.cBlockchainID, toAddresses: [testAddress1], utxos, - feeConfig, }, testContext, ); @@ -999,7 +1005,7 @@ describe('./src/vms/pvm/etna-builder/builder.test.ts', () => { inputs, outputs, additionalInputs: importedIns, - feeConfig, + feeState, }); expect(amountConsumed).toEqual(expectedAmountConsumed); diff --git a/src/vms/pvm/etna-builder/builder.ts b/src/vms/pvm/etna-builder/builder.ts index 4f2c586eb..8eca44a7b 100644 --- a/src/vms/pvm/etna-builder/builder.ts +++ b/src/vms/pvm/etna-builder/builder.ts @@ -49,7 +49,7 @@ import { defaultSpendOptions } from '../../common/defaultSpendOptions'; import type { Dimensions } from '../../common/fees/dimensions'; import { addDimensions, createDimensions } from '../../common/fees/dimensions'; import type { Context } from '../../context'; -import type { FeeConfig } from '../models'; +import type { FeeState } from '../models'; import { INTRINSIC_ADD_PERMISSIONLESS_DELEGATOR_TX_COMPLEXITIES, INTRINSIC_ADD_PERMISSIONLESS_VALIDATOR_TX_COMPLEXITIES, @@ -104,6 +104,7 @@ const getMemoComplexity = ( * Common properties used in all PVM transaction builder functions. */ type CommonTxProps = Readonly<{ + feeState: FeeState; /** * List of addresses that are used for selecting which UTXOs are signable. */ @@ -113,7 +114,6 @@ type CommonTxProps = Readonly<{ * List of UTXOs that are available to be spent. */ utxos: readonly Utxo[]; - feeConfig: FeeConfig; }>; type TxProps> = CommonTxProps & Readonly; @@ -138,7 +138,7 @@ export type NewBaseTxProps = TxProps<{ * @returns {UnsignedTx} An UnsignedTx. */ export const newBaseTx: TxBuilderFn = ( - { fromAddressesBytes, options, outputs, utxos, feeConfig }, + { feeState, fromAddressesBytes, options, outputs, utxos }, context, ) => { const fromAddresses = addressesFromBytes(fromAddressesBytes); @@ -168,13 +168,13 @@ export const newBaseTx: TxBuilderFn = ( const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses, initialComplexity: complexity, shouldConsolidateOutputs: true, spendOptions: defaultedOptions, toBurn, utxos, - feeConfig, }, [useUnlockedUTXOs], context, @@ -229,6 +229,7 @@ export type NewImportTxProps = TxProps<{ */ export const newImportTx: TxBuilderFn = ( { + feeState, fromAddressesBytes, locktime, options, @@ -236,7 +237,6 @@ export const newImportTx: TxBuilderFn = ( threshold, toAddresses, utxos, - feeConfig, }, context, ) => { @@ -332,12 +332,12 @@ export const newImportTx: TxBuilderFn = ( const spendResults = spend( { excessAVAX: importedAvax, + feeState, fromAddresses, initialComplexity: complexity, ownerOverride: OutputOwners.fromNative(toAddresses, locktime, threshold), spendOptions: defaultedOptions, utxos, - feeConfig, }, [useUnlockedUTXOs], context, @@ -381,14 +381,7 @@ export type NewExportTxProps = TxProps<{ * @returns {UnsignedTx} An UnsignedTx. */ export const newExportTx: TxBuilderFn = ( - { - destinationChainId, - fromAddressesBytes, - options, - outputs, - utxos, - feeConfig, - }, + { destinationChainId, feeState, fromAddressesBytes, options, outputs, utxos }, context, ) => { const fromAddresses = addressesFromBytes(fromAddressesBytes); @@ -414,12 +407,12 @@ export const newExportTx: TxBuilderFn = ( const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses, initialComplexity: complexity, spendOptions: defaultedOptions, toBurn, utxos, - feeConfig, }, [useUnlockedUTXOs], context, @@ -472,12 +465,12 @@ export type NewCreateSubnetTxProps = TxProps<{ export const newCreateSubnetTx: TxBuilderFn = ( { fromAddressesBytes, + feeState, locktime, options, subnetOwners, threshold, utxos, - feeConfig, }, context, ) => { @@ -498,11 +491,11 @@ export const newCreateSubnetTx: TxBuilderFn = ( const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses: addressesFromBytes(fromAddressesBytes), initialComplexity: complexity, spendOptions: defaultedOptions, utxos, - feeConfig, }, [useUnlockedUTXOs], context, @@ -567,6 +560,7 @@ export type NewCreateChainTxProps = TxProps<{ export const newCreateChainTx: TxBuilderFn = ( { chainName, + feeState, fromAddressesBytes, fxIds, genesisData, @@ -575,7 +569,6 @@ export const newCreateChainTx: TxBuilderFn = ( subnetId, utxos, vmId, - feeConfig, }, context, ) => { @@ -609,11 +602,11 @@ export const newCreateChainTx: TxBuilderFn = ( const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses: addressesFromBytes(fromAddressesBytes), initialComplexity: complexity, spendOptions: defaultedOptions, utxos, - feeConfig, }, [useUnlockedUTXOs], context, @@ -674,6 +667,7 @@ export const newAddSubnetValidatorTx: TxBuilderFn< > = ( { end, + feeState, fromAddressesBytes, nodeId, options, @@ -682,7 +676,6 @@ export const newAddSubnetValidatorTx: TxBuilderFn< subnetId, utxos, weight, - feeConfig, }, context, ) => { @@ -701,11 +694,11 @@ export const newAddSubnetValidatorTx: TxBuilderFn< const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses: addressesFromBytes(fromAddressesBytes), initialComplexity: complexity, spendOptions: defaultedOptions, utxos, - feeConfig, }, [useUnlockedUTXOs], context, @@ -765,12 +758,12 @@ export const newRemoveSubnetValidatorTx: TxBuilderFn< > = ( { fromAddressesBytes, + feeState, nodeId, options, subnetAuth, subnetId, utxos, - feeConfig, }, context, ) => { @@ -789,11 +782,11 @@ export const newRemoveSubnetValidatorTx: TxBuilderFn< const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses: addressesFromBytes(fromAddressesBytes), initialComplexity: complexity, spendOptions: defaultedOptions, utxos, - feeConfig, }, [useUnlockedUTXOs], context, @@ -897,6 +890,7 @@ export const newAddPermissionlessValidatorTx: TxBuilderFn< { delegatorRewardsOwner, end, + feeState, fromAddressesBytes, locktime = 0n, nodeId, @@ -911,7 +905,6 @@ export const newAddPermissionlessValidatorTx: TxBuilderFn< threshold = 1, utxos, weight, - feeConfig, }, context, ) => { @@ -955,13 +948,13 @@ export const newAddPermissionlessValidatorTx: TxBuilderFn< const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses: addressesFromBytes(fromAddressesBytes), initialComplexity: complexity, shouldConsolidateOutputs: true, spendOptions: defaultedOptions, toStake, utxos, - feeConfig, }, [useSpendableLockedUTXOs, useUnlockedUTXOs], context, @@ -1057,6 +1050,7 @@ export const newAddPermissionlessDelegatorTx: TxBuilderFn< > = ( { end, + feeState, fromAddressesBytes, locktime = 0n, nodeId, @@ -1068,7 +1062,6 @@ export const newAddPermissionlessDelegatorTx: TxBuilderFn< threshold = 1, utxos, weight, - feeConfig, }, context, ) => { @@ -1103,13 +1096,13 @@ export const newAddPermissionlessDelegatorTx: TxBuilderFn< const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses: addressesFromBytes(fromAddressesBytes), initialComplexity: complexity, shouldConsolidateOutputs: true, spendOptions: defaultedOptions, toStake, utxos, - feeConfig, }, [useSpendableLockedUTXOs, useUnlockedUTXOs], context, @@ -1185,6 +1178,7 @@ export const newTransferSubnetOwnershipTx: TxBuilderFn< > = ( { fromAddressesBytes, + feeState, locktime = 0n, options, subnetAuth, @@ -1192,7 +1186,6 @@ export const newTransferSubnetOwnershipTx: TxBuilderFn< subnetOwners, threshold = 1, utxos, - feeConfig, }, context, ) => { @@ -1216,11 +1209,11 @@ export const newTransferSubnetOwnershipTx: TxBuilderFn< const spendResults = spend( { excessAVAX: 0n, + feeState, fromAddresses: addressesFromBytes(fromAddressesBytes), initialComplexity: complexity, spendOptions: defaultedOptions, utxos, - feeConfig, }, [useUnlockedUTXOs], context, diff --git a/src/vms/pvm/etna-builder/spend-reducers/fixtures/reducers.ts b/src/vms/pvm/etna-builder/spend-reducers/fixtures/reducers.ts index 5e8edea49..7a1a93448 100644 --- a/src/vms/pvm/etna-builder/spend-reducers/fixtures/reducers.ts +++ b/src/vms/pvm/etna-builder/spend-reducers/fixtures/reducers.ts @@ -1,8 +1,10 @@ -import { testFeeConfig } from '../../../../../fixtures/feeConfig'; +import { testContext } from '../../../../../fixtures/context'; +import { feeState as testFeeState } from '../../../../../fixtures/pvm'; import { Address, OutputOwners } from '../../../../../serializable'; import type { SpendOptions } from '../../../../common'; import { defaultSpendOptions } from '../../../../common/defaultSpendOptions'; import { createDimensions } from '../../../../common/fees/dimensions'; +import type { FeeState } from '../../../models'; import type { SpendHelperProps } from '../../spendHelper'; import { SpendHelper } from '../../spendHelper'; import type { SpendReducerState } from '../types'; @@ -38,11 +40,11 @@ export const getInitialReducerState = ({ toBurn: new Map(), toStake: new Map(), utxos: [], - feeConfig: testFeeConfig, ...state, }); export const getSpendHelper = ({ + feeState = testFeeState(), initialComplexity = createDimensions({ bandwidth: 1, dbRead: 1, @@ -56,16 +58,20 @@ export const getSpendHelper = ({ Pick< SpendHelperProps, 'initialComplexity' | 'shouldConsolidateOutputs' | 'toBurn' | 'toStake' - > + > & { feeState: FeeState } > = {}) => { return new SpendHelper({ changeOutputs: [], + gasPrice: + feeState.price < testContext.platformFeeConfig.minPrice + ? testContext.platformFeeConfig.minPrice + : feeState.price, initialComplexity, inputs: [], shouldConsolidateOutputs, stakeOutputs: [], toBurn, toStake, - feeConfig: testFeeConfig, + weights: testContext.platformFeeConfig.weights, }); }; diff --git a/src/vms/pvm/etna-builder/spend-reducers/types.ts b/src/vms/pvm/etna-builder/spend-reducers/types.ts index efffe2c94..f0967d594 100644 --- a/src/vms/pvm/etna-builder/spend-reducers/types.ts +++ b/src/vms/pvm/etna-builder/spend-reducers/types.ts @@ -3,7 +3,7 @@ import type { SpendProps } from '../spend'; import type { SpendHelper } from '../spendHelper'; export type SpendReducerState = Readonly< - Required> + Required> >; export type SpendReducerFunction = ( diff --git a/src/vms/pvm/etna-builder/spend.test.ts b/src/vms/pvm/etna-builder/spend.test.ts index 763572c74..41a48d6e9 100644 --- a/src/vms/pvm/etna-builder/spend.test.ts +++ b/src/vms/pvm/etna-builder/spend.test.ts @@ -10,8 +10,9 @@ import { type SpendReducerState, handleFeeAndChange, } from './spend-reducers'; +import type { SpendProps } from './spend'; import { spend } from './spend'; -import { testFeeConfig } from '../../../fixtures/feeConfig'; +import { feeState as testFeeState } from '../../../fixtures/pvm'; jest.mock('./spend-reducers', () => ({ verifyAssetsConsumed: jest.fn((state) => state), @@ -25,9 +26,7 @@ const CHANGE_OWNERS: OutputOwners = OutputOwners.fromNative([ CHANGE_ADDRESS.toBytes(), ]); -const getInitialReducerState = ( - state: Partial = {}, -): SpendReducerState => ({ +const getSpendProps = (state: Partial = {}): SpendProps => ({ excessAVAX: 0n, initialComplexity: createDimensions({ bandwidth: 1, @@ -35,6 +34,7 @@ const getInitialReducerState = ( dbWrite: 1, compute: 1, }), + feeState: testFeeState(), fromAddresses: [CHANGE_ADDRESS], ownerOverride: null, spendOptions: defaultSpendOptions( @@ -45,7 +45,6 @@ const getInitialReducerState = ( toBurn: new Map(), toStake: new Map(), utxos: [], - feeConfig: testFeeConfig, ...state, }); @@ -55,11 +54,7 @@ describe('./src/vms/pvm/etna-builder/spend.test.ts', () => { test.skip('calls spend reducers', () => { const testReducer = jest.fn((state) => state); - spend( - getInitialReducerState({ excessAVAX: 1_000n }), - [testReducer], - testContext, - ); + spend(getSpendProps({ excessAVAX: 1_000n }), [testReducer], testContext); expect(testReducer).toHaveBeenCalledTimes(1); expect(verifyAssetsConsumed).toHaveBeenCalledTimes(1); @@ -72,11 +67,7 @@ describe('./src/vms/pvm/etna-builder/spend.test.ts', () => { }); expect(() => - spend( - getInitialReducerState({ excessAVAX: 1_000n }), - [testReducer], - testContext, - ), + spend(getSpendProps({ excessAVAX: 1_000n }), [testReducer], testContext), ).toThrow('Test error'); }); @@ -86,18 +77,14 @@ describe('./src/vms/pvm/etna-builder/spend.test.ts', () => { }); expect(() => - spend( - getInitialReducerState({ excessAVAX: 1_000n }), - [testReducer], - testContext, - ), + spend(getSpendProps({ excessAVAX: 1_000n }), [testReducer], testContext), ).toThrow('An unexpected error occurred during spend calculation'); }); test('change owners in state should default to change addresses', () => { expect.assertions(1); - const initialState = getInitialReducerState({ excessAVAX: 1_000n }); + const initialState = getSpendProps({ excessAVAX: 1_000n }); const testReducer = jest.fn((state) => { expect(state.ownerOverride).toEqual( OutputOwners.fromNative(initialState.spendOptions.changeAddresses), @@ -111,7 +98,7 @@ describe('./src/vms/pvm/etna-builder/spend.test.ts', () => { test('change owners in state should be ownerOverride if provided', () => { expect.assertions(1); - const initialState = getInitialReducerState({ + const initialState = getSpendProps({ excessAVAX: 1_000n, ownerOverride: CHANGE_OWNERS, }); diff --git a/src/vms/pvm/etna-builder/spend.ts b/src/vms/pvm/etna-builder/spend.ts index b223f430a..b37817775 100644 --- a/src/vms/pvm/etna-builder/spend.ts +++ b/src/vms/pvm/etna-builder/spend.ts @@ -8,7 +8,7 @@ import type { Utxo } from '../../../serializable/avax/utxo'; import type { SpendOptions } from '../../common'; import type { Dimensions } from '../../common/fees/dimensions'; import type { Context } from '../../context'; -import type { FeeConfig } from '../models'; +import type { FeeState } from '../models'; import type { SpendReducerFunction, SpendReducerState } from './spend-reducers'; import { handleFeeAndChange, verifyAssetsConsumed } from './spend-reducers'; import { SpendHelper } from './spendHelper'; @@ -42,6 +42,7 @@ export type SpendProps = Readonly<{ * the change outputs in addition to the consumed and not burned AVAX. */ excessAVAX?: bigint; + feeState: FeeState; /** * List of Addresses that are used for selecting which UTXOs are signable. */ @@ -85,7 +86,6 @@ export type SpendProps = Readonly<{ * List of UTXOs that are available to be spent. */ utxos: readonly Utxo[]; - feeConfig: FeeConfig; }>; /** @@ -103,6 +103,7 @@ export type SpendProps = Readonly<{ export const spend = ( { excessAVAX = 0n, + feeState, fromAddresses, initialComplexity, ownerOverride, @@ -111,7 +112,6 @@ export const spend = ( toBurn = new Map(), toStake = new Map(), utxos, - feeConfig, }: SpendProps, spendReducers: readonly SpendReducerFunction[], context: Context, @@ -120,15 +120,21 @@ export const spend = ( const changeOwners = ownerOverride || OutputOwners.fromNative(spendOptions.changeAddresses); + const gasPrice: bigint = + feeState.price < context.platformFeeConfig.minPrice + ? context.platformFeeConfig.minPrice + : feeState.price; + const spendHelper = new SpendHelper({ changeOutputs: [], + gasPrice, initialComplexity, inputs: [], shouldConsolidateOutputs, stakeOutputs: [], toBurn, toStake, - feeConfig, + weights: context.platformFeeConfig.weights, }); const initialState: SpendReducerState = { @@ -140,7 +146,6 @@ export const spend = ( toBurn, toStake, utxos, - feeConfig, }; const spendReducerFunctions: readonly SpendReducerFunction[] = [ diff --git a/src/vms/pvm/etna-builder/spendHelper.test.ts b/src/vms/pvm/etna-builder/spendHelper.test.ts index 8569e822e..bc07973ae 100644 --- a/src/vms/pvm/etna-builder/spendHelper.test.ts +++ b/src/vms/pvm/etna-builder/spendHelper.test.ts @@ -4,7 +4,6 @@ import { utxo, } from '../../../fixtures/avax'; import { id } from '../../../fixtures/common'; -import { testFeeConfig } from '../../../fixtures/feeConfig'; import { stakeableLockOut } from '../../../fixtures/pvm'; import { TransferableOutput } from '../../../serializable'; import { isTransferOut } from '../../../utils'; @@ -26,6 +25,7 @@ const DEFAULT_WEIGHTS = createDimensions({ const DEFAULT_PROPS: SpendHelperProps = { changeOutputs: [], + gasPrice: DEFAULT_GAS_PRICE, initialComplexity: createDimensions({ bandwidth: 1, dbRead: 1, @@ -37,11 +37,7 @@ const DEFAULT_PROPS: SpendHelperProps = { stakeOutputs: [], toBurn: new Map(), toStake: new Map(), - feeConfig: { - ...testFeeConfig, - minPrice: DEFAULT_GAS_PRICE, - weights: DEFAULT_WEIGHTS, - }, + weights: DEFAULT_WEIGHTS, }; describe('src/vms/pvm/etna-builder/spendHelper', () => { diff --git a/src/vms/pvm/etna-builder/spendHelper.ts b/src/vms/pvm/etna-builder/spendHelper.ts index 28293f493..f464a3024 100644 --- a/src/vms/pvm/etna-builder/spendHelper.ts +++ b/src/vms/pvm/etna-builder/spendHelper.ts @@ -11,18 +11,18 @@ import { dimensionsToGas, } from '../../common/fees/dimensions'; import { consolidateOutputs } from '../../utils/consolidateOutputs'; -import type { FeeConfig } from '../models'; import { getInputComplexity, getOutputComplexity } from '../txs/fee'; export interface SpendHelperProps { changeOutputs: readonly TransferableOutput[]; + gasPrice: bigint; initialComplexity: Dimensions; inputs: readonly TransferableInput[]; shouldConsolidateOutputs: boolean; stakeOutputs: readonly TransferableOutput[]; toBurn: Map; toStake: Map; - feeConfig: FeeConfig; + weights: Dimensions; } /** @@ -47,20 +47,21 @@ export class SpendHelper { constructor({ changeOutputs, + gasPrice, initialComplexity, inputs, shouldConsolidateOutputs, stakeOutputs, toBurn, toStake, - feeConfig, + weights, }: SpendHelperProps) { - this.gasPrice = feeConfig.minPrice; - this.weights = feeConfig.weights; + this.gasPrice = gasPrice; this.initialComplexity = initialComplexity; this.shouldConsolidateOutputs = shouldConsolidateOutputs; this.toBurn = toBurn; this.toStake = toStake; + this.weights = weights; this.changeOutputs = changeOutputs; this.inputs = inputs; diff --git a/src/vms/pvm/models.ts b/src/vms/pvm/models.ts index 9acacdd9e..b7e43e832 100644 --- a/src/vms/pvm/models.ts +++ b/src/vms/pvm/models.ts @@ -252,7 +252,12 @@ export interface ValidatesResponse { } export interface FeeConfigResponse { - weights: [number, number, number, number]; // Weights to merge fee dimensions into a single gas value. + weights: [ + bandwidth: number, + dbRead: number, + dbWrite: number, + compute: number, + ]; // Weights to merge fee dimensions into a single gas value. maxCapacity: number; // Maximum amount of gas the chain is allowed to store for future use. maxPerSecond: number; // Maximum amount of gas the chain is allowed to consume per second. targetPerSecond: number; // Target amount of gas the chain should consume per second to keep the fees stable. @@ -265,6 +270,23 @@ export interface FeeConfig { maxCapacity: bigint; maxPerSecond: bigint; targetPerSecond: bigint; + /** Minimum gas price */ minPrice: bigint; excessConversionConstant: bigint; } + +export interface FeeStateResponse { + capacity: number; + excess: number; + price: number; + timestamp: string; +} + +export interface FeeState { + capacity: bigint; + excess: bigint; + /** Price to use for dynamic fee calculation */ + price: bigint; + /** ISO8601 DateTime */ + timestamp: string; +} diff --git a/src/vms/pvm/txs/fee/calculator.test.ts b/src/vms/pvm/txs/fee/calculator.test.ts index bc98c267d..14af4378b 100644 --- a/src/vms/pvm/txs/fee/calculator.test.ts +++ b/src/vms/pvm/txs/fee/calculator.test.ts @@ -1,4 +1,3 @@ -import { testFeeConfig } from '../../../../fixtures/feeConfig'; import { txHexToTransaction } from '../../../../fixtures/transactions'; import { calculateFee } from './calculator'; import { @@ -13,11 +12,11 @@ describe('Calculator', () => { test.each(TEST_TRANSACTIONS)( 'calculates the fee for $name', ({ txHex, expectedDynamicFee }) => { - const result = calculateFee(txHexToTransaction('PVM', txHex), { - ...testFeeConfig, - weights: TEST_DYNAMIC_WEIGHTS, - minPrice: TEST_DYNAMIC_PRICE, - }); + const result = calculateFee( + txHexToTransaction('PVM', txHex), + TEST_DYNAMIC_WEIGHTS, + TEST_DYNAMIC_PRICE, + ); expect(result).toBe(expectedDynamicFee); }, @@ -29,11 +28,7 @@ describe('Calculator', () => { const tx = txHexToTransaction('PVM', txHex); expect(() => { - calculateFee(tx, { - ...testFeeConfig, - weights: TEST_DYNAMIC_WEIGHTS, - minPrice: TEST_DYNAMIC_PRICE, - }); + calculateFee(tx, TEST_DYNAMIC_WEIGHTS, TEST_DYNAMIC_PRICE); }).toThrow('Unsupported transaction type.'); }, ); diff --git a/src/vms/pvm/txs/fee/calculator.ts b/src/vms/pvm/txs/fee/calculator.ts index b4b034b90..31a5d7d44 100644 --- a/src/vms/pvm/txs/fee/calculator.ts +++ b/src/vms/pvm/txs/fee/calculator.ts @@ -1,16 +1,19 @@ import type { Transaction } from '../../../common'; +import type { Dimensions } from '../../../common/fees/dimensions'; import { dimensionsToGas } from '../../../common/fees/dimensions'; -import type { FeeConfig } from '../../models'; import { getTxComplexity } from './complexity'; /** * Calculates the minimum required fee, in nAVAX, that an unsigned * transaction must pay for valid inclusion into a block. */ -export const calculateFee = (tx: Transaction, feeConfig: FeeConfig): bigint => { +export const calculateFee = ( + tx: Transaction, + weights: Dimensions, + price: bigint, +): bigint => { const complexity = getTxComplexity(tx); - const { weights, minPrice } = feeConfig; const gas = dimensionsToGas(complexity, weights); - return gas * minPrice; + return gas * price; };