Skip to content

Commit 266eb04

Browse files
committed
Improve autobalancing errors when change has no lovelace
1 parent a10f917 commit 266eb04

File tree

2 files changed

+53
-34
lines changed
  • cardano-api

2 files changed

+53
-34
lines changed

cardano-api/src/Cardano/Api/Internal/Fees.hs

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -645,13 +645,16 @@ estimateBalancedTxBody
645645
balance =
646646
evaluateTransactionBalance sbe pparams poolids stakeDelegDeposits drepDelegDeposits fakeUTxO txbody2
647647
balanceTxOut = TxOut changeaddr balance TxOutDatumNone ReferenceScriptNone
648-
-- check if the balance is positive or negative
649-
-- in one case we can produce change, in the other the inputs are insufficient
650-
first TxFeeEstimationBalanceError $ balanceCheck sbe pparams balanceTxOut
651648

652649
-- Step 6. Check all txouts have the min required UTxO value
653650
forM_ (txOuts txbodycontent1) $
654-
\txout -> first TxFeeEstimationBalanceError $ checkMinUTxOValue sbe txout pparams
651+
\txout -> first TxFeeEstimationBalanceError $ checkMinUTxOValue sbe pparams txout
652+
653+
-- check if the balance is positive or negative
654+
-- in one case we can produce change, in the other the inputs are insufficient
655+
finalTxOuts <-
656+
first TxFeeEstimationBalanceError $
657+
checkAndIncludeChange sbe pparams balanceTxOut (txOuts txbodycontent1)
655658

656659
-- Step 7.
657660

@@ -663,10 +666,7 @@ estimateBalancedTxBody
663666
let finalTxBodyContent =
664667
txbodycontent1
665668
{ txFee = TxFeeExplicit sbe fee
666-
, txOuts =
667-
accountForNoChange
668-
balanceTxOut
669-
(txOuts txbodycontent)
669+
, txOuts = finalTxOuts
670670
, txReturnCollateral = retColl
671671
, txTotalCollateral = reqCol
672672
}
@@ -1371,7 +1371,7 @@ makeTransactionBodyAutoBalance
13711371
TxOutDatumNone
13721372
ReferenceScriptNone
13731373

1374-
balanceCheck sbe pp initialChangeTxOut
1374+
_ <- balanceCheck sbe pp initialChangeTxOut
13751375

13761376
-- Tx body used only for evaluating execution units. Because txout exact
13771377
-- values do not matter much here, we are using an initial change value,
@@ -1473,11 +1473,10 @@ makeTransactionBodyAutoBalance
14731473
}
14741474
let balance = evaluateTransactionBalance sbe pp poolids stakeDelegDeposits drepDelegDeposits utxo txbody2
14751475
balanceTxOut = TxOut changeaddr balance TxOutDatumNone ReferenceScriptNone
1476-
forM_ (txOuts txbodycontent1) $ \txout -> checkMinUTxOValue sbe txout pp
1476+
forM_ (txOuts txbodycontent1) $ \txout -> checkMinUTxOValue sbe pp txout
14771477

1478-
-- check if the balance is positive or negative
1479-
-- in one case we can produce change, in the other the inputs are insufficient
1480-
balanceCheck sbe pp balanceTxOut
1478+
-- check if change meets txout criteria, and include if non-zero
1479+
finalTxOuts <- checkAndIncludeChange sbe pp balanceTxOut (txOuts txbodycontent1)
14811480

14821481
-- TODO: we could add the extra fee for the CBOR encoding of the change,
14831482
-- now that we know the magnitude of the change: i.e. 1-8 bytes extra.
@@ -1489,10 +1488,7 @@ makeTransactionBodyAutoBalance
14891488
let finalTxBodyContent =
14901489
txbodycontent1
14911490
{ txFee = TxFeeExplicit sbe fee
1492-
, txOuts =
1493-
accountForNoChange
1494-
balanceTxOut
1495-
(txOuts txbodycontent)
1491+
, txOuts = finalTxOuts
14961492
, txReturnCollateral = retColl
14971493
, txTotalCollateral = reqCol
14981494
}
@@ -1512,47 +1508,63 @@ makeTransactionBodyAutoBalance
15121508
era :: CardanoEra era
15131509
era = toCardanoEra sbe
15141510

1515-
-- | In the event of spending the exact amount of lovelace in
1511+
-- | In the event of spending the exact amount of lovelace and non-ada assets in
15161512
-- the specified input(s), this function excludes the change
15171513
-- output. Note that this does not save any fees because by default
15181514
-- the fee calculation includes a change address for simplicity and
15191515
-- we make no attempt to recalculate the tx fee without a change address.
1520-
accountForNoChange :: TxOut CtxTx era -> [TxOut CtxTx era] -> [TxOut CtxTx era]
1521-
accountForNoChange change@(TxOut _ balance _ _) rest =
1522-
case txOutValueToLovelace balance of
1523-
L.Coin 0 -> rest
1524-
-- We append change at the end so a client can predict the indexes
1525-
-- of the outputs
1526-
_ -> rest ++ [change]
1516+
checkAndIncludeChange
1517+
:: ShelleyBasedEra era
1518+
-> Ledger.PParams (ShelleyLedgerEra era)
1519+
-> TxOut CtxTx era
1520+
-> [TxOut CtxTx era]
1521+
-> Either (TxBodyErrorAutoBalance era) [TxOut CtxTx era]
1522+
checkAndIncludeChange sbe pp change rest = do
1523+
isChangeEmpty <- balanceCheck sbe pp change
1524+
if isChangeEmpty == Empty
1525+
then pure rest
1526+
else do
1527+
-- We append change at the end so a client can predict the indexes of the outputs.
1528+
-- Note that if this function will append change with 0 ADA, and non-ada assets in it.
1529+
pure $ rest <> [change]
15271530

