diff --git a/cardano-api/cardano-api.cabal b/cardano-api/cardano-api.cabal index 010bb3da82..d5dbdb8d1f 100644 --- a/cardano-api/cardano-api.cabal +++ b/cardano-api/cardano-api.cabal @@ -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, @@ -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 @@ -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 @@ -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 diff --git a/cardano-api/gen/Test/Gen/Cardano/Api/Typed.hs b/cardano-api/gen/Test/Gen/Cardano/Api/Typed.hs index 2f432702fa..531f0818ff 100644 --- a/cardano-api/gen/Test/Gen/Cardano/Api/Typed.hs +++ b/cardano-api/gen/Test/Gen/Cardano/Api/Typed.hs @@ -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 diff --git a/cardano-api/internal/Cardano/Api/Eon/ConwayEraOnwards.hs b/cardano-api/internal/Cardano/Api/Eon/ConwayEraOnwards.hs index 06fa98fc0a..d991fdfbd3 100644 --- a/cardano-api/internal/Cardano/Api/Eon/ConwayEraOnwards.hs +++ b/cardano-api/internal/Cardano/Api/Eon/ConwayEraOnwards.hs @@ -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 @@ -114,6 +116,10 @@ conwayEraOnwardsToShelleyBasedEra :: ConwayEraOnwards era -> ShelleyBasedEra era conwayEraOnwardsToShelleyBasedEra = \case ConwayEraOnwardsConway -> ShelleyBasedEraConway +conwayEraOnwardsToBabbageEraOnwards :: ConwayEraOnwards era -> BabbageEraOnwards era +conwayEraOnwardsToBabbageEraOnwards = \case + ConwayEraOnwardsConway -> BabbageEraOnwardsConway + class IsConwayBasedEra era where conwayBasedEra :: ConwayEraOnwards era diff --git a/cardano-api/internal/Cardano/Api/Fees.hs b/cardano-api/internal/Cardano/Api/Fees.hs index 7f736b6f53..eb0b917714 100644 --- a/cardano-api/internal/Cardano/Api/Fees.hs +++ b/cardano-api/internal/Cardano/Api/Fees.hs @@ -30,6 +30,7 @@ module Cardano.Api.Fees , estimateBalancedTxBody , estimateOrCalculateBalancedTxBody , makeTransactionBodyAutoBalance + , calcReturnAndTotalCollateral , AutoBalanceError (..) , BalancedTxBody (..) , FeeEstimationMode (..) @@ -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 @@ -325,7 +327,7 @@ estimateBalancedTxBody (txReturnCollateral txbodycontent) (txTotalCollateral txbodycontent) changeaddr - totalPotentialCollateral + (A.mkAdaValue sbe totalPotentialCollateral) ) sbe @@ -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 @@ -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 @@ -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 + 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 diff --git a/cardano-api/src/Cardano/Api.hs b/cardano-api/src/Cardano/Api.hs index c168cc0cc9..40c7f270f7 100644 --- a/cardano-api/src/Cardano/Api.hs +++ b/cardano-api/src/Cardano/Api.hs @@ -104,6 +104,7 @@ module Cardano.Api , ConwayEraOnwards (..) , conwayEraOnwardsConstraints , conwayEraOnwardsToShelleyBasedEra + , conwayEraOnwardsToBabbageEraOnwards , IsConwayBasedEra (..) -- * Era case handling diff --git a/cardano-api/test/cardano-api-golden/Test/Golden/Cardano/Api/Typed/Script.hs b/cardano-api/test/cardano-api-golden/Test/Golden/Cardano/Api/Script.hs similarity index 99% rename from cardano-api/test/cardano-api-golden/Test/Golden/Cardano/Api/Typed/Script.hs rename to cardano-api/test/cardano-api-golden/Test/Golden/Cardano/Api/Script.hs index ff433ac31c..f4eceeb706 100644 --- a/cardano-api/test/cardano-api-golden/Test/Golden/Cardano/Api/Typed/Script.hs +++ b/cardano-api/test/cardano-api-golden/Test/Golden/Cardano/Api/Script.hs @@ -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 diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Address.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Address.hs similarity index 95% rename from cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Address.hs rename to cardano-api/test/cardano-api-test/Test/Cardano/Api/Address.hs index 0f2ebe33aa..87a53f13b2 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Address.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Address.hs @@ -1,6 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} -module Test.Cardano.Api.Typed.Address +module Test.Cardano.Api.Address ( tests ) where @@ -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 diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Bech32.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Bech32.hs similarity index 95% rename from cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Bech32.hs rename to cardano-api/test/cardano-api-test/Test/Cardano/Api/Bech32.hs index 5a3179fd69..0627c9dccd 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Bech32.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Bech32.hs @@ -1,4 +1,4 @@ -module Test.Cardano.Api.Typed.Bech32 +module Test.Cardano.Api.Bech32 ( tests ) where diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/CBOR.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/CBOR.hs similarity index 99% rename from cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/CBOR.hs rename to cardano-api/test/cardano-api-test/Test/Cardano/Api/CBOR.hs index 217caa9e73..3bf25a923e 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/CBOR.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/CBOR.hs @@ -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 @@ -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 diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Envelope.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Envelope.hs similarity index 98% rename from cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Envelope.hs rename to cardano-api/test/cardano-api-test/Test/Cardano/Api/Envelope.hs index d006e51700..af7b0c19a3 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Envelope.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Envelope.hs @@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} -module Test.Cardano.Api.Typed.Envelope +module Test.Cardano.Api.Envelope ( tests ) where @@ -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 diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Json.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Json.hs index 50175923b8..e8122a77de 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Json.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Json.hs @@ -6,6 +6,7 @@ module Test.Cardano.Api.Json ) where +import Cardano.Api import Cardano.Api.Orphans () import Cardano.Api.Shelley @@ -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) @@ -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 @@ -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 ] diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/KeysByron.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/KeysByron.hs index 28b0fc4571..670bcc8644 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/KeysByron.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/KeysByron.hs @@ -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) diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Ord.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Ord.hs similarity index 98% rename from cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Ord.hs rename to cardano-api/test/cardano-api-test/Test/Cardano/Api/Ord.hs index a5c65ff096..7544bd891d 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Ord.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Ord.hs @@ -1,4 +1,4 @@ -module Test.Cardano.Api.Typed.Ord +module Test.Cardano.Api.Ord ( tests ) where diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Orphans.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Orphans.hs similarity index 95% rename from cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Orphans.hs rename to cardano-api/test/cardano-api-test/Test/Cardano/Api/Orphans.hs index f5b2cdf7d4..3d26964d4a 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Orphans.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Orphans.hs @@ -5,7 +5,7 @@ {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -Wno-orphans #-} -module Test.Cardano.Api.Typed.Orphans () where +module Test.Cardano.Api.Orphans () where import Cardano.Api.Shelley diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/RawBytes.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/RawBytes.hs similarity index 98% rename from cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/RawBytes.hs rename to cardano-api/test/cardano-api-test/Test/Cardano/Api/RawBytes.hs index b90f1c410d..3ca4b8f10b 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/RawBytes.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/RawBytes.hs @@ -1,7 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} -module Test.Cardano.Api.Typed.RawBytes +module Test.Cardano.Api.RawBytes ( tests ) where @@ -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 diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Transaction/Autobalance.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Transaction/Autobalance.hs new file mode 100644 index 0000000000..23ef930e92 --- /dev/null +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Transaction/Autobalance.hs @@ -0,0 +1,389 @@ +{-# LANGUAGE GADTs #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} + +{- HLINT ignore "Use camelCase" -} + +module Test.Cardano.Api.Transaction.Autobalance + ( tests + ) +where + +import Cardano.Api +import Cardano.Api.Fees +import qualified Cardano.Api.Ledger as L +import qualified Cardano.Api.Ledger.Lens as L +import Cardano.Api.Script +import Cardano.Api.Shelley (Address (..), LedgerProtocolParameters (..)) + +import qualified Cardano.Ledger.Alonzo.Core as L +import qualified Cardano.Ledger.Coin as L +import qualified Cardano.Ledger.Mary.Value as L +import Cardano.Ledger.Val ((<->)) +import qualified Cardano.Ledger.Val as L +import qualified Cardano.Slotting.EpochInfo as CS +import qualified Cardano.Slotting.Slot as CS +import qualified Cardano.Slotting.Time as CS + +import qualified Data.ByteString as B +import Data.Default (def) +import Data.Function +import qualified Data.Map.Strict as M +import Data.Maybe +import Data.Ratio ((%)) +import qualified Data.Time.Format as DT +import GHC.Exts (IsList (..)) +import GHC.Stack +import Lens.Micro ((^.)) + +import Test.Gen.Cardano.Api.Typed + +import Test.Cardano.Api.Orphans () + +import Hedgehog (MonadTest, Property, forAll, (===)) +import qualified Hedgehog as H +import qualified Hedgehog.Extras as H +import qualified Hedgehog.Gen as Gen +import Test.Tasty (TestTree, testGroup) +import Test.Tasty.Hedgehog (testProperty) + +-- | Test that the fee is the same when spending minted asset manually or when autobalancing it +prop_make_transaction_body_autobalance_return_correct_fee_for_multi_asset :: Property +prop_make_transaction_body_autobalance_return_correct_fee_for_multi_asset = H.propertyOnce $ do + let ceo = ConwayEraOnwardsConway + beo = conwayEraOnwardsToBabbageEraOnwards ceo + meo = babbageEraOnwardsToMaryEraOnwards beo + sbe = conwayEraOnwardsToShelleyBasedEra ceo + era = toCardanoEra sbe + aeo <- H.nothingFail $ forEraMaybeEon @AlonzoEraOnwards era + + systemStart <- + fmap SystemStart . H.evalIO $ + DT.parseTimeM True DT.defaultTimeLocale "%Y-%m-%dT%H:%M:%S%QZ" "2021-09-01T00:00:00Z" + + let epochInfo = LedgerEpochInfo $ CS.fixedEpochInfo (CS.EpochSize 100) (CS.mkSlotLength 1000) + + pparams <- + LedgerProtocolParameters + <$> H.readJsonFileOk "test/cardano-api-test/files/input/protocol-parameters/conway.json" + + (sh@(ScriptHash scriptHash), plutusWitness) <- loadPlutusWitness ceo + let policyId' = PolicyId sh + -- one UTXO with an asset - the same we're minting in the transaction + let utxos = mkUtxos beo scriptHash + txInputs = map (,BuildTxWith (KeyWitness KeyWitnessForSpending)) . toList . M.keys . unUTxO $ utxos + txInputsCollateral = TxInsCollateral aeo $ toList . M.keys . unUTxO $ utxos + let address = mkAddress sbe scriptHash + + let txMint = + TxMintValue + meo + [(AssetId policyId' "eeee", 1)] + (BuildTxWith [(policyId', plutusWitness)]) + + -- tx body content without an asset in TxOut + let content = + defaultTxBodyContent sbe + & setTxIns txInputs + & setTxInsCollateral txInputsCollateral + & setTxOuts (mkTxOutput beo address Nothing) -- include minted asset in txout manually + & setTxMintValue txMint + & setTxProtocolParams (pure $ pure pparams) + + -- tx body content with manually added asset to TxOut + let contentWithTxoutAsset = content & setTxOuts (mkTxOutput beo address (Just scriptHash)) + + -- change txout only with ADA + (BalancedTxBody balancedContentWithTxoutAsset _ _ feeWithTxoutAsset) <- + H.leftFail $ + makeTransactionBodyAutoBalance + sbe + systemStart + epochInfo + pparams + mempty + mempty + mempty + utxos + contentWithTxoutAsset + address + Nothing + -- the correct amount with manual balancing of assets + 335_475 === feeWithTxoutAsset + + -- autobalanced body has assets and ADA in the change txout + (BalancedTxBody balancedContent _ _ fee) <- + H.leftFail $ + makeTransactionBodyAutoBalance + sbe + systemStart + epochInfo + pparams + mempty + mempty + mempty + utxos + content + address + Nothing + + H.noteShow_ feeWithTxoutAsset + H.noteShow_ fee + H.note_ "There are differences between fees for two autobalanced TxBodyContents. Diff:" + H.diff balancedContentWithTxoutAsset (\_ _ -> feeWithTxoutAsset == fee) balancedContent + feeWithTxoutAsset === fee + +prop_make_transaction_body_autobalance_multi_asset_collateral :: Property +prop_make_transaction_body_autobalance_multi_asset_collateral = H.propertyOnce $ do + let ceo = ConwayEraOnwardsConway + beo = conwayEraOnwardsToBabbageEraOnwards ceo + sbe = babbageEraOnwardsToShelleyBasedEra beo + meo = babbageEraOnwardsToMaryEraOnwards beo + era = toCardanoEra sbe + aeo <- H.nothingFail $ forEraMaybeEon @AlonzoEraOnwards era + + systemStart <- + fmap SystemStart . H.evalIO $ + DT.parseTimeM True DT.defaultTimeLocale "%Y-%m-%dT%H:%M:%S%QZ" "2021-09-01T00:00:00Z" + + let epochInfo = LedgerEpochInfo $ CS.fixedEpochInfo (CS.EpochSize 100) (CS.mkSlotLength 1000) + + pparams <- + LedgerProtocolParameters + <$> H.readJsonFileOk "test/cardano-api-test/files/input/protocol-parameters/conway.json" + + (sh@(ScriptHash scriptHash), plutusWitness) <- loadPlutusWitness ceo + let policyId' = PolicyId sh + -- one UTXO with an asset - the same we're minting in the transaction + let utxos = mkUtxos beo scriptHash + txInputs = map (,BuildTxWith (KeyWitness KeyWitnessForSpending)) . toList . M.keys . unUTxO $ utxos + txInputsCollateral = TxInsCollateral aeo $ toList . M.keys . unUTxO $ utxos + let address = mkAddress sbe scriptHash + let txMint = + TxMintValue + meo + [(AssetId policyId' "eeee", 1)] + (BuildTxWith [(policyId', plutusWitness)]) + + let content = + defaultTxBodyContent sbe + & setTxIns txInputs + & setTxInsCollateral txInputsCollateral + & setTxOuts (mkTxOutput beo address Nothing) + & setTxMintValue txMint + & setTxProtocolParams (pure $ pure pparams) + + -- autobalanced body has assets and ADA in the change txout + (BalancedTxBody balancedContent _ _ fee) <- + H.leftFail $ + makeTransactionBodyAutoBalance + sbe + systemStart + epochInfo + pparams + mempty + mempty + mempty + utxos + content + address + Nothing + + 335_475 === fee + TxReturnCollateral _ (TxOut _ txOutValue _ _) <- H.noteShow $ txReturnCollateral balancedContent + let assets = [a | a@(AssetId _ _, _) <- toList $ txOutValueToValue txOutValue] + H.note_ "Check that all assets from UTXO, from the collateral txin, are in the return collateral." + [(AssetId policyId' "eeee", 1)] === assets + +-- | Implements collateral validation from Babbage spec, from +-- https://github.com/IntersectMBO/cardano-ledger/releases, babbage-ledger.pdf, Figure 2. +-- +-- Seems that under 400 runs the test is not able to detect the violation of properties. +prop_calcReturnAndTotalCollateral :: Property +prop_calcReturnAndTotalCollateral = H.withTests 400 . H.property $ do + let beo = BabbageEraOnwardsConway + sbe = babbageEraOnwardsToShelleyBasedEra beo + era = toCardanoEra beo + feeCoin@(L.Coin fee) <- forAll genLovelace + totalCollateral <- forAll $ genValueForTxOut sbe + let totalCollateralAda = totalCollateral ^. L.adaAssetL sbe + pparams <- + H.readJsonFileOk "test/cardano-api-test/files/input/protocol-parameters/conway.json" + requiredCollateralPct <- H.noteShow . fromIntegral $ pparams ^. L.ppCollateralPercentageL + requiredCollateralAda <- + H.noteShow . L.rationalToCoinViaCeiling $ (fee * requiredCollateralPct) % 100 + txInsColl <- forAll $ genTxInsCollateral era + txRetColl <- + forAll $ Gen.frequency [(4, pure TxReturnCollateralNone), (1, genTxReturnCollateral sbe)] + txTotColl <- forAll $ Gen.frequency [(4, pure TxTotalCollateralNone), (1, genTxTotalCollateral era)] + let address = AddressInEra (ShelleyAddressInEra sbe) (ShelleyAddress L.Testnet def L.StakeRefNull) + + let (resRetColl, resTotColl) = + calcReturnAndTotalCollateral + beo + feeCoin + pparams + txInsColl + txRetColl + txTotColl + address + totalCollateral + + H.annotateShow resRetColl + H.annotateShow resTotColl + + let resRetCollValue = + mconcat + [ txOutValue + | TxReturnCollateral _ (TxOut _ (TxOutValueShelleyBased _ txOutValue) _ _) <- pure resRetColl + ] + collBalance = totalCollateral <-> resRetCollValue + + resTotCollValue <- + H.noteShow $ mconcat [L.mkAdaValue sbe lovelace | TxTotalCollateral _ lovelace <- pure resTotColl] + + if + | txInsColl == TxInsCollateralNone -> do + -- no inputs - no outputs + TxReturnCollateralNone === resRetColl + TxTotalCollateralNone === resTotColl + | txRetColl /= TxReturnCollateralNone || txTotColl /= TxTotalCollateralNone -> do + -- got collateral values as function arguments - not calculating anything + txRetColl === resRetColl + txTotColl === resTotColl + | totalCollateralAda < requiredCollateralAda -> do + -- provided collateral not enough, not calculating anything + TxReturnCollateralNone === resRetColl + TxTotalCollateralNone === resTotColl + | otherwise -> do + -- no explicit collateral or return collateral was provided, we do the calculation + H.annotateShow collBalance + H.note_ "Check if collateral balance is positive" + H.assertWith collBalance $ L.pointwise (<=) mempty + H.note_ "Check if collateral balance contains only ada" + H.assertWith collBalance L.isAdaOnly + H.note_ "Check if collateral balance is at least minimum required" + H.assertWith collBalance $ L.pointwise (<=) (L.inject requiredCollateralAda) + H.note_ "Check that collateral balance is equal to collateral in tx body" + resTotCollValue === collBalance + +-- * Utilities + +loadPlutusWitness + :: HasCallStack + => MonadFail m + => MonadIO m + => MonadTest m + => ConwayEraOnwards era + -> m (ScriptHash, ScriptWitness WitCtxMint era) +loadPlutusWitness ceo = do + envelope <- + H.leftFailM $ + fmap (deserialiseFromJSON AsTextEnvelope) . H.evalIO $ + B.readFile "test/cardano-api-test/files/input/plutus/v3.alwaysTrue.json" + ScriptInAnyLang (PlutusScriptLanguage PlutusScriptV3) s@(PlutusScript PlutusScriptV3 script) <- + H.leftFail $ deserialiseFromTextEnvelopeAnyOf textEnvTypes envelope + let scriptLangInEra = case ceo of + ConwayEraOnwardsConway -> PlutusScriptV3InConway + pure + ( hashScript s + , PlutusScriptWitness + scriptLangInEra + PlutusScriptV3 + (PScript script) + NoScriptDatumForMint + (unsafeHashableScriptData (ScriptDataMap [])) + (ExecutionUnits 0 0) + ) + +textEnvTypes :: [FromSomeType HasTextEnvelope ScriptInAnyLang] +textEnvTypes = + [ FromSomeType + (AsScript AsPlutusScriptV3) + (ScriptInAnyLang (PlutusScriptLanguage PlutusScriptV3)) + ] + +mkUtxos :: BabbageEraOnwards era -> L.ScriptHash L.StandardCrypto -> UTxO era +mkUtxos beo scriptHash = babbageEraOnwardsConstraints beo $ do + let sbe = babbageEraOnwardsToShelleyBasedEra beo + UTxO + [ + ( TxIn + "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53" + (TxIx 0) + , TxOut + ( AddressInEra + (ShelleyAddressInEra sbe) + ( ShelleyAddress + L.Testnet + ( L.KeyHashObj $ + L.KeyHash "ebe9de78a37f84cc819c0669791aa0474d4f0a764e54b9f90cfe2137" + ) + L.StakeRefNull + ) + ) + ( TxOutValueShelleyBased + sbe + ( L.MaryValue + (L.Coin 4_000_000) + (L.MultiAsset [(L.PolicyID scriptHash, [(L.AssetName "eeee", 1)])]) + ) + ) + TxOutDatumNone + ReferenceScriptNone + ) + ] + +-- | Make an address from a script hash +mkAddress :: ShelleyBasedEra era -> L.ScriptHash L.StandardCrypto -> AddressInEra era +mkAddress sbe scriptHash = + AddressInEra + (ShelleyAddressInEra sbe) + ( ShelleyAddress + L.Testnet + (L.ScriptHashObj scriptHash) + L.StakeRefNull + ) + +-- | Make a single txout with an optional asset +mkTxOutput + :: BabbageEraOnwards era + -> AddressInEra era + -> Maybe (L.ScriptHash L.StandardCrypto) + -- ^ there will be an asset in the txout if provided + -> [TxOut CtxTx era] +mkTxOutput beo address mScriptHash = babbageEraOnwardsConstraints beo $ do + let sbe = babbageEraOnwardsToShelleyBasedEra beo + [ TxOut + address + ( TxOutValueShelleyBased + sbe + ( L.MaryValue + (L.Coin 2_000_000) + ( L.MultiAsset $ + fromList + [(L.PolicyID scriptHash, [(L.AssetName "eeee", 2)]) | scriptHash <- maybeToList mScriptHash] + ) + ) + ) + TxOutDatumNone + ReferenceScriptNone + ] + +tests :: TestTree +tests = + testGroup + "Test.Cardano.Api.Typed.TxBody" + [ testProperty + "makeTransactionBodyAutoBalance test correct fees when mutli-asset tx" + prop_make_transaction_body_autobalance_return_correct_fee_for_multi_asset + , testProperty + "makeTransactionBodyAutoBalance autobalances multi-asset collateral" + prop_make_transaction_body_autobalance_multi_asset_collateral + , testProperty "calcReturnAndTotalCollateral constraints hold" prop_calcReturnAndTotalCollateral + ] diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/TxBody.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/TxBody.hs new file mode 100644 index 0000000000..033f2f4b2c --- /dev/null +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/TxBody.hs @@ -0,0 +1,122 @@ +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} + +{- HLINT ignore "Use camelCase" -} + +module Test.Cardano.Api.TxBody + ( tests + ) +where + +import Cardano.Api +import qualified Cardano.Api.Ledger as L +import Cardano.Api.Script +import Cardano.Api.Shelley (ShelleyLedgerEra) + +import Data.Maybe (isJust) +import Data.Type.Equality (TestEquality (testEquality)) +import GHC.Exts (IsList (..)) + +import Test.Gen.Cardano.Api.Typed + +import Test.Cardano.Api.Orphans () + +import Hedgehog (MonadTest, Property, (===)) +import qualified Hedgehog as H +import Test.Tasty (TestTree, testGroup) +import Test.Tasty.Hedgehog (testProperty) + +-- | Check the txOuts in a TxBodyContent after a ledger roundtrip. +prop_roundtrip_txbodycontent_txouts :: forall era. ShelleyBasedEra era -> Property +prop_roundtrip_txbodycontent_txouts era = H.property $ do + (body, content :: TxBodyContent BuildTx era) <- + shelleyBasedEraConstraints era $ H.forAll $ genValidTxBody era + -- Convert ledger body back via 'getTxBodyContent' and 'fromLedgerTxBody' + let (TxBody content') = body + matchTxOuts (txOuts content) (txOuts content') + where + matchTxOuts :: MonadTest m => [TxOut CtxTx era] -> [TxOut CtxTx era] -> m () + matchTxOuts as bs = + mapM_ matchTxOut $ zip as bs + + matchTxOut :: MonadTest m => (TxOut CtxTx era, TxOut CtxTx era) -> m () + matchTxOut (a, b) = do + let TxOut aAddress aValue aDatum aRefScript = a + let TxOut bAddress bValue bDatum bRefScript = b + aAddress === bAddress + aValue === bValue + matchDatum (aDatum, bDatum) + matchRefScript (aRefScript, bRefScript) + + -- NOTE: We accept TxOutDatumInTx instead of TxOutDatumHash as it may be + -- correctly resolved given a datum matching the hash was generated. + matchDatum :: MonadTest m => (TxOutDatum CtxTx era, TxOutDatum CtxTx era) -> m () + matchDatum = \case + (TxOutDatumHash _ dh, TxOutDatumInTx _ d) -> + dh === hashScriptDataBytes d + (a, b) -> + a === b + + -- NOTE: After Allegra, all eras interpret SimpleScriptV1 as SimpleScriptV2 + -- because V2 is a superset of V1. So we accept that as a valid conversion. + matchRefScript :: MonadTest m => (ReferenceScript era, ReferenceScript era) -> m () + matchRefScript (a, b) + | isSimpleScriptV2 a && isSimpleScriptV2 b = + shelleyBasedEraConstraints era $ + refScriptToShelleyScript era a + === refScriptToShelleyScript era b + | otherwise = + a === b + + isSimpleScriptV2 :: ReferenceScript era -> Bool + isSimpleScriptV2 = isLang SimpleScriptLanguage + + isLang :: ScriptLanguage a -> ReferenceScript era -> Bool + isLang expected = \case + (ReferenceScript _ (ScriptInAnyLang actual _)) -> isJust $ testEquality expected actual + _ -> False + +prop_roundtrip_txbodycontent_conway_fields :: Property +prop_roundtrip_txbodycontent_conway_fields = H.property $ do + let sbe = ShelleyBasedEraConway + (body, content) <- H.forAll $ genValidTxBody sbe + -- Convert ledger body back via 'getTxBodyContent' and 'fromLedgerTxBody' + let (TxBody content') = body + + let proposals = getProposalProcedures . unFeatured <$> txProposalProcedures content + proposals' = getProposalProcedures . unFeatured <$> txProposalProcedures content' + votes = getVotingProcedures . unFeatured <$> txVotingProcedures content + votes' = getVotingProcedures . unFeatured <$> txVotingProcedures content' + currTreasury = unFeatured <$> txCurrentTreasuryValue content + currTreasury' = unFeatured <$> txCurrentTreasuryValue content' + treasuryDonation = unFeatured <$> txTreasuryDonation content + treasuryDonation' = unFeatured <$> txTreasuryDonation content' + + proposals === proposals' + votes === votes' + currTreasury === currTreasury' + treasuryDonation === treasuryDonation' + where + getVotingProcedures TxVotingProceduresNone = Nothing + getVotingProcedures (TxVotingProcedures vps _) = Just vps + getProposalProcedures + :: TxProposalProcedures build era + -> Maybe [L.ProposalProcedure (ShelleyLedgerEra era)] + getProposalProcedures TxProposalProceduresNone = Nothing + getProposalProcedures txpp@(TxProposalProcedures _ _) = Just . toList $ convProposalProcedures txpp + +tests :: TestTree +tests = + testGroup + "Test.Cardano.Api.Typed.TxBody" + [ testProperty "roundtrip txbodycontent txouts Babbage" $ + prop_roundtrip_txbodycontent_txouts ShelleyBasedEraBabbage + , testProperty "roundtrip txbodycontent txouts Conway" $ + prop_roundtrip_txbodycontent_txouts ShelleyBasedEraConway + , testProperty + "roundtrip txbodycontent new conway fields" + prop_roundtrip_txbodycontent_conway_fields + ] diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/JSON.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/JSON.hs deleted file mode 100644 index c1d9ab26ae..0000000000 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/JSON.hs +++ /dev/null @@ -1,46 +0,0 @@ -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE TypeFamilies #-} -{-# OPTIONS_GHC -Wno-orphans #-} - -module Test.Cardano.Api.Typed.JSON - ( tests - ) -where - -import Cardano.Api - -import Data.Aeson (eitherDecode, encode) - -import Test.Gen.Cardano.Api.Typed (genMaybePraosNonce, genProtocolParameters) - -import Test.Cardano.Api.Typed.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) - -{- HLINT ignore "Use camelCase" -} - -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 - "Test.Cardano.Api.Typed.JSON" - [ testProperty "roundtrip praos nonce JSON" prop_roundtrip_praos_nonce_JSON - , testProperty "roundtrip protocol parameters JSON" prop_roundtrip_protocol_parameters_JSON - ] diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/TxBody.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/TxBody.hs deleted file mode 100644 index 58e91572b9..0000000000 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/TxBody.hs +++ /dev/null @@ -1,309 +0,0 @@ -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedLists #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} - -{- HLINT ignore "Use list comprehension" -} -{- HLINT ignore "Use camelCase" -} - -module Test.Cardano.Api.Typed.TxBody - ( tests - ) -where - -import Cardano.Api -import qualified Cardano.Api.Ledger as L -import Cardano.Api.Script -import Cardano.Api.Shelley (Address (..), LedgerProtocolParameters (..), ShelleyLedgerEra) - -import qualified Cardano.Ledger.Mary.Value as L -import qualified Cardano.Ledger.Shelley.Scripts as L -import qualified Cardano.Slotting.EpochInfo as CS -import qualified Cardano.Slotting.Slot as CS -import qualified Cardano.Slotting.Time as CS - -import qualified Data.ByteString as B -import Data.Function -import qualified Data.Map.Strict as M -import Data.Maybe (isJust) -import qualified Data.Time.Format as DT -import Data.Type.Equality (TestEquality (testEquality)) -import GHC.Exts (IsList (..), IsString (..)) -import GHC.Stack - -import Test.Gen.Cardano.Api.Typed - -import Test.Cardano.Api.Typed.Orphans () - -import Hedgehog (MonadTest, Property, (===)) -import qualified Hedgehog as H -import qualified Hedgehog.Extras as H -import Test.Tasty (TestTree, testGroup) -import Test.Tasty.Hedgehog (testProperty) - --- | Check the txOuts in a TxBodyContent after a ledger roundtrip. -prop_roundtrip_txbodycontent_txouts :: forall era. ShelleyBasedEra era -> Property -prop_roundtrip_txbodycontent_txouts era = H.property $ do - (body, content :: TxBodyContent BuildTx era) <- - shelleyBasedEraConstraints era $ H.forAll $ genValidTxBody era - -- Convert ledger body back via 'getTxBodyContent' and 'fromLedgerTxBody' - let (TxBody content') = body - matchTxOuts (txOuts content) (txOuts content') - where - matchTxOuts :: MonadTest m => [TxOut CtxTx era] -> [TxOut CtxTx era] -> m () - matchTxOuts as bs = - mapM_ matchTxOut $ zip as bs - - matchTxOut :: MonadTest m => (TxOut CtxTx era, TxOut CtxTx era) -> m () - matchTxOut (a, b) = do - let TxOut aAddress aValue aDatum aRefScript = a - let TxOut bAddress bValue bDatum bRefScript = b - aAddress === bAddress - aValue === bValue - matchDatum (aDatum, bDatum) - matchRefScript (aRefScript, bRefScript) - - -- NOTE: We accept TxOutDatumInTx instead of TxOutDatumHash as it may be - -- correctly resolved given a datum matching the hash was generated. - matchDatum :: MonadTest m => (TxOutDatum CtxTx era, TxOutDatum CtxTx era) -> m () - matchDatum = \case - (TxOutDatumHash _ dh, TxOutDatumInTx _ d) -> - dh === hashScriptDataBytes d - (a, b) -> - a === b - - -- NOTE: After Allegra, all eras interpret SimpleScriptV1 as SimpleScriptV2 - -- because V2 is a superset of V1. So we accept that as a valid conversion. - matchRefScript :: MonadTest m => (ReferenceScript era, ReferenceScript era) -> m () - matchRefScript (a, b) - | isSimpleScriptV2 a && isSimpleScriptV2 b = - shelleyBasedEraConstraints era $ - refScriptToShelleyScript era a - === refScriptToShelleyScript era b - | otherwise = - a === b - - isSimpleScriptV2 :: ReferenceScript era -> Bool - isSimpleScriptV2 = isLang SimpleScriptLanguage - - isLang :: ScriptLanguage a -> ReferenceScript era -> Bool - isLang expected = \case - (ReferenceScript _ (ScriptInAnyLang actual _)) -> isJust $ testEquality expected actual - _ -> False - -prop_roundtrip_txbodycontent_conway_fields :: Property -prop_roundtrip_txbodycontent_conway_fields = H.property $ do - let sbe = ShelleyBasedEraConway - (body, content) <- H.forAll $ genValidTxBody sbe - -- Convert ledger body back via 'getTxBodyContent' and 'fromLedgerTxBody' - let (TxBody content') = body - - let proposals = getProposalProcedures . unFeatured <$> txProposalProcedures content - proposals' = getProposalProcedures . unFeatured <$> txProposalProcedures content' - votes = getVotingProcedures . unFeatured <$> txVotingProcedures content - votes' = getVotingProcedures . unFeatured <$> txVotingProcedures content' - currTreasury = unFeatured <$> txCurrentTreasuryValue content - currTreasury' = unFeatured <$> txCurrentTreasuryValue content' - treasuryDonation = unFeatured <$> txTreasuryDonation content - treasuryDonation' = unFeatured <$> txTreasuryDonation content' - - proposals === proposals' - votes === votes' - currTreasury === currTreasury' - treasuryDonation === treasuryDonation' - where - getVotingProcedures TxVotingProceduresNone = Nothing - getVotingProcedures (TxVotingProcedures vps _) = Just vps - getProposalProcedures - :: TxProposalProcedures build era - -> Maybe [L.ProposalProcedure (ShelleyLedgerEra era)] - getProposalProcedures TxProposalProceduresNone = Nothing - getProposalProcedures txpp@(TxProposalProcedures _ _) = Just . toList $ convProposalProcedures txpp - --- | Test that the fee is the same when spending minted asset manually or when autobalancing it -prop_make_transaction_body_autobalance_return_correct_fee_for_multi_asset :: Property -prop_make_transaction_body_autobalance_return_correct_fee_for_multi_asset = H.propertyOnce $ do - let sbe = ShelleyBasedEraConway - era = toCardanoEra sbe - aeo <- H.nothingFail $ forEraMaybeEon @AlonzoEraOnwards era - - systemStart <- - fmap SystemStart . H.evalIO $ - DT.parseTimeM True DT.defaultTimeLocale "%Y-%m-%dT%H:%M:%S%QZ" "2021-09-01T00:00:00Z" - - let epochInfo = LedgerEpochInfo $ CS.fixedEpochInfo (CS.EpochSize 100) (CS.mkSlotLength 1000) - - pparams <- - LedgerProtocolParameters @ConwayEra - <$> H.readJsonFileOk "test/cardano-api-test/files/input/protocol-parameters/conway.json" - - plutusWitness <- loadPlutusWitness - - let scriptHashStr = "e2b715a86bee4f14fef84081217f9e2646893a7d60a38af69e0aa572" - let policyId' = fromString scriptHashStr - let scriptHash = L.ScriptHash $ fromString scriptHashStr - -- one UTXO with an asset - the same we're minting in the transaction - let utxos = - UTxO - [ - ( TxIn - "01f4b788593d4f70de2a45c2e1e87088bfbdfa29577ae1b62aba60e095e3ab53" - (TxIx 0) - , TxOut - ( AddressInEra - (ShelleyAddressInEra ShelleyBasedEraConway) - ( ShelleyAddress - L.Testnet - ( L.KeyHashObj $ - L.KeyHash "ebe9de78a37f84cc819c0669791aa0474d4f0a764e54b9f90cfe2137" - ) - L.StakeRefNull - ) - ) - ( TxOutValueShelleyBased - ShelleyBasedEraConway - ( L.MaryValue - (L.Coin 4_000_000) - (L.MultiAsset [(L.PolicyID scriptHash, [(L.AssetName "eeee", 1)])]) - ) - ) - TxOutDatumNone - ReferenceScriptNone - ) - ] - - txInputs = map (,BuildTxWith (KeyWitness KeyWitnessForSpending)) . toList . M.keys . unUTxO $ utxos - txInputsCollateral = TxInsCollateral aeo $ toList . M.keys . unUTxO $ utxos - - let address = - AddressInEra - (ShelleyAddressInEra ShelleyBasedEraConway) - ( ShelleyAddress - L.Testnet - (L.ScriptHashObj scriptHash) - L.StakeRefNull - ) - let txOutputs doesIncludeAsset = - [ TxOut - address - ( TxOutValueShelleyBased - ShelleyBasedEraConway - ( L.MaryValue - (L.Coin 2_000_000) - ( L.MultiAsset $ - if doesIncludeAsset - then [(L.PolicyID scriptHash, [(L.AssetName "eeee", 2)])] - else [] - ) - ) - ) - TxOutDatumNone - ReferenceScriptNone - ] - - let txMint = - TxMintValue - MaryEraOnwardsConway - [(AssetId policyId' "eeee", 1)] - (BuildTxWith [(policyId', plutusWitness)]) - - -- tx body content without an asset in TxOut - let content = - defaultTxBodyContent sbe - & setTxIns txInputs - & setTxInsCollateral txInputsCollateral - & setTxOuts (txOutputs False) -- include minted asset in txout manually - & setTxMintValue txMint - & setTxProtocolParams (pure $ pure pparams) - - -- tx body content with manually added asset to TxOut - let contentWithTxoutAsset = content & setTxOuts (txOutputs True) - - -- change txout only with ADA - (BalancedTxBody balancedContentWithTxoutAsset _ _ feeWithTxoutAsset) <- - H.leftFail $ - makeTransactionBodyAutoBalance - sbe - systemStart - epochInfo - pparams - mempty - mempty - mempty - utxos - contentWithTxoutAsset - address - Nothing - -- the correct amount with manual balancing of assets - 335_475 === feeWithTxoutAsset - - -- autobalanced body has assets and ADA in the change txout - (BalancedTxBody balancedContent _ _ fee) <- - H.leftFail $ - makeTransactionBodyAutoBalance - sbe - systemStart - epochInfo - pparams - mempty - mempty - mempty - utxos - content - address - Nothing - - H.noteShow_ feeWithTxoutAsset - H.noteShow_ fee - H.note_ "There are differences between fees for two autobalanced TxBodyContents. Diff:" - H.diff balancedContentWithTxoutAsset (\_ _ -> feeWithTxoutAsset == fee) balancedContent - feeWithTxoutAsset === fee - where - loadPlutusWitness - :: HasCallStack - => MonadFail m - => MonadIO m - => MonadTest m - => m (ScriptWitness WitCtxMint ConwayEra) - loadPlutusWitness = do - envelope <- - H.leftFailM $ - fmap (deserialiseFromJSON AsTextEnvelope) . H.evalIO $ - B.readFile "test/cardano-api-test/files/input/plutus/v3.alwaysTrue.json" - ScriptInAnyLang (PlutusScriptLanguage PlutusScriptV3) (PlutusScript PlutusScriptV3 script) <- - H.leftFail $ deserialiseFromTextEnvelopeAnyOf textEnvTypes envelope - pure $ - PlutusScriptWitness - PlutusScriptV3InConway - PlutusScriptV3 - (PScript script) - NoScriptDatumForMint - (unsafeHashableScriptData (ScriptDataMap [])) - (ExecutionUnits 0 0) - - textEnvTypes :: [FromSomeType HasTextEnvelope ScriptInAnyLang] - textEnvTypes = - [ FromSomeType - (AsScript AsPlutusScriptV3) - (ScriptInAnyLang (PlutusScriptLanguage PlutusScriptV3)) - ] - -tests :: TestTree -tests = - testGroup - "Test.Cardano.Api.Typed.TxBody" - [ testProperty "roundtrip txbodycontent txouts Babbage" $ - prop_roundtrip_txbodycontent_txouts ShelleyBasedEraBabbage - , testProperty "roundtrip txbodycontent txouts Conway" $ - prop_roundtrip_txbodycontent_txouts ShelleyBasedEraConway - , testProperty - "roundtrip txbodycontent new conway fields" - prop_roundtrip_txbodycontent_conway_fields - , testProperty - "makeTransactionBodyAutoBalance test correct fees when mutli-asset tx" - prop_make_transaction_body_autobalance_return_correct_fee_for_multi_asset - ] diff --git a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Value.hs b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Value.hs similarity index 98% rename from cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Value.hs rename to cardano-api/test/cardano-api-test/Test/Cardano/Api/Value.hs index 1e85200b5f..7bfc366395 100644 --- a/cardano-api/test/cardano-api-test/Test/Cardano/Api/Typed/Value.hs +++ b/cardano-api/test/cardano-api-test/Test/Cardano/Api/Value.hs @@ -1,4 +1,4 @@ -module Test.Cardano.Api.Typed.Value +module Test.Cardano.Api.Value ( tests ) where diff --git a/cardano-api/test/cardano-api-test/cardano-api-test.hs b/cardano-api/test/cardano-api-test/cardano-api-test.hs index e1ed1e081b..3b977fb1c0 100644 --- a/cardano-api/test/cardano-api-test/cardano-api-test.hs +++ b/cardano-api/test/cardano-api-test/cardano-api-test.hs @@ -6,7 +6,11 @@ import System.IO (BufferMode (LineBuffering), hSetBuffering, hSetEncod import qualified Test.Gen.Cardano.Api.Byron +import qualified Test.Cardano.Api.Address +import qualified Test.Cardano.Api.Bech32 +import qualified Test.Cardano.Api.CBOR import qualified Test.Cardano.Api.Crypto +import qualified Test.Cardano.Api.Envelope import qualified Test.Cardano.Api.EpochLeadership import qualified Test.Cardano.Api.Eras import qualified Test.Cardano.Api.Genesis @@ -15,16 +19,12 @@ import qualified Test.Cardano.Api.Json import qualified Test.Cardano.Api.KeysByron import qualified Test.Cardano.Api.Ledger import qualified Test.Cardano.Api.Metadata +import qualified Test.Cardano.Api.Ord import qualified Test.Cardano.Api.ProtocolParameters -import qualified Test.Cardano.Api.Typed.Address -import qualified Test.Cardano.Api.Typed.Bech32 -import qualified Test.Cardano.Api.Typed.CBOR -import qualified Test.Cardano.Api.Typed.Envelope -import qualified Test.Cardano.Api.Typed.JSON -import qualified Test.Cardano.Api.Typed.Ord -import qualified Test.Cardano.Api.Typed.RawBytes -import qualified Test.Cardano.Api.Typed.TxBody -import qualified Test.Cardano.Api.Typed.Value +import qualified Test.Cardano.Api.RawBytes +import qualified Test.Cardano.Api.Transaction.Autobalance +import qualified Test.Cardano.Api.TxBody +import qualified Test.Cardano.Api.Value import Test.Tasty (TestTree, defaultMain, testGroup) @@ -41,7 +41,11 @@ tests = testGroup "Cardano.Api" [ Test.Gen.Cardano.Api.Byron.tests + , Test.Cardano.Api.Address.tests + , Test.Cardano.Api.Bech32.tests + , Test.Cardano.Api.CBOR.tests , Test.Cardano.Api.Crypto.tests + , Test.Cardano.Api.Envelope.tests , Test.Cardano.Api.EpochLeadership.tests , Test.Cardano.Api.Eras.tests , Test.Cardano.Api.Genesis.tests @@ -50,14 +54,10 @@ tests = , Test.Cardano.Api.KeysByron.tests , Test.Cardano.Api.Ledger.tests , Test.Cardano.Api.Metadata.tests + , Test.Cardano.Api.Ord.tests , Test.Cardano.Api.ProtocolParameters.tests - , Test.Cardano.Api.Typed.Address.tests - , Test.Cardano.Api.Typed.Bech32.tests - , Test.Cardano.Api.Typed.CBOR.tests - , Test.Cardano.Api.Typed.Envelope.tests - , Test.Cardano.Api.Typed.JSON.tests - , Test.Cardano.Api.Typed.Ord.tests - , Test.Cardano.Api.Typed.RawBytes.tests - , Test.Cardano.Api.Typed.TxBody.tests - , Test.Cardano.Api.Typed.Value.tests + , Test.Cardano.Api.RawBytes.tests + , Test.Cardano.Api.Transaction.Autobalance.tests + , Test.Cardano.Api.TxBody.tests + , Test.Cardano.Api.Value.tests ]