Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix transaction autobalancing when deregistering credential #718

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 48 additions & 37 deletions cardano-api/internal/Cardano/Api/Fees.hs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import GHC.Exts (IsList (..))
import GHC.Stack
import Lens.Micro ((.~), (^.))

-- | Type synonym for logs returned by the ledger's @evalTxExUnitsWithLogs@ function.
Expand Down Expand Up @@ -190,7 +191,8 @@ instance Error (TxFeeEstimationError era) where
-- | Use when you do not have access to the UTxOs you intend to spend
estimateBalancedTxBody
:: forall era
. MaryEraOnwards era
. HasCallStack
=> MaryEraOnwards era
-> TxBodyContent BuildTx era
-> L.PParams (ShelleyLedgerEra era)
-> Set PoolId
Expand Down Expand Up @@ -276,9 +278,9 @@ estimateBalancedTxBody
, negateValue (lovelaceToValue totalDeposits)
]

let change = toLedgerValue w $ calculateChangeValue sbe availableUTxOValue txbodycontent1
let partialChange = toLedgerValue w $ calculatePartialChangeValue sbe availableUTxOValue txbodycontent1
maxLovelaceChange = L.Coin (2 ^ (64 :: Integer)) - 1
changeWithMaxLovelace = change & A.adaAssetL sbe .~ maxLovelaceChange
changeWithMaxLovelace = partialChange & A.adaAssetL sbe .~ maxLovelaceChange
changeTxOut =
forShelleyBasedEraInEon
sbe
Expand Down Expand Up @@ -1004,6 +1006,7 @@ data FeeEstimationMode era
makeTransactionBodyAutoBalance
:: forall era
. ()
=> HasCallStack
=> ShelleyBasedEra era
-> SystemStart
-> LedgerEpochInfo
Expand All @@ -1018,7 +1021,7 @@ makeTransactionBodyAutoBalance
-- ^ Map of all deposits for drep credentials that are being
-- unregistered in this transaction
-> UTxO era
-- ^ Just the transaction inputs, not the entire 'UTxO'.
-- ^ Just the transaction inputs (including reference and collateral ones), not the entire 'UTxO'.
-> TxBodyContent BuildTx era
-> AddressInEra era
-- ^ Change address
Expand All @@ -1044,18 +1047,41 @@ makeTransactionBodyAutoBalance
-- 3. update tx with fees
-- 4. balance the transaction and update tx change output

let totalValueAtSpendableUTxO = fromLedgerValue sbe . calculateIncomingUTxOValue . Map.elems $ unUTxO utxo
change =
-- UTXO inputs, which inclue also non-ada assets
let totalValueAtUTxO =
fromLedgerValue sbe . calculateIncomingUTxOValue . Map.elems $ unUTxO utxo
-- this is a partial change: it does not include deposits, but we need to have non-ada assets in it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to have non-ada assets in it?

-- from utxo and inputs
partialChange =
monoidForEraInEon (toCardanoEra sbe) $ \w ->
toLedgerValue w $ calculateChangeValue sbe totalValueAtSpendableUTxO txbodycontent
toLedgerValue w $ calculatePartialChangeValue sbe totalValueAtUTxO txbodycontent

-- For the purpose of fees and execution units calculation we just need to make a txbody larger than
-- strictly necessary. We do not need the right values for the fee or change output. We use
-- "big enough" values for the change output and set so that the CBOR encoding size of the tx will
-- be big enough to cover the size of the final output and fee. Yes this means this current code will
-- only work for final fee of less than around 4000 ada (2^32-1 lovelace) and change output of less
-- than around 18 trillion ada (2^64-1 lovelace). However, since at this point we know how much
-- non-Ada change to give we can use the true values for that.
let maxLovelaceChange = L.Coin (2 ^ (64 :: Integer)) - 1
let maxLovelaceFee = L.Coin (2 ^ (32 :: Integer) - 1)
let changeWithMaxLovelace = partialChange & A.adaAssetL sbe .~ maxLovelaceChange
let changeTxOut =
forShelleyBasedEraInEon
sbe
(lovelaceToTxOutValue sbe maxLovelaceChange)
(\w -> maryEraOnwardsConstraints w $ TxOutValueShelleyBased sbe changeWithMaxLovelace)

