From 33527d52479b7b520c7bee1f6d9d6a59d5effb71 Mon Sep 17 00:00:00 2001 From: Angel Castillo Date: Thu, 16 Jan 2025 10:54:28 +0800 Subject: [PATCH] feat!: fee calculation now takes into account reference script size BREAKING CHANGE: minFee now takes resolvedInputs and ProtocolParametersForInputSelection as arguments --- packages/input-selection/src/types.ts | 8 +- packages/tx-construction/src/fees/fees.ts | 96 +++++++++++++------ .../input-selection/selectionConstraints.ts | 34 ++++--- .../tx-construction/test/fees/fees.test.ts | 66 +++++++++++-- 4 files changed, 148 insertions(+), 56 deletions(-) diff --git a/packages/input-selection/src/types.ts b/packages/input-selection/src/types.ts index 0c645c82c9a..ea691f65fce 100644 --- a/packages/input-selection/src/types.ts +++ b/packages/input-selection/src/types.ts @@ -102,7 +102,13 @@ export interface InputSelector { export type ProtocolParametersForInputSelection = Pick< Cardano.ProtocolParameters, - 'coinsPerUtxoByte' | 'maxTxSize' | 'maxValueSize' | 'minFeeCoefficient' | 'minFeeConstant' | 'prices' + | 'coinsPerUtxoByte' + | 'maxTxSize' + | 'maxValueSize' + | 'minFeeCoefficient' + | 'minFeeConstant' + | 'prices' + | 'minFeeRefScriptCostPerByte' >; export type ProtocolParametersRequiredByInputSelection = Required<{ diff --git a/packages/tx-construction/src/fees/fees.ts b/packages/tx-construction/src/fees/fees.ts index 9eef6c676b9..05b72a9c557 100644 --- a/packages/tx-construction/src/fees/fees.ts +++ b/packages/tx-construction/src/fees/fees.ts @@ -1,5 +1,5 @@ import { Cardano, Serialization } from '@cardano-sdk/core'; -import { OpaqueNumber } from '@cardano-sdk/util'; +import { ProtocolParametersForInputSelection } from '@cardano-sdk/input-selection'; /** * The constant overhead of 160 bytes accounts for the transaction input and the entry in the UTxO map data @@ -52,34 +52,69 @@ const getTotalExUnits = (redeemers: Cardano.Redeemer[]): Cardano.ExUnits => { }; /** - * Gets the minimum fee incurred by the scripts on the transaction. + * Starting in the Conway era, the ref script min fee calculation is given by the total size (in bytes) of + * reference scripts priced according to a different, growing tiered pricing model. + * See https://github.com/CardanoSolutions/ogmios/releases/tag/v6.5.0 * - * @param tx The transaction to compute the min script fee from. - * @param exUnitsPrice The prices of the execution units. + * @param tx The transaction to compute the min ref script fee from. + * @param resolvedInputs The resolved inputs of the transaction. + * @param coinsPerRefScriptByte The price per byte of the reference script. */ -const minScriptFee = (tx: Cardano.Tx, exUnitsPrice: Cardano.Prices): bigint => { - if (!tx.witness.redeemers) return BigInt(0); +const minRefScriptFee = (tx: Cardano.Tx, resolvedInputs: Cardano.Utxo[], coinsPerRefScriptByte: number): bigint => { + if (coinsPerRefScriptByte === 0) return BigInt(0); - const totalExUnits = getTotalExUnits(tx.witness.redeemers); + let base: number = coinsPerRefScriptByte; + const range = 25_600; + const multiplier = 1.2; + + let totalRefScriptsSize = 0; + + const totalInputs = [...tx.body.inputs, ...(tx.body.referenceInputs ?? [])]; + for (const output of totalInputs) { + const resolvedInput = resolvedInputs.find( + (input) => input[0].txId === output.txId && input[0].index === output.index + ); - return BigInt(Math.ceil(totalExUnits.steps * exUnitsPrice.steps + totalExUnits.memory * exUnitsPrice.memory)); + if (resolvedInput && resolvedInput[1].scriptReference) { + totalRefScriptsSize += Serialization.Script.fromCore(resolvedInput[1].scriptReference).toCbor().length / 2; + } + } + + let scriptRefFee = 0; + while (totalRefScriptsSize > 0) { + scriptRefFee += Math.ceil(Math.min(range, totalRefScriptsSize) * base); + totalRefScriptsSize = Math.max(totalRefScriptsSize - range, 0); + base *= multiplier; + } + + return BigInt(scriptRefFee); }; /** - * The value of the min fee constant is a payable fee, regardless of the size of the transaction. This parameter was - * primarily introduced to prevent Distributed-Denial-of-Service (DDoS) attacks. This constant makes such attacks - * prohibitively expensive, and eliminates the possibility of an attacker generating millions of small transactions - * to flood and crash the system. + * Gets the minimum fee incurred by the scripts on the transaction. + * + * @param tx The transaction to compute the min script fee from. + * @param exUnitsPrice The prices of the execution units. + * @param resolvedInputs The resolved inputs of the transaction. + * @param coinsPerRefScriptByte The price per byte of the reference script. */ -export type MinFeeConstant = OpaqueNumber<'MinFeeConstant'>; -export const MinFeeConstant = (value: number): MinFeeConstant => value as unknown as MinFeeConstant; +const minScriptFee = ( + tx: Cardano.Tx, + exUnitsPrice: Cardano.Prices, + resolvedInputs: Cardano.Utxo[], + coinsPerRefScriptByte: number +): bigint => { + const scriptRefFee = minRefScriptFee(tx, resolvedInputs, coinsPerRefScriptByte); -/** - * Min fee coefficient reflects the dependence of the transaction cost on the size of the transaction. The larger - * the transaction, the more resources are needed to store and process it. - */ -export type MinFeeCoefficient = OpaqueNumber<'MinFeeCoefficient'>; -export const MinFeeCoefficient = (value: number): MinFeeCoefficient => value as unknown as MinFeeCoefficient; + if (!tx.witness.redeemers) return BigInt(scriptRefFee); + + const totalExUnits = getTotalExUnits(tx.witness.redeemers); + + return ( + BigInt(Math.ceil(totalExUnits.steps * exUnitsPrice.steps + totalExUnits.memory * exUnitsPrice.memory)) + + scriptRefFee + ); +}; /** * Gets the minimum fee incurred by the transaction size. @@ -88,7 +123,7 @@ export const MinFeeCoefficient = (value: number): MinFeeCoefficient => value as * @param minFeeConstant The prices of the execution units. * @param minFeeCoefficient The prices of the execution units. */ -const minNoScriptFee = (tx: Cardano.Tx, minFeeConstant: MinFeeConstant, minFeeCoefficient: MinFeeCoefficient) => { +const minNoScriptFee = (tx: Cardano.Tx, minFeeConstant: number, minFeeCoefficient: number) => { const txSize = serializeTx(tx).length; return BigInt(Math.ceil(txSize * minFeeCoefficient + minFeeConstant)); }; @@ -130,13 +165,14 @@ export const minAdaRequired = (output: Cardano.TxOut, coinsPerUtxoByte: bigint): * Gets the minimum transaction fee for the given transaction given its size and its execution units budget. * * @param tx The transaction to compute the min fee from. - * @param exUnitsPrice The current (given by protocol parameters) execution unit prices. - * @param minFeeConstant The current (given by protocol parameters) constant fee that all transaction must pay. - * @param minFeeCoefficient The current (given by protocol parameters) transaction size fee coefficient. + * @param resolvedInputs The resolved inputs of the transaction. + * @param pparams The protocol parameters. */ -export const minFee = ( - tx: Cardano.Tx, - exUnitsPrice: Cardano.Prices, - minFeeConstant: MinFeeConstant, - minFeeCoefficient: MinFeeCoefficient -) => minNoScriptFee(tx, minFeeConstant, minFeeCoefficient) + minScriptFee(tx, exUnitsPrice); +export const minFee = (tx: Cardano.Tx, resolvedInputs: Cardano.Utxo[], pparams: ProtocolParametersForInputSelection) => + minNoScriptFee(tx, pparams.minFeeConstant, pparams.minFeeCoefficient) + + minScriptFee( + tx, + pparams.prices, + resolvedInputs, + pparams.minFeeRefScriptCostPerByte ? Number(pparams.minFeeRefScriptCostPerByte) : 0 + ); diff --git a/packages/tx-construction/src/input-selection/selectionConstraints.ts b/packages/tx-construction/src/input-selection/selectionConstraints.ts index 8ae7d23951e..135e9f05e08 100644 --- a/packages/tx-construction/src/input-selection/selectionConstraints.ts +++ b/packages/tx-construction/src/input-selection/selectionConstraints.ts @@ -10,8 +10,8 @@ import { TokenBundleSizeExceedsLimit, sortTxIn } from '@cardano-sdk/input-selection'; -import { MinFeeCoefficient, MinFeeConstant, minAdaRequired, minFee } from '../fees'; import { TxEvaluationResult, TxEvaluator, TxIdWithIndex } from '../tx-builder'; +import { minAdaRequired, minFee } from '../fees'; export const MAX_U64 = 18_446_744_073_709_551_615n; @@ -105,11 +105,7 @@ const reorgRedeemers = ( export const computeMinimumCost = ( - { - minFeeCoefficient, - minFeeConstant, - prices - }: Pick, + pparams: ProtocolParametersForInputSelection, buildTx: BuildTx, txEvaluator: TxEvaluator, redeemersByType: RedeemersByType @@ -126,7 +122,7 @@ export const computeMinimumCost = } return { - fee: minFee(tx, prices, MinFeeConstant(minFeeConstant), MinFeeCoefficient(minFeeCoefficient)), + fee: minFee(tx, utxos, pparams), redeemers: tx.witness.redeemers }; }; @@ -170,25 +166,27 @@ export const computeSelectionLimit = }; export const defaultSelectionConstraints = ({ - protocolParameters: { coinsPerUtxoByte, maxTxSize, maxValueSize, minFeeCoefficient, minFeeConstant, prices }, + protocolParameters, buildTx, redeemersByType, txEvaluator }: DefaultSelectionConstraintsProps): SelectionConstraints => { - if (!coinsPerUtxoByte || !maxTxSize || !maxValueSize || !minFeeCoefficient || !minFeeConstant || !prices) { + if ( + !protocolParameters.coinsPerUtxoByte || + !protocolParameters.maxTxSize || + !protocolParameters.maxValueSize || + !protocolParameters.minFeeCoefficient || + !protocolParameters.minFeeConstant || + !protocolParameters.prices + ) { throw new InvalidProtocolParametersError( 'Missing one of: coinsPerUtxoByte, maxTxSize, maxValueSize, minFeeCoefficient, minFeeConstant, prices' ); } return { - computeMinimumCoinQuantity: computeMinimumCoinQuantity(coinsPerUtxoByte), - computeMinimumCost: computeMinimumCost( - { minFeeCoefficient, minFeeConstant, prices }, - buildTx, - txEvaluator, - redeemersByType - ), - computeSelectionLimit: computeSelectionLimit(maxTxSize, buildTx), - tokenBundleSizeExceedsLimit: tokenBundleSizeExceedsLimit(maxValueSize) + computeMinimumCoinQuantity: computeMinimumCoinQuantity(protocolParameters.coinsPerUtxoByte), + computeMinimumCost: computeMinimumCost(protocolParameters, buildTx, txEvaluator, redeemersByType), + computeSelectionLimit: computeSelectionLimit(protocolParameters.maxTxSize, buildTx), + tokenBundleSizeExceedsLimit: tokenBundleSizeExceedsLimit(protocolParameters.maxValueSize) }; }; diff --git a/packages/tx-construction/test/fees/fees.test.ts b/packages/tx-construction/test/fees/fees.test.ts index 6b73d7d9861..366ac987fbc 100644 --- a/packages/tx-construction/test/fees/fees.test.ts +++ b/packages/tx-construction/test/fees/fees.test.ts @@ -1,5 +1,5 @@ -import { Cardano } from '@cardano-sdk/core'; -import { MinFeeCoefficient, MinFeeConstant, minAdaRequired, minFee } from '../../src'; +import { Cardano, Serialization } from '@cardano-sdk/core'; +import { ProtocolParametersForInputSelection } from '@cardano-sdk/input-selection'; import { babbageTx, babbageTxWithoutScript, @@ -27,9 +27,57 @@ import { twoPolicyOne0CharAssetDatumMinAda } from '../testData'; import { generateRandomHexString } from '@cardano-sdk/util-dev'; +import { minAdaRequired, minFee } from '../../src'; const COST_PER_UTXO_BYTE = BigInt(4310); +const protocolParams = { + minFeeCoefficient: 44, + minFeeConstant: 155_381, + minFeeRefScriptCostPerByte: 15, + prices: { memory: 0.0577, steps: 0.000_072_1 } +} as unknown as ProtocolParametersForInputSelection; + +const TX_WITH_REF_SCRIPT_CBOR = Serialization.TxCBOR( + '84ab0081825820fbecbe69bc3ee617653b95893f50b0362cbaff3e27b01a936969a25bfc100a7c000182835839319068a7a3f008803edac87af1619860f2cdcde40c26987325ace138ad2c967f4bd28944b06462e13c5e3f5d5fa6e03f8567569438cd833e6d1a093d1cc0582057ad45489e9d4e3d7df98fb6b273d647cbed6990125dc51815bdee9abbc3a84a82583901e6d3410be0b3d51a52b874983bc6f0e48bcf4274352552e683e1c211b91d1fe82203de4c0de2c150746383a893cd21eb1082565252af63a51a00eef6fe021a00052d02031a082ee80007582026e4e8217ceb7c9eee2dffc410d77bbe3efd952288573f1c9a19fe62979634bc0b5820fe1f0d446610edf6890cbce2c3e69ad4052c557fd6d044b1f195a4f916c3e0fe0d8182582047754bf3cb4adf7374496b17fa41c197043533355c7a31a1776207fe627f5a5d010e81581ce6d3410be0b3d51a52b874983bc6f0e48bcf4274352552e683e1c2111082583901e6d3410be0b3d51a52b874983bc6f0e48bcf4274352552e683e1c211b91d1fe82203de4c0de2c150746383a893cd21eb1082565252af63a51a00461558111a0007c38312818258209a32459bd4ef6bbafdeb8cf3b909d0e3e2ec806e4cc6268529280b0fc1d06f5b00a3008182582005e884ca7c466df47785af770be8495ec0998e60ebe63e4cd187cd17eeac5e9258402cdcec5c4ba1ea76c558554dea99f472b67488be18f1f7085bac4cc55376ca8f4ed61b23565ddbffcd85a4e84963c36c98272314d2637b238e65def639969f0b0481d8799f581ce6d3410be0b3d51a52b874983bc6f0e48bcf4274352552e683e1c2119fd8799fd8799fd87a9f581c84cc25ea4c29951d40b443b95bbc5676bc425470f96376d1984af9abffd8799fd8799fd87a9f581c2c967f4bd28944b06462e13c5e3f5d5fa6e03f8567569438cd833e6dffffffffa140d8799f00a1401a00342f60ffffd8799fd8799fd8799f581cf437291791dda80d0bba9f3616f8b7533c8a8db2f788b8468a26bd5affd8799fd8799fd8799f581ce3c9536e2947e33703d5793a02b593a8d32b49aaaef03ea0b2b03c87ffffffffa140d8799f00a1401a0104ece0ffffd8799fd8799fd8799f581ce6d3410be0b3d51a52b874983bc6f0e48bcf4274352552e683e1c211ffd8799fd8799fd8799f581cb91d1fe82203de4c0de2c150746383a893cd21eb1082565252af63a5ffffffffa1581cb2d25f829ebb7f4c97b5e847923a1115b23ebf78000722c229c9c9f7d8799f01a0ffffffff0581840000d87980821a000af3301a0b01ca09f5ae181e613518327840643837393966353831636536643334313062653062336435316135326238373439383362633666306534386263663432373433353235353265363833653163321833784031313966643837393966643837393966643837613966353831633834636332356561346332393935316434306234343362393562626335363736626334323534183478403730663936333736643139383461663961626666643837393966643837393966643837613966353831633263393637663462643238393434623036343632653118357840336335653366356435666136653033663835363735363934333863643833336536646666666666666666613134306438373939663030613134303161303032661836784034643630666666666438373939666438373939666438373939663538316366343337323931373931646461383064306262613966333631366638623735333363183778403861386462326637383862383436386132366264356166666438373939666438373939666438373939663538316365336339353336653239343765333337303318387840643537393361303262353933613864333262343961616165663033656130623262303363383766666666666666666131343064383739396630306131343031611839784030306563383265306666666664383739396664383739396664383739396635383163653664333431306265306233643531613532623837343938336263366630183a784065343862636634323734333532353532653638336531633231316666643837393966643837393966643837393966353831636239316431666538323230336465183b784034633064653263313530373436333833613839336364323165623130383235363532353261663633613566666666666666666131353831636232643235663832183c784039656262376634633937623565383437393233613131313562323365626637383030303732326332323963396339663764383739396630316130666666666666183d6366662c183e783c62326432356638323965626237663463393762356538343739323361313131356232336562663738303030373232633232396339633966373a3a3030' +); + +const resolvedInputs = [ + [ + { + index: 0, + txId: '9a32459bd4ef6bbafdeb8cf3b909d0e3e2ec806e4cc6268529280b0fc1d06f5b' + }, + { + address: + 'addr1zxgx3far7qygq0k6epa0zcvcvrevmn0ypsnfsue94nsn3tvpw288a4x0xf8pxgcntelxmyclq83s0ykeehchz2wtspks905plm', + scriptReference: { + __type: 'plutus', + bytes: + '5909fe010000323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232222323232533535533357346064606a0062646464642466002008004a666ae68c0d8c0e00044c848c004008c078d5d0981b8008191baa357426ae88c0d80154ccd5cd1819981b0008991919191919191919191919191919191919191919190919999999999980080b80a8098088078068058048038028018011aba135744004666068eb88004d5d08009aba2002357420026ae88008cc0c9d71aba1001357440046ae84004d5d10011aba1001357440046ae84004d5d10011aba1001357440046ae84004d5d10011981300f1aba1001357440046ae84004d5d1181b001198111192999ab9a30353038001132321233001003002301d357426ae88c0e0008c078d5d0981b8008191baa00135742606a0020606ea8d5d0981a001817911a8011111111111111a80691919299aa99a998149aa99a80109815a481035054380022100203d00303903a03a1533501213302549101350033302330340362350012232333027303803a235001223500122533533302b0440040062153353333026303e040223500222533500321533533303104a0030062153353302b0010031303f3305722533500104c221350022253353305100200a100313304d33047002001300600300215335330370010031303f333302d04b0043370200200600409209008e60720020044266060920102313000333573466e20ccd54c0fc104c0a8cc0f1c024000400266aa608008246a00209600200809208e266ae712410231310004813357389201023132000470023335530360393501b0403501b04233355303603922533535002222253353302200800413038003042213303d001002100103f010333301c303403622350022253353303c00b002100313333020303803a235001222533533302a0210030012133330260220043355303e03f235001223303d002333500120012235002223500322330433370000800466aa608e09046a002446608c004666a0024002e008004ccc0c013400c0048004ccc09c11000c0040084cccc09408400c00800400c0040f140044cc0952410134003330233034036235001223303b00a0025001153353355303403523500122350012222302c533350021303104821001213304e2253350011303404a221350022253353304800200710011300600300c0011302a49010136002213355303603723500122350012222302e533350021303304a2100121330502253350011303604c221350022253353304a00200710011300600300e0033335530310342253353353530283500203f03d203f253353303c001330482253350011302e044221350022253353303000200a135302f001223350022303504b20011300600301003b1302c4901013300133037002001100103a00d1120011533573892010350543500165333573460640020502a666ae68c0c400409c0b8c0ccdd50019baa00133019223355301f020235001223301e002335530220232350012233021002333500137009000380233700002900000099aa980f81011a800911980f001199a800919aa981181211a8009119811001180880080091199806815001000919aa981181211a80091198110011809000800999804012801000812111919807198021a8018139a801013a99a9a80181490a99a8011099a801119a80111980400100091101711119a80210171112999ab9a3370e00c0062a666ae68cdc38028010998068020008158158120a99a80090120121a8008141119a801119a8011198128010009014119a801101411981280100091199ab9a3370e00400204604a44446666aa00866032444600660040024002006002004444466aa603803a46a0024466036004666a0024002052400266600a0080026603c66030006004046444666aa603003603866aa603403646a00244660320046010002666aa6030036446a00444a66a666aa603a03e60106603444a66a00404a200204e46a002446601400400a00c200626604000800604200266aa603403646a00244660320046605e44a66a002260160064426a00444a66a6601800401022444660040140082600c00600800446602644666a0060420040026a00204242444600600842444600200844604e44a66a0020364426a00444a66a6601000400e2602a0022600c0064466aa0046602000603600244a66a004200202e44a66a00202e266ae7000806c8c94ccd5cd180f9811000899190919800801801198079192999ab9a3022302500113232123300100300233301075c464a666ae68c094c0a00044c8cc0514cd4cc028005200110011300e4901022d330033301375c464a66a660180029000080089808249022d3200375a0026ae84d5d118140011bad35742604e0020446ea8004d5d09aba23025002300c35742604800203e6ea8004d5d09aba23022002375c6ae84c084004070dd500091199ab9a3371200400203202e46a002444400844a666ae68cdc79a80100b1a80080b0999ab9a3370e6a0040306a00203002a02e024464a666ae68c06cc0780044c8c8c8c8c8c8c8c848cccc00402401c00c008d5d09aba20045333573466e1d2004001132122230020043574260460042a666ae68c0880044c84888c004010dd71aba1302300215333573460420022244400603c60460026ea8d5d08009aba200233300a75c66014eb9d69aba100135744603c004600a6ae84c074004060dd50009299ab9c001162325333573460326038002264646424660020060046eb4d5d09aba2301d003533357346034603a00226eb8d5d0980e00080b9baa35742603600202c6ea80048c94ccd5cd180c180d80089919191909198008028012999ab9a301b00113232300953335734603c00226464646424466600200c0080066eb4d5d09aba2002375a6ae84004d5d118100019bad35742603e0042a666ae68c0740044c8488c00800cc020d5d0980f80100d180f8009baa35742603a0042a666ae68c070004044060c074004dd51aba135744603600460066ae84c068004054dd5000919192999ab9a30190011321223001003375c6ae84c06800854ccd5cd180c00089909118010019bae35742603400402a60340026ea80048488c00800c888cc06888cccd55cf800900911919807198041803980e8009803180e00098021aba2003357420040166eac0048848cc00400c00888cc05c88cccd55cf800900791980518029aba10023003357440040106eb0004c05088448894cd40044008884cc014008ccd54c01c028014010004c04c88448894cd40044d400c040884ccd4014040c010008ccd54c01c024014010004c0488844894cd4004024884cc020c010008cd54c01801c0100044800488488cc00401000cc03c8894cd40080108854cd4cc02000800c01c4cc01400400c4014400888ccd5cd19b8f0020010030051001220021001220011533573892010350543100164901022d31004901013700370e90001b874800955cf2ab9d2323001001223300330020020011', + version: 1 + }, + value: { + coins: 40_000_000n + } + } + ] as unknown as Cardano.Utxo, + [ + { + index: 0, + txId: 'fbecbe69bc3ee617653b95893f50b0362cbaff3e27b01a936969a25bfc100a7c' + }, + { + address: + 'addr1xxgx3far7qygq0k6epa0zcvcvrevmn0ypsnfsue94nsn3tfvjel5h55fgjcxgchp830r7h2l5msrlpt8262r3nvr8eks2utwdd', + datumHash: 'c6b9e0671fef714142bda45beedf7b51c2d4e3676f79196964082fef164ef7e4', + value: { + coins: 171_000_000n + } + } + ] as unknown as Cardano.Utxo +]; + describe('fees', () => { describe('minAdaRequired', () => { it('calculate the correct min ada for an utxo with no multiasset', () => { @@ -112,15 +160,19 @@ describe('fees', () => { describe('minFee', () => { it('calculate the correct min fee for transaction without scripts', () => { - const prices = { memory: 0.0577, steps: 0.000_007_21 }; - const fee = minFee(babbageTxWithoutScript, prices, MinFeeConstant(155_381), MinFeeCoefficient(44)); + const fee = minFee(babbageTxWithoutScript, [], protocolParams); expect(fee).toBe(176_193n); }); it('calculate the correct min fee for transaction with scripts', () => { - const prices = { memory: 0.0577, steps: 0.000_007_21 }; - const fee = minFee(babbageTx, prices, MinFeeConstant(155_381), MinFeeCoefficient(44)); - expect(fee).toBe(218_763n); + const fee = minFee(babbageTx, [], protocolParams); + expect(fee).toBe(218_764n); + }); + + it('calculate the correct min fee for transaction with inputs with reference scripts', () => { + const tx = Serialization.Transaction.fromCbor(TX_WITH_REF_SCRIPT_CBOR).toCore(); + const fee = minFee(tx, resolvedInputs, protocolParams); + expect(fee).toBe(326_472n); }); }); });