Skip to content

Commit 3487606

Browse files
committed
Improve autobalancing errors when change has no lovelace
1 parent 09c7498 commit 3487606

File tree

2 files changed

+50
-31
lines changed
  • cardano-api

2 files changed

+50
-31
lines changed

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

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -643,13 +643,16 @@ estimateBalancedTxBody
643643
balance =
644644
evaluateTransactionBalance sbe pparams poolids stakeDelegDeposits drepDelegDeposits fakeUTxO txbody2
645645
balanceTxOut = TxOut changeaddr balance TxOutDatumNone ReferenceScriptNone
646-
-- check if the balance is positive or negative
647-
-- in one case we can produce change, in the other the inputs are insufficient
648-
first TxFeeEstimationBalanceError $ balanceCheck sbe pparams balanceTxOut
649646

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

654657
-- Step 7.
655658

@@ -661,10 +664,7 @@ estimateBalancedTxBody
661664
let finalTxBodyContent =
662665
txbodycontent1
663666
{ txFee = TxFeeExplicit sbe fee
664-
, txOuts =
665-
accountForNoChange
666-
balanceTxOut
667-
(txOuts txbodycontent)
667+
, txOuts = finalTxOuts
668668
, txReturnCollateral = retColl
669669
, txTotalCollateral = reqCol
670670
}
@@ -1471,11 +1471,10 @@ makeTransactionBodyAutoBalance
14711471
}
14721472
let balance = evaluateTransactionBalance sbe pp poolids stakeDelegDeposits drepDelegDeposits utxo txbody2
14731473
balanceTxOut = TxOut changeaddr balance TxOutDatumNone ReferenceScriptNone
1474-
forM_ (txOuts txbodycontent1) $ \txout -> checkMinUTxOValue sbe txout pp
1474+
forM_ (txOuts txbodycontent1) $ \txout -> checkMinUTxOValue sbe pp txout
14751475

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

14801479
-- TODO: we could add the extra fee for the CBOR encoding of the change,
14811480
-- now that we know the magnitude of the change: i.e. 1-8 bytes extra.
@@ -1487,10 +1486,7 @@ makeTransactionBodyAutoBalance
14871486
let finalTxBodyContent =
14881487
txbodycontent1
14891488
{ txFee = TxFeeExplicit sbe fee
1490-
, txOuts =
1491-
accountForNoChange
1492-
balanceTxOut
1493-
(txOuts txbodycontent)
1489+
, txOuts = finalTxOuts
14941490
, txReturnCollateral = retColl
14951491
, txTotalCollateral = reqCol
14961492
}
@@ -1510,26 +1506,36 @@ makeTransactionBodyAutoBalance
15101506
era :: CardanoEra era
15111507
era = toCardanoEra sbe
15121508

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

15261532
checkMinUTxOValue
15271533
:: ShelleyBasedEra era
1528-
-> TxOut CtxTx era
15291534
-> Ledger.PParams (ShelleyLedgerEra era)
1535+
-> TxOut CtxTx era
15301536
-> Either (TxBodyErrorAutoBalance era) ()
1531-
checkMinUTxOValue sbe txout@(TxOut _ v _ _) bpp = do
1532-
let minUTxO = calculateMinimumUTxO sbe txout bpp
1537+
checkMinUTxOValue sbe bpp txout@(TxOut _ v _ _) = do
1538+
let minUTxO = calculateMinimumUTxO sbe bpp txout
15331539
if txOutValueToLovelace v >= minUTxO
15341540
then Right ()
15351541
else Left $ TxBodyErrorMinUTxONotMet (txOutInAnyEra (toCardanoEra sbe) txout) minUTxO
@@ -1543,10 +1549,16 @@ balanceCheck sbe bpparams txout@(TxOut _ balance _ _) = do
15431549
let outValue@(L.MaryValue coin multiAsset) = toMaryValue $ txOutValueToValue balance
15441550
isPositiveValue = L.pointwise (>) outValue mempty
15451551
if
1546-
| L.isZero outValue -> pure () -- empty TxOut
1552+
| L.isZero outValue -> pure () -- empty TxOut - ok, it's removed at the end
1553+
| L.isZero coin -> -- no ADA, just non-ADA assets
1554+
Left $
1555+
TxBodyErrorAdaBalanceTooSmall
1556+
(TxOutInAnyEra (toCardanoEra sbe) txout)
1557+
(calculateMinimumUTxO sbe bpparams txout)
1558+
coin
15471559
| not isPositiveValue -> Left $ TxBodyErrorBalanceNegative coin multiAsset
15481560
| otherwise ->
1549-
case checkMinUTxOValue sbe txout bpparams of
1561+
case checkMinUTxOValue sbe bpparams txout of
15501562
Left (TxBodyErrorMinUTxONotMet txOutAny minUTxO) ->
15511563
Left $ TxBodyErrorAdaBalanceTooSmall txOutAny minUTxO coin
15521564
Left err -> Left err
@@ -1891,10 +1903,10 @@ traverseScriptWitnesses =
18911903
calculateMinimumUTxO
18921904
:: HasCallStack
18931905
=> ShelleyBasedEra era
1894-
-> TxOut CtxTx era
18951906
-> Ledger.PParams (ShelleyLedgerEra era)
1907+
-> TxOut CtxTx era
18961908
-> L.Coin
1897-
calculateMinimumUTxO sbe txout pp =
1909+
calculateMinimumUTxO sbe pp txout =
18981910
shelleyBasedEraConstraints sbe $
18991911
let txOutWithMinCoin = L.setMinCoinTxOut pp (toShelleyTxOutAny sbe txout)
19001912
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)