Skip to content

Commit 25b425b

Browse files
committed
Hoist multi-asset balance check out of calcMinFeeRecursive loop
1 parent 7f1d954 commit 25b425b

1 file changed

Lines changed: 41 additions & 29 deletions

File tree

  • cardano-api/src/Cardano/Api/Experimental/Tx/Internal

cardano-api/src/Cardano/Api/Experimental/Tx/Internal/Fee.hs

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -698,27 +698,24 @@ instance Error FeeCalculationError where
698698
-- (i.e. @inputs + mint + withdrawals + refunds = outputs + fee + deposits@
699699
-- for all value components: ADA and every native token).
700700
--
701+
-- Before entering the iterative loop the multi-asset balance is checked.
702+
-- Because fee adjustments only affect ADA, a negative multi-asset balance
703+
-- is unrecoverable and the function returns 'NonAdaAssetsUnbalanced'
704+
-- immediately.
705+
--
701706
-- On each iteration the balance is computed via 'evaluateTransactionBalance'
702707
-- and the minimum fee via @calcMinFeeTx@. The function then proceeds based
703708
-- on the following cases, evaluated in order:
704709
--
705-
-- * __Case 1 – Negative multi-asset balance__: The outputs demand more of a
706-
-- native token than is available from inputs and minting. This is
707-
-- unrecoverable because fee adjustments only affect ADA — they cannot
708-
-- change the multi-asset balance. Remedy: provide additional inputs
709-
-- containing the deficit tokens, mint the missing amount, or reduce the
710-
-- token quantities in the outputs.
711-
-- Returns 'NonAdaAssetsUnbalanced'.
712-
--
713-
-- * __Case 2 – Fee converged, balance is zero__: The transaction is fully
710+
-- * __Case 1 – Fee converged, balance is zero__: The transaction is fully
714711
-- balanced. Before returning, all outputs are checked against the minimum
715712
-- UTxO requirement ('MinUTxONotMet'). Note: a 'MinUTxONotMet' error at
716-
-- this point typically means that Case 3 distributed surplus multi-assets
713+
-- this point typically means that Case 2 distributed surplus multi-assets
717714
-- to an output on a prior iteration but there was not enough ADA surplus
718715
-- to satisfy the increased @coinPerUTxOByte@ requirement for that output.
719716
-- The remedy is to provide additional ADA inputs.
720717
--
721-
-- * __Case 3 – Fee converged, non-zero balance__: There is surplus or
718+
-- * __Case 2 – Fee converged, non-zero balance__: There is surplus or
722719
-- deficit ADA, excess multi-assets (e.g. from minting), or both. A new
723720
-- change output is created at the provided change address with the
724721
-- balance and appended to the end of the existing outputs; if a change
@@ -729,7 +726,7 @@ instance Error FeeCalculationError where
729726
-- required fee, and must also satisfy the minimum UTxO
730727
-- (@coinPerUTxOByte@) constraint.
731728
--
732-
-- * __Case 4 – Fee has not converged__: The fee field is set to the newly
729+
-- * __Case 3 – Fee has not converged__: The fee field is set to the newly
733730
-- computed minimum fee and the function recurses.
734731
--
735732
-- A maximum iteration limit (currently 50) guards against non-termination.
@@ -757,8 +754,29 @@ calcMinFeeRecursive
757754
-> Int
758755
-- ^ Number of extra key hashes for native scripts
759756
-> Either FeeCalculationError (UnsignedTx (LedgerEra era))
760-
calcMinFeeRecursive changeAddr = go maxIterations
757+
calcMinFeeRecursive changeAddr unsignedTx utxo pparams poolids stakeDelegDeposits drepDelegDeposits nExtraWitnesses
758+
-- If multi-assets are non-negative initially, they stay non-negative across
759+
-- iterations (only ADA and fee change), so check once upfront.
760+
| multiAssetIsNegative =
761+
Left $ NonAdaAssetsUnbalanced multiAssets
762+
| otherwise =
763+
go
764+
maxIterations
765+
unsignedTx
766+
utxo
767+
pparams
768+
poolids
769+
stakeDelegDeposits
770+
drepDelegDeposits
771+
nExtraWitnesses
761772
where
773+
initialBalance = evaluateTransactionBalance pparams poolids stakeDelegDeposits drepDelegDeposits utxo unsignedTx
774+
multiAssets = getMultiAssets (useEra @era) initialBalance
775+
-- Check whether any native token quantity is negative.
776+
-- ADA is zeroed out so it doesn't influence the check.
777+
multiAssetIsNegative =
778+
obtainCommonConstraints (useEra @era) $
779+
not (L.pointwise (>=) (L.MaryValue (L.Coin 0) multiAssets) mempty)
762780
maxIterations :: Int
763781
maxIterations = 50
764782

