diff --git a/lib/core/cardano-wallet-core.cabal b/lib/core/cardano-wallet-core.cabal index f20d97e1336..292301065e7 100644 --- a/lib/core/cardano-wallet-core.cabal +++ b/lib/core/cardano-wallet-core.cabal @@ -263,6 +263,7 @@ library -- The following modules define QC generators and shrinkers that can -- be used by both `cardano-wallet-core` and `cardano-wallet`: -- + Cardano.Wallet.CoinSelection.Gen Cardano.Wallet.CoinSelection.Internal.Balance.Gen Cardano.Wallet.Primitive.Types.Address.Gen Cardano.Wallet.Primitive.Types.Coin.Gen @@ -420,6 +421,7 @@ test-suite unit Cardano.Wallet.Api.ServerSpec Cardano.Wallet.Api.TypesSpec Cardano.Wallet.ApiSpec + Cardano.Wallet.CoinSelectionSpec Cardano.Wallet.CoinSelection.InternalSpec Cardano.Wallet.CoinSelection.Internal.BalanceSpec Cardano.Wallet.CoinSelection.Internal.CollateralSpec diff --git a/lib/core/src/Cardano/Wallet/CoinSelection.hs b/lib/core/src/Cardano/Wallet/CoinSelection.hs index e14bd73d2a3..54ae4810bd3 100644 --- a/lib/core/src/Cardano/Wallet/CoinSelection.hs +++ b/lib/core/src/Cardano/Wallet/CoinSelection.hs @@ -34,6 +34,10 @@ module Cardano.Wallet.CoinSelection , toInternalUTxO , toInternalUTxOMap + -- * Mapping between external (wallet) selections and internal selections. + , toExternalSelection + , toInternalSelection + -- * Performing selections , performSelection , Selection @@ -399,9 +403,8 @@ data SelectionOf change = Selection -- type Selection = SelectionOf TokenBundle -toExternalSelection - :: SelectionParams -> Internal.Selection WalletSelectionContext -> Selection -toExternalSelection _ps Internal.Selection {..} = +toExternalSelection :: Internal.Selection WalletSelectionContext -> Selection +toExternalSelection Internal.Selection {..} = Selection { collateral = toExternalUTxO' TokenBundle.fromCoin <$> collateral @@ -450,7 +453,7 @@ performSelection -> SelectionParams -> ExceptT (SelectionError WalletSelectionContext) m Selection performSelection cs ps = - toExternalSelection ps <$> + toExternalSelection <$> Internal.performSelection @m @WalletSelectionContext (toInternalSelectionConstraints cs) (toInternalSelectionParams ps) diff --git a/lib/core/src/Cardano/Wallet/CoinSelection/Gen.hs b/lib/core/src/Cardano/Wallet/CoinSelection/Gen.hs new file mode 100644 index 00000000000..9ec47511e20 --- /dev/null +++ b/lib/core/src/Cardano/Wallet/CoinSelection/Gen.hs @@ -0,0 +1,49 @@ +module Cardano.Wallet.CoinSelection.Gen + ( coarbitraryWalletUTxO + , genWalletUTxO + , genWalletUTxOFunction + , genWalletUTxOLargeRange + , shrinkWalletUTxO + ) + where + +import Prelude + +import Cardano.Wallet.CoinSelection + ( WalletUTxO (..) ) +import Cardano.Wallet.Primitive.Types.Address.Gen + ( genAddress, shrinkAddress ) +import Cardano.Wallet.Primitive.Types.Tx.Gen + ( genTxIn, genTxInLargeRange, shrinkTxIn ) +import Generics.SOP + ( NP (..) ) +import Test.QuickCheck + ( Gen, coarbitrary ) +import Test.QuickCheck.Extra + ( genFunction, genSized2, genericRoundRobinShrink, (<:>), (<@>) ) + +-------------------------------------------------------------------------------- +-- Wallet UTxO identifiers chosen according to the size parameter +-------------------------------------------------------------------------------- + +coarbitraryWalletUTxO :: WalletUTxO -> Gen a -> Gen a +coarbitraryWalletUTxO = coarbitrary . show + +genWalletUTxO :: Gen WalletUTxO +genWalletUTxO = uncurry WalletUTxO <$> genSized2 genTxIn genAddress + +shrinkWalletUTxO :: WalletUTxO -> [WalletUTxO] +shrinkWalletUTxO = genericRoundRobinShrink + <@> shrinkTxIn + <:> shrinkAddress + <:> Nil + +genWalletUTxOFunction :: Gen a -> Gen (WalletUTxO -> a) +genWalletUTxOFunction = genFunction coarbitraryWalletUTxO + +-------------------------------------------------------------------------------- +-- Wallet UTxO identifiers chosen from a large range +-------------------------------------------------------------------------------- + +genWalletUTxOLargeRange :: Gen WalletUTxO +genWalletUTxOLargeRange = WalletUTxO <$> genTxInLargeRange <*> genAddress diff --git a/lib/core/src/Cardano/Wallet/CoinSelection/Internal/Balance/Gen.hs b/lib/core/src/Cardano/Wallet/CoinSelection/Internal/Balance/Gen.hs index eb08f6fd4d6..59c968249d1 100644 --- a/lib/core/src/Cardano/Wallet/CoinSelection/Internal/Balance/Gen.hs +++ b/lib/core/src/Cardano/Wallet/CoinSelection/Internal/Balance/Gen.hs @@ -13,16 +13,14 @@ module Cardano.Wallet.CoinSelection.Internal.Balance.Gen import Prelude -import Cardano.Wallet.CoinSelection - ( WalletSelectionContext ) import Cardano.Wallet.CoinSelection.Internal.Balance ( SelectionLimit , SelectionLimitOf (..) , SelectionSkeleton (..) , SelectionStrategy (..) ) -import Cardano.Wallet.Primitive.Types.Address.Gen - ( genAddress, shrinkAddress ) +import Cardano.Wallet.CoinSelection.Internal.Context + ( SelectionContext (..) ) import Cardano.Wallet.Primitive.Types.Coin ( Coin (..) ) import Cardano.Wallet.Primitive.Types.TokenBundle @@ -72,8 +70,8 @@ shrinkSelectionLimit = \case -- Selection skeletons -------------------------------------------------------------------------------- -genSelectionSkeleton :: Gen (SelectionSkeleton WalletSelectionContext) -genSelectionSkeleton = SelectionSkeleton +genSelectionSkeleton :: Gen (Address ctx) -> Gen (SelectionSkeleton ctx) +genSelectionSkeleton genAddress = SelectionSkeleton <$> genSkeletonInputCount <*> genSkeletonOutputs <*> genSkeletonChange @@ -89,9 +87,9 @@ genSelectionSkeleton = SelectionSkeleton listOf (Set.fromList <$> listOf genAssetId) shrinkSelectionSkeleton - :: SelectionSkeleton WalletSelectionContext - -> [SelectionSkeleton WalletSelectionContext] -shrinkSelectionSkeleton = genericRoundRobinShrink + :: (Address ctx -> [Address ctx]) + -> (SelectionSkeleton ctx -> [SelectionSkeleton ctx]) +shrinkSelectionSkeleton shrinkAddress = genericRoundRobinShrink <@> shrinkSkeletonInputCount <:> shrinkSkeletonOutputs <:> shrinkSkeletonChange diff --git a/lib/core/src/Cardano/Wallet/Primitive/Types/TokenBundle/Gen.hs b/lib/core/src/Cardano/Wallet/Primitive/Types/TokenBundle/Gen.hs index 0ab95ae8660..0f18ed6db3a 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Types/TokenBundle/Gen.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Types/TokenBundle/Gen.hs @@ -2,6 +2,7 @@ module Cardano.Wallet.Primitive.Types.TokenBundle.Gen ( genTokenBundleSmallRange , genTokenBundleSmallRangePositive , genTokenBundle + , shrinkTokenBundle , shrinkTokenBundleSmallRange , shrinkTokenBundleSmallRangePositive ) where @@ -31,6 +32,12 @@ genTokenBundle = TokenBundle <$> genCoin <*> genTokenMap +shrinkTokenBundle :: TokenBundle -> [TokenBundle] +shrinkTokenBundle (TokenBundle c m)= + uncurry TokenBundle <$> shrinkInterleaved + (c, shrinkCoin) + (m, shrinkTokenMap) + -------------------------------------------------------------------------------- -- Token bundles with coins, assets, and quantities chosen from small ranges -------------------------------------------------------------------------------- @@ -41,10 +48,7 @@ genTokenBundleSmallRange = TokenBundle <*> genTokenMapSmallRange shrinkTokenBundleSmallRange :: TokenBundle -> [TokenBundle] -shrinkTokenBundleSmallRange (TokenBundle c m) = - uncurry TokenBundle <$> shrinkInterleaved - (c, shrinkCoin) - (m, shrinkTokenMap) +shrinkTokenBundleSmallRange = shrinkTokenBundle genTokenBundleSmallRangePositive :: Gen TokenBundle genTokenBundleSmallRangePositive = TokenBundle diff --git a/lib/core/src/Cardano/Wallet/Primitive/Types/UTxOIndex/Gen.hs b/lib/core/src/Cardano/Wallet/Primitive/Types/UTxOIndex/Gen.hs index ea419039c9c..256e8456990 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Types/UTxOIndex/Gen.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Types/UTxOIndex/Gen.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE ScopedTypeVariables #-} + module Cardano.Wallet.Primitive.Types.UTxOIndex.Gen ( genUTxOIndex , genUTxOIndexLarge @@ -7,16 +9,10 @@ module Cardano.Wallet.Primitive.Types.UTxOIndex.Gen import Prelude -import Cardano.Wallet.CoinSelection - ( WalletUTxO (..) ) -import Cardano.Wallet.Primitive.Types.Address.Gen - ( genAddress, shrinkAddress ) import Cardano.Wallet.Primitive.Types.TokenBundle ( TokenBundle ) import Cardano.Wallet.Primitive.Types.TokenBundle.Gen ( genTokenBundleSmallRangePositive, shrinkTokenBundleSmallRangePositive ) -import Cardano.Wallet.Primitive.Types.Tx.Gen - ( genTxIn, genTxInLargeRange, shrinkTxIn ) import Cardano.Wallet.Primitive.Types.UTxOIndex ( UTxOIndex ) import Control.Monad @@ -26,7 +22,7 @@ import Generics.SOP import Test.QuickCheck ( Gen, choose, listOf, shrinkList, shrinkMapBy ) import Test.QuickCheck.Extra - ( genSized2, genericRoundRobinShrink, (<:>), (<@>) ) + ( genericRoundRobinShrink, (<:>), (<@>) ) import qualified Cardano.Wallet.Primitive.Types.UTxOIndex as UTxOIndex @@ -34,43 +30,32 @@ import qualified Cardano.Wallet.Primitive.Types.UTxOIndex as UTxOIndex -- Indices generated according to the size parameter -------------------------------------------------------------------------------- -genUTxOIndex :: Gen (UTxOIndex WalletUTxO) -genUTxOIndex = UTxOIndex.fromSequence <$> listOf genEntry +genUTxOIndex :: forall u. Ord u => Gen u -> Gen (UTxOIndex u) +genUTxOIndex genUTxO = UTxOIndex.fromSequence <$> listOf genEntry where - genEntry :: Gen (WalletUTxO, TokenBundle) - genEntry = (,) <$> genWalletUTxO <*> genTokenBundleSmallRangePositive - - genWalletUTxO :: Gen WalletUTxO - genWalletUTxO = uncurry WalletUTxO <$> genSized2 genTxIn genAddress + genEntry :: Gen (u, TokenBundle) + genEntry = (,) <$> genUTxO <*> genTokenBundleSmallRangePositive -shrinkUTxOIndex :: UTxOIndex WalletUTxO -> [UTxOIndex WalletUTxO] -shrinkUTxOIndex = +shrinkUTxOIndex :: forall u. Ord u => (u -> [u]) -> UTxOIndex u -> [UTxOIndex u] +shrinkUTxOIndex shrinkUTxO = shrinkMapBy UTxOIndex.fromSequence UTxOIndex.toList (shrinkList shrinkEntry) where - shrinkEntry :: (WalletUTxO, TokenBundle) -> [(WalletUTxO, TokenBundle)] + shrinkEntry :: (u, TokenBundle) -> [(u, TokenBundle)] shrinkEntry = genericRoundRobinShrink - <@> shrinkWalletUTxO + <@> shrinkUTxO <:> shrinkTokenBundleSmallRangePositive <:> Nil - shrinkWalletUTxO :: WalletUTxO -> [WalletUTxO] - shrinkWalletUTxO = genericRoundRobinShrink - <@> shrinkTxIn - <:> shrinkAddress - <:> Nil - -------------------------------------------------------------------------------- -- Large indices -------------------------------------------------------------------------------- -genUTxOIndexLarge :: Gen (UTxOIndex WalletUTxO) -genUTxOIndexLarge = genUTxOIndexLargeN =<< choose (1024, 4096) +genUTxOIndexLarge :: Ord u => Gen u -> Gen (UTxOIndex u) +genUTxOIndexLarge genUTxO = + genUTxOIndexLargeN genUTxO =<< choose (1024, 4096) -genUTxOIndexLargeN :: Int -> Gen (UTxOIndex WalletUTxO) -genUTxOIndexLargeN n = UTxOIndex.fromSequence <$> replicateM n genEntry +genUTxOIndexLargeN :: forall u. Ord u => Gen u -> Int -> Gen (UTxOIndex u) +genUTxOIndexLargeN genUTxO n = UTxOIndex.fromSequence <$> replicateM n genEntry where - genEntry :: Gen (WalletUTxO, TokenBundle) - genEntry = (,) <$> genWalletUTxO <*> genTokenBundleSmallRangePositive - - genWalletUTxO :: Gen WalletUTxO - genWalletUTxO = WalletUTxO <$> genTxInLargeRange <*> genAddress + genEntry :: Gen (u, TokenBundle) + genEntry = (,) <$> genUTxO <*> genTokenBundleSmallRangePositive diff --git a/lib/core/src/Cardano/Wallet/Primitive/Types/UTxOSelection/Gen.hs b/lib/core/src/Cardano/Wallet/Primitive/Types/UTxOSelection/Gen.hs index 6bc302a0ba5..2c3fad3c94f 100644 --- a/lib/core/src/Cardano/Wallet/Primitive/Types/UTxOSelection/Gen.hs +++ b/lib/core/src/Cardano/Wallet/Primitive/Types/UTxOSelection/Gen.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} module Cardano.Wallet.Primitive.Types.UTxOSelection.Gen @@ -10,8 +11,6 @@ module Cardano.Wallet.Primitive.Types.UTxOSelection.Gen import Prelude -import Cardano.Wallet.CoinSelection - ( WalletUTxO ) import Cardano.Wallet.Primitive.Types.UTxOIndex.Gen ( genUTxOIndex, shrinkUTxOIndex ) import Cardano.Wallet.Primitive.Types.UTxOSelection @@ -29,39 +28,40 @@ import qualified Cardano.Wallet.Primitive.Types.UTxOSelection as UTxOSelection -- Selections that may be empty -------------------------------------------------------------------------------- -coarbitraryWalletUTxO :: WalletUTxO -> Gen a -> Gen a -coarbitraryWalletUTxO = coarbitrary . show +coarbitraryUTxO :: Show u => u -> Gen a -> Gen a +coarbitraryUTxO = coarbitrary . show -genWalletUTxOFunction :: Gen a -> Gen (WalletUTxO -> a) -genWalletUTxOFunction = genFunction coarbitraryWalletUTxO +genUTxOFunction :: Show u => Gen a -> Gen (u -> a) +genUTxOFunction = genFunction coarbitraryUTxO -genUTxOSelection :: Gen (UTxOSelection WalletUTxO) -genUTxOSelection = UTxOSelection.fromIndexFiltered - <$> genFilter - <*> genUTxOIndex +genUTxOSelection :: forall u. (Ord u, Show u) => Gen u -> Gen (UTxOSelection u) +genUTxOSelection genUTxO = UTxOSelection.fromIndexFiltered + <$> genUTxOFilter + <*> genUTxOIndex genUTxO where - genFilter :: Gen (WalletUTxO -> Bool) - genFilter = genWalletUTxOFunction (arbitrary @Bool) + genUTxOFilter :: Gen (u -> Bool) + genUTxOFilter = genUTxOFunction (arbitrary @Bool) -shrinkUTxOSelection :: UTxOSelection WalletUTxO -> [UTxOSelection WalletUTxO] -shrinkUTxOSelection = +shrinkUTxOSelection + :: Ord u => (u -> [u]) -> (UTxOSelection u -> [UTxOSelection u]) +shrinkUTxOSelection shrinkUTxO = shrinkMapBy UTxOSelection.fromIndexPair UTxOSelection.toIndexPair $ liftShrink2 - shrinkUTxOIndex - shrinkUTxOIndex + (shrinkUTxOIndex shrinkUTxO) + (shrinkUTxOIndex shrinkUTxO) -------------------------------------------------------------------------------- -- Selections that are non-empty -------------------------------------------------------------------------------- -genUTxOSelectionNonEmpty :: Gen (UTxOSelectionNonEmpty WalletUTxO) -genUTxOSelectionNonEmpty = - genUTxOSelection `suchThatMap` UTxOSelection.toNonEmpty +genUTxOSelectionNonEmpty + :: (Ord u, Show u) => Gen u -> Gen (UTxOSelectionNonEmpty u) +genUTxOSelectionNonEmpty genUTxO = + genUTxOSelection genUTxO `suchThatMap` UTxOSelection.toNonEmpty shrinkUTxOSelectionNonEmpty - :: UTxOSelectionNonEmpty WalletUTxO - -> [UTxOSelectionNonEmpty WalletUTxO] -shrinkUTxOSelectionNonEmpty + :: Ord u => (u -> [u]) -> (UTxOSelectionNonEmpty u -> [UTxOSelectionNonEmpty u]) +shrinkUTxOSelectionNonEmpty shrinkUTxO = mapMaybe UTxOSelection.toNonEmpty - . shrinkUTxOSelection + . shrinkUTxOSelection shrinkUTxO . UTxOSelection.fromNonEmpty diff --git a/lib/core/test/unit/Cardano/Wallet/CoinSelection/Internal/BalanceSpec.hs b/lib/core/test/unit/Cardano/Wallet/CoinSelection/Internal/BalanceSpec.hs index 80e6e819e5b..cc503709393 100644 --- a/lib/core/test/unit/Cardano/Wallet/CoinSelection/Internal/BalanceSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/CoinSelection/Internal/BalanceSpec.hs @@ -42,6 +42,12 @@ import Cardano.Numeric.Util ( inAscendingPartialOrder ) import Cardano.Wallet.CoinSelection ( WalletSelectionContext, WalletUTxO (..) ) +import Cardano.Wallet.CoinSelection.Gen + ( genWalletUTxO + , genWalletUTxOFunction + , genWalletUTxOLargeRange + , shrinkWalletUTxO + ) import Cardano.Wallet.CoinSelection.Internal.Balance ( AssetCount (..) , BalanceInsufficientError (..) @@ -214,7 +220,6 @@ import Test.QuickCheck , arbitraryBoundedEnum , checkCoverage , choose - , coarbitrary , conjoin , counterexample , cover @@ -240,7 +245,7 @@ import Test.QuickCheck import Test.QuickCheck.Classes ( eqLaws, ordLaws ) import Test.QuickCheck.Extra - ( genFunction, genericRoundRobinShrink, report, verify, (<:>), (<@>) ) + ( genericRoundRobinShrink, report, verify, (<:>), (<@>) ) import Test.QuickCheck.Monadic ( PropertyM (..), assert, monadicIO, monitor, run ) import Test.Utils.Laws @@ -654,7 +659,7 @@ shrinkSelectionParams -> [SelectionParams WalletSelectionContext] shrinkSelectionParams = genericRoundRobinShrink <@> shrinkList shrinkOutput - <:> shrinkUTxOSelection + <:> shrinkUTxOSelection shrinkWalletUTxO <:> shrinkCoin <:> shrinkCoin <:> shrinkTokenMap @@ -868,7 +873,7 @@ prop_performSelection_huge = ioProperty $ -- the cost of re-generating it on every pass. This will still generate -- interesting cases, since selection within that large index is random. property . prop_performSelection_huge_inner - <$> generate (genUTxOIndexLargeN 50000) + <$> generate (genUTxOIndexLargeN genWalletUTxOLargeRange 50000) prop_performSelection_huge_inner :: UTxOIndex WalletUTxO @@ -4371,16 +4376,6 @@ unitTests lbl cases = forM_ (zip [1..] cases) $ \(i, test) -> it (lbl <> " example #" <> show @Int i) test --------------------------------------------------------------------------------- --- Wallet UTxO identifiers --------------------------------------------------------------------------------- - -coarbitraryWalletUTxO :: WalletUTxO -> Gen a -> Gen a -coarbitraryWalletUTxO = coarbitrary . show - -genWalletUTxOFunction :: Gen a -> Gen (WalletUTxO -> a) -genWalletUTxOFunction = genFunction coarbitraryWalletUTxO - -------------------------------------------------------------------------------- -- Arbitrary instances -------------------------------------------------------------------------------- @@ -4444,8 +4439,8 @@ instance Arbitrary TxOut where shrink = shrinkTxOut instance Arbitrary (UTxOSelection WalletUTxO) where - arbitrary = genUTxOSelection - shrink = shrinkUTxOSelection + arbitrary = genUTxOSelection genWalletUTxO + shrink = shrinkUTxOSelection shrinkWalletUTxO newtype Large a = Large { getLarge :: a } @@ -4458,22 +4453,22 @@ newtype Small a = Small instance Arbitrary (Large (SelectionParams WalletSelectionContext)) where arbitrary = Large <$> genSelectionParams (genWalletUTxOFunction (arbitrary @Bool)) - (genUTxOIndexLarge) + (genUTxOIndexLarge genWalletUTxOLargeRange) shrink = shrinkMapBy Large getLarge shrinkSelectionParams instance Arbitrary (Small (SelectionParams WalletSelectionContext)) where arbitrary = Small <$> genSelectionParams (genWalletUTxOFunction (arbitrary @Bool)) - (genUTxOIndex) + (genUTxOIndex genWalletUTxO) shrink = shrinkMapBy Small getSmall shrinkSelectionParams instance Arbitrary (Large (UTxOIndex WalletUTxO)) where - arbitrary = Large <$> genUTxOIndexLarge - shrink = shrinkMapBy Large getLarge shrinkUTxOIndex + arbitrary = Large <$> genUTxOIndexLarge genWalletUTxOLargeRange + shrink = shrinkMapBy Large getLarge (shrinkUTxOIndex shrinkWalletUTxO) instance Arbitrary (Small (UTxOIndex WalletUTxO)) where - arbitrary = Small <$> genUTxOIndex - shrink = shrinkMapBy Small getSmall shrinkUTxOIndex + arbitrary = Small <$> genUTxOIndex genWalletUTxO + shrink = shrinkMapBy Small getSmall (shrinkUTxOIndex shrinkWalletUTxO) instance Arbitrary Coin where arbitrary = genCoinPositive diff --git a/lib/core/test/unit/Cardano/Wallet/CoinSelection/InternalSpec.hs b/lib/core/test/unit/Cardano/Wallet/CoinSelection/InternalSpec.hs index 581ce8f3e78..29c9a8c908a 100644 --- a/lib/core/test/unit/Cardano/Wallet/CoinSelection/InternalSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/CoinSelection/InternalSpec.hs @@ -18,6 +18,8 @@ import Prelude import Cardano.Wallet.CoinSelection ( WalletSelectionContext, WalletUTxO (..) ) +import Cardano.Wallet.CoinSelection.Gen + ( genWalletUTxO, shrinkWalletUTxO ) import Cardano.Wallet.CoinSelection.Internal ( ComputeMinimumCollateralParams (..) , Selection @@ -82,8 +84,6 @@ import Cardano.Wallet.Primitive.Types.TokenQuantity ( TokenQuantity (..) ) import Cardano.Wallet.Primitive.Types.Tx ( txOutMaxTokenQuantity ) -import Cardano.Wallet.Primitive.Types.Tx.Gen - ( genTxIn, shrinkTxIn ) import Cardano.Wallet.Primitive.Types.UTxOSelection ( UTxOSelection ) import Cardano.Wallet.Primitive.Types.UTxOSelection.Gen @@ -138,7 +138,6 @@ import Test.QuickCheck.Extra ( Pretty (..) , chooseNatural , genMapWith - , genSized2 , genericRoundRobinShrink , report , shrinkMapWith @@ -799,13 +798,10 @@ shrinkCollateralRequirement = genericShrink genUTxOAvailableForCollateral :: Gen (Map WalletUTxO Coin) genUTxOAvailableForCollateral = genMapWith genWalletUTxO genCoinPositive - where - genWalletUTxO :: Gen WalletUTxO - genWalletUTxO = uncurry WalletUTxO <$> genSized2 genTxIn genAddress genUTxOAvailableForInputs :: Gen (UTxOSelection WalletUTxO) genUTxOAvailableForInputs = frequency - [ (49, genUTxOSelection) + [ (49, genUTxOSelection genWalletUTxO) , (01, pure UTxOSelection.empty) ] @@ -813,16 +809,10 @@ shrinkUTxOAvailableForCollateral :: Map WalletUTxO Coin -> [Map WalletUTxO Coin] shrinkUTxOAvailableForCollateral = shrinkMapWith shrinkWalletUTxO shrinkCoinPositive - where - shrinkWalletUTxO :: WalletUTxO -> [WalletUTxO] - shrinkWalletUTxO = genericRoundRobinShrink - <@> shrinkTxIn - <:> shrinkAddress - <:> Nil shrinkUTxOAvailableForInputs :: UTxOSelection WalletUTxO -> [UTxOSelection WalletUTxO] -shrinkUTxOAvailableForInputs = shrinkUTxOSelection +shrinkUTxOAvailableForInputs = shrinkUTxOSelection shrinkWalletUTxO -------------------------------------------------------------------------------- -- Unit test support @@ -870,5 +860,5 @@ instance Arbitrary (SelectionParams WalletSelectionContext) where shrink = shrinkSelectionParams instance Arbitrary (SelectionSkeleton WalletSelectionContext) where - arbitrary = genSelectionSkeleton - shrink = shrinkSelectionSkeleton + arbitrary = genSelectionSkeleton genAddress + shrink = shrinkSelectionSkeleton shrinkAddress diff --git a/lib/core/test/unit/Cardano/Wallet/CoinSelectionSpec.hs b/lib/core/test/unit/Cardano/Wallet/CoinSelectionSpec.hs new file mode 100644 index 00000000000..8563cfa1dc3 --- /dev/null +++ b/lib/core/test/unit/Cardano/Wallet/CoinSelectionSpec.hs @@ -0,0 +1,166 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Cardano.Wallet.CoinSelectionSpec + where + +import Prelude + +import Cardano.Wallet.CoinSelection + ( Selection + , SelectionOf (..) + , toExternalSelection + , toExternalUTxO + , toExternalUTxOMap + , toInternalSelection + , toInternalUTxO + , toInternalUTxOMap + ) +import Cardano.Wallet.Primitive.Types.Address.Gen + ( genAddress ) +import Cardano.Wallet.Primitive.Types.Coin.Gen + ( genCoin, shrinkCoin ) +import Cardano.Wallet.Primitive.Types.TokenBundle.Gen + ( genTokenBundle, shrinkTokenBundle ) +import Cardano.Wallet.Primitive.Types.TokenMap.Gen + ( genTokenMap, shrinkTokenMap ) +import Cardano.Wallet.Primitive.Types.Tx + ( TxIn, TxOut (..) ) +import Cardano.Wallet.Primitive.Types.Tx.Gen + ( genTxIn, genTxOut, shrinkTxIn, shrinkTxOut ) +import Cardano.Wallet.Primitive.Types.UTxO + ( UTxO ) +import Cardano.Wallet.Primitive.Types.UTxO.Gen + ( genUTxO, genUTxOLarge, shrinkUTxO ) +import Data.Function + ( (&) ) +import Generics.SOP + ( NP (..) ) +import Test.Hspec + ( Spec, describe, it ) +import Test.Hspec.Extra + ( parallel ) +import Test.QuickCheck + ( Arbitrary (..) + , Gen + , Property + , liftShrink2 + , listOf + , oneof + , property + , shrinkList + , (===) + ) +import Test.QuickCheck.Extra + ( genNonEmpty, genericRoundRobinShrink, shrinkNonEmpty, (<:>), (<@>) ) +import Test.Utils.Pretty + ( (====) ) + +import qualified Cardano.Wallet.Primitive.Types.TokenBundle as TokenBundle + +spec :: Spec +spec = describe "Cardano.Wallet.CoinSelectionSpec" $ do + + parallel $ describe + "Conversion between external (wallet) and internal UTxOs" $ do + + it "prop_toInternalUTxO_toExternalUTxO" $ + prop_toInternalUTxO_toExternalUTxO & property + + it "prop_toInternalUTxOMap_toExternalUTxOMap" $ + prop_toInternalUTxOMap_toExternalUTxOMap & property + + parallel $ describe + "Conversion between external (wallet) and internal selections" $ do + + it "prop_toInternalSelection_toExternalSelection" $ + prop_toInternalSelection_toExternalSelection & property + +-------------------------------------------------------------------------------- +-- Conversion between external (wallet) and internal UTxOs +-------------------------------------------------------------------------------- + +prop_toInternalUTxO_toExternalUTxO :: TxIn -> TxOut -> Property +prop_toInternalUTxO_toExternalUTxO i o = + (toExternalUTxO . toInternalUTxO) (i, o) === (i, o) + +prop_toInternalUTxOMap_toExternalUTxOMap :: UTxO -> Property +prop_toInternalUTxOMap_toExternalUTxOMap u = + (toExternalUTxOMap . toInternalUTxOMap) u === u + +-------------------------------------------------------------------------------- +-- Conversion between external (wallet) and internal selections +-------------------------------------------------------------------------------- + +prop_toInternalSelection_toExternalSelection :: Selection -> Property +prop_toInternalSelection_toExternalSelection s = + (toExternalSelection . toInternalSelection id) s ==== s + +-------------------------------------------------------------------------------- +-- External (wallet) selections +-------------------------------------------------------------------------------- + +genSelection :: Gen Selection +genSelection = Selection + <$> genInputs + <*> genCollateral + <*> genOutputs + <*> genChange + <*> genAssetsToMint + <*> genAssetsToBurn + <*> genExtraCoinSource + <*> genExtraCoinSink + where + genInputs = genNonEmpty ((,) <$> genTxIn <*> genTxOut) + genCollateral = listOf ((,) <$> genTxIn <*> genTxOutCoin) + genOutputs = listOf genTxOut + genChange = listOf genTokenBundle + genAssetsToMint = genTokenMap + genAssetsToBurn = genTokenMap + genExtraCoinSource = genCoin + genExtraCoinSink = genCoin + genTxOutCoin = TxOut <$> genAddress <*> (TokenBundle.fromCoin <$> genCoin) + +shrinkSelection :: Selection -> [Selection] +shrinkSelection = genericRoundRobinShrink + <@> shrinkInputs + <:> shrinkCollateral + <:> shrinkOutputs + <:> shrinkChange + <:> shrinkAssetsToMint + <:> shrinkAssetsToBurn + <:> shrinkExtraCoinSource + <:> shrinkExtraCoinSink + <:> Nil + where + shrinkInputs = shrinkNonEmpty (liftShrink2 shrinkTxIn shrinkTxOut) + shrinkCollateral = shrinkList (liftShrink2 shrinkTxIn shrinkTxOut) + shrinkOutputs = shrinkList shrinkTxOut + shrinkChange = shrinkList shrinkTokenBundle + shrinkAssetsToMint = shrinkTokenMap + shrinkAssetsToBurn = shrinkTokenMap + shrinkExtraCoinSource = shrinkCoin + shrinkExtraCoinSink = shrinkCoin + +-------------------------------------------------------------------------------- +-- Arbitrary instances +-------------------------------------------------------------------------------- + +instance Arbitrary Selection where + arbitrary = genSelection + shrink = shrinkSelection + +instance Arbitrary TxIn where + arbitrary = genTxIn + shrink = shrinkTxIn + +instance Arbitrary TxOut where + arbitrary = genTxOut + shrink = shrinkTxOut + +instance Arbitrary UTxO where + arbitrary = oneof + [ genUTxO + , genUTxOLarge + ] + shrink = shrinkUTxO diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/Types/UTxOIndexSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/Types/UTxOIndexSpec.hs index c35d39cb79c..993d1003374 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/Types/UTxOIndexSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/Types/UTxOIndexSpec.hs @@ -14,10 +14,12 @@ import Prelude import Cardano.Wallet.CoinSelection ( WalletUTxO (..) ) +import Cardano.Wallet.CoinSelection.Gen + ( coarbitraryWalletUTxO, genWalletUTxO, shrinkWalletUTxO ) import Cardano.Wallet.Primitive.Types.Address ( Address ) import Cardano.Wallet.Primitive.Types.Address.Gen - ( coarbitraryAddress, genAddress ) + ( coarbitraryAddress ) import Cardano.Wallet.Primitive.Types.TokenBundle ( TokenBundle ) import Cardano.Wallet.Primitive.Types.TokenBundle.Gen @@ -29,7 +31,7 @@ import Cardano.Wallet.Primitive.Types.TokenMap.Gen import Cardano.Wallet.Primitive.Types.Tx ( TxIn, TxOut (..) ) import Cardano.Wallet.Primitive.Types.Tx.Gen - ( coarbitraryTxIn, genTxIn, genTxOut, shrinkTxOut ) + ( coarbitraryTxIn, genTxOut, shrinkTxOut ) import Cardano.Wallet.Primitive.Types.UTxOIndex.Gen ( genUTxOIndex, shrinkUTxOIndex ) import Cardano.Wallet.Primitive.Types.UTxOIndex.Internal @@ -57,7 +59,6 @@ import Test.QuickCheck , Testable , checkCoverage , checkCoverageWith - , coarbitraryShow , conjoin , counterexample , cover @@ -71,8 +72,6 @@ import Test.QuickCheck ) import Test.QuickCheck.Classes ( eqLaws ) -import Test.QuickCheck.Extra - ( genSized2 ) import Test.QuickCheck.Monadic ( assert, monadicIO, monitor, run ) import Test.Utils.Laws @@ -782,12 +781,10 @@ tokenBundleIsAdaOnly = TokenBundle.isCoin instance Arbitrary WalletUTxO where arbitrary = genWalletUTxO - -genWalletUTxO :: Gen WalletUTxO -genWalletUTxO = uncurry WalletUTxO <$> genSized2 genTxIn genAddress + shrink = shrinkWalletUTxO instance CoArbitrary WalletUTxO where - coarbitrary = coarbitraryShow + coarbitrary = coarbitraryWalletUTxO instance CoArbitrary Address where coarbitrary = coarbitraryAddress @@ -797,8 +794,8 @@ instance Arbitrary AssetId where shrink = shrinkAssetId instance Arbitrary (UTxOIndex WalletUTxO) where - arbitrary = genUTxOIndex - shrink = shrinkUTxOIndex + arbitrary = genUTxOIndex genWalletUTxO + shrink = shrinkUTxOIndex shrinkWalletUTxO instance CoArbitrary TxIn where coarbitrary = coarbitraryTxIn diff --git a/lib/core/test/unit/Cardano/Wallet/Primitive/Types/UTxOSelectionSpec.hs b/lib/core/test/unit/Cardano/Wallet/Primitive/Types/UTxOSelectionSpec.hs index b6cc8619224..18e3420edb1 100644 --- a/lib/core/test/unit/Cardano/Wallet/Primitive/Types/UTxOSelectionSpec.hs +++ b/lib/core/test/unit/Cardano/Wallet/Primitive/Types/UTxOSelectionSpec.hs @@ -12,14 +12,16 @@ import Prelude import Cardano.Wallet.CoinSelection ( WalletUTxO (..) ) +import Cardano.Wallet.CoinSelection.Gen + ( coarbitraryWalletUTxO, genWalletUTxO, shrinkWalletUTxO ) import Cardano.Wallet.Primitive.Types.Address ( Address ) import Cardano.Wallet.Primitive.Types.Address.Gen - ( coarbitraryAddress, genAddress, shrinkAddress ) + ( coarbitraryAddress ) import Cardano.Wallet.Primitive.Types.Tx ( TxIn ) import Cardano.Wallet.Primitive.Types.Tx.Gen - ( coarbitraryTxIn, genTxIn, shrinkTxIn ) + ( coarbitraryTxIn ) import Cardano.Wallet.Primitive.Types.UTxOIndex ( UTxOIndex ) import Cardano.Wallet.Primitive.Types.UTxOIndex.Gen @@ -32,8 +34,6 @@ import Cardano.Wallet.Primitive.Types.UTxOSelection.Gen , shrinkUTxOSelection , shrinkUTxOSelectionNonEmpty ) -import Generics.SOP - ( NP (..) ) import Test.Hspec ( Spec, describe, it ) import Test.Hspec.Extra @@ -44,15 +44,12 @@ import Test.QuickCheck , Property , Testable , checkCoverage - , coarbitraryShow , conjoin , cover , forAll , property , (===) ) -import Test.QuickCheck.Extra - ( genSized2, genericRoundRobinShrink, (<:>), (<@>) ) import qualified Cardano.Wallet.Primitive.Types.TokenBundle as TokenBundle import qualified Cardano.Wallet.Primitive.Types.UTxOIndex as UTxOIndex @@ -144,25 +141,26 @@ spec = prop_genUTxOSelection :: Property prop_genUTxOSelection = - forAll genUTxOSelection $ \s -> + forAll (genUTxOSelection genWalletUTxO) $ \s -> checkCoverage_UTxOSelection s $ isValidSelection s === True prop_genUTxOSelectionNonEmpty :: Property prop_genUTxOSelectionNonEmpty = - forAll genUTxOSelectionNonEmpty $ \s -> + forAll (genUTxOSelectionNonEmpty genWalletUTxO) $ \s -> checkCoverage_UTxOSelectionNonEmpty s $ isValidSelectionNonEmpty s === True prop_shrinkUTxOSelection :: Property prop_shrinkUTxOSelection = - forAll genUTxOSelection $ \s -> - conjoin (isValidSelection <$> shrinkUTxOSelection s) + forAll (genUTxOSelection genWalletUTxO) $ \s -> + conjoin (isValidSelection <$> shrinkUTxOSelection shrinkWalletUTxO s) prop_shrinkUTxOSelectionNonEmpty :: Property prop_shrinkUTxOSelectionNonEmpty = - forAll genUTxOSelectionNonEmpty $ \s -> - conjoin (isValidSelectionNonEmpty <$> shrinkUTxOSelectionNonEmpty s) + forAll (genUTxOSelectionNonEmpty genWalletUTxO) $ \s -> + conjoin $ isValidSelectionNonEmpty + <$> shrinkUTxOSelectionNonEmpty shrinkWalletUTxO s checkCoverage_UTxOSelection :: Testable p @@ -431,23 +429,20 @@ isValidSelectionNonEmpty s = -- of input identifier has been made into a type parameter. -- instance Arbitrary WalletUTxO where - arbitrary = uncurry WalletUTxO <$> genSized2 genTxIn genAddress - shrink = genericRoundRobinShrink - <@> shrinkTxIn - <:> shrinkAddress - <:> Nil + arbitrary = genWalletUTxO + shrink = shrinkWalletUTxO instance Arbitrary (UTxOIndex WalletUTxO) where - arbitrary = genUTxOIndex - shrink = shrinkUTxOIndex + arbitrary = genUTxOIndex genWalletUTxO + shrink = shrinkUTxOIndex shrinkWalletUTxO instance Arbitrary (UTxOSelection WalletUTxO) where - arbitrary = genUTxOSelection - shrink = shrinkUTxOSelection + arbitrary = genUTxOSelection genWalletUTxO + shrink = shrinkUTxOSelection shrinkWalletUTxO instance Arbitrary (UTxOSelectionNonEmpty WalletUTxO) where - arbitrary = genUTxOSelectionNonEmpty - shrink = shrinkUTxOSelectionNonEmpty + arbitrary = genUTxOSelectionNonEmpty genWalletUTxO + shrink = shrinkUTxOSelectionNonEmpty shrinkWalletUTxO -------------------------------------------------------------------------------- -- CoArbitrary instances @@ -460,7 +455,7 @@ instance CoArbitrary TxIn where coarbitrary = coarbitraryTxIn instance CoArbitrary WalletUTxO where - coarbitrary = coarbitraryShow + coarbitrary = coarbitraryWalletUTxO -------------------------------------------------------------------------------- -- Show instances diff --git a/lib/test-utils/src/Test/QuickCheck/Extra.hs b/lib/test-utils/src/Test/QuickCheck/Extra.hs index 2b536fcbc28..7ddc29dee1f 100644 --- a/lib/test-utils/src/Test/QuickCheck/Extra.hs +++ b/lib/test-utils/src/Test/QuickCheck/Extra.hs @@ -35,6 +35,10 @@ module Test.QuickCheck.Extra , chooseNatural , shrinkNatural + -- * Generating and shrinking non-empty lists + , genNonEmpty + , shrinkNonEmpty + -- * Counterexamples , report , verify @@ -54,6 +58,8 @@ import Prelude import Data.IntCast ( intCast, intCastMaybe ) +import Data.List.NonEmpty + ( NonEmpty (..) ) import Data.Map.Strict ( Map ) import Data.Maybe @@ -90,6 +96,7 @@ import Text.Pretty.Simple ( pShow ) import qualified Data.List as L +import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map import qualified Data.Text.Lazy as TL import qualified Generics.SOP.GGP as GGP @@ -197,6 +204,16 @@ shrinkNatural n $ shrinkIntegral $ intCast n +-------------------------------------------------------------------------------- +-- Generating and shrinking non-empty lists +-------------------------------------------------------------------------------- + +genNonEmpty :: Gen a -> Gen (NonEmpty a) +genNonEmpty genA = (:|) <$> genA <*> listOf genA + +shrinkNonEmpty :: (a -> [a]) -> (NonEmpty a -> [NonEmpty a]) +shrinkNonEmpty shrinkA = mapMaybe NE.nonEmpty . shrinkList shrinkA . NE.toList + -------------------------------------------------------------------------------- -- Generating functions -------------------------------------------------------------------------------- diff --git a/nix/materialized/stack-nix/cardano-wallet-core.nix b/nix/materialized/stack-nix/cardano-wallet-core.nix index 8967cf0cc69..7c2252cbe1c 100644 --- a/nix/materialized/stack-nix/cardano-wallet-core.nix +++ b/nix/materialized/stack-nix/cardano-wallet-core.nix @@ -250,6 +250,7 @@ "Network/Wai/Middleware/Logging" "Ouroboros/Network/Client/Wallet" "UnliftIO/Compat" + "Cardano/Wallet/CoinSelection/Gen" "Cardano/Wallet/CoinSelection/Internal/Balance/Gen" "Cardano/Wallet/Primitive/Types/Address/Gen" "Cardano/Wallet/Primitive/Types/Coin/Gen" @@ -388,6 +389,7 @@ "Cardano/Wallet/Api/ServerSpec" "Cardano/Wallet/Api/TypesSpec" "Cardano/Wallet/ApiSpec" + "Cardano/Wallet/CoinSelectionSpec" "Cardano/Wallet/CoinSelection/InternalSpec" "Cardano/Wallet/CoinSelection/Internal/BalanceSpec" "Cardano/Wallet/CoinSelection/Internal/CollateralSpec"