@@ -4,24 +4,28 @@ module Spec.BotPlutusInterface.CoinSelection (tests) where
44
55import BotPlutusInterface.CoinSelection (selectTxIns , uniqueAssetClasses , valuesToVecs )
66import BotPlutusInterface.Effects (PABEffect )
7- import Control.Lens (folded , to , (^..) )
8- import Control.Monad (replicateM )
7+ import Control.Lens (filtered , foldOf , folded , to , (^..) )
98import Data.Default (def )
9+ import Data.Either (isLeft )
10+ import Data.Map (Map )
1011import Data.Map qualified as Map
12+ import Data.Maybe (fromJust )
1113import Data.Set (Set )
1214import Data.Set qualified as Set
15+ import Data.Text (Text )
1316import Data.Text qualified as Text
1417import Data.Vector (Vector )
1518import Data.Vector qualified as Vec
1619import Ledger qualified
1720import Ledger.Ada qualified as Ada
18- import Ledger.Address (Address , PaymentPubKeyHash (PaymentPubKeyHash ))
21+ import Ledger.Address (Address ( Address ) , PaymentPubKeyHash (PaymentPubKeyHash ))
1922import Ledger.Address qualified as Address
2023import Ledger.CardanoWallet qualified as Wallet
2124import Ledger.Crypto (PubKeyHash )
2225import Ledger.Tx (TxIn (.. ), TxInType (.. ), TxOut (.. ), TxOutRef (.. ))
23- import Ledger.Value (Value , AssetClass )
26+ import Ledger.Value (AssetClass , Value , leq )
2427import Ledger.Value qualified as Value
28+ import Plutus.V1.Ledger.Credential (Credential (PubKeyCredential , ScriptCredential ))
2529import Spec.MockContract (runPABEffectPure )
2630import Spec.RandomLedger
2731import Test.QuickCheck (Gen , Property , forAll , withMaxSuccess )
@@ -36,7 +40,8 @@ tests =
3640 " BotPlutusInterface.CoinSelection"
3741 [ testProperty " Have All unique assetClasses" assertUniqueAssetClasses
3842 , testProperty " columns of vectors represent same assetClass" validValueVectors
39- , testCase " Coin selection greedy Approx" greedyApprox
43+ , testProperty " coin selection results in valid balance of Tx" validateBalancing
44+ , testCase " coin selection greedy Approx" greedyApprox
4045 ]
4146
4247pkh1 :: PubKeyHash
@@ -107,36 +112,118 @@ greedyApprox = do
107112 Right result -> result @?= expectedResults
108113
109114assertUniqueAssetClasses :: Property
110- assertUniqueAssetClasses = withMaxSuccess 1000 (forAll isSubsetGen id )
115+ assertUniqueAssetClasses = withMaxSuccess 1000 (forAll uniqueAssetClassesGen validate )
111116 where
112- isSubsetGen :: Gen Bool
113- isSubsetGen =
117+ validate :: (Set AssetClass , [TxOut ]) -> Bool
118+ validate (allAssetClasses, utxos) =
119+ Set. isSubsetOf (uniqueAssetClasses $ map txOutValue utxos) allAssetClasses
120+
121+ uniqueAssetClassesGen :: Gen (Set AssetClass , [TxOut ])
122+ uniqueAssetClassesGen =
114123 do
115- allAcs <- randomAssetClasses 30
116- values <- replicateM 10 (txOutValue <$> randomTxOut 10 allAcs)
124+ let numUniqueAssetClasses :: Int
125+ numUniqueAssetClasses = 30
126+
127+ assetClassSampleSize :: Int
128+ assetClassSampleSize = 30
129+
130+ numUTxOs :: Int
131+ numUTxOs = 10
117132
118- let uniqueAcs :: Set AssetClass
119- uniqueAcs = uniqueAssetClasses values
133+ allAssetClasses <- randomAssetClasses numUniqueAssetClasses
120134
121- return $ Set. isSubsetOf uniqueAcs allAcs
135+ utxos <- randomTxOuts numUTxOs assetClassSampleSize allAssetClasses
136+
137+ return (allAssetClasses, utxos)
122138
123139validValueVectors :: Property
124- validValueVectors = withMaxSuccess 1000 (forAll valueVectorsGen id )
140+ validValueVectors = withMaxSuccess 1000 (forAll txOutsGen validate )
125141 where
126- valueVectorsGen :: Gen Bool
127- valueVectorsGen =
142+ validate :: [TxOut ] -> Bool
143+ validate utxos =
144+ let values :: [Value ]
145+ values = map txOutValue utxos
146+
147+ uniqueAcs :: Set AssetClass
148+ uniqueAcs = uniqueAssetClasses values
149+
150+ valueCheck :: Value -> Vector Integer -> Bool
151+ valueCheck value vec =
152+ Vec. fromList (uniqueAcs ^.. folded . to (Value. assetClassValueOf value)) == vec
153+
154+ valuesVec :: [Vector Integer ]
155+ valuesVec = Vec. toList $ valuesToVecs uniqueAcs values
156+ in all (uncurry valueCheck) (zip values valuesVec)
157+
158+ txOutsGen :: Gen [TxOut ]
159+ txOutsGen =
128160 do
129- allAcs <- randomAssetClasses 30
130- values <- replicateM 10 (txOutValue <$> randomTxOut 10 allAcs)
161+ let numUniqueAssetClasses :: Int
162+ numUniqueAssetClasses = 30
163+
164+ assetClassSampleSize :: Int
165+ assetClassSampleSize = 30
166+
167+ numUTxOs :: Int
168+ numUTxOs = 10
169+
170+ allAssetClasses <- randomAssetClasses numUniqueAssetClasses
171+ randomTxOuts numUTxOs assetClassSampleSize allAssetClasses
172+
173+ validateBalancing :: Property
174+ validateBalancing = withMaxSuccess 10000 (forAll balanceGen validate)
175+ where
176+ validate :: (TxOut , Map TxOutRef TxOut ) -> Bool
177+ validate (txOutput, utxos) =
178+ let result :: (Either Text (Either Text (Set TxIn )))
179+ result =
180+ fst $
181+ runPABEffectPure def $
182+ selectTxIns @ () @ '[PABEffect () ] mempty utxos (txOutValue txOutput)
183+
184+ isScriptOutput :: TxOut -> Bool
185+ isScriptOutput TxOut {txOutAddress = Address {addressCredential = ScriptCredential _}} = True
186+ isScriptOutput TxOut {txOutAddress = Address {addressCredential = PubKeyCredential _}} = False
187+
188+ sufficientValue :: Bool
189+ sufficientValue =
190+ txOutValue txOutput
191+ `leq` foldOf (folded . filtered (not . isScriptOutput) . to txOutValue) utxos
192+
193+ toTxOut :: TxIn -> TxOut
194+ toTxOut = fromJust . (`Map.lookup` utxos) . txInRef
195+ in case result of
196+ Left _ -> False
197+ Right eselectedTxIns
198+ | not sufficientValue -> isLeft eselectedTxIns
199+ | otherwise ->
200+ case eselectedTxIns of
201+ Left err
202+ | txOutValue txOutput == mempty -> True
203+ | otherwise -> error (show err)
204+ Right selectedTxIns ->
205+ txOutValue txOutput `leq` foldOf (folded . to (txOutValue . toTxOut)) selectedTxIns
206+
207+ balanceGen :: Gen (TxOut , Map TxOutRef TxOut )
208+ balanceGen =
209+ do
210+ let numUniqueAssetClasses :: Int
211+ numUniqueAssetClasses = 10
212+
213+ assetClassSampleSize :: Int
214+ assetClassSampleSize = 30
215+
216+ numUTxOs :: Int
217+ numUTxOs = 20
218+
219+ allAcs <- randomAssetClasses numUniqueAssetClasses
220+ rTxOuts <- randomTxOuts numUTxOs assetClassSampleSize allAcs
221+ rTxOutRefs <- randomTxOutRefs 19
131222
132- let uniqueAcs :: Set AssetClass
133- uniqueAcs = uniqueAssetClasses values
223+ let txOutput :: TxOut
224+ txOutput = head rTxOuts
134225
135- valueCheck :: Value -> Vector Integer -> Bool
136- valueCheck value vec =
137- Vec. fromList (uniqueAcs ^.. folded . to (Value. assetClassValueOf value)) == vec
226+ utxos :: Map TxOutRef TxOut
227+ utxos = Map. fromList $ zip (tail rTxOutRefs) (tail rTxOuts)
138228
139- valuesVec :: [Vector Integer ]
140- valuesVec = Vec. toList $ valuesToVecs uniqueAcs values
141-
142- return $ all (uncurry valueCheck) (zip values valuesVec)
229+ return (txOutput, utxos)
0 commit comments