-- Tx body used only for evaluating execution units, txout exact values do not matter much here. We also
-- use 'maxLovelaceChange' in txout to avoid ending up with negative change accidentally (this could
-- happen if there's a big certificate deposit being returned for example).
txbody0 <-
first TxBodyError
$ createTransactionBody
sbe
$ txbodycontent
& modTxOuts
(<> [TxOut changeaddr (TxOutValueShelleyBased sbe change) TxOutDatumNone ReferenceScriptNone])
(<> [TxOut changeaddr changeTxOut TxOutDatumNone ReferenceScriptNone])
exUnitsMapWithLogs <-
first TxBodyErrorValidityInterval $
evaluateTransactionExecutionUnits
Expand All @@ -1078,25 +1104,7 @@ makeTransactionBodyAutoBalance

txbodycontent1 <- substituteExecutionUnits exUnitsMap' txbodycontent

-- Make a txbody that we will use for calculating the fees. For the purpose
-- of fees we just need to make a txbody of the right size in bytes. We do
-- not need the right values for the fee or change output. We use
-- "big enough" values for the change output and set so that the CBOR
-- encoding size of the tx will be big enough to cover the size of the final
-- output and fee. Yes this means this current code will only work for
-- final fee of less than around 4000 ada (2^32-1 lovelace) and change output
-- of less than around 18 trillion ada (2^64-1 lovelace).
-- However, since at this point we know how much non-Ada change to give
-- we can use the true values for that.
let maxLovelaceChange = L.Coin (2 ^ (64 :: Integer)) - 1
let maxLovelaceFee = L.Coin (2 ^ (32 :: Integer) - 1)
let changeWithMaxLovelace = change & A.adaAssetL sbe .~ maxLovelaceChange
let changeTxOut =
forShelleyBasedEraInEon
sbe
(lovelaceToTxOutValue sbe maxLovelaceChange)
(\w -> maryEraOnwardsConstraints w $ TxOutValueShelleyBased sbe changeWithMaxLovelace)

-- Make a txbody that we will use for calculating the fees.
let (dummyCollRet, dummyTotColl) = maybeDummyTotalCollAndCollReturnOutput sbe txbodycontent changeaddr
txbody1 <-
first TxBodyError $ -- TODO: impossible to fail now
Expand Down Expand Up @@ -1313,17 +1321,19 @@ calcReturnAndTotalCollateral w fee pp' TxInsCollateral{} txReturnCollateral txTo
, totalCollateral
)

calculateCreatedUTOValue
:: ShelleyBasedEra era -> TxBodyContent build era -> Value
calculateCreatedUTOValue sbe txbodycontent =
mconcat [fromLedgerValue sbe v | (TxOut _ (TxOutValueShelleyBased _ v) _ _) <- txOuts txbodycontent]

calculateChangeValue
:: ShelleyBasedEra era -> Value -> TxBodyContent build era -> Value
calculateChangeValue sbe incoming txbodycontent =
let outgoing = calculateCreatedUTOValue sbe txbodycontent
-- | Calculate the partial change - this does not include certificates' deposits
calculatePartialChangeValue
:: ShelleyBasedEra era
-> Value
-> TxBodyContent build era
-> Value
calculatePartialChangeValue sbe incoming txbodycontent =
let outgoing = newUtxoValue
mintedValue = txMintValueToValue $ txMintValue txbodycontent
in mconcat [incoming, mintedValue, negateValue outgoing]
where
newUtxoValue =
mconcat [fromLedgerValue sbe v | (TxOut _ (TxOutValueShelleyBased _ v) _ _) <- txOuts txbodycontent]

-- | This is used in the balance calculation in the event where
-- the user does not supply the UTxO(s) they intend to spend
Expand Down Expand Up @@ -1578,7 +1588,8 @@ traverseScriptWitnesses =
traverse (\(item, eRes) -> eRes >>= (\res -> Right (item, res)))

calculateMinimumUTxO
:: ShelleyBasedEra era
:: HasCallStack
=> ShelleyBasedEra era
-> TxOut CtxTx era
-> Ledger.PParams (ShelleyLedgerEra era)
-> L.Coin
Expand Down
5 changes: 4 additions & 1 deletion cardano-api/internal/Cardano/Api/Tx/Body.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,7 @@ instance Error TxBodyError where

createTransactionBody
:: ()
=> HasCallStack
=> ShelleyBasedEra era
-> TxBodyContent BuildTx era
-> Either TxBodyError (TxBody era)
Expand Down Expand Up @@ -2633,7 +2634,8 @@ convTotalCollateral txTotalCollateral =

convTxOuts
:: forall ctx era ledgerera
. ShelleyLedgerEra era ~ ledgerera
. HasCallStack
=> ShelleyLedgerEra era ~ ledgerera
=> ShelleyBasedEra era
-> [TxOut ctx era]
-> Seq.StrictSeq (Ledger.TxOut ledgerera)
Expand Down Expand Up @@ -2816,6 +2818,7 @@ guardShelleyTxInsOverflow txIns = do
-- all eras
mkCommonTxBody
:: ()
=> HasCallStack
=> ShelleyBasedEra era
-> TxIns BuildTx era
-> [TxOut ctx era]
Expand Down
4 changes: 3 additions & 1 deletion cardano-api/internal/Cardano/Api/Tx/Compatible.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import Data.Maybe.Strict
import Data.Monoid
import qualified Data.Sequence.Strict as Seq
import GHC.Exts (IsList (..))
import GHC.Stack
import Lens.Micro hiding (ix)

data AnyProtocolUpdate era where
Expand Down Expand Up @@ -205,7 +206,8 @@ createCompatibleSignedTx sbe ins outs witnesses txFee' anyProtocolUpdate anyVote
.~ shelleyBootstrapWitnesses

createCommonTxBody
:: ShelleyBasedEra era
:: HasCallStack
=> ShelleyBasedEra era
-> [TxIn]
-> [TxOut ctx era]
-> Lovelace
Expand Down
Loading
Loading