@@ -4,15 +4,108 @@ import Hydra.Prelude
4
4
import Test.Hydra.Prelude
5
5
6
6
import Cardano.Api.UTxO qualified as UTxO
7
+ import Data.Map.Strict qualified as Map
7
8
import Data.Set qualified as Set
8
- import Hydra.Cardano.Api (Coin (.. ), UTxO , selectLovelace , txOutValue )
9
- import Hydra.Tx.Deposit (capUTxO )
9
+ import Hydra.Cardano.Api (AssetId (.. ), AssetName , Coin (.. ), PolicyAssets (.. ), PolicyId , Quantity (.. ), UTxO , selectLovelace , txOutValue , valueToPolicyAssets )
10
+ import Hydra.Tx.Deposit (capUTxO , checkTokens )
11
+ import Test.Hydra.Tx.Fixture (testPolicyId )
10
12
import Test.Hydra.Tx.Gen (genUTxOSized )
11
13
import Test.QuickCheck (Property , chooseInteger , counterexample , (===) , (==>) )
12
14
13
15
spec :: Spec
14
16
spec =
15
17
parallel $ do
18
+ describe " checkTokens" $ do
19
+ describe " tests" $ do
20
+ it " returns empty results when no tokens are specified" $ do
21
+ let utxo = genUTxOSized 3 `generateWith` 42
22
+ let (valid, invalid) = checkTokens utxo mempty
23
+ valid `shouldBe` mempty
24
+ invalid `shouldBe` mempty
25
+
26
+ it " returns empty results when UTxO is empty" $ do
27
+ let tokens = Map. fromList [(testPolicyId, PolicyAssets $ Map. fromList [(testAssetName, Quantity 10 )])]
28
+ let (valid, invalid) = checkTokens mempty tokens
29
+ valid `shouldBe` mempty
30
+ invalid `shouldBe` tokens
31
+
32
+ it " returns all tokens as invalid when policy is missing from UTxO" $ do
33
+ let utxo = genUTxOSized 3 `generateWith` 42 -- UTxO with only ADA
34
+ let tokens = Map. fromList [(testPolicyId, PolicyAssets $ Map. fromList [(testAssetName, Quantity 10 )])]
35
+ let (valid, invalid) = checkTokens utxo tokens
36
+ valid `shouldBe` mempty
37
+ invalid `shouldBe` tokens
38
+
39
+ it " validates tokens correctly when exact quantities match" $ do
40
+ let testUTxO = utxoWithTokens [(testPolicyId, testAssetName, Quantity 100 )]
41
+ let tokens = Map. fromList [(testPolicyId, PolicyAssets $ Map. fromList [(testAssetName, Quantity 100 )])]
42
+ let (valid, invalid) = checkTokens testUTxO tokens
43
+ valid `shouldBe` tokens
44
+ invalid `shouldBe` mempty
45
+
46
+ it " validates tokens correctly when UTxO has more than required" $ do
47
+ let testUTxO = utxoWithTokens [(testPolicyId, testAssetName, Quantity 150 )]
48
+ let tokens = Map. fromList [(testPolicyId, PolicyAssets $ Map. fromList [(testAssetName, Quantity 100 )])]
49
+ let (valid, invalid) = checkTokens testUTxO tokens
50
+ valid `shouldBe` tokens
51
+ invalid `shouldBe` mempty
52
+
53
+ it " returns tokens as invalid when UTxO has less than required" $ do
54
+ let testUTxO = utxoWithTokens [(testPolicyId, testAssetName, Quantity 50 )]
55
+ let tokens = Map. fromList [(testPolicyId, PolicyAssets $ Map. fromList [(testAssetName, Quantity 100 )])]
56
+ let (valid, invalid) = checkTokens testUTxO tokens
57
+ valid `shouldBe` mempty
58
+ invalid `shouldBe` tokens
59
+
60
+ it " handles mixed scenarios with multiple tokens" $ do
61
+ let testUTxO =
62
+ utxoWithTokens
63
+ [ (testPolicyId, testAssetName, Quantity 100 )
64
+ , (testPolicyId2, testAssetName, Quantity 50 )
65
+ ]
66
+ let tokens =
67
+ Map. fromList
68
+ [ (testPolicyId, PolicyAssets $ Map. fromList [(testAssetName, Quantity 100 )]) -- Valid
69
+ , (testPolicyId2, PolicyAssets $ Map. fromList [(testAssetName, Quantity 75 )]) -- Invalid - insufficient
70
+ , (testPolicyId3, PolicyAssets $ Map. fromList [(testAssetName, Quantity 25 )]) -- Invalid - missing policy
71
+ ]
72
+ let (valid, invalid) = checkTokens testUTxO tokens
73
+
74
+ valid `shouldBe` Map. fromList [(testPolicyId, PolicyAssets $ Map. fromList [(testAssetName, Quantity 100 )])]
75
+ invalid
76
+ `shouldBe` Map. fromList
77
+ [ (testPolicyId2, PolicyAssets $ Map. fromList [(testAssetName, Quantity 75 )])
78
+ , (testPolicyId3, PolicyAssets $ Map. fromList [(testAssetName, Quantity 25 )])
79
+ ]
80
+
81
+ it " handles multiple assets within the same policy" $ do
82
+ let testUTxO =
83
+ utxoWithTokens
84
+ [ (testPolicyId, testAssetName, Quantity 100 )
85
+ , (testPolicyId, testAssetName2, Quantity 200 )
86
+ ]
87
+ let tokens =
88
+ Map. fromList
89
+ [
90
+ ( testPolicyId
91
+ , PolicyAssets $
92
+ Map. fromList
93
+ [ (testAssetName, Quantity 50 ) -- Valid
94
+ , (testAssetName2, Quantity 150 ) -- Valid
95
+ , (testAssetName3, Quantity 10 ) -- Invalid - missing asset
96
+ ]
97
+ )
98
+ ]
99
+ let (valid, invalid) = checkTokens testUTxO tokens
100
+ valid `shouldBe` mempty -- All assets in policy must be valid for policy to be valid
101
+ invalid `shouldBe` tokens
102
+
103
+ describe " property tests" $ do
104
+ prop " preserves all input tokens (completeness)" propPreservesAllTokens
105
+ prop " valid and invalid tokens are disjoint" propValidInvalidDisjoint
106
+ prop " all valid tokens exist in UTxO with sufficient quantities" propValidTokensExistInUTxO
107
+ prop " monotonic with respect to UTxO additions" propMonotonicUTxOAdditions
108
+
16
109
describe " capUTxO" $ do
17
110
describe " tests" $ do
18
111
it " returns empty UTxO when target is 0" $ do
@@ -154,3 +247,96 @@ propNoUTxOLoss utxo target =
154
247
& counterexample (" Input set size: " <> show (Set. size inputSet))
155
248
& counterexample (" Selected set size: " <> show (Set. size selectedSet))
156
249
& counterexample (" Leftover set size: " <> show (Set. size leftoverSet))
250
+
251
+ -- * Helper functions for checkTokens tests
252
+
253
+ -- | Create additional test PolicyIds for testing (using the existing testPolicyId from fixtures)
254
+ testPolicyId2 :: PolicyId
255
+ testPolicyId2 = generateWith arbitrary 43
256
+
257
+ testPolicyId3 :: PolicyId
258
+ testPolicyId3 = generateWith arbitrary 44
259
+
260
+ -- | Create test AssetNames for testing
261
+ testAssetName :: AssetName
262
+ testAssetName = " TestToken1"
263
+
264
+ testAssetName2 :: AssetName
265
+ testAssetName2 = " TestToken2"
266
+
267
+ testAssetName3 :: AssetName
268
+ testAssetName3 = " TestToken3"
269
+
270
+ -- | Create a UTxO with specific tokens for testing
271
+ utxoWithTokens :: [(PolicyId , AssetName , Quantity )] -> UTxO
272
+ utxoWithTokens tokens =
273
+ let value = fromList $ map (\ (pid, aname, qty) -> (AssetId pid aname, qty)) tokens
274
+ -- Add some ADA to make it a valid UTxO
275
+ valueWithAda = value <> fromList [(AdaAssetId , Quantity 1000000 )]
276
+ txIn = generateWith arbitrary 42
277
+ baseTxOut = generateWith arbitrary 42
278
+ -- Update the txOut to have our custom value
279
+ txOut = baseTxOut{txOutValue = valueWithAda}
280
+ in UTxO. singleton txIn txOut
281
+
282
+ -- * Property tests for checkTokens
283
+
284
+ -- | Property: All input tokens are preserved in either valid or invalid results
285
+ propPreservesAllTokens :: UTxO -> Map PolicyId PolicyAssets -> Property
286
+ propPreservesAllTokens utxo specifiedTokens =
287
+ let (valid, invalid) = checkTokens utxo specifiedTokens
288
+ inputPolicies = Map. keysSet specifiedTokens
289
+ validPolicies = Map. keysSet valid
290
+ invalidPolicies = Map. keysSet invalid
291
+ allResultPolicies = validPolicies <> invalidPolicies
292
+ in inputPolicies === allResultPolicies
293
+ & counterexample (" Input policies: " <> show inputPolicies)
294
+ & counterexample (" Valid policies: " <> show validPolicies)
295
+ & counterexample (" Invalid policies: " <> show invalidPolicies)
296
+
297
+ -- | Property: Valid and invalid results are disjoint sets
298
+ propValidInvalidDisjoint :: UTxO -> Map PolicyId PolicyAssets -> Property
299
+ propValidInvalidDisjoint utxo specifiedTokens =
300
+ let (valid, invalid) = checkTokens utxo specifiedTokens
301
+ validPolicies = Map. keysSet valid
302
+ invalidPolicies = Map. keysSet invalid
303
+ intersection = Set. intersection validPolicies invalidPolicies
304
+ in Set. null intersection
305
+ & counterexample (" Valid policies: " <> show validPolicies)
306
+ & counterexample (" Invalid policies: " <> show invalidPolicies)
307
+ & counterexample (" Intersection: " <> show intersection)
308
+
309
+ -- | Property: All valid tokens must exist in UTxO with sufficient quantities
310
+ propValidTokensExistInUTxO :: UTxO -> Map PolicyId PolicyAssets -> Property
311
+ propValidTokensExistInUTxO utxo specifiedTokens =
312
+ let (valid, _) = checkTokens utxo specifiedTokens
313
+ utxoValue = UTxO. totalValue utxo
314
+ utxoPolicyAssets = valueToPolicyAssets utxoValue
315
+ in all (checkValidTokenInUTxO utxoPolicyAssets) (Map. toList valid)
316
+ & counterexample (" Valid tokens: " <> show valid)
317
+ & counterexample (" UTxO policy assets: " <> show utxoPolicyAssets)
318
+ where
319
+ checkValidTokenInUTxO :: Map PolicyId PolicyAssets -> (PolicyId , PolicyAssets ) -> Bool
320
+ checkValidTokenInUTxO utxoAssets (policyId, PolicyAssets requiredAssets) =
321
+ case Map. lookup policyId utxoAssets of
322
+ Nothing -> False
323
+ Just (PolicyAssets availableAssets) ->
324
+ all
325
+ ( \ (assetName, requiredQty) ->
326
+ case Map. lookup assetName availableAssets of
327
+ Nothing -> False
328
+ Just availableQty -> availableQty >= requiredQty
329
+ )
330
+ (Map. toList requiredAssets)
331
+
332
+ -- | Property: Adding more assets to UTxO never decreases valid tokens (monotonic)
333
+ propMonotonicUTxOAdditions :: UTxO -> UTxO -> Map PolicyId PolicyAssets -> Property
334
+ propMonotonicUTxOAdditions utxo1 utxo2 specifiedTokens =
335
+ let combinedUTxO = utxo1 <> utxo2
336
+ (valid1, _) = checkTokens utxo1 specifiedTokens
337
+ (validCombined, _) = checkTokens combinedUTxO specifiedTokens
338
+ valid1Policies = Map. keysSet valid1
339
+ validCombinedPolicies = Map. keysSet validCombined
340
+ in valid1Policies `Set.isSubsetOf` validCombinedPolicies
341
+ & counterexample (" Valid policies in UTxO1: " <> show valid1Policies)
342
+ & counterexample (" Valid policies in combined UTxO: " <> show validCombinedPolicies)
0 commit comments