15281531
checkMinUTxOValue
15291532
:: ShelleyBasedEra era
1530-
-> TxOut CtxTx era
15311533
-> Ledger.PParams (ShelleyLedgerEra era)
1534+
-> TxOut CtxTx era
15321535
-> Either (TxBodyErrorAutoBalance era) ()
1533-
checkMinUTxOValue sbe txout@(TxOut _ v _ _) bpp = do
1534-
let minUTxO = calculateMinimumUTxO sbe txout bpp
1536+
checkMinUTxOValue sbe bpp txout@(TxOut _ v _ _) = do
1537+
let minUTxO = calculateMinimumUTxO sbe bpp txout
15351538
if txOutValueToLovelace v >= minUTxO
15361539
then Right ()
15371540
else Left $ TxBodyErrorMinUTxONotMet (txOutInAnyEra (toCardanoEra sbe) txout) minUTxO
15381541

1542+
data IsEmpty = Empty | NonEmpty
1543+
deriving (Eq, Show)
1544+
15391545
balanceCheck
15401546
:: ShelleyBasedEra era
15411547
-> Ledger.PParams (ShelleyLedgerEra era)
15421548
-> TxOut CtxTx era
1543-
-> Either (TxBodyErrorAutoBalance era) ()
1549+
-> Either (TxBodyErrorAutoBalance era) IsEmpty
15441550
balanceCheck sbe bpparams txout@(TxOut _ balance _ _) = do
15451551
let outValue@(L.MaryValue coin multiAsset) = toMaryValue $ txOutValueToValue balance
15461552
isPositiveValue = L.pointwise (>) outValue mempty
15471553
if
1548-
| L.isZero outValue -> pure () -- empty TxOut
1554+
| L.isZero outValue -> pure Empty -- empty TxOut - ok, it's removed at the end
1555+
| L.isZero coin -> -- no ADA, just non-ADA assets
1556+
Left $
1557+
TxBodyErrorAdaBalanceTooSmall
1558+
(TxOutInAnyEra (toCardanoEra sbe) txout)
1559+
(calculateMinimumUTxO sbe bpparams txout)
1560+
coin
15491561
| not isPositiveValue -> Left $ TxBodyErrorBalanceNegative coin multiAsset
15501562
| otherwise ->
1551-
case checkMinUTxOValue sbe txout bpparams of
1563+
case checkMinUTxOValue sbe bpparams txout of
15521564
Left (TxBodyErrorMinUTxONotMet txOutAny minUTxO) ->
15531565
Left $ TxBodyErrorAdaBalanceTooSmall txOutAny minUTxO coin
15541566
Left err -> Left err
1555-
Right _ -> Right ()
1567+
Right _ -> Right NonEmpty
15561568

15571569
-- Calculation taken from validateInsufficientCollateral:
15581570
-- https://github.com/input-output-hk/cardano-ledger/blob/389b266d6226dedf3d2aec7af640b3ca4984c5ea/eras/alonzo/impl/src/Cardano/Ledger/Alonzo/Rules/Utxo.hs#L335
@@ -1893,10 +1905,10 @@ traverseScriptWitnesses =
18931905
calculateMinimumUTxO
18941906
:: HasCallStack
18951907
=> ShelleyBasedEra era
1896-
-> TxOut CtxTx era
18971908
-> Ledger.PParams (ShelleyLedgerEra era)
1909+
-> TxOut CtxTx era
18981910
-> L.Coin
1899-
calculateMinimumUTxO sbe txout pp =
1911+
calculateMinimumUTxO sbe pp txout =
19001912
shelleyBasedEraConstraints sbe $
19011913
let txOutWithMinCoin = L.setMinCoinTxOut pp (toShelleyTxOutAny sbe txout)
19021914
in txOutWithMinCoin ^. L.coinTxOutL

cardano-api/test/cardano-api-test/Test/Cardano/Api/Orphans.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
{-# LANGUAGE DataKinds #-}
22
{-# LANGUAGE FlexibleInstances #-}
33
{-# LANGUAGE GADTs #-}
4+
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
45
{-# LANGUAGE StandaloneDeriving #-}
56
{-# OPTIONS_GHC -Wno-orphans #-}
67

78
module Test.Cardano.Api.Orphans () where
89

910
import Cardano.Api.Shelley
1011

12+
import Cardano.Ledger.Mary.Value qualified as L
13+
14+
import Data.String (IsString (..))
15+
1116
import Test.Cardano.Crypto.Orphans ()
1217

1318
-- Signing Key instances
@@ -29,3 +34,5 @@ deriving instance Eq (SigningKey GenesisUTxOKey)
2934
deriving instance Eq (SigningKey KesKey)
3035

3136
deriving instance Eq (SigningKey VrfKey)
37+
38+
deriving instance IsString L.AssetName

0 commit comments

Comments
 (0)