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 collateral balancing when building transaction #631

Merged
merged 2 commits into from
Sep 16, 2024
Merged
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
25 changes: 13 additions & 12 deletions cardano-api/cardano-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -318,16 +318,17 @@ test-suite cardano-api-test
cardano-ledger-binary,
cardano-ledger-core:{cardano-ledger-core, testlib} >=1.14,
cardano-ledger-mary,
cardano-ledger-shelley,
cardano-protocol-tpraos,
cardano-slotting,
cborg,
containers,
data-default,
directory,
hedgehog >=1.1,
hedgehog-extras,
hedgehog-quickcheck,
interpolatedstring-perl6,
microlens,
mtl,
ouroboros-consensus,
ouroboros-consensus-cardano,
Expand All @@ -340,7 +341,11 @@ test-suite cardano-api-test
time,

other-modules:
Test.Cardano.Api.Address
Test.Cardano.Api.Bech32
Test.Cardano.Api.CBOR
Test.Cardano.Api.Crypto
Test.Cardano.Api.Envelope
Test.Cardano.Api.EpochLeadership
Test.Cardano.Api.Eras
Test.Cardano.Api.Genesis
Expand All @@ -349,17 +354,13 @@ test-suite cardano-api-test
Test.Cardano.Api.KeysByron
Test.Cardano.Api.Ledger
Test.Cardano.Api.Metadata
Test.Cardano.Api.Ord
Test.Cardano.Api.Orphans
Test.Cardano.Api.ProtocolParameters
Test.Cardano.Api.Typed.Address
Test.Cardano.Api.Typed.Bech32
Test.Cardano.Api.Typed.CBOR
Test.Cardano.Api.Typed.Envelope
Test.Cardano.Api.Typed.JSON
Test.Cardano.Api.Typed.Ord
Test.Cardano.Api.Typed.Orphans
Test.Cardano.Api.Typed.RawBytes
Test.Cardano.Api.Typed.TxBody
Test.Cardano.Api.Typed.Value
Test.Cardano.Api.RawBytes
Test.Cardano.Api.Transaction.Autobalance
Test.Cardano.Api.TxBody
Test.Cardano.Api.Value

ghc-options:
-threaded
Expand Down Expand Up @@ -412,6 +413,6 @@ test-suite cardano-api-golden
Test.Golden.Cardano.Api.Genesis
Test.Golden.Cardano.Api.Ledger
Test.Golden.Cardano.Api.ProtocolParameters
Test.Golden.Cardano.Api.Typed.Script
Test.Golden.Cardano.Api.Script
Test.Golden.Cardano.Api.Value
Test.Golden.ErrorsSpec
4 changes: 2 additions & 2 deletions cardano-api/gen/Test/Gen/Cardano/Api/Typed.hs
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,8 @@ genValueForTxOut sbe = do
caseShelleyToAllegraOrMaryEraOnwards
(const (pure ada))
( \w -> do
v <- genValue w genAssetId genPositiveQuantity
pure $ ada <> v
v <- Gen.list (Range.constant 0 5) $ genValue w genAssetId genPositiveQuantity
pure $ ada <> mconcat v
)
sbe

Expand Down
6 changes: 6 additions & 0 deletions cardano-api/internal/Cardano/Api/Eon/ConwayEraOnwards.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ module Cardano.Api.Eon.ConwayEraOnwards
( ConwayEraOnwards (..)
, conwayEraOnwardsConstraints
, conwayEraOnwardsToShelleyBasedEra
, conwayEraOnwardsToBabbageEraOnwards
, ConwayEraOnwardsConstraints
, IsConwayBasedEra (..)
)
where

import Cardano.Api.Eon.BabbageEraOnwards
import Cardano.Api.Eon.ShelleyBasedEra
import Cardano.Api.Eras.Core
import Cardano.Api.Modes
Expand Down Expand Up @@ -114,6 +116,10 @@ conwayEraOnwardsToShelleyBasedEra :: ConwayEraOnwards era -> ShelleyBasedEra era
conwayEraOnwardsToShelleyBasedEra = \case
ConwayEraOnwardsConway -> ShelleyBasedEraConway

conwayEraOnwardsToBabbageEraOnwards :: ConwayEraOnwards era -> BabbageEraOnwards era
Copy link
Contributor

Choose a reason for hiding this comment

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

Yikes

conwayEraOnwardsToBabbageEraOnwards = \case
ConwayEraOnwardsConway -> BabbageEraOnwardsConway

class IsConwayBasedEra era where
conwayBasedEra :: ConwayEraOnwards era

