diff --git a/cabal.project b/cabal.project index b4dfaf52d1b..cfa89af33af 100644 --- a/cabal.project +++ b/cabal.project @@ -27,7 +27,7 @@ source-repository-package -- NOTE: If you would like to update the above, -- see CONTRIBUTING.md#to-update-the-referenced-agda-ledger-spec index-state: - , hackage.haskell.org 2026-05-06T14:09:41Z + , hackage.haskell.org 2026-05-15T10:51:09Z , cardano-haskell-packages 2026-04-11T06:29:42Z packages: @@ -104,8 +104,8 @@ source-repository-package type: git location: https://github.com/tweag/cardano-cls.git subdir: merkle-tree-incremental mempack-scls scls-cbor scls-cardano scls-format scls-core - --sha256: sha256-XY9e1iqr8Kc7PXNKw1KPyJpEOIqIll+1ZrTg6WWhcJU= - tag: 310a407f572fd227020b598db5926e44a7365a1b + --sha256: sha256-3rAc99T2/MZk7Rd2gb9zq7iUM9hMDOzNvehOq/8hpfs= + tag: 8e2bba9442cb388089f53d708cc4aedcb47726ea constraints: -- Happy version 2.2.1 fails to compile haskell-src-exts diff --git a/eras/alonzo/impl/cardano-ledger-alonzo.cabal b/eras/alonzo/impl/cardano-ledger-alonzo.cabal index 7b47fc744dd..1f030023fa6 100644 --- a/eras/alonzo/impl/cardano-ledger-alonzo.cabal +++ b/eras/alonzo/impl/cardano-ledger-alonzo.cabal @@ -133,6 +133,7 @@ library cddl build-depends: base, cardano-ledger-alonzo, + cardano-ledger-core:cddl, cardano-ledger-mary:cddl, heredoc, diff --git a/eras/alonzo/impl/cddl/data/alonzo.cddl b/eras/alonzo/impl/cddl/data/alonzo.cddl index 401458e37f1..30cfcce8194 100644 --- a/eras/alonzo/impl/cddl/data/alonzo.cddl +++ b/eras/alonzo/impl/cddl/data/alonzo.cddl @@ -436,7 +436,7 @@ plutus_data = / bounded_bytes constr = - #6.102([uint, [* a0]]) + #6.101([uint, [* a0]]) / #6.121([* a0]) / #6.122([* a0]) / #6.123([* a0]) diff --git a/eras/alonzo/impl/cddl/lib/Cardano/Ledger/Alonzo/HuddleSpec.hs b/eras/alonzo/impl/cddl/lib/Cardano/Ledger/Alonzo/HuddleSpec.hs index 0a30a8bfe07..dd35cb8d68b 100644 --- a/eras/alonzo/impl/cddl/lib/Cardano/Ledger/Alonzo/HuddleSpec.hs +++ b/eras/alonzo/impl/cddl/lib/Cardano/Ledger/Alonzo/HuddleSpec.hs @@ -33,6 +33,18 @@ module Cardano.Ledger.Alonzo.HuddleSpec ( ) where import Cardano.Ledger.Alonzo (AlonzoEra) +import Cardano.Ledger.Huddle.Gen ( + MonadGen (choose), + RuleTerm (..), + Term (..), + genArrayTerm, + generateFromGRef, + liftAntiGen, + listOf, + oneof, + scale, + (|!), + ) import Cardano.Ledger.Mary.HuddleSpec import Data.Proxy (Proxy (..)) import Data.Word (Word64) @@ -129,19 +141,40 @@ requiredSignersRule pname p = pname =.= huddleRule1 @"set" p (huddleRule @"addr_ constr :: IsType0 a => Proxy "constr" -> a -> GRuleCall constr pname = binding $ \x -> - pname - =.= - -- We use 'unType0 . toType0' to convert each 'Tagged ArrayChoice' to 'Choice Type2', - -- making the list homogeneous so that 'foldl1 (/)' can be used. - -- Ideally, we should have used `toChoice`, but it's not exported by `cuddle`. - foldl1 - (/) - ( fmap - (unType0 . toType0) - ( tag 102 (arr [a VUInt, a $ arr [0 <+ a x]]) - : [tag t (arr [0 <+ a x]) | t <- [121 .. 127] ++ [1280 .. 1400]] - ) - ) + withCBORGen (generator x) $ + pname + =.= + -- We use 'unType0 . toType0' to convert each 'Tagged ArrayChoice' to 'Choice Type2', + -- making the list homogeneous so that 'foldl1 (/)' can be used. + -- Ideally, we should have used `toChoice`, but it's not exported by `cuddle`. + foldl1 + (/) + ( fmap + (unType0 . toType0) + ( tag 101 (arr [a VUInt, a $ arr [0 <+ a x]]) + : [tag t (arr [0 <+ a x]) | t <- [121 .. 127] ++ [1280 .. 1400]] + ) + ) + where + generator ref = do + t <- + liftAntiGen $ + oneof [choose (121, 127), choose (1280, 1400)] + |! oneof [choose (0, 120), choose (128, 1279), choose (1401, 0xffffffffffffffff)] + let + unwrapElems = traverse $ \case + SingleTerm e -> pure e + _ -> error "Expected single term" + elems <- + scale (`div` 2) $ + if t == 101 + then do + uInt <- TInt <$> choose (0, 2 ^ (64 :: Int) - 1) + elems <- genArrayTerm =<< unwrapElems =<< listOf (generateFromGRef ref) + pure . SingleTerm <$> genArrayTerm [uInt, elems] + else listOf $ generateFromGRef ref + singleElems <- unwrapElems elems + SingleTerm . TTagged t <$> genArrayTerm singleElems instance HuddleGroup "operational_cert" AlonzoEra where huddleGroupNamed = shelleyOperationalCertGroup diff --git a/eras/babbage/impl/cddl/data/babbage.cddl b/eras/babbage/impl/cddl/data/babbage.cddl index 86fb9840d04..238e75681a3 100644 --- a/eras/babbage/impl/cddl/data/babbage.cddl +++ b/eras/babbage/impl/cddl/data/babbage.cddl @@ -168,7 +168,7 @@ plutus_data = / bounded_bytes constr = - #6.102([uint, [* a0]]) + #6.101([uint, [* a0]]) / #6.121([* a0]) / #6.122([* a0]) / #6.123([* a0]) diff --git a/eras/conway/impl/CHANGELOG.md b/eras/conway/impl/CHANGELOG.md index f5b12384b78..24e77e1ff85 100644 --- a/eras/conway/impl/CHANGELOG.md +++ b/eras/conway/impl/CHANGELOG.md @@ -42,10 +42,13 @@ ### cddl +* Change `maybeTaggedNonemptySet` to take an extra argument for the projection function +* Add `generateMaybeTaggedSet` * Extend `constr` CDDL rule to include tags 1280–1400 for Plutus `Data` constructor indexes ### `testlib` +* Add `genNonEmptyVotingProcedures` * Add to `Test.Cardano.Ledger.Conway.Examples`: - `exampleConwayOnwardsEraPParams` - `exampleConwayOnwardsEraPParamsUpdate` diff --git a/eras/conway/impl/cardano-ledger-conway.cabal b/eras/conway/impl/cardano-ledger-conway.cabal index 89d5e942fe2..d4cb47c6476 100644 --- a/eras/conway/impl/cardano-ledger-conway.cabal +++ b/eras/conway/impl/cardano-ledger-conway.cabal @@ -152,6 +152,7 @@ library cddl -Wunused-packages build-depends: + QuickCheck, base, cardano-ledger-babbage:cddl, cardano-ledger-conway, diff --git a/eras/conway/impl/cddl/data/conway.cddl b/eras/conway/impl/cddl/data/conway.cddl index f2216d9fe26..b1d86f5bb8a 100644 --- a/eras/conway/impl/cddl/data/conway.cddl +++ b/eras/conway/impl/cddl/data/conway.cddl @@ -213,7 +213,7 @@ plutus_data = / bounded_bytes constr = - #6.102([uint, [* a0]]) + #6.101([uint, [* a0]]) / #6.121([* a0]) / #6.122([* a0]) / #6.123([* a0]) diff --git a/eras/conway/impl/cddl/lib/Cardano/Ledger/Conway/HuddleSpec.hs b/eras/conway/impl/cddl/lib/Cardano/Ledger/Conway/HuddleSpec.hs index bb053c5ba60..89e50235af0 100644 --- a/eras/conway/impl/cddl/lib/Cardano/Ledger/Conway/HuddleSpec.hs +++ b/eras/conway/impl/cddl/lib/Cardano/Ledger/Conway/HuddleSpec.hs @@ -3,6 +3,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedLists #-} @@ -11,6 +12,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -Wno-orphans #-} module Cardano.Ledger.Conway.HuddleSpec ( @@ -65,6 +67,7 @@ module Cardano.Ledger.Conway.HuddleSpec ( maybeTaggedNonemptySet, maybeTaggedNonemptyOset, conwayCostModelsGenerator, + generateMaybeTaggedSet, ) where import Cardano.Ledger.Babbage.HuddleSpec hiding ( @@ -83,21 +86,35 @@ import Cardano.Ledger.Huddle.Gen ( MonadGen (choose, resize), RuleTerm (..), Term (..), + antiChoose, + antiVectorOfUnique, + arbitrary, + faultyNum, genArrayTerm, genMapTerm, genRule, + generateFromGRef, liftAntiGen, oneof, replicateMNorm, shuffle, + unwrapSingle, + unwrapSingleOrError, + validateArrayTerm, + validateFromGRef, withAntiGen, (|!), ) import Cardano.Ledger.Huddle.Gen qualified as Gen +import Control.Monad (unless) +import Data.Foldable (traverse_) +import Data.List (nubBy) +import Data.Maybe (fromMaybe) import Data.Proxy (Proxy (..)) import Data.Traversable (forM) import Data.Word (Word64) import GHC.TypeLits (KnownSymbol) +import Test.QuickCheck qualified as QC import Text.Heredoc import Prelude hiding ((/)) @@ -706,18 +723,51 @@ conwayRedeemer pname p = , "ex_units" ==> huddleRule @"ex_units" p ] +generateMaybeTaggedSet :: Int -> CBORGen Term -> CBORGen Term +generateMaybeTaggedSet nElems gen = do + elems <- fromMaybe QC.discard <$> withAntiGen (antiVectorOfUnique nElems) gen + elemsArr <- genArrayTerm elems + tagged <- arbitrary + if tagged + then do + t <- liftAntiGen $ faultyNum 258 + pure $ TTagged t elemsArr + else pure elemsArr + mkMaybeTaggedSet :: - forall name a. (KnownSymbol name, IsType0 a) => Proxy name -> Word64 -> a -> GRuleCall -mkMaybeTaggedSet pname n = binding $ \x -> pname =.= tag 258 (arr [n <+ a x]) / sarr [n <+ a x] + forall name a. + (KnownSymbol name, IsType0 a) => Proxy name -> Int -> (Term -> Term -> Bool) -> a -> GRuleCall +mkMaybeTaggedSet pname n eq = binding $ \x -> + withCBORGen (generator x) + . withValidator (validator x) + $ pname =.= tag 258 (arr [fromIntegral n <+ a x]) / sarr [fromIntegral n <+ a x] + where + generator :: GRef -> CBORGen RuleTerm + generator ref = do + nElems <- liftAntiGen . Gen.sized $ \sz -> + let sz' = max n sz in antiChoose (n, sz') (0, sz') + fmap SingleTerm . generateMaybeTaggedSet nElems $ unwrapSingleOrError <$> generateFromGRef ref + validator ref term = do + term_ <- unwrapSingle term + let + validateInner t = do + elems <- validateArrayTerm t + unless (length elems == length (nubBy eq elems)) $ + fail "not all elements are unique" + traverse_ (validateFromGRef ref) elems + case term_ of + TTagged t x | t == 258 -> validateInner x + x -> validateInner x maybeTaggedSet :: IsType0 a => Proxy "set" -> a -> GRuleCall -maybeTaggedSet pname = mkMaybeTaggedSet pname 0 +maybeTaggedSet pname = mkMaybeTaggedSet pname 0 (==) maybeTaggedNonemptySet :: IsType0 a => Proxy "nonempty_set" -> a -> GRuleCall -maybeTaggedNonemptySet pname = mkMaybeTaggedSet pname 1 +maybeTaggedNonemptySet pname = mkMaybeTaggedSet pname 1 (==) -maybeTaggedNonemptyOset :: IsType0 a => Proxy "nonempty_oset" -> a -> GRuleCall -maybeTaggedNonemptyOset pname = mkMaybeTaggedSet pname 1 +maybeTaggedNonemptyOset :: + IsType0 a => Proxy "nonempty_oset" -> (Term -> Term -> Bool) -> a -> GRuleCall +maybeTaggedNonemptyOset pname eq = mkMaybeTaggedSet pname 1 eq instance HuddleRule "bounded_bytes" ConwayEra where huddleRuleNamed pname _ = boundedBytesRule pname @@ -1372,7 +1422,7 @@ instance HuddleRule1 "nonempty_set" ConwayEra where huddleRule1Named pname _ = maybeTaggedNonemptySet pname instance HuddleRule1 "nonempty_oset" ConwayEra where - huddleRule1Named pname _ = maybeTaggedNonemptyOset pname + huddleRule1Named pname _ = maybeTaggedNonemptyOset pname (==) instance HuddleRule1 "multiasset" ConwayEra where huddleRule1Named = conwayMultiasset diff --git a/eras/conway/impl/testlib/Test/Cardano/Ledger/Conway/Arbitrary.hs b/eras/conway/impl/testlib/Test/Cardano/Ledger/Conway/Arbitrary.hs index 80fe95bfe29..8010b847b55 100644 --- a/eras/conway/impl/testlib/Test/Cardano/Ledger/Conway/Arbitrary.hs +++ b/eras/conway/impl/testlib/Test/Cardano/Ledger/Conway/Arbitrary.hs @@ -31,6 +31,7 @@ module Test.Cardano.Ledger.Conway.Arbitrary ( genCommitteeGovAction, genConstitutionGovAction, genProposals, + genNonEmptyVotingProcedures, ProposalsNewActions (..), ProposalsForEnactment (..), ShuffledGovActionStates (..), @@ -897,3 +898,8 @@ instance Arbitrary (TransitionConfig ConwayEra) where deriving newtype instance Arbitrary (Tx TopTx ConwayEra) deriving newtype instance Arbitrary (ApplyTxError ConwayEra) + +genNonEmptyVotingProcedures :: Era era => Gen (VotingProcedures era) +genNonEmptyVotingProcedures = + VotingProcedures . Map.fromList <$> do + listOf1 $ (,) <$> arbitrary <*> (Map.fromList <$> listOf1 arbitrary) diff --git a/eras/dijkstra/impl/CHANGELOG.md b/eras/dijkstra/impl/CHANGELOG.md index a31cc69b1e4..a4c5284f96d 100644 --- a/eras/dijkstra/impl/CHANGELOG.md +++ b/eras/dijkstra/impl/CHANGELOG.md @@ -71,6 +71,7 @@ ### testlib +* Add `genSmallDijkstraBlockBody` * Add to `Test.Cardano.Ledger.Dijkstra.Examples`: - `exampleDijkstraOnwardsEraPParams` - `exampleDijkstraOnwardsEraPParamsUpdate` diff --git a/eras/dijkstra/impl/cardano-ledger-dijkstra.cabal b/eras/dijkstra/impl/cardano-ledger-dijkstra.cabal index b03f181b990..285719df4af 100644 --- a/eras/dijkstra/impl/cardano-ledger-dijkstra.cabal +++ b/eras/dijkstra/impl/cardano-ledger-dijkstra.cabal @@ -211,16 +211,13 @@ library cddl -Wunused-packages build-depends: - QuickCheck, antigen, base, cardano-ledger-conway:cddl, cardano-ledger-core:cddl, cardano-ledger-dijkstra, cborg, - cuddle, heredoc, - quickcheck-transformer, text, executable generate-cddl @@ -266,6 +263,7 @@ test-suite tests build-depends: base, + cardano-data, cardano-ledger-alonzo:{cardano-ledger-alonzo, testlib}, cardano-ledger-babbage:testlib, cardano-ledger-binary:testlib, diff --git a/eras/dijkstra/impl/cddl/data/dijkstra.cddl b/eras/dijkstra/impl/cddl/data/dijkstra.cddl index d4c2b63b5af..9ff267df110 100644 --- a/eras/dijkstra/impl/cddl/data/dijkstra.cddl +++ b/eras/dijkstra/impl/cddl/data/dijkstra.cddl @@ -222,7 +222,7 @@ plutus_data = / bounded_bytes constr = - #6.102([uint, [* a0]]) + #6.101([uint, [* a0]]) / #6.121([* a0]) / #6.122([* a0]) / #6.123([* a0]) diff --git a/eras/dijkstra/impl/cddl/lib/Cardano/Ledger/Dijkstra/HuddleSpec.hs b/eras/dijkstra/impl/cddl/lib/Cardano/Ledger/Dijkstra/HuddleSpec.hs index 17e0114fd92..b5821a96b7c 100644 --- a/eras/dijkstra/impl/cddl/lib/Cardano/Ledger/Dijkstra/HuddleSpec.hs +++ b/eras/dijkstra/impl/cddl/lib/Cardano/Ledger/Dijkstra/HuddleSpec.hs @@ -34,18 +34,33 @@ module Cardano.Ledger.Dijkstra.HuddleSpec ( import Cardano.Ledger.Conway.HuddleSpec hiding () import Cardano.Ledger.Dijkstra (DijkstraEra) -import Cardano.Ledger.Huddle.Gen (CBORGen, RuleTerm (..), genArrayTerm, liftAntiGen, withAntiGen) -import Codec.CBOR.Cuddle.CBOR.Gen (generateFromName) +import Cardano.Ledger.Huddle.Gen ( + CBORGen, + MonadGen (choose, liftGen), + Name (..), + RuleTerm (..), + genArrayTerm, + genRule, + generateFromName, + liftAntiGen, + scale, + shuffle, + unwrapSingle, + validateArrayTerm, + validateFromName, + withAntiGen, + ) +import Cardano.Ledger.Huddle.Gen qualified as Gen import Codec.CBOR.Term (Term (..)) -import Control.Monad (zipWithM) +import Control.Monad (unless, zipWithM) +import Data.Foldable (traverse_) +import Data.List (nub) +import Data.Maybe (mapMaybe) import Data.Proxy (Proxy (..)) import Data.Text () import Data.Text qualified as T import Data.Word (Word16, Word64) import Test.AntiGen (withAnnotation, (|!)) -import Test.QuickCheck (choose, shuffle) -import Test.QuickCheck qualified as QC -import Test.QuickCheck.GenT (liftGen) import Text.Heredoc import Prelude hiding ((/)) @@ -93,7 +108,51 @@ subTransactionsRule :: Proxy era -> Rule subTransactionsRule pname p = - pname =.= huddleRule1 @"nonempty_oset" p (huddleRule @"sub_transaction" p) + withCBORGen generate + . withValidator validator + $ pname =.= huddleRule1 @"nonempty_oset" p (huddleRule @"sub_transaction" p) + where + -- The Haskell representation is @OMap TxId (Tx SubTx era)@: dedup is by + -- body hash, so both the generator and the validator must work in terms + -- of body equality, not full-element equality (which is what the generic + -- @nonempty_oset@ validator checks). + validator term = do + term_ <- unwrapSingle term + let + bodyOf (TList (b : _)) = Just b + bodyOf (TListI (b : _)) = Just b + bodyOf _ = Nothing + validateInner t = do + elems <- validateArrayTerm t + let bodies = mapMaybe bodyOf elems + unless (length bodies == length (nub bodies)) $ + fail "duplicate sub_transaction bodies" + traverse_ (validateFromName (Name "sub_transaction")) elems + case term_ of + TTagged 258 x -> validateInner x + x -> validateInner x + generate = do + nElems <- Gen.sized $ \sz -> choose (1, max 1 (min sz 3)) + let subTxGen = scale (`div` 2) $ genRule @"sub_transaction" @era + txs <- uniqueByBody nElems subTxGen + elemsArr <- genArrayTerm txs + tagged <- Gen.arbitrary + pure $ SingleTerm $ if tagged then TTagged 258 elemsArr else elemsArr + uniqueByBody :: Int -> CBORGen Term -> CBORGen [Term] + uniqueByBody n gen = loop [] n + where + triesPerElement = 20 :: Int + loop acc 0 = pure acc + loop acc k = attempt triesPerElement acc k + attempt 0 acc _ = pure acc + attempt tries acc k = do + tx <- gen + case bodyOf tx of + Just b | b `notElem` mapMaybe bodyOf acc -> loop (tx : acc) (k - 1) + _ -> attempt (tries - 1) acc k + bodyOf (TList (b : _)) = Just b + bodyOf (TListI (b : _)) = Just b + bodyOf _ = Nothing subTransactionRule :: forall era. @@ -917,10 +976,13 @@ instance HuddleRule "block_body" DijkstraEra where blockBodyGen :: CBORGen RuleTerm blockBodyGen = do - numTxs <- liftGen . QC.sized $ \s -> choose (0 :: Int, s) + numTxs <- liftGen . Gen.sized $ \s -> choose (0 :: Int, s `div` 15) txs <- mapM - (\i -> withAntiGen (withAnnotation (T.pack $ show i)) $ generateFromName "transaction") + ( \i -> + withAntiGen (withAnnotation (T.pack $ show i)) . scale (`div` max 1 numTxs) $ + generateFromName "transaction" + ) [0 .. numTxs - 1] invalidIxIxs <- if numTxs == 0 @@ -941,10 +1003,13 @@ blockBodyGen = do liftAntiGen $ withAnnotation "invalid_transactions" $ zipWithM faultyIndex [0 :: Int ..] txIndicesWithOverflow - invalidTxIxsTerm <- genArrayTerm $ TInteger . toInteger <$> invalidIxIxs + invalidTxIxsTerm <- + if null invalidIxIxs + then pure TNull + else genArrayTerm $ TInteger . toInteger <$> invalidIxIxs txsTerm <- withAntiGen (withAnnotation "transactions") $ genArrayTerm txs perasCertTerm <- generateFromName "peras_certificate" - SingleTerm <$> liftGen (genArrayTerm [invalidTxIxsTerm, txsTerm, perasCertTerm]) + SingleTerm <$> genArrayTerm [invalidTxIxsTerm, txsTerm, perasCertTerm] instance HuddleRule "auxiliary_scripts" DijkstraEra where huddleRuleNamed = auxiliaryScriptsRule @@ -1083,7 +1148,7 @@ instance HuddleRule1 "nonempty_set" DijkstraEra where huddleRule1Named pname _ = maybeTaggedNonemptySet pname instance HuddleRule1 "nonempty_oset" DijkstraEra where - huddleRule1Named pname _ = maybeTaggedNonemptyOset pname + huddleRule1Named pname _ = maybeTaggedNonemptyOset pname (==) instance HuddleRule1 "multiasset" DijkstraEra where huddleRule1Named = dijkstraMultiasset diff --git a/eras/dijkstra/impl/test/Test/Cardano/Ledger/Dijkstra/Binary/CddlSpec.hs b/eras/dijkstra/impl/test/Test/Cardano/Ledger/Dijkstra/Binary/CddlSpec.hs index 22065c2714b..ff350cf3e24 100644 --- a/eras/dijkstra/impl/test/Test/Cardano/Ledger/Dijkstra/Binary/CddlSpec.hs +++ b/eras/dijkstra/impl/test/Test/Cardano/Ledger/Dijkstra/Binary/CddlSpec.hs @@ -9,26 +9,36 @@ module Test.Cardano.Ledger.Dijkstra.Binary.CddlSpec (spec) where import Cardano.Ledger.Alonzo.Scripts (CostModels) import Cardano.Ledger.Alonzo.TxWits (Redeemers) -import Cardano.Ledger.Conway.Governance (GovAction, ProposalProcedure, VotingProcedure) +import Cardano.Ledger.Conway.Governance ( + GovAction, + ProposalProcedure, + VotingProcedure, + VotingProcedures, + ) import Cardano.Ledger.Core import Cardano.Ledger.Dijkstra (DijkstraEra) import Cardano.Ledger.Dijkstra.HuddleSpec (dijkstraCDDL) import Cardano.Ledger.Dijkstra.Scripts (AccountBalanceInterval, AccountBalanceIntervals) import Cardano.Ledger.Dijkstra.Tx (Tx (..)) import Cardano.Ledger.Plutus.Data (Data, Datum) +import Data.OSet.Strict (OSet) import Test.Cardano.Ledger.Alonzo.Arbitrary (genDatumPresent, genNonEmptyRedeemers) import Test.Cardano.Ledger.Binary.Cuddle ( noTwiddle, specWithHuddle, ) import Test.Cardano.Ledger.Common +import Test.Cardano.Ledger.Conway.Arbitrary (genNonEmptyVotingProcedures) import Test.Cardano.Ledger.Core.Binary ( fullAnnCddlSpec, fullAnnGenCddlSpec, fullCddlSpec, fullGenCddlSpec, ) -import Test.Cardano.Ledger.Dijkstra.Arbitrary (genNonEmptyAccountBalanceIntervals) +import Test.Cardano.Ledger.Dijkstra.Arbitrary ( + genNonEmptyAccountBalanceIntervals, + genSmallDijkstraBlockBody, + ) import Test.Cardano.Ledger.Dijkstra.Binary.Annotator () spec :: Spec @@ -36,53 +46,30 @@ spec = do describe "CDDL" $ do let v = eraProtVerHigh @DijkstraEra describe "Huddle" $ specWithHuddle dijkstraCDDL . noTwiddle $ do - -- BlockBody - xdescribe "fix transaction" $ - fullAnnCddlSpec @(BlockBody DijkstraEra) v "block_body" - -- AccountBalanceInterval + fullAnnGenCddlSpec @(BlockBody DijkstraEra) genSmallDijkstraBlockBody v "block_body" fullCddlSpec @(AccountBalanceInterval DijkstraEra) v "account_balance_interval" - -- AccountBalanceIntervals fullGenCddlSpec @(AccountBalanceIntervals DijkstraEra) genNonEmptyAccountBalanceIntervals v "account_balance_intervals" - -- Value fullCddlSpec @(Value DijkstraEra) v "value" - -- TxBody TopTx - xdescribe "fix TxBody" $ do - fullAnnCddlSpec @(TxBody TopTx DijkstraEra) v "transaction_body" - -- TxBody SubTx - xdescribe "fix TxBody" $ do - fullAnnCddlSpec @(TxBody SubTx DijkstraEra) v "sub_transaction_body" - -- TxAuxData + fullAnnCddlSpec @(TxBody TopTx DijkstraEra) v "transaction_body" + fullAnnCddlSpec @(TxBody SubTx DijkstraEra) v "sub_transaction_body" fullAnnCddlSpec @(TxAuxData DijkstraEra) v "auxiliary_data" - -- NativeScript fullAnnCddlSpec @(NativeScript DijkstraEra) v "native_script" - -- Data fullAnnCddlSpec @(Data DijkstraEra) v "plutus_data" - -- TxOut fullCddlSpec @(TxOut DijkstraEra) v "transaction_output" - -- Script fullAnnCddlSpec @(Script DijkstraEra) v "script" - -- Datum fullGenCddlSpec @(Datum DijkstraEra) genDatumPresent v "datum_option" - -- TxWits - xdescribe "fix plutus_v4_script" $ do - fullAnnCddlSpec @(TxWits DijkstraEra) v "transaction_witness_set" - -- PParamsUpdate + fullAnnCddlSpec @(TxWits DijkstraEra) v "transaction_witness_set" fullCddlSpec @(PParamsUpdate DijkstraEra) v "protocol_param_update" - -- CostModels fullCddlSpec @CostModels v "cost_models" - -- Redeemers fullAnnGenCddlSpec @(Redeemers DijkstraEra) genNonEmptyRedeemers v "redeemers" - -- Tx - xdescribe "fix Transaction" $ do - fullAnnCddlSpec @(Tx TopTx DijkstraEra) v "transaction" - -- VotingProcedure + fullAnnCddlSpec @(Tx TopTx DijkstraEra) v "transaction" fullCddlSpec @(VotingProcedure DijkstraEra) v "voting_procedure" - -- ProposalProcedure fullCddlSpec @(ProposalProcedure DijkstraEra) v "proposal_procedure" - -- GovAction fullCddlSpec @(GovAction DijkstraEra) v "gov_action" - -- TxCert fullCddlSpec @(TxCert DijkstraEra) v "certificate" + fullCddlSpec @(OSet (TxCert DijkstraEra)) v "certificates" + fullCddlSpec @(OSet (ProposalProcedure DijkstraEra)) v "proposal_procedures" + fullGenCddlSpec @(VotingProcedures DijkstraEra) genNonEmptyVotingProcedures v "voting_procedures" diff --git a/eras/dijkstra/impl/testlib/Test/Cardano/Ledger/Dijkstra/Arbitrary.hs b/eras/dijkstra/impl/testlib/Test/Cardano/Ledger/Dijkstra/Arbitrary.hs index f03b4625cf7..c993a64d755 100644 --- a/eras/dijkstra/impl/testlib/Test/Cardano/Ledger/Dijkstra/Arbitrary.hs +++ b/eras/dijkstra/impl/testlib/Test/Cardano/Ledger/Dijkstra/Arbitrary.hs @@ -14,7 +14,7 @@ {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wno-orphans #-} -module Test.Cardano.Ledger.Dijkstra.Arbitrary (genNonEmptyAccountBalanceIntervals) where +module Test.Cardano.Ledger.Dijkstra.Arbitrary (genNonEmptyAccountBalanceIntervals, genSmallDijkstraBlockBody) where import Cardano.Ledger.Allegra.Scripts ( pattern RequireTimeExpire, @@ -40,6 +40,7 @@ import Cardano.Ledger.Shelley.Scripts (pattern RequireSignature) import Data.Functor.Identity (Identity) import qualified Data.Map.Strict as Map import qualified Data.OMap.Strict as OMap +import qualified Data.Sequence.Strict as SSeq import Data.Typeable (Typeable) import Generic.Random (genericArbitraryU) import Test.Cardano.Ledger.Allegra.Arbitrary (maxTimelockDepth) @@ -303,6 +304,21 @@ instance where arbitrary = DijkstraBlockBody <$> arbitrary <*> arbitrary +genSmallDijkstraBlockBody :: + ( AlonzoEraTx era + , Arbitrary (Tx TopTx era) + ) => + Gen (DijkstraBlockBody era) +genSmallDijkstraBlockBody = DijkstraBlockBody <$> genFewTxs <*> arbitrary + where + genFewTxs = sized $ \sz -> do + numTxs <- + frequency + [ (99, choose (1, max 1 $ sz `div` 20)) + , (1, pure 0) + ] + SSeq.fromList <$> vectorOf numTxs (scale (`div` numTxs) arbitrary) + deriving newtype instance Arbitrary (ApplyTxError DijkstraEra) instance Arbitrary (DijkstraMempoolPredFailure DijkstraEra) where diff --git a/flake.lock b/flake.lock index 2741b7d461f..2572c7b8059 100644 --- a/flake.lock +++ b/flake.lock @@ -292,11 +292,11 @@ "hackageNix": { "flake": false, "locked": { - "lastModified": 1778128665, - "narHash": "sha256-A8X56xHMs9hWFr8tG91n4WhjCn/1BiD9oGd7otQgk1E=", + "lastModified": 1778843394, + "narHash": "sha256-Y8DZ2RSqgoK5Wj8iYV4VHJznJGFa59xLusgcL9/Iahc=", "owner": "input-output-hk", "repo": "hackage.nix", - "rev": "692da95600702017553fb12d4594303e16937a31", + "rev": "b66bffbd60bdd7c90671fc34aea80e160fff04a5", "type": "github" }, "original": { diff --git a/libs/cardano-ledger-binary/cardano-ledger-binary.cabal b/libs/cardano-ledger-binary/cardano-ledger-binary.cabal index 1b90596be44..60824643b08 100644 --- a/libs/cardano-ledger-binary/cardano-ledger-binary.cabal +++ b/libs/cardano-ledger-binary/cardano-ledger-binary.cabal @@ -126,7 +126,7 @@ library testlib cardano-strict-containers, cborg, containers, - cuddle >=1.7, + cuddle >=1.8, directory, filepath, formatting, diff --git a/libs/cardano-ledger-binary/testlib/Test/Cardano/Ledger/Binary/Cuddle.hs b/libs/cardano-ledger-binary/testlib/Test/Cardano/Ledger/Binary/Cuddle.hs index 6f0e64f7990..8b8810eda34 100644 --- a/libs/cardano-ledger-binary/testlib/Test/Cardano/Ledger/Binary/Cuddle.hs +++ b/libs/cardano-ledger-binary/testlib/Test/Cardano/Ledger/Binary/Cuddle.hs @@ -41,14 +41,13 @@ import Cardano.Ledger.Binary ( ) import Cardano.Ledger.Binary.Decoding (label) import Codec.CBOR.Cuddle.CBOR.Gen (generateFromName) -import Codec.CBOR.Cuddle.CBOR.Validator (validateCBOR) +import Codec.CBOR.Cuddle.CBOR.Validator (ValidateCBORError (..), validateCBOR) import Codec.CBOR.Cuddle.CBOR.Validator.Trace ( Evidenced (..), SValidity (..), TraceOptions (..), ValidationTrace, defaultTraceOptions, - isValid, prettyValidationTrace, ) import Codec.CBOR.Cuddle.CDDL (Name (..)) @@ -65,7 +64,6 @@ import qualified Codec.CBOR.Pretty as CBOR import qualified Codec.CBOR.Term as CBOR import qualified Codec.CBOR.Write as C import qualified Codec.CBOR.Write as CBOR -import Control.Monad (unless) import Data.Data (Proxy (..)) import Data.Either (isLeft) import qualified Data.Text as T @@ -215,7 +213,13 @@ huddleAntiCborProp version ruleName env@HuddleEnv {heRoot} = property @(Gen Prop encoding = toPlainEncoding version $ encodeTerm zrValue bs = C.toStrictByteString encoding case validateCBOR bs (Name ruleName) (mapIndex heRoot) of - Evidenced SInvalid trc -> do + -- cborg-level failure (e.g. twiddler turned a tag into a + -- bignum tag with a non-bytes body): the decoder will also + -- fail, which is exactly what a zap test is meant to detect. + Left (DecodingFailed _) -> pure $ property () + Left e@LeftoverBytes {} -> pure . property . expectationFailure $ show e + Left e@RuleDoesNotExist {} -> pure . property . expectationFailure $ show e + Right (Evidenced SInvalid trc) -> do let errMsg = unlines @@ -231,7 +235,7 @@ huddleAntiCborProp version ruleName env@HuddleEnv {heRoot} = property @(Gen Prop , "Decoding succeeded, expected failure" ] pure . counterexample errMsg . isLeft $ decodeFull' @a version bs - Evidenced SValid _ -> discard + Right (Evidenced SValid _) -> discard | otherwise -> discard huddleAntiCborSpec :: @@ -352,9 +356,11 @@ huddleRoundTripGenValidateProp gen version ruleName HuddleEnv {heRoot = cddl} = \(val :: a) -> do let bs = serialize' version val - res = validateCBOR bs (Name ruleName) (mapIndex cddl) - unless (isValid res) . expectationFailure $ - "CBOR Validation failed\nError:\n" <> showValidationTrace res + case validateCBOR bs (Name ruleName) (mapIndex cddl) of + Left e -> expectationFailure $ "Validation input error:\n" <> show e + Right (Evidenced SValid _) -> pure () + Right res@(Evidenced SInvalid _) -> + expectationFailure $ "CBOR Validation failed:\n" <> showValidationTrace res huddleRoundTripGenValidate :: forall a. diff --git a/libs/cardano-ledger-core/CHANGELOG.md b/libs/cardano-ledger-core/CHANGELOG.md index b9db3f500ed..f44a5e5a775 100644 --- a/libs/cardano-ledger-core/CHANGELOG.md +++ b/libs/cardano-ledger-core/CHANGELOG.md @@ -31,6 +31,22 @@ ### `cddl` +* Added various custom generator and validator helpers: + - `validateFromName` + - `validateFromGRef` + - `validateInt` + - `validateUInt` + - `validateNInt` + - `validateArrayTerm` + - `validateBytesTerm` + - `validateStringTerm` + - `validateMapTerm` + - `unwrapSingle` + - `unwrapSingleOrError` + - `antiVectorOfUnique` + - `antiVectorOfUniqueBy` + - `antiVectorOfUniqueOn` +* Changed the type of `gen*` functions to return a `CBORGen` value instead of `m` * Remove `pickOne` * Add `genMapTerm` * Modify `genBytesTerm`, `genArrayTerm` and `genStringTerm` so it works with `MonadGen` instead of `StatefulGen` diff --git a/libs/cardano-ledger-core/cardano-ledger-core.cabal b/libs/cardano-ledger-core/cardano-ledger-core.cabal index d267d8ff832..f7e9cfc18af 100644 --- a/libs/cardano-ledger-core/cardano-ledger-core.cabal +++ b/libs/cardano-ledger-core/cardano-ledger-core.cabal @@ -201,9 +201,10 @@ library cddl cardano-ledger-binary, cardano-ledger-core, cborg, - cuddle >=1.7, + cuddle >=1.8, heredoc, mempack, + mtl, quickcheck-transformer, text >=2.0, diff --git a/libs/cardano-ledger-core/cddl/Cardano/Ledger/Huddle/Gen.hs b/libs/cardano-ledger-core/cddl/Cardano/Ledger/Huddle/Gen.hs index adbc5d9b776..e7c61287c37 100644 --- a/libs/cardano-ledger-core/cddl/Cardano/Ledger/Huddle/Gen.hs +++ b/libs/cardano-ledger-core/cddl/Cardano/Ledger/Huddle/Gen.hs @@ -3,6 +3,7 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ViewPatterns #-} module Cardano.Ledger.Huddle.Gen ( -- * MonadGen @@ -14,14 +15,29 @@ module Cardano.Ledger.Huddle.Gen ( -- * Term generators module CustomGen, Term (..), + RuleTerm (..), + Name (..), genRule, + generateFromName, + generateFromGRef, genArrayTerm, genBytesTerm, genStringTerm, genMapTerm, + unwrapSingleOrError, -- * Term validators module CustomValidator, + validateFromName, + validateFromGRef, + validateInt, + validateUInt, + validateNInt, + validateArrayTerm, + validateBytesTerm, + validateStringTerm, + validateMapTerm, + unwrapSingle, -- * Lifted generators arbitrary, @@ -30,18 +46,26 @@ module Cardano.Ledger.Huddle.Gen ( -- * Antigen module AntiGen, + antiVectorOfUnique, + antiVectorOfUniqueBy, + antiVectorOfUniqueOn, ) where import Cardano.Ledger.Binary (Term (..)) import Cardano.Ledger.Huddle (HuddleRule ()) -import Codec.CBOR.Cuddle.CBOR.Gen (generateFromName) +import Codec.CBOR.Cuddle.CBOR.Gen (generateFromGRef, generateFromName) +import Codec.CBOR.Cuddle.CBOR.Validator (validateFromGRef, validateFromName) import Codec.CBOR.Cuddle.CDDL (Name (..)) +import Codec.CBOR.Cuddle.CDDL.CTree (nintMin, uintMax) import Codec.CBOR.Cuddle.CDDL.Custom.Core as CustomCore import Codec.CBOR.Cuddle.CDDL.Custom.Generator as CustomGen import Codec.CBOR.Cuddle.CDDL.Custom.Validator as CustomValidator +import Control.Monad.Reader (asks) import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as LBS +import Data.Function (on) import Data.Proxy (Proxy (..)) +import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.Lazy as LT import GHC.TypeLits (symbolVal) @@ -67,14 +91,97 @@ shuffle = liftGen . QC.shuffle -- Term generators -genArrayTerm :: MonadGen m => [Term] -> m Term -genArrayTerm es = GenT.elements [TList es, TListI es] - -genBytesTerm :: MonadGen m => ByteString -> m Term -genBytesTerm bs = GenT.elements [TBytes bs, TBytesI $ LBS.fromStrict bs] - -genStringTerm :: MonadGen m => T.Text -> m Term -genStringTerm t = GenT.elements [TString t, TStringI $ LT.fromStrict t] - -genMapTerm :: MonadGen m => [(Term, Term)] -> m Term -genMapTerm m = GenT.elements [TMap m, TMapI m] +-- | Generate a list of @n@ pairwise-distinct elements. Returns 'Nothing' if +-- the underlying generator could not produce enough distinct elements within +-- the per-element retry budget. +antiVectorOfUnique :: Eq a => Int -> AntiGen a -> AntiGen (Maybe [a]) +antiVectorOfUnique = antiVectorOfUniqueBy (==) + +-- | Like 'antiVectorOfUnique', but compares elements by a key projection. +antiVectorOfUniqueOn :: Eq b => (a -> b) -> Int -> AntiGen a -> AntiGen (Maybe [a]) +antiVectorOfUniqueOn key = antiVectorOfUniqueBy ((==) `on` key) + +-- | Like 'antiVectorOfUnique', but takes a user-supplied equivalence relation. +antiVectorOfUniqueBy :: (a -> a -> Bool) -> Int -> AntiGen a -> AntiGen (Maybe [a]) +antiVectorOfUniqueBy eq n gen = do + disallowDuplicates <- faultyBool True + let + triesPerElement = 10 :: Int + go _ 0 _ = pure Nothing + go m tries elems + | m > 0 = do + x <- gen + if disallowDuplicates && any (eq x) elems + then go m (tries - 1) elems + else go (m - 1) triesPerElement (x : elems) + | otherwise = pure (Just elems) + go n triesPerElement [] + +genArrayTerm :: [Term] -> CBORGen Term +genArrayTerm es = + ifTwiddle (GenT.elements [TList es, TListI es]) (pure $ TList es) + +genBytesTerm :: ByteString -> CBORGen Term +genBytesTerm bs = + ifTwiddle (GenT.elements [TBytes bs, TBytesI $ LBS.fromStrict bs]) (pure $ TBytes bs) + +genStringTerm :: T.Text -> CBORGen Term +genStringTerm t = + ifTwiddle (GenT.elements [TString t, TStringI $ LT.fromStrict t]) (pure $ TString t) + +genMapTerm :: [(Term, Term)] -> CBORGen Term +genMapTerm m = + ifTwiddle (GenT.elements [TMap m, TMapI m]) (pure $ TMap m) + +ifTwiddle :: CBORGen a -> CBORGen a -> CBORGen a +ifTwiddle yes no = do + twiddle <- asks (gcTwiddle . geConfig) + if twiddle then yes else no + +-- Term validators + +validateInt :: Term -> Validator Integer +validateInt (TInt (toInteger -> x)) + | x >= nintMin && x <= uintMax = pure x + | otherwise = fail "Number not in int range" +validateInt _ = fail "Expected int" + +validateUInt :: Term -> Validator Integer +validateUInt (TInt (toInteger -> x)) + | x >= 0 && x <= uintMax = pure x + | otherwise = fail "Number not in uint range" +validateUInt _ = fail "Expected uint" + +validateNInt :: Term -> Validator Integer +validateNInt (TInt (toInteger -> x)) + | x >= nintMin && x < 0 = pure x + | otherwise = fail "Number not in nint range" +validateNInt _ = fail "Expected nint" + +validateArrayTerm :: Term -> Validator [Term] +validateArrayTerm (TList xs) = pure xs +validateArrayTerm (TListI xs) = pure xs +validateArrayTerm _ = fail "Expected list" + +validateBytesTerm :: Term -> Validator ByteString +validateBytesTerm (TBytes bs) = pure bs +validateBytesTerm (TBytesI bs) = pure $ LBS.toStrict bs +validateBytesTerm _ = fail "Expected bytes" + +validateStringTerm :: Term -> Validator Text +validateStringTerm (TString x) = pure x +validateStringTerm (TStringI x) = pure $ LT.toStrict x +validateStringTerm _ = fail "Expected string" + +validateMapTerm :: Term -> Validator [(Term, Term)] +validateMapTerm (TMap xs) = pure xs +validateMapTerm (TMapI xs) = pure xs +validateMapTerm _ = fail "Expected map" + +unwrapSingle :: RuleTerm -> Validator Term +unwrapSingle (SingleTerm x) = pure x +unwrapSingle _ = fail "Expected a single term" + +unwrapSingleOrError :: RuleTerm -> Term +unwrapSingleOrError (SingleTerm x) = x +unwrapSingleOrError _ = error "Expected a single term" diff --git a/libs/cardano-ledger-repl-environment/cardano-ledger-repl-environment.cabal b/libs/cardano-ledger-repl-environment/cardano-ledger-repl-environment.cabal index 374f2138d5a..698191644b0 100644 --- a/libs/cardano-ledger-repl-environment/cardano-ledger-repl-environment.cabal +++ b/libs/cardano-ledger-repl-environment/cardano-ledger-repl-environment.cabal @@ -14,21 +14,26 @@ library build-depends: ImpSpec, QuickCheck, + antigen, base, bytestring, cardano-ledger-allegra, cardano-ledger-alonzo, cardano-ledger-api:{cardano-ledger-api, testlib}, cardano-ledger-babbage, - cardano-ledger-binary, + cardano-ledger-binary:{cardano-ledger-binary, testlib}, cardano-ledger-conformance, cardano-ledger-conway:{cardano-ledger-conway, testlib}, cardano-ledger-core:{cardano-ledger-core, testlib}, + cardano-ledger-dijkstra:{cardano-ledger-dijkstra, cddl}, cardano-ledger-mary, cardano-ledger-shelley, cardano-ledger-test, cardano-strict-containers, constrained-generators, containers, + cuddle, data-default, + deepseq, microlens, + text,