Skip to content

Commit eb8034f

Browse files
vrom911v0d1ch
authored andcommitted
Add tests for checkTokens fn
1 parent 5666a3d commit eb8034f

File tree

2 files changed

+189
-2
lines changed

2 files changed

+189
-2
lines changed

hydra-tx/test/Hydra/Tx/Contract/Deposit.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ genHealthyDepositTx = do
3131
slot
3232
healthyDeadline
3333
Nothing
34+
mempty
3435
pure (tx, toDeposit)
3536
where
3637
slot = chooseEnum (0, healthyDeadlineSlot) `generateWith` 42

hydra-tx/test/Hydra/Tx/DepositSpec.hs

Lines changed: 188 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,108 @@ import Hydra.Prelude
44
import Test.Hydra.Prelude
55

66
import Cardano.Api.UTxO qualified as UTxO
7+
import Data.Map.Strict qualified as Map
78
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)
1012
import Test.Hydra.Tx.Gen (genUTxOSized)
1113
import Test.QuickCheck (Property, chooseInteger, counterexample, (===), (==>))
1214

1315
spec :: Spec
1416
spec =
1517
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+
16109
describe "capUTxO" $ do
17110
describe "tests" $ do
18111
it "returns empty UTxO when target is 0" $ do
@@ -154,3 +247,96 @@ propNoUTxOLoss utxo target =
154247
& counterexample ("Input set size: " <> show (Set.size inputSet))
155248
& counterexample ("Selected set size: " <> show (Set.size selectedSet))
156249
& 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

Comments
 (0)