Expand Down
127 changes: 66 additions & 61 deletions cardano-api/internal/Cardano/Api/Fees.hs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module Cardano.Api.Fees
, estimateBalancedTxBody
, estimateOrCalculateBalancedTxBody
, makeTransactionBodyAutoBalance
, calcReturnAndTotalCollateral
, AutoBalanceError (..)
, BalancedTxBody (..)
, FeeEstimationMode (..)
Expand Down Expand Up @@ -81,6 +82,7 @@ import Cardano.Ledger.Credential as Ledger (Credential)
import qualified Cardano.Ledger.Crypto as Ledger
import qualified Cardano.Ledger.Keys as Ledger
import qualified Cardano.Ledger.Plutus.Language as Plutus
import qualified Cardano.Ledger.Val as L
import qualified Ouroboros.Consensus.HardFork.History as Consensus
import qualified PlutusLedgerApi.V1 as Plutus

Expand Down Expand Up @@ -325,7 +327,7 @@ estimateBalancedTxBody
(txReturnCollateral txbodycontent)
(txTotalCollateral txbodycontent)
changeaddr
totalPotentialCollateral
(A.mkAdaValue sbe totalPotentialCollateral)
)
sbe

Expand Down Expand Up @@ -1070,10 +1072,8 @@ makeTransactionBodyAutoBalance
availableEra
$ obtainCommonConstraints availableEra
$ txbodycontent
{ txOuts =
txOuts txbodycontent
<> [TxOut changeaddr (TxOutValueShelleyBased sbe change) TxOutDatumNone ReferenceScriptNone]
}
& modTxOuts
(<> [TxOut changeaddr (TxOutValueShelleyBased sbe change) TxOutDatumNone ReferenceScriptNone])
exUnitsMapWithLogs <-
first TxBodyErrorValidityInterval
$ evaluateTransactionExecutionUnitsShelley
Expand Down Expand Up @@ -1143,21 +1143,23 @@ makeTransactionBodyAutoBalance
(retColl, reqCol) =
caseShelleyToAlonzoOrBabbageEraOnwards
(const (TxReturnCollateralNone, TxTotalCollateralNone))
( \w ->
let collIns = case txInsCollateral txbodycontent of
TxInsCollateral _ collIns' -> collIns'
TxInsCollateralNone -> mempty
collateralOuts = catMaybes [Map.lookup txin (unUTxO utxo) | txin <- collIns]
totalPotentialCollateral = mconcat $ map (\(TxOut _ txOutVal _ _) -> txOutValueToLovelace txOutVal) collateralOuts
in calcReturnAndTotalCollateral
w
fee
pp
(txInsCollateral txbodycontent)
(txReturnCollateral txbodycontent)
(txTotalCollateral txbodycontent)
changeaddr
totalPotentialCollateral
( \w -> do
let totalPotentialCollateral =
mconcat
[ txOutValue
| TxInsCollateral _ collInputs <- pure $ txInsCollateral txbodycontent
, collTxIn <- collInputs
, Just (TxOut _ (TxOutValueShelleyBased _ txOutValue) _ _) <- pure $ Map.lookup collTxIn (unUTxO utxo)
]
calcReturnAndTotalCollateral
w
fee
pp
(txInsCollateral txbodycontent)
(txReturnCollateral txbodycontent)
(txTotalCollateral txbodycontent)
changeaddr
totalPotentialCollateral
)
sbe

Expand Down Expand Up @@ -1295,49 +1297,52 @@ calcReturnAndTotalCollateral
-- ^ From the initial TxBodyContent
-> AddressInEra era
-- ^ Change address
-> Coin
-- ^ Total available collateral in lovelace
-> L.Value (ShelleyLedgerEra era)
-- ^ Total available collateral (can include non-ada)
-> (TxReturnCollateral CtxTx era, TxTotalCollateral era)
calcReturnAndTotalCollateral _ _ _ TxInsCollateralNone _ _ _ _ = (TxReturnCollateralNone, TxTotalCollateralNone)
calcReturnAndTotalCollateral retColSup fee pp' TxInsCollateral{} txReturnCollateral txTotalCollateral cAddr totalAvailableAda =
do
let colPerc = pp' ^. Ledger.ppCollateralPercentageL
-- We must first figure out how much lovelace we have committed
-- as collateral and we must determine if we have enough lovelace at our
-- collateral tx inputs to cover the tx
totalCollateralLovelace = totalAvailableAda
requiredCollateral@(L.Coin reqAmt) = fromIntegral colPerc * fee
totalCollateral =
TxTotalCollateral retColSup . L.rationalToCoinViaCeiling $
reqAmt % 100
-- Why * 100? requiredCollateral is the product of the collateral percentage and the tx fee
-- We choose to multiply 100 rather than divide by 100 to make the calculation
-- easier to manage. At the end of the calculation we then use % 100 to perform our division
-- and round the returnCollateral down which has the effect of potentially slightly
-- overestimating the required collateral.
L.Coin amt = totalCollateralLovelace * 100 - requiredCollateral
returnCollateral = L.rationalToCoinViaFloor $ amt % 100
case (txReturnCollateral, txTotalCollateral) of
(rc@TxReturnCollateral{}, tc@TxTotalCollateral{}) ->
(rc, tc)
(rc@TxReturnCollateral{}, TxTotalCollateralNone) ->
(rc, TxTotalCollateralNone)
(TxReturnCollateralNone, tc@TxTotalCollateral{}) ->
(TxReturnCollateralNone, tc)
(TxReturnCollateralNone, TxTotalCollateralNone) ->
if totalCollateralLovelace * 100 >= requiredCollateral
then
( TxReturnCollateral
retColSup
( TxOut
cAddr
(lovelaceToTxOutValue (babbageEraOnwardsToShelleyBasedEra retColSup) returnCollateral)
TxOutDatumNone
ReferenceScriptNone
)
, totalCollateral
)
else (TxReturnCollateralNone, TxTotalCollateralNone)
calcReturnAndTotalCollateral w fee pp' TxInsCollateral{} txReturnCollateral txTotalCollateral cAddr totalAvailableCollateral = babbageEraOnwardsConstraints w $ do
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this function exist in cardano-ledger? Or any function that can assist in this calculation? The more we can call from ledger the better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Something similar is implemented here, but it's a validation, not a calculation, so we can't just reuse it. https://github.com/IntersectMBO/cardano-ledger/blob/master/eras/babbage/impl/src/Cardano/Ledger/Babbage/Rules/Utxo.hs#L307

let sbe = babbageEraOnwardsToShelleyBasedEra w
colPerc = pp' ^. Ledger.ppCollateralPercentageL
-- We must first figure out how much lovelace we have committed
-- as collateral and we must determine if we have enough lovelace at our
-- collateral tx inputs to cover the tx
totalCollateralLovelace = totalAvailableCollateral ^. A.adaAssetL sbe
requiredCollateral@(L.Coin reqAmt) = fromIntegral colPerc * fee
totalCollateral =
TxTotalCollateral w . L.rationalToCoinViaCeiling $
reqAmt % 100
-- Why * 100? requiredCollateral is the product of the collateral percentage and the tx fee
-- We choose to multiply 100 rather than divide by 100 to make the calculation
-- easier to manage. At the end of the calculation we then use % 100 to perform our division
-- and round the returnCollateral down which has the effect of potentially slightly
-- overestimating the required collateral.
L.Coin returnCollateralAmount = totalCollateralLovelace * 100 - requiredCollateral
returnAdaCollateral = A.mkAdaValue sbe $ L.rationalToCoinViaFloor $ returnCollateralAmount % 100
-- non-ada collateral is not used, so just return it as is in the return collateral output
nonAdaCollateral = L.modifyCoin (const mempty) totalAvailableCollateral
returnCollateral = returnAdaCollateral <> nonAdaCollateral
case (txReturnCollateral, txTotalCollateral) of
(rc@TxReturnCollateral{}, tc@TxTotalCollateral{}) ->
(rc, tc)
(rc@TxReturnCollateral{}, TxTotalCollateralNone) ->
(rc, TxTotalCollateralNone)
(TxReturnCollateralNone, tc@TxTotalCollateral{}) ->
(TxReturnCollateralNone, tc)
(TxReturnCollateralNone, TxTotalCollateralNone)
| returnCollateralAmount < 0 ->
(TxReturnCollateralNone, TxTotalCollateralNone)
| otherwise ->
( TxReturnCollateral
w
( TxOut
cAddr
(TxOutValueShelleyBased sbe returnCollateral)
TxOutDatumNone
ReferenceScriptNone
)
, totalCollateral
)

calculateCreatedUTOValue
:: ShelleyBasedEra era -> TxBodyContent build era -> Value
Expand Down
1 change: 1 addition & 0 deletions cardano-api/src/Cardano/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ module Cardano.Api
, ConwayEraOnwards (..)
, conwayEraOnwardsConstraints
, conwayEraOnwardsToShelleyBasedEra
, conwayEraOnwardsToBabbageEraOnwards
, IsConwayBasedEra (..)

-- * Era case handling
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{-# LANGUAGE TypeApplications #-}

module Test.Golden.Cardano.Api.Typed.Script
module Test.Golden.Cardano.Api.Script
( test_golden_SimpleScriptV1_All
, test_golden_SimpleScriptV1_Any
, test_golden_SimpleScriptV1_MofN
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{-# LANGUAGE FlexibleContexts #-}

module Test.Cardano.Api.Typed.Address
module Test.Cardano.Api.Address
( tests
)
where
Expand All @@ -11,7 +11,7 @@ import qualified Data.Aeson as Aeson

import Test.Gen.Cardano.Api.Typed (genAddressByron, genAddressShelley)

import Test.Cardano.Api.Typed.Orphans ()
import Test.Cardano.Api.Orphans ()

import Hedgehog (Property)
import qualified Hedgehog as H
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Test.Cardano.Api.Typed.Bech32
module Test.Cardano.Api.Bech32
( tests
)
where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
-- TODO remove when serialiseTxLedgerCddl is removed
{-# OPTIONS_GHC -Wno-deprecations #-}

module Test.Cardano.Api.Typed.CBOR
module Test.Cardano.Api.CBOR
( tests
)
where
Expand All @@ -16,7 +16,7 @@ import Data.Proxy (Proxy (..))

import Test.Gen.Cardano.Api.Typed

import Test.Cardano.Api.Typed.Orphans ()
import Test.Cardano.Api.Orphans ()

import Hedgehog (Property, forAll, property, tripping)
import qualified Hedgehog as H
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}

module Test.Cardano.Api.Typed.Envelope
module Test.Cardano.Api.Envelope
( tests
)
where
Expand All @@ -10,7 +10,7 @@ import Cardano.Api

import Test.Gen.Cardano.Api.Typed

import Test.Cardano.Api.Typed.Orphans ()
import Test.Cardano.Api.Orphans ()

import Hedgehog (Property)
import qualified Hedgehog as H
Expand Down
17 changes: 17 additions & 0 deletions cardano-api/test/cardano-api-test/Test/Cardano/Api/Json.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Test.Cardano.Api.Json
)
where

import Cardano.Api
import Cardano.Api.Orphans ()
import Cardano.Api.Shelley

Expand All @@ -14,8 +15,11 @@ import Data.Aeson (eitherDecode, encode)
import Test.Gen.Cardano.Api (genAlonzoGenesis)
import Test.Gen.Cardano.Api.Typed

import Test.Cardano.Api.Orphans ()

import Hedgehog (Property, forAll, tripping)
import qualified Hedgehog as H
import qualified Hedgehog.Gen as Gen
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.Hedgehog (testProperty)

Expand Down Expand Up @@ -56,6 +60,17 @@ prop_json_roundtrip_scriptdata_detailed_json = H.property $ do
sData <- forAll genHashableScriptData
tripping sData scriptDataToJsonDetailedSchema scriptDataFromJsonDetailedSchema

prop_roundtrip_praos_nonce_JSON :: Property
prop_roundtrip_praos_nonce_JSON = H.property $ do
pNonce <- forAll $ Gen.just genMaybePraosNonce
tripping pNonce encode eitherDecode

prop_roundtrip_protocol_parameters_JSON :: Property
prop_roundtrip_protocol_parameters_JSON = H.property $ do
AnyCardanoEra era <- forAll $ Gen.element [minBound .. maxBound]
pp <- forAll (genProtocolParameters era)
tripping pp encode eitherDecode

tests :: TestTree
tests =
testGroup
Expand All @@ -67,4 +82,6 @@ tests =
, testProperty "json roundtrip txout tx context" prop_json_roundtrip_txout_tx_context
, testProperty "json roundtrip txout utxo context" prop_json_roundtrip_txout_utxo_context
, testProperty "json roundtrip scriptdata detailed json" prop_json_roundtrip_scriptdata_detailed_json
, testProperty "json roundtrip praos nonce" prop_roundtrip_praos_nonce_JSON
, testProperty "json roundtrip protocol parameters" prop_roundtrip_protocol_parameters_JSON
]
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ where

import Cardano.Api (AsType (AsByronKey, AsSigningKey), Key (deterministicSigningKey))

import Test.Cardano.Api.Typed.Orphans ()
import Test.Cardano.Api.Orphans ()
import qualified Test.Gen.Cardano.Crypto.Seed as Gen

import Hedgehog (Property)
Expand Down
Loading
Loading