@@ -773,32 +791,26 @@ calcMinFeeRecursive changeAddr = go maxIterations
773791
-> Int
774792
-> Either FeeCalculationError (UnsignedTx (LedgerEra era))
775793
go 0 _ _ _ _ _ _ _ = Left FeeCalculationDidNotConverge
776-
go n unSignTx@(UnsignedTx ledgerTx) utxo pparams poolids stakeDelegDeposits drepDelegDeposits nExtraWitnesses
777-
| multiAssetIsNegative =
778-
-- Case 1
779-
Left $ NonAdaAssetsUnbalanced (getMultiAssets (useEra @era) txBalanceValue)
794+
go n unSignTx@(UnsignedTx ledgerTx) utxo' pparams' poolids' stakeDelegDeposits' drepDelegDeposits' nExtraWitnesses'
780795
| minFee == txBodyFee && L.isZero txBalanceValue = do
781-
-- Case 2
796+
-- Case 1
782797
let outs = toList $ ledgerTx ^. L.bodyTxL . L.outputsTxBodyL
783-
mapM_ (checkOutputMinUTxO pparams) outs
798+
mapM_ (checkOutputMinUTxO pparams') outs
784799
return unSignTx
785800
| minFee == txBodyFee = do
786-
-- Case 3
801+
-- Case 2
787802
balancedOuts <- balanceTxOuts @era changeAddr txBalanceValue unSignTx
788803
let updatedTx = UnsignedTx (ledgerTx & L.bodyTxL . L.outputsTxBodyL .~ balancedOuts)
789-
go (n - 1) updatedTx utxo pparams poolids stakeDelegDeposits drepDelegDeposits nExtraWitnesses
804+
go (n - 1) updatedTx utxo' pparams' poolids' stakeDelegDeposits' drepDelegDeposits' nExtraWitnesses'
790805
| otherwise =
791-
-- Case 4
806+
-- Case 3
792807
let newTx = UnsignedTx (ledgerTx & L.bodyTxL . L.feeTxBodyL .~ minFee)
793-
in go (n - 1) newTx utxo pparams poolids stakeDelegDeposits drepDelegDeposits nExtraWitnesses
808+
in go (n - 1) newTx utxo' pparams' poolids' stakeDelegDeposits' drepDelegDeposits' nExtraWitnesses'
794809
where
795-
minFee = obtainCommonConstraints (useEra @era) $ L.calcMinFeeTx utxo pparams ledgerTx nExtraWitnesses
810+
minFee = obtainCommonConstraints (useEra @era) $ L.calcMinFeeTx utxo' pparams' ledgerTx nExtraWitnesses'
796811
txBodyFee = ledgerTx ^. L.bodyTxL . L.feeTxBodyL
797-
txBalanceValue = evaluateTransactionBalance pparams poolids stakeDelegDeposits drepDelegDeposits utxo unSignTx
798-
txBalanceCoin = L.coin txBalanceValue
799-
multiAssetIsNegative =
800-
obtainCommonConstraints (useEra @era) $
801-
not (L.pointwise (>=) txBalanceValue (L.inject txBalanceCoin))
812+
txBalanceValue =
813+
evaluateTransactionBalance pparams' poolids' stakeDelegDeposits' drepDelegDeposits' utxo' unSignTx
802814

803815
checkOutputMinUTxO
804816
:: forall era

0 commit comments

Comments
 (0)