From 579ecbe791e14918184de6801e1ed97dc094f763 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 4 Jul 2024 13:27:57 +0200 Subject: [PATCH 01/71] tests pass with new mockchain return type --- src/Cooked/MockChain.hs | 10 +-- src/Cooked/MockChain/BlockChain.hs | 12 ++++ src/Cooked/MockChain/Direct.hs | 52 +++++++++----- src/Cooked/MockChain/Staged.hs | 69 ++++--------------- src/Cooked/MockChain/Testing.hs | 4 +- src/Cooked/Pretty/Cooked.hs | 83 +++++++++++----------- src/Cooked/Pretty/Options.hs | 21 +++++- tests/Cooked/Attack/DatumHijackingSpec.hs | 8 +-- tests/Cooked/Attack/DoubleSatSpec.hs | 84 ++++++++++++----------- tests/Cooked/Attack/DupTokenSpec.hs | 8 +-- tests/Cooked/InlineDatumsSpec.hs | 4 +- tests/Cooked/MockChain/BlockChainSpec.hs | 6 +- tests/Cooked/Tweak/CommonSpec.hs | 83 +++++++++++----------- tests/Cooked/Tweak/OutPermutationsSpec.hs | 10 +-- tests/Cooked/Tweak/TamperDatumSpec.hs | 66 +++++++++--------- tests/Cooked/Tweak/ValidityRangeSpec.hs | 6 +- 16 files changed, 268 insertions(+), 258 deletions(-) diff --git a/src/Cooked/MockChain.hs b/src/Cooked/MockChain.hs index 78ba59c79..b5a7b3193 100644 --- a/src/Cooked/MockChain.hs +++ b/src/Cooked/MockChain.hs @@ -3,14 +3,10 @@ module Cooked.MockChain (module X) where import Cooked.MockChain.Balancing as X -import Cooked.MockChain.BlockChain as X -import Cooked.MockChain.Direct as X +import Cooked.MockChain.BlockChain as X hiding (BlockChainLogEntry) +import Cooked.MockChain.Direct as X hiding (MockChainReturn) import Cooked.MockChain.MinAda as X -import Cooked.MockChain.Staged as X hiding - ( MockChainLog, - MockChainLogEntry, - StagedMockChain, - ) +import Cooked.MockChain.Staged as X hiding (StagedMockChain) import Cooked.MockChain.Testing as X import Cooked.MockChain.UtxoSearch as X import Cooked.MockChain.UtxoState as X (UtxoState) diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index fcdf38e79..5003e9a1c 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -15,6 +15,7 @@ -- from the core definition of our blockchain. module Cooked.MockChain.BlockChain ( MockChainError (..), + BlockChainLogEntry (..), MonadBlockChainBalancing (..), MonadBlockChainWithoutValidation (..), MonadBlockChain (..), @@ -110,6 +111,12 @@ data MockChainError where FailWith :: String -> MockChainError deriving (Show, Eq) +data BlockChainLogEntry where + BCLogSubmittedTxSkel :: SkelContext -> TxSkel -> BlockChainLogEntry + BCLogNewTx :: Api.TxId -> BlockChainLogEntry + BCLogInfo :: String -> BlockChainLogEntry + BCLogFail :: String -> BlockChainLogEntry + -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where -- | Returns the parameters of the chain. @@ -128,6 +135,9 @@ class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m w -- | Returns an output given a reference to it txOutByRefLedger :: Api.TxOutRef -> m (Maybe Ledger.TxOut) + -- | Logs an event that occured during a BlockChain run + blockChainLog :: BlockChainLogEntry -> m () + class (MonadBlockChainBalancing m) => MonadBlockChainWithoutValidation m where -- | Returns a list of all currently known outputs. allUtxosLedger :: m [(Api.TxOutRef, Ledger.TxOut)] @@ -493,6 +503,7 @@ instance (MonadTrans t, MonadBlockChainBalancing m, Monad (t m), MonadError Mock utxosAtLedger = lift . utxosAtLedger txOutByRefLedger = lift . txOutByRefLedger datumFromHash = lift . datumFromHash + blockChainLog = lift . blockChainLog instance (MonadTrans t, MonadBlockChainWithoutValidation m, Monad (t m), MonadError MockChainError (AsTrans t m)) => MonadBlockChainWithoutValidation (AsTrans t m) where allUtxosLedger = lift allUtxosLedger @@ -536,6 +547,7 @@ instance (MonadBlockChainBalancing m) => MonadBlockChainBalancing (ListT m) wher utxosAtLedger = lift . utxosAtLedger txOutByRefLedger = lift . txOutByRefLedger datumFromHash = lift . datumFromHash + blockChainLog = lift . blockChainLog instance (MonadBlockChainWithoutValidation m) => MonadBlockChainWithoutValidation (ListT m) where allUtxosLedger = lift allUtxosLedger diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index c2a977eca..a92ad8f05 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -16,6 +16,7 @@ import Control.Monad.Except import Control.Monad.Identity import Control.Monad.Reader import Control.Monad.State.Strict +import Control.Monad.Writer import Cooked.Conversion.ToScript import Cooked.Conversion.ToScriptHash import Cooked.InitialDistribution @@ -97,6 +98,12 @@ data MockChainSt = MockChainSt } deriving (Show) +mcstToSkelContext :: MockChainSt -> SkelContext +mcstToSkelContext MockChainSt {..} = + SkelContext + (txOutV2FromLedger <$> getIndex mcstIndex) + (Map.map fst mcstDatums) + -- | Generating an emulated state for the emulator from a mockchain state and -- some parameters, based on a standard initial state mcstToEmulatedLedgerState :: MockChainSt -> Emulator.EmulatedLedgerState @@ -129,8 +136,8 @@ instance Eq MockChainSt where ] newtype MockChainT m a = MockChainT - {unMockChain :: StateT MockChainSt (ExceptT MockChainError m) a} - deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError) + {unMockChain :: (StateT MockChainSt (ExceptT MockChainError (WriterT [BlockChainLogEntry] m))) a} + deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError, MonadWriter [BlockChainLogEntry]) type MockChain = MockChainT Identity @@ -140,13 +147,15 @@ instance (Monad m) => Monad (MockChainT m) where MockChainT x >>= f = MockChainT $ x >>= unMockChain . f instance (Monad m) => MonadFail (MockChainT m) where - fail = throwError . FailWith + fail s = do + blockChainLog $ BCLogFail s + throwError $ FailWith s instance MonadTrans MockChainT where - lift = MockChainT . lift . lift + lift = MockChainT . lift . lift . lift instance (Monad m, Alternative m) => Alternative (MockChainT m) where - empty = MockChainT $ StateT $ const $ ExceptT empty + empty = MockChainT $ StateT $ const $ ExceptT $ WriterT empty (<|>) = combineMockChainT (<|>) combineMockChainT :: @@ -157,15 +166,17 @@ combineMockChainT :: MockChainT m x combineMockChainT f ma mb = MockChainT $ StateT $ \s -> - let resA = runExceptT $ runStateT (unMockChain ma) s - resB = runExceptT $ runStateT (unMockChain mb) s - in ExceptT $ f resA resB + let resA = runWriterT $ runExceptT $ runStateT (unMockChain ma) s + resB = runWriterT $ runExceptT $ runStateT (unMockChain mb) s + in ExceptT $ WriterT $ f resA resB + +type MockChainReturn a b = (Either MockChainError (a, b), [BlockChainLogEntry]) mapMockChainT :: - (m (Either MockChainError (a, MockChainSt)) -> n (Either MockChainError (b, MockChainSt))) -> + (m (MockChainReturn a MockChainSt) -> n (MockChainReturn b MockChainSt)) -> MockChainT m a -> MockChainT n b -mapMockChainT f = MockChainT . mapStateT (mapExceptT f) . unMockChain +mapMockChainT f = MockChainT . mapStateT (mapExceptT (mapWriterT f)) . unMockChain -- | Executes a 'MockChainT' from some initial state; does /not/ convert the -- 'MockChainSt' into a 'UtxoState'. @@ -173,8 +184,8 @@ runMockChainTRaw :: (Monad m) => MockChainSt -> MockChainT m a -> - m (Either MockChainError (a, MockChainSt)) -runMockChainTRaw i0 = runExceptT . flip runStateT i0 . unMockChain + m (MockChainReturn a MockChainSt) +runMockChainTRaw i0 = runWriterT . runExceptT . flip runStateT i0 . unMockChain -- | Executes a 'MockChainT' from an initial state set up with the given initial -- value distribution. Similar to 'runMockChainT', uses the default @@ -184,25 +195,25 @@ runMockChainTFrom :: (Monad m) => InitialDistribution -> MockChainT m a -> - m (Either MockChainError (a, UtxoState)) -runMockChainTFrom i0 = fmap (fmap $ second mcstToUtxoState) . runMockChainTRaw (mockChainSt0From i0) + m (MockChainReturn a UtxoState) +runMockChainTFrom i0 s = first (right (second mcstToUtxoState)) <$> runMockChainTRaw (mockChainSt0From i0) s -- | Executes a 'MockChainT' from the canonical initial state and environment. -- The canonical environment uses the default 'SlotConfig' and -- @Cooked.Wallet.wallet 1@ as the sole wallet signing transactions. -runMockChainT :: (Monad m) => MockChainT m a -> m (Either MockChainError (a, UtxoState)) +runMockChainT :: (Monad m) => MockChainT m a -> m (MockChainReturn a UtxoState) runMockChainT = runMockChainTFrom def -- | See 'runMockChainTRaw' -runMockChainRaw :: MockChain a -> Either MockChainError (a, MockChainSt) +runMockChainRaw :: MockChain a -> MockChainReturn a MockChainSt runMockChainRaw = runIdentity . runMockChainTRaw def -- | See 'runMockChainTFrom' -runMockChainFrom :: InitialDistribution -> MockChain a -> Either MockChainError (a, UtxoState) +runMockChainFrom :: InitialDistribution -> MockChain a -> MockChainReturn a UtxoState runMockChainFrom i0 = runIdentity . runMockChainTFrom i0 -- | See 'runMockChainT' -runMockChain :: MockChain a -> Either MockChainError (a, UtxoState) +runMockChain :: MockChain a -> MockChainReturn a UtxoState runMockChain = runIdentity . runMockChainT -- * Canonical initial values @@ -316,6 +327,7 @@ instance (Monad m) => MonadBlockChainBalancing (MockChainT m) where txOutByRefLedger outref = gets $ Map.lookup outref . getIndex . mcstIndex datumFromHash datumHash = (txSkelOutUntypedDatum <=< Just . fst <=< Map.lookup datumHash) <$> gets mcstDatums utxosAtLedger addr = filter ((addr ==) . outputAddress . txOutV2FromLedger . snd) <$> allUtxosLedger + blockChainLog l = tell [l] instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where allUtxosLedger = gets $ Map.toList . getIndex . mcstIndex @@ -325,6 +337,8 @@ instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where instance (Monad m) => MonadBlockChain (MockChainT m) where validateTxSkel skelUnbal = do + -- We log the submitted skeleton + gets mcstToSkelContext >>= blockChainLog . (`BCLogSubmittedTxSkel` skelUnbal) -- We retrieve the current parameters oldParams <- getParams -- We compute the optionally modified parameters @@ -386,6 +400,8 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where modify' (\st -> st {mcstCurrentSlot = mcstCurrentSlot st + 1}) -- We return the parameters to their original state setParams oldParams + -- We log the validated transaction + blockChainLog $ BCLogNewTx (Ledger.fromCardanoTxId $ Ledger.getCardanoTxId cardanoTx) -- We return the validated transaction return cardanoTx where diff --git a/src/Cooked/MockChain/Staged.hs b/src/Cooked/MockChain/Staged.hs index a61a6cff9..9f14917f7 100644 --- a/src/Cooked/MockChain/Staged.hs +++ b/src/Cooked/MockChain/Staged.hs @@ -6,8 +6,6 @@ module Cooked.MockChain.Staged ( interpretAndRunWith, interpretAndRun, - MockChainLogEntry (..), - MockChainLog (..), StagedMockChain, runTweakFrom, MonadModalBlockChain, @@ -26,7 +24,6 @@ import Control.Monad (MonadPlus (..), msum) import Control.Monad.Except import Control.Monad.Reader import Control.Monad.State -import Control.Monad.Trans.Writer (WriterT (runWriterT), tell) import Cooked.Ltl import Cooked.MockChain.BlockChain import Cooked.MockChain.Direct @@ -34,10 +31,8 @@ import Cooked.MockChain.UtxoState import Cooked.Skeleton import Cooked.Tweak.Common import Data.Default -import Data.Map qualified as Map import Ledger.Slot qualified as Ledger import Ledger.Tx qualified as Ledger -import Ledger.Tx.CardanoAPI qualified as Ledger import Plutus.Script.Utils.Scripts qualified as Script import PlutusLedgerApi.V3 qualified as Api @@ -49,30 +44,15 @@ import PlutusLedgerApi.V3 qualified as Api interpretAndRunWith :: (forall m. (Monad m) => MockChainT m a -> m res) -> StagedMockChain a -> - [(res, MockChainLog)] -interpretAndRunWith f smc = runWriterT $ f $ interpret smc + [res] +interpretAndRunWith f smc = f $ interpret smc -interpretAndRun :: - StagedMockChain a -> - [(Either MockChainError (a, UtxoState), MockChainLog)] +interpretAndRun :: StagedMockChain a -> [MockChainReturn a UtxoState] interpretAndRun = interpretAndRunWith runMockChainT -data MockChainLogEntry - = MCLogSubmittedTxSkel SkelContext TxSkel - | MCLogNewTx Api.TxId - | MCLogFail String - -newtype MockChainLog = MockChainLog {unMockChainLog :: [MockChainLogEntry]} - -instance Semigroup MockChainLog where - MockChainLog x <> MockChainLog y = MockChainLog $ x <> y - -instance Monoid MockChainLog where - mempty = MockChainLog [] - -- | The semantic domain in which 'StagedMockChain' gets interpreted; see the -- 'interpret' function for more. -type InterpMockChain = MockChainT (WriterT MockChainLog []) +type InterpMockChain = MockChainT [] -- | The 'interpret' function gives semantics to our traces. One -- 'StagedMockChain' computation yields a potential list of 'MockChainT' @@ -99,6 +79,7 @@ data MockChainBuiltin a where AllUtxosLedger :: MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] UtxosAtLedger :: Api.Address -> MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] ValidatorFromHash :: Script.ValidatorHash -> MockChainBuiltin (Maybe (Script.Versioned Script.Validator)) + BlockChainLog :: BlockChainLogEntry -> MockChainBuiltin () -- | The empty set of traces Empty :: MockChainBuiltin a -- | The union of two sets of traces @@ -132,33 +113,17 @@ instance InterpLtl (UntypedTweak InterpMockChain) MockChainBuiltin InterpMockCha interpBuiltin (ValidateTxSkel skel) = get >>= msum - . map (uncurry interpretAndTell) + . map (uncurry interpretNow) . nowLaterList where - interpretAndTell :: + interpretNow :: UntypedTweak InterpMockChain -> [Ltl (UntypedTweak InterpMockChain)] -> StateT [Ltl (UntypedTweak InterpMockChain)] InterpMockChain Ledger.CardanoTx - interpretAndTell (UntypedTweak now) later = do - mcst <- lift get - let managedTxOuts = getIndex . mcstIndex $ mcst - managedDatums = mcstDatums mcst + interpretNow (UntypedTweak now) later = do (_, skel') <- lift $ runTweakInChain now skel - lift $ - lift $ - tell $ - MockChainLog - [ MCLogSubmittedTxSkel - (SkelContext (txOutV2FromLedger <$> managedTxOuts) $ Map.map fst managedDatums) - skel' - ] - tx <- validateTxSkel skel' - lift $ - lift $ - tell $ - MockChainLog [MCLogNewTx (Ledger.fromCardanoTxId $ Ledger.getCardanoTxId tx)] put later - return tx + validateTxSkel skel' interpBuiltin (TxOutByRefLedger o) = txOutByRefLedger o interpBuiltin GetCurrentSlot = currentSlot interpBuiltin (AwaitSlot s) = awaitSlot s @@ -168,23 +133,18 @@ instance InterpLtl (UntypedTweak InterpMockChain) MockChainBuiltin InterpMockCha interpBuiltin (UtxosAtLedger address) = utxosAtLedger address interpBuiltin Empty = mzero interpBuiltin (Alt l r) = interpLtl l `mplus` interpLtl r - interpBuiltin (Fail msg) = do - lift $ lift $ tell $ MockChainLog [MCLogFail msg] - fail msg + interpBuiltin (Fail msg) = fail msg interpBuiltin (ThrowError err) = throwError err interpBuiltin (CatchError act handler) = catchError (interpLtl act) (interpLtl . handler) + interpBuiltin (BlockChainLog entry) = blockChainLog entry -- ** Helpers to run tweaks for use in tests for tweaks -runTweak :: Tweak InterpMockChain a -> TxSkel -> [Either MockChainError (a, TxSkel)] +runTweak :: Tweak InterpMockChain a -> TxSkel -> [MockChainReturn a TxSkel] runTweak = runTweakFrom def -runTweakFrom :: MockChainSt -> Tweak InterpMockChain a -> TxSkel -> [Either MockChainError (a, TxSkel)] -runTweakFrom mcst tweak skel = - map (right fst . fst) - . runWriterT - . runMockChainTRaw mcst - $ runTweakInChain tweak skel +runTweakFrom :: MockChainSt -> Tweak InterpMockChain a -> TxSkel -> [MockChainReturn a TxSkel] +runTweakFrom mcst tweak = map (first (right fst)) . runMockChainTRaw mcst . runTweakInChain tweak -- ** Modalities @@ -241,6 +201,7 @@ instance MonadBlockChainBalancing StagedMockChain where txOutByRefLedger = singletonBuiltin . TxOutByRefLedger utxosAtLedger = singletonBuiltin . UtxosAtLedger validatorFromHash = singletonBuiltin . ValidatorFromHash + blockChainLog = singletonBuiltin . BlockChainLog instance MonadBlockChainWithoutValidation StagedMockChain where allUtxosLedger = singletonBuiltin AllUtxosLedger diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index afa3f39bd..89d4735b6 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -148,7 +148,7 @@ testAllSatisfiesFrom :: prop testAllSatisfiesFrom pcOpts f = testSatisfiesFrom' (testAll go) where - go :: (Either MockChainError (a, UtxoState), MockChainLog) -> prop + go :: MockChainReturn a UtxoState -> prop go (prop, mcLog) = testCounterexample (renderString (prettyCookedOpt pcOpts) mcLog) (f prop) -- | Asserts that the given 'StagedMockChain' produces exactly two outcomes, @@ -227,7 +227,7 @@ testOneEquivClass pcOpts rel = testSatisfiesFrom' $ \case -- predicates. Check 'testAllSatisfiesFrom' or 'testBinaryRelatedBy' for -- examples on using this. testSatisfiesFrom' :: - ([(Either MockChainError (a, UtxoState), MockChainLog)] -> prop) -> + ([MockChainReturn a UtxoState] -> prop) -> InitialDistribution -> StagedMockChain a -> prop diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 6ef15fd3a..75d5a39ce 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -31,8 +31,8 @@ where import Cooked.Conversion import Cooked.MockChain.BlockChain +import Cooked.MockChain.Direct import Cooked.MockChain.GenerateTx -import Cooked.MockChain.Staged import Cooked.MockChain.UtxoState import Cooked.Output import Cooked.Pretty.Class @@ -124,51 +124,50 @@ instance (Show a) => PrettyCooked (a, UtxoState) where "-" ["Returns:" <+> PP.viaShow res, prettyCookedOpt opts state] -instance (Show a) => PrettyCooked (Either MockChainError (a, UtxoState)) where - prettyCookedOpt opts (Left err) = "🔴" <+> prettyCookedOpt opts err - prettyCookedOpt opts (Right endState) = "🟢" <+> prettyCookedOpt opts endState +prettyBlockChainEntries :: PrettyCookedOpts -> [BlockChainLogEntry] -> DocCooked +prettyBlockChainEntries opts entries = + prettyItemize + "MockChain run:" + "-" + (prettyCookedOpt opts <$> entries) + +instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where + prettyCookedOpt opts (res, entries) = prettyLogWith $ + case res of + Left err -> "🔴" <+> prettyCookedOpt opts err + Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) + where + prettyLogWith :: DocCooked -> DocCooked + prettyLogWith inner = + case pcOptLog opts of + PCOptLogNone -> inner + PCOptLogNoInfo -> + prettyItemize + "End result:" + "-" + [prettyBlockChainEntries opts (filter (\case BCLogInfo _ -> False; _ -> True) entries), inner] + PCOptLogAll -> + prettyItemize + "End result:" + "-" + [prettyBlockChainEntries opts entries, inner] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. -instance PrettyCooked MockChainLog where - prettyCookedOpt opts = - prettyEnumerate "MockChain run:" "." - . go [] - . unMockChainLog - where - -- In order to avoid printing 'MockChainLogValidateTxSkel' then - -- 'MockChainLogNewTx' as two different items, we combine them into one - -- single 'DocCooked' - go :: [DocCooked] -> [MockChainLogEntry] -> [DocCooked] - go - acc - ( MCLogSubmittedTxSkel skelContext skel - : MCLogNewTx txId - : entries - ) - | pcOptPrintTxHashes opts = - go - ( "Validated" - <+> PP.parens ("TxId:" <+> prettyCookedOpt opts txId) - <+> prettyTxSkel opts skelContext skel - : acc - ) - entries - | otherwise = go ("Validated" <+> prettyTxSkel opts skelContext skel : acc) entries - go - acc - ( MCLogSubmittedTxSkel skelContext skel - : entries - ) = - go ("Submitted" <+> prettyTxSkel opts skelContext skel : acc) entries - go acc (MCLogFail msg : entries) = - go ("Fail:" <+> PP.pretty msg : acc) entries - -- This case is not supposed to occur because it should follow a - -- 'MCLogSubmittedTxSkel' - go acc (MCLogNewTx txId : entries) = - go ("New transaction:" <+> prettyCookedOpt opts txId : acc) entries - go acc [] = reverse acc +instance PrettyCooked BlockChainLogEntry where + prettyCookedOpt opts (BCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel + prettyCookedOpt _ (BCLogFail msg) = "Fail:" <+> PP.pretty msg + prettyCookedOpt opts (BCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId + prettyCookedOpt _ (BCLogInfo info) = "Info:" <+> PP.pretty info + +-- go acc (MCLogFail msg : entries) = +-- go ("Fail:" <+> PP.pretty msg : acc) entries +-- -- This case is not supposed to occur because it should follow a +-- -- 'MCLogSubmittedTxSkel' +-- go acc (MCLogNewTx txId : entries) = +-- go ("New transaction:" <+> prettyCookedOpt opts txId : acc) entries +-- go acc [] = reverse acc prettyTxSkel :: PrettyCookedOpts -> SkelContext -> TxSkel -> DocCooked prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals) = diff --git a/src/Cooked/Pretty/Options.hs b/src/Cooked/Pretty/Options.hs index 602a65009..dfc6db987 100644 --- a/src/Cooked/Pretty/Options.hs +++ b/src/Cooked/Pretty/Options.hs @@ -4,6 +4,7 @@ module Cooked.Pretty.Options ( PrettyCookedOpts (..), PrettyCookedHashOpts (..), PCOptTxOutRefs (..), + PCOptLog (..), hashNamesFromList, defaultHashNames, ) @@ -31,7 +32,9 @@ data PrettyCookedOpts = PrettyCookedOpts -- @53_000_000@ instead of @53000000@. By default: True pcOptNumericUnderscores :: Bool, -- | Options related to printing hashes - pcOptHashes :: PrettyCookedHashOpts + pcOptHashes :: PrettyCookedHashOpts, + -- | What kind of log to print + pcOptLog :: PCOptLog } deriving (Eq, Show) @@ -42,9 +45,23 @@ instance Default PrettyCookedOpts where pcOptPrintTxOutRefs = PCOptTxOutRefsHidden, pcOptPrintDefaultTxOpts = False, pcOptNumericUnderscores = True, - pcOptHashes = def + pcOptHashes = def, + pcOptLog = def } +-- | What log to display +data PCOptLog + = -- | No logging at all + PCOptLogNone + | -- | All logging except for infos, default option + PCOptLogNoInfo + | -- | All logging, for debugging purpose + PCOptLogAll + deriving (Eq, Show) + +instance Default PCOptLog where + def = PCOptLogNoInfo + -- | Whether to print transaction outputs references. data PCOptTxOutRefs = -- | Hide them diff --git a/tests/Cooked/Attack/DatumHijackingSpec.hs b/tests/Cooked/Attack/DatumHijackingSpec.hs index 89dd0cfbb..9f86b29b4 100644 --- a/tests/Cooked/Attack/DatumHijackingSpec.hs +++ b/tests/Cooked/Attack/DatumHijackingSpec.hs @@ -201,14 +201,14 @@ tests = ], txSkelSigners = [wallet 1] } - in [ testCase "no modified transactions if no interesting outputs to steal" $ [] @=? skelOut mempty (const True), + in [ testCase "no modified transactions if no interesting outputs to steal" $ [] @=? fst <$> skelOut mempty (const True), testCase "one modified transaction for one interesting output" $ [ Right ( [ConcreteOutput val1 Nothing (TxSkelOutInlineDatum SecondLock) x3 Nothing], skelExpected thief val1 ) ] - @=? skelOut x2 (0 ==), + @=? fst <$> skelOut x2 (0 ==), testCase "two modified transactions for two interesting outputs" $ [ Right ( [ ConcreteOutput val1 Nothing (TxSkelOutInlineDatum SecondLock) x3 Nothing, @@ -217,14 +217,14 @@ tests = skelExpected thief thief ) ] - @=? skelOut x2 (const True), + @=? fst <$> skelOut x2 (const True), testCase "select second interesting output to get one modified transaction" $ [ Right ( [ConcreteOutput val1 Nothing (TxSkelOutInlineDatum SecondLock) x2 Nothing], skelExpected val1 thief ) ] - @=? skelOut x2 (1 ==) + @=? fst <$> skelOut x2 (1 ==) ], testCase "careful validator" $ testFails diff --git a/tests/Cooked/Attack/DoubleSatSpec.hs b/tests/Cooked/Attack/DoubleSatSpec.hs index 68a641a72..64384aa07 100644 --- a/tests/Cooked/Attack/DoubleSatSpec.hs +++ b/tests/Cooked/Attack/DoubleSatSpec.hs @@ -121,7 +121,7 @@ customInitDist = -- | Utxos generated from the initial distribution aUtxo1, aUtxo2, aUtxo3, aUtxo4, bUtxo1, bUtxo2 :: (Api.TxOutRef, Api.TxOut) (aUtxo1, aUtxo2, aUtxo3, aUtxo4, bUtxo1, bUtxo2) = - case runMockChainFrom customInitDist $ do + case fst $ runMockChainFrom customInitDist $ do [a1, a2, a3, a4] <- runUtxoSearch $ utxosAtSearch aValidator [b1, b2] <- runUtxoSearch $ utxosAtSearch bValidator return (a1, a2, a3, a4, b1, b2) of @@ -160,46 +160,48 @@ tests = -- on the focused input 'aValidator' UTxO. skelsOut :: ([Api.TxOutRef] -> [[Api.TxOutRef]]) -> [(ARedeemer, Api.TxOutRef)] -> [TxSkel] skelsOut splitMode aInputs = - mapMaybe (\case Right (_, skel') -> Just skel'; _ -> Nothing) $ - runTweakFrom - (mockChainSt0From customInitDist) - ( doubleSatAttack - splitMode - (txSkelInsL % itraversed) -- we know that every 'TxOutRef' in the inputs points to a UTxO that the 'aValidator' owns - ( \aOref _aRedeemer -> do - bUtxos <- runUtxoSearch $ scriptOutputsSearch bValidator - if - | aOref == fst aUtxo1 -> - return - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) - | (bOref, bOut) <- bUtxos, - outputValue bOut == Script.lovelaceValueOf 123 -- not satisfied by any UTxO in 'dsTestMockChain' - ] - | aOref == fst aUtxo2 -> - return - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) - | (bOref, _) <- bUtxos, - bOref == fst bUtxo1 - ] - | aOref == fst aUtxo3 -> - return $ - concatMap - ( \(bOref, _) -> - if - | bOref == fst bUtxo1 -> - [(txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1)] - | bOref == fst bUtxo2 -> - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1), - (txSkelSomeRedeemer ARedeemer3, toDelta bOref $ txSkelSomeRedeemer BRedeemer2) - ] - | otherwise -> [] - ) - bUtxos - | otherwise -> return [] - ) - (wallet 6) - ) - (skelIn aInputs) + mapMaybe + ((\case Right (_, skel') -> Just skel'; _ -> Nothing) . fst) + ( runTweakFrom + (mockChainSt0From customInitDist) + ( doubleSatAttack + splitMode + (txSkelInsL % itraversed) -- we know that every 'TxOutRef' in the inputs points to a UTxO that the 'aValidator' owns + ( \aOref _aRedeemer -> do + bUtxos <- runUtxoSearch $ scriptOutputsSearch bValidator + if + | aOref == fst aUtxo1 -> + return + [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) + | (bOref, bOut) <- bUtxos, + outputValue bOut == Script.lovelaceValueOf 123 -- not satisfied by any UTxO in 'dsTestMockChain' + ] + | aOref == fst aUtxo2 -> + return + [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) + | (bOref, _) <- bUtxos, + bOref == fst bUtxo1 + ] + | aOref == fst aUtxo3 -> + return $ + concatMap + ( \(bOref, _) -> + if + | bOref == fst bUtxo1 -> + [(txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1)] + | bOref == fst bUtxo2 -> + [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1), + (txSkelSomeRedeemer ARedeemer3, toDelta bOref $ txSkelSomeRedeemer BRedeemer2) + ] + | otherwise -> [] + ) + bUtxos + | otherwise -> return [] + ) + (wallet 6) + ) + (skelIn aInputs) + ) where toDelta :: Api.TxOutRef -> TxSkelRedeemer -> DoubleSatDelta toDelta oref howSpent = (Map.singleton oref howSpent, [], mempty) diff --git a/tests/Cooked/Attack/DupTokenSpec.hs b/tests/Cooked/Attack/DupTokenSpec.hs index b15dd49c5..2f6866eb3 100644 --- a/tests/Cooked/Attack/DupTokenSpec.hs +++ b/tests/Cooked/Attack/DupTokenSpec.hs @@ -105,11 +105,11 @@ tests = ) ] in [ testCase "add one token in every asset class" $ - skelExpected 6 8 @=? skelOut (\_ n -> n + 1), + skelExpected 6 8 @=? fst <$> skelOut (\_ n -> n + 1), testCase "no modified transaction if no increase in value specified" $ - [] @=? skelOut (\_ n -> n), + [] @=? fst <$> skelOut (\_ n -> n), testCase "add tokens depending on the asset class" $ - skelExpected 10 7 @=? skelOut (\ac n -> if ac == ac1 then n + 5 else n) + skelExpected 10 7 @=? fst <$> skelOut (\ac n -> if ac == ac1 then n + 5 else n) ], testCase "careful minting policy" $ let tName = Script.tokenName "MockToken" @@ -155,5 +155,5 @@ tests = ) ] skelOut = runTweak (dupTokenAttack (\_ i -> i + 1) attacker) skelIn - in skelExpected @=? skelOut + in skelExpected @=? fst <$> skelOut ] diff --git a/tests/Cooked/InlineDatumsSpec.hs b/tests/Cooked/InlineDatumsSpec.hs index df39f0e80..52ae2433a 100644 --- a/tests/Cooked/InlineDatumsSpec.hs +++ b/tests/Cooked/InlineDatumsSpec.hs @@ -177,7 +177,7 @@ tests = let theValidator = inputDatumValidator True in [ testCase "the datum is retrieved correctly" $ assertBool "... it's not" $ - case runMockChain (listUtxosTestTrace True theValidator >> allUtxos) of + case fst $ runMockChain (listUtxosTestTrace True theValidator >> allUtxos) of Right (utxos, _endState) -> case mapMaybe ((outputOutputDatum <$>) . isScriptOutputFrom theValidator . snd) utxos of [Api.OutputDatum _] -> True @@ -185,7 +185,7 @@ tests = _ -> False, testCase "the datum hash is retrieved correctly" $ assertBool "... it's not" $ - case runMockChain (listUtxosTestTrace False theValidator >> allUtxos) of + case fst $ runMockChain (listUtxosTestTrace False theValidator >> allUtxos) of Right (utxos, _endState) -> case mapMaybe ((outputOutputDatum <$>) . isScriptOutputFrom theValidator . snd) utxos of [Api.OutputDatumHash _] -> True diff --git a/tests/Cooked/MockChain/BlockChainSpec.hs b/tests/Cooked/MockChain/BlockChainSpec.hs index faacacdf2..0bd506cf3 100644 --- a/tests/Cooked/MockChain/BlockChainSpec.hs +++ b/tests/Cooked/MockChain/BlockChainSpec.hs @@ -15,7 +15,7 @@ tests = "time handling" [ testProperty "bounds computed by slotToTimeInterval are included in slot" $ \n -> - case runMockChain $ do + case fst $ runMockChain $ do (l, r) <- slotToTimeInterval $ Ledger.Slot n Ledger.Slot nl <- getEnclosingSlot l Ledger.Slot nr <- getEnclosingSlot r @@ -24,7 +24,7 @@ tests = Right ((nl, nr), _) -> nl == n && nr == n, testProperty "bounds computed by slotToTimeInterval are maximal" $ \n -> - case runMockChain $ do + case fst $ runMockChain $ do (l, r) <- slotToTimeInterval $ Ledger.Slot n Ledger.Slot nl <- getEnclosingSlot (l - 1) Ledger.Slot nr <- getEnclosingSlot (r + 1) @@ -32,7 +32,7 @@ tests = Left _err -> False Right ((nl, nr), _) -> nl == n - 1 && nr == n + 1, testProperty "time is always included in enclosing slot" $ - \t -> case runMockChain $ slotToTimeInterval =<< getEnclosingSlot (Api.POSIXTime t) of + \t -> case fst $ runMockChain $ slotToTimeInterval =<< getEnclosingSlot (Api.POSIXTime t) of Left _err -> False Right ((Api.POSIXTime a, Api.POSIXTime b), _) -> a <= t && a <= b ] diff --git a/tests/Cooked/Tweak/CommonSpec.hs b/tests/Cooked/Tweak/CommonSpec.hs index 0be654572..43f0ded04 100644 --- a/tests/Cooked/Tweak/CommonSpec.hs +++ b/tests/Cooked/Tweak/CommonSpec.hs @@ -22,35 +22,38 @@ tests = let skel = mkSkel [123, 234, 345] in [ testCase "return empty list and don't change anything if no applicable modifications" $ -- this one is a regression test [Right ([], skel)] - @=? runTweak - ( overMaybeSelectingTweak - (txSkelOutsL % traversed % txSkelOutValueL) - (const Nothing) - (const True) - ) - skel, + @=? fst + <$> runTweak + ( overMaybeSelectingTweak + (txSkelOutsL % traversed % txSkelOutValueL) + (const Nothing) + (const True) + ) + skel, testCase "select applied modification by index" $ [Right ([Script.lovelaceValueOf 345], mkSkel [123, 234, 789])] - @=? runTweak - ( overMaybeSelectingTweak - (txSkelOutsL % traversed % txSkelOutValueL) - ( \value -> - if value `Script.geq` Script.lovelaceValueOf 200 - then Just $ Script.lovelaceValueOf 789 - else Nothing - ) - (== 1) - ) - skel, + @=? fst + <$> runTweak + ( overMaybeSelectingTweak + (txSkelOutsL % traversed % txSkelOutValueL) + ( \value -> + if value `Script.geq` Script.lovelaceValueOf 200 + then Just $ Script.lovelaceValueOf 789 + else Nothing + ) + (== 1) + ) + skel, testCase "return unmodified foci in the right order" $ [Right ([Script.lovelaceValueOf 123, Script.lovelaceValueOf 345], mkSkel [789, 234, 789])] - @=? runTweak - ( overMaybeSelectingTweak - (txSkelOutsL % traversed % txSkelOutValueL) - (const $ Just $ Script.lovelaceValueOf 789) - (`elem` [0, 2]) - ) - skel + @=? fst + <$> runTweak + ( overMaybeSelectingTweak + (txSkelOutsL % traversed % txSkelOutValueL) + (const $ Just $ Script.lovelaceValueOf 789) + (`elem` [0, 2]) + ) + skel ], testGroup "combineModsTweak" $ let skelIn = mkSkel [0, 0, 0] @@ -87,13 +90,14 @@ tests = skelOut 2 2 1, skelOut 2 2 2 ] - ( runTweak - ( combineModsTweak - (tail . subsequences) - (txSkelOutsL % itraversed % txSkelOutValueL % adaL) - (\i x -> return [(x + 1, i), (x + 2, i)]) - ) - skelIn + ( fst + <$> runTweak + ( combineModsTweak + (tail . subsequences) + (txSkelOutsL % itraversed % txSkelOutValueL % adaL) + (\i x -> return [(x + 1, i), (x + 2, i)]) + ) + skelIn ), testCase "separate modifications" $ assertSameSets @@ -105,13 +109,14 @@ tests = skelOut 0 0 1, skelOut 0 0 2 ] - ( runTweak - ( combineModsTweak - (map (: [])) - (txSkelOutsL % itraversed % txSkelOutValueL % adaL) - (\i x -> return [(x + 1, i), (x + 2, i)]) - ) - skelIn + ( fst + <$> runTweak + ( combineModsTweak + (map (: [])) + (txSkelOutsL % itraversed % txSkelOutValueL % adaL) + (\i x -> return [(x + 1, i), (x + 2, i)]) + ) + skelIn ) ] ] diff --git a/tests/Cooked/Tweak/OutPermutationsSpec.hs b/tests/Cooked/Tweak/OutPermutationsSpec.hs index f8a4e6c41..897a2a88e 100644 --- a/tests/Cooked/Tweak/OutPermutationsSpec.hs +++ b/tests/Cooked/Tweak/OutPermutationsSpec.hs @@ -67,23 +67,23 @@ tests = in [ testCase "KeepIdentity (Just 2)" $ assertSameSets (map (Right . ((),)) [skel a b c, skel b a c]) - (runTweak (allOutPermutsTweak $ KeepIdentity $ Just 2) $ skel a b c), + (fst <$> runTweak (allOutPermutsTweak $ KeepIdentity $ Just 2) (skel a b c)), testCase "KeepIdentity Nothing" $ assertSameSets (map (Right . ((),)) [skel a b c, skel a c b, skel b a c, skel b c a, skel c a b, skel c b a]) - (runTweak (allOutPermutsTweak $ KeepIdentity Nothing) $ skel a b c), + (fst <$> runTweak (allOutPermutsTweak $ KeepIdentity Nothing) (skel a b c)), testCase "OmitIdentity (Just 2)" $ assertSameSets [Right ((), skel b a c)] - (runTweak (allOutPermutsTweak $ OmitIdentity $ Just 2) $ skel a b c), + (fst <$> runTweak (allOutPermutsTweak $ OmitIdentity $ Just 2) (skel a b c)), testCase "OmitIdentity Nothing" $ assertSameSets (map (Right . ((),)) [skel a c b, skel b a c, skel b c a, skel c a b, skel c b a]) - (runTweak (allOutPermutsTweak $ OmitIdentity Nothing) $ skel a b c) + (fst <$> runTweak (allOutPermutsTweak $ OmitIdentity Nothing) (skel a b c)) ], testGroup "tests for a single random outputs permutation:" $ let l = (\i -> paysPK (wallet i) $ Script.lovelaceValueOf 123) <$> [1 .. 5] - runs = txSkelOuts . snd <$> rights ((\i -> runTweak (singleOutPermutTweak i) txSkelTemplate {txSkelOuts = l}) =<< [1 .. 5]) + runs = txSkelOuts . snd <$> rights (fst <$> ((\i -> runTweak (singleOutPermutTweak i) txSkelTemplate {txSkelOuts = l}) =<< [1 .. 5])) in [ testCase "All permutations contain the correct elements" $ mapM_ (assertSameSets l) runs, testCase "All permutations are different from the initial distribution" $ diff --git a/tests/Cooked/Tweak/TamperDatumSpec.hs b/tests/Cooked/Tweak/TamperDatumSpec.hs index b1cd22980..212279468 100644 --- a/tests/Cooked/Tweak/TamperDatumSpec.hs +++ b/tests/Cooked/Tweak/TamperDatumSpec.hs @@ -32,18 +32,19 @@ tamperDatumTweakTest = } ) ] - @=? runTweak - ( tamperDatumTweak @(Integer, Integer) - (\(x, y) -> if y == 77 then Nothing else Just (x, y + 1)) - ) - ( txSkelTemplate - { txSkelOuts = - [ paysPK alice (Script.lovelaceValueOf 789) `withDatum` (52 :: Integer, 53 :: Integer), - paysPK alice (Script.lovelaceValueOf 234) `withDatum` (), - paysPK alice (Script.lovelaceValueOf 567) `withDatum` (76 :: Integer, 77 :: Integer) - ] - } - ) + @=? fst + <$> runTweak + ( tamperDatumTweak @(Integer, Integer) + (\(x, y) -> if y == 77 then Nothing else Just (x, y + 1)) + ) + ( txSkelTemplate + { txSkelOuts = + [ paysPK alice (Script.lovelaceValueOf 789) `withDatum` (52 :: Integer, 53 :: Integer), + paysPK alice (Script.lovelaceValueOf 234) `withDatum` (), + paysPK alice (Script.lovelaceValueOf 567) `withDatum` (76 :: Integer, 77 :: Integer) + ] + } + ) malformDatumTweakTest :: TestTree malformDatumTweakTest = @@ -72,26 +73,27 @@ malformDatumTweakTest = txSkelWithDatums1And4 (52 :: Integer, 53 :: Integer) (84 :: Integer, ()), -- datum1 untouched, datum4 changed txSkelWithDatums1And4 (52 :: Integer, 53 :: Integer) False -- datum1 untouched, datum4 changed ] - ( runTweak - ( malformDatumTweak @(Integer, Integer) - ( \(x, y) -> - if y == 77 - then [] - else - [ PlutusTx.toBuiltinData (x, ()), - PlutusTx.toBuiltinData False - ] - ) - ) - ( txSkelTemplate - { txSkelOuts = - [ paysPK alice (Script.lovelaceValueOf 789) `withDatum` (52 :: Integer, 53 :: Integer), - paysPK alice (Script.lovelaceValueOf 234) `withDatum` (), - paysPK alice (Script.lovelaceValueOf 567) `withDatum` (76 :: Integer, 77 :: Integer), - paysPK alice (Script.lovelaceValueOf 567) `withDatum` (84 :: Integer, 85 :: Integer) - ] - } - ) + ( fst + <$> runTweak + ( malformDatumTweak @(Integer, Integer) + ( \(x, y) -> + if y == 77 + then [] + else + [ PlutusTx.toBuiltinData (x, ()), + PlutusTx.toBuiltinData False + ] + ) + ) + ( txSkelTemplate + { txSkelOuts = + [ paysPK alice (Script.lovelaceValueOf 789) `withDatum` (52 :: Integer, 53 :: Integer), + paysPK alice (Script.lovelaceValueOf 234) `withDatum` (), + paysPK alice (Script.lovelaceValueOf 567) `withDatum` (76 :: Integer, 77 :: Integer), + paysPK alice (Script.lovelaceValueOf 567) `withDatum` (84 :: Integer, 85 :: Integer) + ] + } + ) ) tests :: TestTree diff --git a/tests/Cooked/Tweak/ValidityRangeSpec.hs b/tests/Cooked/Tweak/ValidityRangeSpec.hs index e9ecf0e8d..b94d3c9c7 100644 --- a/tests/Cooked/Tweak/ValidityRangeSpec.hs +++ b/tests/Cooked/Tweak/ValidityRangeSpec.hs @@ -62,7 +62,7 @@ tests :: TestTree tests = testGroup "Validity range tweaks" - [ testCase "Validity inclusion" $ fst . head . rights $ runTweak checkIsValidDuring txSkelTemplate, - testCase "Validity intersection" $ fst . head . rights $ runTweak checkAddToValidityRange txSkelTemplate, - testCase "Time shifting in validity range" $ fst . head . rights $ runTweak checkMoveCurrentSlot txSkelTemplate + [ testCase "Validity inclusion" $ fst . head . rights $ fst <$> runTweak checkIsValidDuring txSkelTemplate, + testCase "Validity intersection" $ fst . head . rights $ fst <$> runTweak checkAddToValidityRange txSkelTemplate, + testCase "Time shifting in validity range" $ fst . head . rights $ fst <$> runTweak checkMoveCurrentSlot txSkelTemplate ] From fc36439a1dde19b24423d840a4370490ae130daf Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 5 Jul 2024 00:13:21 +0200 Subject: [PATCH 02/71] more log levels --- src/Cooked/MockChain/BlockChain.hs | 15 +++++++++++- src/Cooked/MockChain/Direct.hs | 4 ++- src/Cooked/Pretty/Cooked.hs | 39 +++++++++++++++--------------- src/Cooked/Pretty/Options.hs | 17 ++++++++++--- 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 5003e9a1c..5aaeddc43 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -52,6 +52,7 @@ module Cooked.MockChain.BlockChain validateTxSkel', txSkelProposalsDeposit, govActionDeposit, + blockChainLogEntryToInt, ) where @@ -78,6 +79,7 @@ import Data.Kind import Data.Map (Map) import Data.Map qualified as Map import Data.Maybe +import Data.Set (Set) import Ledger.Index qualified as Ledger import Ledger.Slot qualified as Ledger import Ledger.Tx qualified as Ledger @@ -99,6 +101,7 @@ data MockChainError where -- | Thrown when not enough collateral are provided. Built upon the fee, the -- percentage and the expected minimal collateral value. MCENoSuitableCollateral :: Integer -> Integer -> Api.Value -> MockChainError + -- | Thrown when an error occured during transaction generation MCEGenerationError :: GenerateTxError -> MockChainError -- | Thrown when an output reference should be in the state of the mockchain, -- but isn't. @@ -113,9 +116,19 @@ data MockChainError where data BlockChainLogEntry where BCLogSubmittedTxSkel :: SkelContext -> TxSkel -> BlockChainLogEntry + BCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> BlockChainLogEntry BCLogNewTx :: Api.TxId -> BlockChainLogEntry + BCLogError :: String -> BlockChainLogEntry + BCLogWarning :: String -> BlockChainLogEntry BCLogInfo :: String -> BlockChainLogEntry - BCLogFail :: String -> BlockChainLogEntry + +blockChainLogEntryToInt :: BlockChainLogEntry -> Integer +blockChainLogEntryToInt BCLogSubmittedTxSkel {} = 3 +blockChainLogEntryToInt BCLogAdjustedTxSkel {} = 3 +blockChainLogEntryToInt BCLogNewTx {} = 3 +blockChainLogEntryToInt BCLogError {} = 3 +blockChainLogEntryToInt BCLogWarning {} = 2 +blockChainLogEntryToInt BCLogInfo {} = 1 -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index a92ad8f05..a3cf340ca 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -148,7 +148,7 @@ instance (Monad m) => Monad (MockChainT m) where instance (Monad m) => MonadFail (MockChainT m) where fail s = do - blockChainLog $ BCLogFail s + blockChainLog $ BCLogError s throwError $ FailWith s instance MonadTrans MockChainT where @@ -351,6 +351,8 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- We balance the skeleton when requested in the skeleton option, and get -- the associated fee, collateral inputs and return collateral wallet (skel, fee, collateralIns, returnCollateralWallet) <- balanceTxSkel minAdaSkelUnbal + -- We log the adjusted skeleton + gets mcstToSkelContext >>= \ctx -> blockChainLog $ BCLogAdjustedTxSkel ctx skel fee collateralIns returnCollateralWallet -- We retrieve data that will be used in the transaction generation process: -- datums, validators and various kinds of inputs. This idea is to provide a -- rich-enough context for the transaction generation to succeed. diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 75d5a39ce..7d9af66dd 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -132,33 +132,32 @@ prettyBlockChainEntries opts entries = (prettyCookedOpt opts <$> entries) instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where - prettyCookedOpt opts (res, entries) = prettyLogWith $ - case res of - Left err -> "🔴" <+> prettyCookedOpt opts err - Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - where - prettyLogWith :: DocCooked -> DocCooked - prettyLogWith inner = - case pcOptLog opts of - PCOptLogNone -> inner - PCOptLogNoInfo -> - prettyItemize - "End result:" - "-" - [prettyBlockChainEntries opts (filter (\case BCLogInfo _ -> False; _ -> True) entries), inner] - PCOptLogAll -> - prettyItemize - "End result:" - "-" - [prettyBlockChainEntries opts entries, inner] + prettyCookedOpt opts (res, entries) = + let inner = case res of + Left err -> "🔴" <+> prettyCookedOpt opts err + Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) + in -- We only keep log entries above the required level + case filter ((pcOptLogToInt (pcOptLog opts) <=) . blockChainLogEntryToInt) entries of + [] -> inner + subEntries -> prettyItemize "End result:" "-" [prettyBlockChainEntries opts subEntries, inner] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. instance PrettyCooked BlockChainLogEntry where prettyCookedOpt opts (BCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel - prettyCookedOpt _ (BCLogFail msg) = "Fail:" <+> PP.pretty msg + prettyCookedOpt opts (BCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = + prettyItemize + "Adjusted:" + "-" + [ "Fee:" <+> prettyCookedOpt opts fee, + "Collaterals:" <+> prettyCookedOpt opts (Set.toList collaterals), + "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet), + prettyTxSkel opts skelContext skel + ] prettyCookedOpt opts (BCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId + prettyCookedOpt _ (BCLogError err) = "Fail:" <+> PP.pretty err + prettyCookedOpt _ (BCLogWarning warn) = "Warning:" <+> PP.pretty warn prettyCookedOpt _ (BCLogInfo info) = "Info:" <+> PP.pretty info -- go acc (MCLogFail msg : entries) = diff --git a/src/Cooked/Pretty/Options.hs b/src/Cooked/Pretty/Options.hs index dfc6db987..37b12d9a5 100644 --- a/src/Cooked/Pretty/Options.hs +++ b/src/Cooked/Pretty/Options.hs @@ -7,6 +7,7 @@ module Cooked.Pretty.Options PCOptLog (..), hashNamesFromList, defaultHashNames, + pcOptLogToInt, ) where @@ -53,14 +54,22 @@ instance Default PrettyCookedOpts where data PCOptLog = -- | No logging at all PCOptLogNone - | -- | All logging except for infos, default option - PCOptLogNoInfo - | -- | All logging, for debugging purpose + | -- | Log errors and special log entries + PCOptLogSpecial + | -- | Same as `PCOptLogSpecial`, but with warnings + PCOptLogWarning + | -- | Log everything PCOptLogAll deriving (Eq, Show) instance Default PCOptLog where - def = PCOptLogNoInfo + def = PCOptLogAll + +pcOptLogToInt :: PCOptLog -> Integer +pcOptLogToInt PCOptLogNone = 4 +pcOptLogToInt PCOptLogSpecial = 3 +pcOptLogToInt PCOptLogWarning = 2 +pcOptLogToInt PCOptLogAll = 1 -- | Whether to print transaction outputs references. data PCOptTxOutRefs From 38c0718a0733cb4e58f171bb473d26e35a618636 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 5 Jul 2024 00:57:09 +0200 Subject: [PATCH 03/71] logging removal of unusable balancing utxos --- src/Cooked/MockChain/Balancing.hs | 22 ++++++++++++++++++---- src/Cooked/Pretty/Cooked.hs | 8 ++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 910422baa..61034bb80 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -7,6 +7,7 @@ import Cardano.Api.Ledger qualified as Cardano import Cardano.Api.Shelley qualified as Cardano import Cardano.Node.Emulator.Internal.Node.Params qualified as Emulator import Cardano.Node.Emulator.Internal.Node.Validation qualified as Emulator +import Control.Monad import Control.Monad.Except import Cooked.Conversion import Cooked.MockChain.BlockChain @@ -83,10 +84,23 @@ balanceTxSkel skelUnbal@TxSkel {..} = do Just bWallet -> do -- The balancing should be performed. We collect the balancing utxos and -- filter out those already used in the unbalanced skeleton. - (filter ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) -> balancingUtxos) <- - runUtxoSearch $ case txOptBalancingUtxos txSkelOpts of - BalancingUtxosFromBalancingWallet -> onlyValueOutputsAtSearch bWallet `filterWithAlways` outputTxOut - BalancingUtxosFromSet utxos -> txOutByRefSearch (Set.toList utxos) `filterWithPure` isPKOutput `filterWithAlways` outputTxOut + candidateBalancingUtxos <- + case txOptBalancingUtxos txSkelOpts of + BalancingUtxosFromBalancingWallet -> runUtxoSearch $ onlyValueOutputsAtSearch bWallet `filterWithAlways` outputTxOut + BalancingUtxosFromSet utxos -> do + bUtxos <- runUtxoSearch $ txOutByRefSearch (Set.toList utxos) + let (pkUtxos, length -> scriptUtxosNb) = partition (isJust . isPKOutput . snd) bUtxos + unless (scriptUtxosNb == 0) $ + blockChainLog $ + BCLogWarning $ + show scriptUtxosNb <> " utxos belonging to scripts have been provided for balancing and are ignored." + return pkUtxos + let (balancingUtxos, length -> knownUtxosNb) = partition ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) candidateBalancingUtxos + unless (knownUtxosNb == 0) $ + blockChainLog $ + BCLogWarning $ + show knownUtxosNb <> " utxos already used in the skeleton have been provided for balancing and are ignored." + case txOptFeePolicy txSkelOpts of -- If fees are left for us to compute, we run a dichotomic search. This -- is full auto mode, the most powerful but time-consuming. diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 7d9af66dd..0e1bf244d 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -150,10 +150,10 @@ instance PrettyCooked BlockChainLogEntry where prettyItemize "Adjusted:" "-" - [ "Fee:" <+> prettyCookedOpt opts fee, - "Collaterals:" <+> prettyCookedOpt opts (Set.toList collaterals), - "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet), - prettyTxSkel opts skelContext skel + [ prettyTxSkel opts skelContext skel, + "Fee: Lovelace" <+> prettyCookedOpt opts fee, + "Collateral inputs:" <+> prettyCookedOpt opts (Set.toList collaterals), + "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] prettyCookedOpt opts (BCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId prettyCookedOpt _ (BCLogError err) = "Fail:" <+> PP.pretty err From b525aeb7f36707e972f50918bcfecc0dfbeb8f71 Mon Sep 17 00:00:00 2001 From: mmontin Date: Mon, 8 Jul 2024 19:37:29 +0200 Subject: [PATCH 04/71] improving logging in balancing --- src/Cooked/MockChain/Balancing.hs | 39 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 61034bb80..abed19761 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -43,8 +43,8 @@ type BalancingOutputs = [(Api.TxOutRef, Api.TxOut)] -- | This is the main entry point of our balancing mechanism. This function -- takes a skeleton and returns a (possibly) balanced skeleton alongside the -- associated fee, collateral inputs and return collateral wallet. The options --- from the skeleton control whether it should be balanced, and how to compute its --- associated elements. +-- from the skeleton control whether it should be balanced, and how to compute +-- its associated elements. balanceTxSkel :: (MonadBlockChainBalancing m) => TxSkel -> m (TxSkel, Fee, Collaterals, Wallet) balanceTxSkel skelUnbal@TxSkel {..} = do -- We retrieve the possible balancing wallet. Any extra payment will be @@ -82,24 +82,23 @@ balanceTxSkel skelUnbal@TxSkel {..} = do ManualFee fee' -> fee' in (skelUnbal,fee,) <$> collateralInsFromFees fee collateralIns returnCollateralWallet Just bWallet -> do - -- The balancing should be performed. We collect the balancing utxos and - -- filter out those already used in the unbalanced skeleton. - candidateBalancingUtxos <- + -- The balancing should be performed. We collect the candidates balancing + -- utxos based on the associated policy + balancingUtxos <- case txOptBalancingUtxos txSkelOpts of BalancingUtxosFromBalancingWallet -> runUtxoSearch $ onlyValueOutputsAtSearch bWallet `filterWithAlways` outputTxOut - BalancingUtxosFromSet utxos -> do - bUtxos <- runUtxoSearch $ txOutByRefSearch (Set.toList utxos) - let (pkUtxos, length -> scriptUtxosNb) = partition (isJust . isPKOutput . snd) bUtxos - unless (scriptUtxosNb == 0) $ - blockChainLog $ - BCLogWarning $ - show scriptUtxosNb <> " utxos belonging to scripts have been provided for balancing and are ignored." - return pkUtxos - let (balancingUtxos, length -> knownUtxosNb) = partition ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) candidateBalancingUtxos - unless (knownUtxosNb == 0) $ - blockChainLog $ - BCLogWarning $ - show knownUtxosNb <> " utxos already used in the skeleton have been provided for balancing and are ignored." + BalancingUtxosFromSet utxos -> + -- We resolve the given set of utxos + runUtxoSearch (txOutByRefSearch (Set.toList utxos)) + >>= + -- We filter out those belonging to scripts, while throwing a + -- warning if any was actually discarded. + filterAndWarn (isJust . isPKOutput . snd) "utxos belonging to scripts have been provided for balancing and are discarded." + -- We filter the candidate utxos by removing those already present in the + -- skeleton, throwing a warning if any was actually discarder + >>= filterAndWarn + ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) + "utxos already used in the skeleton have been provided for balancing and are discarded." case txOptFeePolicy txSkelOpts of -- If fees are left for us to compute, we run a dichotomic search. This @@ -114,6 +113,10 @@ balanceTxSkel skelUnbal@TxSkel {..} = do return (attemptedSkel, fee, adjustedCollateralIns) return (txSkelBal, fee, adjustedCollateralIns, returnCollateralWallet) + where + filterAndWarn f s l + | (ok, length -> koLength) <- partition f l = + unless (koLength == 0) (blockChainLog $ BCLogWarning $ show koLength <> " " <> s) >> return ok -- | This computes the minimum and maximum possible fee a transaction can cost -- based on the current protocol parameters From 98b5e810ee5c50f145e995f7d4f1f7104e612fab Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 11:55:38 +0200 Subject: [PATCH 05/71] new log version with dedicated constructors --- src/Cooked/MockChain.hs | 2 +- src/Cooked/MockChain/Balancing.hs | 11 +++------ src/Cooked/MockChain/BlockChain.hs | 39 ++++++++++-------------------- src/Cooked/MockChain/Direct.hs | 18 ++++++-------- src/Cooked/MockChain/Staged.hs | 6 ++--- src/Cooked/Pretty/Cooked.hs | 27 ++++++--------------- src/Cooked/Pretty/Options.hs | 29 +++------------------- src/Cooked/Skeleton.hs | 1 + 8 files changed, 41 insertions(+), 92 deletions(-) diff --git a/src/Cooked/MockChain.hs b/src/Cooked/MockChain.hs index b5a7b3193..65903ff85 100644 --- a/src/Cooked/MockChain.hs +++ b/src/Cooked/MockChain.hs @@ -3,7 +3,7 @@ module Cooked.MockChain (module X) where import Cooked.MockChain.Balancing as X -import Cooked.MockChain.BlockChain as X hiding (BlockChainLogEntry) +import Cooked.MockChain.BlockChain as X hiding (MockChainLogEntry) import Cooked.MockChain.Direct as X hiding (MockChainReturn) import Cooked.MockChain.MinAda as X import Cooked.MockChain.Staged as X hiding (StagedMockChain) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index abed19761..9765fa442 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -90,15 +90,12 @@ balanceTxSkel skelUnbal@TxSkel {..} = do BalancingUtxosFromSet utxos -> -- We resolve the given set of utxos runUtxoSearch (txOutByRefSearch (Set.toList utxos)) - >>= -- We filter out those belonging to scripts, while throwing a -- warning if any was actually discarded. - filterAndWarn (isJust . isPKOutput . snd) "utxos belonging to scripts have been provided for balancing and are discarded." + >>= filterAndWarn (isJust . isPKOutput . snd) "They belong to scripts." -- We filter the candidate utxos by removing those already present in the -- skeleton, throwing a warning if any was actually discarder - >>= filterAndWarn - ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) - "utxos already used in the skeleton have been provided for balancing and are discarded." + >>= filterAndWarn ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) "They are already used in the skeleton." case txOptFeePolicy txSkelOpts of -- If fees are left for us to compute, we run a dichotomic search. This @@ -115,8 +112,8 @@ balanceTxSkel skelUnbal@TxSkel {..} = do return (txSkelBal, fee, adjustedCollateralIns, returnCollateralWallet) where filterAndWarn f s l - | (ok, length -> koLength) <- partition f l = - unless (koLength == 0) (blockChainLog $ BCLogWarning $ show koLength <> " " <> s) >> return ok + | (ok, toInteger . length -> koLength) <- partition f l = + unless (koLength == 0) (publish $ MCLogDiscardedUtxos koLength s) >> return ok -- | This computes the minimum and maximum possible fee a transaction can cost -- based on the current protocol parameters diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 5aaeddc43..2d8e7f02b 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -15,7 +15,7 @@ -- from the core definition of our blockchain. module Cooked.MockChain.BlockChain ( MockChainError (..), - BlockChainLogEntry (..), + MockChainLogEntry (..), MonadBlockChainBalancing (..), MonadBlockChainWithoutValidation (..), MonadBlockChain (..), @@ -52,7 +52,6 @@ module Cooked.MockChain.BlockChain validateTxSkel', txSkelProposalsDeposit, govActionDeposit, - blockChainLogEntryToInt, ) where @@ -89,12 +88,11 @@ import Optics.Core import Plutus.Script.Utils.Scripts qualified as Script import PlutusLedgerApi.V3 qualified as Api --- * BlockChain monad +-- * MockChain errors -- | The errors that can be produced by the 'MockChainT' monad data MockChainError where - -- FIXME, maybe the validation phase can be deduced from the nature of the - -- error + -- | Validation errors, either in Phase 1 or Phase 2 MCEValidationError :: Ledger.ValidationPhase -> Ledger.ValidationError -> MockChainError -- | Thrown when the balancing wallet does not have enough funds MCEUnbalanceable :: Wallet -> Api.Value -> TxSkel -> MockChainError @@ -103,8 +101,7 @@ data MockChainError where MCENoSuitableCollateral :: Integer -> Integer -> Api.Value -> MockChainError -- | Thrown when an error occured during transaction generation MCEGenerationError :: GenerateTxError -> MockChainError - -- | Thrown when an output reference should be in the state of the mockchain, - -- but isn't. + -- | Thrown when an output reference is missing from the mockchain state MCEUnknownOutRefError :: String -> Api.TxOutRef -> MockChainError -- | Same as 'MCEUnknownOutRefError' for validators. MCEUnknownValidator :: String -> Script.ValidatorHash -> MockChainError @@ -114,25 +111,15 @@ data MockChainError where FailWith :: String -> MockChainError deriving (Show, Eq) -data BlockChainLogEntry where - BCLogSubmittedTxSkel :: SkelContext -> TxSkel -> BlockChainLogEntry - BCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> BlockChainLogEntry - BCLogNewTx :: Api.TxId -> BlockChainLogEntry - BCLogError :: String -> BlockChainLogEntry - BCLogWarning :: String -> BlockChainLogEntry - BCLogInfo :: String -> BlockChainLogEntry - -blockChainLogEntryToInt :: BlockChainLogEntry -> Integer -blockChainLogEntryToInt BCLogSubmittedTxSkel {} = 3 -blockChainLogEntryToInt BCLogAdjustedTxSkel {} = 3 -blockChainLogEntryToInt BCLogNewTx {} = 3 -blockChainLogEntryToInt BCLogError {} = 3 -blockChainLogEntryToInt BCLogWarning {} = 2 -blockChainLogEntryToInt BCLogInfo {} = 1 +data MockChainLogEntry where + MCLogSubmittedTxSkel :: SkelContext -> TxSkel -> MockChainLogEntry + MCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> MockChainLogEntry + MCLogNewTx :: Api.TxId -> MockChainLogEntry + MCLogDiscardedUtxos :: Integer -> String -> MockChainLogEntry -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where - -- | Returns the parameters of the chain. + -- | Returns the emulator parameters, including protocol parameters getParams :: m Emulator.Params -- | Returns a list of all UTxOs at a certain address. @@ -149,7 +136,7 @@ class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m w txOutByRefLedger :: Api.TxOutRef -> m (Maybe Ledger.TxOut) -- | Logs an event that occured during a BlockChain run - blockChainLog :: BlockChainLogEntry -> m () + publish :: MockChainLogEntry -> m () class (MonadBlockChainBalancing m) => MonadBlockChainWithoutValidation m where -- | Returns a list of all currently known outputs. @@ -516,7 +503,7 @@ instance (MonadTrans t, MonadBlockChainBalancing m, Monad (t m), MonadError Mock utxosAtLedger = lift . utxosAtLedger txOutByRefLedger = lift . txOutByRefLedger datumFromHash = lift . datumFromHash - blockChainLog = lift . blockChainLog + publish = lift . publish instance (MonadTrans t, MonadBlockChainWithoutValidation m, Monad (t m), MonadError MockChainError (AsTrans t m)) => MonadBlockChainWithoutValidation (AsTrans t m) where allUtxosLedger = lift allUtxosLedger @@ -560,7 +547,7 @@ instance (MonadBlockChainBalancing m) => MonadBlockChainBalancing (ListT m) wher utxosAtLedger = lift . utxosAtLedger txOutByRefLedger = lift . txOutByRefLedger datumFromHash = lift . datumFromHash - blockChainLog = lift . blockChainLog + publish = lift . publish instance (MonadBlockChainWithoutValidation m) => MonadBlockChainWithoutValidation (ListT m) where allUtxosLedger = lift allUtxosLedger diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index a3cf340ca..01c08fef6 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -136,8 +136,8 @@ instance Eq MockChainSt where ] newtype MockChainT m a = MockChainT - {unMockChain :: (StateT MockChainSt (ExceptT MockChainError (WriterT [BlockChainLogEntry] m))) a} - deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError, MonadWriter [BlockChainLogEntry]) + {unMockChain :: (StateT MockChainSt (ExceptT MockChainError (WriterT [MockChainLogEntry] m))) a} + deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError, MonadWriter [MockChainLogEntry]) type MockChain = MockChainT Identity @@ -147,9 +147,7 @@ instance (Monad m) => Monad (MockChainT m) where MockChainT x >>= f = MockChainT $ x >>= unMockChain . f instance (Monad m) => MonadFail (MockChainT m) where - fail s = do - blockChainLog $ BCLogError s - throwError $ FailWith s + fail = throwError . FailWith instance MonadTrans MockChainT where lift = MockChainT . lift . lift . lift @@ -170,7 +168,7 @@ combineMockChainT f ma mb = MockChainT $ resB = runWriterT $ runExceptT $ runStateT (unMockChain mb) s in ExceptT $ WriterT $ f resA resB -type MockChainReturn a b = (Either MockChainError (a, b), [BlockChainLogEntry]) +type MockChainReturn a b = (Either MockChainError (a, b), [MockChainLogEntry]) mapMockChainT :: (m (MockChainReturn a MockChainSt) -> n (MockChainReturn b MockChainSt)) -> @@ -327,7 +325,7 @@ instance (Monad m) => MonadBlockChainBalancing (MockChainT m) where txOutByRefLedger outref = gets $ Map.lookup outref . getIndex . mcstIndex datumFromHash datumHash = (txSkelOutUntypedDatum <=< Just . fst <=< Map.lookup datumHash) <$> gets mcstDatums utxosAtLedger addr = filter ((addr ==) . outputAddress . txOutV2FromLedger . snd) <$> allUtxosLedger - blockChainLog l = tell [l] + publish l = tell [l] instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where allUtxosLedger = gets $ Map.toList . getIndex . mcstIndex @@ -338,7 +336,7 @@ instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where instance (Monad m) => MonadBlockChain (MockChainT m) where validateTxSkel skelUnbal = do -- We log the submitted skeleton - gets mcstToSkelContext >>= blockChainLog . (`BCLogSubmittedTxSkel` skelUnbal) + gets mcstToSkelContext >>= publish . (`MCLogSubmittedTxSkel` skelUnbal) -- We retrieve the current parameters oldParams <- getParams -- We compute the optionally modified parameters @@ -352,7 +350,7 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- the associated fee, collateral inputs and return collateral wallet (skel, fee, collateralIns, returnCollateralWallet) <- balanceTxSkel minAdaSkelUnbal -- We log the adjusted skeleton - gets mcstToSkelContext >>= \ctx -> blockChainLog $ BCLogAdjustedTxSkel ctx skel fee collateralIns returnCollateralWallet + gets mcstToSkelContext >>= \ctx -> publish $ MCLogAdjustedTxSkel ctx skel fee collateralIns returnCollateralWallet -- We retrieve data that will be used in the transaction generation process: -- datums, validators and various kinds of inputs. This idea is to provide a -- rich-enough context for the transaction generation to succeed. @@ -403,7 +401,7 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- We return the parameters to their original state setParams oldParams -- We log the validated transaction - blockChainLog $ BCLogNewTx (Ledger.fromCardanoTxId $ Ledger.getCardanoTxId cardanoTx) + publish $ MCLogNewTx (Ledger.fromCardanoTxId $ Ledger.getCardanoTxId cardanoTx) -- We return the validated transaction return cardanoTx where diff --git a/src/Cooked/MockChain/Staged.hs b/src/Cooked/MockChain/Staged.hs index 9f14917f7..17016929f 100644 --- a/src/Cooked/MockChain/Staged.hs +++ b/src/Cooked/MockChain/Staged.hs @@ -79,7 +79,7 @@ data MockChainBuiltin a where AllUtxosLedger :: MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] UtxosAtLedger :: Api.Address -> MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] ValidatorFromHash :: Script.ValidatorHash -> MockChainBuiltin (Maybe (Script.Versioned Script.Validator)) - BlockChainLog :: BlockChainLogEntry -> MockChainBuiltin () + Publish :: MockChainLogEntry -> MockChainBuiltin () -- | The empty set of traces Empty :: MockChainBuiltin a -- | The union of two sets of traces @@ -136,7 +136,7 @@ instance InterpLtl (UntypedTweak InterpMockChain) MockChainBuiltin InterpMockCha interpBuiltin (Fail msg) = fail msg interpBuiltin (ThrowError err) = throwError err interpBuiltin (CatchError act handler) = catchError (interpLtl act) (interpLtl . handler) - interpBuiltin (BlockChainLog entry) = blockChainLog entry + interpBuiltin (Publish entry) = publish entry -- ** Helpers to run tweaks for use in tests for tweaks @@ -201,7 +201,7 @@ instance MonadBlockChainBalancing StagedMockChain where txOutByRefLedger = singletonBuiltin . TxOutByRefLedger utxosAtLedger = singletonBuiltin . UtxosAtLedger validatorFromHash = singletonBuiltin . ValidatorFromHash - blockChainLog = singletonBuiltin . BlockChainLog + publish = singletonBuiltin . Publish instance MonadBlockChainWithoutValidation StagedMockChain where allUtxosLedger = singletonBuiltin AllUtxosLedger diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 0e1bf244d..6cee78e6c 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -124,29 +124,20 @@ instance (Show a) => PrettyCooked (a, UtxoState) where "-" ["Returns:" <+> PP.viaShow res, prettyCookedOpt opts state] -prettyBlockChainEntries :: PrettyCookedOpts -> [BlockChainLogEntry] -> DocCooked -prettyBlockChainEntries opts entries = - prettyItemize - "MockChain run:" - "-" - (prettyCookedOpt opts <$> entries) - instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where prettyCookedOpt opts (res, entries) = - let inner = case res of + let mcLog = prettyItemize "MockChain run log:" "-" (prettyCookedOpt opts <$> entries) + mcEndResult = case res of Left err -> "🔴" <+> prettyCookedOpt opts err Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - in -- We only keep log entries above the required level - case filter ((pcOptLogToInt (pcOptLog opts) <=) . blockChainLogEntryToInt) entries of - [] -> inner - subEntries -> prettyItemize "End result:" "-" [prettyBlockChainEntries opts subEntries, inner] + in prettyItemizeNoTitle "-" $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. -instance PrettyCooked BlockChainLogEntry where - prettyCookedOpt opts (BCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel - prettyCookedOpt opts (BCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = +instance PrettyCooked MockChainLogEntry where + prettyCookedOpt opts (MCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel + prettyCookedOpt opts (MCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = prettyItemize "Adjusted:" "-" @@ -155,10 +146,8 @@ instance PrettyCooked BlockChainLogEntry where "Collateral inputs:" <+> prettyCookedOpt opts (Set.toList collaterals), "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] - prettyCookedOpt opts (BCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId - prettyCookedOpt _ (BCLogError err) = "Fail:" <+> PP.pretty err - prettyCookedOpt _ (BCLogWarning warn) = "Warning:" <+> PP.pretty warn - prettyCookedOpt _ (BCLogInfo info) = "Info:" <+> PP.pretty info + prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId + prettyCookedOpt opts (MCLogDiscardedUtxos n s) = prettyCookedOpt opts n <+> "balancing utxos were discarded:" <+> PP.pretty s -- go acc (MCLogFail msg : entries) = -- go ("Fail:" <+> PP.pretty msg : acc) entries diff --git a/src/Cooked/Pretty/Options.hs b/src/Cooked/Pretty/Options.hs index 37b12d9a5..8e630ed92 100644 --- a/src/Cooked/Pretty/Options.hs +++ b/src/Cooked/Pretty/Options.hs @@ -4,10 +4,8 @@ module Cooked.Pretty.Options ( PrettyCookedOpts (..), PrettyCookedHashOpts (..), PCOptTxOutRefs (..), - PCOptLog (..), hashNamesFromList, defaultHashNames, - pcOptLogToInt, ) where @@ -34,8 +32,8 @@ data PrettyCookedOpts = PrettyCookedOpts pcOptNumericUnderscores :: Bool, -- | Options related to printing hashes pcOptHashes :: PrettyCookedHashOpts, - -- | What kind of log to print - pcOptLog :: PCOptLog + -- | Whether to display the log + pcOptLog :: Bool } deriving (Eq, Show) @@ -47,30 +45,9 @@ instance Default PrettyCookedOpts where pcOptPrintDefaultTxOpts = False, pcOptNumericUnderscores = True, pcOptHashes = def, - pcOptLog = def + pcOptLog = True } --- | What log to display -data PCOptLog - = -- | No logging at all - PCOptLogNone - | -- | Log errors and special log entries - PCOptLogSpecial - | -- | Same as `PCOptLogSpecial`, but with warnings - PCOptLogWarning - | -- | Log everything - PCOptLogAll - deriving (Eq, Show) - -instance Default PCOptLog where - def = PCOptLogAll - -pcOptLogToInt :: PCOptLog -> Integer -pcOptLogToInt PCOptLogNone = 4 -pcOptLogToInt PCOptLogSpecial = 3 -pcOptLogToInt PCOptLogWarning = 2 -pcOptLogToInt PCOptLogAll = 1 - -- | Whether to print transaction outputs references. data PCOptTxOutRefs = -- | Hide them diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 23e415a40..b504a7ab6 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -1051,6 +1051,7 @@ data SkelContext = SkelContext { skelContextTxOuts :: Map Api.TxOutRef Api.TxOut, skelContextTxSkelOutDatums :: Map Api.DatumHash TxSkelOutDatum } + deriving (Show, Eq) -- | Returns the full value contained in the skeleton outputs txSkelValueInOutputs :: TxSkel -> Api.Value From 47f54a506b5daee738d6706364b0f0878df6463e Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 12:02:43 +0200 Subject: [PATCH 06/71] changing item --- src/Cooked/Pretty/Cooked.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 6cee78e6c..4ca784258 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -130,7 +130,7 @@ instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where mcEndResult = case res of Left err -> "🔴" <+> prettyCookedOpt opts err Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - in prettyItemizeNoTitle "-" $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] + in prettyItemizeNoTitle "*" $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has From 2ff0e461553a4bedd99d869bda4eb2dfcef066b1 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 15:38:43 +0200 Subject: [PATCH 07/71] integrating comments, adding comments and more readable bullets --- src/Cooked/MockChain/BlockChain.hs | 12 ++++++++++++ src/Cooked/Pretty/Cooked.hs | 20 ++++++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 2d8e7f02b..01828985d 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -111,10 +111,22 @@ data MockChainError where FailWith :: String -> MockChainError deriving (Show, Eq) +-- * MockChain logs + +-- | This represents the specific events that should be logged when processing +-- transactions. If a new kind of event arises, then a new constructor should be +-- provided here. data MockChainLogEntry where + -- | Logging a Skeleton as it is submitted by the user. MCLogSubmittedTxSkel :: SkelContext -> TxSkel -> MockChainLogEntry + -- | Logging a Skeleton as it has been adjusted by the balancing mechanism, + -- alongside fee, collateral utxos and return collateral wallet. MCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> MockChainLogEntry + -- | Logging the appearance of a new transaction, after a skeleton has been + -- successfully sent for validation. MCLogNewTx :: Api.TxId -> MockChainLogEntry + -- | Logging the fact that utxos provided by the user for balancing have to be + -- discarded for a specific reason. MCLogDiscardedUtxos :: Integer -> String -> MockChainLogEntry -- | Contains methods needed for balancing. diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 4ca784258..b2d3d4e63 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -126,41 +126,33 @@ instance (Show a) => PrettyCooked (a, UtxoState) where instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where prettyCookedOpt opts (res, entries) = - let mcLog = prettyItemize "MockChain run log:" "-" (prettyCookedOpt opts <$> entries) + let mcLog = "📘" <+> prettyItemize "MockChain run log:" "⁍" (prettyCookedOpt opts <$> entries) mcEndResult = case res of Left err -> "🔴" <+> prettyCookedOpt opts err Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - in prettyItemizeNoTitle "*" $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] + in PP.vsep $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. instance PrettyCooked MockChainLogEntry where - prettyCookedOpt opts (MCLogSubmittedTxSkel skelContext skel) = "Submitted:" <+> prettyTxSkel opts skelContext skel + prettyCookedOpt opts (MCLogSubmittedTxSkel skelContext skel) = prettyItemize "Submitted:" "-" [prettyTxSkel opts skelContext skel] prettyCookedOpt opts (MCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = prettyItemize "Adjusted:" "-" [ prettyTxSkel opts skelContext skel, "Fee: Lovelace" <+> prettyCookedOpt opts fee, - "Collateral inputs:" <+> prettyCookedOpt opts (Set.toList collaterals), - "Return collateral wallet:" <+> prettyCookedOpt opts (walletPKHash returnWallet) + prettyItemize "Collateral inputs:" "-" (prettyCookedOpt opts <$> Set.toList collaterals), + "Return collateral target:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId prettyCookedOpt opts (MCLogDiscardedUtxos n s) = prettyCookedOpt opts n <+> "balancing utxos were discarded:" <+> PP.pretty s --- go acc (MCLogFail msg : entries) = --- go ("Fail:" <+> PP.pretty msg : acc) entries --- -- This case is not supposed to occur because it should follow a --- -- 'MCLogSubmittedTxSkel' --- go acc (MCLogNewTx txId : entries) = --- go ("New transaction:" <+> prettyCookedOpt opts txId : acc) entries --- go acc [] = reverse acc - prettyTxSkel :: PrettyCookedOpts -> SkelContext -> TxSkel -> DocCooked prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals) = prettyItemize - "transaction skeleton:" + "Transaction skeleton:" "-" ( catMaybes [ prettyItemizeNonEmpty "Labels:" "-" (prettyCookedOpt opts <$> Set.toList lbl), From 73d8ab22a08fc850eec6c45aafb21a65dcae00f2 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 16:27:17 +0200 Subject: [PATCH 08/71] fixing the bug where collateral inputs were not resolved --- src/Cooked/Pretty/Cooked.hs | 46 +++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index b2d3d4e63..2c5f43536 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -143,7 +143,7 @@ instance PrettyCooked MockChainLogEntry where "-" [ prettyTxSkel opts skelContext skel, "Fee: Lovelace" <+> prettyCookedOpt opts fee, - prettyItemize "Collateral inputs:" "-" (prettyCookedOpt opts <$> Set.toList collaterals), + prettyItemize "Collateral inputs:" "-" (prettyCollateralIn opts skelContext <$> Set.toList collaterals), "Return collateral target:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId @@ -348,29 +348,35 @@ prettyTxSkelOut opts (Pays output) = prettyTxSkelOutDatumMaybe :: PrettyCookedOpts -> TxSkelOutDatum -> Maybe DocCooked prettyTxSkelOutDatumMaybe _ TxSkelOutNoDatum = Nothing prettyTxSkelOutDatumMaybe opts txSkelOutDatum@(TxSkelOutInlineDatum _) = - Just $ - "Datum (inlined):" - <+> PP.align (prettyCookedOpt opts txSkelOutDatum) + Just $ "Datum (inlined):" <+> PP.align (prettyCookedOpt opts txSkelOutDatum) prettyTxSkelOutDatumMaybe opts txSkelOutDatum = - Just $ - "Datum (hashed):" - <+> PP.align (prettyCookedOpt opts txSkelOutDatum) + Just $ "Datum (hashed):" <+> PP.align (prettyCookedOpt opts txSkelOutDatum) + +prettyTxOutRefM :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> Maybe (DocCooked, DocCooked, [DocCooked]) +prettyTxOutRefM opts skelContext txOutRef = + ( \(output, txSkelOutDatum) -> + ( prettyCookedOpt opts (outputAddress output), + prettyCookedOpt opts (outputValue output), + catMaybes + [ prettyTxSkelOutDatumMaybe opts txSkelOutDatum, + getReferenceScriptDoc opts output + ] + ) + ) + <$> lookupOutput skelContext txOutRef + +prettyCollateralIn :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> DocCooked +prettyCollateralIn opts skelContext txOutRef = + case prettyTxOutRefM opts skelContext txOutRef of + Nothing -> prettyCookedOpt opts txOutRef <+> "(non resolved)" + Just (addressDoc, valueDoc, otherDocs) -> prettyItemize ("Belonging to" <+> addressDoc) "-" (valueDoc : otherDocs) prettyTxSkelIn :: PrettyCookedOpts -> SkelContext -> (Api.TxOutRef, TxSkelRedeemer) -> DocCooked -prettyTxSkelIn opts skelContext (txOutRef, txSkelRedeemer) = do - case lookupOutput skelContext txOutRef of +prettyTxSkelIn opts skelContext (txOutRef, txSkelRedeemer) = + case prettyTxOutRefM opts skelContext txOutRef of Nothing -> "Spends" <+> prettyCookedOpt opts txOutRef <+> "(non resolved)" - Just (output, txSkelOutDatum) -> - prettyItemize - ("Spends from" <+> prettyCookedOpt opts (outputAddress output)) - "-" - ( prettyCookedOpt opts (outputValue output) - : prettyTxSkelRedeemer opts txSkelRedeemer - <> catMaybes - [ prettyTxSkelOutDatumMaybe opts txSkelOutDatum, - getReferenceScriptDoc opts output - ] - ) + Just (addressDoc, valueDoc, otherDocs) -> + prettyItemize ("Spends from" <+> addressDoc) "-" (valueDoc : prettyTxSkelRedeemer opts txSkelRedeemer <> otherDocs) prettyTxSkelInReference :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> Maybe DocCooked prettyTxSkelInReference opts skelContext txOutRef = do From a94db0a8f72f2248abb7d2737c87d0bbf311f396 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 16:32:39 +0200 Subject: [PATCH 09/71] CHANGELOG.md --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ad33523..f1a349d8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,13 @@ - `toInitDistWithMinAda` and `unsafeToInitDistWithMinAda` to ensure the initial distribution only provides outputs with the required minimal ada based on default parameters. - +- PrettyCooked option `pcOptLog`, which is a boolean, to turn on or off the log + display in the pretty printer. The default value is `True`. + ### Removed - `positivePart` and `negativePart` in `ValueUtils.hs`. Replaced by `Api.split`. +- Redundant logging of errors in mockchain runs. ### Changed @@ -22,6 +25,11 @@ constructors: `txSkelSomeRedeemer`, `txSkelEmptyRedeemer`, `txSkelSomeRedeemerAndReferenceScript`, `txSkelEmptyRedeemerAndReferenceScript`. +- Logging has been reworked: + * it is no longer limited to `StagedMockChain` runs + * it is now a component of `MonadBlockChainBalancing` + * it can be turned on/off in skeleton options + * it now displays the discarding of utxos during balancing. ### Fixed From f2cfce6f98815fc85f1c06f2df0f6fe317654b81 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sun, 28 Jul 2024 02:50:08 +0200 Subject: [PATCH 10/71] integrating review comments --- CHANGELOG.md | 3 ++- src/Cooked/MockChain.hs | 2 +- src/Cooked/Pretty/Cooked.hs | 16 ++++++++++------ src/Cooked/Pretty/Options.hs | 4 ++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a349d8d..c4189d309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,9 @@ - Logging has been reworked: * it is no longer limited to `StagedMockChain` runs * it is now a component of `MonadBlockChainBalancing` - * it can be turned on/off in skeleton options + * it can be turned on/off in pretty-printing options * it now displays the discarding of utxos during balancing. + * it is not visible from outside of `cooked-validators` ### Fixed diff --git a/src/Cooked/MockChain.hs b/src/Cooked/MockChain.hs index 65903ff85..a980de21e 100644 --- a/src/Cooked/MockChain.hs +++ b/src/Cooked/MockChain.hs @@ -3,7 +3,7 @@ module Cooked.MockChain (module X) where import Cooked.MockChain.Balancing as X -import Cooked.MockChain.BlockChain as X hiding (MockChainLogEntry) +import Cooked.MockChain.BlockChain as X hiding (MockChainLogEntry, publish) import Cooked.MockChain.Direct as X hiding (MockChainReturn) import Cooked.MockChain.MinAda as X import Cooked.MockChain.Staged as X hiding (StagedMockChain) diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index e92c2d25c..36ec31017 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -49,6 +49,7 @@ import Data.Set qualified as Set import Optics.Core import Plutus.Script.Utils.Ada qualified as Script import Plutus.Script.Utils.Scripts qualified as Script +import Plutus.Script.Utils.Value qualified as Script import PlutusLedgerApi.V3 qualified as Api import Prettyprinter ((<+>)) import Prettyprinter qualified as PP @@ -129,7 +130,7 @@ instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where mcEndResult = case res of Left err -> "🔴" <+> prettyCookedOpt opts err Right (a, s) -> "🟢" <+> prettyCookedOpt opts (a, s) - in PP.vsep $ if pcOptLog opts then [mcLog, mcEndResult] else [mcEndResult] + in PP.vsep $ if pcOptPrintLog opts then [mcLog, mcEndResult] else [mcEndResult] -- | This pretty prints a 'MockChainLog' that usually consists of the list of -- validated or submitted transactions. In the log, we know a transaction has @@ -141,7 +142,7 @@ instance PrettyCooked MockChainLogEntry where "Adjusted:" "-" [ prettyTxSkel opts skelContext skel, - "Fee: Lovelace" <+> prettyCookedOpt opts fee, + "Fee:" <+> prettyCookedOpt opts (Script.lovelace fee), prettyItemize "Collateral inputs:" "-" (prettyCollateralIn opts skelContext <$> Set.toList collaterals), "Return collateral target:" <+> prettyCookedOpt opts (walletPKHash returnWallet) ] @@ -351,8 +352,11 @@ prettyTxSkelOutDatumMaybe opts txSkelOutDatum@(TxSkelOutInlineDatum _) = prettyTxSkelOutDatumMaybe opts txSkelOutDatum = Just $ "Datum (hashed):" <+> PP.align (prettyCookedOpt opts txSkelOutDatum) -prettyTxOutRefM :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> Maybe (DocCooked, DocCooked, [DocCooked]) -prettyTxOutRefM opts skelContext txOutRef = +-- | Resolves a "TxOutRef" from a given context, builds a doc cooked for its +-- address and value, and also builds a possibly empty list for its datum and +-- reference script when they exist. +utxoToPartsAsDocCooked :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> Maybe (DocCooked, DocCooked, [DocCooked]) +utxoToPartsAsDocCooked opts skelContext txOutRef = ( \(output, txSkelOutDatum) -> ( prettyCookedOpt opts (outputAddress output), prettyCookedOpt opts (outputValue output), @@ -366,13 +370,13 @@ prettyTxOutRefM opts skelContext txOutRef = prettyCollateralIn :: PrettyCookedOpts -> SkelContext -> Api.TxOutRef -> DocCooked prettyCollateralIn opts skelContext txOutRef = - case prettyTxOutRefM opts skelContext txOutRef of + case utxoToPartsAsDocCooked opts skelContext txOutRef of Nothing -> prettyCookedOpt opts txOutRef <+> "(non resolved)" Just (addressDoc, valueDoc, otherDocs) -> prettyItemize ("Belonging to" <+> addressDoc) "-" (valueDoc : otherDocs) prettyTxSkelIn :: PrettyCookedOpts -> SkelContext -> (Api.TxOutRef, TxSkelRedeemer) -> DocCooked prettyTxSkelIn opts skelContext (txOutRef, txSkelRedeemer) = - case prettyTxOutRefM opts skelContext txOutRef of + case utxoToPartsAsDocCooked opts skelContext txOutRef of Nothing -> "Spends" <+> prettyCookedOpt opts txOutRef <+> "(non resolved)" Just (addressDoc, valueDoc, otherDocs) -> prettyItemize ("Spends from" <+> addressDoc) "-" (valueDoc : prettyTxSkelRedeemer opts txSkelRedeemer <> otherDocs) diff --git a/src/Cooked/Pretty/Options.hs b/src/Cooked/Pretty/Options.hs index f9d998ea3..b87525bd4 100644 --- a/src/Cooked/Pretty/Options.hs +++ b/src/Cooked/Pretty/Options.hs @@ -33,7 +33,7 @@ data PrettyCookedOpts = PrettyCookedOpts -- | Options related to printing hashes pcOptHashes :: PrettyCookedHashOpts, -- | Whether to display the log - pcOptLog :: Bool + pcOptPrintLog :: Bool } deriving (Eq, Show) @@ -45,7 +45,7 @@ instance Default PrettyCookedOpts where pcOptPrintDefaultTxOpts = False, pcOptNumericUnderscores = True, pcOptHashes = def, - pcOptLog = True + pcOptPrintLog = True } -- | Whether to print transaction outputs references. From 8764a1aaf7eedc0adc49151cad07397850da6e36 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sun, 28 Jul 2024 11:53:53 +0200 Subject: [PATCH 11/71] typo --- src/Cooked/MockChain/Balancing.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 462b74433..deffd976b 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -94,7 +94,7 @@ balanceTxSkel skelUnbal@TxSkel {..} = do -- warning if any was actually discarded. >>= filterAndWarn (isJust . isPKOutput . snd) "They belong to scripts." -- We filter the candidate utxos by removing those already present in the - -- skeleton, throwing a warning if any was actually discarder + -- skeleton, throwing a warning if any was actually discarded >>= filterAndWarn ((`notElem` txSkelKnownTxOutRefs skelUnbal) . fst) "They are already used in the skeleton." case txOptFeePolicy txSkelOpts of From 798c8c3e12a34f08bf95b26763840f533a12ec03 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sun, 28 Jul 2024 12:29:19 +0200 Subject: [PATCH 12/71] removing useless instances --- src/Cooked/Skeleton.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index b504a7ab6..23e415a40 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -1051,7 +1051,6 @@ data SkelContext = SkelContext { skelContextTxOuts :: Map Api.TxOutRef Api.TxOut, skelContextTxSkelOutDatums :: Map Api.DatumHash TxSkelOutDatum } - deriving (Show, Eq) -- | Returns the full value contained in the skeleton outputs txSkelValueInOutputs :: TxSkel -> Api.Value From 492d057b9d4a209a12c2ed9127ac9d27fcf8dbe2 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sun, 7 Jul 2024 22:07:29 +0200 Subject: [PATCH 13/71] wip --- src/Cooked/MockChain/Balancing.hs | 49 +++++++++++++++---------- src/Cooked/MockChain/GenerateTx/Body.hs | 16 ++++++-- tests/Cooked/BalancingSpec.hs | 16 +++++++- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index deffd976b..f2ee56c43 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -1,7 +1,12 @@ -- | This module handles auto-balancing of transaction skeleton. This includes -- computation of fees and collaterals because their computation cannot be -- separated from the balancing. -module Cooked.MockChain.Balancing (balanceTxSkel, getMinAndMaxFee, estimateTxSkelFee) where +module Cooked.MockChain.Balancing + ( balanceTxSkel, + getMinAndMaxFee, + estimateTxSkelFee, + ) +where import Cardano.Api.Ledger qualified as Cardano import Cardano.Api.Shelley qualified as Cardano @@ -61,9 +66,9 @@ balanceTxSkel skelUnbal@TxSkel {..} = do -- single transaction fee, which we retrieve. (minFee, maxFee) <- getMinAndMaxFee - -- We collect collateral inputs. They might be directly provided in the - -- skeleton, or should be retrieved from a given wallet. They are associated - -- with a return collateral wallet, which we retrieve as well. + -- We collect collateral inputs candidates. They might be directly provided in + -- the skeleton, or should be retrieved from a given wallet. They are + -- associated with a return collateral wallet, which we retrieve as well. (collateralIns, returnCollateralWallet) <- case txOptCollateralUtxos txSkelOpts of CollateralUtxosFromBalancingWallet -> case balancingWallet of Nothing -> fail "Can't select collateral utxos from a balancing wallet because it does not exist." @@ -71,6 +76,11 @@ balanceTxSkel skelUnbal@TxSkel {..} = do CollateralUtxosFromWallet cWallet -> (,cWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch cWallet) CollateralUtxosFromSet utxos rWallet -> return (utxos, rWallet) + -- The transaction will only require collaterals when involving scripts. + requireCollaterals <- do + insValidators <- txSkelInputValidators skelUnbal + return $ not $ Map.null txSkelMints && null (mapMaybe txSkelProposalWitness txSkelProposals) && Map.null insValidators + -- At this point, the presence (or absence) of balancing wallet dictates -- whether the transaction should be automatically balanced or not. (txSkelBal, fee, adjustedCollateralIns) <- case balancingWallet of @@ -80,7 +90,7 @@ balanceTxSkel skelUnbal@TxSkel {..} = do let fee = case txOptFeePolicy txSkelOpts of AutoFeeComputation -> maxFee ManualFee fee' -> fee' - in (skelUnbal,fee,) <$> collateralInsFromFees fee collateralIns returnCollateralWallet + in (skelUnbal,fee,) <$> collateralInsFromFees requireCollaterals fee collateralIns returnCollateralWallet Just bWallet -> do -- The balancing should be performed. We collect the candidates balancing -- utxos based on the associated policy @@ -101,11 +111,11 @@ balanceTxSkel skelUnbal@TxSkel {..} = do -- If fees are left for us to compute, we run a dichotomic search. This -- is full auto mode, the most powerful but time-consuming. AutoFeeComputation -> - computeFeeAndBalance bWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skelUnbal + computeFeeAndBalance requireCollaterals bWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skelUnbal -- If fee are provided manually, we adjust the collaterals and the -- skeleton around them directly. ManualFee fee -> do - adjustedCollateralIns <- collateralInsFromFees fee collateralIns returnCollateralWallet + adjustedCollateralIns <- collateralInsFromFees requireCollaterals fee collateralIns returnCollateralWallet attemptedSkel <- computeBalancedTxSkel bWallet balancingUtxos skelUnbal fee return (attemptedSkel, fee, adjustedCollateralIns) @@ -142,22 +152,22 @@ getMinAndMaxFee = do -- | Computes optimal fee for a given skeleton and balances it around those fees. -- This uses a dichotomic search for an optimal "balanceable around" fee. -computeFeeAndBalance :: (MonadBlockChainBalancing m) => Wallet -> Fee -> Fee -> Collaterals -> BalancingOutputs -> Wallet -> TxSkel -> m (TxSkel, Fee, Collaterals) -computeFeeAndBalance _ minFee maxFee _ _ _ _ +computeFeeAndBalance :: (MonadBlockChainBalancing m) => Bool -> Wallet -> Fee -> Fee -> Collaterals -> BalancingOutputs -> Wallet -> TxSkel -> m (TxSkel, Fee, Collaterals) +computeFeeAndBalance _ _ minFee maxFee _ _ _ _ | minFee > maxFee = throwError $ FailWith "Unreachable case, please report a bug at https://github.com/tweag/cooked-validators/issues" -computeFeeAndBalance balancingWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skel +computeFeeAndBalance requireCollaterals balancingWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skel | minFee == maxFee = do -- The fee interval is reduced to a single element, we balance around it - (adjustedCollateralIns, attemptedSkel) <- attemptBalancingAndCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet minFee skel + (adjustedCollateralIns, attemptedSkel) <- attemptBalancingAndCollaterals requireCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet minFee skel return (attemptedSkel, minFee, adjustedCollateralIns) -computeFeeAndBalance balancingWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skel +computeFeeAndBalance requireCollaterals balancingWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skel | fee <- (minFee + maxFee) `div` 2 = do -- The fee interval is larger than a single element. We attempt to balance -- around its central point, which can fail due to missing value in -- balancing utxos or collateral utxos. attemptedBalancing <- catchError - (Just <$> attemptBalancingAndCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet fee skel) + (Just <$> attemptBalancingAndCollaterals requireCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet fee skel) $ \case -- If it fails, and the remaining fee interval is not reduced to the -- current fee attempt, we return `Nothing` which signifies that we @@ -192,13 +202,13 @@ computeFeeAndBalance balancingWallet minFee maxFee collateralIns balancingUtxos -- fee of the input skeleton. _ -> (minFee, newFee) - computeFeeAndBalance balancingWallet newMinFee newMaxFee collateralIns balancingUtxos returnCollateralWallet skel + computeFeeAndBalance requireCollaterals balancingWallet newMinFee newMaxFee collateralIns balancingUtxos returnCollateralWallet skel -- | Helper function to group the two real steps of the balancing: balance a -- skeleton around a given fee, and compute the associated collateral inputs -attemptBalancingAndCollaterals :: (MonadBlockChainBalancing m) => Wallet -> Collaterals -> BalancingOutputs -> Wallet -> Fee -> TxSkel -> m (Collaterals, TxSkel) -attemptBalancingAndCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet fee skel = do - adjustedCollateralIns <- collateralInsFromFees fee collateralIns returnCollateralWallet +attemptBalancingAndCollaterals :: (MonadBlockChainBalancing m) => Bool -> Wallet -> Collaterals -> BalancingOutputs -> Wallet -> Fee -> TxSkel -> m (Collaterals, TxSkel) +attemptBalancingAndCollaterals requireCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet fee skel = do + adjustedCollateralIns <- collateralInsFromFees requireCollaterals fee collateralIns returnCollateralWallet attemptedSkel <- computeBalancedTxSkel balancingWallet balancingUtxos skel fee return (adjustedCollateralIns, attemptedSkel) @@ -206,8 +216,8 @@ attemptBalancingAndCollaterals balancingWallet collateralIns balancingUtxos retu -- accounting for the ratio to respect between fees and total collaterals, the -- min ada requirements in the associated return collateral and the maximum -- number of collateral inputs authorized by protocol parameters. -collateralInsFromFees :: (MonadBlockChainBalancing m) => Fee -> Collaterals -> Wallet -> m Collaterals -collateralInsFromFees fee collateralIns returnCollateralWallet = do +collateralInsFromFees :: (MonadBlockChainBalancing m) => Bool -> Fee -> Collaterals -> Wallet -> m Collaterals +collateralInsFromFees True fee collateralIns returnCollateralWallet = do -- We retrieve the max number of collateral inputs, with a default of 10. In -- practice this will be around 3. nbMax <- toInteger . fromMaybe 10 . Cardano.protocolParamMaxCollateralInputs . Emulator.pProtocolParams <$> getParams @@ -225,6 +235,7 @@ collateralInsFromFees fee collateralIns returnCollateralWallet = do let noSuitableCollateralError = MCENoSuitableCollateral fee percentage totalCollateral -- Retrieving and returning the best candidate as a utxo set Set.fromList . fst <$> getOptimalCandidate candidatesRaw returnCollateralWallet noSuitableCollateralError +collateralInsFromFees False _ _ _ = return Set.empty -- | The main computing function for optimal balancing and collaterals. It -- computes the subsets of a set of UTxOs that sum up to a certain target. It diff --git a/src/Cooked/MockChain/GenerateTx/Body.hs b/src/Cooked/MockChain/GenerateTx/Body.hs index bbfb958be..3ac292d96 100644 --- a/src/Cooked/MockChain/GenerateTx/Body.hs +++ b/src/Cooked/MockChain/GenerateTx/Body.hs @@ -10,11 +10,13 @@ import Cooked.MockChain.GenerateTx.Input qualified as Input import Cooked.MockChain.GenerateTx.Mint qualified as Mint import Cooked.MockChain.GenerateTx.Output qualified as Output import Cooked.MockChain.GenerateTx.Proposal qualified as Proposal +import Cooked.Output import Cooked.Skeleton import Cooked.Wallet import Data.Bifunctor import Data.Map (Map) import Data.Map qualified as Map +import Data.Maybe import Data.Set (Set) import Ledger.Address qualified as Ledger import Ledger.Tx qualified as Ledger @@ -63,11 +65,19 @@ txSkelToBodyContent skel@TxSkel {..} | txSkelReferenceInputs <- txSkelReferenceT "txSkelToBodyContent: Unable to translate reference inputs." (Cardano.TxInsReference Cardano.BabbageEraOnwardsConway) $ mapM Ledger.toCardanoTxIn txSkelReferenceInputs - (txInsCollateral, txTotalCollateral, txReturnCollateral) <- liftTxGen Collateral.toCollateralTriplet + (txInsCollateral, txTotalCollateral, txReturnCollateral) <- do + refs <- asks managedTxOuts + txOuts <- forM (Map.keys txSkelIns) $ flip (throwOnLookup "txSkelToBodyContent: Unable to resolve input utxo.") refs + if not $ + null (mapMaybe isScriptOutput txOuts) + && Map.null txSkelMints + && null (mapMaybe txSkelProposalWitness txSkelProposals) + then liftTxGen Collateral.toCollateralTriplet + else return (Cardano.TxInsCollateralNone, Cardano.TxTotalCollateralNone, Cardano.TxReturnCollateralNone) txOuts <- mapM (liftTxGen . Output.toCardanoTxOut) txSkelOuts (txValidityLowerBound, txValidityUpperBound) <- throwOnToCardanoError - "txSkelToBodyContent: Unable to translate transaction validity range" + "txSkelToBodyContent: Unable to translate transaction validity range." $ Ledger.toCardanoValidityRange txSkelValidityRange txMintValue <- liftTxGen $ Mint.toMintValue txSkelMints txExtraKeyWits <- @@ -89,7 +99,7 @@ txSkelToBodyContent skel@TxSkel {..} | txSkelReferenceInputs <- txSkelReferenceT txCertificates = Cardano.TxCertificatesNone -- That's what plutus-apps does as well txUpdateProposal = Cardano.TxUpdateProposalNone -- That's what plutus-apps does as well txScriptValidity = Cardano.TxScriptValidityNone -- That's what plutus-apps does as well - txVotingProcedures = Nothing -- TODO, same as above + txVotingProcedures = Nothing return Cardano.TxBodyContent {..} -- | Generates a transaction for a skeleton. We first generate a body and we diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index dcf08c95b..2b4d81097 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -80,7 +80,11 @@ testingBalancingTemplate toBobValue toAliceValue spendSearch balanceSearch colla then CollateralUtxosFromBalancingWallet else CollateralUtxosFromSet (Set.fromList toCollateralUtxos) alice }, - txSkelSigners = [alice] + txSkelSigners = [alice], + txSkelProposals = + [ simpleTxSkelProposal alice (TxGovActionTreasuryWithdrawals Map.empty) + `withWitness` (alwaysTrueProposingValidator, TxSkelNoRedeemer) + ] } (skel', fee, cols, _) <- balanceTxSkel skel void $ validateTxSkel skel @@ -132,6 +136,10 @@ noBalanceMaxFee = do txOptFeePolicy = AutoFeeComputation, txOptCollateralUtxos = CollateralUtxosFromSet (Set.singleton txOutRef) alice }, + txSkelProposals = + [ simpleTxSkelProposal alice (TxGovActionTreasuryWithdrawals Map.empty) + `withWitness` (alwaysTrueProposingValidator, TxSkelNoRedeemer) + ], txSkelSigners = [alice] } @@ -166,7 +174,11 @@ reachingMagic = do txSkelOpts = def { txOptBalancingUtxos = BalancingUtxosFromSet (Set.fromList bananaOutRefs) - } + }, + txSkelProposals = + [ simpleTxSkelProposal alice (TxGovActionTreasuryWithdrawals Map.empty) + `withWitness` (alwaysTrueProposingValidator, TxSkelNoRedeemer) + ] } type ResProp prop = TestBalancingOutcome -> prop From 817c777023f27833ad2182cfc365b4c9a671e5d9 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 18:45:34 +0200 Subject: [PATCH 14/71] reverting balancingspec --- tests/Cooked/BalancingSpec.hs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index 2b4d81097..dcf08c95b 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -80,11 +80,7 @@ testingBalancingTemplate toBobValue toAliceValue spendSearch balanceSearch colla then CollateralUtxosFromBalancingWallet else CollateralUtxosFromSet (Set.fromList toCollateralUtxos) alice }, - txSkelSigners = [alice], - txSkelProposals = - [ simpleTxSkelProposal alice (TxGovActionTreasuryWithdrawals Map.empty) - `withWitness` (alwaysTrueProposingValidator, TxSkelNoRedeemer) - ] + txSkelSigners = [alice] } (skel', fee, cols, _) <- balanceTxSkel skel void $ validateTxSkel skel @@ -136,10 +132,6 @@ noBalanceMaxFee = do txOptFeePolicy = AutoFeeComputation, txOptCollateralUtxos = CollateralUtxosFromSet (Set.singleton txOutRef) alice }, - txSkelProposals = - [ simpleTxSkelProposal alice (TxGovActionTreasuryWithdrawals Map.empty) - `withWitness` (alwaysTrueProposingValidator, TxSkelNoRedeemer) - ], txSkelSigners = [alice] } @@ -174,11 +166,7 @@ reachingMagic = do txSkelOpts = def { txOptBalancingUtxos = BalancingUtxosFromSet (Set.fromList bananaOutRefs) - }, - txSkelProposals = - [ simpleTxSkelProposal alice (TxGovActionTreasuryWithdrawals Map.empty) - `withWitness` (alwaysTrueProposingValidator, TxSkelNoRedeemer) - ] + } } type ResProp prop = TestBalancingOutcome -> prop From c59f14cc5f687d2c4d37cd7c6fd450f288bffcdd Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 10 Jul 2024 19:09:22 +0200 Subject: [PATCH 15/71] starting to consume scripts in balancing spec, to be continued --- tests/Cooked/BalancingSpec.hs | 81 +++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index dcf08c95b..536628aca 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -7,6 +7,7 @@ import Cooked.MockChain.GenerateTx import Cooked.MockChain.Staged import Data.Default import Data.List qualified as List +import Data.Map (Map) import Data.Map qualified as Map import Data.Maybe import Data.Set @@ -33,7 +34,8 @@ banana = permanentValue "banana" initialDistributionBalancing :: InitialDistribution initialDistributionBalancing = InitialDistribution - [ paysPK alice (Script.ada 2 <> apple 3), + [ paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42), + paysPK alice (Script.ada 2 <> apple 3), paysPK alice (Script.ada 25), paysPK alice (Script.ada 40 <> orange 6), paysPK alice (Script.ada 8), @@ -45,6 +47,12 @@ initialDistributionBalancing = type TestBalancingOutcome = (TxSkel, TxSkel, Integer, Set Api.TxOutRef, [Api.TxOutRef]) +spendsScriptUtxo :: (MonadBlockChain m) => Bool -> m (Map Api.TxOutRef TxSkelRedeemer) +spendsScriptUtxo False = return Map.empty +spendsScriptUtxo True = do + (scriptOutRef, _) : _ <- runUtxoSearch $ utxosAtSearch $ alwaysTrueValidator @MockContract + return $ Map.singleton scriptOutRef txSkelEmptyRedeemer + testingBalancingTemplate :: (MonadBlockChain m) => -- Value to pay to bob @@ -57,17 +65,20 @@ testingBalancingTemplate :: UtxoSearch m b -> -- Search for utxos to be used for collaterals UtxoSearch m c -> + -- Whether to consum the script utxo + Bool -> -- Option modifications (TxOpts -> TxOpts) -> m TestBalancingOutcome -testingBalancingTemplate toBobValue toAliceValue spendSearch balanceSearch collateralSearch optionsMod = do +testingBalancingTemplate toBobValue toAliceValue spendSearch balanceSearch collateralSearch consumeScriptUtxo optionsMod = do ((fst <$>) -> toSpendUtxos) <- runUtxoSearch spendSearch ((fst <$>) -> toBalanceUtxos) <- runUtxoSearch balanceSearch ((fst <$>) -> toCollateralUtxos) <- runUtxoSearch collateralSearch + additionalSpend <- spendsScriptUtxo consumeScriptUtxo let skel = txSkelTemplate { txSkelOuts = List.filter ((/= mempty) . (^. txSkelOutValueL)) [paysPK bob toBobValue, paysPK alice toAliceValue], - txSkelIns = Map.fromList $ (,txSkelEmptyRedeemer) <$> toSpendUtxos, + txSkelIns = additionalSpend <> Map.fromList ((,txSkelEmptyRedeemer) <$> toSpendUtxos), txSkelOpts = optionsMod def @@ -99,7 +110,7 @@ aliceRefScriptUtxos = utxosAtSearch alice `filterWithPred` \o -> isJust (Api.txO emptySearch :: (MonadBlockChain m) => UtxoSearch m Api.TxOut emptySearch = ListT.fromFoldable [] -simplePaymentToBob :: (MonadBlockChain m) => Integer -> Integer -> Integer -> Integer -> (TxOpts -> TxOpts) -> m TestBalancingOutcome +simplePaymentToBob :: (MonadBlockChain m) => Integer -> Integer -> Integer -> Integer -> Bool -> (TxOpts -> TxOpts) -> m TestBalancingOutcome simplePaymentToBob lv apples oranges bananas = testingBalancingTemplate (Script.lovelace lv <> apple apples <> orange oranges <> banana bananas) @@ -108,7 +119,7 @@ simplePaymentToBob lv apples oranges bananas = emptySearch emptySearch -bothPaymentsToBobAndAlice :: (MonadBlockChain m) => Integer -> (TxOpts -> TxOpts) -> m TestBalancingOutcome +bothPaymentsToBobAndAlice :: (MonadBlockChain m) => Integer -> Bool -> (TxOpts -> TxOpts) -> m TestBalancingOutcome bothPaymentsToBobAndAlice val = testingBalancingTemplate (Script.lovelace val) @@ -238,23 +249,27 @@ tests = [ testBalancingFailsWith "Balancing does not occur when not requested, fails with empty inputs" failsWithEmptyTxIns - (simplePaymentToBob 20_000_000 0 0 0 (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), + (simplePaymentToBob 20_000_000 0 0 0 False (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), testBalancingFailsWith "Balancing does not occur when not requested, fails with too small inputs" failsWithValueNotConserved - (testingBalancingTemplate (Script.ada 50) mempty (aliceNAdaUtxos 8) emptySearch emptySearch (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), + (testingBalancingTemplate (Script.ada 50) mempty (aliceNAdaUtxos 8) emptySearch emptySearch False (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), testBalancingSucceedsWith - "It is still possible to balance the transaction by hand" - [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch emptySearch (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), + "It is possible to balance the transaction by hand without collaterals" + [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 0, retOutsNb 3] + (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch emptySearch False (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), + testBalancingSucceedsWith + "It is also possible to balance the transaction by hand with collaterals" + [hasFee 1_000_000, insNb 2, additionalOutsNb 0, colInsNb 1, retOutsNb 3] + (testingBalancingTemplate (Script.ada 49) mempty (aliceNAdaUtxos 8) emptySearch emptySearch True (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), testBalancingFailsWith "A collateral wallet needs to be provided when auto balancing is enabled" failsLackOfCollateralWallet - (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch emptySearch (setDontBalance . setFixedFee 1_000_000)), + (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch emptySearch False (setDontBalance . setFixedFee 1_000_000)), testBalancingSucceedsWith "We can also directly give a set of collateral utxos" [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch (aliceNAdaUtxos 8) (setDontBalance . setFixedFee 1_000_000)) + (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch (aliceNAdaUtxos 8) False (setDontBalance . setFixedFee 1_000_000)) ], testGroup "Manual balancing with auto fee" @@ -266,7 +281,7 @@ tests = [ testBalancingSucceedsWith "We can auto balance a transaction with auto fee" [insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 20_000_000 0 0 0 id), + (simplePaymentToBob 20_000_000 0 0 0 False id), testCase "Auto fee are minimal: less fee will lead to strictly smaller fee than Cardano's estimate" $ testSucceedsFrom' def @@ -274,20 +289,20 @@ tests = initialDistributionBalancing balanceReduceFee, testCase "The auto-fee process can sometimes recover from a temporary balancing error..." $ - testSucceedsFrom def initialDistributionBalancing (simplePaymentToBob 103_500_000 0 0 0 id), + testSucceedsFrom def initialDistributionBalancing (simplePaymentToBob 103_500_000 0 0 0 False id), testCase "... but not always" $ - testFailsFrom def failsAtBalancing initialDistributionBalancing (simplePaymentToBob 104_000_000 0 0 0 id), + testFailsFrom def failsAtBalancing initialDistributionBalancing (simplePaymentToBob 104_000_000 0 0 0 False id), testCase "The auto-fee process can recover from a temporary collateral error..." $ testSucceedsFrom def initialDistributionBalancing - (testingBalancingTemplate (Script.ada 100) mempty emptySearch emptySearch (aliceNAdaUtxos 2) id), + (testingBalancingTemplate (Script.ada 100) mempty emptySearch emptySearch (aliceNAdaUtxos 2) False id), testCase "... but not always" $ testFailsFrom def failsAtCollaterals initialDistributionBalancing - (testingBalancingTemplate (Script.ada 100) mempty (utxosAtSearch alice) emptySearch (aliceNAdaUtxos 1) id), + (testingBalancingTemplate (Script.ada 100) mempty (utxosAtSearch alice) emptySearch (aliceNAdaUtxos 1) False id), testCase "Reaching magical spot with the exact balance during auto fee computation" $ testSucceedsFrom def @@ -299,66 +314,66 @@ tests = [ testBalancingSucceedsWith "We can use a single utxo for balancing purpose" [hasFee 1_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 20_000_000 0 0 0 (setFixedFee 1_000_000)), + (simplePaymentToBob 20_000_000 0 0 0 False (setFixedFee 1_000_000)), testBalancingSucceedsWith "We can use several utxos for balancing with ridiculously high fee" [hasFee 40_000_000, insNb 3, additionalOutsNb 1, colInsNb 3, retOutsNb 3] - (simplePaymentToBob 20_000_000 0 0 0 (setFixedFee 40_000_000)), + (simplePaymentToBob 20_000_000 0 0 0 False (setFixedFee 40_000_000)), testBalancingFailsWith "We cannot balance with too little fee" failsWithTooLittleFee - (simplePaymentToBob 20_000_000 0 0 0 (setFixedFee 150_000)), + (simplePaymentToBob 20_000_000 0 0 0 False (setFixedFee 150_000)), testBalancingFailsWith "Fee are rightfully included in the balancing process, which fails when they are too high" (failsAtBalancingWith (Script.ada 1) alice) - (simplePaymentToBob 100_000_000 0 0 0 (setFixedFee 6_000_000)), + (simplePaymentToBob 100_000_000 0 0 0 False (setFixedFee 6_000_000)), testBalancingFailsWith "Collaterals are rightfully included in the balancing process, which fails when they are too high" (failsAtCollateralsWith 80_000_000) - (simplePaymentToBob 6_000_000 0 0 0 (setFixedFee 80_000_000)), + (simplePaymentToBob 6_000_000 0 0 0 False (setFixedFee 80_000_000)), testBalancingSucceedsWith "Exactly the right amount leads to no output change" [hasFee 2_000_000, insNb 3, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 65_000_000 3 6 0 (setFixedFee 2_000_000)), + (simplePaymentToBob 65_000_000 3 6 0 False (setFixedFee 2_000_000)), testBalancingSucceedsWith "It still leads to no output change when requesting a new output" [hasFee 2_000_000, insNb 3, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 65_000_000 3 6 0 (setDontAdjustOutput . setFixedFee 2_000_000)), + (simplePaymentToBob 65_000_000 3 6 0 False (setDontAdjustOutput . setFixedFee 2_000_000)), testBalancingSucceedsWith "1 lovelace more than the exact right amount leads to an additional output" [hasFee 2_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 65_000_001 3 6 0 (setFixedFee 2_000_000)), + (simplePaymentToBob 65_000_001 3 6 0 False (setFixedFee 2_000_000)), testBalancingSucceedsWith "1 lovelace less than the exact right amount leads to an additional output to account for minAda" [hasFee 2_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 65_000_001 3 6 0 (setFixedFee 2_000_000)), + (simplePaymentToBob 65_000_001 3 6 0 False (setFixedFee 2_000_000)), testBalancingSucceedsWith "We can merge assets to an existing outputs at the balancing wallet address" [hasFee 2_000_000, insNb 1, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (bothPaymentsToBobAndAlice 6_000_000 (setFixedFee 2_000_000)), + (bothPaymentsToBobAndAlice 6_000_000 False (setFixedFee 2_000_000)), testBalancingSucceedsWith "We can create a new output at the balancing wallet address even if one already exists" [hasFee 2_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (bothPaymentsToBobAndAlice 6_000_000 (setFixedFee 2_000_000 . setDontAdjustOutput)), + (bothPaymentsToBobAndAlice 6_000_000 False (setFixedFee 2_000_000 . setDontAdjustOutput)), testBalancingSucceedsWith "We can balance transactions with non-Script.ada assets" [hasFee 2_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 0 0 5 0 (setFixedFee 2_000_000 . setEnsureMinAda)), + (simplePaymentToBob 0 0 5 0 False (setFixedFee 2_000_000 . setEnsureMinAda)), testBalancingSucceedsWith "Successful balancing with multiple assets" [hasFee 1_000_000, insNb 2, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 0 2 5 0 (setEnsureMinAda . setFixedFee 1_000_000)), + (simplePaymentToBob 0 2 5 0 False (setEnsureMinAda . setFixedFee 1_000_000)), testBalancingFailsWith "Unsuccessful balancing with multiple assets in non value only utxos" (failsAtBalancingWith (banana 4) alice) - (simplePaymentToBob 0 2 5 4 (setEnsureMinAda . setFixedFee 1_000_000)), + (simplePaymentToBob 0 2 5 4 False (setEnsureMinAda . setFixedFee 1_000_000)), testBalancingSucceedsWith "Successful balancing with multiple assets and explicit utxo set, reference script is lost" [hasFee 1_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 2] - (testingBalancingTemplate (apple 2 <> orange 5 <> banana 4) mempty emptySearch (utxosAtSearch alice) emptySearch (setEnsureMinAda . setFixedFee 1_000_000)), + (testingBalancingTemplate (apple 2 <> orange 5 <> banana 4) mempty emptySearch (utxosAtSearch alice) emptySearch False (setEnsureMinAda . setFixedFee 1_000_000)), testBalancingSucceedsWith "Successful balancing with excess initial consumption" [hasFee 1_000_000, insNb 5, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (testingBalancingTemplate mempty mempty (onlyValueOutputsAtSearch alice) emptySearch emptySearch (setFixedFee 1_000_000)) + (testingBalancingTemplate mempty mempty (onlyValueOutputsAtSearch alice) emptySearch emptySearch False (setFixedFee 1_000_000)) ] ] From 96b3250e6c08fb7878369e2f401d36433c13760d Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 11 Jul 2024 12:39:24 +0200 Subject: [PATCH 16/71] reworking empty collaterals --- src/Cooked/MockChain/Balancing.hs | 100 ++++--- src/Cooked/MockChain/BlockChain.hs | 2 +- src/Cooked/MockChain/Direct.hs | 10 +- src/Cooked/MockChain/GenerateTx.hs | 16 +- src/Cooked/MockChain/GenerateTx/Body.hs | 15 +- src/Cooked/MockChain/GenerateTx/Collateral.hs | 118 ++++---- tests/Cooked/BalancingSpec.hs | 275 +++++++++++++++--- tests/Cooked/ShowBSSpec.hs | 2 +- 8 files changed, 373 insertions(+), 165 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index f2ee56c43..8f2f649e4 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -43,14 +43,17 @@ type Fee = Integer type Collaterals = Set Api.TxOutRef +type MCollaterals = Maybe (Collaterals, Wallet) + type BalancingOutputs = [(Api.TxOutRef, Api.TxOut)] -- | This is the main entry point of our balancing mechanism. This function -- takes a skeleton and returns a (possibly) balanced skeleton alongside the --- associated fee, collateral inputs and return collateral wallet. The options --- from the skeleton control whether it should be balanced, and how to compute --- its associated elements. -balanceTxSkel :: (MonadBlockChainBalancing m) => TxSkel -> m (TxSkel, Fee, Collaterals, Wallet) +-- associated fee, collateral inputs and return collateral wallet, which might +-- be empty when no script is involved in the transaction. The options from the +-- skeleton control whether it should be balanced, and how to compute its +-- associated elements. +balanceTxSkel :: (MonadBlockChainBalancing m) => TxSkel -> m (TxSkel, Fee, MCollaterals) balanceTxSkel skelUnbal@TxSkel {..} = do -- We retrieve the possible balancing wallet. Any extra payment will be -- redirected to them, and utxos will be taken from their wallet if associated @@ -68,29 +71,34 @@ balanceTxSkel skelUnbal@TxSkel {..} = do -- We collect collateral inputs candidates. They might be directly provided in -- the skeleton, or should be retrieved from a given wallet. They are - -- associated with a return collateral wallet, which we retrieve as well. - (collateralIns, returnCollateralWallet) <- case txOptCollateralUtxos txSkelOpts of - CollateralUtxosFromBalancingWallet -> case balancingWallet of - Nothing -> fail "Can't select collateral utxos from a balancing wallet because it does not exist." - Just bWallet -> (,bWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch bWallet) - CollateralUtxosFromWallet cWallet -> (,cWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch cWallet) - CollateralUtxosFromSet utxos rWallet -> return (utxos, rWallet) - - -- The transaction will only require collaterals when involving scripts. - requireCollaterals <- do - insValidators <- txSkelInputValidators skelUnbal - return $ not $ Map.null txSkelMints && null (mapMaybe txSkelProposalWitness txSkelProposals) && Map.null insValidators + -- associated with a return collateral wallet, which we retrieve as well. All + -- of this is wrapped in a `Maybe` type to represent the case when the + -- transaction does not involve script and should not have any kind of + -- collaterals attached to it. + mCollaterals <- do + -- We retrieve the various kinds of scripts + spendingScripts <- txSkelInputValidators skelUnbal + -- The transaction will only require collaterals when involving scripts + if Map.null txSkelMints && null (mapMaybe txSkelProposalWitness txSkelProposals) && Map.null spendingScripts + then return Nothing + else + Just <$> case txOptCollateralUtxos txSkelOpts of + CollateralUtxosFromBalancingWallet -> case balancingWallet of + Nothing -> fail "Can't select collateral utxos from a balancing wallet because it does not exist." + Just bWallet -> (,bWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch bWallet) + CollateralUtxosFromWallet cWallet -> (,cWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch cWallet) + CollateralUtxosFromSet utxos rWallet -> return (utxos, rWallet) -- At this point, the presence (or absence) of balancing wallet dictates -- whether the transaction should be automatically balanced or not. - (txSkelBal, fee, adjustedCollateralIns) <- case balancingWallet of + (txSkelBal, fee, adjustedMCollaterals) <- case balancingWallet of Nothing -> -- The balancing should not be performed. We still adjust the collaterals -- though around a provided fee, or the maximum fee. let fee = case txOptFeePolicy txSkelOpts of AutoFeeComputation -> maxFee ManualFee fee' -> fee' - in (skelUnbal,fee,) <$> collateralInsFromFees requireCollaterals fee collateralIns returnCollateralWallet + in (skelUnbal,fee,) <$> collateralsFromFees fee mCollaterals Just bWallet -> do -- The balancing should be performed. We collect the candidates balancing -- utxos based on the associated policy @@ -111,15 +119,15 @@ balanceTxSkel skelUnbal@TxSkel {..} = do -- If fees are left for us to compute, we run a dichotomic search. This -- is full auto mode, the most powerful but time-consuming. AutoFeeComputation -> - computeFeeAndBalance requireCollaterals bWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skelUnbal + computeFeeAndBalance bWallet minFee maxFee balancingUtxos mCollaterals skelUnbal -- If fee are provided manually, we adjust the collaterals and the -- skeleton around them directly. ManualFee fee -> do - adjustedCollateralIns <- collateralInsFromFees requireCollaterals fee collateralIns returnCollateralWallet + adjustedMCollaterals <- collateralsFromFees fee mCollaterals attemptedSkel <- computeBalancedTxSkel bWallet balancingUtxos skelUnbal fee - return (attemptedSkel, fee, adjustedCollateralIns) + return (attemptedSkel, fee, adjustedMCollaterals) - return (txSkelBal, fee, adjustedCollateralIns, returnCollateralWallet) + return (txSkelBal, fee, adjustedMCollaterals) where filterAndWarn f s l | (ok, toInteger . length -> koLength) <- partition f l = @@ -152,22 +160,22 @@ getMinAndMaxFee = do -- | Computes optimal fee for a given skeleton and balances it around those fees. -- This uses a dichotomic search for an optimal "balanceable around" fee. -computeFeeAndBalance :: (MonadBlockChainBalancing m) => Bool -> Wallet -> Fee -> Fee -> Collaterals -> BalancingOutputs -> Wallet -> TxSkel -> m (TxSkel, Fee, Collaterals) -computeFeeAndBalance _ _ minFee maxFee _ _ _ _ +computeFeeAndBalance :: (MonadBlockChainBalancing m) => Wallet -> Fee -> Fee -> BalancingOutputs -> MCollaterals -> TxSkel -> m (TxSkel, Fee, MCollaterals) +computeFeeAndBalance _ minFee maxFee _ _ _ | minFee > maxFee = throwError $ FailWith "Unreachable case, please report a bug at https://github.com/tweag/cooked-validators/issues" -computeFeeAndBalance requireCollaterals balancingWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skel +computeFeeAndBalance balancingWallet minFee maxFee balancingUtxos mCollaterals skel | minFee == maxFee = do -- The fee interval is reduced to a single element, we balance around it - (adjustedCollateralIns, attemptedSkel) <- attemptBalancingAndCollaterals requireCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet minFee skel - return (attemptedSkel, minFee, adjustedCollateralIns) -computeFeeAndBalance requireCollaterals balancingWallet minFee maxFee collateralIns balancingUtxos returnCollateralWallet skel + (adjustedMCollaterals, attemptedSkel) <- attemptBalancingAndCollaterals balancingWallet balancingUtxos minFee mCollaterals skel + return (attemptedSkel, minFee, adjustedMCollaterals) +computeFeeAndBalance balancingWallet minFee maxFee balancingUtxos mCollaterals skel | fee <- (minFee + maxFee) `div` 2 = do -- The fee interval is larger than a single element. We attempt to balance -- around its central point, which can fail due to missing value in -- balancing utxos or collateral utxos. attemptedBalancing <- catchError - (Just <$> attemptBalancingAndCollaterals requireCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet fee skel) + (Just <$> attemptBalancingAndCollaterals balancingWallet balancingUtxos fee mCollaterals skel) $ \case -- If it fails, and the remaining fee interval is not reduced to the -- current fee attempt, we return `Nothing` which signifies that we @@ -182,8 +190,8 @@ computeFeeAndBalance requireCollaterals balancingWallet minFee maxFee collateral Nothing -> return (minFee, fee - 1) -- The skeleton was balanceable, we compute and analyse the resulting -- fee to seach upwards or downwards for an optimal solution - Just (adjustedCollateralIns, attemptedSkel) -> do - newFee <- estimateTxSkelFee attemptedSkel fee adjustedCollateralIns returnCollateralWallet + Just (adjustedMCollaterals, attemptedSkel) -> do + newFee <- estimateTxSkelFee attemptedSkel fee adjustedMCollaterals return $ case fee - newFee of -- Current fee is insufficient, we look on the right (strictly) n | n < 0 -> (fee + 1, maxFee) @@ -202,13 +210,13 @@ computeFeeAndBalance requireCollaterals balancingWallet minFee maxFee collateral -- fee of the input skeleton. _ -> (minFee, newFee) - computeFeeAndBalance requireCollaterals balancingWallet newMinFee newMaxFee collateralIns balancingUtxos returnCollateralWallet skel + computeFeeAndBalance balancingWallet newMinFee newMaxFee balancingUtxos mCollaterals skel -- | Helper function to group the two real steps of the balancing: balance a -- skeleton around a given fee, and compute the associated collateral inputs -attemptBalancingAndCollaterals :: (MonadBlockChainBalancing m) => Bool -> Wallet -> Collaterals -> BalancingOutputs -> Wallet -> Fee -> TxSkel -> m (Collaterals, TxSkel) -attemptBalancingAndCollaterals requireCollaterals balancingWallet collateralIns balancingUtxos returnCollateralWallet fee skel = do - adjustedCollateralIns <- collateralInsFromFees requireCollaterals fee collateralIns returnCollateralWallet +attemptBalancingAndCollaterals :: (MonadBlockChainBalancing m) => Wallet -> BalancingOutputs -> Fee -> MCollaterals -> TxSkel -> m (MCollaterals, TxSkel) +attemptBalancingAndCollaterals balancingWallet balancingUtxos fee mCollaterals skel = do + adjustedCollateralIns <- collateralsFromFees fee mCollaterals attemptedSkel <- computeBalancedTxSkel balancingWallet balancingUtxos skel fee return (adjustedCollateralIns, attemptedSkel) @@ -216,8 +224,8 @@ attemptBalancingAndCollaterals requireCollaterals balancingWallet collateralIns -- accounting for the ratio to respect between fees and total collaterals, the -- min ada requirements in the associated return collateral and the maximum -- number of collateral inputs authorized by protocol parameters. -collateralInsFromFees :: (MonadBlockChainBalancing m) => Bool -> Fee -> Collaterals -> Wallet -> m Collaterals -collateralInsFromFees True fee collateralIns returnCollateralWallet = do +collateralInsFromFees :: (MonadBlockChainBalancing m) => Fee -> Collaterals -> Wallet -> m Collaterals +collateralInsFromFees fee collateralIns returnCollateralWallet = do -- We retrieve the max number of collateral inputs, with a default of 10. In -- practice this will be around 3. nbMax <- toInteger . fromMaybe 10 . Cardano.protocolParamMaxCollateralInputs . Emulator.pProtocolParams <$> getParams @@ -235,7 +243,12 @@ collateralInsFromFees True fee collateralIns returnCollateralWallet = do let noSuitableCollateralError = MCENoSuitableCollateral fee percentage totalCollateral -- Retrieving and returning the best candidate as a utxo set Set.fromList . fst <$> getOptimalCandidate candidatesRaw returnCollateralWallet noSuitableCollateralError -collateralInsFromFees False _ _ _ = return Set.empty + +-- | This adjusts collateral inputs when necessary +collateralsFromFees :: (MonadBlockChainBalancing m) => Fee -> MCollaterals -> m MCollaterals +collateralsFromFees _ Nothing = return Nothing +collateralsFromFees fee (Just (collateralIns, returnCollateralWallet)) = + Just . (,returnCollateralWallet) <$> collateralInsFromFees fee collateralIns returnCollateralWallet -- | The main computing function for optimal balancing and collaterals. It -- computes the subsets of a set of UTxOs that sum up to a certain target. It @@ -281,15 +294,18 @@ getOptimalCandidate candidates paymentTarget mceError = do -- | This function is essentially a copy of -- https://github.com/input-output-hk/plutus-apps/blob/d4255f05477fd8477ee9673e850ebb9ebb8c9657/plutus-ledger/src/Ledger/Fee.hs#L19 -estimateTxSkelFee :: (MonadBlockChainBalancing m) => TxSkel -> Fee -> Collaterals -> Wallet -> m Fee -estimateTxSkelFee skel fee collateralIns returnCollateralWallet = do +estimateTxSkelFee :: (MonadBlockChainBalancing m) => TxSkel -> Fee -> MCollaterals -> m Fee +estimateTxSkelFee skel fee mCollaterals = do -- We retrieve the necessary data to generate the transaction body params <- getParams managedData <- txSkelInputData skel - managedTxOuts <- lookupUtxosPl $ txSkelKnownTxOutRefs skel <> Set.toList collateralIns + let collateralIns = case mCollaterals of + Nothing -> [] + Just (s, _) -> Set.toList s + managedTxOuts <- lookupUtxosPl $ txSkelKnownTxOutRefs skel <> collateralIns managedValidators <- txSkelInputValidators skel -- We generate the transaction body content, handling errors in the meantime - txBodyContent <- case generateBodyContent fee returnCollateralWallet collateralIns params managedData managedTxOuts managedValidators skel of + txBodyContent <- case generateBodyContent fee params managedData managedTxOuts managedValidators mCollaterals skel of Left err -> throwError $ MCEGenerationError err Right txBodyContent -> return txBodyContent -- We create the actual body and send if for validation diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 01828985d..ee7b5f0a0 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -121,7 +121,7 @@ data MockChainLogEntry where MCLogSubmittedTxSkel :: SkelContext -> TxSkel -> MockChainLogEntry -- | Logging a Skeleton as it has been adjusted by the balancing mechanism, -- alongside fee, collateral utxos and return collateral wallet. - MCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Set Api.TxOutRef -> Wallet -> MockChainLogEntry + MCLogAdjustedTxSkel :: SkelContext -> TxSkel -> Integer -> Maybe (Set Api.TxOutRef, Wallet) -> MockChainLogEntry -- | Logging the appearance of a new transaction, after a skeleton has been -- successfully sent for validation. MCLogNewTx :: Api.TxId -> MockChainLogEntry diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index 01c08fef6..122cdb784 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -348,9 +348,9 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where minAdaSkelUnbal <- if txOptEnsureMinAda . txSkelOpts $ skelUnbal then toTxSkelWithMinAda skelUnbal else return skelUnbal -- We balance the skeleton when requested in the skeleton option, and get -- the associated fee, collateral inputs and return collateral wallet - (skel, fee, collateralIns, returnCollateralWallet) <- balanceTxSkel minAdaSkelUnbal + (skel, fee, mCollaterals) <- balanceTxSkel minAdaSkelUnbal -- We log the adjusted skeleton - gets mcstToSkelContext >>= \ctx -> publish $ MCLogAdjustedTxSkel ctx skel fee collateralIns returnCollateralWallet + gets mcstToSkelContext >>= \ctx -> publish $ MCLogAdjustedTxSkel ctx skel fee mCollaterals -- We retrieve data that will be used in the transaction generation process: -- datums, validators and various kinds of inputs. This idea is to provide a -- rich-enough context for the transaction generation to succeed. @@ -358,11 +358,13 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where insValidators <- txSkelInputValidators skel insMap <- txSkelInputUtxosPl skel refInsMap <- txSkelReferenceInputUtxosPl skel - collateralInsMap <- lookupUtxosPl $ Set.toList collateralIns + collateralInsMap <- case mCollaterals of + Nothing -> return Map.empty + Just (collateralIns, _) -> lookupUtxosPl $ Set.toList collateralIns -- We attempt to generate the transaction associated with the balanced -- skeleton and the retrieved data. This is an internal generation, there is -- no validation involved yet. - cardanoTx <- case generateTx fee returnCollateralWallet collateralIns newParams insData (insMap <> refInsMap <> collateralInsMap) insValidators skel of + cardanoTx <- case generateTx fee newParams insData (insMap <> refInsMap <> collateralInsMap) insValidators mCollaterals skel of Left err -> throwError . MCEGenerationError $ err -- We apply post-generation modification when applicable Right tx -> return $ Ledger.CardanoEmulatorEraTx $ applyRawModOnBalancedTx (txOptUnsafeModTx . txSkelOpts $ skelUnbal) tx diff --git a/src/Cooked/MockChain/GenerateTx.hs b/src/Cooked/MockChain/GenerateTx.hs index 2220f486d..f4af14830 100644 --- a/src/Cooked/MockChain/GenerateTx.hs +++ b/src/Cooked/MockChain/GenerateTx.hs @@ -36,10 +36,6 @@ generateTxOut networkId txSkelOut = runReaderT (toCardanoTxOut txSkelOut) networ generateBodyContent :: -- | fee to apply to body generation Integer -> - -- | wallet to return collaterals to - Wallet -> - -- | collaterals to add to body generation - Set Api.TxOutRef -> -- | parameters of the emulator Emulator.Params -> -- | datums present in our environment @@ -48,10 +44,12 @@ generateBodyContent :: Map Api.TxOutRef Api.TxOut -> -- | validators present in our environment Map Script.ValidatorHash (Script.Versioned Script.Validator) -> + -- | Possible collaterals to use + Maybe (Set Api.TxOutRef, Wallet) -> -- | The skeleton to translate TxSkel -> Either GenerateTxError (Cardano.TxBodyContent Cardano.BuildTx Cardano.ConwayEra) -generateBodyContent fee returnCollateralWallet collateralIns params managedData managedTxOuts managedValidators = +generateBodyContent fee params managedData managedTxOuts managedValidators mCollaterals = flip runReaderT TxContext {..} . txSkelToBodyContent -- | Generates a transaction from a skeleton. Shares the same parameters as @@ -59,10 +57,6 @@ generateBodyContent fee returnCollateralWallet collateralIns params managedData generateTx :: -- | fee to apply to body generation Integer -> - -- | wallet to return collaterals to - Wallet -> - -- | collaterals to add to body generation - Set Api.TxOutRef -> -- | parameters of the emulator Emulator.Params -> -- | datums present in our environment @@ -71,8 +65,10 @@ generateTx :: Map Api.TxOutRef Api.TxOut -> -- | validators present in our environment Map Script.ValidatorHash (Script.Versioned Script.Validator) -> + -- | The collateral inputs and associated collateral wallet + Maybe (Set Api.TxOutRef, Wallet) -> -- | The skeleton to translate TxSkel -> Either GenerateTxError (Cardano.Tx Cardano.ConwayEra) -generateTx fee returnCollateralWallet collateralIns params managedData managedTxOuts managedValidators = +generateTx fee params managedData managedTxOuts managedValidators mCollaterals = flip runReaderT TxContext {..} . txSkelToCardanoTx diff --git a/src/Cooked/MockChain/GenerateTx/Body.hs b/src/Cooked/MockChain/GenerateTx/Body.hs index 3ac292d96..5d628c5f6 100644 --- a/src/Cooked/MockChain/GenerateTx/Body.hs +++ b/src/Cooked/MockChain/GenerateTx/Body.hs @@ -10,13 +10,11 @@ import Cooked.MockChain.GenerateTx.Input qualified as Input import Cooked.MockChain.GenerateTx.Mint qualified as Mint import Cooked.MockChain.GenerateTx.Output qualified as Output import Cooked.MockChain.GenerateTx.Proposal qualified as Proposal -import Cooked.Output import Cooked.Skeleton import Cooked.Wallet import Data.Bifunctor import Data.Map (Map) import Data.Map qualified as Map -import Data.Maybe import Data.Set (Set) import Ledger.Address qualified as Ledger import Ledger.Tx qualified as Ledger @@ -27,8 +25,7 @@ import PlutusLedgerApi.V3 qualified as Api data TxContext where TxContext :: { fee :: Integer, - collateralIns :: Set Api.TxOutRef, - returnCollateralWallet :: Wallet, + mCollaterals :: Maybe (Set Api.TxOutRef, Wallet), params :: Emulator.Params, managedData :: Map Api.DatumHash Api.Datum, managedTxOuts :: Map Api.TxOutRef Api.TxOut, @@ -65,15 +62,7 @@ txSkelToBodyContent skel@TxSkel {..} | txSkelReferenceInputs <- txSkelReferenceT "txSkelToBodyContent: Unable to translate reference inputs." (Cardano.TxInsReference Cardano.BabbageEraOnwardsConway) $ mapM Ledger.toCardanoTxIn txSkelReferenceInputs - (txInsCollateral, txTotalCollateral, txReturnCollateral) <- do - refs <- asks managedTxOuts - txOuts <- forM (Map.keys txSkelIns) $ flip (throwOnLookup "txSkelToBodyContent: Unable to resolve input utxo.") refs - if not $ - null (mapMaybe isScriptOutput txOuts) - && Map.null txSkelMints - && null (mapMaybe txSkelProposalWitness txSkelProposals) - then liftTxGen Collateral.toCollateralTriplet - else return (Cardano.TxInsCollateralNone, Cardano.TxTotalCollateralNone, Cardano.TxReturnCollateralNone) + (txInsCollateral, txTotalCollateral, txReturnCollateral) <- liftTxGen Collateral.toCollateralTriplet txOuts <- mapM (liftTxGen . Output.toCardanoTxOut) txSkelOuts (txValidityLowerBound, txValidityUpperBound) <- throwOnToCardanoError diff --git a/src/Cooked/MockChain/GenerateTx/Collateral.hs b/src/Cooked/MockChain/GenerateTx/Collateral.hs index b7df81534..28a854922 100644 --- a/src/Cooked/MockChain/GenerateTx/Collateral.hs +++ b/src/Cooked/MockChain/GenerateTx/Collateral.hs @@ -21,9 +21,8 @@ import PlutusTx.Numeric qualified as PlutusTx data CollateralContext where CollateralContext :: { managedTxOuts :: Map Api.TxOutRef Api.TxOut, - collateralIns :: Set Api.TxOutRef, fee :: Integer, - returnCollateralWallet :: Wallet, + mCollaterals :: Maybe (Set Api.TxOutRef, Wallet), params :: Emulator.Params } -> CollateralContext @@ -44,59 +43,62 @@ toCollateralTriplet :: Cardano.TxReturnCollateral Cardano.CtxTx Cardano.ConwayEra ) toCollateralTriplet = do - -- Retrieving know outputs - knownTxOuts <- asks managedTxOuts - -- Retrieving the outputs to be used as collateral inputs - collateralInsList <- asks (Set.toList . collateralIns) - -- We build the collateral inputs from this list - txInsCollateral <- - case collateralInsList of - [] -> return Cardano.TxInsCollateralNone - l -> throwOnToCardanoError "txOutRefsToTxInCollateral" (Cardano.TxInsCollateral Cardano.AlonzoEraOnwardsConway <$> mapM Ledger.toCardanoTxIn l) - -- Retrieving the total value in collateral inputs. This fails if one of the - -- collaterals has been been successfully resolved. - collateralInsValue <- do - let collateralInsResolved = mapMaybe (`Map.lookup` knownTxOuts) collateralInsList - when (length collateralInsResolved /= length collateralInsList) $ throwOnString "toCollateralTriplet: unresolved txOutRefs" - return $ mconcat (Api.txOutValue <$> collateralInsResolved) - -- We retrieve the collateral percentage compared to fees. By default, we use - -- 150% which is the current value in the parameters, although the default - -- value should never be used here, as the call is supposed to always succeed. - collateralPercentage <- asks (toInteger . fromMaybe 150 . Cardano.protocolParamCollateralPercent . Emulator.pProtocolParams . params) - -- The total collateral corresponds to the fees multiplied by the collateral - -- percentage. We add 1 because the ledger apparently rounds up this value. - coinTotalCollateral <- asks (Emulator.Coin . (+ 1) . (`div` 100) . (* collateralPercentage) . fee) - -- We create the total collateral based on the computed value - let txTotalCollateral = Cardano.TxTotalCollateral Cardano.BabbageEraOnwardsConway coinTotalCollateral - -- We compute a return collateral value by subtracting the total collateral to - -- the value in collateral inputs - let returnCollateralValue = collateralInsValue <> PlutusTx.negate (toValue coinTotalCollateral) - -- This should never happen, as we always compute the collaterals for the - -- user, but we guard against having some negative elements in the value in - -- case we give more freedom to the users in the future - when (fst (Api.split returnCollateralValue) /= mempty) $ throwOnString "toCollateralTriplet: negative parts in return collateral value" - -- The return collateral is then computed - txReturnCollateral <- - -- If the total collateral equal what the inputs provide, we return `None` - if returnCollateralValue == mempty - then return Cardano.TxReturnCollateralNone - else -- Otherwise, we compute the elements of a new output - do - -- The value is a translation of the remaining value - txReturnCollateralValue <- - Ledger.toCardanoTxOutValue - <$> throwOnToCardanoError - "toCollateralTriplet: cannot build return collateral value" - (Ledger.toCardanoValue returnCollateralValue) - -- The address is the one from the return collateral wallet, which is - -- required to exist here. - address <- do - returnCollateralWallet <- asks returnCollateralWallet - networkId <- asks (Emulator.pNetworkId . params) - throwOnToCardanoError "toCollateralTriplet: cannot build return collateral address" $ - Ledger.toCardanoAddressInEra networkId (walletAddress returnCollateralWallet) - -- The return collateral is built up from those elements - return $ - Cardano.TxReturnCollateral Cardano.BabbageEraOnwardsConway $ - Cardano.TxOut address txReturnCollateralValue Cardano.TxOutDatumNone Cardano.ReferenceScriptNone - return (txInsCollateral, txTotalCollateral, txReturnCollateral) + -- Retrieving the optional collaterals and associated wallet + mCollaterals <- asks mCollaterals + case mCollaterals of + -- If this is nothing, it means no collateral is needed (no script involved) + Nothing -> return (Cardano.TxInsCollateralNone, Cardano.TxTotalCollateralNone, Cardano.TxReturnCollateralNone) + Just (Set.toList -> collateralInsList, returnCollateralWallet) -> do + -- Retrieving know outputs + knownTxOuts <- asks managedTxOuts + -- We build the collateral inputs from this list + txInsCollateral <- + case collateralInsList of + [] -> return Cardano.TxInsCollateralNone + l -> throwOnToCardanoError "txOutRefsToTxInCollateral" (Cardano.TxInsCollateral Cardano.AlonzoEraOnwardsConway <$> mapM Ledger.toCardanoTxIn l) + -- Retrieving the total value in collateral inputs. This fails if one of the + -- collaterals has been been successfully resolved. + collateralInsValue <- do + let collateralInsResolved = mapMaybe (`Map.lookup` knownTxOuts) collateralInsList + when (length collateralInsResolved /= length collateralInsList) $ throwOnString "toCollateralTriplet: unresolved txOutRefs" + return $ mconcat (Api.txOutValue <$> collateralInsResolved) + -- We retrieve the collateral percentage compared to fees. By default, we use + -- 150% which is the current value in the parameters, although the default + -- value should never be used here, as the call is supposed to always succeed. + collateralPercentage <- asks (toInteger . fromMaybe 150 . Cardano.protocolParamCollateralPercent . Emulator.pProtocolParams . params) + -- The total collateral corresponds to the fees multiplied by the collateral + -- percentage. We add 1 because the ledger apparently rounds up this value. + coinTotalCollateral <- asks (Emulator.Coin . (+ 1) . (`div` 100) . (* collateralPercentage) . fee) + -- We create the total collateral based on the computed value + let txTotalCollateral = Cardano.TxTotalCollateral Cardano.BabbageEraOnwardsConway coinTotalCollateral + -- We compute a return collateral value by subtracting the total collateral to + -- the value in collateral inputs + let returnCollateralValue = collateralInsValue <> PlutusTx.negate (toValue coinTotalCollateral) + -- This should never happen, as we always compute the collaterals for the + -- user, but we guard against having some negative elements in the value in + -- case we give more freedom to the users in the future + when (fst (Api.split returnCollateralValue) /= mempty) $ throwOnString "toCollateralTriplet: negative parts in return collateral value" + -- The return collateral is then computed + txReturnCollateral <- + -- If the total collateral equal what the inputs provide, we return + -- `TxReturnCollateralNone`, otherwise, we compute the new output + if returnCollateralValue == mempty + then return Cardano.TxReturnCollateralNone + else do + -- The value is a translation of the remaining value + txReturnCollateralValue <- + Ledger.toCardanoTxOutValue + <$> throwOnToCardanoError + "toCollateralTriplet: cannot build return collateral value" + (Ledger.toCardanoValue returnCollateralValue) + -- The address is the one from the return collateral wallet, which is + -- required to exist here. + address <- do + networkId <- asks (Emulator.pNetworkId . params) + throwOnToCardanoError "toCollateralTriplet: cannot build return collateral address" $ + Ledger.toCardanoAddressInEra networkId (walletAddress returnCollateralWallet) + -- The return collateral is built up from those elements + return $ + Cardano.TxReturnCollateral Cardano.BabbageEraOnwardsConway $ + Cardano.TxOut address txReturnCollateralValue Cardano.TxOutDatumNone Cardano.ReferenceScriptNone + return (txInsCollateral, txTotalCollateral, txReturnCollateral) diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index 536628aca..878361fb0 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -45,7 +45,7 @@ initialDistributionBalancing = paysPK alice (Script.ada 105 <> banana 2) `withDatumHash` () ] -type TestBalancingOutcome = (TxSkel, TxSkel, Integer, Set Api.TxOutRef, [Api.TxOutRef]) +type TestBalancingOutcome = (TxSkel, TxSkel, Integer, Maybe (Set Api.TxOutRef, Wallet), [Api.TxOutRef]) spendsScriptUtxo :: (MonadBlockChain m) => Bool -> m (Map Api.TxOutRef TxSkelRedeemer) spendsScriptUtxo False = return Map.empty @@ -93,10 +93,10 @@ testingBalancingTemplate toBobValue toAliceValue spendSearch balanceSearch colla }, txSkelSigners = [alice] } - (skel', fee, cols, _) <- balanceTxSkel skel + (skel', fee, mCols) <- balanceTxSkel skel void $ validateTxSkel skel nonOnlyValueUtxos <- runUtxoSearch $ utxosAtSearch alice `filterWithPred` \o -> isJust (Api.txOutReferenceScript o) || (Api.txOutDatum o /= Api.NoOutputDatum) - return (skel, skel', fee, cols, fst <$> nonOnlyValueUtxos) + return (skel, skel', fee, mCols, fst <$> nonOnlyValueUtxos) aliceNonOnlyValueUtxos :: (MonadBlockChain m) => UtxoSearch m Api.TxOut aliceNonOnlyValueUtxos = utxosAtSearch alice `filterWithPred` \o -> isJust (Api.txOutReferenceScript o) || (Api.txOutDatum o /= Api.NoOutputDatum) @@ -153,8 +153,8 @@ balanceReduceFee = do { txSkelOuts = [paysPK bob (Script.ada 50)], txSkelSigners = [alice] } - (skelBalanced, feeBalanced, cols, rColWal) <- balanceTxSkel skelAutoFee - feeBalanced' <- estimateTxSkelFee skelBalanced feeBalanced cols rColWal + (skelBalanced, feeBalanced, mCols) <- balanceTxSkel skelAutoFee + feeBalanced' <- estimateTxSkelFee skelBalanced feeBalanced mCols let skelManualFee = skelAutoFee { txSkelOpts = @@ -162,8 +162,8 @@ balanceReduceFee = do { txOptFeePolicy = ManualFee (feeBalanced - 1) } } - (skelBalancedManual, feeBalancedManual, colsManual, rColWalManual) <- balanceTxSkel skelManualFee - feeBalancedManual' <- estimateTxSkelFee skelBalancedManual feeBalancedManual colsManual rColWalManual + (skelBalancedManual, feeBalancedManual, mColsManual) <- balanceTxSkel skelManualFee + feeBalancedManual' <- estimateTxSkelFee skelBalancedManual feeBalancedManual mColsManual return (feeBalanced, feeBalanced', feeBalancedManual, feeBalancedManual') reachingMagic :: (MonadBlockChain m) => m () @@ -192,7 +192,8 @@ insNb :: (IsProp prop) => Int -> ResProp prop insNb is (_, TxSkel {..}, _, _, _) = testBool $ length txSkelIns == is colInsNb :: (IsProp prop) => Int -> ResProp prop -colInsNb cis (_, _, _, refs, _) = testBool $ cis == length refs +colInsNb cis (_, _, _, Nothing, _) = testBool $ cis == 0 +colInsNb cis (_, _, _, Just (refs, _), _) = testBool $ cis == length refs retOutsNb :: (IsProp prop) => Int -> ResProp prop retOutsNb ros (_, _, _, _, refs) = testBool $ ros == length refs @@ -249,27 +250,74 @@ tests = [ testBalancingFailsWith "Balancing does not occur when not requested, fails with empty inputs" failsWithEmptyTxIns - (simplePaymentToBob 20_000_000 0 0 0 False (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), + ( simplePaymentToBob + 20_000_000 + 0 + 0 + 0 + False + (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000) + ), testBalancingFailsWith "Balancing does not occur when not requested, fails with too small inputs" failsWithValueNotConserved - (testingBalancingTemplate (Script.ada 50) mempty (aliceNAdaUtxos 8) emptySearch emptySearch False (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), + ( testingBalancingTemplate + (Script.ada 50) + mempty + (aliceNAdaUtxos 8) + emptySearch + emptySearch + False + (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000) + ), testBalancingSucceedsWith "It is possible to balance the transaction by hand without collaterals" [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 0, retOutsNb 3] - (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch emptySearch False (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), + ( testingBalancingTemplate + (Script.ada 7) + mempty + (aliceNAdaUtxos 8) + emptySearch + emptySearch + False + (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000) + ), testBalancingSucceedsWith "It is also possible to balance the transaction by hand with collaterals" [hasFee 1_000_000, insNb 2, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (testingBalancingTemplate (Script.ada 49) mempty (aliceNAdaUtxos 8) emptySearch emptySearch True (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000)), + ( testingBalancingTemplate + (Script.ada 49) + mempty + (aliceNAdaUtxos 8) + emptySearch + emptySearch + True + (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000) + ), testBalancingFailsWith "A collateral wallet needs to be provided when auto balancing is enabled" failsLackOfCollateralWallet - (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch emptySearch False (setDontBalance . setFixedFee 1_000_000)), + ( testingBalancingTemplate + (Script.ada 7) + mempty + (aliceNAdaUtxos 8) + emptySearch + emptySearch + False + (setDontBalance . setFixedFee 1_000_000) + ), testBalancingSucceedsWith "We can also directly give a set of collateral utxos" [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (testingBalancingTemplate (Script.ada 7) mempty (aliceNAdaUtxos 8) emptySearch (aliceNAdaUtxos 8) False (setDontBalance . setFixedFee 1_000_000)) + ( testingBalancingTemplate + (Script.ada 7) + mempty + (aliceNAdaUtxos 8) + emptySearch + (aliceNAdaUtxos 8) + False + (setDontBalance . setFixedFee 1_000_000) + ) ], testGroup "Manual balancing with auto fee" @@ -281,28 +329,75 @@ tests = [ testBalancingSucceedsWith "We can auto balance a transaction with auto fee" [insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 20_000_000 0 0 0 False id), + ( simplePaymentToBob + 20_000_000 + 0 + 0 + 0 + False + id + ), testCase "Auto fee are minimal: less fee will lead to strictly smaller fee than Cardano's estimate" $ testSucceedsFrom' def - (\(feeBalanced, feeBalanced', feeBalancedManual, feeBalancedManual') _ -> testBool $ feeBalanced' <= feeBalanced && feeBalancedManual' > feeBalancedManual) + ( \(feeBalanced, feeBalanced', feeBalancedManual, feeBalancedManual') _ -> + testBool $ + feeBalanced' <= feeBalanced && feeBalancedManual' > feeBalancedManual + ) initialDistributionBalancing balanceReduceFee, testCase "The auto-fee process can sometimes recover from a temporary balancing error..." $ - testSucceedsFrom def initialDistributionBalancing (simplePaymentToBob 103_500_000 0 0 0 False id), + testSucceedsFrom + def + initialDistributionBalancing + ( simplePaymentToBob + 103_500_000 + 0 + 0 + 0 + False + id + ), testCase "... but not always" $ - testFailsFrom def failsAtBalancing initialDistributionBalancing (simplePaymentToBob 104_000_000 0 0 0 False id), + testFailsFrom + def + failsAtBalancing + initialDistributionBalancing + ( simplePaymentToBob + 104_000_000 + 0 + 0 + 0 + False + id + ), testCase "The auto-fee process can recover from a temporary collateral error..." $ testSucceedsFrom def initialDistributionBalancing - (testingBalancingTemplate (Script.ada 100) mempty emptySearch emptySearch (aliceNAdaUtxos 2) False id), + ( testingBalancingTemplate + (Script.ada 100) + mempty + emptySearch + emptySearch + (aliceNAdaUtxos 2) + False + id + ), testCase "... but not always" $ testFailsFrom def failsAtCollaterals initialDistributionBalancing - (testingBalancingTemplate (Script.ada 100) mempty (utxosAtSearch alice) emptySearch (aliceNAdaUtxos 1) False id), + ( testingBalancingTemplate + (Script.ada 100) + mempty + (utxosAtSearch alice) + emptySearch + (aliceNAdaUtxos 1) + False + id + ), testCase "Reaching magical spot with the exact balance during auto fee computation" $ testSucceedsFrom def @@ -314,66 +409,174 @@ tests = [ testBalancingSucceedsWith "We can use a single utxo for balancing purpose" [hasFee 1_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 20_000_000 0 0 0 False (setFixedFee 1_000_000)), + ( simplePaymentToBob + 20_000_000 + 0 + 0 + 0 + False + (setFixedFee 1_000_000) + ), testBalancingSucceedsWith "We can use several utxos for balancing with ridiculously high fee" [hasFee 40_000_000, insNb 3, additionalOutsNb 1, colInsNb 3, retOutsNb 3] - (simplePaymentToBob 20_000_000 0 0 0 False (setFixedFee 40_000_000)), + ( simplePaymentToBob + 20_000_000 + 0 + 0 + 0 + False + (setFixedFee 40_000_000) + ), testBalancingFailsWith "We cannot balance with too little fee" failsWithTooLittleFee - (simplePaymentToBob 20_000_000 0 0 0 False (setFixedFee 150_000)), + ( simplePaymentToBob + 20_000_000 + 0 + 0 + 0 + False + (setFixedFee 150_000) + ), testBalancingFailsWith "Fee are rightfully included in the balancing process, which fails when they are too high" (failsAtBalancingWith (Script.ada 1) alice) - (simplePaymentToBob 100_000_000 0 0 0 False (setFixedFee 6_000_000)), + ( simplePaymentToBob + 100_000_000 + 0 + 0 + 0 + False + (setFixedFee 6_000_000) + ), testBalancingFailsWith "Collaterals are rightfully included in the balancing process, which fails when they are too high" (failsAtCollateralsWith 80_000_000) - (simplePaymentToBob 6_000_000 0 0 0 False (setFixedFee 80_000_000)), + ( simplePaymentToBob + 6_000_000 + 0 + 0 + 0 + False + (setFixedFee 80_000_000) + ), testBalancingSucceedsWith "Exactly the right amount leads to no output change" [hasFee 2_000_000, insNb 3, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 65_000_000 3 6 0 False (setFixedFee 2_000_000)), + ( simplePaymentToBob + 65_000_000 + 3 + 6 + 0 + False + (setFixedFee 2_000_000) + ), testBalancingSucceedsWith "It still leads to no output change when requesting a new output" [hasFee 2_000_000, insNb 3, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 65_000_000 3 6 0 False (setDontAdjustOutput . setFixedFee 2_000_000)), + ( simplePaymentToBob + 65_000_000 + 3 + 6 + 0 + False + (setDontAdjustOutput . setFixedFee 2_000_000) + ), testBalancingSucceedsWith "1 lovelace more than the exact right amount leads to an additional output" [hasFee 2_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 65_000_001 3 6 0 False (setFixedFee 2_000_000)), + ( simplePaymentToBob + 65_000_001 + 3 + 6 + 0 + False + (setFixedFee 2_000_000) + ), testBalancingSucceedsWith "1 lovelace less than the exact right amount leads to an additional output to account for minAda" [hasFee 2_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 65_000_001 3 6 0 False (setFixedFee 2_000_000)), + ( simplePaymentToBob + 65_000_001 + 3 + 6 + 0 + False + (setFixedFee 2_000_000) + ), testBalancingSucceedsWith "We can merge assets to an existing outputs at the balancing wallet address" [hasFee 2_000_000, insNb 1, additionalOutsNb 0, colInsNb 1, retOutsNb 3] - (bothPaymentsToBobAndAlice 6_000_000 False (setFixedFee 2_000_000)), + ( bothPaymentsToBobAndAlice + 6_000_000 + False + (setFixedFee 2_000_000) + ), testBalancingSucceedsWith "We can create a new output at the balancing wallet address even if one already exists" [hasFee 2_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (bothPaymentsToBobAndAlice 6_000_000 False (setFixedFee 2_000_000 . setDontAdjustOutput)), + ( bothPaymentsToBobAndAlice + 6_000_000 + False + (setFixedFee 2_000_000 . setDontAdjustOutput) + ), testBalancingSucceedsWith "We can balance transactions with non-Script.ada assets" [hasFee 2_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 0 0 5 0 False (setFixedFee 2_000_000 . setEnsureMinAda)), + ( simplePaymentToBob + 0 + 0 + 5 + 0 + False + (setFixedFee 2_000_000 . setEnsureMinAda) + ), testBalancingSucceedsWith "Successful balancing with multiple assets" [hasFee 1_000_000, insNb 2, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (simplePaymentToBob 0 2 5 0 False (setEnsureMinAda . setFixedFee 1_000_000)), + ( simplePaymentToBob + 0 + 2 + 5 + 0 + False + (setEnsureMinAda . setFixedFee 1_000_000) + ), testBalancingFailsWith "Unsuccessful balancing with multiple assets in non value only utxos" (failsAtBalancingWith (banana 4) alice) - (simplePaymentToBob 0 2 5 4 False (setEnsureMinAda . setFixedFee 1_000_000)), + ( simplePaymentToBob + 0 + 2 + 5 + 4 + False + (setEnsureMinAda . setFixedFee 1_000_000) + ), testBalancingSucceedsWith "Successful balancing with multiple assets and explicit utxo set, reference script is lost" [hasFee 1_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 2] - (testingBalancingTemplate (apple 2 <> orange 5 <> banana 4) mempty emptySearch (utxosAtSearch alice) emptySearch False (setEnsureMinAda . setFixedFee 1_000_000)), + ( testingBalancingTemplate + (apple 2 <> orange 5 <> banana 4) + mempty + emptySearch + (utxosAtSearch alice) + emptySearch + False + (setEnsureMinAda . setFixedFee 1_000_000) + ), testBalancingSucceedsWith "Successful balancing with excess initial consumption" [hasFee 1_000_000, insNb 5, additionalOutsNb 1, colInsNb 1, retOutsNb 3] - (testingBalancingTemplate mempty mempty (onlyValueOutputsAtSearch alice) emptySearch emptySearch False (setFixedFee 1_000_000)) + ( testingBalancingTemplate + mempty + mempty + (onlyValueOutputsAtSearch alice) + emptySearch + emptySearch + False + (setFixedFee 1_000_000) + ) ] ] diff --git a/tests/Cooked/ShowBSSpec.hs b/tests/Cooked/ShowBSSpec.hs index e786bbdd5..806ab4a29 100644 --- a/tests/Cooked/ShowBSSpec.hs +++ b/tests/Cooked/ShowBSSpec.hs @@ -82,7 +82,7 @@ tests = ) ], testCase "printing the 'TxInfo' from a validator produces the expected string" $ - let isExpectedString = (==) "(Script context:Script Tx info:(inputs:[(TxInInfo (TxOutRef (TxId \"ad1f1a5a545dbe830383711cc3302f8fb5eb41c5154a7bc5336921abd453f001\") 0) (TxOut (Address (ScriptCredential (ScriptHash \"d02455c9a6cc9296707d031d8c668ea75612663d111d54a4155e4371\")) Nothing) (Value (fromList [((CurrencySymbol \"\"),(fromList [((TokenName \"\"),30000000)]))])) (OutputDatum (Datum (BuiltinData (Constr 0 [])))) Nothing))]reference inputs:[]outputs:[(TxOut (Address (PubKeyCredential (PubKeyHash \"a2c20c77887ace1cd986193e4e75babd8993cfd56995cd5cfce609c2\")) Nothing) (Value (fromList [((CurrencySymbol \"\"),(fromList [((TokenName \"\"),29515587)]))])) NoOutputDatum Nothing)]fees:(Lovelace 484413)minted value:(Value (fromList []))certificates:[]wdrl:(fromList [])valid range:(Interval (LowerBound NegInf True) (UpperBound PosInf True))signatories:[(PubKeyHash \"a2c20c77887ace1cd986193e4e75babd8993cfd56995cd5cfce609c2\")]redeemers:(fromList [((Spending (TxOutRef (TxId \"ad1f1a5a545dbe830383711cc3302f8fb5eb41c5154a7bc5336921abd453f001\") 0)),(Redeemer (BuiltinData (Constr 1 []))))])datums:(fromList [])transaction id:(TxId \"6fb3cb19b99bd2a5e880f64a80555ea096e583c269597329d03adb3a69ca53dd\")votes:(fromList [])proposals:[]treasury amount:Nothingtreasury donation:Nothing)Script purpose:(Spending (TxOutRef (TxId \"ad1f1a5a545dbe830383711cc3302f8fb5eb41c5154a7bc5336921abd453f001\") 0)))" + let isExpectedString = (==) "(Script context:Script Tx info:(inputs:[(TxInInfo (TxOutRef (TxId \"41892ee014fdfc05ace214461e61ba074679c711c761fdf5eb655d08acfb5450\") 0) (TxOut (Address (ScriptCredential (ScriptHash \"d02455c9a6cc9296707d031d8c668ea75612663d111d54a4155e4371\")) Nothing) (Value (fromList [((CurrencySymbol \"\"),(fromList [((TokenName \"\"),30000000)]))])) (OutputDatum (Datum (BuiltinData (Constr 0 [])))) Nothing))]reference inputs:[]outputs:[(TxOut (Address (PubKeyCredential (PubKeyHash \"a2c20c77887ace1cd986193e4e75babd8993cfd56995cd5cfce609c2\")) Nothing) (Value (fromList [((CurrencySymbol \"\"),(fromList [((TokenName \"\"),29515587)]))])) NoOutputDatum Nothing)]fees:(Lovelace 484413)minted value:(Value (fromList []))certificates:[]wdrl:(fromList [])valid range:(Interval (LowerBound NegInf True) (UpperBound PosInf True))signatories:[(PubKeyHash \"a2c20c77887ace1cd986193e4e75babd8993cfd56995cd5cfce609c2\")]redeemers:(fromList [((Spending (TxOutRef (TxId \"41892ee014fdfc05ace214461e61ba074679c711c761fdf5eb655d08acfb5450\") 0)),(Redeemer (BuiltinData (Constr 1 []))))])datums:(fromList [])transaction id:(TxId \"0c2e3a3357b837273758c9d15647b039763a1d326341087dacc830afa324accb\")votes:(fromList [])proposals:[]treasury amount:Nothingtreasury donation:Nothing)Script purpose:(Spending (TxOutRef (TxId \"41892ee014fdfc05ace214461e61ba074679c711c761fdf5eb655d08acfb5450\") 0)))" in testFails (def @PrettyCookedOpts) ( isCekEvaluationFailureWithMsg From 34aa2af7149ee6969c6a7339cc275d37b93184b2 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 11 Jul 2024 14:36:12 +0200 Subject: [PATCH 17/71] =?UTF-8?q?2=20first=20test=20groups=20pass=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Cooked/BalancingSpec.hs | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index 878361fb0..57ff89e3f 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -140,8 +140,7 @@ noBalanceMaxFee = do txSkelOpts = def { txOptBalancingPolicy = DoNotBalance, - txOptFeePolicy = AutoFeeComputation, - txOptCollateralUtxos = CollateralUtxosFromSet (Set.singleton txOutRef) alice + txOptFeePolicy = AutoFeeComputation }, txSkelSigners = [alice] } @@ -295,8 +294,20 @@ tests = (setCollateralWallet alice . setDontBalance . setFixedFee 1_000_000) ), testBalancingFailsWith - "A collateral wallet needs to be provided when auto balancing is enabled" + "A collateral wallet needs to be provided when auto balancing is enabled and script are involved..." failsLackOfCollateralWallet + ( testingBalancingTemplate + (Script.ada 49) + mempty + (aliceNAdaUtxos 8) + emptySearch + emptySearch + True + (setDontBalance . setFixedFee 1_000_000) + ), + testBalancingSucceedsWith + "... but is not necessary otherwise." + [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 0, retOutsNb 3] ( testingBalancingTemplate (Script.ada 7) mempty @@ -307,8 +318,20 @@ tests = (setDontBalance . setFixedFee 1_000_000) ), testBalancingSucceedsWith - "We can also directly give a set of collateral utxos" - [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 1, retOutsNb 3] + "We can also directly give a set of collateral utxos..." + [hasFee 1_000_000, insNb 2, additionalOutsNb 0, colInsNb 1, retOutsNb 3] + ( testingBalancingTemplate + (ada 49) + mempty + (aliceNAdaUtxos 8) + emptySearch + (aliceNAdaUtxos 8) + True + (setDontBalance . setFixedFee 1_000_000) + ), + testBalancingSucceedsWith + "... which will be ignored when no script is involved." + [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 0, retOutsNb 3] ( testingBalancingTemplate (Script.ada 7) mempty From 291df890b61aabaa5260e7fb38628119afd0df62 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 11 Jul 2024 16:33:41 +0200 Subject: [PATCH 18/71] all tests fixed --- tests/Cooked/BalancingSpec.hs | 68 +++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index 57ff89e3f..210d0b5c0 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -330,7 +330,7 @@ tests = (setDontBalance . setFixedFee 1_000_000) ), testBalancingSucceedsWith - "... which will be ignored when no script is involved." + "... which will be ignored when no script is involved" [hasFee 1_000_000, insNb 1, additionalOutsNb 0, colInsNb 0, retOutsNb 3] ( testingBalancingTemplate (Script.ada 7) @@ -350,8 +350,19 @@ tests = testGroup "Auto balancing with auto fee" [ testBalancingSucceedsWith - "We can auto balance a transaction with auto fee" + "We can auto balance a transaction with auto fee, collaterals and no pk inputs" [insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + ( simplePaymentToBob + 20_000_000 + 0 + 0 + 0 + True + id + ), + testBalancingSucceedsWith + "We can auto balance a transaction with auto fee, no collateral and no script inputs" + [insNb 1, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( simplePaymentToBob 20_000_000 0 @@ -360,6 +371,17 @@ tests = False id ), + testBalancingSucceedsWith + "We can auto balance a transaction with auto fee, collaterals, script and pk inputs" + [insNb 2, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + ( simplePaymentToBob + 60_000_000 + 0 + 0 + 0 + True + id + ), testCase "Auto fee are minimal: less fee will lead to strictly smaller fee than Cardano's estimate" $ testSucceedsFrom' def @@ -374,7 +396,7 @@ tests = def initialDistributionBalancing ( simplePaymentToBob - 103_500_000 + 103_650_000 0 0 0 @@ -399,12 +421,12 @@ tests = def initialDistributionBalancing ( testingBalancingTemplate - (Script.ada 100) + (Script.ada 142) mempty emptySearch emptySearch (aliceNAdaUtxos 2) - False + True id ), testCase "... but not always" $ @@ -413,12 +435,12 @@ tests = failsAtCollaterals initialDistributionBalancing ( testingBalancingTemplate - (Script.ada 100) + (Script.ada 142) mempty (utxosAtSearch alice) emptySearch (aliceNAdaUtxos 1) - False + True id ), testCase "Reaching magical spot with the exact balance during auto fee computation" $ @@ -431,7 +453,7 @@ tests = "Auto balancing with manual fee" [ testBalancingSucceedsWith "We can use a single utxo for balancing purpose" - [hasFee 1_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + [hasFee 1_000_000, insNb 1, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( simplePaymentToBob 20_000_000 0 @@ -442,7 +464,7 @@ tests = ), testBalancingSucceedsWith "We can use several utxos for balancing with ridiculously high fee" - [hasFee 40_000_000, insNb 3, additionalOutsNb 1, colInsNb 3, retOutsNb 3] + [hasFee 40_000_000, insNb 3, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( simplePaymentToBob 20_000_000 0 @@ -477,16 +499,16 @@ tests = "Collaterals are rightfully included in the balancing process, which fails when they are too high" (failsAtCollateralsWith 80_000_000) ( simplePaymentToBob - 6_000_000 + 48_000_000 0 0 0 - False + True (setFixedFee 80_000_000) ), testBalancingSucceedsWith "Exactly the right amount leads to no output change" - [hasFee 2_000_000, insNb 3, additionalOutsNb 0, colInsNb 1, retOutsNb 3] + [hasFee 2_000_000, insNb 3, additionalOutsNb 0, colInsNb 0, retOutsNb 3] ( simplePaymentToBob 65_000_000 3 @@ -497,7 +519,7 @@ tests = ), testBalancingSucceedsWith "It still leads to no output change when requesting a new output" - [hasFee 2_000_000, insNb 3, additionalOutsNb 0, colInsNb 1, retOutsNb 3] + [hasFee 2_000_000, insNb 3, additionalOutsNb 0, colInsNb 0, retOutsNb 3] ( simplePaymentToBob 65_000_000 3 @@ -508,7 +530,7 @@ tests = ), testBalancingSucceedsWith "1 lovelace more than the exact right amount leads to an additional output" - [hasFee 2_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + [hasFee 2_000_000, insNb 3, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( simplePaymentToBob 65_000_001 3 @@ -519,9 +541,9 @@ tests = ), testBalancingSucceedsWith "1 lovelace less than the exact right amount leads to an additional output to account for minAda" - [hasFee 2_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + [hasFee 2_000_000, insNb 3, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( simplePaymentToBob - 65_000_001 + 64_999_999 3 6 0 @@ -530,7 +552,7 @@ tests = ), testBalancingSucceedsWith "We can merge assets to an existing outputs at the balancing wallet address" - [hasFee 2_000_000, insNb 1, additionalOutsNb 0, colInsNb 1, retOutsNb 3] + [hasFee 2_000_000, insNb 1, additionalOutsNb 0, colInsNb 0, retOutsNb 3] ( bothPaymentsToBobAndAlice 6_000_000 False @@ -538,15 +560,15 @@ tests = ), testBalancingSucceedsWith "We can create a new output at the balancing wallet address even if one already exists" - [hasFee 2_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + [hasFee 2_000_000, insNb 1, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( bothPaymentsToBobAndAlice 6_000_000 False (setFixedFee 2_000_000 . setDontAdjustOutput) ), testBalancingSucceedsWith - "We can balance transactions with non-Script.ada assets" - [hasFee 2_000_000, insNb 1, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + "We can balance transactions with non-ada assets" + [hasFee 2_000_000, insNb 1, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( simplePaymentToBob 0 0 @@ -557,7 +579,7 @@ tests = ), testBalancingSucceedsWith "Successful balancing with multiple assets" - [hasFee 1_000_000, insNb 2, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + [hasFee 1_000_000, insNb 2, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( simplePaymentToBob 0 2 @@ -579,7 +601,7 @@ tests = ), testBalancingSucceedsWith "Successful balancing with multiple assets and explicit utxo set, reference script is lost" - [hasFee 1_000_000, insNb 3, additionalOutsNb 1, colInsNb 1, retOutsNb 2] + [hasFee 1_000_000, insNb 3, additionalOutsNb 1, colInsNb 0, retOutsNb 2] ( testingBalancingTemplate (apple 2 <> orange 5 <> banana 4) mempty @@ -591,7 +613,7 @@ tests = ), testBalancingSucceedsWith "Successful balancing with excess initial consumption" - [hasFee 1_000_000, insNb 5, additionalOutsNb 1, colInsNb 1, retOutsNb 3] + [hasFee 1_000_000, insNb 5, additionalOutsNb 1, colInsNb 0, retOutsNb 3] ( testingBalancingTemplate mempty mempty From 5f696fdce161f7f79c258ccc057483a3432f121b Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 11 Jul 2024 16:58:53 +0200 Subject: [PATCH 19/71] doc --- doc/BALANCING.md | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/doc/BALANCING.md b/doc/BALANCING.md index 5bb6f8984..14183e5e6 100644 --- a/doc/BALANCING.md +++ b/doc/BALANCING.md @@ -11,16 +11,18 @@ is currently implemented, and which options affect this mechanism. ## Balancing: what this is about. ### Balancing requirements + In Cardano, transactions must be balanced before they can be submitted for validation. This means the equation `input value + minted value = output value + burned value + fee` must be satisfied to proceed to phase 2 of the validation -process. Additionally, collaterals must be provided to account for transaction -failures in phase 2. These collaterals are related to the fee by the following -inequation: `totalCollateral >= fee * feeToCollateralRatio`, and they must -satisfy their own preservation equation: `collateralInputs = totalCollaterals + -returnCollaterals`. Lastly, the actual required fee for a given transaction -depends on the size of the transaction and the (not yet executed) resources used -by the scripts during validation. +process. Additionally, when a transaction involves scripts, and thus its +validation can fail in phase 2, collaterals must be provided to account for such +possible failures. These collaterals are related to the fee through the protocol +parameter `feeToCollateralRatio` by the inequation `totalCollateral >= fee * +feeToCollateralRatio`, and they must satisfy their own preservation equation: +`collateralInputs = totalCollaterals + returnCollaterals`. Lastly, the actual +required fee for a given transaction depends on the size of the transaction and +the (not yet executed) resources used by the scripts during validation. ### Balancing mechanism @@ -213,17 +215,19 @@ data CollateralUtxos | CollateralUtxosFromSet (Set Api.TxOutRef) Wallet ``` -In addition to the regular UTXOs consumed in a transaction, additional UTXOs -must be provided to cover potential phase 2 validation failures. These UTXOs -need to be sufficient to meet a specified total collateral requirement, -typically 1.5 times the transaction fee based on protocol parameters. Any -surplus can be returned to a designated wallet through an output known as return -collateral. This setting determines which UTXOs the balancing mechanism should -consider for inclusion in the transaction. Similar to -[`BalancingUtxos`](#balancing-utxos), the final set of UTXOs included may not -necessarily match the considered set, especially for collaterals, as protocol -parameters also impose limits on the number of allowable collateral UTXOs -(typically 3). +When a transaction involves executing scripts, UTXOs must be provided as +collaterals to cover potential phase 2 validation failures. These UTXOs need to +be sufficient to meet a specified total collateral requirement, typically 1.5 +times the transaction fee based on protocol parameters. Any surplus can be +returned to a designated wallet through an output known as return +collateral. + +This setting is ignored when the transaction does not involve any +scripts, but otherwise determines which UTXOs the balancing mechanism should +consider for inclusion. Similar to [`BalancingUtxos`](#balancing-utxos), the +final set of UTXOs included may not necessarily match the considered set, +especially for collaterals, as protocol parameters also impose a limit on the +number of allowable collateral UTXOs (typically 3). Here are the options available: @@ -387,4 +391,3 @@ the estimated fee resulting from this balancing, we adjust our search interval: The recursion continues until an error is propagated or the interval is reduced to a single point. - From c8cf3733ee40c799730e6a2f1445b1dfd84fbbac Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 11 Jul 2024 17:06:57 +0200 Subject: [PATCH 20/71] updating doc --- doc/BALANCING.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/BALANCING.md b/doc/BALANCING.md index 14183e5e6..4d394b640 100644 --- a/doc/BALANCING.md +++ b/doc/BALANCING.md @@ -14,15 +14,16 @@ is currently implemented, and which options affect this mechanism. In Cardano, transactions must be balanced before they can be submitted for validation. This means the equation `input value + minted value = output value + -burned value + fee` must be satisfied to proceed to phase 2 of the validation -process. Additionally, when a transaction involves scripts, and thus its -validation can fail in phase 2, collaterals must be provided to account for such -possible failures. These collaterals are related to the fee through the protocol -parameter `feeToCollateralRatio` by the inequation `totalCollateral >= fee * -feeToCollateralRatio`, and they must satisfy their own preservation equation: -`collateralInputs = totalCollaterals + returnCollaterals`. Lastly, the actual -required fee for a given transaction depends on the size of the transaction and -the (not yet executed) resources used by the scripts during validation. +burned value + deposited value + fee` must be satisfied to proceed to phase 2 of +the validation process. Additionally, when a transaction involves scripts, and +thus its validation can fail in phase 2, collaterals must be provided to account +for such possible failures. These collaterals are related to the fee through the +protocol parameter `collateralPercentage` by the inequation `totalCollateral >= +fee * collateralPercentage`, and they must satisfy their own preservation +equation: `collateralInputs = totalCollaterals + returnCollaterals`. Lastly, the +actual required fee for a given transaction depends on the size of the +transaction and the (not yet executed) resources used by the scripts during +validation. ### Balancing mechanism From 73c209f47f57d5b4f718682b77df6d7481c407f0 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 11 Jul 2024 17:51:46 +0200 Subject: [PATCH 21/71] logging of unused collateral option --- CHANGELOG.md | 3 +++ src/Cooked/MockChain/Balancing.hs | 19 ++++++++++--------- src/Cooked/MockChain/BlockChain.hs | 5 ++++- src/Cooked/Pretty/Cooked.hs | 8 ++++++++ 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4189d309..adffbe2bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,11 +30,14 @@ * it is now a component of `MonadBlockChainBalancing` * it can be turned on/off in pretty-printing options * it now displays the discarding of utxos during balancing. + * it now displays when the users specifies useless collateral utxos. * it is not visible from outside of `cooked-validators` ### Fixed - All kinds of scripts can now be used as reference scripts. +- Transactions that do not involve script are now properly generated without any + collateral. ## [[4.0.0]](https://github.com/tweag/cooked-validators/releases/tag/v4.0.0) - 2024-06-28 diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 8f2f649e4..c66f71701 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -79,15 +79,16 @@ balanceTxSkel skelUnbal@TxSkel {..} = do -- We retrieve the various kinds of scripts spendingScripts <- txSkelInputValidators skelUnbal -- The transaction will only require collaterals when involving scripts - if Map.null txSkelMints && null (mapMaybe txSkelProposalWitness txSkelProposals) && Map.null spendingScripts - then return Nothing - else - Just <$> case txOptCollateralUtxos txSkelOpts of - CollateralUtxosFromBalancingWallet -> case balancingWallet of - Nothing -> fail "Can't select collateral utxos from a balancing wallet because it does not exist." - Just bWallet -> (,bWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch bWallet) - CollateralUtxosFromWallet cWallet -> (,cWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch cWallet) - CollateralUtxosFromSet utxos rWallet -> return (utxos, rWallet) + let noScriptInvolved = Map.null txSkelMints && null (mapMaybe txSkelProposalWitness txSkelProposals) && Map.null spendingScripts + case (noScriptInvolved, txOptCollateralUtxos txSkelOpts) of + (True, CollateralUtxosFromSet utxos _) -> publish (MCLogUnusedCollaterals $ Right utxos) >> return Nothing + (True, CollateralUtxosFromWallet cWallet) -> publish (MCLogUnusedCollaterals $ Left cWallet) >> return Nothing + (True, CollateralUtxosFromBalancingWallet) -> return Nothing + (False, CollateralUtxosFromSet utxos rWallet) -> return $ Just (utxos, rWallet) + (False, CollateralUtxosFromWallet cWallet) -> Just . (,cWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch cWallet) + (False, CollateralUtxosFromBalancingWallet) -> case balancingWallet of + Nothing -> fail "Can't select collateral utxos from a balancing wallet because it does not exist." + Just bWallet -> Just . (,bWallet) . Set.fromList . map fst <$> runUtxoSearch (onlyValueOutputsAtSearch bWallet) -- At this point, the presence (or absence) of balancing wallet dictates -- whether the transaction should be automatically balanced or not. diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index ee7b5f0a0..7e8a24202 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -126,8 +126,11 @@ data MockChainLogEntry where -- successfully sent for validation. MCLogNewTx :: Api.TxId -> MockChainLogEntry -- | Logging the fact that utxos provided by the user for balancing have to be - -- discarded for a specific reason. + -- discarded for a given reason. MCLogDiscardedUtxos :: Integer -> String -> MockChainLogEntry + -- | Logging the fact that utxos provided as collaterals will not be used + -- because the transaction does not need involve scripts. + MCLogUnusedCollaterals :: Either Wallet (Set Api.TxOutRef) -> MockChainLogEntry -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 36ec31017..bdf8a8043 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -148,6 +148,14 @@ instance PrettyCooked MockChainLogEntry where ] prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId prettyCookedOpt opts (MCLogDiscardedUtxos n s) = prettyCookedOpt opts n <+> "balancing utxos were discarded:" <+> PP.pretty s + prettyCookedOpt opts (MCLogUnusedCollaterals (Left cWallet)) = + "Specific request to fetch collateral utxos from" + <+> prettyCookedOpt opts (walletPKHash cWallet) + <+> "has been disregarded because the transaction does not require collaterals" + prettyCookedOpt opts (MCLogUnusedCollaterals (Right (length -> n))) = + "Specific request to fetch collateral utxos from the given set of" + <+> prettyCookedOpt opts n + <+> "elements has been disregarded because the transaction does not require collaterals" prettyTxSkel :: PrettyCookedOpts -> SkelContext -> TxSkel -> DocCooked prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals) = From 2275916482b4dc2a529dd11806287abc6b2f1c67 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sun, 28 Jul 2024 13:24:07 +0200 Subject: [PATCH 22/71] post-rebase small fixes --- src/Cooked/Pretty/Cooked.hs | 26 ++++++++++++++++---------- tests/Cooked/BalancingSpec.hs | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index bdf8a8043..64d0f61ee 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -44,7 +44,7 @@ import Data.Default import Data.Function (on) import Data.List qualified as List import Data.Map qualified as Map -import Data.Maybe (catMaybes, mapMaybe) +import Data.Maybe (catMaybes, fromMaybe, mapMaybe) import Data.Set qualified as Set import Optics.Core import Plutus.Script.Utils.Ada qualified as Script @@ -137,15 +137,21 @@ instance (Show a) => PrettyCooked (MockChainReturn a UtxoState) where -- been validated if the 'MCLogSubmittedTxSkel' is followed by a 'MCLogNewTx'. instance PrettyCooked MockChainLogEntry where prettyCookedOpt opts (MCLogSubmittedTxSkel skelContext skel) = prettyItemize "Submitted:" "-" [prettyTxSkel opts skelContext skel] - prettyCookedOpt opts (MCLogAdjustedTxSkel skelContext skel fee collaterals returnWallet) = - prettyItemize - "Adjusted:" - "-" - [ prettyTxSkel opts skelContext skel, - "Fee:" <+> prettyCookedOpt opts (Script.lovelace fee), - prettyItemize "Collateral inputs:" "-" (prettyCollateralIn opts skelContext <$> Set.toList collaterals), - "Return collateral target:" <+> prettyCookedOpt opts (walletPKHash returnWallet) - ] + prettyCookedOpt opts (MCLogAdjustedTxSkel skelContext skel fee mCollaterals) = + let mCollateralsDoc = + ( \(collaterals, returnWallet) -> + [ prettyItemize "Collateral inputs:" "-" (prettyCollateralIn opts skelContext <$> Set.toList collaterals), + "Return collateral target:" <+> prettyCookedOpt opts (walletPKHash returnWallet) + ] + ) + <$> mCollaterals + in prettyItemize + "Adjusted:" + "-" + $ [ prettyTxSkel opts skelContext skel, + "Fee:" <+> prettyCookedOpt opts (Script.lovelace fee) + ] + ++ fromMaybe [] mCollateralsDoc prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId prettyCookedOpt opts (MCLogDiscardedUtxos n s) = prettyCookedOpt opts n <+> "balancing utxos were discarded:" <+> PP.pretty s prettyCookedOpt opts (MCLogUnusedCollaterals (Left cWallet)) = diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index 210d0b5c0..df25e251f 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -321,7 +321,7 @@ tests = "We can also directly give a set of collateral utxos..." [hasFee 1_000_000, insNb 2, additionalOutsNb 0, colInsNb 1, retOutsNb 3] ( testingBalancingTemplate - (ada 49) + (Script.ada 49) mempty (aliceNAdaUtxos 8) emptySearch From f44b98e1786d6a559b7596c746bc64785c65c300 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sun, 28 Jul 2024 23:26:40 +0200 Subject: [PATCH 23/71] bye bye Ledger.TxOut --- src/Cooked/MockChain/Balancing.hs | 2 +- src/Cooked/MockChain/BlockChain.hs | 69 ++++++++++-------------------- src/Cooked/MockChain/Direct.hs | 18 ++++---- src/Cooked/MockChain/Staged.hs | 18 ++++---- src/Cooked/MockChain/UtxoSearch.hs | 12 ------ src/Cooked/Skeleton.hs | 2 +- 6 files changed, 43 insertions(+), 78 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index c66f71701..1d9577429 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -303,7 +303,7 @@ estimateTxSkelFee skel fee mCollaterals = do let collateralIns = case mCollaterals of Nothing -> [] Just (s, _) -> Set.toList s - managedTxOuts <- lookupUtxosPl $ txSkelKnownTxOutRefs skel <> collateralIns + managedTxOuts <- lookupUtxos $ txSkelKnownTxOutRefs skel <> collateralIns managedValidators <- txSkelInputValidators skel -- We generate the transaction body content, handling errors in the meantime txBodyContent <- case generateBodyContent fee params managedData managedTxOuts managedValidators mCollaterals skel of diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 7e8a24202..1e2ea52af 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -20,13 +20,9 @@ module Cooked.MockChain.BlockChain MonadBlockChainWithoutValidation (..), MonadBlockChain (..), AsTrans (..), - allUtxos, currentTime, waitNSlots, - utxosAt, - txOutByRef, utxosFromCardanoTx, - txOutV2FromLedger, typedDatumFromTxOutRef, valueFromTxOutRef, outputDatumFromTxOutRef, @@ -40,15 +36,12 @@ module Cooked.MockChain.BlockChain slotRangeBefore, slotRangeAfter, slotToTimeInterval, - txSkelInputUtxosPl, txSkelInputUtxos, - txSkelReferenceInputUtxosPl, txSkelReferenceInputUtxos, txSkelInputValidators, txSkelInputValue, txSkelInputData, lookupUtxos, - lookupUtxosPl, validateTxSkel', txSkelProposalsDeposit, govActionDeposit, @@ -59,7 +52,6 @@ import Cardano.Api.Ledger qualified as Cardano import Cardano.Ledger.Conway.PParams qualified as Conway import Cardano.Node.Emulator qualified as Emulator import Cardano.Node.Emulator.Internal.Node qualified as Emulator -import Control.Arrow import Control.Lens qualified as Lens import Control.Monad import Control.Monad.Except @@ -138,7 +130,7 @@ class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m w getParams :: m Emulator.Params -- | Returns a list of all UTxOs at a certain address. - utxosAtLedger :: Api.Address -> m [(Api.TxOutRef, Ledger.TxOut)] + utxosAt :: Api.Address -> m [(Api.TxOutRef, Api.TxOut)] -- | Returns the datum with the given hash if present. datumFromHash :: Api.DatumHash -> m (Maybe Api.Datum) @@ -148,14 +140,14 @@ class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m w validatorFromHash :: Script.ValidatorHash -> m (Maybe (Script.Versioned Script.Validator)) -- | Returns an output given a reference to it - txOutByRefLedger :: Api.TxOutRef -> m (Maybe Ledger.TxOut) + txOutByRef :: Api.TxOutRef -> m (Maybe Api.TxOut) -- | Logs an event that occured during a BlockChain run publish :: MockChainLogEntry -> m () class (MonadBlockChainBalancing m) => MonadBlockChainWithoutValidation m where -- | Returns a list of all currently known outputs. - allUtxosLedger :: m [(Api.TxOutRef, Ledger.TxOut)] + allUtxos :: m [(Api.TxOutRef, Api.TxOut)] -- | Updates parameters setParams :: Emulator.Params -> m () @@ -188,15 +180,6 @@ class (MonadBlockChainWithoutValidation m) => MonadBlockChain m where validateTxSkel' :: (MonadBlockChain m) => TxSkel -> m [Api.TxOutRef] validateTxSkel' = (map fst . utxosFromCardanoTx <$>) . validateTxSkel -allUtxos :: (MonadBlockChainWithoutValidation m) => m [(Api.TxOutRef, Api.TxOut)] -allUtxos = fmap (second txOutV2FromLedger) <$> allUtxosLedger - -utxosAt :: (MonadBlockChainBalancing m) => Api.Address -> m [(Api.TxOutRef, Api.TxOut)] -utxosAt address = fmap (second txOutV2FromLedger) <$> utxosAtLedger address - -txOutByRef :: (MonadBlockChainBalancing m) => Api.TxOutRef -> m (Maybe Api.TxOut) -txOutByRef oref = fmap txOutV2FromLedger <$> txOutByRefLedger oref - -- | Retrieve the ordered list of outputs of the given "CardanoTx". -- -- This is useful when writing endpoints and/or traces to fetch utxos of @@ -204,10 +187,13 @@ txOutByRef oref = fmap txOutV2FromLedger <$> txOutByRefLedger oref -- afterwards using 'allUtxos' or similar functions. utxosFromCardanoTx :: Ledger.CardanoTx -> [(Api.TxOutRef, Api.TxOut)] utxosFromCardanoTx = - map (\(txOut, txOutRef) -> (Ledger.fromCardanoTxIn txOutRef, txOutV2FromLedger txOut)) . Ledger.getCardanoTxOutRefs - -txOutV2FromLedger :: Ledger.TxOut -> Api.TxOut -txOutV2FromLedger = Ledger.fromCardanoTxOutToPV2TxInfoTxOut . Ledger.getTxOut + map + ( \(txOut, txOutRef) -> + ( Ledger.fromCardanoTxIn txOutRef, + Ledger.fromCardanoTxOutToPV2TxInfoTxOut $ Ledger.getTxOut txOut + ) + ) + . Ledger.getCardanoTxOutRefs -- | Try to resolve the datum on the output: If there's an inline datum, take -- that; if there's a datum hash, look the corresponding datum up (with @@ -333,16 +319,10 @@ typedDatumFromTxOutRef = ((>>= (\(Api.Datum datum) -> Api.fromBuiltinData datum) valueFromTxOutRef :: (MonadBlockChainWithoutValidation m) => Api.TxOutRef -> m (Maybe Api.Value) valueFromTxOutRef = ((outputValue <$>) <$>) . txOutByRef -txSkelInputUtxosPl :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.TxOutRef Api.TxOut) -txSkelInputUtxosPl = lookupUtxosPl . Map.keys . txSkelIns - -txSkelInputUtxos :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.TxOutRef Ledger.TxOut) +txSkelInputUtxos :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.TxOutRef Api.TxOut) txSkelInputUtxos = lookupUtxos . Map.keys . txSkelIns -txSkelReferenceInputUtxosPl :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.TxOutRef Api.TxOut) -txSkelReferenceInputUtxosPl = (Map.map txOutV2FromLedger <$>) . txSkelReferenceInputUtxos - -txSkelReferenceInputUtxos :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.TxOutRef Ledger.TxOut) +txSkelReferenceInputUtxos :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.TxOutRef Api.TxOut) txSkelReferenceInputUtxos = lookupUtxos . txSkelReferenceTxOutRefs -- | Retrieves the required deposit amount for issuing governance actions. @@ -361,7 +341,7 @@ maybeErrM err f = (maybe (throwError err) (return . f) =<<) -- | All validators which protect transaction inputs txSkelInputValidators :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Script.ValidatorHash (Script.Versioned Script.Validator)) txSkelInputValidators skel = do - utxos <- Map.toList <$> lookupUtxosPl (Map.keys . txSkelIns $ skel) + utxos <- Map.toList <$> lookupUtxos (Map.keys . txSkelIns $ skel) Map.fromList . catMaybes <$> mapM ( \(_oref, out) -> case outputAddress out of @@ -380,17 +360,14 @@ txSkelInputValidators skel = do -- | Go through all of the 'Api.TxOutRef's in the list and look them up in the -- state of the blockchain, throwing an error if one of them cannot be resolved. -lookupUtxosPl :: (MonadBlockChainBalancing m) => [Api.TxOutRef] -> m (Map Api.TxOutRef Api.TxOut) -lookupUtxosPl outRefs = Map.map txOutV2FromLedger <$> lookupUtxos outRefs - -lookupUtxos :: (MonadBlockChainBalancing m) => [Api.TxOutRef] -> m (Map Api.TxOutRef Ledger.TxOut) +lookupUtxos :: (MonadBlockChainBalancing m) => [Api.TxOutRef] -> m (Map Api.TxOutRef Api.TxOut) lookupUtxos = (Map.fromList <$>) - . mapM (\oRef -> (oRef,) <$> maybeErrM (MCEUnknownOutRefError "lookupUtxos: unknown TxOutRef" oRef) id (txOutByRefLedger oRef)) + . mapM (\oRef -> (oRef,) <$> maybeErrM (MCEUnknownOutRefError "lookupUtxos: unknown TxOutRef" oRef) id (txOutByRef oRef)) -- | look up the UTxOs the transaction consumes, and sum their values. txSkelInputValue :: (MonadBlockChainBalancing m) => TxSkel -> m Api.Value -txSkelInputValue = (foldMap (Api.txOutValue . txOutV2FromLedger) <$>) . txSkelInputUtxos +txSkelInputValue = (foldMap Api.txOutValue <$>) . txSkelInputUtxos -- | Look up the data on UTxOs the transaction consumes. txSkelInputData :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.DatumHash Api.Datum) @@ -404,7 +381,7 @@ txSkelInputData skel = do Api.OutputDatumHash dHash -> Just dHash ) . Map.elems - <$> txSkelInputUtxosPl skel + <$> txSkelInputUtxos skel Map.fromList <$> mapM ( \dHash -> @@ -515,13 +492,13 @@ instance (MonadTransControl t, MonadError MockChainError m, Monad (t m)) => Mona instance (MonadTrans t, MonadBlockChainBalancing m, Monad (t m), MonadError MockChainError (AsTrans t m)) => MonadBlockChainBalancing (AsTrans t m) where getParams = lift getParams validatorFromHash = lift . validatorFromHash - utxosAtLedger = lift . utxosAtLedger - txOutByRefLedger = lift . txOutByRefLedger + utxosAt = lift . utxosAt + txOutByRef = lift . txOutByRef datumFromHash = lift . datumFromHash publish = lift . publish instance (MonadTrans t, MonadBlockChainWithoutValidation m, Monad (t m), MonadError MockChainError (AsTrans t m)) => MonadBlockChainWithoutValidation (AsTrans t m) where - allUtxosLedger = lift allUtxosLedger + allUtxos = lift allUtxos setParams = lift . setParams currentSlot = lift currentSlot awaitSlot = lift . awaitSlot @@ -559,13 +536,13 @@ deriving via (AsTrans (StateT s) m) instance (MonadBlockChain m) => MonadBlockCh instance (MonadBlockChainBalancing m) => MonadBlockChainBalancing (ListT m) where getParams = lift getParams validatorFromHash = lift . validatorFromHash - utxosAtLedger = lift . utxosAtLedger - txOutByRefLedger = lift . txOutByRefLedger + utxosAt = lift . utxosAt + txOutByRef = lift . txOutByRef datumFromHash = lift . datumFromHash publish = lift . publish instance (MonadBlockChainWithoutValidation m) => MonadBlockChainWithoutValidation (ListT m) where - allUtxosLedger = lift allUtxosLedger + allUtxos = lift allUtxos setParams = lift . setParams currentSlot = lift currentSlot awaitSlot = lift . awaitSlot diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index 122cdb784..5337d90ef 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -101,7 +101,7 @@ data MockChainSt = MockChainSt mcstToSkelContext :: MockChainSt -> SkelContext mcstToSkelContext MockChainSt {..} = SkelContext - (txOutV2FromLedger <$> getIndex mcstIndex) + (getIndex mcstIndex) (Map.map fst mcstDatums) -- | Generating an emulated state for the emulator from a mockchain state and @@ -302,10 +302,10 @@ utxoIndex0 = utxoIndex0From def -- * Direct Interpretation of Operations -getIndex :: Ledger.UtxoIndex -> Map Api.TxOutRef Ledger.TxOut +getIndex :: Ledger.UtxoIndex -> Map Api.TxOutRef Api.TxOut getIndex = Map.fromList - . map (bimap Ledger.fromCardanoTxIn (Ledger.TxOut . toCtxTxTxOut)) + . map (bimap Ledger.fromCardanoTxIn (Ledger.fromCardanoTxOutToPV2TxInfoTxOut . toCtxTxTxOut)) . Map.toList . Cardano.unUTxO where @@ -322,13 +322,13 @@ getIndex = instance (Monad m) => MonadBlockChainBalancing (MockChainT m) where getParams = gets mcstParams validatorFromHash valHash = gets $ Map.lookup valHash . mcstValidators - txOutByRefLedger outref = gets $ Map.lookup outref . getIndex . mcstIndex + txOutByRef outref = gets $ Map.lookup outref . getIndex . mcstIndex datumFromHash datumHash = (txSkelOutUntypedDatum <=< Just . fst <=< Map.lookup datumHash) <$> gets mcstDatums - utxosAtLedger addr = filter ((addr ==) . outputAddress . txOutV2FromLedger . snd) <$> allUtxosLedger + utxosAt addr = filter ((addr ==) . outputAddress . snd) <$> allUtxos publish l = tell [l] instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where - allUtxosLedger = gets $ Map.toList . getIndex . mcstIndex + allUtxos = gets $ Map.toList . getIndex . mcstIndex setParams newParams = modify (\st -> st {mcstParams = newParams}) currentSlot = gets mcstCurrentSlot awaitSlot s = modify' (\st -> st {mcstCurrentSlot = max s (mcstCurrentSlot st)}) >> currentSlot @@ -356,11 +356,11 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- rich-enough context for the transaction generation to succeed. insData <- txSkelInputData skel insValidators <- txSkelInputValidators skel - insMap <- txSkelInputUtxosPl skel - refInsMap <- txSkelReferenceInputUtxosPl skel + insMap <- txSkelInputUtxos skel + refInsMap <- txSkelReferenceInputUtxos skel collateralInsMap <- case mCollaterals of Nothing -> return Map.empty - Just (collateralIns, _) -> lookupUtxosPl $ Set.toList collateralIns + Just (collateralIns, _) -> lookupUtxos $ Set.toList collateralIns -- We attempt to generate the transaction associated with the balanced -- skeleton and the retrieved data. This is an internal generation, there is -- no validation involved yet. diff --git a/src/Cooked/MockChain/Staged.hs b/src/Cooked/MockChain/Staged.hs index 17016929f..d06709697 100644 --- a/src/Cooked/MockChain/Staged.hs +++ b/src/Cooked/MockChain/Staged.hs @@ -72,12 +72,12 @@ data MockChainBuiltin a where GetParams :: MockChainBuiltin Emulator.Params SetParams :: Emulator.Params -> MockChainBuiltin () ValidateTxSkel :: TxSkel -> MockChainBuiltin Ledger.CardanoTx - TxOutByRefLedger :: Api.TxOutRef -> MockChainBuiltin (Maybe Ledger.TxOut) + TxOutByRef :: Api.TxOutRef -> MockChainBuiltin (Maybe Api.TxOut) GetCurrentSlot :: MockChainBuiltin Ledger.Slot AwaitSlot :: Ledger.Slot -> MockChainBuiltin Ledger.Slot DatumFromHash :: Api.DatumHash -> MockChainBuiltin (Maybe Api.Datum) - AllUtxosLedger :: MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] - UtxosAtLedger :: Api.Address -> MockChainBuiltin [(Api.TxOutRef, Ledger.TxOut)] + AllUtxos :: MockChainBuiltin [(Api.TxOutRef, Api.TxOut)] + UtxosAt :: Api.Address -> MockChainBuiltin [(Api.TxOutRef, Api.TxOut)] ValidatorFromHash :: Script.ValidatorHash -> MockChainBuiltin (Maybe (Script.Versioned Script.Validator)) Publish :: MockChainLogEntry -> MockChainBuiltin () -- | The empty set of traces @@ -124,13 +124,13 @@ instance InterpLtl (UntypedTweak InterpMockChain) MockChainBuiltin InterpMockCha (_, skel') <- lift $ runTweakInChain now skel put later validateTxSkel skel' - interpBuiltin (TxOutByRefLedger o) = txOutByRefLedger o + interpBuiltin (TxOutByRef o) = txOutByRef o interpBuiltin GetCurrentSlot = currentSlot interpBuiltin (AwaitSlot s) = awaitSlot s interpBuiltin (DatumFromHash h) = datumFromHash h interpBuiltin (ValidatorFromHash h) = validatorFromHash h - interpBuiltin AllUtxosLedger = allUtxosLedger - interpBuiltin (UtxosAtLedger address) = utxosAtLedger address + interpBuiltin AllUtxos = allUtxos + interpBuiltin (UtxosAt address) = utxosAt address interpBuiltin Empty = mzero interpBuiltin (Alt l r) = interpLtl l `mplus` interpLtl r interpBuiltin (Fail msg) = fail msg @@ -198,13 +198,13 @@ instance MonadError MockChainError StagedMockChain where instance MonadBlockChainBalancing StagedMockChain where getParams = singletonBuiltin GetParams datumFromHash = singletonBuiltin . DatumFromHash - txOutByRefLedger = singletonBuiltin . TxOutByRefLedger - utxosAtLedger = singletonBuiltin . UtxosAtLedger + txOutByRef = singletonBuiltin . TxOutByRef + utxosAt = singletonBuiltin . UtxosAt validatorFromHash = singletonBuiltin . ValidatorFromHash publish = singletonBuiltin . Publish instance MonadBlockChainWithoutValidation StagedMockChain where - allUtxosLedger = singletonBuiltin AllUtxosLedger + allUtxos = singletonBuiltin AllUtxos setParams = singletonBuiltin . SetParams currentSlot = singletonBuiltin GetCurrentSlot awaitSlot = singletonBuiltin . AwaitSlot diff --git a/src/Cooked/MockChain/UtxoSearch.hs b/src/Cooked/MockChain/UtxoSearch.hs index 3ed50d709..d517f5873 100644 --- a/src/Cooked/MockChain/UtxoSearch.hs +++ b/src/Cooked/MockChain/UtxoSearch.hs @@ -5,9 +5,7 @@ module Cooked.MockChain.UtxoSearch ( UtxoSearch, runUtxoSearch, allUtxosSearch, - allUtxosLedgerSearch, utxosAtSearch, - utxosAtLedgerSearch, utxosFromCardanoTxSearch, txOutByRefSearch, filterWith, @@ -54,21 +52,11 @@ runUtxoSearch = ListT.toList allUtxosSearch :: (MonadBlockChain m) => UtxoSearch m Api.TxOut allUtxosSearch = allUtxos >>= ListT.fromFoldable --- | Like 'allUtxosSearch', but returns a Ledger-level representation of the --- transaction outputs, which might contain more information. -allUtxosLedgerSearch :: (MonadBlockChain m) => UtxoSearch m Ledger.TxOut -allUtxosLedgerSearch = allUtxosLedger >>= ListT.fromFoldable - -- | Search all 'TxOutRef's at a certain address, together with their -- 'TxInfo'-'TxOut'. utxosAtSearch :: (MonadBlockChainBalancing m, ToAddress addr) => addr -> UtxoSearch m Api.TxOut utxosAtSearch = utxosAt . toAddress >=> ListT.fromFoldable --- | Like 'utxosAtSearch', but returns a Ledger-level representation of the --- transaction outputs, which might contain more information. -utxosAtLedgerSearch :: (MonadBlockChainBalancing m, ToAddress addr) => addr -> UtxoSearch m Ledger.TxOut -utxosAtLedgerSearch = utxosAtLedger . toAddress >=> ListT.fromFoldable - -- | Search all 'TxOutRef's of a transaction, together with their -- 'TxInfo'-'TxOut'. utxosFromCardanoTxSearch :: (Monad m) => Ledger.CardanoTx -> UtxoSearch m Api.TxOut diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 23e415a40..db2401858 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -1100,7 +1100,7 @@ txSkelReferenceTxOutRefs TxSkel {..} = Set.toList txSkelInsReference -- reference inputs in inputs redeemers <> mapMaybe txSkelReferenceScript (Map.elems txSkelIns) - -- reference inputs in porposals redeemers + -- reference inputs in proposals redeemers <> mapMaybe (txSkelReferenceScript . snd) (mapMaybe txSkelProposalWitness txSkelProposals) -- reference inputs in mints redeemers <> mapMaybe (txSkelReferenceScript . fst . snd) (Map.toList txSkelMints) From 14da58f7db2f57e31f2b8d0ec59334f6361859a1 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 31 Jul 2024 14:29:38 +0200 Subject: [PATCH 24/71] update capi --- cabal.project | 9 +++---- flake.lock | 6 ++--- src/Cooked/MockChain/Balancing.hs | 30 ++++++++++++++++++++-- src/Cooked/MockChain/Direct.hs | 18 ++++++++++++- src/Cooked/MockChain/GenerateTx/Witness.hs | 6 ++--- tests/Cooked/ProposingScriptSpec.hs | 2 +- 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/cabal.project b/cabal.project index 4721e608c..7e81c2dea 100644 --- a/cabal.project +++ b/cabal.project @@ -10,8 +10,8 @@ package cardano-crypto-praos source-repository-package type: git - location: https://github.com/IntersectMBO/cardano-node-emulator - tag: 645c1d04ba1c90aa76ad720e66215e754568b81e + location: https://github.com/tweag/cardano-node-emulator-forked/ + tag: 3087d61fc26929dc29b97bf4d25a519907174324 subdir: cardano-node-emulator plutus-ledger @@ -69,13 +69,12 @@ package plutus-ledger haddock-options: "--optghc=-fplugin-opt PlutusTx.Plugin:defer-errors" constraints: - cardano-api ^>= 8.42 + cardano-api ^>= 8.46 source-repository-package type: git location: https://github.com/input-output-hk/quickcheck-contractmodel - tag: fdfbbfed09b48dada31bcb4e5b0e0e6d2cffba7f - --sha256: sha256-25BrEBetu35rAlNM+rZijjgS4cIG/Q4gcd5YYNqn2Vk= + tag: b19a7689a0d40ba3c7f91da87ef5fbcf20f3926c subdir: quickcheck-contractmodel quickcheck-threatmodel diff --git a/flake.lock b/flake.lock index 7706bb0e8..b7fa84f75 100644 --- a/flake.lock +++ b/flake.lock @@ -57,11 +57,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1721051935, - "narHash": "sha256-Cb9yT0wk1+ExeAPCqSImSM1AaB7cmKUbW+7oqIEDYcE=", + "lastModified": 1722428703, + "narHash": "sha256-8oQgiehHJymg0l2Cd+97zd7+qeXwpYKnKY7lAB5Om5I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "87d7e80daaa1c8c8d9cf399c2b49ea3e0b0d2b44", + "rev": "e26d97050286eb781956720c1e868a9d587e48c0", "type": "github" }, "original": { diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index ee9814630..3b53e0645 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -7,6 +7,7 @@ import Cardano.Api.Ledger qualified as Cardano import Cardano.Api.Shelley qualified as Cardano import Cardano.Node.Emulator.Internal.Node.Params qualified as Emulator import Cardano.Node.Emulator.Internal.Node.Validation qualified as Emulator +import Control.Monad import Control.Monad.Except import Cooked.Conversion import Cooked.MockChain.BlockChain @@ -17,13 +18,18 @@ import Cooked.Output import Cooked.Skeleton import Cooked.Wallet import Data.Bifunctor +import Data.Either.Combinators import Data.Function import Data.List +import Data.Map (Map) import Data.Map qualified as Map import Data.Maybe import Data.Ratio qualified as Rat import Data.Set (Set) import Data.Set qualified as Set +import Ledger.Index qualified as Ledger +import Ledger.Tx qualified as Ledger +import Ledger.Tx.CardanoAPI qualified as Ledger import Optics.Core import Plutus.Script.Utils.Ada qualified as Script import Plutus.Script.Utils.Value qualified as Script @@ -273,8 +279,28 @@ estimateTxSkelFee skel fee collateralIns returnCollateralWallet = do Right txBody -> return txBody -- We retrieve the estimate number of required witness in the transaction let nkeys = Cardano.estimateTransactionKeyWitnessCount txBodyContent - -- We return an accurate estimate of the resulting transaction fee - return $ Emulator.unCoin $ Cardano.evaluateTransactionFee Cardano.ShelleyBasedEraConway (Emulator.pEmulatorPParams params) txBody nkeys 0 + index <- toIndex <$> lookupUtxos (txSkelKnownTxOutRefs skel <> Set.toList collateralIns) + case index of + Left err -> throwError $ MCEGenerationError err + -- We return an accurate estimate of the resulting transaction fee + Right index' -> + return . Emulator.unCoin $ + Cardano.calculateMinTxFee Cardano.ShelleyBasedEraConway (Emulator.pEmulatorPParams params) index' txBody nkeys + where + toIndex :: Map Api.TxOutRef Ledger.TxOut -> Either GenerateTxError Ledger.UtxoIndex + toIndex innerMap = do + let (txOutRefL, txOutL) = unzip $ Map.toList innerMap + txInL <- mapLeft (ToCardanoError "toIndex: unable to generate TxOut") $ forM txOutRefL Ledger.toCardanoTxIn + txOutL' <- forM (Ledger.getTxOut <$> txOutL) toCtxUTxOTxOut + return $ Cardano.UTxO $ Map.fromList $ zip txInL txOutL' + toCtxUTxOTxOut :: Cardano.TxOut Cardano.CtxTx era -> Either GenerateTxError (Cardano.TxOut Cardano.CtxUTxO era) + toCtxUTxOTxOut (Cardano.TxOut addr val d refS) = do + dat <- case d of + Cardano.TxOutDatumNone -> return Cardano.TxOutDatumNone + Cardano.TxOutDatumInTx _ _ -> Left $ GenerateTxErrorGeneral "Wrong datum kind" + Cardano.TxOutDatumHash s h -> return $ Cardano.TxOutDatumHash s h + Cardano.TxOutDatumInline s sd -> return $ Cardano.TxOutDatumInline s sd + return $ Cardano.TxOut addr val dat refS -- | This creates a balanced skeleton from a given skeleton and fee. In other -- words, this ensures that the following equation holds: input value + minted diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index c2a977eca..ec98c56b2 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -52,7 +52,7 @@ import PlutusLedgerApi.V3 qualified as Api -- complexity the 'Contract' monad introduces. -- -- Running a 'MockChain' produces a 'UtxoState', a simplified view on --- 'Api.UtxoIndex', which we also keep in our state. +-- 'Ledger.UtxoIndex', which we also keep in our state. mcstToUtxoState :: MockChainSt -> UtxoState mcstToUtxoState MockChainSt {mcstIndex, mcstDatums} = @@ -310,6 +310,22 @@ getIndex = Cardano.TxOutDatumInline s sd -> Cardano.TxOutDatumInline s sd in Cardano.TxOut addr val dat refS +toIndex :: Map Api.TxOutRef Ledger.TxOut -> Either Ledger.ToCardanoError Ledger.UtxoIndex +toIndex innerMap = do + let (txOutRefL, txOutL) = unzip $ Map.toList innerMap + txInL <- forM txOutRefL Ledger.toCardanoTxIn + txOutL' <- forM (Ledger.getTxOut <$> txOutL) toCtxUTxOTxOut + return $ Cardano.UTxO $ Map.fromList $ zip txInL txOutL' + where + toCtxUTxOTxOut :: Cardano.TxOut Cardano.CtxTx era -> Either Ledger.ToCardanoError (Cardano.TxOut Cardano.CtxUTxO era) + toCtxUTxOTxOut (Cardano.TxOut addr val d refS) = do + dat <- case d of + Cardano.TxOutDatumNone -> return Cardano.TxOutDatumNone + Cardano.TxOutDatumInTx _ _ -> Left $ Ledger.TxBodyError "Wrong datum kind" + Cardano.TxOutDatumHash s h -> return $ Cardano.TxOutDatumHash s h + Cardano.TxOutDatumInline s sd -> return $ Cardano.TxOutDatumInline s sd + return $ Cardano.TxOut addr val dat refS + instance (Monad m) => MonadBlockChainBalancing (MockChainT m) where getParams = gets mcstParams validatorFromHash valHash = gets $ Map.lookup valHash . mcstValidators diff --git a/src/Cooked/MockChain/GenerateTx/Witness.hs b/src/Cooked/MockChain/GenerateTx/Witness.hs index a52f9ac3e..276269660 100644 --- a/src/Cooked/MockChain/GenerateTx/Witness.hs +++ b/src/Cooked/MockChain/GenerateTx/Witness.hs @@ -25,9 +25,9 @@ import PlutusLedgerApi.V3 qualified as Api type WitnessGen a = TxGen (Map Api.TxOutRef Api.TxOut) a -- | Translates a given credential to a reward account. -toRewardAccount :: Api.Credential -> WitnessGen (Cardano.RewardAcnt Crypto.StandardCrypto) +toRewardAccount :: Api.Credential -> WitnessGen (Cardano.RewardAccount Crypto.StandardCrypto) toRewardAccount cred = - Cardano.RewardAcnt Cardano.Testnet <$> case cred of + Cardano.RewardAccount Cardano.Testnet <$> case cred of Api.ScriptCredential scriptHash -> do Cardano.ScriptHash cHash <- throwOnToCardanoError @@ -38,8 +38,6 @@ toRewardAccount cred = Cardano.StakeKeyHash pkHash <- throwOnToCardanoError "toRewardAccount: Unable to convert private key hash." - -- TODO: we take the pubkeyHash, maybe we should take the stakehash if - -- any exists. The nature of the stake address can be confusing. (Ledger.toCardanoStakeKeyHash pubkeyHash) return $ Cardano.KeyHashObj pkHash diff --git a/tests/Cooked/ProposingScriptSpec.hs b/tests/Cooked/ProposingScriptSpec.hs index fa0c67936..95c945ad5 100644 --- a/tests/Cooked/ProposingScriptSpec.hs +++ b/tests/Cooked/ProposingScriptSpec.hs @@ -25,7 +25,7 @@ checkParameterChangeScript _ ctx = in case Api.ppGovernanceAction proposalProcedure of Api.ParameterChange _ (Api.ChangedParameters dat) _ -> let innerMap = PlutusTx.unsafeFromBuiltinData @(PlutusTx.Map PlutusTx.Integer PlutusTx.Integer) dat - in if innerMap PlutusTx.== PlutusTx.fromList [(0, 100)] then () else PlutusTx.traceError "wrong map" + in if innerMap PlutusTx.== PlutusTx.safeFromList [(0, 100)] then () else PlutusTx.traceError "wrong map" _ -> PlutusTx.traceError "Wrong proposal procedure" checkProposingScript :: Script.Versioned Script.Script From 1ccb4b1a2473ae7f31541074b3be56830205b60f Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 31 Jul 2024 18:42:14 +0200 Subject: [PATCH 25/71] Proper script hash computation for all plutus versions --- CHANGELOG.md | 4 +++- src/Cooked/Conversion/ToScriptHash.hs | 16 ++++++---------- src/Cooked/MockChain/GenerateTx/Witness.hs | 6 +++--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ad33523..ef7ee6e7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - `toInitDistWithMinAda` and `unsafeToInitDistWithMinAda` to ensure the initial distribution only provides outputs with the required minimal ada based on default parameters. +- All kinds of scripts can now be used as reference scripts. ### Removed @@ -25,7 +26,8 @@ ### Fixed -- All kinds of scripts can now be used as reference scripts. +- A bug where the script hashes would not be computed properly for early plutus + version (V1 and V2). ## [[4.0.0]](https://github.com/tweag/cooked-validators/releases/tag/v4.0.0) - 2024-06-28 diff --git a/src/Cooked/Conversion/ToScriptHash.hs b/src/Cooked/Conversion/ToScriptHash.hs index 22407a92c..bc8165f61 100644 --- a/src/Cooked/Conversion/ToScriptHash.hs +++ b/src/Cooked/Conversion/ToScriptHash.hs @@ -2,9 +2,8 @@ module Cooked.Conversion.ToScriptHash where import Cooked.Conversion.ToScript -import Plutus.Script.Utils.Scripts qualified as Script hiding (scriptHash) +import Plutus.Script.Utils.Scripts qualified as Script import Plutus.Script.Utils.Typed qualified as Script -import Plutus.Script.Utils.V3.Scripts qualified as Script (scriptHash) import PlutusLedgerApi.V3 qualified as Api class ToScriptHash a where @@ -13,17 +12,14 @@ class ToScriptHash a where instance ToScriptHash Api.ScriptHash where toScriptHash = id -instance ToScriptHash Script.Script where - toScriptHash = Script.scriptHash - -instance ToScriptHash Api.SerialisedScript where - toScriptHash = toScriptHash . Script.Script +instance ToScriptHash Script.MintingPolicyHash where + toScriptHash (Script.MintingPolicyHash hash) = Script.ScriptHash hash instance ToScriptHash Script.ValidatorHash where - toScriptHash (Script.ValidatorHash h) = Script.ScriptHash h + toScriptHash (Script.ValidatorHash hash) = Script.ScriptHash hash instance ToScriptHash (Script.Versioned Script.Script) where - toScriptHash (Script.Versioned s _) = toScriptHash s + toScriptHash = Script.scriptHash instance ToScriptHash (Script.Versioned Script.Validator) where toScriptHash = toScriptHash . toScript @@ -32,4 +28,4 @@ instance ToScriptHash (Script.TypedValidator a) where toScriptHash = toScriptHash . toScript instance ToScriptHash (Script.Versioned Script.MintingPolicy) where - toScriptHash = toScriptHash . toScript + toScriptHash = toScriptHash . Script.mintingPolicyHash diff --git a/src/Cooked/MockChain/GenerateTx/Witness.hs b/src/Cooked/MockChain/GenerateTx/Witness.hs index a52f9ac3e..5e31129fd 100644 --- a/src/Cooked/MockChain/GenerateTx/Witness.hs +++ b/src/Cooked/MockChain/GenerateTx/Witness.hs @@ -45,8 +45,8 @@ toRewardAccount cred = -- | Translate a script and a reference script utxo into into either a plutus -- script or a reference input containing the right script -toPlutusScriptOrReferenceInput :: Api.SerialisedScript -> Maybe Api.TxOutRef -> WitnessGen (Cardano.PlutusScriptOrReferenceInput lang) -toPlutusScriptOrReferenceInput script Nothing = return $ Cardano.PScript $ Cardano.PlutusScriptSerialised script +toPlutusScriptOrReferenceInput :: Script.Versioned Script.Script -> Maybe Api.TxOutRef -> WitnessGen (Cardano.PlutusScriptOrReferenceInput lang) +toPlutusScriptOrReferenceInput (Script.Versioned (Script.Script script) _) Nothing = return $ Cardano.PScript $ Cardano.PlutusScriptSerialised script toPlutusScriptOrReferenceInput script (Just scriptOutRef) = do referenceScriptsMap <- asks $ Map.mapMaybe (^. outputReferenceScriptL) refScriptHash <- @@ -69,7 +69,7 @@ toPlutusScriptOrReferenceInput script (Just scriptOutRef) = do -- | Translates a script with its associated redeemer and datum to a script -- witness. toScriptWitness :: (ToScript a) => a -> TxSkelRedeemer -> Cardano.ScriptDatum b -> WitnessGen (Cardano.ScriptWitness b Cardano.ConwayEra) -toScriptWitness (toScript -> (Script.Versioned (Script.Script script) version)) (TxSkelRedeemer {..}) datum = +toScriptWitness (toScript -> script@(Script.Versioned _ version)) (TxSkelRedeemer {..}) datum = let scriptData = case txSkelRedeemer of EmptyRedeemer -> Ledger.toCardanoScriptData $ Api.toBuiltinData () SomeRedeemer s -> Ledger.toCardanoScriptData $ Api.toBuiltinData s From 5114f606c039920ec5074e99ecf7c2ae08c1fed9 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 1 Aug 2024 00:13:46 +0200 Subject: [PATCH 26/71] helpers and qol changes --- CHANGELOG.md | 6 +++ src/Cooked/MockChain/BlockChain.hs | 71 +++++++++-------------------- src/Cooked/Skeleton.hs | 14 +++--- src/Cooked/Tweak/TamperDatum.hs | 2 +- src/Cooked/Validators.hs | 30 +++++++++--- src/Cooked/Wallet.hs | 5 ++ tests/Cooked/ProposingScriptSpec.hs | 2 +- 7 files changed, 66 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef7ee6e7f..c77dcd6c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ distribution only provides outputs with the required minimal ada based on default parameters. - All kinds of scripts can now be used as reference scripts. +- `validateTxSkel_` which validates a skeleton and ignores the output. +- `txSkelMintsFromList'` which only allows one redeemer per minting policy. +- `validatorToTypedValidatorV2` +- `walletPKHashToWallet` that retrives a wallet from a pkh. It used to be + present but somehow disapeared. ### Removed @@ -23,6 +28,7 @@ constructors: `txSkelSomeRedeemer`, `txSkelEmptyRedeemer`, `txSkelSomeRedeemerAndReferenceScript`, `txSkelEmptyRedeemerAndReferenceScript`. +- `mkProposingScript` changed to `mkScript` ### Fixed diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index fcdf38e79..ed4f9c2ff 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -49,6 +49,7 @@ module Cooked.MockChain.BlockChain lookupUtxos, lookupUtxosPl, validateTxSkel', + validateTxSkel_, txSkelProposalsDeposit, govActionDeposit, ) @@ -163,6 +164,10 @@ class (MonadBlockChainWithoutValidation m) => MonadBlockChain m where validateTxSkel' :: (MonadBlockChain m) => TxSkel -> m [Api.TxOutRef] validateTxSkel' = (map fst . utxosFromCardanoTx <$>) . validateTxSkel +-- | Validates a skeleton, and erases the outputs +validateTxSkel_ :: (MonadBlockChain m) => TxSkel -> m () +validateTxSkel_ = void . validateTxSkel + allUtxos :: (MonadBlockChainWithoutValidation m) => m [(Api.TxOutRef, Api.TxOut)] allUtxos = fmap (second txOutV2FromLedger) <$> allUtxosLedger @@ -200,16 +205,9 @@ resolveDatum out = do Api.OutputDatumHash datumHash -> datumFromHash datumHash Api.OutputDatum datum -> return $ Just datum Api.NoOutputDatum -> return Nothing - return $ - ( \mDat -> - ConcreteOutput - (out ^. outputOwnerL) - (out ^. outputStakingCredentialL) - mDat - (out ^. outputValueL) - (out ^. outputReferenceScriptL) - ) - <$> mDatum + return $ do + mDat <- mDatum + return $ (fromAbstractOutput out) {concreteOutputDatum = mDat} -- | Like 'resolveDatum', but also tries to use 'fromBuiltinData' to extract a -- datum of the suitable type. @@ -223,19 +221,11 @@ resolveTypedDatum :: m (Maybe (ConcreteOutput (OwnerType out) a (ValueType out) (ReferenceScriptType out))) resolveTypedDatum out = do mOut <- resolveDatum out - return $ - ( \out' -> do - let Api.Datum datum = out' ^. outputDatumL - dat <- Api.fromBuiltinData datum - return $ - ConcreteOutput - (out' ^. outputOwnerL) - (out' ^. outputStakingCredentialL) - dat - (out' ^. outputValueL) - (out' ^. outputReferenceScriptL) - ) - =<< mOut + return $ do + out' <- mOut + let Api.Datum datum = out' ^. outputDatumL + dat <- Api.fromBuiltinData datum + return $ (fromAbstractOutput out) {concreteOutputDatum = dat} -- | Try to resolve the validator that owns an output: If the output is owned by -- a public key, or if the validator's hash is not known (i.e. if @@ -252,16 +242,9 @@ resolveValidator out = Api.PubKeyCredential _ -> return Nothing Api.ScriptCredential (Api.ScriptHash hash) -> do mVal <- validatorFromHash (Script.ValidatorHash hash) - return $ - ( \val -> - ConcreteOutput - val - (out ^. outputStakingCredentialL) - (out ^. outputDatumL) - (out ^. outputValueL) - (out ^. outputReferenceScriptL) - ) - <$> mVal + return $ do + val <- mVal + return $ (fromAbstractOutput out) {concreteOutputOwner = val} -- | Try to resolve the reference script on an output: If the output has no -- reference script, or if the reference script's hash is not known (i.e. if @@ -273,22 +256,12 @@ resolveReferenceScript :: ) => out -> m (Maybe (ConcreteOutput (OwnerType out) (DatumType out) (ValueType out) (Script.Versioned Script.Validator))) -resolveReferenceScript out = - maybe - (return Nothing) - ( \(Api.ScriptHash hash) -> - ( \mVal -> - ConcreteOutput - (out ^. outputOwnerL) - (out ^. outputStakingCredentialL) - (out ^. outputDatumL) - (out ^. outputValueL) - . Just - <$> mVal - ) - <$> validatorFromHash (Script.ValidatorHash hash) - ) - $ outputReferenceScriptHash out +resolveReferenceScript out | Just (Api.ScriptHash hash) <- outputReferenceScriptHash out = do + mVal <- validatorFromHash (Script.ValidatorHash hash) + return $ do + val <- mVal + return $ (fromAbstractOutput out) {concreteOutputReferenceScript = Just val} +resolveReferenceScript _ = return Nothing outputDatumFromTxOutRef :: (MonadBlockChainWithoutValidation m) => Api.TxOutRef -> m (Maybe Api.OutputDatum) outputDatumFromTxOutRef = ((outputOutputDatum <$>) <$>) . txOutByRef diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 23e415a40..f5aa339db 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -35,6 +35,7 @@ module Cooked.Skeleton addToTxSkelMints, txSkelMintsToList, txSkelMintsFromList, + txSkelMintsFromList', txSkelMintsValue, txSkelOutValueL, txSkelOutDatumL, @@ -608,11 +609,7 @@ type TxSkelMints = -- In every case, if you add mints with a different redeemer for the same -- policy, the redeemer used in the right argument takes precedence. instance {-# OVERLAPPING #-} Semigroup TxSkelMints where - a <> b = - foldl - (flip addToTxSkelMints) - a - (txSkelMintsToList b) + a <> b = foldl (flip addToTxSkelMints) a (txSkelMintsToList b) instance {-# OVERLAPPING #-} Monoid TxSkelMints where mempty = Map.empty @@ -694,6 +691,11 @@ txSkelMintsToList = txSkelMintsFromList :: [(Script.Versioned Script.MintingPolicy, TxSkelRedeemer, Api.TokenName, Integer)] -> TxSkelMints txSkelMintsFromList = foldr addToTxSkelMints mempty +-- | Another smart constructor for 'TxSkelMints', where the redeemer and minting +-- policies are not duplicated. +txSkelMintsFromList' :: [(Script.Versioned Script.MintingPolicy, TxSkelRedeemer, [(Api.TokenName, Integer)])] -> TxSkelMints +txSkelMintsFromList' = txSkelMintsFromList . concatMap (\(mp, r, m) -> (\(tn, i) -> (mp, r, tn, i)) <$> m) + -- | The value described by a 'TxSkelMints' txSkelMintsValue :: TxSkelMints -> Api.Value txSkelMintsValue = @@ -1001,7 +1003,7 @@ data TxSkel where -- specifying how to spend it. You must make sure that -- -- - On 'TxOutRef's referencing UTxOs belonging to public keys, you use - -- the 'TxSkelEmptyRedeemer' constructor. + -- the 'txSkelEmptyRedeemer' smart constructor. -- -- - On 'TxOutRef's referencing UTxOs belonging to scripts, you must make -- sure that the type of the redeemer is appropriate for the script. diff --git a/src/Cooked/Tweak/TamperDatum.hs b/src/Cooked/Tweak/TamperDatum.hs index f00647bb2..f1d6fbafc 100644 --- a/src/Cooked/Tweak/TamperDatum.hs +++ b/src/Cooked/Tweak/TamperDatum.hs @@ -36,7 +36,7 @@ tamperDatumTweak :: ) => -- | Use this function to return 'Just' the changed datum, if you want to -- perform a change, and 'Nothing', if you want to leave it as-is. All datums - -- on outputs not paying to a validator of type @a@ are never touched. + -- on outputs that are not of type @a@ are never touched. (a -> Maybe a) -> m [a] tamperDatumTweak change = do diff --git a/src/Cooked/Validators.hs b/src/Cooked/Validators.hs index b99ad1423..aba8ff9c6 100644 --- a/src/Cooked/Validators.hs +++ b/src/Cooked/Validators.hs @@ -6,7 +6,9 @@ module Cooked.Validators alwaysFalseValidator, alwaysFalseProposingValidator, alwaysTrueProposingValidator, - mkProposingScript, + mkScript, + validatorToTypedValidator, + validatorToTypedValidatorV2, MockContract, ) where @@ -34,6 +36,20 @@ validatorToTypedValidator val = forwardingPolicy = Script.mkForwardingMintingPolicy vValidatorHash vMintingPolicy = Script.Versioned forwardingPolicy Script.PlutusV3 +validatorToTypedValidatorV2 :: Script.Validator -> Script.TypedValidator a +validatorToTypedValidatorV2 val = + Script.TypedValidator + { Script.tvValidator = vValidator, + Script.tvValidatorHash = vValidatorHash, + Script.tvForwardingMPS = vMintingPolicy, + Script.tvForwardingMPSHash = Script.mintingPolicyHash vMintingPolicy + } + where + vValidator = Script.Versioned val Script.PlutusV2 + vValidatorHash = Script.validatorHash vValidator + forwardingPolicy = Script.mkForwardingMintingPolicy vValidatorHash + vMintingPolicy = Script.Versioned forwardingPolicy Script.PlutusV2 + -- | The trivial validator that always succeds; this is in particular a -- sufficient target for the datum hijacking attack since we only want to show -- feasibility of the attack. @@ -54,14 +70,14 @@ instance Script.ValidatorTypes MockContract where -- | A dummy false proposing validator alwaysFalseProposingValidator :: Script.Versioned Script.Script alwaysFalseProposingValidator = - mkProposingScript $$(PlutusTx.compile [||PlutusTx.traceError "False proposing validator"||]) + mkScript $$(PlutusTx.compile [||PlutusTx.traceError "False proposing validator"||]) -- | A dummy true proposing validator alwaysTrueProposingValidator :: Script.Versioned Script.Script alwaysTrueProposingValidator = - mkProposingScript $$(PlutusTx.compile [||\_ _ -> ()||]) + mkScript $$(PlutusTx.compile [||\_ _ -> ()||]) --- | Helper to build a proposing script. This should come from --- plutus-script-utils at some point. -mkProposingScript :: PlutusTx.CompiledCode (PlutusTx.BuiltinData -> PlutusTx.BuiltinData -> ()) -> Script.Versioned Script.Script -mkProposingScript code = Script.Versioned (Script.Script $ Api.serialiseCompiledCode code) Script.PlutusV3 +-- | Helper to build a script. This should come from plutus-script-utils at some +-- point. +mkScript :: PlutusTx.CompiledCode (PlutusTx.BuiltinData -> PlutusTx.BuiltinData -> ()) -> Script.Versioned Script.Script +mkScript code = Script.Versioned (Script.Script $ Api.serialiseCompiledCode code) Script.PlutusV3 diff --git a/src/Cooked/Wallet.hs b/src/Cooked/Wallet.hs index 4506c83d1..e4d14b66b 100644 --- a/src/Cooked/Wallet.hs +++ b/src/Cooked/Wallet.hs @@ -7,6 +7,7 @@ module Cooked.Wallet ( knownWallets, wallet, walletPKHashToId, + walletPKHashToWallet, walletPK, walletStakingPK, walletPKHash, @@ -66,6 +67,10 @@ wallet j walletPKHashToId :: Api.PubKeyHash -> Maybe Int walletPKHashToId = (succ <$>) . flip elemIndex (walletPKHash <$> knownWallets) +-- | Retrieves the known wallet that corresponds to a public key hash +walletPKHashToWallet :: Api.PubKeyHash -> Maybe Wallet +walletPKHashToWallet pkh = wallet . fromIntegral <$> walletPKHashToId pkh + -- | Retrieves a wallet public key (PK) walletPK :: Wallet -> Ledger.PubKey walletPK = Ledger.unPaymentPubKey . Ledger.paymentPubKey diff --git a/tests/Cooked/ProposingScriptSpec.hs b/tests/Cooked/ProposingScriptSpec.hs index fa0c67936..e152c0046 100644 --- a/tests/Cooked/ProposingScriptSpec.hs +++ b/tests/Cooked/ProposingScriptSpec.hs @@ -29,7 +29,7 @@ checkParameterChangeScript _ ctx = _ -> PlutusTx.traceError "Wrong proposal procedure" checkProposingScript :: Script.Versioned Script.Script -checkProposingScript = mkProposingScript $$(PlutusTx.compile [||checkParameterChangeScript||]) +checkProposingScript = mkScript $$(PlutusTx.compile [||checkParameterChangeScript||]) testProposingScript :: (MonadBlockChain m) => Script.Versioned Script.Script -> TxGovAction -> m () testProposingScript script govAction = From 5c6adbe427c7cd6bb1b4cec81907a3e0131b30f7 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 1 Aug 2024 00:26:44 +0200 Subject: [PATCH 27/71] MockChainSt has its own module now --- cooked-validators.cabal | 1 + src/Cooked/MockChain.hs | 1 + src/Cooked/MockChain/Direct.hs | 186 +------------------------ src/Cooked/MockChain/MockChainSt.hs | 209 ++++++++++++++++++++++++++++ src/Cooked/MockChain/Staged.hs | 1 + 5 files changed, 219 insertions(+), 179 deletions(-) create mode 100644 src/Cooked/MockChain/MockChainSt.hs diff --git a/cooked-validators.cabal b/cooked-validators.cabal index f6839dc13..71f6167e9 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -44,6 +44,7 @@ library Cooked.MockChain.GenerateTx.Proposal Cooked.MockChain.GenerateTx.Witness Cooked.MockChain.MinAda + Cooked.MockChain.MockChainSt Cooked.MockChain.Staged Cooked.MockChain.Testing Cooked.MockChain.UtxoSearch diff --git a/src/Cooked/MockChain.hs b/src/Cooked/MockChain.hs index 78ba59c79..4c9a59411 100644 --- a/src/Cooked/MockChain.hs +++ b/src/Cooked/MockChain.hs @@ -6,6 +6,7 @@ import Cooked.MockChain.Balancing as X import Cooked.MockChain.BlockChain as X import Cooked.MockChain.Direct as X import Cooked.MockChain.MinAda as X +import Cooked.MockChain.MockChainSt as X (MockChainSt (..), mockChainSt0From) import Cooked.MockChain.Staged as X hiding ( MockChainLog, MockChainLogEntry, diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index c2a977eca..f0dc703c8 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -5,9 +5,6 @@ module Cooked.MockChain.Direct where import Cardano.Api qualified as Cardano -import Cardano.Api.Shelley qualified as Cardano -import Cardano.Ledger.Shelley.API qualified as Shelley -import Cardano.Ledger.Shelley.LedgerState qualified as Shelley import Cardano.Node.Emulator.Internal.Node qualified as Emulator import Control.Applicative import Control.Arrow @@ -16,31 +13,24 @@ import Control.Monad.Except import Control.Monad.Identity import Control.Monad.Reader import Control.Monad.State.Strict -import Cooked.Conversion.ToScript -import Cooked.Conversion.ToScriptHash import Cooked.InitialDistribution import Cooked.MockChain.Balancing import Cooked.MockChain.BlockChain import Cooked.MockChain.GenerateTx import Cooked.MockChain.MinAda +import Cooked.MockChain.MockChainSt import Cooked.MockChain.UtxoState import Cooked.Output import Cooked.Skeleton import Data.Bifunctor (bimap) import Data.Default -import Data.Either.Combinators (mapLeft) -import Data.List (foldl') import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map -import Data.Maybe (mapMaybe) import Data.Set qualified as Set import Ledger.Index qualified as Ledger import Ledger.Orphans () -import Ledger.Slot qualified as Ledger import Ledger.Tx qualified as Ledger import Ledger.Tx.CardanoAPI qualified as Ledger -import Optics.Core (view) -import Plutus.Script.Utils.Scripts qualified as Script import PlutusLedgerApi.V3 qualified as Api -- * Direct Emulation @@ -54,80 +44,6 @@ import PlutusLedgerApi.V3 qualified as Api -- Running a 'MockChain' produces a 'UtxoState', a simplified view on -- 'Api.UtxoIndex', which we also keep in our state. -mcstToUtxoState :: MockChainSt -> UtxoState -mcstToUtxoState MockChainSt {mcstIndex, mcstDatums} = - UtxoState - . foldr (\(address, utxoValueSet) acc -> Map.insertWith (<>) address utxoValueSet acc) Map.empty - . mapMaybe - ( extractPayload - . bimap - Ledger.fromCardanoTxIn - Ledger.fromCardanoTxOutToPV2TxInfoTxOut' - ) - . Map.toList - . Cardano.unUTxO - $ mcstIndex - where - extractPayload :: (Api.TxOutRef, Api.TxOut) -> Maybe (Api.Address, UtxoPayloadSet) - extractPayload (txOutRef, out@Api.TxOut {Api.txOutAddress, Api.txOutValue, Api.txOutDatum}) = - do - let mRefScript = outputReferenceScriptHash out - txSkelOutDatum <- - case txOutDatum of - Api.NoOutputDatum -> Just TxSkelOutNoDatum - Api.OutputDatum datum -> fst <$> Map.lookup (Script.datumHash datum) mcstDatums - Api.OutputDatumHash hash -> fst <$> Map.lookup hash mcstDatums - return - ( txOutAddress, - UtxoPayloadSet [UtxoPayload txOutRef txOutValue txSkelOutDatum mRefScript] - ) - --- | Slightly more concrete version of 'UtxoState', used to actually run the --- simulation. -data MockChainSt = MockChainSt - { mcstParams :: Emulator.Params, - mcstIndex :: Ledger.UtxoIndex, - -- map from datum hash to (datum, count), where count is the number of UTxOs - -- that currently have the datum. This map is used to display the contents - -- of the state to the user, and to recover datums for transaction - -- generation. - mcstDatums :: Map Api.DatumHash (TxSkelOutDatum, Integer), - mcstValidators :: Map Script.ValidatorHash (Script.Versioned Script.Validator), - mcstCurrentSlot :: Ledger.Slot - } - deriving (Show) - --- | Generating an emulated state for the emulator from a mockchain state and --- some parameters, based on a standard initial state -mcstToEmulatedLedgerState :: MockChainSt -> Emulator.EmulatedLedgerState -mcstToEmulatedLedgerState MockChainSt {..} = - let els@(Emulator.EmulatedLedgerState le mps) = Emulator.initialState mcstParams - in els - { Emulator._ledgerEnv = le {Shelley.ledgerSlotNo = fromIntegral mcstCurrentSlot}, - Emulator._memPoolState = - mps - { Shelley.lsUTxOState = - Shelley.smartUTxOState - (Emulator.emulatorPParams mcstParams) - (Ledger.fromPlutusIndex mcstIndex) - (Emulator.Coin 0) - (Emulator.Coin 0) - def - (Emulator.Coin 0) - } - } - -instance Eq MockChainSt where - (MockChainSt params1 index1 datums1 validators1 currentSlot1) - == (MockChainSt params2 index2 datums2 validators2 currentSlot2) = - and - [ params1 == params2, - index1 == index2, - datums1 == datums2, - validators1 == validators2, - currentSlot1 == currentSlot2 - ] - newtype MockChainT m a = MockChainT {unMockChain :: StateT MockChainSt (ExceptT MockChainError m) a} deriving newtype (Functor, Applicative, MonadState MockChainSt, MonadError MockChainError) @@ -205,92 +121,6 @@ runMockChainFrom i0 = runIdentity . runMockChainTFrom i0 runMockChain :: MockChain a -> Either MockChainError (a, UtxoState) runMockChain = runIdentity . runMockChainT --- * Canonical initial values - -utxoState0 :: UtxoState -utxoState0 = mcstToUtxoState mockChainSt0 - -mockChainSt0 :: MockChainSt -mockChainSt0 = MockChainSt def utxoIndex0 Map.empty Map.empty 0 - --- * Initial `MockChainSt` from an initial distribution - -mockChainSt0From :: InitialDistribution -> MockChainSt -mockChainSt0From i0 = MockChainSt def (utxoIndex0From i0) (datumMap0From i0) (referenceScriptMap0From i0) 0 - -instance Default MockChainSt where - def = mockChainSt0 - --- | Reference scripts from initial distributions should be accounted for in the --- `MockChainSt` which is done using this function. -referenceScriptMap0From :: InitialDistribution -> Map Script.ValidatorHash (Script.Versioned Script.Validator) -referenceScriptMap0From (InitialDistribution initDist) = - -- This builds a map of entries from the reference scripts contained in the - -- initial distribution - Map.fromList $ mapMaybe unitMaybeFrom initDist - where - -- This takes a single output and returns a possible map entry when it - -- contains a reference script - unitMaybeFrom :: TxSkelOut -> Maybe (Script.ValidatorHash, Script.Versioned Script.Validator) - unitMaybeFrom (Pays output) = do - refScript <- view outputReferenceScriptL output - let vScript@(Script.Versioned script version) = toScript refScript - Api.ScriptHash scriptHash = toScriptHash vScript - return (Script.ValidatorHash scriptHash, Script.Versioned (Script.Validator script) version) - --- | Datums from initial distributions should be accounted for in the --- `MockChainSt` which is done using this function. -datumMap0From :: InitialDistribution -> Map Api.DatumHash (TxSkelOutDatum, Integer) -datumMap0From (InitialDistribution initDist) = - -- This concatenates singleton maps from inputs and accounts for the number of - -- occurrences of similar datums - foldl' (\m -> Map.unionWith (\(d, n1) (_, n2) -> (d, n1 + n2)) m . unitMapFrom) Map.empty initDist - where - -- This takes a single output and creates an empty map if it contains no - -- datum, or a singleton map if it contains one - unitMapFrom :: TxSkelOut -> Map Api.DatumHash (TxSkelOutDatum, Integer) - unitMapFrom txSkelOut = - let datum = view txSkelOutDatumL txSkelOut - in maybe Map.empty (flip Map.singleton (datum, 1) . Script.datumHash) $ txSkelOutUntypedDatum datum - --- | This creates the initial UtxoIndex from an initial distribution by --- submitting an initial transaction with the appropriate content: --- --- - inputs consist of a single dummy pseudo input --- --- - all non-ada assets in outputs are considered minted --- --- - outputs are translated from the `TxSkelOut` list in the initial --- distribution --- --- Two things to note: --- --- - We don't know what "Magic" means for the network ID (TODO) --- --- - The genesis key hash has been taken from --- https://github.com/input-output-hk/cardano-node/blob/543b267d75d3d448e1940f9ec04b42bd01bbb16b/cardano-api/test/Test/Cardano/Api/Genesis.hs#L60 -utxoIndex0From :: InitialDistribution -> Ledger.UtxoIndex -utxoIndex0From (InitialDistribution initDist) = case mkBody of - Left err -> error $ show err - -- TODO: There may be better ways to generate this initial state, see - -- createGenesisTransaction for instance - Right body -> Ledger.initialise [[Emulator.unsafeMakeValid $ Ledger.CardanoEmulatorEraTx $ Cardano.Tx body []]] - where - mkBody :: Either GenerateTxError (Cardano.TxBody Cardano.ConwayEra) - mkBody = do - value <- mapLeft (ToCardanoError "Value error") $ Ledger.toCardanoValue (foldl' (\v -> (v <>) . view txSkelOutValueL) mempty initDist) - let mintValue = flip (Cardano.TxMintValue Cardano.MaryEraOnwardsConway) (Cardano.BuildTxWith mempty) . Cardano.filterValue (/= Cardano.AdaAssetId) $ value - theNetworkId = Cardano.Testnet $ Cardano.NetworkMagic 42 - genesisKeyHash = Cardano.GenesisUTxOKeyHash $ Shelley.KeyHash "23d51e91ae5adc7ae801e9de4cd54175fb7464ec2680b25686bbb194" - inputs = [(Cardano.genesisUTxOPseudoTxIn theNetworkId genesisKeyHash, Cardano.BuildTxWith $ Cardano.KeyWitness Cardano.KeyWitnessForSpending)] - outputs <- mapM (generateTxOut theNetworkId) initDist - left (TxBodyError "Body error") $ - Cardano.createAndValidateTransactionBody Cardano.ShelleyBasedEraConway $ - Ledger.emptyTxBodyContent {Cardano.txMintValue = mintValue, Cardano.txOuts = outputs, Cardano.txIns = inputs} - -utxoIndex0 :: Ledger.UtxoIndex -utxoIndex0 = utxoIndex0From def - -- * Direct Interpretation of Operations getIndex :: Ledger.UtxoIndex -> Map Api.TxOutRef Ledger.TxOut @@ -378,9 +208,12 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- behavior could be subject to change in the future. Just err -> throwError (uncurry MCEValidationError err) -- Otherwise, we update known validators and datums. - Nothing -> do - modify' (\st -> st {mcstDatums = (mcstDatums st `removeMcstDatums` insData) `addMcstDatums` txSkelDataInOutputs skel}) - modify' (\st -> st {mcstValidators = mcstValidators st `Map.union` (txSkelValidatorsInOutputs skel <> txSkelReferenceScripts skel)}) + Nothing -> + modify' + ( removeDatums (Map.keys insData) + . addDatums (Map.toList $ txSkelDataInOutputs skel) + . addValidators (txSkelValidatorsInOutputs skel <> txSkelReferenceScripts skel) + ) -- We apply a change of slot when requested in the options when (txOptAutoSlotIncrease $ txSkelOpts skel) $ modify' (\st -> st {mcstCurrentSlot = mcstCurrentSlot st + 1}) @@ -388,8 +221,3 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where setParams oldParams -- We return the validated transaction return cardanoTx - where - addMcstDatums stored new = Map.unionWith (\(d, n1) (_, n2) -> (d, n1 + n2)) stored (Map.map (,1) new) - -- FIXME: is this correct? What happens if we remove several similar - -- datums? - removeMcstDatums = Map.differenceWith $ \(d, n) _ -> if n == 1 then Nothing else Just (d, n - 1) diff --git a/src/Cooked/MockChain/MockChainSt.hs b/src/Cooked/MockChain/MockChainSt.hs new file mode 100644 index 000000000..45b53dbf2 --- /dev/null +++ b/src/Cooked/MockChain/MockChainSt.hs @@ -0,0 +1,209 @@ +module Cooked.MockChain.MockChainSt where + +import Cardano.Api qualified as Cardano +import Cardano.Api.Shelley qualified as Cardano +import Cardano.Ledger.Shelley.API qualified as Shelley +import Cardano.Ledger.Shelley.LedgerState qualified as Shelley +import Cardano.Node.Emulator.Internal.Node qualified as Emulator +import Control.Arrow +import Cooked.Conversion.ToScript +import Cooked.Conversion.ToScriptHash +import Cooked.InitialDistribution +import Cooked.MockChain.GenerateTx (GenerateTxError (..), generateTxOut) +import Cooked.MockChain.UtxoState +import Cooked.Output +import Cooked.Skeleton +import Data.Bifunctor (bimap) +import Data.Default +import Data.Either.Combinators (mapLeft) +import Data.List (foldl') +import Data.Map.Strict (Map) +import Data.Map.Strict qualified as Map +import Data.Maybe (mapMaybe) +import Ledger.Index qualified as Ledger +import Ledger.Orphans () +import Ledger.Slot qualified as Ledger +import Ledger.Tx qualified as Ledger +import Ledger.Tx.CardanoAPI qualified as Ledger +import Optics.Core (view) +import Plutus.Script.Utils.Scripts qualified as Script +import PlutusLedgerApi.V3 qualified as Api + +-- | Slightly more concrete version of 'UtxoState', used to actually run the +-- simulation. +data MockChainSt = MockChainSt + { mcstParams :: Emulator.Params, + mcstIndex :: Ledger.UtxoIndex, + -- map from datum hash to (datum, count), where count is the number of UTxOs + -- that currently have the datum. This map is used to display the contents + -- of the state to the user, and to recover datums for transaction + -- generation. + mcstDatums :: Map Api.DatumHash (TxSkelOutDatum, Integer), + mcstValidators :: Map Script.ValidatorHash (Script.Versioned Script.Validator), + mcstCurrentSlot :: Ledger.Slot + } + deriving (Show, Eq) + +instance Default MockChainSt where + def = mockChainSt0 + +mcstToUtxoState :: MockChainSt -> UtxoState +mcstToUtxoState MockChainSt {mcstIndex, mcstDatums} = + UtxoState + . foldr (\(address, utxoValueSet) acc -> Map.insertWith (<>) address utxoValueSet acc) Map.empty + . mapMaybe + ( extractPayload + . bimap + Ledger.fromCardanoTxIn + Ledger.fromCardanoTxOutToPV2TxInfoTxOut' + ) + . Map.toList + . Cardano.unUTxO + $ mcstIndex + where + extractPayload :: (Api.TxOutRef, Api.TxOut) -> Maybe (Api.Address, UtxoPayloadSet) + extractPayload (txOutRef, out@Api.TxOut {Api.txOutAddress, Api.txOutValue, Api.txOutDatum}) = + do + let mRefScript = outputReferenceScriptHash out + txSkelOutDatum <- + case txOutDatum of + Api.NoOutputDatum -> Just TxSkelOutNoDatum + Api.OutputDatum datum -> fst <$> Map.lookup (Script.datumHash datum) mcstDatums + Api.OutputDatumHash hash -> fst <$> Map.lookup hash mcstDatums + return + ( txOutAddress, + UtxoPayloadSet [UtxoPayload txOutRef txOutValue txSkelOutDatum mRefScript] + ) + +-- | Generating an emulated state for the emulator from a mockchain state and +-- some parameters, based on a standard initial state +mcstToEmulatedLedgerState :: MockChainSt -> Emulator.EmulatedLedgerState +mcstToEmulatedLedgerState MockChainSt {..} = + let els@(Emulator.EmulatedLedgerState le mps) = Emulator.initialState mcstParams + in els + { Emulator._ledgerEnv = le {Shelley.ledgerSlotNo = fromIntegral mcstCurrentSlot}, + Emulator._memPoolState = + mps + { Shelley.lsUTxOState = + Shelley.smartUTxOState + (Emulator.emulatorPParams mcstParams) + (Ledger.fromPlutusIndex mcstIndex) + (Emulator.Coin 0) + (Emulator.Coin 0) + def + (Emulator.Coin 0) + } + } + +addDatums :: [(Api.DatumHash, TxSkelOutDatum)] -> MockChainSt -> MockChainSt +addDatums toAdd st@(MockChainSt {mcstDatums}) = + st + { mcstDatums = + foldl + ( \datumMap (dHash, dat) -> + Map.insertWith (\(d, n) (_, n') -> (d, n + n')) dHash (dat, 1) datumMap + ) + mcstDatums + toAdd + } + +removeDatums :: [Api.DatumHash] -> MockChainSt -> MockChainSt +removeDatums toRemove st@(MockChainSt {mcstDatums}) = + st + { mcstDatums = + foldl + (flip (Map.update (\(dat, n) -> (dat,) <$> minusMaybe n))) + mcstDatums + toRemove + } + where + -- This is unsafe as this assumes n >= 1 + minusMaybe :: Integer -> Maybe Integer + minusMaybe n | n == 1 = Nothing + minusMaybe n = Just $ n - 1 + +addValidators :: Map Script.ValidatorHash (Script.Versioned Script.Validator) -> MockChainSt -> MockChainSt +addValidators valMap st@(MockChainSt {mcstValidators}) = st {mcstValidators = Map.union valMap mcstValidators} + +-- * Canonical initial values + +utxoState0 :: UtxoState +utxoState0 = mcstToUtxoState mockChainSt0 + +mockChainSt0 :: MockChainSt +mockChainSt0 = MockChainSt def utxoIndex0 Map.empty Map.empty 0 + +-- * Initial `MockChainSt` from an initial distribution + +mockChainSt0From :: InitialDistribution -> MockChainSt +mockChainSt0From i0 = MockChainSt def (utxoIndex0From i0) (datumMap0From i0) (referenceScriptMap0From i0) 0 + +-- | Reference scripts from initial distributions should be accounted for in the +-- `MockChainSt` which is done using this function. +referenceScriptMap0From :: InitialDistribution -> Map Script.ValidatorHash (Script.Versioned Script.Validator) +referenceScriptMap0From (InitialDistribution initDist) = + -- This builds a map of entries from the reference scripts contained in the + -- initial distribution + Map.fromList $ mapMaybe unitMaybeFrom initDist + where + -- This takes a single output and returns a possible map entry when it + -- contains a reference script + unitMaybeFrom :: TxSkelOut -> Maybe (Script.ValidatorHash, Script.Versioned Script.Validator) + unitMaybeFrom (Pays output) = do + refScript <- view outputReferenceScriptL output + let vScript@(Script.Versioned script version) = toScript refScript + Api.ScriptHash scriptHash = toScriptHash vScript + return (Script.ValidatorHash scriptHash, Script.Versioned (Script.Validator script) version) + +-- | Datums from initial distributions should be accounted for in the +-- `MockChainSt` which is done using this function. +datumMap0From :: InitialDistribution -> Map Api.DatumHash (TxSkelOutDatum, Integer) +datumMap0From (InitialDistribution initDist) = + -- This concatenates singleton maps from inputs and accounts for the number of + -- occurrences of similar datums + foldl' (\m -> Map.unionWith (\(d, n1) (_, n2) -> (d, n1 + n2)) m . unitMapFrom) Map.empty initDist + where + -- This takes a single output and creates an empty map if it contains no + -- datum, or a singleton map if it contains one + unitMapFrom :: TxSkelOut -> Map Api.DatumHash (TxSkelOutDatum, Integer) + unitMapFrom txSkelOut = + let datum = view txSkelOutDatumL txSkelOut + in maybe Map.empty (flip Map.singleton (datum, 1) . Script.datumHash) $ txSkelOutUntypedDatum datum + +-- | This creates the initial UtxoIndex from an initial distribution by +-- submitting an initial transaction with the appropriate content: +-- +-- - inputs consist of a single dummy pseudo input +-- +-- - all non-ada assets in outputs are considered minted +-- +-- - outputs are translated from the `TxSkelOut` list in the initial +-- distribution +-- +-- Two things to note: +-- +-- - We don't know what "Magic" means for the network ID (TODO) +-- +-- - The genesis key hash has been taken from +-- https://github.com/input-output-hk/cardano-node/blob/543b267d75d3d448e1940f9ec04b42bd01bbb16b/cardano-api/test/Test/Cardano/Api/Genesis.hs#L60 +utxoIndex0From :: InitialDistribution -> Ledger.UtxoIndex +utxoIndex0From (InitialDistribution initDist) = case mkBody of + Left err -> error $ show err + -- TODO: There may be better ways to generate this initial state, see + -- createGenesisTransaction for instance + Right body -> Ledger.initialise [[Emulator.unsafeMakeValid $ Ledger.CardanoEmulatorEraTx $ Cardano.Tx body []]] + where + mkBody :: Either GenerateTxError (Cardano.TxBody Cardano.ConwayEra) + mkBody = do + value <- mapLeft (ToCardanoError "Value error") $ Ledger.toCardanoValue (foldl' (\v -> (v <>) . view txSkelOutValueL) mempty initDist) + let mintValue = flip (Cardano.TxMintValue Cardano.MaryEraOnwardsConway) (Cardano.BuildTxWith mempty) . Cardano.filterValue (/= Cardano.AdaAssetId) $ value + theNetworkId = Cardano.Testnet $ Cardano.NetworkMagic 42 + genesisKeyHash = Cardano.GenesisUTxOKeyHash $ Shelley.KeyHash "23d51e91ae5adc7ae801e9de4cd54175fb7464ec2680b25686bbb194" + inputs = [(Cardano.genesisUTxOPseudoTxIn theNetworkId genesisKeyHash, Cardano.BuildTxWith $ Cardano.KeyWitness Cardano.KeyWitnessForSpending)] + outputs <- mapM (generateTxOut theNetworkId) initDist + left (TxBodyError "Body error") $ + Cardano.createAndValidateTransactionBody Cardano.ShelleyBasedEraConway $ + Ledger.emptyTxBodyContent {Cardano.txMintValue = mintValue, Cardano.txOuts = outputs, Cardano.txIns = inputs} + +utxoIndex0 :: Ledger.UtxoIndex +utxoIndex0 = utxoIndex0From def diff --git a/src/Cooked/MockChain/Staged.hs b/src/Cooked/MockChain/Staged.hs index a61a6cff9..7bb1bdc7f 100644 --- a/src/Cooked/MockChain/Staged.hs +++ b/src/Cooked/MockChain/Staged.hs @@ -30,6 +30,7 @@ import Control.Monad.Trans.Writer (WriterT (runWriterT), tell) import Cooked.Ltl import Cooked.MockChain.BlockChain import Cooked.MockChain.Direct +import Cooked.MockChain.MockChainSt import Cooked.MockChain.UtxoState import Cooked.Skeleton import Cooked.Tweak.Common From db8439404a7a82ce54f4ee381e644b65f73d9b21 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 1 Aug 2024 00:48:06 +0200 Subject: [PATCH 28/71] Support for hashed datums in reference inputs --- CHANGELOG.md | 7 +++ cooked-validators.cabal | 2 + package.yaml | 1 + src/Cooked/MockChain/Balancing.hs | 2 +- src/Cooked/MockChain/BlockChain.hs | 55 +++++++++++------- src/Cooked/MockChain/Direct.hs | 9 +-- src/Cooked/MockChain/GenerateTx/Body.hs | 73 ++++++++++++++++++------ src/Cooked/Pretty/Cooked.hs | 21 +++++-- src/Cooked/Skeleton.hs | 32 ++++++----- tests/Cooked/BalancingSpec.hs | 2 +- tests/Cooked/InitialDistributionSpec.hs | 6 +- tests/Cooked/InlineDatumsSpec.hs | 2 +- tests/Cooked/ReferenceInputsSpec.hs | 75 +++++++++++++++++++------ 13 files changed, 204 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c77dcd6c8..5a1891413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - `validatorToTypedValidatorV2` - `walletPKHashToWallet` that retrives a wallet from a pkh. It used to be present but somehow disapeared. +- It is now possible to reference an output which has a hashed datum. +- `txSkelHashedData` the gives all the datum hashes in inputs and reference inputs. ### Removed @@ -29,6 +31,11 @@ `txSkelSomeRedeemerAndReferenceScript`, `txSkelEmptyRedeemerAndReferenceScript`. - `mkProposingScript` changed to `mkScript` +- `withDatumHashed` changed to `withUnresolvedDatumHash` +- `paysScriptDatumHashed` changed to `paysScriptUnresolvedDatumHash` +- `txSkelInputData` changed to `txSkelConsumedData` +- Pretty printing of hashed datum now includes the hash (and not only the + resolved datum). ### Fixed diff --git a/cooked-validators.cabal b/cooked-validators.cabal index 71f6167e9..03cd53ba9 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -110,6 +110,7 @@ library , cardano-api , cardano-crypto , cardano-data + , cardano-ledger-alonzo , cardano-ledger-conway , cardano-ledger-core , cardano-ledger-shelley @@ -203,6 +204,7 @@ test-suite spec , cardano-api , cardano-crypto , cardano-data + , cardano-ledger-alonzo , cardano-ledger-conway , cardano-ledger-core , cardano-ledger-shelley diff --git a/package.yaml b/package.yaml index e0fc963f0..6bd307a0a 100644 --- a/package.yaml +++ b/package.yaml @@ -11,6 +11,7 @@ dependencies: - cardano-api - cardano-crypto - cardano-data + - cardano-ledger-alonzo - cardano-ledger-core - cardano-ledger-shelley - cardano-ledger-conway diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index ee9814630..d58c75481 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -260,7 +260,7 @@ estimateTxSkelFee :: (MonadBlockChainBalancing m) => TxSkel -> Fee -> Collateral estimateTxSkelFee skel fee collateralIns returnCollateralWallet = do -- We retrieve the necessary data to generate the transaction body params <- getParams - managedData <- txSkelInputData skel + managedData <- txSkelHashedData skel managedTxOuts <- lookupUtxosPl $ txSkelKnownTxOutRefs skel <> Set.toList collateralIns managedValidators <- txSkelInputValidators skel -- We generate the transaction body content, handling errors in the meantime diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index ed4f9c2ff..038d26c99 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -45,7 +45,8 @@ module Cooked.MockChain.BlockChain txSkelReferenceInputUtxos, txSkelInputValidators, txSkelInputValue, - txSkelInputData, + txSkelHashedData, + txSkelConsumedData, lookupUtxos, lookupUtxosPl, validateTxSkel', @@ -340,28 +341,40 @@ lookupUtxos = txSkelInputValue :: (MonadBlockChainBalancing m) => TxSkel -> m Api.Value txSkelInputValue = (foldMap (Api.txOutValue . txOutV2FromLedger) <$>) . txSkelInputUtxos --- | Look up the data on UTxOs the transaction consumes. -txSkelInputData :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.DatumHash Api.Datum) -txSkelInputData skel = do - mDatumHashes <- - mapMaybe - ( \output -> - case output ^. outputDatumL of - Api.NoOutputDatum -> Nothing - Api.OutputDatum datum -> Just $ Script.datumHash datum - Api.OutputDatumHash dHash -> Just dHash - ) - . Map.elems - <$> txSkelInputUtxosPl skel - Map.fromList - <$> mapM - ( \dHash -> +-- | Looks up and resolves the hashed datums on UTxOs the transaction consumes +-- or references, which will be needed by the transaction body. +txSkelHashedData :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.DatumHash Api.Datum) +txSkelHashedData skel = do + let outputToDatumHashM output = case output ^. outputDatumL of + Api.OutputDatumHash dHash -> Just dHash + _ -> Nothing + (Map.elems -> inputTxOuts) <- txSkelInputUtxosPl skel + (Map.elems -> refInputTxOuts) <- txSkelReferenceInputUtxosPl skel + foldM + ( \dat dHash -> + maybeErrM + (MCEUnknownDatum "txSkelHashedData: Transaction input with unknown datum hash" dHash) + (\rDat -> Map.insert dHash rDat dat) + (datumFromHash dHash) + ) + Map.empty + (mapMaybe outputToDatumHashM $ inputTxOuts <> refInputTxOuts) + +-- | Looks us the data on UTxOs the transaction consumes. This corresponds to +-- the keys of what should be removed from the stored datums in our mockchain. +-- There can be duplicates, which is expected. +txSkelConsumedData :: (MonadBlockChainBalancing m) => TxSkel -> m [Api.DatumHash] +txSkelConsumedData skel = do + let outputToDatumHashM output = case output ^. outputDatumL of + Api.OutputDatumHash dHash -> maybeErrM - (MCEUnknownDatum "txSkelInputData: Transaction input with un-resolvable datum hash" dHash) - (dHash,) + (MCEUnknownDatum "txSkelConsumedData: Transaction input with unknown datum hash" dHash) + (Just . const dHash) (datumFromHash dHash) - ) - mDatumHashes + Api.OutputDatum datum -> return $ Just $ Script.datumHash datum + Api.NoOutputDatum -> return Nothing + (Map.elems -> inputTxOuts) <- txSkelInputUtxosPl skel + catMaybes <$> mapM outputToDatumHashM inputTxOuts -- ** Slot and Time Management diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index f0dc703c8..f69cf444e 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -170,7 +170,8 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- We retrieve data that will be used in the transaction generation process: -- datums, validators and various kinds of inputs. This idea is to provide a -- rich-enough context for the transaction generation to succeed. - insData <- txSkelInputData skel + hashedData <- txSkelHashedData skel + insData <- txSkelConsumedData skel insValidators <- txSkelInputValidators skel insMap <- txSkelInputUtxosPl skel refInsMap <- txSkelReferenceInputUtxosPl skel @@ -178,7 +179,7 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- We attempt to generate the transaction associated with the balanced -- skeleton and the retrieved data. This is an internal generation, there is -- no validation involved yet. - cardanoTx <- case generateTx fee returnCollateralWallet collateralIns newParams insData (insMap <> refInsMap <> collateralInsMap) insValidators skel of + cardanoTx <- case generateTx fee returnCollateralWallet collateralIns newParams hashedData (insMap <> refInsMap <> collateralInsMap) insValidators skel of Left err -> throwError . MCEGenerationError $ err -- We apply post-generation modification when applicable Right tx -> return $ Ledger.CardanoEmulatorEraTx $ applyRawModOnBalancedTx (txOptUnsafeModTx . txSkelOpts $ skelUnbal) tx @@ -210,8 +211,8 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- Otherwise, we update known validators and datums. Nothing -> modify' - ( removeDatums (Map.keys insData) - . addDatums (Map.toList $ txSkelDataInOutputs skel) + ( removeDatums insData + . addDatums (txSkelDataInOutputs skel) . addValidators (txSkelValidatorsInOutputs skel <> txSkelReferenceScripts skel) ) -- We apply a change of slot when requested in the options diff --git a/src/Cooked/MockChain/GenerateTx/Body.hs b/src/Cooked/MockChain/GenerateTx/Body.hs index bbfb958be..2c816617f 100644 --- a/src/Cooked/MockChain/GenerateTx/Body.hs +++ b/src/Cooked/MockChain/GenerateTx/Body.hs @@ -1,7 +1,14 @@ module Cooked.MockChain.GenerateTx.Body where import Cardano.Api qualified as Cardano +import Cardano.Api.Shelley qualified as Cardano +import Cardano.Ledger.Alonzo.Tx qualified as Alonzo +import Cardano.Ledger.Alonzo.TxBody qualified as Alonzo +import Cardano.Ledger.Alonzo.TxWits qualified as Alonzo +import Cardano.Ledger.Conway.PParams qualified as Conway +import Cardano.Ledger.Plutus qualified as Cardano import Cardano.Node.Emulator.Internal.Node qualified as Emulator +import Control.Lens qualified as Lens import Control.Monad import Control.Monad.Reader import Cooked.MockChain.GenerateTx.Collateral qualified as Collateral @@ -12,10 +19,11 @@ import Cooked.MockChain.GenerateTx.Output qualified as Output import Cooked.MockChain.GenerateTx.Proposal qualified as Proposal import Cooked.Skeleton import Cooked.Wallet -import Data.Bifunctor +import Data.Either.Combinators import Data.Map (Map) import Data.Map qualified as Map import Data.Set (Set) +import Data.Set qualified as Set import Ledger.Address qualified as Ledger import Ledger.Tx qualified as Ledger import Ledger.Tx.CardanoAPI qualified as Ledger @@ -86,8 +94,8 @@ txSkelToBodyContent skel@TxSkel {..} | txSkelReferenceInputs <- txSkelReferenceT let txMetadata = Cardano.TxMetadataNone -- That's what plutus-apps does as well txAuxScripts = Cardano.TxAuxScriptsNone -- That's what plutus-apps does as well txWithdrawals = Cardano.TxWithdrawalsNone -- That's what plutus-apps does as well - txCertificates = Cardano.TxCertificatesNone -- That's what plutus-apps does as well txUpdateProposal = Cardano.TxUpdateProposalNone -- That's what plutus-apps does as well + txCertificates = Cardano.TxCertificatesNone -- That's what plutus-apps does as well txScriptValidity = Cardano.TxScriptValidityNone -- That's what plutus-apps does as well txVotingProcedures = Nothing -- TODO, same as above return Cardano.TxBodyContent {..} @@ -97,17 +105,50 @@ txSkelToBodyContent skel@TxSkel {..} | txSkelReferenceInputs <- txSkelReferenceT txSkelToCardanoTx :: TxSkel -> BodyGen (Cardano.Tx Cardano.ConwayEra) txSkelToCardanoTx txSkel = do txBodyContent <- txSkelToBodyContent txSkel - cardanoTxUnsigned <- - lift $ - bimap - (TxBodyError "generateTx: ") - (`Cardano.Tx` []) - (Cardano.createAndValidateTransactionBody Cardano.ShelleyBasedEraConway txBodyContent) - foldM - ( \tx wal -> - case Ledger.addCardanoTxWitness (Ledger.toWitness $ Ledger.PaymentPrivateKey $ walletSK wal) (Ledger.CardanoTx tx Cardano.ShelleyBasedEraConway) of - Ledger.CardanoTx tx' Cardano.ShelleyBasedEraConway -> return tx' - _ -> throwOnString "txSkelToCardanoTx: Wrong output era" - ) - cardanoTxUnsigned - (txSkelSigners txSkel) + + -- We create the associated Shelley TxBody + txBody@(Cardano.ShelleyTxBody a body c dats e f) <- + lift $ mapLeft (TxBodyError "generateTx :") $ Cardano.createAndValidateTransactionBody Cardano.ShelleyBasedEraConway txBodyContent + + -- There is a chance that the body is in need of additional data. This happens + -- when the set of reference inputs contains hashed datums that will need to + -- be resolved during phase 2 validation. All that follows until the + -- definition of "txBody'" aims at doing just that. In the process, we have to + -- reconstruct the body with the new data and the associated hash. Hopefully, + -- in the future, cardano-api provides a way to add those data in the body + -- directly without requiring this methods, which somewhat feels like a hack. + mData <- asks managedData + mTxOut <- asks managedTxOuts + refIns <- forM (txSkelReferenceTxOutRefs txSkel) $ \oRef -> + throwOnLookup ("txSkelToCardanoTx: Unable to resolve TxOutRef " <> show oRef) oRef mTxOut + let datumHashes = [hash | (Api.TxOut _ _ (Api.OutputDatumHash hash) _) <- refIns] + additionalData <- forM datumHashes $ \dHash -> + throwOnLookup ("txSkelToCardanoTx: Unable to resolve datum hash " <> show dHash) dHash mData + let additionalDataMap = Map.fromList [(Cardano.hashData dat, dat) | Api.Datum (Cardano.Data . Api.toData -> dat) <- additionalData] + toLangDepViewParam <- asks (Conway.getLanguageView . Cardano.unLedgerProtocolParameters . Emulator.ledgerProtocolParameters . params) + let txDats' = Alonzo.TxDats additionalDataMap + (era, datums, redeemers) = case dats of + Cardano.TxBodyNoScriptData -> (Cardano.AlonzoEraOnwardsConway, txDats', Alonzo.Redeemers Map.empty) + Cardano.TxBodyScriptData era' txDats reds -> (era', txDats <> txDats', reds) + witnesses = Cardano.collectTxBodyScriptWitnesses Cardano.ShelleyBasedEraConway txBodyContent + languages = [toCardanoLanguage v | (_, Cardano.AnyScriptWitness (Cardano.PlutusScriptWitness _ v _ _ _ _)) <- witnesses] + scriptIntegrityHash = + Cardano.alonzoEraOnwardsConstraints era $ + Alonzo.hashScriptIntegrity (Set.fromList $ toLangDepViewParam <$> languages) redeemers datums + body' = body Lens.& Alonzo.scriptIntegrityHashTxBodyL Lens..~ scriptIntegrityHash + txBody' = Cardano.ShelleyTxBody a body' c (Cardano.TxBodyScriptData era datums redeemers) e f + + -- We return the transaction signed by all the required signers. The body is + -- chosen based on whether or not it required additional data. + return $ + Ledger.getEmulatorEraTx $ + foldl + (flip Ledger.addCardanoTxWitness) + (Ledger.CardanoEmulatorEraTx $ Cardano.Tx (if null additionalDataMap then txBody else txBody') []) + (Ledger.toWitness . Ledger.PaymentPrivateKey . walletSK <$> txSkelSigners txSkel) + where + toCardanoLanguage :: Cardano.PlutusScriptVersion lang -> Cardano.Language + toCardanoLanguage = \case + Cardano.PlutusScriptV1 -> Cardano.PlutusV1 + Cardano.PlutusScriptV2 -> Cardano.PlutusV2 + Cardano.PlutusScriptV3 -> Cardano.PlutusV3 diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index aa1846f08..b4431d8cb 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -355,9 +355,12 @@ prettyTxSkelOut opts (Pays output) = "Datum (inlined):" <+> (PP.align . prettyCookedOpt opts) (output ^. outputDatumL) - Api.OutputDatumHash _datum -> + Api.OutputDatumHash dHash -> Just $ - "Datum (hashed):" + "Datum (hashed)" + <+> "(" + <> prettyHash (pcOptHashes opts) (toHash dHash) + <> "):" <+> (PP.align . prettyCookedOpt opts) (output ^. outputDatumL) Api.NoOutputDatum -> Nothing, @@ -371,9 +374,19 @@ prettyTxSkelOutDatumMaybe opts txSkelOutDatum@(TxSkelOutInlineDatum _) = Just $ "Datum (inlined):" <+> PP.align (prettyCookedOpt opts txSkelOutDatum) -prettyTxSkelOutDatumMaybe opts txSkelOutDatum = +prettyTxSkelOutDatumMaybe opts txSkelOutDatum@(TxSkelOutDatumHash dat) = Just $ - "Datum (hashed):" + "Datum (hashed)" + <+> "(" + <> prettyHash (pcOptHashes opts) (toHash $ Script.datumHash $ Api.Datum $ Api.toBuiltinData dat) + <> "):" + <+> PP.align (prettyCookedOpt opts txSkelOutDatum) +prettyTxSkelOutDatumMaybe opts txSkelOutDatum@(TxSkelOutDatum dat) = + Just $ + "Datum (hashed)" + <+> "(" + <> prettyHash (pcOptHashes opts) (toHash $ Script.datumHash $ Api.Datum $ Api.toBuiltinData dat) + <> "):" <+> PP.align (prettyCookedOpt opts txSkelOutDatum) prettyTxSkelIn :: PrettyCookedOpts -> SkelContext -> (Api.TxOutRef, TxSkelRedeemer) -> DocCooked diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index f5aa339db..4c9d1943e 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -49,11 +49,11 @@ module Cooked.Skeleton paysPK, paysScript, paysScriptInlineDatum, - paysScriptDatumHash, + paysScriptUnresolvedDatumHash, paysScriptNoDatum, withDatum, withInlineDatum, - withDatumHash, + withUnresolvedDatumHash, withReferenceScript, withStakingCredential, TxSkelRedeemer (..), @@ -870,8 +870,9 @@ paysPK pkh value = (Nothing @(Script.Versioned Script.Script)) ) --- | Pays a script a certain value with a certain datum, using the +-- | Pays a script a certain value with a certain datum hash, using the -- 'TxSkelOutDatum' constructor. (See the documentation of 'TxSkelOutDatum'.) +-- The datum is resolved in the transaction. paysScript :: ( Api.ToData (Script.DatumType a), Show (Script.DatumType a), @@ -917,9 +918,9 @@ paysScriptInlineDatum validator datum value = (Nothing @(Script.Versioned Script.Script)) ) --- | Pays a script a certain value with a certain hashed (not resolved in --- transaction) datum. -paysScriptDatumHash :: +-- | Pays a script a certain value with a certain hashed datum which is not +-- resolved in the transaction (as opposed to "paysScript"). +paysScriptUnresolvedDatumHash :: ( Api.ToData (Script.DatumType a), Show (Script.DatumType a), Typeable (Script.DatumType a), @@ -931,7 +932,7 @@ paysScriptDatumHash :: Script.DatumType a -> Api.Value -> TxSkelOut -paysScriptDatumHash validator datum value = +paysScriptUnresolvedDatumHash validator datum value = Pays ( ConcreteOutput validator @@ -942,7 +943,7 @@ paysScriptDatumHash validator datum value = ) -- | Pays a script a certain value without any datum. Intended to be used with --- 'withDatum', 'withDatumHash', or 'withInlineDatum' to try a datum whose type +-- 'withDatum', 'withUnresolvedDatumHash', or 'withInlineDatum' to try a datum whose type -- does not match the validator's. paysScriptNoDatum :: (Typeable a) => Script.TypedValidator a -> Api.Value -> TxSkelOut paysScriptNoDatum validator value = @@ -968,8 +969,8 @@ withInlineDatum (Pays output) datum = Pays $ (fromAbstractOutput output) {concre -- | Set the datum in a payment to the given hashed (not resolved in the -- transaction) datum (whose type may not fit the typed validator in case of a -- script). -withDatumHash :: (Api.ToData a, Show a, Typeable a, PlutusTx.Eq a, PrettyCooked a) => TxSkelOut -> a -> TxSkelOut -withDatumHash (Pays output) datum = Pays $ (fromAbstractOutput output) {concreteOutputDatum = TxSkelOutDatumHash datum} +withUnresolvedDatumHash :: (Api.ToData a, Show a, Typeable a, PlutusTx.Eq a, PrettyCooked a) => TxSkelOut -> a -> TxSkelOut +withUnresolvedDatumHash (Pays output) datum = Pays $ (fromAbstractOutput output) {concreteOutputDatum = TxSkelOutDatumHash datum} -- | Add a reference script to a transaction output (or replace it if there is -- already one) @@ -1058,18 +1059,19 @@ data SkelContext = SkelContext txSkelValueInOutputs :: TxSkel -> Api.Value txSkelValueInOutputs = foldOf (txSkelOutsL % folded % txSkelOutValueL) --- | Return all data on transaction outputs. -txSkelDataInOutputs :: TxSkel -> Map Api.DatumHash TxSkelOutDatum +-- | Return all data on transaction outputs. This can contain duplicates, which +-- is intended. +txSkelDataInOutputs :: TxSkel -> [(Api.DatumHash, TxSkelOutDatum)] txSkelDataInOutputs = foldMapOf ( txSkelOutsL % folded % txSkelOutDatumL ) - ( \txSkelOutDatum -> do + ( \txSkelOutDatum -> maybe - Map.empty - (\datum -> Map.singleton (Script.datumHash datum) txSkelOutDatum) + [] + (\datum -> [(Script.datumHash datum, txSkelOutDatum)]) (txSkelOutUntypedDatum txSkelOutDatum) ) diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index dcf08c95b..660af156c 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -40,7 +40,7 @@ initialDistributionBalancing = paysPK alice (Script.ada 30), paysPK alice (Script.lovelace 1280229 <> banana 3) `withDatum` (10 :: Integer), paysPK alice (Script.ada 1 <> banana 7) `withReferenceScript` (alwaysTrueValidator @MockContract), - paysPK alice (Script.ada 105 <> banana 2) `withDatumHash` () + paysPK alice (Script.ada 105 <> banana 2) `withUnresolvedDatumHash` () ] type TestBalancingOutcome = (TxSkel, TxSkel, Integer, Set Api.TxOutRef, [Api.TxOutRef]) diff --git a/tests/Cooked/InitialDistributionSpec.hs b/tests/Cooked/InitialDistributionSpec.hs index 2ae856e61..d586035a2 100644 --- a/tests/Cooked/InitialDistributionSpec.hs +++ b/tests/Cooked/InitialDistributionSpec.hs @@ -16,7 +16,7 @@ alice, bob :: Wallet -- type Int and value 10 for each datum kind initialDistributionWithDatum :: InitialDistribution initialDistributionWithDatum = - InitialDistribution $ [withDatum, withInlineDatum, withDatumHash] <*> [paysPK alice (Script.ada 2)] <*> [10 :: Integer] + InitialDistribution $ [withDatum, withInlineDatum] <*> [paysPK alice (Script.ada 2)] <*> [10 :: Integer] -- | An initial distribution where alice owns a UTxO with a reference -- script corresponding to the always succeed validators and bob owns @@ -51,8 +51,8 @@ tests :: TestTree tests = testGroup "Initial distributions" - [ testCase "Reading datums placed in the initial distribution, inlined, hashed or vanilla" $ - testSucceedsFrom' def (\results _ -> testBool $ results == [10, 10, 10]) initialDistributionWithDatum getValueFromInitialDatum, + [ testCase "Reading datums placed in the initial distribution, inlined or hashed" $ + testSucceedsFrom' def (\results _ -> testBool $ results == [10, 10]) initialDistributionWithDatum getValueFromInitialDatum, testCase "Spending a script placed as a reference script in the initial distribution" $ testSucceedsFrom def initialDistributionWithReferenceScript spendReferenceAlwaysTrueValidator ] diff --git a/tests/Cooked/InlineDatumsSpec.hs b/tests/Cooked/InlineDatumsSpec.hs index 160d21f75..d09271d7b 100644 --- a/tests/Cooked/InlineDatumsSpec.hs +++ b/tests/Cooked/InlineDatumsSpec.hs @@ -155,7 +155,7 @@ continuingOutputTestTrace datumKindOnSecondPayment validator = do txSkelIns = Map.singleton theTxOutRef $ txSkelSomeRedeemer (), txSkelOuts = [ ( case datumKindOnSecondPayment of - OnlyHash -> paysScriptDatumHash validator SecondPaymentDatum + OnlyHash -> paysScriptUnresolvedDatumHash validator SecondPaymentDatum Datum -> paysScript validator SecondPaymentDatum Inline -> paysScriptInlineDatum validator SecondPaymentDatum ) diff --git a/tests/Cooked/ReferenceInputsSpec.hs b/tests/Cooked/ReferenceInputsSpec.hs index ddde28b71..36b49d06f 100644 --- a/tests/Cooked/ReferenceInputsSpec.hs +++ b/tests/Cooked/ReferenceInputsSpec.hs @@ -10,21 +10,24 @@ import Plutus.Script.Utils.V3.Typed.Scripts qualified as Script import Plutus.Script.Utils.Value qualified as Script import PlutusLedgerApi.V3 qualified as Api import PlutusTx qualified +import PlutusTx.AssocMap qualified as PlutusTx (lookup) +import PlutusTx.Eq qualified as PlutusTx import PlutusTx.Prelude qualified as PlutusTx import Prettyprinter qualified as PP import Test.Tasty qualified as Tasty import Test.Tasty.HUnit qualified as Tasty --- Foo and Bar are two dummy scripts to test reference inputs. They --- serve no purpose and make no real sense. +-- Foo, Bar and Baz are dummy scripts to test reference inputs. They serve no +-- purpose and make no real sense. -- --- Foo contains a pkh in its datum. It can only be spent by ANOTHER --- public key. +-- Foo contains a pkh in its datum. It can only be spent by ANOTHER public key. -- --- Bar has no datum nor redeemer. Its outputs can only be spent by a --- public key who can provide a Foo UTxO containing its pkh as --- reference input (that is a UTxO they could not actually spend, --- according the the design of Foo). +-- Bar has no datum nor redeemer. Its outputs can only be spent by a public key +-- who can provide a Foo UTxO containing its pkh as reference input (that is a +-- UTxO they could not actually spend, according the the design of Foo). +-- +-- Baz has no datum nor redeemer. Its outputs can only be spent when a reference +-- input is provided with a hashed datum contain the integer 10. -- -- The datum in Foo outputs in expected to be inlined. @@ -58,12 +61,6 @@ fooTypedValidator = $$(PlutusTx.compile [||fooValidator||]) $$(PlutusTx.compile [||wrap||]) -data Bar - -instance Script.ValidatorTypes Bar where - type RedeemerType Bar = () - type DatumType Bar = () - -- | Outputs can only be spent by pks who provide a reference input to -- a Foo in which they are mentioned (in an inlined datum). barValidator :: () -> () -> Api.ScriptContext -> Bool @@ -77,13 +74,35 @@ barValidator _ _ (Api.ScriptContext txInfo _) = Just (FooDatum pkh) -> PlutusTx.elem pkh (Api.txInfoSignatories txInfo) f _ = False -barTypedValidator :: Script.TypedValidator Bar +barTypedValidator :: Script.TypedValidator MockContract barTypedValidator = let wrap = Script.mkUntypedValidator - in Script.mkTypedValidator @Bar + in Script.mkTypedValidator @MockContract $$(PlutusTx.compile [||barValidator||]) $$(PlutusTx.compile [||wrap||]) +bazValidator :: () -> () -> Api.ScriptContext -> Bool +bazValidator _ _ context = + let info = Api.scriptContextTxInfo context + refInputs = Api.txInfoReferenceInputs info + txData = Api.txInfoData info + in case refInputs of + [myRefInput] -> + let Api.TxOut _ _ dat _ = Api.txInInfoResolved myRefInput + in case dat of + (Api.OutputDatumHash hash) -> case PlutusTx.lookup hash txData of + Nothing -> False + Just (Api.Datum a) -> PlutusTx.unsafeFromBuiltinData @Integer a PlutusTx.== 10 + _ -> False + _ -> False + +bazTypedValidator :: Script.TypedValidator MockContract +bazTypedValidator = + let wrap = Script.mkUntypedValidator + in Script.mkTypedValidator @MockContract + $$(PlutusTx.compile [||bazValidator||]) + $$(PlutusTx.compile [||wrap||]) + trace1 :: (MonadBlockChain m) => m () trace1 = do txOutRefFoo : txOutRefBar : _ <- @@ -104,8 +123,30 @@ trace1 = do txSkelSigners = [wallet 3] } +trace2 :: (MonadBlockChain m) => m () +trace2 = do + refORef : scriptORef : _ <- + validateTxSkel' + ( txSkelTemplate + { txSkelOuts = + [ paysPK (wallet 1) (ada 2) `withDatum` (10 :: Integer), + paysScript bazTypedValidator () (ada 10) + ], + txSkelSigners = [wallet 2] + } + ) + void $ + validateTxSkel $ + txSkelTemplate + { txSkelSigners = [wallet 1], + txSkelIns = Map.singleton scriptORef (txSkelSomeRedeemer ()), + txSkelInsReference = Set.singleton refORef + } + tests :: Tasty.TestTree tests = Tasty.testGroup "Reference inputs" - [Tasty.testCase "Can reference an input that can't be spent" (testSucceeds def trace1)] + [ Tasty.testCase "We can reference an input that can't be spent" (testSucceeds def trace1), + Tasty.testCase "We can decode the datum hash from a reference input" (testSucceeds def trace2) + ] From 72e28206d8e04ade2975ca0e34047a32f0d49899 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 1 Aug 2024 01:02:41 +0200 Subject: [PATCH 29/71] withdrawal support --- CHANGELOG.md | 3 + cooked-validators.cabal | 2 + src/Cooked/MockChain/Balancing.hs | 5 +- src/Cooked/MockChain/GenerateTx/Body.hs | 9 ++- .../MockChain/GenerateTx/Withdrawals.hs | 58 ++++++++++++++++++ src/Cooked/Pretty/Cooked.hs | 18 +++++- src/Cooked/ShowBS.hs | 2 +- src/Cooked/Skeleton.hs | 27 +++++++-- tests/Cooked/WithdrawalsSpec.hs | 59 +++++++++++++++++++ tests/Spec.hs | 4 +- 10 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 src/Cooked/MockChain/GenerateTx/Withdrawals.hs create mode 100644 tests/Cooked/WithdrawalsSpec.hs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1891413..9114efb7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ present but somehow disapeared. - It is now possible to reference an output which has a hashed datum. - `txSkelHashedData` the gives all the datum hashes in inputs and reference inputs. +- Partial support for withdrawals in txSkels. The rewarding scripts will be ran + and assets will be transferred. However, these withdrawals are not properly + constrainted yet. ### Removed diff --git a/cooked-validators.cabal b/cooked-validators.cabal index 03cd53ba9..e18ed7503 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -42,6 +42,7 @@ library Cooked.MockChain.GenerateTx.Mint Cooked.MockChain.GenerateTx.Output Cooked.MockChain.GenerateTx.Proposal + Cooked.MockChain.GenerateTx.Withdrawals Cooked.MockChain.GenerateTx.Witness Cooked.MockChain.MinAda Cooked.MockChain.MockChainSt @@ -172,6 +173,7 @@ test-suite spec Cooked.Tweak.TamperDatumSpec Cooked.Tweak.ValidityRangeSpec Cooked.TweakSpec + Cooked.WithdrawalsSpec Paths_cooked_validators autogen-modules: Paths_cooked_validators diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index d58c75481..6012b8d67 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -278,17 +278,18 @@ estimateTxSkelFee skel fee collateralIns returnCollateralWallet = do -- | This creates a balanced skeleton from a given skeleton and fee. In other -- words, this ensures that the following equation holds: input value + minted --- value = output value + burned value + fee + deposits +-- value + withdrawn value = output value + burned value + fee + deposits computeBalancedTxSkel :: (MonadBlockChainBalancing m) => Wallet -> BalancingOutputs -> TxSkel -> Fee -> m TxSkel computeBalancedTxSkel balancingWallet balancingUtxos txSkel@TxSkel {..} (Script.lovelace -> feeValue) = do -- We compute the necessary values from the skeleton that are part of the -- equation, except for the `feeValue` which we already have. let (burnedValue, mintedValue) = Api.split $ txSkelMintsValue txSkelMints outValue = txSkelValueInOutputs txSkel + withdrawnValue = txSkelWithdrawnValue txSkel inValue <- txSkelInputValue txSkel depositedValue <- toValue <$> txSkelProposalsDeposit txSkel -- We compute the values missing in the left and right side of the equation - let (missingRight, missingLeft) = Api.split $ outValue <> burnedValue <> feeValue <> depositedValue <> PlutusTx.negate (inValue <> mintedValue) + let (missingRight, missingLeft) = Api.split $ outValue <> burnedValue <> feeValue <> depositedValue <> PlutusTx.negate (inValue <> mintedValue <> withdrawnValue) -- This gives us what we need to run our `reachValue` algorithm and append to -- the resulting values whatever payment was missing in the initial skeleton let candidatesRaw = second (<> missingRight) <$> reachValue balancingUtxos missingLeft (toInteger $ length balancingUtxos) diff --git a/src/Cooked/MockChain/GenerateTx/Body.hs b/src/Cooked/MockChain/GenerateTx/Body.hs index 2c816617f..04c5b1f70 100644 --- a/src/Cooked/MockChain/GenerateTx/Body.hs +++ b/src/Cooked/MockChain/GenerateTx/Body.hs @@ -17,6 +17,7 @@ import Cooked.MockChain.GenerateTx.Input qualified as Input import Cooked.MockChain.GenerateTx.Mint qualified as Mint import Cooked.MockChain.GenerateTx.Output qualified as Output import Cooked.MockChain.GenerateTx.Proposal qualified as Proposal +import Cooked.MockChain.GenerateTx.Withdrawals qualified as Withdrawals import Cooked.Skeleton import Cooked.Wallet import Data.Either.Combinators @@ -59,6 +60,11 @@ instance Transform TxContext Input.InputContext where instance Transform TxContext Collateral.CollateralContext where transform TxContext {..} = Collateral.CollateralContext {..} +instance Transform TxContext Withdrawals.WithdrawalsContext where + transform TxContext {..} = + let networkId = Emulator.pNetworkId params + in Withdrawals.WithdrawalsContext {..} + -- | Generates a body content from a skeleton txSkelToBodyContent :: TxSkel -> BodyGen (Cardano.TxBodyContent Cardano.BuildTx Cardano.ConwayEra) txSkelToBodyContent skel@TxSkel {..} | txSkelReferenceInputs <- txSkelReferenceTxOutRefs skel = do @@ -91,9 +97,9 @@ txSkelToBodyContent skel@TxSkel {..} | txSkelReferenceInputs <- txSkelReferenceT txProposalProcedures <- Just . Cardano.Featured Cardano.ConwayEraOnwardsConway <$> liftTxGen (Proposal.toProposalProcedures txSkelProposals (txOptAnchorResolution txSkelOpts)) + txWithdrawals <- liftTxGen (Withdrawals.toWithdrawals txSkelWithdrawals) let txMetadata = Cardano.TxMetadataNone -- That's what plutus-apps does as well txAuxScripts = Cardano.TxAuxScriptsNone -- That's what plutus-apps does as well - txWithdrawals = Cardano.TxWithdrawalsNone -- That's what plutus-apps does as well txUpdateProposal = Cardano.TxUpdateProposalNone -- That's what plutus-apps does as well txCertificates = Cardano.TxCertificatesNone -- That's what plutus-apps does as well txScriptValidity = Cardano.TxScriptValidityNone -- That's what plutus-apps does as well @@ -104,6 +110,7 @@ txSkelToBodyContent skel@TxSkel {..} | txSkelReferenceInputs <- txSkelReferenceT -- sign it with the required signers. txSkelToCardanoTx :: TxSkel -> BodyGen (Cardano.Tx Cardano.ConwayEra) txSkelToCardanoTx txSkel = do + -- We begin by creating the body content of the transaction txBodyContent <- txSkelToBodyContent txSkel -- We create the associated Shelley TxBody diff --git a/src/Cooked/MockChain/GenerateTx/Withdrawals.hs b/src/Cooked/MockChain/GenerateTx/Withdrawals.hs new file mode 100644 index 000000000..eb9d147b0 --- /dev/null +++ b/src/Cooked/MockChain/GenerateTx/Withdrawals.hs @@ -0,0 +1,58 @@ +module Cooked.MockChain.GenerateTx.Withdrawals + ( WithdrawalsContext (..), + toWithdrawals, + ) +where + +import Cardano.Api qualified as Cardano +import Cardano.Api.Ledger qualified as Cardano +import Cardano.Api.Shelley qualified as Cardano +import Control.Monad +import Control.Monad.Reader +import Cooked.Conversion +import Cooked.MockChain.GenerateTx.Common +import Cooked.MockChain.GenerateTx.Witness +import Cooked.Skeleton +import Data.Map (Map) +import Data.Map qualified as Map +import Ledger.Tx.CardanoAPI qualified as Ledger +import Plutus.Script.Utils.Ada qualified as Script +import PlutusLedgerApi.V3 qualified as Api + +data WithdrawalsContext where + WithdrawalsContext :: + { managedTxOuts :: Map Api.TxOutRef Api.TxOut, + networkId :: Cardano.NetworkId + } -> + WithdrawalsContext + +instance Transform WithdrawalsContext (Map Api.TxOutRef Api.TxOut) where + transform = managedTxOuts + +type WithdrawalsGen a = TxGen WithdrawalsContext a + +toWithdrawals :: TxSkelWithdrawals -> WithdrawalsGen (Cardano.TxWithdrawals Cardano.BuildTx Cardano.ConwayEra) +toWithdrawals (Map.toList -> []) = return Cardano.TxWithdrawalsNone +toWithdrawals (Map.toList -> withdrawals) = + fmap + (Cardano.TxWithdrawals Cardano.ShelleyBasedEraConway) + $ forM withdrawals + $ \(staker, Script.Lovelace n) -> + do + (witness, sCred) <- + case staker of + Right pkh -> do + sCred <- + throwOnToCardanoError "toWithdrawals: unable to translate pkh stake credential" $ + Cardano.StakeCredentialByKey <$> Ledger.toCardanoStakeKeyHash pkh + return (Cardano.KeyWitness Cardano.KeyWitnessForStakeAddr, sCred) + Left (script, red) -> do + witness <- + Cardano.ScriptWitness Cardano.ScriptWitnessForStakeAddr + <$> liftTxGen (toScriptWitness script red Cardano.NoScriptDatumForStake) + sCred <- + throwOnToCardanoError "toWithdrawals: unable to translate script stake credential" $ + Cardano.StakeCredentialByScript <$> Ledger.toCardanoScriptHash (toScriptHash script) + return (witness, sCred) + networkId <- asks networkId + return (Cardano.makeStakeAddress networkId sCred, Cardano.Coin n, Cardano.BuildTxWith witness) diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index b4431d8cb..974005735 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -170,7 +170,7 @@ instance PrettyCooked MockChainLog where go acc [] = reverse acc prettyTxSkel :: PrettyCookedOpts -> SkelContext -> TxSkel -> DocCooked -prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals) = +prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals withdrawals) = prettyItemize "transaction skeleton:" "-" @@ -183,10 +183,24 @@ prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins prettyItemizeNonEmpty "Inputs:" "-" (prettyTxSkelIn opts skelContext <$> Map.toList ins), prettyItemizeNonEmpty "Reference inputs:" "-" (mapMaybe (prettyTxSkelInReference opts skelContext) $ Set.toList insReference), prettyItemizeNonEmpty "Outputs:" "-" (prettyTxSkelOut opts <$> outs), - prettyItemizeNonEmpty "Proposals:" "-" (prettyTxSkelProposal opts <$> proposals) + prettyItemizeNonEmpty "Proposals:" "-" (prettyTxSkelProposal opts <$> proposals), + prettyWithdrawals opts withdrawals ] ) +prettyWithdrawals :: PrettyCookedOpts -> TxSkelWithdrawals -> Maybe DocCooked +prettyWithdrawals pcOpts withdrawals = + prettyItemizeNonEmpty "Withdrawals:" "-" $ prettyWithdrawal <$> Map.toList withdrawals + where + prettyWithdrawal :: (Either (Script.Versioned Script.Script, TxSkelRedeemer) Api.PubKeyHash, Script.Ada) -> DocCooked + prettyWithdrawal (cred, ada) = + prettyItemizeNoTitle "-" $ + ( case cred of + Left (script, red) -> prettyCookedOpt pcOpts script : prettyTxSkelRedeemer pcOpts red + Right pkh -> [prettyCookedOpt pcOpts pkh] + ) + ++ [prettyCookedOpt pcOpts (toValue ada)] + prettyTxParameterChange :: PrettyCookedOpts -> TxParameterChange -> DocCooked prettyTxParameterChange opts (FeePerByte n) = "Fee per byte:" <+> prettyCookedOpt opts n prettyTxParameterChange opts (FeeFixed n) = "Fee fixed:" <+> prettyCookedOpt opts n diff --git a/src/Cooked/ShowBS.hs b/src/Cooked/ShowBS.hs index ba9b3ea9e..f6b09eb91 100644 --- a/src/Cooked/ShowBS.hs +++ b/src/Cooked/ShowBS.hs @@ -370,7 +370,7 @@ instance ShowBS Api.TxInfo where <> showBS txInfoMint <> "certificates:" <> showBS txInfoTxCerts - <> "wdrl:" -- TODO: what is wdrl? Explain better here + <> "wdrl:" <> showBS txInfoWdrl <> "valid range:" <> showBS txInfoValidRange diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 4c9d1943e..d329ef10c 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -67,6 +67,8 @@ module Cooked.Skeleton txSkelProposalActionL, txSkelProposalWitnessL, txSkelProposalAnchorL, + TxSkelWithdrawals, + txSkelWithdrawnValue, TxSkel (..), txSkelLabelL, txSkelOptsL, @@ -76,6 +78,7 @@ module Cooked.Skeleton txSkelInsL, txSkelInsReferenceL, txSkelOutsL, + txSkelWithdrawalsL, txSkelTemplate, txSkelDataInOutputs, txSkelValidatorsInOutputs, @@ -119,6 +122,7 @@ import Data.Set qualified as Set import Ledger.Slot qualified as Ledger import Optics.Core import Optics.TH +import Plutus.Script.Utils.Ada qualified as Script import Plutus.Script.Utils.Scripts qualified as Script import Plutus.Script.Utils.Typed qualified as Script hiding (validatorHash) import Plutus.Script.Utils.Value qualified as Script hiding (adaSymbol, adaToken) @@ -582,6 +586,16 @@ withWitness prop (s, red) = prop {txSkelProposalWitness = Just (toScript s, red) withAnchor :: TxSkelProposal -> String -> TxSkelProposal withAnchor prop url = prop {txSkelProposalAnchor = Just url} +-- * Description of the Withdrawals + +type TxSkelWithdrawals = + Map + (Either (Script.Versioned Script.Script, TxSkelRedeemer) Api.PubKeyHash) + Script.Ada + +txSkelWithdrawnValue :: TxSkel -> Api.Value +txSkelWithdrawnValue = mconcat . (toValue . snd <$>) . Map.toList . txSkelWithdrawals + -- * Description of the Minting -- | A description of what a transaction mints. For every policy, there can only @@ -1014,8 +1028,11 @@ data TxSkel where -- | The outputs of the transaction. These will occur in exactly this -- order on the transaction. txSkelOuts :: [TxSkelOut], - -- | Possible proposals issued in this transaction to be voted on and possible enacted later on. - txSkelProposals :: [TxSkelProposal] + -- | Possible proposals issued in this transaction to be voted on and + -- possible enacted later on. + txSkelProposals :: [TxSkelProposal], + -- | Withdrawals performed by the transaction + txSkelWithdrawals :: TxSkelWithdrawals } -> TxSkel deriving (Show, Eq) @@ -1029,7 +1046,8 @@ makeLensesFor ("txSkelIns", "txSkelInsL"), ("txSkelInsReference", "txSkelInsReferenceL"), ("txSkelOuts", "txSkelOutsL"), - ("txSkelProposals", "txSkelProposalsL") + ("txSkelProposals", "txSkelProposalsL"), + ("txSkelWithdrawals", "txSkelWithdrawalsL") ] ''TxSkel @@ -1045,7 +1063,8 @@ txSkelTemplate = txSkelIns = Map.empty, txSkelInsReference = Set.empty, txSkelOuts = [], - txSkelProposals = [] + txSkelProposals = [], + txSkelWithdrawals = Map.empty } -- | The missing information on a 'TxSkel' that can only be resolved by querying diff --git a/tests/Cooked/WithdrawalsSpec.hs b/tests/Cooked/WithdrawalsSpec.hs new file mode 100644 index 000000000..a451eb375 --- /dev/null +++ b/tests/Cooked/WithdrawalsSpec.hs @@ -0,0 +1,59 @@ +module Cooked.WithdrawalsSpec where + +import Control.Monad +import Cooked +import Data.Default +import Data.Map qualified as Map +import Plutus.Script.Utils.Ada qualified as Script +import Plutus.Script.Utils.Scripts qualified as Script +import PlutusLedgerApi.V3 qualified as Api +import PlutusTx qualified +import PlutusTx.AssocMap qualified as PMap +import PlutusTx.Prelude qualified as PlutusTx +import Test.Tasty +import Test.Tasty.HUnit + +checkWithdrawalScript :: PlutusTx.BuiltinData -> PlutusTx.BuiltinData -> () +checkWithdrawalScript red ctx = + let scriptContext = PlutusTx.unsafeFromBuiltinData @Api.ScriptContext ctx + withdrawals = Api.txInfoWdrl PlutusTx.$ Api.scriptContextTxInfo scriptContext + quantity = PlutusTx.unsafeFromBuiltinData @Integer red + purpose = Api.scriptContextPurpose scriptContext + in case purpose of + Api.Rewarding cred -> case PMap.toList withdrawals of + [(cred', Api.Lovelace n)] -> + if cred PlutusTx.== cred' + then + if n PlutusTx.== quantity + then () + else PlutusTx.traceError "Wrong quantity." + else PlutusTx.traceError "Wrong credential." + _ -> PlutusTx.traceError "Wrong withdrawal." + _ -> PlutusTx.traceError "Wrong script purpose." + +checkWithdrawalVersionedScript :: Script.Versioned Script.Script +checkWithdrawalVersionedScript = mkScript $$(PlutusTx.compile [||checkWithdrawalScript||]) + +testWithdrawingScript :: (MonadBlockChain m) => Integer -> Integer -> m () +testWithdrawingScript n1 n2 = + void $ + validateTxSkel $ + txSkelTemplate + { txSkelSigners = [wallet 1], + txSkelWithdrawals = + Map.singleton + (Left (checkWithdrawalVersionedScript, txSkelSomeRedeemer (n1 * 1_000 :: Integer))) + (Script.Lovelace $ n2 * 1_000) + } + +tests :: TestTree +tests = + testGroup + "Withdrawing scripts" + [ testCase "We can use a withdrawing script" $ + testSucceeds def $ + testWithdrawingScript 2 2, + testCase "But the script might fail" $ + testFailsFrom def (isCekEvaluationFailure def) def $ + testWithdrawingScript 2 1 + ] diff --git a/tests/Spec.hs b/tests/Spec.hs index ec6566fc7..646fbc7ec 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -11,6 +11,7 @@ import Cooked.ReferenceInputsSpec qualified as ReferenceInputsSpec import Cooked.ReferenceScriptsSpec qualified as ReferenceScriptsSpec import Cooked.ShowBSSpec qualified as ShowBSSpec import Cooked.TweakSpec qualified as TweakSpec +import Cooked.WithdrawalsSpec qualified as WithdrawalsSpec import Test.Tasty main :: IO () @@ -30,5 +31,6 @@ main = MockChainSpec.tests, ShowBSSpec.tests, InitDistribSpec.tests, - ProposingSpec.tests + ProposingSpec.tests, + WithdrawalsSpec.tests ] From cc595b183b24b96f1a743889a2ed6304fb601e6d Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 1 Aug 2024 01:23:03 +0200 Subject: [PATCH 30/71] fixing balancing bug --- CHANGELOG.md | 2 ++ src/Cooked/MockChain/Balancing.hs | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9114efb7c..97206fe92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,8 @@ - A bug where the script hashes would not be computed properly for early plutus version (V1 and V2). +- A bug where balancing would fail with excessive inputs and not enough min ada + in the excess. ## [[4.0.0]](https://github.com/tweag/cooked-validators/releases/tag/v4.0.0) - 2024-06-28 diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 6012b8d67..3fa2cbf2b 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -281,6 +281,7 @@ estimateTxSkelFee skel fee collateralIns returnCollateralWallet = do -- value + withdrawn value = output value + burned value + fee + deposits computeBalancedTxSkel :: (MonadBlockChainBalancing m) => Wallet -> BalancingOutputs -> TxSkel -> Fee -> m TxSkel computeBalancedTxSkel balancingWallet balancingUtxos txSkel@TxSkel {..} (Script.lovelace -> feeValue) = do + params <- getParams -- We compute the necessary values from the skeleton that are part of the -- equation, except for the `feeValue` which we already have. let (burnedValue, mintedValue) = Api.split $ txSkelMintsValue txSkelMints @@ -290,13 +291,27 @@ computeBalancedTxSkel balancingWallet balancingUtxos txSkel@TxSkel {..} (Script. depositedValue <- toValue <$> txSkelProposalsDeposit txSkel -- We compute the values missing in the left and right side of the equation let (missingRight, missingLeft) = Api.split $ outValue <> burnedValue <> feeValue <> depositedValue <> PlutusTx.negate (inValue <> mintedValue <> withdrawnValue) + -- We compute the minimal ada requirement of the missing payment + rightMinAda <- case getTxSkelOutMinAda params $ paysPK balancingWallet missingRight of + Left err -> throwError $ MCEGenerationError err + Right a -> return a + -- We compute the current ada of the missing payment. If the missing payment + -- is not empty and the minimal ada is not present, some value is missing. + let Script.Lovelace rightAda = missingRight ^. adaL + missingAda = rightMinAda - rightAda + missingAdaValue = if missingRight /= mempty && missingAda > 0 then lovelace missingAda else mempty + -- The actual missing value on the left might needs to account for any missing + -- min ada on the missing payment of the transaction skeleton. This also has + -- to be repercuted on the missing value on the right. + let missingLeft' = missingLeft <> missingAdaValue + missingRight' = missingRight <> missingAdaValue -- This gives us what we need to run our `reachValue` algorithm and append to -- the resulting values whatever payment was missing in the initial skeleton - let candidatesRaw = second (<> missingRight) <$> reachValue balancingUtxos missingLeft (toInteger $ length balancingUtxos) + let candidatesRaw = second (<> missingRight') <$> reachValue balancingUtxos missingLeft' (toInteger $ length balancingUtxos) -- We prepare a possible balancing error with the difference between the -- requested amount and the maximum amount provided by the balancing wallet let totalValue = mconcat $ Api.txOutValue . snd <$> balancingUtxos - difference = snd $ Api.split $ missingLeft <> PlutusTx.negate totalValue + difference = snd $ Api.split $ missingLeft' <> PlutusTx.negate totalValue balancingError = MCEUnbalanceable balancingWallet difference txSkel -- Which one of our candidates should be picked depends on three factors -- - Whether there exists a perfect candidate set with empty surplus value From 63bb58763ebe2e9f7246e45597642d46bbc6b273 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 1 Aug 2024 01:54:52 +0200 Subject: [PATCH 31/71] small post-rebase changes --- src/Cooked/MockChain/Balancing.hs | 4 ++-- tests/Cooked/ReferenceInputsSpec.hs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 3fa2cbf2b..a4bde19fd 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -297,9 +297,9 @@ computeBalancedTxSkel balancingWallet balancingUtxos txSkel@TxSkel {..} (Script. Right a -> return a -- We compute the current ada of the missing payment. If the missing payment -- is not empty and the minimal ada is not present, some value is missing. - let Script.Lovelace rightAda = missingRight ^. adaL + let Script.Lovelace rightAda = missingRight ^. Script.adaL missingAda = rightMinAda - rightAda - missingAdaValue = if missingRight /= mempty && missingAda > 0 then lovelace missingAda else mempty + missingAdaValue = if missingRight /= mempty && missingAda > 0 then Script.lovelace missingAda else mempty -- The actual missing value on the left might needs to account for any missing -- min ada on the missing payment of the transaction skeleton. This also has -- to be repercuted on the missing value on the right. diff --git a/tests/Cooked/ReferenceInputsSpec.hs b/tests/Cooked/ReferenceInputsSpec.hs index 36b49d06f..cad6e437e 100644 --- a/tests/Cooked/ReferenceInputsSpec.hs +++ b/tests/Cooked/ReferenceInputsSpec.hs @@ -129,8 +129,8 @@ trace2 = do validateTxSkel' ( txSkelTemplate { txSkelOuts = - [ paysPK (wallet 1) (ada 2) `withDatum` (10 :: Integer), - paysScript bazTypedValidator () (ada 10) + [ paysPK (wallet 1) (Script.ada 2) `withDatum` (10 :: Integer), + paysScript bazTypedValidator () (Script.ada 10) ], txSkelSigners = [wallet 2] } From 97d146026f578c0127fe6e0b617790b61bd29dbe Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 31 Jul 2024 14:30:10 +0200 Subject: [PATCH 32/71] showbsspec finally gone --- cooked-validators.cabal | 1 - tests/Cooked/ShowBSSpec.hs | 93 -------------------------------------- tests/Spec.hs | 2 - 3 files changed, 96 deletions(-) delete mode 100644 tests/Cooked/ShowBSSpec.hs diff --git a/cooked-validators.cabal b/cooked-validators.cabal index f6839dc13..854fbf014 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -164,7 +164,6 @@ test-suite spec Cooked.ProposingScriptSpec Cooked.ReferenceInputsSpec Cooked.ReferenceScriptsSpec - Cooked.ShowBSSpec Cooked.Tweak.CommonSpec Cooked.Tweak.OutPermutationsSpec Cooked.Tweak.TamperDatumSpec diff --git a/tests/Cooked/ShowBSSpec.hs b/tests/Cooked/ShowBSSpec.hs deleted file mode 100644 index e786bbdd5..000000000 --- a/tests/Cooked/ShowBSSpec.hs +++ /dev/null @@ -1,93 +0,0 @@ -module Cooked.ShowBSSpec (tests) where - -import Cardano.Node.Emulator.Internal.Node qualified as Emulator -import Control.Monad -import Cooked -import Data.Default -import Data.Map qualified as Map -import Plutus.Script.Utils.Ada qualified as Script -import Plutus.Script.Utils.Typed qualified as Script -import Plutus.Script.Utils.V3.Typed.Scripts qualified as Script -import Plutus.Script.Utils.Value qualified as Script -import PlutusLedgerApi.V3 qualified as Api -import PlutusTx qualified -import PlutusTx.Builtins qualified as PlutusTx -import PlutusTx.Prelude qualified as PlutusTx -import Test.Tasty -import Test.Tasty.HUnit - -data UnitContract - -instance Script.ValidatorTypes UnitContract where - type RedeemerType UnitContract = Bool - type DatumType UnitContract = () - -{-# INLINEABLE traceValidator #-} -traceValidator :: () -> Bool -> Api.ScriptContext -> Bool -traceValidator _ _ ctx = PlutusTx.trace (showBS ctx) False - -printValidator :: Script.TypedValidator UnitContract -printValidator = - Script.mkTypedValidator @UnitContract - $$(PlutusTx.compile [||traceValidator||]) - $$(PlutusTx.compile [||wrap||]) - where - wrap = Script.mkUntypedValidator - -printTrace :: (MonadBlockChain m) => m () -printTrace = do - oref : _ <- - validateTxSkel' - txSkelTemplate - { txSkelSigners = [wallet 1], - txSkelOuts = [paysScriptInlineDatum printValidator () (Script.ada 30)] - } - void $ - validateTxSkel - txSkelTemplate - { txSkelOpts = def {txOptEmulatorParamsModification = Just $ EmulatorParamsModification Emulator.increaseTransactionLimits}, - txSkelSigners = [wallet 1], - txSkelIns = Map.singleton oref $ txSkelSomeRedeemer True - } - -tests :: TestTree -tests = - testGroup - "Printing transaction data as bytestrings" - [ testCase "a few simple examples" $ - testConjoin $ - map - (uncurry (@?=)) - [ (showBS @PlutusTx.Integer 123, "123"), - (showBS @PlutusTx.Integer (-123), "-123"), - (showBS @[PlutusTx.Integer] [1, 2, 3], "[1,2,3]"), - (showBS (PlutusTx.True, PlutusTx.False), "(True,False)"), - (showBS @PlutusTx.BuiltinByteString "abca", "\"61626361\""), - (showBS @Api.Value mempty, "(Value (fromList []))"), - ( showBS @Api.Value (Script.lovelaceValueOf 123), - "(Value (fromList [((CurrencySymbol \"\"),(fromList [((TokenName \"\"),123)]))]))" - ), - ( showBS @Api.Value (quickValue "banana" 4), - "(Value (fromList [((CurrencySymbol \"b9423defc80322887cd6461655989eb97bd9d706884fdd32ca613864\"),(fromList [((TokenName \"62616e616e61\"),4)]))]))" - ), - ( showBS (PlutusTx.mkConstr 0 [PlutusTx.mkMap [(PlutusTx.mkI 1, PlutusTx.mkList [PlutusTx.mkB "abc"])]]), - "(BuiltinData (Constr 0 [(Map [((I 1),(List [(B \"616263\")]))])]))" - ), - ( showBS - ( Api.Interval - (Api.LowerBound (Api.Finite $ Api.POSIXTime 123) True) - (Api.UpperBound (Api.Finite $ Api.POSIXTime 234) False) - ), - "(Interval (LowerBound (Finite (POSIXTime 123)) True) (UpperBound (Finite (POSIXTime 234)) False))" - ) - ], - testCase "printing the 'TxInfo' from a validator produces the expected string" $ - let isExpectedString = (==) "(Script context:Script Tx info:(inputs:[(TxInInfo (TxOutRef (TxId \"ad1f1a5a545dbe830383711cc3302f8fb5eb41c5154a7bc5336921abd453f001\") 0) (TxOut (Address (ScriptCredential (ScriptHash \"d02455c9a6cc9296707d031d8c668ea75612663d111d54a4155e4371\")) Nothing) (Value (fromList [((CurrencySymbol \"\"),(fromList [((TokenName \"\"),30000000)]))])) (OutputDatum (Datum (BuiltinData (Constr 0 [])))) Nothing))]reference inputs:[]outputs:[(TxOut (Address (PubKeyCredential (PubKeyHash \"a2c20c77887ace1cd986193e4e75babd8993cfd56995cd5cfce609c2\")) Nothing) (Value (fromList [((CurrencySymbol \"\"),(fromList [((TokenName \"\"),29515587)]))])) NoOutputDatum Nothing)]fees:(Lovelace 484413)minted value:(Value (fromList []))certificates:[]wdrl:(fromList [])valid range:(Interval (LowerBound NegInf True) (UpperBound PosInf True))signatories:[(PubKeyHash \"a2c20c77887ace1cd986193e4e75babd8993cfd56995cd5cfce609c2\")]redeemers:(fromList [((Spending (TxOutRef (TxId \"ad1f1a5a545dbe830383711cc3302f8fb5eb41c5154a7bc5336921abd453f001\") 0)),(Redeemer (BuiltinData (Constr 1 []))))])datums:(fromList [])transaction id:(TxId \"6fb3cb19b99bd2a5e880f64a80555ea096e583c269597329d03adb3a69ca53dd\")votes:(fromList [])proposals:[]treasury amount:Nothingtreasury donation:Nothing)Script purpose:(Spending (TxOutRef (TxId \"ad1f1a5a545dbe830383711cc3302f8fb5eb41c5154a7bc5336921abd453f001\") 0)))" - in testFails - (def @PrettyCookedOpts) - ( isCekEvaluationFailureWithMsg - (def @PrettyCookedOpts) - isExpectedString - ) - printTrace - ] diff --git a/tests/Spec.hs b/tests/Spec.hs index ec6566fc7..ae3ae1633 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -9,7 +9,6 @@ import Cooked.MockChainSpec qualified as MockChainSpec import Cooked.ProposingScriptSpec qualified as ProposingSpec import Cooked.ReferenceInputsSpec qualified as ReferenceInputsSpec import Cooked.ReferenceScriptsSpec qualified as ReferenceScriptsSpec -import Cooked.ShowBSSpec qualified as ShowBSSpec import Cooked.TweakSpec qualified as TweakSpec import Test.Tasty @@ -28,7 +27,6 @@ main = TweakSpec.tests, LtlSpec.tests, MockChainSpec.tests, - ShowBSSpec.tests, InitDistribSpec.tests, ProposingSpec.tests ] From bdf3676ce0dbcf81bea61c2aa1f498d4aa52aa81 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 31 Jul 2024 15:05:40 +0200 Subject: [PATCH 33/71] Recreating an index to pass to the new fee estimate function --- src/Cooked/MockChain/Balancing.hs | 32 +++++++------------------------ src/Cooked/MockChain/Direct.hs | 16 ---------------- 2 files changed, 7 insertions(+), 41 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 3b53e0645..16274dca2 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -18,16 +18,13 @@ import Cooked.Output import Cooked.Skeleton import Cooked.Wallet import Data.Bifunctor -import Data.Either.Combinators import Data.Function import Data.List -import Data.Map (Map) import Data.Map qualified as Map import Data.Maybe import Data.Ratio qualified as Rat import Data.Set (Set) import Data.Set qualified as Set -import Ledger.Index qualified as Ledger import Ledger.Tx qualified as Ledger import Ledger.Tx.CardanoAPI qualified as Ledger import Optics.Core @@ -279,28 +276,13 @@ estimateTxSkelFee skel fee collateralIns returnCollateralWallet = do Right txBody -> return txBody -- We retrieve the estimate number of required witness in the transaction let nkeys = Cardano.estimateTransactionKeyWitnessCount txBodyContent - index <- toIndex <$> lookupUtxos (txSkelKnownTxOutRefs skel <> Set.toList collateralIns) - case index of - Left err -> throwError $ MCEGenerationError err - -- We return an accurate estimate of the resulting transaction fee - Right index' -> - return . Emulator.unCoin $ - Cardano.calculateMinTxFee Cardano.ShelleyBasedEraConway (Emulator.pEmulatorPParams params) index' txBody nkeys - where - toIndex :: Map Api.TxOutRef Ledger.TxOut -> Either GenerateTxError Ledger.UtxoIndex - toIndex innerMap = do - let (txOutRefL, txOutL) = unzip $ Map.toList innerMap - txInL <- mapLeft (ToCardanoError "toIndex: unable to generate TxOut") $ forM txOutRefL Ledger.toCardanoTxIn - txOutL' <- forM (Ledger.getTxOut <$> txOutL) toCtxUTxOTxOut - return $ Cardano.UTxO $ Map.fromList $ zip txInL txOutL' - toCtxUTxOTxOut :: Cardano.TxOut Cardano.CtxTx era -> Either GenerateTxError (Cardano.TxOut Cardano.CtxUTxO era) - toCtxUTxOTxOut (Cardano.TxOut addr val d refS) = do - dat <- case d of - Cardano.TxOutDatumNone -> return Cardano.TxOutDatumNone - Cardano.TxOutDatumInTx _ _ -> Left $ GenerateTxErrorGeneral "Wrong datum kind" - Cardano.TxOutDatumHash s h -> return $ Cardano.TxOutDatumHash s h - Cardano.TxOutDatumInline s sd -> return $ Cardano.TxOutDatumInline s sd - return $ Cardano.TxOut addr val dat refS + -- We need to reconstruct an index to pass to the fee estimate function + (knownTxORefs, knownTxOuts) <- unzip . Map.toList <$> lookupUtxos (txSkelKnownTxOutRefs skel <> Set.toList collateralIns) + index <- case forM knownTxORefs Ledger.toCardanoTxIn of + Left err -> throwError $ MCEGenerationError $ ToCardanoError "estimateTxSkelFee: unable to generate TxIn" err + Right txInL -> return $ Cardano.UTxO $ Map.fromList $ zip txInL $ Cardano.toCtxUTxOTxOut . Ledger.getTxOut <$> knownTxOuts + -- We finally can the fee estimate function + return . Emulator.unCoin $ Cardano.calculateMinTxFee Cardano.ShelleyBasedEraConway (Emulator.pEmulatorPParams params) index txBody nkeys -- | This creates a balanced skeleton from a given skeleton and fee. In other -- words, this ensures that the following equation holds: input value + minted diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index ec98c56b2..fb1a1f5f3 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -310,22 +310,6 @@ getIndex = Cardano.TxOutDatumInline s sd -> Cardano.TxOutDatumInline s sd in Cardano.TxOut addr val dat refS -toIndex :: Map Api.TxOutRef Ledger.TxOut -> Either Ledger.ToCardanoError Ledger.UtxoIndex -toIndex innerMap = do - let (txOutRefL, txOutL) = unzip $ Map.toList innerMap - txInL <- forM txOutRefL Ledger.toCardanoTxIn - txOutL' <- forM (Ledger.getTxOut <$> txOutL) toCtxUTxOTxOut - return $ Cardano.UTxO $ Map.fromList $ zip txInL txOutL' - where - toCtxUTxOTxOut :: Cardano.TxOut Cardano.CtxTx era -> Either Ledger.ToCardanoError (Cardano.TxOut Cardano.CtxUTxO era) - toCtxUTxOTxOut (Cardano.TxOut addr val d refS) = do - dat <- case d of - Cardano.TxOutDatumNone -> return Cardano.TxOutDatumNone - Cardano.TxOutDatumInTx _ _ -> Left $ Ledger.TxBodyError "Wrong datum kind" - Cardano.TxOutDatumHash s h -> return $ Cardano.TxOutDatumHash s h - Cardano.TxOutDatumInline s sd -> return $ Cardano.TxOutDatumInline s sd - return $ Cardano.TxOut addr val dat refS - instance (Monad m) => MonadBlockChainBalancing (MockChainT m) where getParams = gets mcstParams validatorFromHash valHash = gets $ Map.lookup valHash . mcstValidators From 13271b9af4633cf0adb09a4216160b721b2abf50 Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 7 Aug 2024 00:08:24 +0200 Subject: [PATCH 34/71] post merge fix --- src/Cooked/MockChain/Balancing.hs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 4fadd706a..2e3e79017 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -30,7 +30,6 @@ import Data.Maybe import Data.Ratio qualified as Rat import Data.Set (Set) import Data.Set qualified as Set -import Ledger.Tx qualified as Ledger import Ledger.Tx.CardanoAPI qualified as Ledger import Optics.Core import Plutus.Script.Utils.Ada qualified as Script @@ -315,15 +314,22 @@ estimateTxSkelFee skel fee mCollaterals = do Right txBodyContent -> return txBodyContent -- We create the actual body and send if for validation txBody <- case Cardano.createAndValidateTransactionBody Cardano.ShelleyBasedEraConway txBodyContent of - Left err -> throwError $ MCEGenerationError (TxBodyError "Error creating body when estimating fees" err) + Left err -> throwError $ MCEGenerationError $ TxBodyError "Error creating body when estimating fees" err Right txBody -> return txBody -- We retrieve the estimate number of required witness in the transaction let nkeys = Cardano.estimateTransactionKeyWitnessCount txBodyContent -- We need to reconstruct an index to pass to the fee estimate function - (knownTxORefs, knownTxOuts) <- unzip . Map.toList <$> lookupUtxos (txSkelKnownTxOutRefs skel <> Set.toList collateralIns) - index <- case forM knownTxORefs Ledger.toCardanoTxIn of - Left err -> throwError $ MCEGenerationError $ ToCardanoError "estimateTxSkelFee: unable to generate TxIn" err - Right txInL -> return $ Cardano.UTxO $ Map.fromList $ zip txInL $ Cardano.toCtxUTxOTxOut . Ledger.getTxOut <$> knownTxOuts + -- We begin by retrieving the relevant utxos used in the skeleton + (knownTxORefs, knownTxOuts) <- unzip . Map.toList <$> lookupUtxos (txSkelKnownTxOutRefs skel <> collateralIns) + -- We then compute their Cardano counterparts + let indexOrError = do + txInL <- forM knownTxORefs Ledger.toCardanoTxIn + txOutL <- forM knownTxOuts $ Ledger.toCardanoTxOut $ Emulator.pNetworkId params + return $ Cardano.UTxO $ Map.fromList $ zip txInL $ Cardano.toCtxUTxOTxOut <$> txOutL + -- We retrieve the index when it was successfully created + index <- case indexOrError of + Left err -> throwError $ MCEGenerationError $ ToCardanoError "estimateTxSkelFee: toCardanoError" err + Right index' -> return index' -- We finally can the fee estimate function return . Emulator.unCoin $ Cardano.calculateMinTxFee Cardano.ShelleyBasedEraConway (Emulator.pEmulatorPParams params) index txBody nkeys From 395cc532c378b6157743a69c52cd4f869f061912 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 8 Aug 2024 17:45:04 +0200 Subject: [PATCH 35/71] no tests built for dependencies, relying on cne directly --- cabal.project | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/cabal.project b/cabal.project index 7e81c2dea..3eb18d3f2 100644 --- a/cabal.project +++ b/cabal.project @@ -10,8 +10,8 @@ package cardano-crypto-praos source-repository-package type: git - location: https://github.com/tweag/cardano-node-emulator-forked/ - tag: 3087d61fc26929dc29b97bf4d25a519907174324 + location: https://github.com/IntersectMBO/cardano-node-emulator/ + tag: 3bdb1f2a578226c1aa39fe09b8fb13e3a0be6d6a subdir: cardano-node-emulator plutus-ledger @@ -41,10 +41,6 @@ index-state: -- We never, ever, want this. write-ghc-environment-files: never --- Always build tests and benchmarks. -tests: true -benchmarks: true - -- The only sensible test display option, since it allows us to have colourized -- 'tasty' output. test-show-details: direct @@ -61,13 +57,7 @@ package cardano-api optimization: False package cardano-crypto-praos flags: -external-libsodium-vrf - --- Haddock needs this to be able to build the documentation -package plutus-script-utils - haddock-options: "--optghc=-fplugin-opt PlutusTx.Plugin:defer-errors" -package plutus-ledger - haddock-options: "--optghc=-fplugin-opt PlutusTx.Plugin:defer-errors" - + constraints: cardano-api ^>= 8.46 From 9d4be2d7213b2bd41d47f6cfe8811ab6a0bf2bcd Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 8 Aug 2024 18:02:21 +0200 Subject: [PATCH 36/71] CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ad33523..7610163ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ constructors: `txSkelSomeRedeemer`, `txSkelEmptyRedeemer`, `txSkelSomeRedeemerAndReferenceScript`, `txSkelEmptyRedeemerAndReferenceScript`. +- Dependency to cardano-api bumped to 8.46. ### Fixed From 48545870a382519679cead3c11141678d83a0ba0 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 8 Aug 2024 18:33:44 +0200 Subject: [PATCH 37/71] relying on the fork for translation functions --- cabal.project | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 3eb18d3f2..7e1c1b17e 100644 --- a/cabal.project +++ b/cabal.project @@ -10,8 +10,8 @@ package cardano-crypto-praos source-repository-package type: git - location: https://github.com/IntersectMBO/cardano-node-emulator/ - tag: 3bdb1f2a578226c1aa39fe09b8fb13e3a0be6d6a + location: https://github.com/tweag/cardano-node-emulator-forked/ + tag: 6155b35bad5a2a2ea3e2791e6bf6bda6127ac535 subdir: cardano-node-emulator plutus-ledger From 55c00e2fe109390e4698f30d1c566d072cc63131 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 29 Aug 2024 02:19:04 +0200 Subject: [PATCH 38/71] credential and staking credential of a wallet --- src/Cooked/Wallet.hs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Cooked/Wallet.hs b/src/Cooked/Wallet.hs index e4d14b66b..47daf586c 100644 --- a/src/Cooked/Wallet.hs +++ b/src/Cooked/Wallet.hs @@ -15,6 +15,8 @@ module Cooked.Wallet walletAddress, walletSK, walletStakingSK, + walletStakingCredential, + walletCredential, Wallet, PrivateKey, ) @@ -87,6 +89,13 @@ walletPKHash = Ledger.pubKeyHash . walletPK walletStakingPKHash :: Wallet -> Maybe Api.PubKeyHash walletStakingPKHash = fmap Ledger.pubKeyHash . walletStakingPK +-- | Retrieves a wallet credential +walletCredential :: Wallet -> Api.Credential +walletCredential = Api.PubKeyCredential . walletPKHash + +walletStakingCredential :: Wallet -> Maybe Api.StakingCredential +walletStakingCredential = (Api.StakingHash . Api.PubKeyCredential <$>) . walletStakingPKHash + -- | Retrieves a wallet's address walletAddress :: Wallet -> Api.Address walletAddress w = From 785be72880310b7d765640ef1db6c7c6396487f3 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 29 Aug 2024 02:20:53 +0200 Subject: [PATCH 39/71] moving time from either the lower or upper bound of current slot --- src/Cooked/MockChain/BlockChain.hs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 1db6eeddb..13a7914be 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -33,6 +33,8 @@ module Cooked.MockChain.BlockChain resolveReferenceScript, getEnclosingSlot, awaitEnclosingSlot, + awaitDurationFromLowerBound, + awaitDurationFromUpperBound, slotRangeBefore, slotRangeAfter, slotToTimeInterval, @@ -437,6 +439,16 @@ getEnclosingSlot t = (`Emulator.posixTimeToEnclosingSlot` t) . Emulator.pSlotCon awaitEnclosingSlot :: (MonadBlockChainWithoutValidation m) => Api.POSIXTime -> m Ledger.Slot awaitEnclosingSlot = awaitSlot <=< getEnclosingSlot +-- | Wait a given number of ms from the lower bound of the current slot and +-- returns the current slot after waiting. +awaitDurationFromLowerBound :: (MonadBlockChainWithoutValidation m) => Integer -> m Ledger.Slot +awaitDurationFromLowerBound duration = currentTime >>= awaitEnclosingSlot . (+ fromIntegral duration) . fst + +-- | Wait a given number of ms from the upper bound of the current slot and +-- returns the current slot after waiting. +awaitDurationFromUpperBound :: (MonadBlockChainWithoutValidation m) => Integer -> m Ledger.Slot +awaitDurationFromUpperBound duration = currentTime >>= awaitEnclosingSlot . (+ fromIntegral duration) . fst + -- | The infinite range of slots ending before or at the given time slotRangeBefore :: (MonadBlockChainWithoutValidation m) => Api.POSIXTime -> m Ledger.SlotRange slotRangeBefore t = do From cdc788b57465121a46d485826a64ff58e6fdbe92 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 29 Aug 2024 11:24:40 +0200 Subject: [PATCH 40/71] depending on cne --- cabal.project | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 7e1c1b17e..3eb18d3f2 100644 --- a/cabal.project +++ b/cabal.project @@ -10,8 +10,8 @@ package cardano-crypto-praos source-repository-package type: git - location: https://github.com/tweag/cardano-node-emulator-forked/ - tag: 6155b35bad5a2a2ea3e2791e6bf6bda6127ac535 + location: https://github.com/IntersectMBO/cardano-node-emulator/ + tag: 3bdb1f2a578226c1aa39fe09b8fb13e3a0be6d6a subdir: cardano-node-emulator plutus-ledger From ee333f6ca57e2b566a2557d66aff27b131422891 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 29 Aug 2024 13:51:42 +0200 Subject: [PATCH 41/71] post merge mini fix --- src/Cooked/MockChain/Balancing.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 720493473..54659fbbb 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -318,7 +318,7 @@ estimateTxSkelFee skel fee mCollaterals = do -- We retrieve the estimate number of required witness in the transaction let nkeys = Cardano.estimateTransactionKeyWitnessCount txBodyContent -- We need to reconstruct an index to pass to the fee estimate function - (knownTxORefs, knownTxOuts) <- unzip . Map.toList <$> lookupUtxos (txSkelKnownTxOutRefs skel <> Set.toList collateralIns) + (knownTxORefs, knownTxOuts) <- unzip . Map.toList <$> lookupUtxos (txSkelKnownTxOutRefs skel <> collateralIns) index <- case forM knownTxORefs Ledger.toCardanoTxIn of Left err -> throwError $ MCEGenerationError $ ToCardanoError "estimateTxSkelFee: unable to generate TxIn" err Right txInL -> return $ Cardano.UTxO $ Map.fromList $ zip txInL $ Cardano.toCtxUTxOTxOut . Ledger.getTxOut <$> knownTxOuts From f08e5041ff12cf13b49be4586b8acfffc4b61390 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 17:02:28 +0200 Subject: [PATCH 42/71] reworking withdrawals for proper maps --- cooked-validators.cabal | 2 +- .../MockChain/GenerateTx/Withdrawals.hs | 4 ++-- src/Cooked/Pretty/Cooked.hs | 6 +++--- src/Cooked/Skeleton.hs | 19 +++++++++++++++---- tests/Cooked/WithdrawalsSpec.hs | 6 +----- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/cooked-validators.cabal b/cooked-validators.cabal index db5ce1f23..beb2a6158 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -1,6 +1,6 @@ cabal-version: 3.4 --- This file has been generated from package.yaml by hpack version 0.36.1. +-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack diff --git a/src/Cooked/MockChain/GenerateTx/Withdrawals.hs b/src/Cooked/MockChain/GenerateTx/Withdrawals.hs index eb9d147b0..ef8eb99c6 100644 --- a/src/Cooked/MockChain/GenerateTx/Withdrawals.hs +++ b/src/Cooked/MockChain/GenerateTx/Withdrawals.hs @@ -37,7 +37,7 @@ toWithdrawals (Map.toList -> withdrawals) = fmap (Cardano.TxWithdrawals Cardano.ShelleyBasedEraConway) $ forM withdrawals - $ \(staker, Script.Lovelace n) -> + $ \(staker, (red, Script.Lovelace n)) -> do (witness, sCred) <- case staker of @@ -46,7 +46,7 @@ toWithdrawals (Map.toList -> withdrawals) = throwOnToCardanoError "toWithdrawals: unable to translate pkh stake credential" $ Cardano.StakeCredentialByKey <$> Ledger.toCardanoStakeKeyHash pkh return (Cardano.KeyWitness Cardano.KeyWitnessForStakeAddr, sCred) - Left (script, red) -> do + Left script -> do witness <- Cardano.ScriptWitness Cardano.ScriptWitnessForStakeAddr <$> liftTxGen (toScriptWitness script red Cardano.NoScriptDatumForStake) diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index 1b44b41b1..c239d82cc 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -186,11 +186,11 @@ prettyWithdrawals :: PrettyCookedOpts -> TxSkelWithdrawals -> Maybe DocCooked prettyWithdrawals pcOpts withdrawals = prettyItemizeNonEmpty "Withdrawals:" "-" $ prettyWithdrawal <$> Map.toList withdrawals where - prettyWithdrawal :: (Either (Script.Versioned Script.Script, TxSkelRedeemer) Api.PubKeyHash, Script.Ada) -> DocCooked - prettyWithdrawal (cred, ada) = + prettyWithdrawal :: (Either (Script.Versioned Script.Script) Api.PubKeyHash, (TxSkelRedeemer, Script.Ada)) -> DocCooked + prettyWithdrawal (cred, (red, ada)) = prettyItemizeNoTitle "-" $ ( case cred of - Left (script, red) -> prettyCookedOpt pcOpts script : prettyTxSkelRedeemer pcOpts red + Left script -> prettyCookedOpt pcOpts script : prettyTxSkelRedeemer pcOpts red Right pkh -> [prettyCookedOpt pcOpts pkh] ) ++ [prettyCookedOpt pcOpts (toValue ada)] diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index f320a9c0e..754c8dd86 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -70,6 +70,8 @@ module Cooked.Skeleton TxSkelWithdrawals, txSkelWithdrawnValue, txSkelWithdrawalsScripts, + pkWithdrawal, + scriptWithdrawal, TxSkel (..), txSkelLabelL, txSkelOptsL, @@ -590,16 +592,25 @@ withAnchor prop url = prop {txSkelProposalAnchor = Just url} -- * Description of the Withdrawals +-- | Withdrawals associate either a script or a private key with a redeemer and +-- a certain amount of ada. Note that the redeemer will be ignored in the case +-- of a private key. type TxSkelWithdrawals = Map - (Either (Script.Versioned Script.Script, TxSkelRedeemer) Api.PubKeyHash) - Script.Ada + (Either (Script.Versioned Script.Script) Api.PubKeyHash) + (TxSkelRedeemer, Script.Ada) txSkelWithdrawnValue :: TxSkel -> Api.Value -txSkelWithdrawnValue = mconcat . (toValue . snd <$>) . Map.toList . txSkelWithdrawals +txSkelWithdrawnValue = mconcat . (toValue . snd . snd <$>) . Map.toList . txSkelWithdrawals txSkelWithdrawalsScripts :: TxSkel -> [Script.Versioned Script.Script] -txSkelWithdrawalsScripts = (fst <$>) . fst . partitionEithers . (fst <$>) . Map.toList . txSkelWithdrawals +txSkelWithdrawalsScripts = fst . partitionEithers . (fst <$>) . Map.toList . txSkelWithdrawals + +pkWithdrawal :: (ToPubKeyHash pkh) => pkh -> Script.Ada -> TxSkelWithdrawals +pkWithdrawal pkh amount = Map.singleton (Right $ toPubKeyHash pkh) (txSkelEmptyRedeemer, amount) + +scriptWithdrawal :: (ToScript script) => script -> TxSkelRedeemer -> Script.Ada -> TxSkelWithdrawals +scriptWithdrawal script red amount = Map.singleton (Left $ toScript script) (red, amount) -- * Description of the Minting diff --git a/tests/Cooked/WithdrawalsSpec.hs b/tests/Cooked/WithdrawalsSpec.hs index a451eb375..def3e0e81 100644 --- a/tests/Cooked/WithdrawalsSpec.hs +++ b/tests/Cooked/WithdrawalsSpec.hs @@ -3,7 +3,6 @@ module Cooked.WithdrawalsSpec where import Control.Monad import Cooked import Data.Default -import Data.Map qualified as Map import Plutus.Script.Utils.Ada qualified as Script import Plutus.Script.Utils.Scripts qualified as Script import PlutusLedgerApi.V3 qualified as Api @@ -40,10 +39,7 @@ testWithdrawingScript n1 n2 = validateTxSkel $ txSkelTemplate { txSkelSigners = [wallet 1], - txSkelWithdrawals = - Map.singleton - (Left (checkWithdrawalVersionedScript, txSkelSomeRedeemer (n1 * 1_000 :: Integer))) - (Script.Lovelace $ n2 * 1_000) + txSkelWithdrawals = scriptWithdrawal checkWithdrawalVersionedScript (txSkelSomeRedeemer (n1 * 1_000 :: Integer)) $ Script.Lovelace $ n2 * 1_000 } tests :: TestTree From b15fc6967867a0389e96ec514878a24dacaeebe4 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 17:07:36 +0200 Subject: [PATCH 43/71] hpack --- cooked-validators.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cooked-validators.cabal b/cooked-validators.cabal index beb2a6158..db5ce1f23 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -1,6 +1,6 @@ cabal-version: 3.4 --- This file has been generated from package.yaml by hpack version 0.36.0. +-- This file has been generated from package.yaml by hpack version 0.36.1. -- -- see: https://github.com/sol/hpack From 91b3309483654dd4d48682bd8ee3a756772011c1 Mon Sep 17 00:00:00 2001 From: mmontin Date: Mon, 2 Sep 2024 12:22:55 +0200 Subject: [PATCH 44/71] homogenizing tests + make them depend on log --- src/Cooked/MockChain/Testing.hs | 44 ++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index 89d4735b6..1d78a895a 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -75,8 +75,8 @@ testSucceeds pcOpts = testSucceedsFrom pcOpts def -- -- To test that validation fails, use -- > testFails def (isCekEvaluationFailure def) e -testFails :: (IsProp prop, Show a) => PrettyCookedOpts -> (MockChainError -> prop) -> StagedMockChain a -> prop -testFails pcOpts predi = testFailsFrom pcOpts predi def +testFails :: (IsProp prop, Show a) => PrettyCookedOpts -> StagedMockChain a -> prop +testFails pcOpts = testFailsFrom pcOpts def -- | Ensure that all results produced by the staged mockchain succeed starting -- from some initial distribution but doesn't impose any additional condition on @@ -87,33 +87,47 @@ testSucceedsFrom :: InitialDistribution -> StagedMockChain a -> prop -testSucceedsFrom pcOpts = testSucceedsFrom' pcOpts (\_ _ -> testSuccess) +testSucceedsFrom pcOpts = testSucceedsFrom' pcOpts (\_ _ _ -> testSuccess) -- | Ensure that all results produced by the staged mockchain succeed starting -- from some initial distribution. Additionally impose a condition over the --- resulting state and value. +-- resulting state, value and log. testSucceedsFrom' :: (IsProp prop) => PrettyCookedOpts -> - (a -> UtxoState -> prop) -> + (a -> UtxoState -> [MockChainLogEntry] -> prop) -> InitialDistribution -> StagedMockChain a -> prop -testSucceedsFrom' pcOpts prop = testAllSatisfiesFrom pcOpts (either (testFailureMsg . renderString (prettyCookedOpt pcOpts)) (uncurry prop)) +testSucceedsFrom' pcOpts prop = testAllSatisfiesFrom pcOpts $ \(res, entries) -> + either + (testFailureMsg . renderString (prettyCookedOpt pcOpts)) + (\(a, state) -> prop a state entries) + res --- | Ensure that all results produced by the staged mockchain /fail/ starting --- from some initial distribution. testFailsFrom :: (IsProp prop, Show a) => PrettyCookedOpts -> - (MockChainError -> prop) -> InitialDistribution -> StagedMockChain a -> prop -testFailsFrom pcOpts predi = - testAllSatisfiesFrom - pcOpts - (either predi (testFailureMsg . renderString (prettyCookedOpt pcOpts))) +testFailsFrom pcOpts = testFailsFrom' pcOpts (\_ _ -> testSuccess) + +-- | Ensure that all results produced by the staged mockchain /fail/ starting +-- from some initial distribution. Additionally impose a condition over the +-- resuting error and log. +testFailsFrom' :: + (IsProp prop, Show a) => + PrettyCookedOpts -> + (MockChainError -> [MockChainLogEntry] -> prop) -> + InitialDistribution -> + StagedMockChain a -> + prop +testFailsFrom' pcOpts predi = testAllSatisfiesFrom pcOpts $ \(res, entries) -> + either + (`predi` entries) + (testFailureMsg . renderString (prettyCookedOpt pcOpts)) + res -- | Is satisfied when the given 'MockChainError' is wrapping a -- @CekEvaluationFailure@. This is particularly important when writing negative @@ -142,14 +156,14 @@ testAllSatisfiesFrom :: forall prop a. (IsProp prop) => PrettyCookedOpts -> - (Either MockChainError (a, UtxoState) -> prop) -> + (MockChainReturn a UtxoState -> prop) -> InitialDistribution -> StagedMockChain a -> prop testAllSatisfiesFrom pcOpts f = testSatisfiesFrom' (testAll go) where go :: MockChainReturn a UtxoState -> prop - go (prop, mcLog) = testCounterexample (renderString (prettyCookedOpt pcOpts) mcLog) (f prop) + go ret@(_, mcLog) = testCounterexample (renderString (prettyCookedOpt pcOpts) mcLog) (f ret) -- | Asserts that the given 'StagedMockChain' produces exactly two outcomes, -- both of which are successful and have their resulting states related by a From 15699d2683d840110941b0a1ef2304049fa62467 Mon Sep 17 00:00:00 2001 From: mmontin Date: Mon, 2 Sep 2024 12:40:50 +0200 Subject: [PATCH 45/71] one more refactoring --- src/Cooked/MockChain/Testing.hs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index 1d78a895a..9aa41bf1b 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -70,17 +70,13 @@ a .||. b = testDisjoin [a, b] testSucceeds :: (IsProp prop) => PrettyCookedOpts -> StagedMockChain a -> prop testSucceeds pcOpts = testSucceedsFrom pcOpts def --- | Ensure that all results produced by the staged mockchain /fail/ and that a --- predicate holds over the error. --- --- To test that validation fails, use --- > testFails def (isCekEvaluationFailure def) e +-- | Ensure that all results produced by the staged mockchain /fail/ testFails :: (IsProp prop, Show a) => PrettyCookedOpts -> StagedMockChain a -> prop testFails pcOpts = testFailsFrom pcOpts def -- | Ensure that all results produced by the staged mockchain succeed starting -- from some initial distribution but doesn't impose any additional condition on --- success. Use 'testSucceedsFrom'' for that. +-- success. Use 'testSucceedsFrom'' for that. testSucceedsFrom :: (IsProp prop) => PrettyCookedOpts -> @@ -105,6 +101,9 @@ testSucceedsFrom' pcOpts prop = testAllSatisfiesFrom pcOpts $ \(res, entries) -> (\(a, state) -> prop a state entries) res +-- | Ensure that all results produced by the staged mockchain fail starting +-- from some initial distribution but doesn't impose any additional condition on +-- failure. Use 'testFailssFrom'' for that. testFailsFrom :: (IsProp prop, Show a) => PrettyCookedOpts -> @@ -160,10 +159,11 @@ testAllSatisfiesFrom :: InitialDistribution -> StagedMockChain a -> prop -testAllSatisfiesFrom pcOpts f = testSatisfiesFrom' (testAll go) - where - go :: MockChainReturn a UtxoState -> prop - go ret@(_, mcLog) = testCounterexample (renderString (prettyCookedOpt pcOpts) mcLog) (f ret) +testAllSatisfiesFrom pcOpts f = + testSatisfiesFrom' $ + testAll $ + \ret@(_, mcLog) -> + testCounterexample (renderString (prettyCookedOpt pcOpts) mcLog) (f ret) -- | Asserts that the given 'StagedMockChain' produces exactly two outcomes, -- both of which are successful and have their resulting states related by a From 4d122553a36b4a465b1b2c8d441303c856a7ec33 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 5 Sep 2024 17:14:10 +0200 Subject: [PATCH 46/71] refactoring testing in progress --- src/Cooked/MockChain/Testing.hs | 236 +++++++++++--------------------- tests/Cooked/WithdrawalsSpec.hs | 2 +- 2 files changed, 79 insertions(+), 159 deletions(-) diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index 9aa41bf1b..63c90c1bc 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -65,68 +65,88 @@ infixr 2 .||. (.||.) :: (IsProp prop) => prop -> prop -> prop a .||. b = testDisjoin [a, b] +data Test a prop where + Test :: + { testRun :: StagedMockChain a, + testInitDist :: InitialDistribution, + testErrorProp :: PrettyCookedOpts -> MockChainError -> [MockChainLogEntry] -> prop, + testResultProp :: PrettyCookedOpts -> a -> UtxoState -> [MockChainLogEntry] -> prop, + testPrettyOpts :: PrettyCookedOpts + } -> + Test a prop + +mustSucceedTest :: (IsProp prop) => StagedMockChain a -> Test a prop +mustSucceedTest run = + Test + { testRun = run, + testInitDist = def, + testErrorProp = \opts res _ -> testFailureMsg $ renderString (prettyCookedOpt opts) res, + testResultProp = \_ _ _ _ -> testSuccess, + testPrettyOpts = def + } + +mustFailTest :: (IsProp prop, Show a) => StagedMockChain a -> Test a prop +mustFailTest run = + Test + { testRun = run, + testInitDist = def, + testErrorProp = \_ _ _ -> testSuccess, + testResultProp = \opts a res _ -> testFailureMsg $ renderString (prettyCookedOpt opts) (a, res), + testPrettyOpts = def + } + +emptyTest :: (IsProp prop) => StagedMockChain a -> Test a prop +emptyTest run = + Test + { testRun = run, + testInitDist = def, + testErrorProp = \_ _ _ -> testSuccess, + testResultProp = \_ _ _ _ -> testSuccess, + testPrettyOpts = def + } + +infixl 5 ==> + +class AddToTest a prop b where + (==>) :: Test a prop -> b -> Test a prop + +instance AddToTest a prop InitialDistribution where + test ==> initDist = test {testInitDist = initDist} + +instance AddToTest a prop PrettyCookedOpts where + test ==> opts = test {testPrettyOpts = opts} + +instance (IsProp prop) => AddToTest a prop (MockChainError -> prop) where + test ==> errorProp = test {testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. errorProp err} + +instance (IsProp prop) => AddToTest a prop ([MockChainLogEntry] -> prop) where + test ==> journalProp = + test + { testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. journalProp journal, + testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. journalProp journal + } + +instance (IsProp prop) => AddToTest a prop (a -> UtxoState -> prop) where + test ==> resultProp = test {testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. resultProp val state} + +testProp :: (IsProp prop) => Test a prop -> prop +testProp Test {..} = + let innerProp (res, mcLog) = + case res of + Left err -> testErrorProp testPrettyOpts err mcLog + Right (result, state) -> testResultProp testPrettyOpts result state mcLog + in testAll + (\ret@(_, mcLog) -> testCounterexample (renderString (prettyCookedOpt testPrettyOpts) mcLog) (innerProp ret)) + (interpretAndRunWith (runMockChainTFrom testInitDist) testRun) + -- | Ensure that all results produced by the staged mockchain /succeed/, -- starting from the default initial distribution -testSucceeds :: (IsProp prop) => PrettyCookedOpts -> StagedMockChain a -> prop -testSucceeds pcOpts = testSucceedsFrom pcOpts def +testSucceeds :: (IsProp prop) => StagedMockChain a -> prop +testSucceeds = testProp . mustSucceedTest -- | Ensure that all results produced by the staged mockchain /fail/ -testFails :: (IsProp prop, Show a) => PrettyCookedOpts -> StagedMockChain a -> prop -testFails pcOpts = testFailsFrom pcOpts def - --- | Ensure that all results produced by the staged mockchain succeed starting --- from some initial distribution but doesn't impose any additional condition on --- success. Use 'testSucceedsFrom'' for that. -testSucceedsFrom :: - (IsProp prop) => - PrettyCookedOpts -> - InitialDistribution -> - StagedMockChain a -> - prop -testSucceedsFrom pcOpts = testSucceedsFrom' pcOpts (\_ _ _ -> testSuccess) - --- | Ensure that all results produced by the staged mockchain succeed starting --- from some initial distribution. Additionally impose a condition over the --- resulting state, value and log. -testSucceedsFrom' :: - (IsProp prop) => - PrettyCookedOpts -> - (a -> UtxoState -> [MockChainLogEntry] -> prop) -> - InitialDistribution -> - StagedMockChain a -> - prop -testSucceedsFrom' pcOpts prop = testAllSatisfiesFrom pcOpts $ \(res, entries) -> - either - (testFailureMsg . renderString (prettyCookedOpt pcOpts)) - (\(a, state) -> prop a state entries) - res - --- | Ensure that all results produced by the staged mockchain fail starting --- from some initial distribution but doesn't impose any additional condition on --- failure. Use 'testFailssFrom'' for that. -testFailsFrom :: - (IsProp prop, Show a) => - PrettyCookedOpts -> - InitialDistribution -> - StagedMockChain a -> - prop -testFailsFrom pcOpts = testFailsFrom' pcOpts (\_ _ -> testSuccess) - --- | Ensure that all results produced by the staged mockchain /fail/ starting --- from some initial distribution. Additionally impose a condition over the --- resuting error and log. -testFailsFrom' :: - (IsProp prop, Show a) => - PrettyCookedOpts -> - (MockChainError -> [MockChainLogEntry] -> prop) -> - InitialDistribution -> - StagedMockChain a -> - prop -testFailsFrom' pcOpts predi = testAllSatisfiesFrom pcOpts $ \(res, entries) -> - either - (`predi` entries) - (testFailureMsg . renderString (prettyCookedOpt pcOpts)) - res +testFails :: (IsProp prop, Show a) => StagedMockChain a -> prop +testFails = testProp . mustFailTest -- | Is satisfied when the given 'MockChainError' is wrapping a -- @CekEvaluationFailure@. This is particularly important when writing negative @@ -147,106 +167,6 @@ isCekEvaluationFailureWithMsg _ f (MCEValidationError _ (Ledger.ScriptFailure (L | any (f . T.unpack) msgs = testSuccess isCekEvaluationFailureWithMsg pcOpts _ e = testFailureMsg $ "Expected 'CekEvaluationFailure' with specific messages, got: " ++ renderString (prettyCookedOpt pcOpts) e --- | Ensure that all results produced by the set of traces encoded by the --- 'StagedMockChain' satisfy the given predicate. If you wish to build custom --- predicates you can use 'testSatisfiesFrom'' directly and see --- 'testBinaryRelatedBy' as an example. -testAllSatisfiesFrom :: - forall prop a. - (IsProp prop) => - PrettyCookedOpts -> - (MockChainReturn a UtxoState -> prop) -> - InitialDistribution -> - StagedMockChain a -> - prop -testAllSatisfiesFrom pcOpts f = - testSatisfiesFrom' $ - testAll $ - \ret@(_, mcLog) -> - testCounterexample (renderString (prettyCookedOpt pcOpts) mcLog) (f ret) - --- | Asserts that the given 'StagedMockChain' produces exactly two outcomes, --- both of which are successful and have their resulting states related by a --- given predicate. A typical usage would look like: --- --- > testBinaryRelatedBy equalModuloAda myInitDistr $ do --- > x <- trPrepare --- > execOption1 x <|> execOption2 x -testBinaryRelatedBy :: - (IsProp prop) => - PrettyCookedOpts -> - (UtxoState -> UtxoState -> prop) -> - InitialDistribution -> - StagedMockChain a -> - prop -testBinaryRelatedBy pcOpts rel = testSatisfiesFrom' $ \case - [(ra, ta), (rb, tb)] -> case (ra, rb) of - (Right resA, Right resB) -> rel (snd resA) (snd resB) - (Left errA, Right _) -> - testFailureMsg $ concat ["Expected two outcomes, the first failed with:", renderString (prettyCookedOpt pcOpts) errA, "\n", renderString (prettyCookedOpt pcOpts) ta] - (Right _, Left errB) -> - testFailureMsg $ concat ["Expected two outcomes, the second failed with:", renderString (prettyCookedOpt pcOpts) errB, "\n", renderString (prettyCookedOpt pcOpts) tb] - (Left errA, Left errB) -> - testFailureMsg $ - concat - [ "Expected two outcomes, both failed with:", - renderString (prettyCookedOpt pcOpts) errA, - "; ", - renderString (prettyCookedOpt pcOpts) errB, - "\n First: ", - renderString (prettyCookedOpt pcOpts) ta, - "\nSecond: ", - renderString (prettyCookedOpt pcOpts) tb - ] - xs -> testFailureMsg $ "Expected exactly two outcomes, received: " ++ show (length xs) - --- | Generalizes 'testBinaryRelatedBy', asserting that the given --- 'StagedMockChain' produces more than two outcomes, say @[x,y,z,w]@, all of --- which are successful (i.e. are not a 'MockChainError') and these states are --- in the same equivalence class of (~); that is, they satisfy: --- --- > x ~ y && x ~ z && x ~ z && x ~ w --- --- Because @(~)@ should be symmetric and transitive we can estabilish that these --- states all belong to the same equivalence class. This function does /not/ --- check each pointwise case. -testOneEquivClass :: - (IsProp prop) => - PrettyCookedOpts -> - (UtxoState -> UtxoState -> prop) -> - InitialDistribution -> - StagedMockChain a -> - prop -testOneEquivClass pcOpts rel = testSatisfiesFrom' $ \case - [] -> testFailureMsg "Expected two of more outcomes, received: 0" - [_] -> testFailureMsg "Expected two of more outcomes, received: 1" - ((Left errX, tx) : _) -> testFailureMsg $ concat ["First outcome is a failure: ", renderString (prettyCookedOpt pcOpts) errX, "\n", renderString (prettyCookedOpt pcOpts) tx] - ((Right resX, _) : xs) -> go (snd resX) xs - where - -- we can flag a success here because 'xs' above is guarnateed to have at - -- least one element since we ruled out the empty and the singleton lists in - -- the \case - go _resX [] = testSuccess - go _resX ((Left errY, ty) : _) = testFailureMsg $ concat ["An outcome is a failure: ", renderString (prettyCookedOpt pcOpts) errY, "\n", renderString (prettyCookedOpt pcOpts) ty] - go resX ((Right (_, resY), _) : ys) = testConjoin [rel resX resY, go resX ys] - --- | Asserts that the results produced by running the given 'StagedMockChain' --- from some speficied 'InitialDistribution' satisfy a given assertion. In this --- case, the predicate gets the trace descriptions that led to each potential --- outcome and is responsible for calling 'testCounterexample' communicate these --- to the user. --- --- Although this function is mainly used internally, as a building block for the --- simpler predicates, it can be useful in building some custom --- predicates. Check 'testAllSatisfiesFrom' or 'testBinaryRelatedBy' for --- examples on using this. -testSatisfiesFrom' :: - ([MockChainReturn a UtxoState] -> prop) -> - InitialDistribution -> - StagedMockChain a -> - prop -testSatisfiesFrom' predi i0 = predi . interpretAndRunWith (runMockChainTFrom i0) - -- * 'TestResult' instances -- Catches a HUnit test failure, if the test fails. diff --git a/tests/Cooked/WithdrawalsSpec.hs b/tests/Cooked/WithdrawalsSpec.hs index def3e0e81..8f81cb5ac 100644 --- a/tests/Cooked/WithdrawalsSpec.hs +++ b/tests/Cooked/WithdrawalsSpec.hs @@ -50,6 +50,6 @@ tests = testSucceeds def $ testWithdrawingScript 2 2, testCase "But the script might fail" $ - testFailsFrom def (isCekEvaluationFailure def) def $ + testFailsFrom' def (isCekEvaluationFailure def) def $ testWithdrawingScript 2 1 ] From 1aed73d70b9120ac3a3ea94fe305d582db2f89df Mon Sep 17 00:00:00 2001 From: mmontin Date: Mon, 9 Sep 2024 12:19:37 +0200 Subject: [PATCH 47/71] finishing to rework testing --- src/Cooked/MockChain/Testing.hs | 54 ++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index 63c90c1bc..cd1f2c04a 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -116,9 +116,6 @@ instance AddToTest a prop InitialDistribution where instance AddToTest a prop PrettyCookedOpts where test ==> opts = test {testPrettyOpts = opts} -instance (IsProp prop) => AddToTest a prop (MockChainError -> prop) where - test ==> errorProp = test {testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. errorProp err} - instance (IsProp prop) => AddToTest a prop ([MockChainLogEntry] -> prop) where test ==> journalProp = test @@ -129,6 +126,12 @@ instance (IsProp prop) => AddToTest a prop ([MockChainLogEntry] -> prop) where instance (IsProp prop) => AddToTest a prop (a -> UtxoState -> prop) where test ==> resultProp = test {testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. resultProp val state} +instance (IsProp prop) => AddToTest a prop (PrettyCookedOpts -> MockChainError -> prop) where + test ==> errorProp = test {testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. errorProp opts err} + +instance (IsProp prop) => AddToTest a prop (MockChainError -> prop) where + test ==> errorProp = test ==> \(_ :: PrettyCookedOpts) -> errorProp + testProp :: (IsProp prop) => Test a prop -> prop testProp Test {..} = let innerProp (res, mcLog) = @@ -148,24 +151,33 @@ testSucceeds = testProp . mustSucceedTest testFails :: (IsProp prop, Show a) => StagedMockChain a -> prop testFails = testProp . mustFailTest --- | Is satisfied when the given 'MockChainError' is wrapping a --- @CekEvaluationFailure@. This is particularly important when writing negative --- tests. For example, if we are simulating an attack and writing a test with --- 'testFailsFrom', we might have made a mistake in the attack, yielding a test --- that fails for reasons such as @ValueLessThanMinAda@ or @ValueNotPreserved@, --- which does not rule out the attack being caught by the validator script. For --- these scenarios it is paramount to rely on @testFailsFrom' --- isCekEvaluationFailure@ instead. -isCekEvaluationFailure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop -isCekEvaluationFailure _ (MCEValidationError _ (Ledger.ScriptFailure _)) = testSuccess -isCekEvaluationFailure pcOpts e = testFailureMsg $ "Expected 'CekEvaluationFailure', got: " ++ renderString (prettyCookedOpt pcOpts) e - --- | Similar to 'isCekEvaluationFailure', but enables us to check for a specific --- error message in the error. -isCekEvaluationFailureWithMsg :: (IsProp prop) => PrettyCookedOpts -> (String -> Bool) -> MockChainError -> prop -isCekEvaluationFailureWithMsg _ f (MCEValidationError _ (Ledger.ScriptFailure (Ledger.EvaluationError msgs _))) - | any (f . T.unpack) msgs = testSuccess -isCekEvaluationFailureWithMsg pcOpts _ e = testFailureMsg $ "Expected 'CekEvaluationFailure' with specific messages, got: " ++ renderString (prettyCookedOpt pcOpts) e +isPhase1Failure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop +isPhase1Failure _ (MCEValidationError Ledger.Phase1 _) = testSuccess +isPhase1Failure pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure, got: " ++ renderString (prettyCookedOpt pcOpts) e + +testFailsInPhase1 :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> prop +testFailsInPhase1 run = testProp $ mustFailTest run ==> isPhase1Failure @prop + +isPhase2Failure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop +isPhase2Failure _ (MCEValidationError Ledger.Phase2 _) = testSuccess +isPhase2Failure pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure, got: " ++ renderString (prettyCookedOpt pcOpts) e + +testFailsInPhase2 :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> prop +testFailsInPhase2 run = testProp $ mustFailTest run ==> isPhase2Failure @prop + +isPhase1FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop +isPhase1FailureWithMsg f _ (MCEValidationError Ledger.Phase1 (Ledger.CardanoLedgerValidationError text)) | f $ T.unpack text = testSuccess +isPhase1FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure with constrained messages, got: " ++ renderString (prettyCookedOpt pcOpts) e + +testFailsInPhase1WithMsg :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> (String -> Bool) -> prop +testFailsInPhase1WithMsg run f = testProp $ mustFailTest run ==> isPhase1FailureWithMsg @prop f + +isPhase2FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop +isPhase2FailureWithMsg f _ (MCEValidationError Ledger.Phase2 (Ledger.ScriptFailure (Ledger.EvaluationError texts _))) | any (f . T.unpack) texts = testSuccess +isPhase2FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure with constrained messages, got: " ++ renderString (prettyCookedOpt pcOpts) e + +testFailsInPhase2WithMsg :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> (String -> Bool) -> prop +testFailsInPhase2WithMsg run f = testProp $ mustFailTest run ==> isPhase2FailureWithMsg @prop f -- * 'TestResult' instances From 5d2ae4ef4f198d917744ef7135c7e8c59cf7887a Mon Sep 17 00:00:00 2001 From: mmontin Date: Mon, 9 Sep 2024 12:35:05 +0200 Subject: [PATCH 48/71] flipping ==> --- src/Cooked/MockChain/Testing.hs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index cd1f2c04a..bbf5dd219 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -105,32 +105,32 @@ emptyTest run = testPrettyOpts = def } -infixl 5 ==> +infixl 5 <== class AddToTest a prop b where - (==>) :: Test a prop -> b -> Test a prop + (<==) :: b -> Test a prop -> Test a prop instance AddToTest a prop InitialDistribution where - test ==> initDist = test {testInitDist = initDist} + initDist <== test = test {testInitDist = initDist} instance AddToTest a prop PrettyCookedOpts where - test ==> opts = test {testPrettyOpts = opts} + opts <== test = test {testPrettyOpts = opts} instance (IsProp prop) => AddToTest a prop ([MockChainLogEntry] -> prop) where - test ==> journalProp = + journalProp <== test = test { testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. journalProp journal, testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. journalProp journal } instance (IsProp prop) => AddToTest a prop (a -> UtxoState -> prop) where - test ==> resultProp = test {testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. resultProp val state} + resultProp <== test = test {testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. resultProp val state} instance (IsProp prop) => AddToTest a prop (PrettyCookedOpts -> MockChainError -> prop) where - test ==> errorProp = test {testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. errorProp opts err} + errorProp <== test = test {testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. errorProp opts err} instance (IsProp prop) => AddToTest a prop (MockChainError -> prop) where - test ==> errorProp = test ==> \(_ :: PrettyCookedOpts) -> errorProp + errorProp <== test = (\(_ :: PrettyCookedOpts) -> errorProp) <== test testProp :: (IsProp prop) => Test a prop -> prop testProp Test {..} = @@ -156,28 +156,28 @@ isPhase1Failure _ (MCEValidationError Ledger.Phase1 _) = testSuccess isPhase1Failure pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure, got: " ++ renderString (prettyCookedOpt pcOpts) e testFailsInPhase1 :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> prop -testFailsInPhase1 run = testProp $ mustFailTest run ==> isPhase1Failure @prop +testFailsInPhase1 = testProp . (isPhase1Failure @prop <==) . mustFailTest isPhase2Failure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop isPhase2Failure _ (MCEValidationError Ledger.Phase2 _) = testSuccess isPhase2Failure pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure, got: " ++ renderString (prettyCookedOpt pcOpts) e testFailsInPhase2 :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> prop -testFailsInPhase2 run = testProp $ mustFailTest run ==> isPhase2Failure @prop +testFailsInPhase2 = testProp . (isPhase2Failure @prop <==) . mustFailTest isPhase1FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop isPhase1FailureWithMsg f _ (MCEValidationError Ledger.Phase1 (Ledger.CardanoLedgerValidationError text)) | f $ T.unpack text = testSuccess isPhase1FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure with constrained messages, got: " ++ renderString (prettyCookedOpt pcOpts) e -testFailsInPhase1WithMsg :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> (String -> Bool) -> prop -testFailsInPhase1WithMsg run f = testProp $ mustFailTest run ==> isPhase1FailureWithMsg @prop f +testFailsInPhase1WithMsg :: forall prop a. (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop +testFailsInPhase1WithMsg f = testProp . (isPhase1FailureWithMsg @prop f <==) . mustFailTest isPhase2FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop isPhase2FailureWithMsg f _ (MCEValidationError Ledger.Phase2 (Ledger.ScriptFailure (Ledger.EvaluationError texts _))) | any (f . T.unpack) texts = testSuccess isPhase2FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure with constrained messages, got: " ++ renderString (prettyCookedOpt pcOpts) e -testFailsInPhase2WithMsg :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> (String -> Bool) -> prop -testFailsInPhase2WithMsg run f = testProp $ mustFailTest run ==> isPhase2FailureWithMsg @prop f +testFailsInPhase2WithMsg :: forall prop a. (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop +testFailsInPhase2WithMsg f = testProp . (isPhase2FailureWithMsg @prop f <==) . mustFailTest -- * 'TestResult' instances From 6a879871cb895e7f3f157cdddb79b26acd7c2e4c Mon Sep 17 00:00:00 2001 From: mmontin Date: Mon, 9 Sep 2024 14:53:35 +0200 Subject: [PATCH 49/71] restructuring and commenting testing module --- src/Cooked/MockChain/Testing.hs | 207 +++++++++++++++++++------------- 1 file changed, 123 insertions(+), 84 deletions(-) diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index bbf5dd219..919d234e7 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -17,10 +17,11 @@ import Ledger qualified import Test.QuickCheck qualified as QC import Test.Tasty.HUnit qualified as HU --- | This module provides a common interface for HUnit and QuickCheck tests. We --- do so by abstracting uses of 'HU.Assertion' and 'QC.Property' for @(IsProp --- prop) => prop@, then provide instances for both @HU.Asserton@ and --- @QC.Property@. +-- * Common interface between HUnit and QuickCheck + +-- | 'IsProp' is a common interface for HUnit and QuickCheck tests. It abstracts +-- uses of 'HU.Assertion' and 'QC.Property' for @(IsProp prop) => prop@, then +-- provide instances for both @HU.Asserton@ and @QC.Property@. class IsProp prop where -- | Displays the string to the user in case of failure testCounterexample :: String -> prop -> prop @@ -65,16 +66,99 @@ infixr 2 .||. (.||.) :: (IsProp prop) => prop -> prop -> prop a .||. b = testDisjoin [a, b] -data Test a prop where - Test :: - { testRun :: StagedMockChain a, - testInitDist :: InitialDistribution, - testErrorProp :: PrettyCookedOpts -> MockChainError -> [MockChainLogEntry] -> prop, - testResultProp :: PrettyCookedOpts -> a -> UtxoState -> [MockChainLogEntry] -> prop, - testPrettyOpts :: PrettyCookedOpts - } -> - Test a prop +-- | Catches a HUnit test failure, if the test fails. +assertionToMaybe :: HU.Assertion -> IO (Maybe HU.HUnitFailure) +assertionToMaybe = flip E.catches [E.Handler $ return . Just] . (>> return Nothing) + +-- | HUnit instance of 'IsProp' +instance IsProp HU.Assertion where + testCounterexample msg = maybe testSuccess (E.throw . adjustMsg) <=< assertionToMaybe + where + joinMsg :: String -> String + joinMsg rest = msg ++ ";\n" ++ rest + + adjustMsg :: HU.HUnitFailure -> HU.HUnitFailure + adjustMsg (HU.HUnitFailure loc txt) = + HU.HUnitFailure loc (joinMsg txt) + + testFailure = HU.assertFailure "" + testFailureMsg = HU.assertFailure + + testConjoin = sequence_ + + testDisjoin [] = testFailure + testDisjoin (x : xs) = assertionToMaybe x >>= maybe (testDisjoin xs) E.throw + + testSuccess = return () + +-- | QuickCheck instance of 'IsProp' +instance IsProp QC.Property where + testCounterexample = QC.counterexample + testFailure = QC.property False + testSuccess = QC.property True + testConjoin = QC.conjoin + testDisjoin = QC.disjoin + +-- | Here we provide our own universsal quantifier instead of 'QC.forAll', so we +-- can monomorphize it to returning a 'QC.Property' +forAll :: (Show a) => QC.Gen a -> (a -> QC.Property) -> QC.Property +forAll = QC.forAll + +-- | Bool instance of 'IsProp' instances + +-- TODO: Discuss this instance; its here to enable us to easily run things in a +-- repl but I'm not sure whether to ignore the counterexample messages or not. +instance IsProp Bool where + testCounterexample msg False = trace msg False + testCounterexample _ True = True + testConjoin = and + testDisjoin = or + +-- * Extra HUnit assertions +-- | Asserts whether a set is a subset of another one, both given as lists. +assertSubset :: (Show a, Eq a) => [a] -> [a] -> HU.Assertion +assertSubset l r = + testConjoin + ( map + ( \x -> + HU.assertBool + ( "not a subset:\n\n" + ++ show x + ++ "\n\nis not an element of\n\n" + ++ show r + ) + $ x `elem` r + ) + l + ) + +-- | Asserts whether 2 sets are equal, both given as lists. +assertSameSets :: (Show a, Eq a) => [a] -> [a] -> HU.Assertion +assertSameSets l r = + HU.assertBool + ("expected lists of the same length, got " ++ show (length l) ++ " and " ++ show (length r)) + (length l == length r) + .&&. assertSubset l r + .&&. assertSubset r l + +-- * Testing mockchain runs + +-- | Data structure to test a mockchain run +data Test a prop = Test + { -- | The mockchain run to test + testRun :: StagedMockChain a, + -- | The initial distribution from which the run should be ran + testInitDist :: InitialDistribution, + -- | The property should hold in case of failure + testErrorProp :: PrettyCookedOpts -> MockChainError -> [MockChainLogEntry] -> prop, + -- | The property that should hold in case of success + testResultProp :: PrettyCookedOpts -> a -> UtxoState -> [MockChainLogEntry] -> prop, + -- | The printing option that should be use to render test results + testPrettyOpts :: PrettyCookedOpts + } + +-- | A test template which expects a success from a run mustSucceedTest :: (IsProp prop) => StagedMockChain a -> Test a prop mustSucceedTest run = Test @@ -85,6 +169,7 @@ mustSucceedTest run = testPrettyOpts = def } +-- | A test template which expects a failure from a run mustFailTest :: (IsProp prop, Show a) => StagedMockChain a -> Test a prop mustFailTest run = Test @@ -95,6 +180,7 @@ mustFailTest run = testPrettyOpts = def } +-- | A test template with no particular requirement on the run emptyTest :: (IsProp prop) => StagedMockChain a -> Test a prop emptyTest run = Test @@ -107,15 +193,20 @@ emptyTest run = infixl 5 <== +-- | The represents anything that can be added to a test class AddToTest a prop b where (<==) :: b -> Test a prop -> Test a prop +-- | Appending an initial distribution to a test instance AddToTest a prop InitialDistribution where initDist <== test = test {testInitDist = initDist} +-- | Appending printing options to a test instance AddToTest a prop PrettyCookedOpts where opts <== test = test {testPrettyOpts = opts} +-- | Appending a predicate over the log to a test. This will be used both in +-- case of success or failure of the run. instance (IsProp prop) => AddToTest a prop ([MockChainLogEntry] -> prop) where journalProp <== test = test @@ -123,15 +214,25 @@ instance (IsProp prop) => AddToTest a prop ([MockChainLogEntry] -> prop) where testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. journalProp journal } +-- | Appending a predicate over the resulting mockchain state and value, which +-- will be used in case of success of the run. instance (IsProp prop) => AddToTest a prop (a -> UtxoState -> prop) where - resultProp <== test = test {testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. resultProp val state} + resultProp <== test = + test + { testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. resultProp val state + } +-- | Appending a predicate over an error which uses the printing options, which +-- will be used in case of failure of the run. instance (IsProp prop) => AddToTest a prop (PrettyCookedOpts -> MockChainError -> prop) where errorProp <== test = test {testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. errorProp opts err} +-- | Appending a predicate over and error, which will be used in case of +-- failure of the run. instance (IsProp prop) => AddToTest a prop (MockChainError -> prop) where errorProp <== test = (\(_ :: PrettyCookedOpts) -> errorProp) <== test +-- | This takes a test and transforms it into an actual test case in prop. testProp :: (IsProp prop) => Test a prop -> prop testProp Test {..} = let innerProp (res, mcLog) = @@ -151,100 +252,38 @@ testSucceeds = testProp . mustSucceedTest testFails :: (IsProp prop, Show a) => StagedMockChain a -> prop testFails = testProp . mustFailTest +-- | A property to ensure a phase 1 failure isPhase1Failure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop isPhase1Failure _ (MCEValidationError Ledger.Phase1 _) = testSuccess isPhase1Failure pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure, got: " ++ renderString (prettyCookedOpt pcOpts) e +-- | A test that succeeds when the run result in a phase 1 failure testFailsInPhase1 :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> prop testFailsInPhase1 = testProp . (isPhase1Failure @prop <==) . mustFailTest +-- | A property to ensure a phase 2 failure isPhase2Failure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop isPhase2Failure _ (MCEValidationError Ledger.Phase2 _) = testSuccess isPhase2Failure pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure, got: " ++ renderString (prettyCookedOpt pcOpts) e +-- | A test that succeeds when the run result in a phase 2 failure testFailsInPhase2 :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> prop testFailsInPhase2 = testProp . (isPhase2Failure @prop <==) . mustFailTest +-- | Same as 'isPhaseIFailure' with an added predicate on the text error isPhase1FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop isPhase1FailureWithMsg f _ (MCEValidationError Ledger.Phase1 (Ledger.CardanoLedgerValidationError text)) | f $ T.unpack text = testSuccess isPhase1FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure with constrained messages, got: " ++ renderString (prettyCookedOpt pcOpts) e +-- | Same as 'testFailsInPhase1' with an added predicate on the text error testFailsInPhase1WithMsg :: forall prop a. (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop testFailsInPhase1WithMsg f = testProp . (isPhase1FailureWithMsg @prop f <==) . mustFailTest +-- | Same as 'isPhase2Failure' with an added predicate over the text error isPhase2FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop isPhase2FailureWithMsg f _ (MCEValidationError Ledger.Phase2 (Ledger.ScriptFailure (Ledger.EvaluationError texts _))) | any (f . T.unpack) texts = testSuccess isPhase2FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure with constrained messages, got: " ++ renderString (prettyCookedOpt pcOpts) e +-- | Same as 'testFailsInPhase2' with an added predicate over the text error testFailsInPhase2WithMsg :: forall prop a. (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop testFailsInPhase2WithMsg f = testProp . (isPhase2FailureWithMsg @prop f <==) . mustFailTest - --- * 'TestResult' instances - --- Catches a HUnit test failure, if the test fails. -assertionToMaybe :: HU.Assertion -> IO (Maybe HU.HUnitFailure) -assertionToMaybe = flip E.catches [E.Handler $ return . Just] . (>> return Nothing) - -instance IsProp HU.Assertion where - testCounterexample msg = maybe testSuccess (E.throw . adjustMsg) <=< assertionToMaybe - where - joinMsg :: String -> String - joinMsg rest = msg ++ ";\n" ++ rest - - adjustMsg :: HU.HUnitFailure -> HU.HUnitFailure - adjustMsg (HU.HUnitFailure loc txt) = - HU.HUnitFailure loc (joinMsg txt) - - testFailure = HU.assertFailure "" - testFailureMsg = HU.assertFailure - - testConjoin = sequence_ - - testDisjoin [] = testFailure - testDisjoin (x : xs) = assertionToMaybe x >>= maybe (testDisjoin xs) E.throw - - testSuccess = return () - -instance IsProp QC.Property where - testCounterexample = QC.counterexample - testFailure = QC.property False - testSuccess = QC.property True - testConjoin = QC.conjoin - testDisjoin = QC.disjoin - --- | Here we provide our own universsal quantifier instead of 'QC.forAll', so we --- can monomorphize it to returning a 'QC.Property' -forAll :: (Show a) => QC.Gen a -> (a -> QC.Property) -> QC.Property -forAll = QC.forAll - --- TODO: Discuss this instance; its here to enable us to easily run things in a --- repl but I'm not sure whether to ignore the counterexample messages or not. -instance IsProp Bool where - testCounterexample msg False = trace msg False - testCounterexample _ True = True - testConjoin = and - testDisjoin = or - -assertSubset :: (Show a, Eq a) => [a] -> [a] -> HU.Assertion -assertSubset l r = - testConjoin - ( map - ( \x -> - HU.assertBool - ( "not a subset:\n\n" - ++ show x - ++ "\n\nis not an element of\n\n" - ++ show r - ) - $ x `elem` r - ) - l - ) - -assertSameSets :: (Show a, Eq a) => [a] -> [a] -> HU.Assertion -assertSameSets l r = - HU.assertBool - ("expected lists of the same length, got " ++ show (length l) ++ " and " ++ show (length r)) - (length l == length r) - .&&. assertSubset l r - .&&. assertSubset r l From 410d7b271a5c33df3a0d9311d90c4f880ed426a6 Mon Sep 17 00:00:00 2001 From: mmontin Date: Tue, 10 Sep 2024 11:55:52 +0200 Subject: [PATCH 50/71] spreading around all the changes, removing fancy non-working type class --- src/Cooked/MockChain/Testing.hs | 90 ++++++---- src/Cooked/MockChain/UtxoState.hs | 4 +- tests/Cooked/Attack/DatumHijackingSpec.hs | 45 +++-- tests/Cooked/Attack/DupTokenSpec.hs | 13 +- tests/Cooked/BalancingSpec.hs | 163 +++++++++--------- tests/Cooked/BasicUsageSpec.hs | 10 +- tests/Cooked/InitialDistributionSpec.hs | 11 +- tests/Cooked/InlineDatumsSpec.hs | 26 +-- tests/Cooked/MinAdaSpec.hs | 12 +- tests/Cooked/ProposingScriptSpec.hs | 14 +- tests/Cooked/ReferenceInputsSpec.hs | 5 +- tests/Cooked/ReferenceScriptsSpec.hs | 201 +++++++++------------- tests/Cooked/WithdrawalsSpec.hs | 5 +- 13 files changed, 289 insertions(+), 310 deletions(-) diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index 919d234e7..10b43387e 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -191,46 +191,64 @@ emptyTest run = testPrettyOpts = def } -infixl 5 <== +infixl 5 `withInitDist` --- | The represents anything that can be added to a test -class AddToTest a prop b where - (<==) :: b -> Test a prop -> Test a prop +infixl 5 `withPrettyOpts` + +infixl 5 `withJournalPred` + +infixl 5 `withValueAndStatePred` + +infixl 5 `withValuePred` + +infixl 5 `withStatePred` + +infixl 5 `withPrettyAndErrorPred` + +infixl 5 `withErrorPred` -- | Appending an initial distribution to a test -instance AddToTest a prop InitialDistribution where - initDist <== test = test {testInitDist = initDist} +withInitDist :: (IsProp prop) => Test a prop -> InitialDistribution -> Test a prop +withInitDist test initDist = test {testInitDist = initDist} -- | Appending printing options to a test -instance AddToTest a prop PrettyCookedOpts where - opts <== test = test {testPrettyOpts = opts} +withPrettyOpts :: (IsProp prop) => Test a prop -> PrettyCookedOpts -> Test a prop +withPrettyOpts test opts = test {testPrettyOpts = opts} -- | Appending a predicate over the log to a test. This will be used both in -- case of success or failure of the run. -instance (IsProp prop) => AddToTest a prop ([MockChainLogEntry] -> prop) where - journalProp <== test = - test - { testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. journalProp journal, - testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. journalProp journal - } - --- | Appending a predicate over the resulting mockchain state and value, which --- will be used in case of success of the run. -instance (IsProp prop) => AddToTest a prop (a -> UtxoState -> prop) where - resultProp <== test = - test - { testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. resultProp val state - } +withJournalPred :: (IsProp prop) => Test a prop -> ([MockChainLogEntry] -> prop) -> Test a prop +withJournalPred test journalPred = + test + { testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. journalPred journal, + testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. journalPred journal + } + +-- | Appending a predicate over the return value and state, which will be used +-- in case of success of the run. +withValueAndStatePred :: (IsProp prop) => Test a prop -> (a -> UtxoState -> prop) -> Test a prop +withValueAndStatePred test resultPred = + test + { testResultProp = \opts val state journal -> testResultProp test opts val state journal .&&. resultPred val state + } + +-- | Appending a predicate over the return value, which will be used in case of +-- success of the run. +withValuePred :: (IsProp prop) => Test a prop -> (a -> prop) -> Test a prop +withValuePred test valuePred = withValueAndStatePred test $ \val _ -> valuePred val + +-- | Appending a predicate over the return state, which will be used in case of +-- success of the run. +withStatePred :: (IsProp prop) => Test a prop -> (UtxoState -> prop) -> Test a prop +withStatePred test statePred = withValueAndStatePred test $ \_ st -> statePred st -- | Appending a predicate over an error which uses the printing options, which -- will be used in case of failure of the run. -instance (IsProp prop) => AddToTest a prop (PrettyCookedOpts -> MockChainError -> prop) where - errorProp <== test = test {testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. errorProp opts err} +withPrettyAndErrorPred :: (IsProp prop) => Test a prop -> (PrettyCookedOpts -> MockChainError -> prop) -> Test a prop +withPrettyAndErrorPred test errorPred = test {testErrorProp = \opts err journal -> testErrorProp test opts err journal .&&. errorPred opts err} --- | Appending a predicate over and error, which will be used in case of --- failure of the run. -instance (IsProp prop) => AddToTest a prop (MockChainError -> prop) where - errorProp <== test = (\(_ :: PrettyCookedOpts) -> errorProp) <== test +withErrorPred :: (IsProp prop) => Test a prop -> (MockChainError -> prop) -> Test a prop +withErrorPred test errorPred = withPrettyAndErrorPred test $ \_ err -> errorPred err -- | This takes a test and transforms it into an actual test case in prop. testProp :: (IsProp prop) => Test a prop -> prop @@ -258,8 +276,8 @@ isPhase1Failure _ (MCEValidationError Ledger.Phase1 _) = testSuccess isPhase1Failure pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure, got: " ++ renderString (prettyCookedOpt pcOpts) e -- | A test that succeeds when the run result in a phase 1 failure -testFailsInPhase1 :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> prop -testFailsInPhase1 = testProp . (isPhase1Failure @prop <==) . mustFailTest +testFailsInPhase1 :: (IsProp prop, Show a) => StagedMockChain a -> prop +testFailsInPhase1 = testProp . (`withPrettyAndErrorPred` isPhase1Failure) . mustFailTest -- | A property to ensure a phase 2 failure isPhase2Failure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop @@ -267,8 +285,8 @@ isPhase2Failure _ (MCEValidationError Ledger.Phase2 _) = testSuccess isPhase2Failure pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure, got: " ++ renderString (prettyCookedOpt pcOpts) e -- | A test that succeeds when the run result in a phase 2 failure -testFailsInPhase2 :: forall prop a. (IsProp prop, Show a) => StagedMockChain a -> prop -testFailsInPhase2 = testProp . (isPhase2Failure @prop <==) . mustFailTest +testFailsInPhase2 :: (IsProp prop, Show a) => StagedMockChain a -> prop +testFailsInPhase2 = testProp . (`withPrettyAndErrorPred` isPhase2Failure) . mustFailTest -- | Same as 'isPhaseIFailure' with an added predicate on the text error isPhase1FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop @@ -276,8 +294,8 @@ isPhase1FailureWithMsg f _ (MCEValidationError Ledger.Phase1 (Ledger.CardanoLedg isPhase1FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure with constrained messages, got: " ++ renderString (prettyCookedOpt pcOpts) e -- | Same as 'testFailsInPhase1' with an added predicate on the text error -testFailsInPhase1WithMsg :: forall prop a. (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop -testFailsInPhase1WithMsg f = testProp . (isPhase1FailureWithMsg @prop f <==) . mustFailTest +testFailsInPhase1WithMsg :: (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop +testFailsInPhase1WithMsg f = testProp . (`withPrettyAndErrorPred` isPhase1FailureWithMsg f) . mustFailTest -- | Same as 'isPhase2Failure' with an added predicate over the text error isPhase2FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop @@ -285,5 +303,5 @@ isPhase2FailureWithMsg f _ (MCEValidationError Ledger.Phase2 (Ledger.ScriptFailu isPhase2FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure with constrained messages, got: " ++ renderString (prettyCookedOpt pcOpts) e -- | Same as 'testFailsInPhase2' with an added predicate over the text error -testFailsInPhase2WithMsg :: forall prop a. (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop -testFailsInPhase2WithMsg f = testProp . (isPhase2FailureWithMsg @prop f <==) . mustFailTest +testFailsInPhase2WithMsg :: (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop +testFailsInPhase2WithMsg f = testProp . (`withPrettyAndErrorPred` isPhase2FailureWithMsg f) . mustFailTest diff --git a/src/Cooked/MockChain/UtxoState.hs b/src/Cooked/MockChain/UtxoState.hs index 5100257e2..c0398ae4e 100644 --- a/src/Cooked/MockChain/UtxoState.hs +++ b/src/Cooked/MockChain/UtxoState.hs @@ -14,7 +14,7 @@ import Data.Function (on) import Data.List qualified as List import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map -import Plutus.Script.Utils.Value qualified as Script +import PlutusLedgerApi.V1.Value qualified as Api import PlutusLedgerApi.V3 qualified as Api -- | A description of who owns what in a blockchain. Owners are addresses and @@ -51,7 +51,7 @@ data UtxoPayload = UtxoPayload instance Eq UtxoPayloadSet where (UtxoPayloadSet xs) == (UtxoPayloadSet ys) = xs' == ys' where - k (UtxoPayload ref val dat rs) = (ref, Script.flattenValue val, dat, rs) + k (UtxoPayload ref val dat rs) = (ref, Api.flattenValue val, dat, rs) xs' = List.sortBy (compare `on` k) xs ys' = List.sortBy (compare `on` k) ys diff --git a/tests/Cooked/Attack/DatumHijackingSpec.hs b/tests/Cooked/Attack/DatumHijackingSpec.hs index 9f86b29b4..0f471c6d0 100644 --- a/tests/Cooked/Attack/DatumHijackingSpec.hs +++ b/tests/Cooked/Attack/DatumHijackingSpec.hs @@ -227,30 +227,25 @@ tests = @=? fst <$> skelOut x2 (1 ==) ], testCase "careful validator" $ - testFails - def - (isCekEvaluationFailure def) - ( somewhere - ( datumHijackingAttack @DHContract - ( \(ConcreteOutput v _ d _ _) -> - Script.validatorHash v == Script.validatorHash carefulValidator - && d == TxSkelOutInlineDatum SecondLock - ) - (const True) - ) - (datumHijackingTrace carefulValidator) - ), + testFailsInPhase2 $ + somewhere + ( datumHijackingAttack @DHContract + ( \(ConcreteOutput v _ d _ _) -> + Script.validatorHash v == Script.validatorHash carefulValidator + && d == TxSkelOutInlineDatum SecondLock + ) + (const True) + ) + (datumHijackingTrace carefulValidator), testCase "careless validator" $ - testSucceeds - def - ( somewhere - ( datumHijackingAttack @DHContract - ( \(ConcreteOutput v _ d _ _) -> - Script.validatorHash v == Script.validatorHash carelessValidator - && d == TxSkelOutInlineDatum SecondLock - ) - (const True) - ) - (datumHijackingTrace carelessValidator) - ) + testSucceeds $ + somewhere + ( datumHijackingAttack @DHContract + ( \(ConcreteOutput v _ d _ _) -> + Script.validatorHash v == Script.validatorHash carelessValidator + && d == TxSkelOutInlineDatum SecondLock + ) + (const True) + ) + (datumHijackingTrace carelessValidator) ] diff --git a/tests/Cooked/Attack/DupTokenSpec.hs b/tests/Cooked/Attack/DupTokenSpec.hs index 2f6866eb3..82acdddb8 100644 --- a/tests/Cooked/Attack/DupTokenSpec.hs +++ b/tests/Cooked/Attack/DupTokenSpec.hs @@ -114,17 +114,14 @@ tests = testCase "careful minting policy" $ let tName = Script.tokenName "MockToken" pol = carefulPolicy tName 1 - in testFails - def - (isCekEvaluationFailure def) - ( somewhere - (dupTokenAttack (\_ n -> n + 1) (wallet 6)) - (dupTokenTrace pol tName 1 (wallet 1)) - ), + in testFailsInPhase2 $ + somewhere + (dupTokenAttack (\_ n -> n + 1) (wallet 6)) + (dupTokenTrace pol tName 1 (wallet 1)), testCase "careless minting policy" $ let tName = Script.tokenName "MockToken" pol = carelessPolicy - in testSucceeds def $ + in testSucceeds $ somewhere (dupTokenAttack (\_ n -> n + 1) (wallet 6)) (dupTokenTrace pol tName 1 (wallet 1)), diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index a3833248a..810d14620 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -179,61 +179,71 @@ reachingMagic = do } } -type ResProp prop = TestBalancingOutcome -> prop +type ResProp = TestBalancingOutcome -> Assertion -hasFee :: (IsProp prop) => Integer -> ResProp prop +hasFee :: Integer -> ResProp hasFee fee (_, _, fee', _, _) = testBool $ fee == fee' -additionalOutsNb :: (IsProp prop) => Int -> ResProp prop +additionalOutsNb :: Int -> ResProp additionalOutsNb ao (txSkel1, txSkel2, _, _, _) = testBool $ length (txSkelOuts txSkel2) - length (txSkelOuts txSkel1) == ao -insNb :: (IsProp prop) => Int -> ResProp prop +insNb :: Int -> ResProp insNb is (_, TxSkel {..}, _, _, _) = testBool $ length txSkelIns == is -colInsNb :: (IsProp prop) => Int -> ResProp prop +colInsNb :: Int -> ResProp colInsNb cis (_, _, _, Nothing, _) = testBool $ cis == 0 colInsNb cis (_, _, _, Just (refs, _), _) = testBool $ cis == length refs -retOutsNb :: (IsProp prop) => Int -> ResProp prop +retOutsNb :: Int -> ResProp retOutsNb ros (_, _, _, _, refs) = testBool $ ros == length refs -testBalancingSucceedsWith :: String -> [ResProp Assertion] -> StagedMockChain TestBalancingOutcome -> TestTree -testBalancingSucceedsWith msg props smc = testCase msg $ testSucceedsFrom' def (\res _ -> testConjoin $ ($ res) <$> props) initialDistributionBalancing smc +testBalancingSucceedsWith :: String -> [ResProp] -> StagedMockChain TestBalancingOutcome -> TestTree +testBalancingSucceedsWith msg props run = + testCase msg $ + testProp $ + mustSucceedTest run + `withInitDist` initialDistributionBalancing + `withValuePred` \res -> testConjoin (($ res) <$> props) -failsAtBalancingWith :: (IsProp prop) => Api.Value -> Wallet -> MockChainError -> prop +failsAtBalancingWith :: Api.Value -> Wallet -> MockChainError -> Assertion failsAtBalancingWith val' wal' (MCEUnbalanceable wal val _) = testBool $ val' == val && wal' == wal failsAtBalancingWith _ _ _ = testBool False -failsAtBalancing :: (IsProp prop) => MockChainError -> prop +failsAtBalancing :: MockChainError -> Assertion failsAtBalancing MCEUnbalanceable {} = testBool True failsAtBalancing _ = testBool False -failsWithTooLittleFee :: (IsProp prop) => MockChainError -> prop +failsWithTooLittleFee :: MockChainError -> Assertion failsWithTooLittleFee (MCEValidationError Ledger.Phase1 (Ledger.CardanoLedgerValidationError text)) = testBool $ isInfixOf "FeeTooSmallUTxO" text failsWithTooLittleFee _ = testBool False -failsWithValueNotConserved :: (IsProp prop) => MockChainError -> prop +failsWithValueNotConserved :: MockChainError -> Assertion failsWithValueNotConserved (MCEValidationError Ledger.Phase1 (Ledger.CardanoLedgerValidationError text)) = testBool $ isInfixOf "ValueNotConserved" text failsWithValueNotConserved _ = testBool False -failsWithEmptyTxIns :: (IsProp prop) => MockChainError -> prop +failsWithEmptyTxIns :: MockChainError -> Assertion failsWithEmptyTxIns (MCEGenerationError (TxBodyError _ Cardano.TxBodyEmptyTxIns)) = testBool True failsWithEmptyTxIns _ = testBool False -failsAtCollateralsWith :: (IsProp prop) => Integer -> MockChainError -> prop +failsAtCollateralsWith :: Integer -> MockChainError -> Assertion failsAtCollateralsWith fee' (MCENoSuitableCollateral fee percentage val) = testBool $ fee == fee' && val == Script.lovelace (1 + (fee * percentage) `div` 100) failsAtCollateralsWith _ _ = testBool False -failsAtCollaterals :: (IsProp prop) => MockChainError -> prop +failsAtCollaterals :: MockChainError -> Assertion failsAtCollaterals MCENoSuitableCollateral {} = testBool True failsAtCollaterals _ = testBool False -failsLackOfCollateralWallet :: (IsProp prop) => MockChainError -> prop +failsLackOfCollateralWallet :: MockChainError -> Assertion failsLackOfCollateralWallet (FailWith msg) = testBool $ "Can't select collateral utxos from a balancing wallet because it does not exist." == msg failsLackOfCollateralWallet _ = testBool False testBalancingFailsWith :: (Show a) => String -> (MockChainError -> Assertion) -> StagedMockChain a -> TestTree -testBalancingFailsWith msg p smc = testCase msg $ testFailsFrom def p initialDistributionBalancing smc +testBalancingFailsWith msg p smc = + testCase msg $ + testProp $ + mustFailTest smc + `withInitDist` initialDistributionBalancing + `withErrorPred` p tests :: TestTree tests = @@ -345,7 +355,8 @@ tests = testGroup "Manual balancing with auto fee" [ testCase "Auto fee with manual balancing yields maximum fee" $ - testSucceedsFrom def initialDistributionBalancing noBalanceMaxFee + testProp $ + mustSucceedTest noBalanceMaxFee `withInitDist` initialDistributionBalancing ], testGroup "Auto balancing with auto fee" @@ -383,71 +394,67 @@ tests = id ), testCase "Auto fee are minimal: less fee will lead to strictly smaller fee than Cardano's estimate" $ - testSucceedsFrom' - def - ( \(feeBalanced, feeBalanced', feeBalancedManual, feeBalancedManual') _ -> - testBool $ - feeBalanced' <= feeBalanced && feeBalancedManual' > feeBalancedManual - ) - initialDistributionBalancing - balanceReduceFee, + testProp $ + mustSucceedTest balanceReduceFee + `withInitDist` initialDistributionBalancing + `withValuePred` \(feeBalanced, feeBalanced', feeBalancedManual, feeBalancedManual') -> + testBool $ feeBalanced' <= feeBalanced && feeBalancedManual' > feeBalancedManual, testCase "The auto-fee process can sometimes recover from a temporary balancing error..." $ - testSucceedsFrom - def - initialDistributionBalancing - ( simplePaymentToBob - 103_650_000 - 0 - 0 - 0 - False - id - ), + testProp $ + mustSucceedTest + ( simplePaymentToBob + 103_650_000 + 0 + 0 + 0 + False + id + ) + `withInitDist` initialDistributionBalancing, testCase "... but not always" $ - testFailsFrom - def - failsAtBalancing - initialDistributionBalancing - ( simplePaymentToBob - 104_000_000 - 0 - 0 - 0 - False - id - ), + testProp $ + mustFailTest + ( simplePaymentToBob + 104_000_000 + 0 + 0 + 0 + False + id + ) + `withInitDist` initialDistributionBalancing + `withErrorPred` failsAtBalancing, testCase "The auto-fee process can recover from a temporary collateral error..." $ - testSucceedsFrom - def - initialDistributionBalancing - ( testingBalancingTemplate - (Script.ada 142) - mempty - emptySearch - emptySearch - (aliceNAdaUtxos 2) - True - id - ), + testProp $ + mustSucceedTest + ( testingBalancingTemplate + (Script.ada 142) + mempty + emptySearch + emptySearch + (aliceNAdaUtxos 2) + True + id + ) + `withInitDist` initialDistributionBalancing, testCase "... but not always" $ - testFailsFrom - def - failsAtCollaterals - initialDistributionBalancing - ( testingBalancingTemplate - (Script.ada 142) - mempty - (utxosAtSearch alice) - emptySearch - (aliceNAdaUtxos 1) - True - id - ), + testProp $ + mustFailTest + ( testingBalancingTemplate + (Script.ada 142) + mempty + (utxosAtSearch alice) + emptySearch + (aliceNAdaUtxos 1) + True + id + ) + `withInitDist` initialDistributionBalancing + `withErrorPred` failsAtCollaterals, testCase "Reaching magical spot with the exact balance during auto fee computation" $ - testSucceedsFrom - def - initialDistributionBalancing - reachingMagic + testProp $ + mustSucceedTest reachingMagic + `withInitDist` initialDistributionBalancing ], testGroup "Auto balancing with manual fee" diff --git a/tests/Cooked/BasicUsageSpec.hs b/tests/Cooked/BasicUsageSpec.hs index 7a5a1a653..f9c1cfcc6 100644 --- a/tests/Cooked/BasicUsageSpec.hs +++ b/tests/Cooked/BasicUsageSpec.hs @@ -67,9 +67,9 @@ tests :: TestTree tests = testGroup "Basic usage" - [ testCase "Payment from alice to bob, with auto-balancing" $ testSucceedsFrom def def (pkToPk alice bob 10), - testCase "Circular payments of 10 Script.ada between alice bob and carrie" $ testSucceedsFrom def def multiplePksToPks, - testCase "Minting quick tokens" $ testSucceedsFrom def def mintingQuickValue, - testCase "Paying to the always true validator" $ testSucceedsFrom def def payToAlwaysTrueValidator, - testCase "Consuming the always true validator" $ testSucceedsFrom def def consumeAlwaysTrueValidator + [ testCase "Payment from alice to bob, with auto-balancing" $ testSucceeds $ pkToPk alice bob 10, + testCase "Circular payments of 10 Script.ada between alice bob and carrie" $ testSucceeds multiplePksToPks, + testCase "Minting quick tokens" $ testSucceeds mintingQuickValue, + testCase "Paying to the always true validator" $ testSucceeds payToAlwaysTrueValidator, + testCase "Consuming the always true validator" $ testSucceeds consumeAlwaysTrueValidator ] diff --git a/tests/Cooked/InitialDistributionSpec.hs b/tests/Cooked/InitialDistributionSpec.hs index d586035a2..f354ce280 100644 --- a/tests/Cooked/InitialDistributionSpec.hs +++ b/tests/Cooked/InitialDistributionSpec.hs @@ -2,7 +2,6 @@ module Cooked.InitialDistributionSpec where import Control.Monad import Cooked -import Data.Default import Data.Map qualified as Map import Data.Maybe (catMaybes) import Plutus.Script.Utils.Value qualified as Script @@ -52,7 +51,13 @@ tests = testGroup "Initial distributions" [ testCase "Reading datums placed in the initial distribution, inlined or hashed" $ - testSucceedsFrom' def (\results _ -> testBool $ results == [10, 10]) initialDistributionWithDatum getValueFromInitialDatum, + testProp $ + mustSucceedTest getValueFromInitialDatum + `withInitDist` initialDistributionWithDatum + `withValuePred` testBool + . (== [10, 10]), testCase "Spending a script placed as a reference script in the initial distribution" $ - testSucceedsFrom def initialDistributionWithReferenceScript spendReferenceAlwaysTrueValidator + testProp $ + mustSucceedTest spendReferenceAlwaysTrueValidator + `withInitDist` initialDistributionWithReferenceScript ] diff --git a/tests/Cooked/InlineDatumsSpec.hs b/tests/Cooked/InlineDatumsSpec.hs index 0a82d35dc..85b4da1d1 100644 --- a/tests/Cooked/InlineDatumsSpec.hs +++ b/tests/Cooked/InlineDatumsSpec.hs @@ -200,19 +200,19 @@ tests = [ testGroup "validator expects an inline datum..." [ testCase "...and gets an inline datum, expecting success" $ - testSucceeds def $ + testSucceeds $ spendOutputTestTrace True (inputDatumValidator True), testCase "...and gets a datum hash, expecting script failure" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ spendOutputTestTrace False (inputDatumValidator True) ], testGroup "validator expects a datum hash..." [ testCase "...and gets an inline datum, expecting script failure" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ spendOutputTestTrace True (inputDatumValidator False), testCase "...and gets a datum hash, expecting success" $ - testSucceeds def $ + testSucceeds $ spendOutputTestTrace False (inputDatumValidator False) ] ], @@ -221,37 +221,37 @@ tests = [ testGroup "validator expects a regular datum..." [ testCase "...and gets a regular datum, expecting success" $ - testSucceeds def $ + testSucceeds $ continuingOutputTestTrace Datum (outputDatumValidator Datum), testCase "...and gets an inline datum, expecting script failure" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ continuingOutputTestTrace Inline (outputDatumValidator Datum), testCase "...and gets a datum hash, expecting script failure" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ continuingOutputTestTrace OnlyHash (outputDatumValidator Datum) ], testGroup "validator expects an inline datum..." [ testCase "...and gets a regular datum, expecting script failure" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ continuingOutputTestTrace Datum (outputDatumValidator Inline), testCase "...and gets an inline datum, expecting success" $ - testSucceeds def $ + testSucceeds $ continuingOutputTestTrace Inline (outputDatumValidator Inline), testCase "...and gets a datum hash, expecting script failure" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ continuingOutputTestTrace OnlyHash (outputDatumValidator Inline) ], testGroup "validator expects a datum hash..." [ testCase "...and gets a regular datum, expecting script failure" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ continuingOutputTestTrace Datum (outputDatumValidator OnlyHash), testCase "...and gets an inline datum, expecting script failure" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ continuingOutputTestTrace Inline (outputDatumValidator OnlyHash), testCase "...and gets a datum hash, expecting success" $ - testSucceeds def $ + testSucceeds $ continuingOutputTestTrace OnlyHash (outputDatumValidator OnlyHash) ] ] diff --git a/tests/Cooked/MinAdaSpec.hs b/tests/Cooked/MinAdaSpec.hs index a24b54324..6a38c2fb9 100644 --- a/tests/Cooked/MinAdaSpec.hs +++ b/tests/Cooked/MinAdaSpec.hs @@ -3,7 +3,6 @@ module Cooked.MinAdaSpec where import Control.Monad import Cooked import Data.Default -import Ledger.Index qualified as Ledger import Optics.Core ((^.)) import Plutus.Script.Utils.Ada qualified as Script import Plutus.Script.Utils.Value qualified as Script @@ -36,13 +35,6 @@ tests :: TestTree tests = testGroup "MinAda auto adjustment of transaction outputs" - [ testCase "adjusted transaction passes" $ testSucceeds def paymentWithMinAda, - testCase "adjusted transaction contains minimal amount" - $ testFails - def - ( \case - MCEValidationError Ledger.Phase1 _ -> testSuccess - _ -> testFailure - ) - $ paymentWithMinAda >>= paymentWithoutMinAda . (+ (-1)) + [ testCase "adjusted transaction passes" $ testSucceeds paymentWithMinAda, + testCase "adjusted transaction contains minimal amount" $ testFailsInPhase1 $ paymentWithMinAda >>= paymentWithoutMinAda . (+ (-1)) ] diff --git a/tests/Cooked/ProposingScriptSpec.hs b/tests/Cooked/ProposingScriptSpec.hs index 63d118aa6..f2b53b398 100644 --- a/tests/Cooked/ProposingScriptSpec.hs +++ b/tests/Cooked/ProposingScriptSpec.hs @@ -2,9 +2,7 @@ module Cooked.ProposingScriptSpec where import Control.Monad import Cooked -import Data.Default import Data.Map qualified as Map -import Ledger.Index qualified as Ledger import Plutus.Script.Utils.Scripts qualified as Script import Plutus.Script.Utils.Value qualified as Script import PlutusLedgerApi.V3 qualified as Api @@ -60,21 +58,21 @@ tests = testGroup "Proposing scripts" [ testCase "The always True proposing script succeeds" $ - testSucceeds def $ + testSucceeds $ testProposingScript alwaysTrueProposingValidator (TxGovActionTreasuryWithdrawals Map.empty), testCase "The always True proposing script suceeds as a reference script" $ - testSucceeds def $ + testSucceeds $ testProposingRefScript alwaysTrueProposingValidator (TxGovActionTreasuryWithdrawals Map.empty), testCase "The always False proposing script fails" $ - testFails def (isCekEvaluationFailure def) $ + testFailsInPhase2 $ testProposingScript alwaysFalseProposingValidator (TxGovActionTreasuryWithdrawals Map.empty), testCase "A more advanced proposing script can succeed" $ - testSucceeds def $ + testSucceeds $ testProposingScript checkProposingScript (TxGovActionParameterChange [FeePerByte 100]), testCase "A more advanced proposing script can succeed as a reference script" $ - testSucceeds def $ + testSucceeds $ testProposingRefScript checkProposingScript (TxGovActionParameterChange [FeePerByte 100]), testCase "Proposing scripts are restricted to parameter changes or treasury withdrawals" $ - testFails def (\case (MCEValidationError Ledger.Phase1 _) -> testBool True; _ -> testBool False) $ + testFailsInPhase1 $ testProposingScript alwaysFalseProposingValidator TxGovActionNoConfidence ] diff --git a/tests/Cooked/ReferenceInputsSpec.hs b/tests/Cooked/ReferenceInputsSpec.hs index cad6e437e..f4a8c0a6f 100644 --- a/tests/Cooked/ReferenceInputsSpec.hs +++ b/tests/Cooked/ReferenceInputsSpec.hs @@ -2,7 +2,6 @@ module Cooked.ReferenceInputsSpec where import Control.Monad import Cooked -import Data.Default import Data.Map qualified as Map import Data.Set qualified as Set import Plutus.Script.Utils.Typed qualified as Script @@ -147,6 +146,6 @@ tests :: Tasty.TestTree tests = Tasty.testGroup "Reference inputs" - [ Tasty.testCase "We can reference an input that can't be spent" (testSucceeds def trace1), - Tasty.testCase "We can decode the datum hash from a reference input" (testSucceeds def trace2) + [ Tasty.testCase "We can reference an input that can't be spent" $ testSucceeds trace1, + Tasty.testCase "We can decode the datum hash from a reference input" $ testSucceeds trace2 ] diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index 254f1ecc0..633dc18d7 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -7,7 +7,6 @@ import Data.Default import Data.Map qualified as Map import Data.Maybe import Data.Set qualified as Set -import Ledger.Index qualified as Ledger import Optics.Core import Plutus.Script.Utils.Ada qualified as Script import Plutus.Script.Utils.Scripts qualified as Script @@ -144,113 +143,93 @@ tests = [ testGroup "putting reference scripts on chain and retrieving them" $ let theRefScript = alwaysFalseValidator theRefScriptHash = toScriptHash theRefScript - in [ testCase "on a public key output" - $ testSucceedsFrom' - def - ( \mScriptHash _ -> - testCounterexample "the script hash on the retrieved output is wrong" $ - Just theRefScriptHash .==. mScriptHash - ) - def - $ putRefScriptOnWalletOutput (wallet 3) theRefScript - >>= retrieveRefScriptHash, - testCase "on a script output" - $ testSucceedsFrom' - def - ( \mScriptHash _ -> - testCounterexample "the script hash on the retrieved output is wrong" $ - Just theRefScriptHash .==. mScriptHash - ) - def - $ putRefScriptOnScriptOutput alwaysTrueValidator theRefScript - >>= retrieveRefScriptHash, - testCase "retrieving the complete script from its hash" - $ testSucceedsFrom' - def - ( \mOut _ -> case mOut of - Nothing -> testFailure - Just out -> Just (Script.vValidatorScript theRefScript) .==. out ^. outputReferenceScriptL - ) - def - $ putRefScriptOnWalletOutput (wallet 3) theRefScript - >>= fmap fromJust . txOutByRef - >>= resolveReferenceScript + in [ testCase "on a public key output" $ + testProp $ + mustSucceedTest + ( putRefScriptOnWalletOutput (wallet 3) theRefScript + >>= retrieveRefScriptHash + ) + `withValuePred` testCounterexample "the script hash on the retrieved output is wrong" + . (Just theRefScriptHash .==.), + testCase "on a script output" $ + testProp $ + mustSucceedTest + ( putRefScriptOnScriptOutput alwaysTrueValidator theRefScript + >>= retrieveRefScriptHash + ) + `withValuePred` testCounterexample "the script hash on the retrieved output is wrong" + . (Just theRefScriptHash .==.), + testCase "retrieving the complete script from its hash" $ + testProp $ + mustSucceedTest + ( putRefScriptOnWalletOutput (wallet 3) theRefScript + >>= fmap fromJust . txOutByRef + >>= resolveReferenceScript + ) + `withValuePred` maybe testFailure ((Just (Script.vValidatorScript theRefScript) .==.) . (^. outputReferenceScriptL)) ], testGroup "checking the presence of reference scripts on the TxInfo" [ testCase "fail if wrong reference script" - $ testFails - def - ( isCekEvaluationFailureWithMsg - def - (== "there is no reference input with the correct script hash") - ) + $ testFailsInPhase2WithMsg + (== "there is no reference input with the correct script hash") $ putRefScriptOnWalletOutput (wallet 3) alwaysFalseValidator >>= checkReferenceScriptOnOref (toScriptHash alwaysTrueValidator), testCase "succeed if correct reference script" $ - testSucceeds def $ + testSucceeds $ putRefScriptOnWalletOutput (wallet 3) alwaysTrueValidator >>= checkReferenceScriptOnOref (toScriptHash alwaysTrueValidator) ], testGroup "using reference scripts" - [ testCase "fail from transaction generation for missing reference scripts" - $ testFailsFrom - def - ( \case + [ testCase "fail from transaction generation for missing reference scripts" $ + testProp $ + mustFailTest + ( do + (consumedOref, _) : _ <- + runUtxoSearch $ + utxosAtSearch (wallet 1) + `filterWithPred` ((`Script.geq` Script.lovelaceValueOf 42_000_000) . outputValue) + oref : _ <- + validateTxSkel' + txSkelTemplate + { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], + txSkelIns = Map.singleton consumedOref txSkelEmptyRedeemer, + txSkelSigners = [wallet 1] + } + void $ + validateTxSkel + txSkelTemplate + { txSkelIns = Map.singleton oref (txSkelSomeRedeemerAndReferenceScript consumedOref ()), + txSkelSigners = [wallet 1] + } + ) + `withErrorPred` \case MCEUnknownOutRefError "lookupUtxos: unknown TxOutRef" _ -> testSuccess - _ -> testFailure - ) - def - $ do - (consumedOref, _) : _ <- - runUtxoSearch $ - utxosAtSearch (wallet 1) - `filterWithPred` ((`Script.geq` Script.lovelaceValueOf 42_000_000) . outputValue) - oref : _ <- - validateTxSkel' - txSkelTemplate - { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], - txSkelIns = Map.singleton consumedOref txSkelEmptyRedeemer, - txSkelSigners = [wallet 1] - } - void $ - validateTxSkel - txSkelTemplate - { txSkelIns = Map.singleton oref (txSkelSomeRedeemerAndReferenceScript consumedOref ()), - txSkelSigners = [wallet 1] - }, - testCase "fail from transaction generation for mismatching reference scripts" - $ testFailsFrom - def - ( \case + _ -> testFailure, + testCase "fail from transaction generation for mismatching reference scripts" $ + testProp $ + mustFailTest + ( do + scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysFalseValidator + oref : _ <- + validateTxSkel' + txSkelTemplate + { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], + txSkelSigners = [wallet 1] + } + void $ + validateTxSkel + txSkelTemplate + { txSkelIns = Map.singleton oref (txSkelSomeRedeemerAndReferenceScript scriptOref ()), + txSkelSigners = [wallet 1] + } + ) + `withErrorPred` \case MCEGenerationError err -> err .==. GenerateTxErrorGeneral "toPlutusScriptOrReferenceInput: Wrong reference script hash." - _ -> testFailure - ) - def - $ do - scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysFalseValidator - oref : _ <- - validateTxSkel' - txSkelTemplate - { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], - txSkelSigners = [wallet 1] - } - void $ - validateTxSkel - txSkelTemplate - { txSkelIns = Map.singleton oref (txSkelSomeRedeemerAndReferenceScript scriptOref ()), - txSkelSigners = [wallet 1] - }, - testCase "phase 1 - fail if using a reference script with 'txSkelSomeRedeemer'" - $ testFailsFrom - def - ( \case - MCEValidationError Ledger.Phase1 _ -> testSuccess - _ -> testFailure - ) - def - $ do + _ -> testFailure, + testCase "phase 1 - fail if using a reference script with 'txSkelSomeRedeemer'" $ + testFailsInPhase1 $ do scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysTrueValidator oref : _ <- validateTxSkel' @@ -267,38 +246,28 @@ tests = }, testCase "fail if reference script's requirement is violated" - $ testFailsFrom - (def {pcOptPrintTxHashes = True}) - ( isCekEvaluationFailureWithMsg - def - (== "the required signer is missing") - ) - def + $ testFailsInPhase2WithMsg (== "the required signer is missing") $ useReferenceScript (wallet 1) (requireSignerValidator $ walletPKHash $ wallet 2), testCase "succeed if reference script's requirement is met" $ - testSucceeds def $ + testSucceeds $ useReferenceScript (wallet 1) (requireSignerValidator $ walletPKHash $ wallet 1) ], testGroup "referencing minting policies" [ testCase "succeed if given a reference minting policy" $ - testSucceeds def $ + testSucceeds $ referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0, - testCase "fail if given the wrong reference minting policy" - $ testFails - def - ( \case + testCase "fail if given the wrong reference minting policy" $ + testProp $ + mustFailTest (referenceMint permanentCurrencyPolicyV3 quickCurrencyPolicyV3 0) + `withErrorPred` \case MCEGenerationError (GenerateTxErrorGeneral err) -> err .==. "toPlutusScriptOrReferenceInput: Wrong reference script hash." - _ -> testFailure - ) - $ referenceMint permanentCurrencyPolicyV3 quickCurrencyPolicyV3 0, - testCase "fail if referencing the wrong utxo" - $ testFails - def - ( \case + _ -> testFailure, + testCase "fail if referencing the wrong utxo" $ + testProp $ + mustFailTest (referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 1) + `withErrorPred` \case MCEGenerationError (GenerateTxErrorGeneral err) -> err .==. "toPlutusScriptOrReferenceInput: Can't resolve reference script utxo." _ -> testFailure - ) - $ referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 1 ] ] diff --git a/tests/Cooked/WithdrawalsSpec.hs b/tests/Cooked/WithdrawalsSpec.hs index 8f81cb5ac..8d5ed9345 100644 --- a/tests/Cooked/WithdrawalsSpec.hs +++ b/tests/Cooked/WithdrawalsSpec.hs @@ -2,7 +2,6 @@ module Cooked.WithdrawalsSpec where import Control.Monad import Cooked -import Data.Default import Plutus.Script.Utils.Ada qualified as Script import Plutus.Script.Utils.Scripts qualified as Script import PlutusLedgerApi.V3 qualified as Api @@ -47,9 +46,9 @@ tests = testGroup "Withdrawing scripts" [ testCase "We can use a withdrawing script" $ - testSucceeds def $ + testSucceeds $ testWithdrawingScript 2 2, testCase "But the script might fail" $ - testFailsFrom' def (isCekEvaluationFailure def) def $ + testFailsInPhase2 $ testWithdrawingScript 2 1 ] From a53419a629d356d3fc61ac87dc32877687f5d042 Mon Sep 17 00:00:00 2001 From: mmontin Date: Tue, 10 Sep 2024 12:04:03 +0200 Subject: [PATCH 51/71] better name --- src/Cooked/MockChain/Testing.hs | 16 ++++++++-------- tests/Cooked/BalancingSpec.hs | 18 +++++++++--------- tests/Cooked/InitialDistributionSpec.hs | 4 ++-- tests/Cooked/ReferenceScriptsSpec.hs | 14 +++++++------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/Cooked/MockChain/Testing.hs b/src/Cooked/MockChain/Testing.hs index 10b43387e..ab9636244 100644 --- a/src/Cooked/MockChain/Testing.hs +++ b/src/Cooked/MockChain/Testing.hs @@ -251,8 +251,8 @@ withErrorPred :: (IsProp prop) => Test a prop -> (MockChainError -> prop) -> Tes withErrorPred test errorPred = withPrettyAndErrorPred test $ \_ err -> errorPred err -- | This takes a test and transforms it into an actual test case in prop. -testProp :: (IsProp prop) => Test a prop -> prop -testProp Test {..} = +testToProp :: (IsProp prop) => Test a prop -> prop +testToProp Test {..} = let innerProp (res, mcLog) = case res of Left err -> testErrorProp testPrettyOpts err mcLog @@ -264,11 +264,11 @@ testProp Test {..} = -- | Ensure that all results produced by the staged mockchain /succeed/, -- starting from the default initial distribution testSucceeds :: (IsProp prop) => StagedMockChain a -> prop -testSucceeds = testProp . mustSucceedTest +testSucceeds = testToProp . mustSucceedTest -- | Ensure that all results produced by the staged mockchain /fail/ testFails :: (IsProp prop, Show a) => StagedMockChain a -> prop -testFails = testProp . mustFailTest +testFails = testToProp . mustFailTest -- | A property to ensure a phase 1 failure isPhase1Failure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop @@ -277,7 +277,7 @@ isPhase1Failure pcOpts e = testFailureMsg $ "Expected phase 1 evaluation failure -- | A test that succeeds when the run result in a phase 1 failure testFailsInPhase1 :: (IsProp prop, Show a) => StagedMockChain a -> prop -testFailsInPhase1 = testProp . (`withPrettyAndErrorPred` isPhase1Failure) . mustFailTest +testFailsInPhase1 = testToProp . (`withPrettyAndErrorPred` isPhase1Failure) . mustFailTest -- | A property to ensure a phase 2 failure isPhase2Failure :: (IsProp prop) => PrettyCookedOpts -> MockChainError -> prop @@ -286,7 +286,7 @@ isPhase2Failure pcOpts e = testFailureMsg $ "Expected phase 2 evaluation failure -- | A test that succeeds when the run result in a phase 2 failure testFailsInPhase2 :: (IsProp prop, Show a) => StagedMockChain a -> prop -testFailsInPhase2 = testProp . (`withPrettyAndErrorPred` isPhase2Failure) . mustFailTest +testFailsInPhase2 = testToProp . (`withPrettyAndErrorPred` isPhase2Failure) . mustFailTest -- | Same as 'isPhaseIFailure' with an added predicate on the text error isPhase1FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop @@ -295,7 +295,7 @@ isPhase1FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 1 evaluatio -- | Same as 'testFailsInPhase1' with an added predicate on the text error testFailsInPhase1WithMsg :: (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop -testFailsInPhase1WithMsg f = testProp . (`withPrettyAndErrorPred` isPhase1FailureWithMsg f) . mustFailTest +testFailsInPhase1WithMsg f = testToProp . (`withPrettyAndErrorPred` isPhase1FailureWithMsg f) . mustFailTest -- | Same as 'isPhase2Failure' with an added predicate over the text error isPhase2FailureWithMsg :: (IsProp prop) => (String -> Bool) -> PrettyCookedOpts -> MockChainError -> prop @@ -304,4 +304,4 @@ isPhase2FailureWithMsg _ pcOpts e = testFailureMsg $ "Expected phase 2 evaluatio -- | Same as 'testFailsInPhase2' with an added predicate over the text error testFailsInPhase2WithMsg :: (IsProp prop, Show a) => (String -> Bool) -> StagedMockChain a -> prop -testFailsInPhase2WithMsg f = testProp . (`withPrettyAndErrorPred` isPhase2FailureWithMsg f) . mustFailTest +testFailsInPhase2WithMsg f = testToProp . (`withPrettyAndErrorPred` isPhase2FailureWithMsg f) . mustFailTest diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index 810d14620..496e55f44 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -200,7 +200,7 @@ retOutsNb ros (_, _, _, _, refs) = testBool $ ros == length refs testBalancingSucceedsWith :: String -> [ResProp] -> StagedMockChain TestBalancingOutcome -> TestTree testBalancingSucceedsWith msg props run = testCase msg $ - testProp $ + testToProp $ mustSucceedTest run `withInitDist` initialDistributionBalancing `withValuePred` \res -> testConjoin (($ res) <$> props) @@ -240,7 +240,7 @@ failsLackOfCollateralWallet _ = testBool False testBalancingFailsWith :: (Show a) => String -> (MockChainError -> Assertion) -> StagedMockChain a -> TestTree testBalancingFailsWith msg p smc = testCase msg $ - testProp $ + testToProp $ mustFailTest smc `withInitDist` initialDistributionBalancing `withErrorPred` p @@ -355,7 +355,7 @@ tests = testGroup "Manual balancing with auto fee" [ testCase "Auto fee with manual balancing yields maximum fee" $ - testProp $ + testToProp $ mustSucceedTest noBalanceMaxFee `withInitDist` initialDistributionBalancing ], testGroup @@ -394,13 +394,13 @@ tests = id ), testCase "Auto fee are minimal: less fee will lead to strictly smaller fee than Cardano's estimate" $ - testProp $ + testToProp $ mustSucceedTest balanceReduceFee `withInitDist` initialDistributionBalancing `withValuePred` \(feeBalanced, feeBalanced', feeBalancedManual, feeBalancedManual') -> testBool $ feeBalanced' <= feeBalanced && feeBalancedManual' > feeBalancedManual, testCase "The auto-fee process can sometimes recover from a temporary balancing error..." $ - testProp $ + testToProp $ mustSucceedTest ( simplePaymentToBob 103_650_000 @@ -412,7 +412,7 @@ tests = ) `withInitDist` initialDistributionBalancing, testCase "... but not always" $ - testProp $ + testToProp $ mustFailTest ( simplePaymentToBob 104_000_000 @@ -425,7 +425,7 @@ tests = `withInitDist` initialDistributionBalancing `withErrorPred` failsAtBalancing, testCase "The auto-fee process can recover from a temporary collateral error..." $ - testProp $ + testToProp $ mustSucceedTest ( testingBalancingTemplate (Script.ada 142) @@ -438,7 +438,7 @@ tests = ) `withInitDist` initialDistributionBalancing, testCase "... but not always" $ - testProp $ + testToProp $ mustFailTest ( testingBalancingTemplate (Script.ada 142) @@ -452,7 +452,7 @@ tests = `withInitDist` initialDistributionBalancing `withErrorPred` failsAtCollaterals, testCase "Reaching magical spot with the exact balance during auto fee computation" $ - testProp $ + testToProp $ mustSucceedTest reachingMagic `withInitDist` initialDistributionBalancing ], diff --git a/tests/Cooked/InitialDistributionSpec.hs b/tests/Cooked/InitialDistributionSpec.hs index f354ce280..87b6a5150 100644 --- a/tests/Cooked/InitialDistributionSpec.hs +++ b/tests/Cooked/InitialDistributionSpec.hs @@ -51,13 +51,13 @@ tests = testGroup "Initial distributions" [ testCase "Reading datums placed in the initial distribution, inlined or hashed" $ - testProp $ + testToProp $ mustSucceedTest getValueFromInitialDatum `withInitDist` initialDistributionWithDatum `withValuePred` testBool . (== [10, 10]), testCase "Spending a script placed as a reference script in the initial distribution" $ - testProp $ + testToProp $ mustSucceedTest spendReferenceAlwaysTrueValidator `withInitDist` initialDistributionWithReferenceScript ] diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index 633dc18d7..178324c40 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -144,7 +144,7 @@ tests = let theRefScript = alwaysFalseValidator theRefScriptHash = toScriptHash theRefScript in [ testCase "on a public key output" $ - testProp $ + testToProp $ mustSucceedTest ( putRefScriptOnWalletOutput (wallet 3) theRefScript >>= retrieveRefScriptHash @@ -152,7 +152,7 @@ tests = `withValuePred` testCounterexample "the script hash on the retrieved output is wrong" . (Just theRefScriptHash .==.), testCase "on a script output" $ - testProp $ + testToProp $ mustSucceedTest ( putRefScriptOnScriptOutput alwaysTrueValidator theRefScript >>= retrieveRefScriptHash @@ -160,7 +160,7 @@ tests = `withValuePred` testCounterexample "the script hash on the retrieved output is wrong" . (Just theRefScriptHash .==.), testCase "retrieving the complete script from its hash" $ - testProp $ + testToProp $ mustSucceedTest ( putRefScriptOnWalletOutput (wallet 3) theRefScript >>= fmap fromJust . txOutByRef @@ -183,7 +183,7 @@ tests = testGroup "using reference scripts" [ testCase "fail from transaction generation for missing reference scripts" $ - testProp $ + testToProp $ mustFailTest ( do (consumedOref, _) : _ <- @@ -208,7 +208,7 @@ tests = MCEUnknownOutRefError "lookupUtxos: unknown TxOutRef" _ -> testSuccess _ -> testFailure, testCase "fail from transaction generation for mismatching reference scripts" $ - testProp $ + testToProp $ mustFailTest ( do scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysFalseValidator @@ -258,13 +258,13 @@ tests = testSucceeds $ referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0, testCase "fail if given the wrong reference minting policy" $ - testProp $ + testToProp $ mustFailTest (referenceMint permanentCurrencyPolicyV3 quickCurrencyPolicyV3 0) `withErrorPred` \case MCEGenerationError (GenerateTxErrorGeneral err) -> err .==. "toPlutusScriptOrReferenceInput: Wrong reference script hash." _ -> testFailure, testCase "fail if referencing the wrong utxo" $ - testProp $ + testToProp $ mustFailTest (referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 1) `withErrorPred` \case MCEGenerationError (GenerateTxErrorGeneral err) -> err .==. "toPlutusScriptOrReferenceInput: Can't resolve reference script utxo." From 943d937c415c0b7ac3e224debf8aa432e13b279b Mon Sep 17 00:00:00 2001 From: mmontin Date: Tue, 10 Sep 2024 14:14:56 +0200 Subject: [PATCH 52/71] post review changes --- CHANGELOG.md | 6 +++--- src/Cooked/MockChain/BlockChain.hs | 19 ++++++++----------- src/Cooked/MockChain/Direct.hs | 2 +- src/Cooked/MockChain/GenerateTx/Body.hs | 17 ++++++++++++++++- src/Cooked/Skeleton.hs | 9 +++++---- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed8d6b73..d45887598 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,9 +17,9 @@ present but somehow disapeared. - It is now possible to reference an output which has a hashed datum. - `txSkelHashedData` the gives all the datum hashes in inputs and reference inputs. -- Partial support for withdrawals in txSkels. The rewarding scripts will be ran +- Partial support for withdrawals in txSkels. The rewarding scripts will be run and assets will be transferred. However, these withdrawals are not properly - constrainted yet. + constrained yet. - PrettyCooked option `pcOptPrintLog`, which is a boolean, to turn on or off the log display in the pretty printer. The default value is `True`. @@ -40,7 +40,7 @@ - `mkProposingScript` changed to `mkScript` - `withDatumHashed` changed to `withUnresolvedDatumHash` - `paysScriptDatumHashed` changed to `paysScriptUnresolvedDatumHash` -- `txSkelInputData` changed to `txSkelConsumedData` +- `txSkelInputData` changed to `txSkelInputDataAsHashes` - Pretty printing of hashed datum now includes the hash (and not only the resolved datum). - Dependency to cardano-api bumped to 8.46. diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 2f291b901..5c0b4324e 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -43,7 +43,7 @@ module Cooked.MockChain.BlockChain txSkelInputValidators, txSkelInputValue, txSkelHashedData, - txSkelConsumedData, + txSkelInputDataAsHashes, lookupUtxos, validateTxSkel', validateTxSkel_, @@ -351,9 +351,6 @@ txSkelInputValue = (foldMap Api.txOutValue <$>) . txSkelInputUtxos -- or references, which will be needed by the transaction body. txSkelHashedData :: (MonadBlockChainBalancing m) => TxSkel -> m (Map Api.DatumHash Api.Datum) txSkelHashedData skel = do - let outputToDatumHashM output = case output ^. outputDatumL of - Api.OutputDatumHash dHash -> Just dHash - _ -> Nothing (Map.elems -> inputTxOuts) <- txSkelInputUtxos skel (Map.elems -> refInputTxOuts) <- txSkelReferenceInputUtxos skel foldM @@ -364,17 +361,17 @@ txSkelHashedData skel = do (datumFromHash dHash) ) Map.empty - (mapMaybe outputToDatumHashM $ inputTxOuts <> refInputTxOuts) + (mapMaybe (fmap (^. outputDatumL) . isOutputWithDatumHash) $ inputTxOuts <> refInputTxOuts) --- | Looks us the data on UTxOs the transaction consumes. This corresponds to --- the keys of what should be removed from the stored datums in our mockchain. --- There can be duplicates, which is expected. -txSkelConsumedData :: (MonadBlockChainBalancing m) => TxSkel -> m [Api.DatumHash] -txSkelConsumedData skel = do +-- | Looks up the data on UTxOs the transaction consumes and returns their +-- hashes. This corresponds to the keys of what should be removed from the +-- stored datums in our mockchain. There can be duplicates, which is expected. +txSkelInputDataAsHashes :: (MonadBlockChainBalancing m) => TxSkel -> m [Api.DatumHash] +txSkelInputDataAsHashes skel = do let outputToDatumHashM output = case output ^. outputDatumL of Api.OutputDatumHash dHash -> maybeErrM - (MCEUnknownDatum "txSkelConsumedData: Transaction input with unknown datum hash" dHash) + (MCEUnknownDatum "txSkelInputDataAsHashes: Transaction input with unknown datum hash" dHash) (Just . const dHash) (datumFromHash dHash) Api.OutputDatum datum -> return $ Just $ Script.datumHash datum diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index 3047a2396..43dcdf2b5 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -158,7 +158,7 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where -- datums, validators and various kinds of inputs. This idea is to provide a -- rich-enough context for the transaction generation to succeed. hashedData <- txSkelHashedData skel - insData <- txSkelConsumedData skel + insData <- txSkelInputDataAsHashes skel insValidators <- txSkelInputValidators skel insMap <- txSkelInputUtxos skel refInsMap <- txSkelReferenceInputUtxos skel diff --git a/src/Cooked/MockChain/GenerateTx/Body.hs b/src/Cooked/MockChain/GenerateTx/Body.hs index 8e84ffe4b..38655246b 100644 --- a/src/Cooked/MockChain/GenerateTx/Body.hs +++ b/src/Cooked/MockChain/GenerateTx/Body.hs @@ -122,25 +122,40 @@ txSkelToCardanoTx txSkel = do -- definition of "txBody'" aims at doing just that. In the process, we have to -- reconstruct the body with the new data and the associated hash. Hopefully, -- in the future, cardano-api provides a way to add those data in the body - -- directly without requiring this methods, which somewhat feels like a hack. + -- directly without requiring this method, which somewhat feels like a hack. + + -- We retrieve the data available in the context mData <- asks managedData + -- We retrieve the outputs available in the context mTxOut <- asks managedTxOuts + -- We attempt to resolve the reference inputs used by the skeleton refIns <- forM (txSkelReferenceTxOutRefs txSkel) $ \oRef -> throwOnLookup ("txSkelToCardanoTx: Unable to resolve TxOutRef " <> show oRef) oRef mTxOut + -- We collect the datum hashes present at these outputs let datumHashes = [hash | (Api.TxOut _ _ (Api.OutputDatumHash hash) _) <- refIns] + -- We resolve those datum hashes from the context additionalData <- forM datumHashes $ \dHash -> throwOnLookup ("txSkelToCardanoTx: Unable to resolve datum hash " <> show dHash) dHash mData + -- We compute the map from datum hash to datum of these additional required data let additionalDataMap = Map.fromList [(Cardano.hashData dat, dat) | Api.Datum (Cardano.Data . Api.toData -> dat) <- additionalData] + -- We retrieve a needed parameter to process difference plutus languages toLangDepViewParam <- asks (Conway.getLanguageView . Cardano.unLedgerProtocolParameters . Emulator.ledgerProtocolParameters . params) + -- We convert our data map into a 'TxDats' let txDats' = Alonzo.TxDats additionalDataMap + -- We compute the new era, datums and redeemers based on the current dats + -- in the body and the additional data to include in the body. (era, datums, redeemers) = case dats of Cardano.TxBodyNoScriptData -> (Cardano.AlonzoEraOnwardsConway, txDats', Alonzo.Redeemers Map.empty) Cardano.TxBodyScriptData era' txDats reds -> (era', txDats <> txDats', reds) + -- We collect the various witnesses in the body witnesses = Cardano.collectTxBodyScriptWitnesses Cardano.ShelleyBasedEraConway txBodyContent + -- We collect their associated languages languages = [toCardanoLanguage v | (_, Cardano.AnyScriptWitness (Cardano.PlutusScriptWitness _ v _ _ _ _)) <- witnesses] + -- We compute the new script integrity hash with the added data scriptIntegrityHash = Cardano.alonzoEraOnwardsConstraints era $ Alonzo.hashScriptIntegrity (Set.fromList $ toLangDepViewParam <$> languages) redeemers datums + -- We wrap all of this in the new body body' = body Lens.& Alonzo.scriptIntegrityHashTxBodyL Lens..~ scriptIntegrityHash txBody' = Cardano.ShelleyTxBody a body' c (Cardano.TxBodyScriptData era datums redeemers) e f diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 754c8dd86..bfa80c91a 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -901,8 +901,8 @@ paysPK pkh value = ) -- | Pays a script a certain value with a certain datum hash, using the --- 'TxSkelOutDatum' constructor. (See the documentation of 'TxSkelOutDatum'.) --- The datum is resolved in the transaction. +-- 'TxSkelOutDatum' constructor. The resolved datum is provided in the body of +-- the transaction that issues the payment. paysScript :: ( Api.ToData (Script.DatumType a), Show (Script.DatumType a), @@ -948,8 +948,9 @@ paysScriptInlineDatum validator datum value = (Nothing @(Script.Versioned Script.Script)) ) --- | Pays a script a certain value with a certain hashed datum which is not --- resolved in the transaction (as opposed to "paysScript"). +-- | Pays a script a certain value with a certain hashed datum, whose resolved +-- datum is not provided in the transaction body that issues the payment (as +-- opposed to "paysScript"). paysScriptUnresolvedDatumHash :: ( Api.ToData (Script.DatumType a), Show (Script.DatumType a), From c54041836533585b0e5761b614afd01de886f146 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 11:40:37 +0200 Subject: [PATCH 53/71] ToScript -> ToVersionedScript --- cooked-validators.cabal | 2 +- src/Cooked/Conversion.hs | 2 +- src/Cooked/Conversion/ToScript.hs | 20 -------------------- src/Cooked/Conversion/ToScriptHash.hs | 8 ++++---- src/Cooked/Conversion/ToVersionedScript.hs | 20 ++++++++++++++++++++ src/Cooked/MockChain/GenerateTx/Output.hs | 2 +- src/Cooked/MockChain/GenerateTx/Proposal.hs | 2 +- src/Cooked/MockChain/GenerateTx/Witness.hs | 4 ++-- src/Cooked/Skeleton.hs | 12 ++++++------ 9 files changed, 36 insertions(+), 36 deletions(-) delete mode 100644 src/Cooked/Conversion/ToScript.hs create mode 100644 src/Cooked/Conversion/ToVersionedScript.hs diff --git a/cooked-validators.cabal b/cooked-validators.cabal index db5ce1f23..29addbeb1 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -24,9 +24,9 @@ library Cooked.Conversion.ToHash Cooked.Conversion.ToOutputDatum Cooked.Conversion.ToPubKeyHash - Cooked.Conversion.ToScript Cooked.Conversion.ToScriptHash Cooked.Conversion.ToValue + Cooked.Conversion.ToVersionedScript Cooked.Currencies Cooked.InitialDistribution Cooked.Ltl diff --git a/src/Cooked/Conversion.hs b/src/Cooked/Conversion.hs index 17f8df084..371239c61 100644 --- a/src/Cooked/Conversion.hs +++ b/src/Cooked/Conversion.hs @@ -6,6 +6,6 @@ import Cooked.Conversion.ToCredential as X import Cooked.Conversion.ToHash as X import Cooked.Conversion.ToOutputDatum as X import Cooked.Conversion.ToPubKeyHash as X -import Cooked.Conversion.ToScript as X import Cooked.Conversion.ToScriptHash as X import Cooked.Conversion.ToValue as X +import Cooked.Conversion.ToVersionedScript as X diff --git a/src/Cooked/Conversion/ToScript.hs b/src/Cooked/Conversion/ToScript.hs deleted file mode 100644 index 8ce9ee943..000000000 --- a/src/Cooked/Conversion/ToScript.hs +++ /dev/null @@ -1,20 +0,0 @@ --- | Objects from which a versioned script can be extracted -module Cooked.Conversion.ToScript where - -import Plutus.Script.Utils.Scripts qualified as Script -import Plutus.Script.Utils.Typed qualified as Script - -class ToScript a where - toScript :: a -> Script.Versioned Script.Script - -instance ToScript (Script.Versioned Script.Script) where - toScript = id - -instance ToScript (Script.Versioned Script.Validator) where - toScript (Script.Versioned (Script.Validator script) version) = Script.Versioned script version - -instance ToScript (Script.TypedValidator a) where - toScript = toScript . Script.vValidatorScript - -instance ToScript (Script.Versioned Script.MintingPolicy) where - toScript (Script.Versioned (Script.MintingPolicy script) version) = Script.Versioned script version diff --git a/src/Cooked/Conversion/ToScriptHash.hs b/src/Cooked/Conversion/ToScriptHash.hs index bc8165f61..3247297ec 100644 --- a/src/Cooked/Conversion/ToScriptHash.hs +++ b/src/Cooked/Conversion/ToScriptHash.hs @@ -1,7 +1,7 @@ -- | Objects from which a script hash can be extracted module Cooked.Conversion.ToScriptHash where -import Cooked.Conversion.ToScript +import Cooked.Conversion.ToVersionedScript import Plutus.Script.Utils.Scripts qualified as Script import Plutus.Script.Utils.Typed qualified as Script import PlutusLedgerApi.V3 qualified as Api @@ -22,10 +22,10 @@ instance ToScriptHash (Script.Versioned Script.Script) where toScriptHash = Script.scriptHash instance ToScriptHash (Script.Versioned Script.Validator) where - toScriptHash = toScriptHash . toScript + toScriptHash = toScriptHash . toVersionedScript instance ToScriptHash (Script.TypedValidator a) where - toScriptHash = toScriptHash . toScript + toScriptHash = toScriptHash . toVersionedScript instance ToScriptHash (Script.Versioned Script.MintingPolicy) where - toScriptHash = toScriptHash . Script.mintingPolicyHash + toScriptHash = toScriptHash . toVersionedScript diff --git a/src/Cooked/Conversion/ToVersionedScript.hs b/src/Cooked/Conversion/ToVersionedScript.hs new file mode 100644 index 000000000..557a3348b --- /dev/null +++ b/src/Cooked/Conversion/ToVersionedScript.hs @@ -0,0 +1,20 @@ +-- | Objects from which a versioned script can be extracted +module Cooked.Conversion.ToVersionedScript where + +import Plutus.Script.Utils.Scripts qualified as Script +import Plutus.Script.Utils.Typed qualified as Script + +class ToVersionedScript a where + toVersionedScript :: a -> Script.Versioned Script.Script + +instance ToVersionedScript (Script.Versioned Script.Script) where + toVersionedScript = id + +instance ToVersionedScript (Script.Versioned Script.Validator) where + toVersionedScript (Script.Versioned (Script.Validator script) version) = Script.Versioned script version + +instance ToVersionedScript (Script.TypedValidator a) where + toVersionedScript = toVersionedScript . Script.vValidatorScript + +instance ToVersionedScript (Script.Versioned Script.MintingPolicy) where + toVersionedScript (Script.Versioned (Script.MintingPolicy script) version) = Script.Versioned script version diff --git a/src/Cooked/MockChain/GenerateTx/Output.hs b/src/Cooked/MockChain/GenerateTx/Output.hs index 3e86b1aed..654923c1b 100644 --- a/src/Cooked/MockChain/GenerateTx/Output.hs +++ b/src/Cooked/MockChain/GenerateTx/Output.hs @@ -45,4 +45,4 @@ toCardanoTxOut (Pays output) = do <$> Ledger.toCardanoScriptDataHash (Script.datumHash $ Api.Datum $ Api.toBuiltinData datum) TxSkelOutDatum datum -> return $ Cardano.TxOutDatumInTx Cardano.AlonzoEraOnwardsConway $ toHashableScriptData datum TxSkelOutInlineDatum datum -> return $ Cardano.TxOutDatumInline Cardano.BabbageEraOnwardsConway $ toHashableScriptData datum - return $ Cardano.TxOut address value datum $ Ledger.toCardanoReferenceScript (toScript <$> oRefScript) + return $ Cardano.TxOut address value datum $ Ledger.toCardanoReferenceScript (toVersionedScript <$> oRefScript) diff --git a/src/Cooked/MockChain/GenerateTx/Proposal.hs b/src/Cooked/MockChain/GenerateTx/Proposal.hs index d1a927558..98ea4b2b4 100644 --- a/src/Cooked/MockChain/GenerateTx/Proposal.hs +++ b/src/Cooked/MockChain/GenerateTx/Proposal.hs @@ -133,7 +133,7 @@ toProposalProcedureAndWitness txSkelProposal@TxSkelProposal {..} anchorResolutio let conwayProposalProcedure = Conway.ProposalProcedure (Emulator.Coin minDeposit) cred govAction anchor (conwayProposalProcedure,) <$> case txSkelProposalWitness of Nothing -> return Nothing - Just (script, redeemer) -> Just <$> liftTxGen (toScriptWitness (toScript script) redeemer Cardano.NoScriptDatumForStake) + Just (script, redeemer) -> Just <$> liftTxGen (toScriptWitness (toVersionedScript script) redeemer Cardano.NoScriptDatumForStake) -- | Translates a list of skeleton proposals into a proposal procedures toProposalProcedures :: [TxSkelProposal] -> AnchorResolution -> ProposalGen (Cardano.TxProposalProcedures Cardano.BuildTx Cardano.ConwayEra) diff --git a/src/Cooked/MockChain/GenerateTx/Witness.hs b/src/Cooked/MockChain/GenerateTx/Witness.hs index eeac4a4cd..52f619055 100644 --- a/src/Cooked/MockChain/GenerateTx/Witness.hs +++ b/src/Cooked/MockChain/GenerateTx/Witness.hs @@ -66,8 +66,8 @@ toPlutusScriptOrReferenceInput script (Just scriptOutRef) = do -- | Translates a script with its associated redeemer and datum to a script -- witness. -toScriptWitness :: (ToScript a) => a -> TxSkelRedeemer -> Cardano.ScriptDatum b -> WitnessGen (Cardano.ScriptWitness b Cardano.ConwayEra) -toScriptWitness (toScript -> script@(Script.Versioned _ version)) (TxSkelRedeemer {..}) datum = +toScriptWitness :: (ToVersionedScript a) => a -> TxSkelRedeemer -> Cardano.ScriptDatum b -> WitnessGen (Cardano.ScriptWitness b Cardano.ConwayEra) +toScriptWitness (toVersionedScript -> script@(Script.Versioned _ version)) (TxSkelRedeemer {..}) datum = let scriptData = case txSkelRedeemer of EmptyRedeemer -> Ledger.toCardanoScriptData $ Api.toBuiltinData () SomeRedeemer s -> Ledger.toCardanoScriptData $ Api.toBuiltinData s diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index bfa80c91a..9bdbcda99 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -584,8 +584,8 @@ makeLensesFor simpleTxSkelProposal :: (ToAddress a) => a -> TxGovAction -> TxSkelProposal simpleTxSkelProposal a govAction = TxSkelProposal (toAddress a) govAction Nothing Nothing -withWitness :: (ToScript a) => TxSkelProposal -> (a, TxSkelRedeemer) -> TxSkelProposal -withWitness prop (s, red) = prop {txSkelProposalWitness = Just (toScript s, red)} +withWitness :: (ToVersionedScript a) => TxSkelProposal -> (a, TxSkelRedeemer) -> TxSkelProposal +withWitness prop (s, red) = prop {txSkelProposalWitness = Just (toVersionedScript s, red)} withAnchor :: TxSkelProposal -> String -> TxSkelProposal withAnchor prop url = prop {txSkelProposalAnchor = Just url} @@ -765,7 +765,7 @@ data TxSkelOut where ToCredential (OwnerType o), DatumType o ~ TxSkelOutDatum, ValueType o ~ Api.Value, -- needed for the 'txSkelOutValueL' - ToScript (ReferenceScriptType o), + ToVersionedScript (ReferenceScriptType o), Show (OwnerType o), Show (ReferenceScriptType o), Typeable (ReferenceScriptType o) @@ -1005,7 +1005,7 @@ withUnresolvedDatumHash (Pays output) datum = Pays $ (fromAbstractOutput output) -- | Add a reference script to a transaction output (or replace it if there is -- already one) -withReferenceScript :: (Show script, ToScript script, Typeable script, ToScriptHash script) => TxSkelOut -> script -> TxSkelOut +withReferenceScript :: (Show script, ToVersionedScript script, Typeable script, ToScriptHash script) => TxSkelOut -> script -> TxSkelOut withReferenceScript (Pays output) script = Pays $ (fromAbstractOutput output) {concreteOutputReferenceScript = Just script} -- | Add a staking credential to a transaction output (or replace it if there is @@ -1127,7 +1127,7 @@ txSkelReferenceScripts = case output ^. outputReferenceScriptL of Nothing -> Map.empty Just x -> - let vScript@(Script.Versioned script version) = toScript x + let vScript@(Script.Versioned script version) = toVersionedScript x Script.ScriptHash hash = toScriptHash vScript in Map.singleton (Script.ValidatorHash hash) $ Script.Versioned (Script.Validator script) version ) @@ -1171,7 +1171,7 @@ txSkelOutOwnerTypeP = case typeOf (output ^. outputOwnerL) `eqTypeRep` typeRep @ownerType of Just HRefl -> let cOut = fromAbstractOutput output - in Just $ cOut {concreteOutputReferenceScript = toScript <$> concreteOutputReferenceScript cOut} + in Just $ cOut {concreteOutputReferenceScript = toVersionedScript <$> concreteOutputReferenceScript cOut} Nothing -> Nothing ) From b234f9c15ad29c9ce605651190e7caecc362b620 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 11:46:32 +0200 Subject: [PATCH 54/71] typos in comments --- src/Cooked/MockChain/GenerateTx/Witness.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cooked/MockChain/GenerateTx/Witness.hs b/src/Cooked/MockChain/GenerateTx/Witness.hs index 52f619055..09fc7103f 100644 --- a/src/Cooked/MockChain/GenerateTx/Witness.hs +++ b/src/Cooked/MockChain/GenerateTx/Witness.hs @@ -41,8 +41,8 @@ toRewardAccount cred = (Ledger.toCardanoStakeKeyHash pubkeyHash) return $ Cardano.KeyHashObj pkHash --- | Translate a script and a reference script utxo into into either a plutus --- script or a reference input containing the right script +-- | Translates a script and a reference script utxo into either a plutus script +-- or a reference input containing the right script toPlutusScriptOrReferenceInput :: Script.Versioned Script.Script -> Maybe Api.TxOutRef -> WitnessGen (Cardano.PlutusScriptOrReferenceInput lang) toPlutusScriptOrReferenceInput (Script.Versioned (Script.Script script) _) Nothing = return $ Cardano.PScript $ Cardano.PlutusScriptSerialised script toPlutusScriptOrReferenceInput script (Just scriptOutRef) = do From 69cf2341f1afbd4d0e035d89f8d20368a080db98 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 16:36:51 +0200 Subject: [PATCH 55/71] first feature but it does not compile due to no ord on redeemers --- cooked-validators.cabal | 1 + src/Cooked/MockChain/AutoReferenceScripts.hs | 57 ++++++++++++++++++++ src/Cooked/Skeleton.hs | 16 ++++-- 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/Cooked/MockChain/AutoReferenceScripts.hs diff --git a/cooked-validators.cabal b/cooked-validators.cabal index 29addbeb1..093b51d04 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -31,6 +31,7 @@ library Cooked.InitialDistribution Cooked.Ltl Cooked.MockChain + Cooked.MockChain.AutoReferenceScripts Cooked.MockChain.Balancing Cooked.MockChain.BlockChain Cooked.MockChain.Direct diff --git a/src/Cooked/MockChain/AutoReferenceScripts.hs b/src/Cooked/MockChain/AutoReferenceScripts.hs new file mode 100644 index 000000000..cea8e912b --- /dev/null +++ b/src/Cooked/MockChain/AutoReferenceScripts.hs @@ -0,0 +1,57 @@ +-- | This module provides a function to ensure that each redeemer used in a +-- skeleton is attached a reference scripts when a known utxos contains it. +module Cooked.MockChain.AutoReferenceScripts (setAllReferenceScripts) where + +import Control.Monad +import Cooked.Conversion +import Cooked.MockChain.BlockChain +import Cooked.MockChain.UtxoSearch +import Cooked.Output +import Cooked.Skeleton +import Data.Map qualified as Map +import Data.Maybe +import Optics.Core +import PlutusLedgerApi.V3 qualified as Api + +-- | Searches through the known utxos for a utxo containing a reference script +-- with a given script hash, and returns the first such utxo found, if any. +retrieveReferenceScript :: (MonadBlockChain m, ToScriptHash s) => s -> m (Maybe Api.TxOutRef) +retrieveReferenceScript script = listToMaybe . (fst <$>) <$> runUtxoSearch (referenceScriptOutputsSearch script) + +-- | Attempts to attach a reference script to a redeemer when it can be found in +-- the context. Will not override any existing reference script. +updateRedeemer :: (MonadBlockChain m, ToScriptHash s) => s -> TxSkelRedeemer -> m TxSkelRedeemer +updateRedeemer script (TxSkelRedeemer red Nothing) = TxSkelRedeemer red <$> retrieveReferenceScript script +updateRedeemer _ redeemer = return redeemer + +-- | Goes through the various parts of the skeleton where a redeemer can appear, +-- and attempt to attach a reference script to each of them, following the rules +-- from `updateRedeemer` +setAllReferenceScripts :: (MonadBlockChain m) => TxSkel -> m TxSkel +setAllReferenceScripts txSkel = do + newMints <- forM (txSkelMintsToList $ txSkel ^. txSkelMintsL) $ \(mPol, red, tk, nb) -> + (mPol,,tk,nb) <$> updateRedeemer mPol red + newInputs <- forM (Map.toList $ txSkel ^. txSkelInsL) $ \(oRef, red) -> do + outputM <- txOutByRef oRef + -- We retrieve the possible script hash of the current oRef + case (^. outputOwnerL) <$> (outputM >>= isScriptOutput) of + -- Either the txOutRef is unknown, or it belongs to a private key + Nothing -> return (oRef, red) + Just scriptHash -> (oRef,) <$> updateRedeemer scriptHash red + newProposals <- forM (txSkel ^. txSkelProposalsL) $ \prop -> + case prop ^. txSkelProposalWitnessL of + Nothing -> return prop + Just (script, red) -> flip (set txSkelProposalWitnessL) prop . Just . (script,) <$> updateRedeemer script red + newWithdrawals <- forM (Map.toList $ txSkel ^. txSkelWithdrawalsL) $ \(wit, quantity) -> case wit of + Right _ -> return (wit, quantity) + Left (script, red) -> (,quantity) . Left . (script,) <$> updateRedeemer script red + return $ + txSkel + & txSkelMintsL + .~ txSkelMintsFromList newMints + & txSkelInsL + .~ Map.fromList newInputs + & txSkelProposalsL + .~ newProposals + & txSkelWithdrawalsL + .~ Map.fromList newWithdrawals diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 9bdbcda99..871db9cab 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -31,6 +31,7 @@ module Cooked.Skeleton txOptEmulatorParamsModificationL, txOptCollateralUtxosL, txOptAnchorResolutionL, + txOptAutoReferenceScriptsL, TxSkelMints, addToTxSkelMints, txSkelMintsToList, @@ -358,7 +359,14 @@ data TxOpts = TxOpts -- | How to resolve anchor in proposal procedures -- -- Default is 'AnchorResolutionLocal Map.Empty' - txOptAnchorResolution :: AnchorResolution + txOptAnchorResolution :: AnchorResolution, + -- | Whether to automatically fill up reference scripts in redeemers when + -- possible. This will imply going through all the known utxos with + -- reference scripts and compare their hashes, thus will slightly reduce + -- performance, especially when handling a lot of utxos. + -- + -- Defaut is 'True'. + txOptAutoReferenceScripts :: Bool } deriving (Eq, Show) @@ -372,7 +380,8 @@ makeLensesFor ("txOptBalancingUtxos", "txOptBalancingUtxosL"), ("txOptEmulatorParamsModification", "txOptEmulatorParamsModificationL"), ("txOptCollateralUtxos", "txOptCollateralUtxosL"), - ("txOptAnchorResolution", "txOptAnchorResolutionL") + ("txOptAnchorResolution", "txOptAnchorResolutionL"), + ("txOptAutoReferenceScripts", "txOptAutoReferenceScriptsL") ] ''TxOpts @@ -388,7 +397,8 @@ instance Default TxOpts where txOptBalancingUtxos = def, txOptEmulatorParamsModification = Nothing, txOptCollateralUtxos = def, - txOptAnchorResolution = def + txOptAnchorResolution = def, + txOptAutoReferenceScripts = True } -- * Redeemers for transaction inputs From cbbc28c37a68bede022d3ac8ad12e1250b3bcb02 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 17:22:45 +0200 Subject: [PATCH 56/71] auto-ref-script feature --- src/Cooked/MockChain/AutoReferenceScripts.hs | 6 +++--- src/Cooked/MockChain/MockChainSt.hs | 4 ++-- src/Cooked/Skeleton.hs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Cooked/MockChain/AutoReferenceScripts.hs b/src/Cooked/MockChain/AutoReferenceScripts.hs index cea8e912b..b6c153896 100644 --- a/src/Cooked/MockChain/AutoReferenceScripts.hs +++ b/src/Cooked/MockChain/AutoReferenceScripts.hs @@ -42,9 +42,9 @@ setAllReferenceScripts txSkel = do case prop ^. txSkelProposalWitnessL of Nothing -> return prop Just (script, red) -> flip (set txSkelProposalWitnessL) prop . Just . (script,) <$> updateRedeemer script red - newWithdrawals <- forM (Map.toList $ txSkel ^. txSkelWithdrawalsL) $ \(wit, quantity) -> case wit of - Right _ -> return (wit, quantity) - Left (script, red) -> (,quantity) . Left . (script,) <$> updateRedeemer script red + newWithdrawals <- forM (Map.toList $ txSkel ^. txSkelWithdrawalsL) $ \(wit, (red, quantity)) -> case wit of + Right _ -> return (wit, (red, quantity)) + Left script -> (Left script,) . (,quantity) <$> updateRedeemer script red return $ txSkel & txSkelMintsL diff --git a/src/Cooked/MockChain/MockChainSt.hs b/src/Cooked/MockChain/MockChainSt.hs index 2dafcc03f..80aa56aa0 100644 --- a/src/Cooked/MockChain/MockChainSt.hs +++ b/src/Cooked/MockChain/MockChainSt.hs @@ -6,8 +6,8 @@ import Cardano.Ledger.Shelley.API qualified as Shelley import Cardano.Ledger.Shelley.LedgerState qualified as Shelley import Cardano.Node.Emulator.Internal.Node qualified as Emulator import Control.Arrow -import Cooked.Conversion.ToScript import Cooked.Conversion.ToScriptHash +import Cooked.Conversion.ToVersionedScript import Cooked.InitialDistribution import Cooked.MockChain.GenerateTx (GenerateTxError (..), generateTxOut) import Cooked.MockChain.UtxoState @@ -178,7 +178,7 @@ referenceScriptMap0From (InitialDistribution initDist) = unitMaybeFrom :: TxSkelOut -> Maybe (Script.ValidatorHash, Script.Versioned Script.Validator) unitMaybeFrom (Pays output) = do refScript <- view outputReferenceScriptL output - let vScript@(Script.Versioned script version) = toScript refScript + let vScript@(Script.Versioned script version) = toVersionedScript refScript Api.ScriptHash scriptHash = toScriptHash vScript return (Script.ValidatorHash scriptHash, Script.Versioned (Script.Validator script) version) diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 871db9cab..cad5e505d 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -619,8 +619,8 @@ txSkelWithdrawalsScripts = fst . partitionEithers . (fst <$>) . Map.toList . txS pkWithdrawal :: (ToPubKeyHash pkh) => pkh -> Script.Ada -> TxSkelWithdrawals pkWithdrawal pkh amount = Map.singleton (Right $ toPubKeyHash pkh) (txSkelEmptyRedeemer, amount) -scriptWithdrawal :: (ToScript script) => script -> TxSkelRedeemer -> Script.Ada -> TxSkelWithdrawals -scriptWithdrawal script red amount = Map.singleton (Left $ toScript script) (red, amount) +scriptWithdrawal :: (ToVersionedScript script) => script -> TxSkelRedeemer -> Script.Ada -> TxSkelWithdrawals +scriptWithdrawal script red amount = Map.singleton (Left $ toVersionedScript script) (red, amount) -- * Description of the Minting From ee2b9fe5b25281f5167a12dc0c08c3f12dc826ca Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 17:34:51 +0200 Subject: [PATCH 57/71] adapting tests --- src/Cooked/MockChain/AutoReferenceScripts.hs | 6 +++--- src/Cooked/MockChain/Direct.hs | 14 ++++++++++++-- tests/Cooked/ReferenceScriptsSpec.hs | 3 ++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Cooked/MockChain/AutoReferenceScripts.hs b/src/Cooked/MockChain/AutoReferenceScripts.hs index b6c153896..d4d93eedf 100644 --- a/src/Cooked/MockChain/AutoReferenceScripts.hs +++ b/src/Cooked/MockChain/AutoReferenceScripts.hs @@ -1,6 +1,6 @@ -- | This module provides a function to ensure that each redeemer used in a -- skeleton is attached a reference scripts when a known utxos contains it. -module Cooked.MockChain.AutoReferenceScripts (setAllReferenceScripts) where +module Cooked.MockChain.AutoReferenceScripts (toTxSkelWithReferenceScripts) where import Control.Monad import Cooked.Conversion @@ -27,8 +27,8 @@ updateRedeemer _ redeemer = return redeemer -- | Goes through the various parts of the skeleton where a redeemer can appear, -- and attempt to attach a reference script to each of them, following the rules -- from `updateRedeemer` -setAllReferenceScripts :: (MonadBlockChain m) => TxSkel -> m TxSkel -setAllReferenceScripts txSkel = do +toTxSkelWithReferenceScripts :: (MonadBlockChain m) => TxSkel -> m TxSkel +toTxSkelWithReferenceScripts txSkel = do newMints <- forM (txSkelMintsToList $ txSkel ^. txSkelMintsL) $ \(mPol, red, tk, nb) -> (mPol,,tk,nb) <$> updateRedeemer mPol red newInputs <- forM (Map.toList $ txSkel ^. txSkelInsL) $ \(oRef, red) -> do diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index 43dcdf2b5..8de984ab4 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -14,6 +14,7 @@ import Control.Monad.Reader import Control.Monad.State.Strict import Control.Monad.Writer import Cooked.InitialDistribution +import Cooked.MockChain.AutoReferenceScripts import Cooked.MockChain.Balancing import Cooked.MockChain.BlockChain import Cooked.MockChain.GenerateTx @@ -148,10 +149,19 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where setParams newParams -- We ensure that the outputs have the required minimal amount of ada, when -- requested in the skeleton options - minAdaSkelUnbal <- if txOptEnsureMinAda . txSkelOpts $ skelUnbal then toTxSkelWithMinAda skelUnbal else return skelUnbal + minAdaSkelUnbal <- + if txOptEnsureMinAda . txSkelOpts $ skelUnbal + then toTxSkelWithMinAda skelUnbal + else return skelUnbal + -- We add reference scripts in the various redeemers of the skeleton, when + -- they can be found in the index and are requested in the skeleton options + minAdaRefScriptsSkelUnbal <- + if txOptAutoReferenceScripts . txSkelOpts $ minAdaSkelUnbal + then toTxSkelWithReferenceScripts minAdaSkelUnbal + else return minAdaSkelUnbal -- We balance the skeleton when requested in the skeleton option, and get -- the associated fee, collateral inputs and return collateral wallet - (skel, fee, mCollaterals) <- balanceTxSkel minAdaSkelUnbal + (skel, fee, mCollaterals) <- balanceTxSkel minAdaRefScriptsSkelUnbal -- We log the adjusted skeleton gets mcstToSkelContext >>= \ctx -> logEvent $ MCLogAdjustedTxSkel ctx skel fee mCollaterals -- We retrieve data that will be used in the transaction generation process: diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index 178324c40..f9341c1b8 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -242,7 +242,8 @@ tests = txSkelTemplate { txSkelIns = Map.singleton oref (txSkelSomeRedeemer ()), txSkelInsReference = Set.singleton scriptOref, - txSkelSigners = [wallet 1] + txSkelSigners = [wallet 1], + txSkelOpts = def {txOptAutoReferenceScripts = False} }, testCase "fail if reference script's requirement is violated" From bcc134042214de45bb4007413fb112651c7bd904 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 17:39:54 +0200 Subject: [PATCH 58/71] a first test of auto ref script is done --- tests/Cooked/ReferenceScriptsSpec.hs | 35 +++++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index f9341c1b8..3aab74749 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -120,8 +120,8 @@ useReferenceScript spendingSubmitter theScript = do txSkelSigners = [spendingSubmitter] } -referenceMint :: (MonadBlockChain m) => Script.Versioned Script.MintingPolicy -> Script.Versioned Script.MintingPolicy -> Int -> m () -referenceMint mp1 mp2 n = do +referenceMint :: (MonadBlockChain m) => Script.Versioned Script.MintingPolicy -> Script.Versioned Script.MintingPolicy -> Int -> Bool -> m () +referenceMint mp1 mp2 n autoRefScript = do ((!! n) -> mpOutRef) <- validateTxSkel' $ txSkelTemplate @@ -131,7 +131,7 @@ referenceMint mp1 mp2 n = do void $ validateTxSkel $ txSkelTemplate - { txSkelMints = txSkelMintsFromList [(mp2, txSkelEmptyRedeemerAndReferenceScript mpOutRef, "banana", 3)], + { txSkelMints = txSkelMintsFromList [(mp2, if autoRefScript then txSkelEmptyRedeemer else txSkelEmptyRedeemerAndReferenceScript mpOutRef, "banana", 3)], txSkelOuts = [paysPK (wallet 1) (Script.ada 2 <> Script.assetClassValue (Script.AssetClass (Script.scriptCurrencySymbol mp2, "banana")) 3)], txSkelSigners = [wallet 1] } @@ -256,19 +256,26 @@ tests = testGroup "referencing minting policies" [ testCase "succeed if given a reference minting policy" $ - testSucceeds $ - referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0, - testCase "fail if given the wrong reference minting policy" $ - testToProp $ - mustFailTest (referenceMint permanentCurrencyPolicyV3 quickCurrencyPolicyV3 0) - `withErrorPred` \case + testSucceeds def $ + referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0 False, + testCase "succeed if relying on automated finding of reference minting policy" $ + testSucceeds def $ + referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0 True, + testCase "fail if given the wrong reference minting policy" + $ testFails + def + ( \case MCEGenerationError (GenerateTxErrorGeneral err) -> err .==. "toPlutusScriptOrReferenceInput: Wrong reference script hash." - _ -> testFailure, - testCase "fail if referencing the wrong utxo" $ - testToProp $ - mustFailTest (referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 1) - `withErrorPred` \case + _ -> testFailure + ) + $ referenceMint permanentCurrencyPolicyV3 quickCurrencyPolicyV3 0 False, + testCase "fail if referencing the wrong utxo" + $ testFails + def + ( \case MCEGenerationError (GenerateTxErrorGeneral err) -> err .==. "toPlutusScriptOrReferenceInput: Can't resolve reference script utxo." _ -> testFailure + ) + $ referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 1 False ] ] From db4fb0f3e500e1cf1010c8528a67d9e73e542e9d Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 17:51:43 +0200 Subject: [PATCH 59/71] logging the addition of a reference script --- src/Cooked/MockChain/AutoReferenceScripts.hs | 11 +++++++++-- src/Cooked/MockChain/BlockChain.hs | 2 ++ src/Cooked/Pretty/Cooked.hs | 8 ++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Cooked/MockChain/AutoReferenceScripts.hs b/src/Cooked/MockChain/AutoReferenceScripts.hs index d4d93eedf..58a91bce5 100644 --- a/src/Cooked/MockChain/AutoReferenceScripts.hs +++ b/src/Cooked/MockChain/AutoReferenceScripts.hs @@ -19,9 +19,16 @@ retrieveReferenceScript :: (MonadBlockChain m, ToScriptHash s) => s -> m (Maybe retrieveReferenceScript script = listToMaybe . (fst <$>) <$> runUtxoSearch (referenceScriptOutputsSearch script) -- | Attempts to attach a reference script to a redeemer when it can be found in --- the context. Will not override any existing reference script. +-- the context. Will not override any existing reference script. If this results +-- in the addition of a reference script, will log the event. updateRedeemer :: (MonadBlockChain m, ToScriptHash s) => s -> TxSkelRedeemer -> m TxSkelRedeemer -updateRedeemer script (TxSkelRedeemer red Nothing) = TxSkelRedeemer red <$> retrieveReferenceScript script +updateRedeemer script txSkelRed@(TxSkelRedeemer red Nothing) = do + oRefM <- retrieveReferenceScript script + case oRefM of + Nothing -> return txSkelRed + Just oRef -> do + logEvent $ MCLogAddedReferenceScript red oRef + return $ TxSkelRedeemer red $ Just oRef updateRedeemer _ redeemer = return redeemer -- | Goes through the various parts of the skeleton where a redeemer can appear, diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index 5c0b4324e..d60316fc8 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -129,6 +129,8 @@ data MockChainLogEntry -- depending on whether the user has provided an explicit wallet or a set of -- utxos to be used as collaterals. MCLogUnusedCollaterals (Either Wallet (Set Api.TxOutRef)) + | -- | Logging the automatic addition of a reference script + MCLogAddedReferenceScript Redeemer Api.TxOutRef -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index c239d82cc..e9ff12b4c 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -162,6 +162,14 @@ instance PrettyCooked MockChainLogEntry where "Specific request to fetch collateral utxos from the given set of" <+> prettyCookedOpt opts n <+> "elements has been disregarded because the transaction does not require collaterals" + prettyCookedOpt opts (MCLogAddedReferenceScript red oRef) = + "A reference script sitting at " + <> prettyCookedOpt opts oRef + <> "has been automatically associated to redeemer " + <> ( case red of + EmptyRedeemer -> "Empty" + SomeRedeemer s -> prettyCookedOpt opts s + ) prettyTxSkel :: PrettyCookedOpts -> SkelContext -> TxSkel -> DocCooked prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals withdrawals) = From d1007c59ee14df154fcd0a4f0da5082ca9f378c9 Mon Sep 17 00:00:00 2001 From: mmontin Date: Fri, 30 Aug 2024 18:05:50 +0200 Subject: [PATCH 60/71] logging new ref scripts --- src/Cooked/MockChain/AutoReferenceScripts.hs | 2 +- src/Cooked/MockChain/BlockChain.hs | 2 +- src/Cooked/Pretty/Cooked.hs | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Cooked/MockChain/AutoReferenceScripts.hs b/src/Cooked/MockChain/AutoReferenceScripts.hs index 58a91bce5..676dc79ff 100644 --- a/src/Cooked/MockChain/AutoReferenceScripts.hs +++ b/src/Cooked/MockChain/AutoReferenceScripts.hs @@ -27,7 +27,7 @@ updateRedeemer script txSkelRed@(TxSkelRedeemer red Nothing) = do case oRefM of Nothing -> return txSkelRed Just oRef -> do - logEvent $ MCLogAddedReferenceScript red oRef + logEvent $ MCLogAddedReferenceScript red oRef (toScriptHash script) return $ TxSkelRedeemer red $ Just oRef updateRedeemer _ redeemer = return redeemer diff --git a/src/Cooked/MockChain/BlockChain.hs b/src/Cooked/MockChain/BlockChain.hs index d60316fc8..869811d6e 100644 --- a/src/Cooked/MockChain/BlockChain.hs +++ b/src/Cooked/MockChain/BlockChain.hs @@ -130,7 +130,7 @@ data MockChainLogEntry -- utxos to be used as collaterals. MCLogUnusedCollaterals (Either Wallet (Set Api.TxOutRef)) | -- | Logging the automatic addition of a reference script - MCLogAddedReferenceScript Redeemer Api.TxOutRef + MCLogAddedReferenceScript Redeemer Api.TxOutRef Script.ScriptHash -- | Contains methods needed for balancing. class (MonadFail m, MonadError MockChainError m) => MonadBlockChainBalancing m where diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index e9ff12b4c..ae5d004b4 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -155,21 +155,23 @@ instance PrettyCooked MockChainLogEntry where prettyCookedOpt opts (MCLogNewTx txId) = "New transaction:" <+> prettyCookedOpt opts txId prettyCookedOpt opts (MCLogDiscardedUtxos n s) = prettyCookedOpt opts n <+> "balancing utxos were discarded:" <+> PP.pretty s prettyCookedOpt opts (MCLogUnusedCollaterals (Left cWallet)) = - "Specific request to fetch collateral utxos from" - <+> prettyCookedOpt opts (walletPKHash cWallet) - <+> "has been disregarded because the transaction does not require collaterals" + "Specific request to fetch collateral utxos from " + <> prettyCookedOpt opts (walletPKHash cWallet) + <> " has been disregarded because the transaction does not require collaterals" prettyCookedOpt opts (MCLogUnusedCollaterals (Right (length -> n))) = - "Specific request to fetch collateral utxos from the given set of" - <+> prettyCookedOpt opts n - <+> "elements has been disregarded because the transaction does not require collaterals" - prettyCookedOpt opts (MCLogAddedReferenceScript red oRef) = + "Specific request to fetch collateral utxos from the given set of " + <> prettyCookedOpt opts n + <> " elements has been disregarded because the transaction does not require collaterals" + prettyCookedOpt opts (MCLogAddedReferenceScript red oRef sHash) = "A reference script sitting at " <> prettyCookedOpt opts oRef - <> "has been automatically associated to redeemer " + <> " has been automatically associated to redeemer " <> ( case red of EmptyRedeemer -> "Empty" SomeRedeemer s -> prettyCookedOpt opts s ) + <> " for script " + <> prettyCookedOpt opts sHash prettyTxSkel :: PrettyCookedOpts -> SkelContext -> TxSkel -> DocCooked prettyTxSkel opts skelContext (TxSkel lbl txopts mints signers validityRange ins insReference outs proposals withdrawals) = From 3d17c7aa19cfa38de9df49a41016c5056080695d Mon Sep 17 00:00:00 2001 From: mmontin Date: Sat, 31 Aug 2024 00:31:20 +0200 Subject: [PATCH 61/71] small upgrade in direct --- src/Cooked/MockChain/Direct.hs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index 8de984ab4..53aae90cf 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -138,27 +138,21 @@ instance (Monad m) => MonadBlockChainWithoutValidation (MockChainT m) where awaitSlot s = modify' (\st -> st {mcstCurrentSlot = max s (mcstCurrentSlot st)}) >> currentSlot instance (Monad m) => MonadBlockChain (MockChainT m) where - validateTxSkel skelUnbal = do + validateTxSkel skelUnbal@TxSkel {..} | TxOpts {..} <- txSkelOpts = do -- We log the submitted skeleton gets mcstToSkelContext >>= logEvent . (`MCLogSubmittedTxSkel` skelUnbal) -- We retrieve the current parameters oldParams <- getParams -- We compute the optionally modified parameters - let newParams = applyEmulatorParamsModification (txOptEmulatorParamsModification . txSkelOpts $ skelUnbal) oldParams + let newParams = applyEmulatorParamsModification txOptEmulatorParamsModification oldParams -- We change the parameters for the duration of the validation process setParams newParams -- We ensure that the outputs have the required minimal amount of ada, when -- requested in the skeleton options - minAdaSkelUnbal <- - if txOptEnsureMinAda . txSkelOpts $ skelUnbal - then toTxSkelWithMinAda skelUnbal - else return skelUnbal + minAdaSkelUnbal <- if txOptEnsureMinAda then toTxSkelWithMinAda skelUnbal else return skelUnbal -- We add reference scripts in the various redeemers of the skeleton, when -- they can be found in the index and are requested in the skeleton options - minAdaRefScriptsSkelUnbal <- - if txOptAutoReferenceScripts . txSkelOpts $ minAdaSkelUnbal - then toTxSkelWithReferenceScripts minAdaSkelUnbal - else return minAdaSkelUnbal + minAdaRefScriptsSkelUnbal <- if txOptAutoReferenceScripts then toTxSkelWithReferenceScripts minAdaSkelUnbal else return minAdaSkelUnbal -- We balance the skeleton when requested in the skeleton option, and get -- the associated fee, collateral inputs and return collateral wallet (skel, fee, mCollaterals) <- balanceTxSkel minAdaRefScriptsSkelUnbal @@ -179,7 +173,7 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where cardanoTx <- case generateTx fee newParams hashedData (insMap <> refInsMap <> collateralInsMap) insValidators mCollaterals skel of Left err -> throwError . MCEGenerationError $ err -- We apply post-generation modification when applicable - Right tx -> return $ Ledger.CardanoEmulatorEraTx $ applyRawModOnBalancedTx (txOptUnsafeModTx . txSkelOpts $ skelUnbal) tx + Right tx -> return $ Ledger.CardanoEmulatorEraTx $ applyRawModOnBalancedTx txOptUnsafeModTx tx -- To run transaction validation we need a minimal ledger state eLedgerState <- gets mcstToEmulatedLedgerState -- We finally run the emulated validation, and we only care about the @@ -213,8 +207,7 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where . addValidators (txSkelValidatorsInOutputs skel <> txSkelReferenceScripts skel) ) -- We apply a change of slot when requested in the options - when (txOptAutoSlotIncrease $ txSkelOpts skel) $ - modify' (\st -> st {mcstCurrentSlot = mcstCurrentSlot st + 1}) + when txOptAutoSlotIncrease $ modify' (\st -> st {mcstCurrentSlot = mcstCurrentSlot st + 1}) -- We return the parameters to their original state setParams oldParams -- We log the validated transaction From 919ae424e3c62c580a79fc60e75b2e7a5729cde4 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sat, 31 Aug 2024 00:52:50 +0200 Subject: [PATCH 62/71] small improvement --- src/Cooked/MockChain/Direct.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cooked/MockChain/Direct.hs b/src/Cooked/MockChain/Direct.hs index 53aae90cf..b1261bc78 100644 --- a/src/Cooked/MockChain/Direct.hs +++ b/src/Cooked/MockChain/Direct.hs @@ -149,10 +149,10 @@ instance (Monad m) => MonadBlockChain (MockChainT m) where setParams newParams -- We ensure that the outputs have the required minimal amount of ada, when -- requested in the skeleton options - minAdaSkelUnbal <- if txOptEnsureMinAda then toTxSkelWithMinAda skelUnbal else return skelUnbal + minAdaSkelUnbal <- (if txOptEnsureMinAda then toTxSkelWithMinAda else return) skelUnbal -- We add reference scripts in the various redeemers of the skeleton, when -- they can be found in the index and are requested in the skeleton options - minAdaRefScriptsSkelUnbal <- if txOptAutoReferenceScripts then toTxSkelWithReferenceScripts minAdaSkelUnbal else return minAdaSkelUnbal + minAdaRefScriptsSkelUnbal <- (if txOptAutoReferenceScripts then toTxSkelWithReferenceScripts else return) minAdaSkelUnbal -- We balance the skeleton when requested in the skeleton option, and get -- the associated fee, collateral inputs and return collateral wallet (skel, fee, mCollaterals) <- balanceTxSkel minAdaRefScriptsSkelUnbal From fb7a68b3bd5fb394698d81393f016693434247f9 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sat, 31 Aug 2024 11:47:31 +0200 Subject: [PATCH 63/71] renaming redeemers --- src/Cooked/MockChain/Balancing.hs | 2 +- src/Cooked/Skeleton.hs | 28 ++++---- tests/Cooked/Attack/DatumHijackingSpec.hs | 4 +- tests/Cooked/Attack/DoubleSatSpec.hs | 20 +++--- tests/Cooked/Attack/DupTokenSpec.hs | 14 ++-- tests/Cooked/BalancingSpec.hs | 6 +- tests/Cooked/BasicUsageSpec.hs | 4 +- tests/Cooked/InitialDistributionSpec.hs | 2 +- tests/Cooked/InlineDatumsSpec.hs | 4 +- tests/Cooked/ProposingScriptSpec.hs | 4 +- tests/Cooked/ReferenceInputsSpec.hs | 4 +- tests/Cooked/ReferenceScriptsSpec.hs | 82 +++++++++++++++-------- tests/Cooked/WithdrawalsSpec.hs | 2 +- 13 files changed, 102 insertions(+), 74 deletions(-) diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 417cf73b2..676d1bbf7 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -398,5 +398,5 @@ computeBalancedTxSkel balancingWallet balancingUtxos txSkel@TxSkel {..} (Script. -- a new output at the end of the list, to keep the order intact. (txOutRefs, val) <- getOptimalCandidate candidatesRaw balancingWallet balancingError return (txOutRefs, txSkelOuts ++ [paysPK balancingWallet val]) - let newTxSkelIns = txSkelIns <> Map.fromList ((,txSkelEmptyRedeemer) <$> additionalInsTxOutRefs) + let newTxSkelIns = txSkelIns <> Map.fromList ((,emptyRedeemer) <$> additionalInsTxOutRefs) return $ (txSkel & txSkelOutsL .~ newTxSkelOuts) & txSkelInsL .~ newTxSkelIns diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index cad5e505d..3c0b865f2 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -96,10 +96,10 @@ module Cooked.Skeleton txSkelValueInOutputs, txSkelReferenceScripts, txSkelReferenceTxOutRefs, - txSkelSomeRedeemer, - txSkelEmptyRedeemer, - txSkelSomeRedeemerAndReferenceScript, - txSkelEmptyRedeemerAndReferenceScript, + someRedeemer, + emptyRedeemer, + someRedeemerAndReferenceScript, + emptyRedeemerAndReferenceScript, ) where @@ -431,17 +431,17 @@ data TxSkelRedeemer = TxSkelRedeemer } deriving (Show, Eq) -txSkelSomeRedeemer :: (RedeemerConstrs redeemer) => redeemer -> TxSkelRedeemer -txSkelSomeRedeemer a = TxSkelRedeemer (SomeRedeemer a) Nothing +someRedeemer :: (RedeemerConstrs redeemer) => redeemer -> TxSkelRedeemer +someRedeemer a = TxSkelRedeemer (SomeRedeemer a) Nothing -txSkelEmptyRedeemer :: TxSkelRedeemer -txSkelEmptyRedeemer = TxSkelRedeemer EmptyRedeemer Nothing +emptyRedeemer :: TxSkelRedeemer +emptyRedeemer = TxSkelRedeemer EmptyRedeemer Nothing -txSkelSomeRedeemerAndReferenceScript :: (RedeemerConstrs redeemer) => Api.TxOutRef -> redeemer -> TxSkelRedeemer -txSkelSomeRedeemerAndReferenceScript outRef a = TxSkelRedeemer (SomeRedeemer a) (Just outRef) +someRedeemerAndReferenceScript :: (RedeemerConstrs redeemer) => Api.TxOutRef -> redeemer -> TxSkelRedeemer +someRedeemerAndReferenceScript outRef a = TxSkelRedeemer (SomeRedeemer a) (Just outRef) -txSkelEmptyRedeemerAndReferenceScript :: Api.TxOutRef -> TxSkelRedeemer -txSkelEmptyRedeemerAndReferenceScript outRef = TxSkelRedeemer EmptyRedeemer (Just outRef) +emptyRedeemerAndReferenceScript :: Api.TxOutRef -> TxSkelRedeemer +emptyRedeemerAndReferenceScript outRef = TxSkelRedeemer EmptyRedeemer (Just outRef) txSkelTypedRedeemer :: (Api.FromData (Script.RedeemerType a)) => TxSkelRedeemer -> Maybe (Script.RedeemerType a) txSkelTypedRedeemer (TxSkelRedeemer (SomeRedeemer red) _) = Api.fromData . Api.toData $ red @@ -617,7 +617,7 @@ txSkelWithdrawalsScripts :: TxSkel -> [Script.Versioned Script.Script] txSkelWithdrawalsScripts = fst . partitionEithers . (fst <$>) . Map.toList . txSkelWithdrawals pkWithdrawal :: (ToPubKeyHash pkh) => pkh -> Script.Ada -> TxSkelWithdrawals -pkWithdrawal pkh amount = Map.singleton (Right $ toPubKeyHash pkh) (txSkelEmptyRedeemer, amount) +pkWithdrawal pkh amount = Map.singleton (Right $ toPubKeyHash pkh) (emptyRedeemer, amount) scriptWithdrawal :: (ToVersionedScript script) => script -> TxSkelRedeemer -> Script.Ada -> TxSkelWithdrawals scriptWithdrawal script red amount = Map.singleton (Left $ toVersionedScript script) (red, amount) @@ -1045,7 +1045,7 @@ data TxSkel where -- specifying how to spend it. You must make sure that -- -- - On 'TxOutRef's referencing UTxOs belonging to public keys, you use - -- the 'txSkelEmptyRedeemer' smart constructor. + -- the 'emptyRedeemer' smart constructor. -- -- - On 'TxOutRef's referencing UTxOs belonging to scripts, you must make -- sure that the type of the redeemer is appropriate for the script. diff --git a/tests/Cooked/Attack/DatumHijackingSpec.hs b/tests/Cooked/Attack/DatumHijackingSpec.hs index 0f471c6d0..de70bcee6 100644 --- a/tests/Cooked/Attack/DatumHijackingSpec.hs +++ b/tests/Cooked/Attack/DatumHijackingSpec.hs @@ -56,7 +56,7 @@ lockTxSkel :: Api.TxOutRef -> Script.TypedValidator DHContract -> TxSkel lockTxSkel o v = txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, - txSkelIns = Map.singleton o txSkelEmptyRedeemer, + txSkelIns = Map.singleton o emptyRedeemer, txSkelOuts = [paysScriptInlineDatum v FirstLock lockValue], txSkelSigners = [wallet 1] } @@ -73,7 +73,7 @@ relockTxSkel :: Script.TypedValidator DHContract -> Api.TxOutRef -> TxSkel relockTxSkel v o = txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, - txSkelIns = Map.singleton o $ txSkelSomeRedeemer (), + txSkelIns = Map.singleton o $ someRedeemer (), txSkelOuts = [paysScriptInlineDatum v SecondLock lockValue], txSkelSigners = [wallet 1] } diff --git a/tests/Cooked/Attack/DoubleSatSpec.hs b/tests/Cooked/Attack/DoubleSatSpec.hs index 658662e61..9c9fdbfcd 100644 --- a/tests/Cooked/Attack/DoubleSatSpec.hs +++ b/tests/Cooked/Attack/DoubleSatSpec.hs @@ -138,9 +138,9 @@ tests = toAddress aValidator /= toAddress bValidator, testGroup "unit tests on a 'TxSkel'" $ -- The following tests make sure that, depending on some - -- 'txSkelSomeRedeemer' constraints for UTxOs + -- 'someRedeemer' constraints for UTxOs -- belonging to the 'aValidator' on the input 'TxSkel', - -- the correct additional 'txSkelSomeRedeemer' + -- the correct additional 'someRedeemer' -- constraints for UTxOs of the 'bValidator' are on the -- output 'TxSkel's. Both 'DoubleSatSplitMode's are -- tested. @@ -149,7 +149,7 @@ tests = skelIn :: [(ARedeemer, Api.TxOutRef)] -> TxSkel skelIn aInputs = txSkelTemplate - { txSkelIns = Map.fromList $ map (second txSkelSomeRedeemer . swap) aInputs, + { txSkelIns = Map.fromList $ map (second someRedeemer . swap) aInputs, txSkelOuts = [paysPK (wallet 2) (Script.lovelaceValueOf 2_500_000)], txSkelSigners = [wallet 1] } @@ -173,13 +173,13 @@ tests = if | aOref == fst aUtxo1 -> return - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) + [ (someRedeemer ARedeemer2, toDelta bOref $ someRedeemer BRedeemer1) | (bOref, bOut) <- bUtxos, outputValue bOut == Script.lovelaceValueOf 123 -- not satisfied by any UTxO in 'dsTestMockChain' ] | aOref == fst aUtxo2 -> return - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1) + [ (someRedeemer ARedeemer2, toDelta bOref $ someRedeemer BRedeemer1) | (bOref, _) <- bUtxos, bOref == fst bUtxo1 ] @@ -189,10 +189,10 @@ tests = ( \(bOref, _) -> if | bOref == fst bUtxo1 -> - [(txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1)] + [(someRedeemer ARedeemer2, toDelta bOref $ someRedeemer BRedeemer1)] | bOref == fst bUtxo2 -> - [ (txSkelSomeRedeemer ARedeemer2, toDelta bOref $ txSkelSomeRedeemer BRedeemer1), - (txSkelSomeRedeemer ARedeemer3, toDelta bOref $ txSkelSomeRedeemer BRedeemer2) + [ (someRedeemer ARedeemer2, toDelta bOref $ someRedeemer BRedeemer1), + (someRedeemer ARedeemer3, toDelta bOref $ someRedeemer BRedeemer2) ] | otherwise -> [] ) @@ -219,13 +219,13 @@ tests = txSkelIns = Map.fromList ( ( \(bRedeemer, (bOref, _)) -> - (bOref, txSkelSomeRedeemer bRedeemer) + (bOref, someRedeemer bRedeemer) ) <$> bInputs ) <> Map.fromList ( ( \(aRedeemer, aOref) -> - (aOref, txSkelSomeRedeemer aRedeemer) + (aOref, someRedeemer aRedeemer) ) <$> aInputs ), diff --git a/tests/Cooked/Attack/DupTokenSpec.hs b/tests/Cooked/Attack/DupTokenSpec.hs index 82acdddb8..0c5f2ee46 100644 --- a/tests/Cooked/Attack/DupTokenSpec.hs +++ b/tests/Cooked/Attack/DupTokenSpec.hs @@ -49,7 +49,7 @@ dupTokenTrace :: (MonadBlockChain m) => Script.Versioned Script.MintingPolicy -> dupTokenTrace pol tName amount recipient = void $ validateTxSkel skel where skel = - let mints = txSkelMintsFromList [(pol, txSkelEmptyRedeemer, tName, amount)] + let mints = txSkelMintsFromList [(pol, emptyRedeemer, tName, amount)] mintedValue = txSkelMintsValue mints in txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, @@ -74,8 +74,8 @@ tests = txSkelTemplate { txSkelMints = txSkelMintsFromList - [ (pol1, txSkelEmptyRedeemer, tName1, 5), - (pol2, txSkelEmptyRedeemer, tName2, 7) + [ (pol1, emptyRedeemer, tName1, 5), + (pol2, emptyRedeemer, tName2, 7) ], txSkelOuts = [ paysPK (wallet 1) (Script.assetClassValue ac1 1 <> Script.lovelaceValueOf 1234), @@ -92,8 +92,8 @@ tests = { txSkelLabel = Set.singleton $ TxLabel DupTokenLbl, txSkelMints = txSkelMintsFromList - [ (pol1, txSkelEmptyRedeemer, tName1, v1), - (pol2, txSkelEmptyRedeemer, tName2, v2) + [ (pol1, emptyRedeemer, tName1, v1), + (pol2, emptyRedeemer, tName2, v2) ], txSkelOuts = [ paysPK (wallet 1) (Script.assetClassValue ac1 1 <> Script.lovelaceValueOf 1234), @@ -133,7 +133,7 @@ tests = ac2 = quickAssetClass "preExistingToken" skelIn = txSkelTemplate - { txSkelMints = txSkelMintsFromList [(pol, txSkelEmptyRedeemer, tName1, 1)], + { txSkelMints = txSkelMintsFromList [(pol, emptyRedeemer, tName1, 1)], txSkelOuts = [paysPK (wallet 1) (Script.assetClassValue ac1 1 <> Script.assetClassValue ac2 2)], txSkelSigners = [wallet 2] } @@ -142,7 +142,7 @@ tests = ( Script.assetClassValue ac1 1, txSkelTemplate { txSkelLabel = Set.singleton $ TxLabel DupTokenLbl, - txSkelMints = txSkelMintsFromList [(pol, txSkelEmptyRedeemer, tName1, 2)], + txSkelMints = txSkelMintsFromList [(pol, emptyRedeemer, tName1, 2)], txSkelOuts = [ paysPK (wallet 1) (Script.assetClassValue ac1 1 <> Script.assetClassValue ac2 2), paysPK attacker (Script.assetClassValue ac1 1) diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index 496e55f44..7a9b86709 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -51,7 +51,7 @@ spendsScriptUtxo :: (MonadBlockChain m) => Bool -> m (Map Api.TxOutRef TxSkelRed spendsScriptUtxo False = return Map.empty spendsScriptUtxo True = do (scriptOutRef, _) : _ <- runUtxoSearch $ utxosAtSearch $ alwaysTrueValidator @MockContract - return $ Map.singleton scriptOutRef txSkelEmptyRedeemer + return $ Map.singleton scriptOutRef emptyRedeemer testingBalancingTemplate :: (MonadBlockChain m) => @@ -78,7 +78,7 @@ testingBalancingTemplate toBobValue toAliceValue spendSearch balanceSearch colla let skel = txSkelTemplate { txSkelOuts = List.filter ((/= mempty) . (^. txSkelOutValueL)) [paysPK bob toBobValue, paysPK alice toAliceValue], - txSkelIns = additionalSpend <> Map.fromList ((,txSkelEmptyRedeemer) <$> toSpendUtxos), + txSkelIns = additionalSpend <> Map.fromList ((,emptyRedeemer) <$> toSpendUtxos), txSkelOpts = optionsMod def @@ -136,7 +136,7 @@ noBalanceMaxFee = do validateTxSkel $ txSkelTemplate { txSkelOuts = [paysPK bob (Script.lovelace (30_000_000 - maxFee))], - txSkelIns = Map.singleton txOutRef txSkelEmptyRedeemer, + txSkelIns = Map.singleton txOutRef emptyRedeemer, txSkelOpts = def { txOptBalancingPolicy = DoNotBalance, diff --git a/tests/Cooked/BasicUsageSpec.hs b/tests/Cooked/BasicUsageSpec.hs index f9c1cfcc6..351b844c3 100644 --- a/tests/Cooked/BasicUsageSpec.hs +++ b/tests/Cooked/BasicUsageSpec.hs @@ -36,7 +36,7 @@ mintingQuickValue = void $ validateTxSkel $ txSkelTemplate - { txSkelMints = txSkelMintsFromList [(Script.Versioned quickCurrencyPolicy Script.PlutusV3, txSkelEmptyRedeemer, "banana", 10)], + { txSkelMints = txSkelMintsFromList [(Script.Versioned quickCurrencyPolicy Script.PlutusV3, emptyRedeemer, "banana", 10)], txSkelOuts = [paysPK alice (quickValue "banana" 10)], txSkelSigners = [alice], txSkelOpts = def {txOptEnsureMinAda = True} @@ -58,7 +58,7 @@ consumeAlwaysTrueValidator = do void $ validateTxSkel $ txSkelTemplate - { txSkelIns = Map.fromList [(outref, txSkelSomeRedeemer ())], + { txSkelIns = Map.fromList [(outref, someRedeemer ())], txSkelOuts = [paysPK alice (Script.ada 10)], txSkelSigners = [alice] } diff --git a/tests/Cooked/InitialDistributionSpec.hs b/tests/Cooked/InitialDistributionSpec.hs index 87b6a5150..85352da98 100644 --- a/tests/Cooked/InitialDistributionSpec.hs +++ b/tests/Cooked/InitialDistributionSpec.hs @@ -42,7 +42,7 @@ spendReferenceAlwaysTrueValidator = do validateTxSkel $ txSkelTemplate { txSkelOuts = [paysPK alice (Script.ada 2)], - txSkelIns = Map.singleton scriptTxOutRef (txSkelSomeRedeemerAndReferenceScript referenceScriptTxOutRef ()), + txSkelIns = Map.singleton scriptTxOutRef (someRedeemerAndReferenceScript referenceScriptTxOutRef ()), txSkelSigners = [bob] } diff --git a/tests/Cooked/InlineDatumsSpec.hs b/tests/Cooked/InlineDatumsSpec.hs index 85b4da1d1..5bc7423d1 100644 --- a/tests/Cooked/InlineDatumsSpec.hs +++ b/tests/Cooked/InlineDatumsSpec.hs @@ -127,7 +127,7 @@ spendOutputTestTrace useInlineDatum validator = do validateTxSkel txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, - txSkelIns = Map.singleton theTxOutRef $ txSkelSomeRedeemer (), + txSkelIns = Map.singleton theTxOutRef $ someRedeemer (), txSkelSigners = [wallet 1] } @@ -152,7 +152,7 @@ continuingOutputTestTrace datumKindOnSecondPayment validator = do validateTxSkel txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, - txSkelIns = Map.singleton theTxOutRef $ txSkelSomeRedeemer (), + txSkelIns = Map.singleton theTxOutRef $ someRedeemer (), txSkelOuts = [ ( case datumKindOnSecondPayment of OnlyHash -> paysScriptUnresolvedDatumHash validator SecondPaymentDatum diff --git a/tests/Cooked/ProposingScriptSpec.hs b/tests/Cooked/ProposingScriptSpec.hs index f2b53b398..c1ad8b607 100644 --- a/tests/Cooked/ProposingScriptSpec.hs +++ b/tests/Cooked/ProposingScriptSpec.hs @@ -35,7 +35,7 @@ testProposingScript script govAction = validateTxSkel txSkelTemplate { txSkelSigners = [wallet 1], - txSkelProposals = [simpleTxSkelProposal (wallet 1) govAction `withWitness` (script, txSkelEmptyRedeemer)] + txSkelProposals = [simpleTxSkelProposal (wallet 1) govAction `withWitness` (script, emptyRedeemer)] } testProposingRefScript :: (MonadBlockChain m) => Script.Versioned Script.Script -> TxGovAction -> m () @@ -50,7 +50,7 @@ testProposingRefScript script govAction = do validateTxSkel $ txSkelTemplate { txSkelSigners = [wallet 1], - txSkelProposals = [simpleTxSkelProposal (wallet 1) govAction `withWitness` (script, txSkelEmptyRedeemerAndReferenceScript pOutRef)] + txSkelProposals = [simpleTxSkelProposal (wallet 1) govAction `withWitness` (script, emptyRedeemerAndReferenceScript pOutRef)] } tests :: TestTree diff --git a/tests/Cooked/ReferenceInputsSpec.hs b/tests/Cooked/ReferenceInputsSpec.hs index f4a8c0a6f..4698bbe9e 100644 --- a/tests/Cooked/ReferenceInputsSpec.hs +++ b/tests/Cooked/ReferenceInputsSpec.hs @@ -116,7 +116,7 @@ trace1 = do void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton txOutRefBar $ txSkelSomeRedeemer (), + { txSkelIns = Map.singleton txOutRefBar $ someRedeemer (), txSkelInsReference = Set.singleton txOutRefFoo, txSkelOuts = [paysPK (wallet 4) (Script.ada 5)], txSkelSigners = [wallet 3] @@ -138,7 +138,7 @@ trace2 = do validateTxSkel $ txSkelTemplate { txSkelSigners = [wallet 1], - txSkelIns = Map.singleton scriptORef (txSkelSomeRedeemer ()), + txSkelIns = Map.singleton scriptORef (someRedeemer ()), txSkelInsReference = Set.singleton refORef } diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index 3aab74749..949399edc 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -99,7 +99,7 @@ checkReferenceScriptOnOref expectedScriptHash refScriptOref = do void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref $ txSkelSomeRedeemer (), + { txSkelIns = Map.singleton oref $ someRedeemer (), txSkelInsReference = Set.singleton refScriptOref, txSkelSigners = [wallet 1] } @@ -116,7 +116,7 @@ useReferenceScript spendingSubmitter theScript = do void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref $ txSkelSomeRedeemerAndReferenceScript scriptOref (), + { txSkelIns = Map.singleton oref $ someRedeemerAndReferenceScript scriptOref (), txSkelSigners = [spendingSubmitter] } @@ -131,7 +131,7 @@ referenceMint mp1 mp2 n autoRefScript = do void $ validateTxSkel $ txSkelTemplate - { txSkelMints = txSkelMintsFromList [(mp2, if autoRefScript then txSkelEmptyRedeemer else txSkelEmptyRedeemerAndReferenceScript mpOutRef, "banana", 3)], + { txSkelMints = txSkelMintsFromList [(mp2, if autoRefScript then emptyRedeemer else emptyRedeemerAndReferenceScript mpOutRef, "banana", 3)], txSkelOuts = [paysPK (wallet 1) (Script.ada 2 <> Script.assetClassValue (Script.AssetClass (Script.scriptCurrencySymbol mp2, "banana")) 3)], txSkelSigners = [wallet 1] } @@ -206,30 +206,58 @@ tests = ) `withErrorPred` \case MCEUnknownOutRefError "lookupUtxos: unknown TxOutRef" _ -> testSuccess - _ -> testFailure, - testCase "fail from transaction generation for mismatching reference scripts" $ - testToProp $ - mustFailTest - ( do - scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysFalseValidator - oref : _ <- - validateTxSkel' - txSkelTemplate - { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], - txSkelSigners = [wallet 1] - } - void $ - validateTxSkel - txSkelTemplate - { txSkelIns = Map.singleton oref (txSkelSomeRedeemerAndReferenceScript scriptOref ()), - txSkelSigners = [wallet 1] - } - ) - `withErrorPred` \case + _ -> testFailure + ) + def + $ do + (consumedOref, _) : _ <- + runUtxoSearch $ + utxosAtSearch (wallet 1) + `filterWithPred` ((`Script.geq` Script.lovelaceValueOf 42_000_000) . outputValue) + oref : _ <- + validateTxSkel' + txSkelTemplate + { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], + txSkelIns = Map.singleton consumedOref emptyRedeemer, + txSkelSigners = [wallet 1] + } + void $ + validateTxSkel + txSkelTemplate + { txSkelIns = Map.singleton oref (someRedeemerAndReferenceScript consumedOref ()), + txSkelSigners = [wallet 1] + }, + testCase "fail from transaction generation for mismatching reference scripts" + $ testFailsFrom + def + ( \case MCEGenerationError err -> err .==. GenerateTxErrorGeneral "toPlutusScriptOrReferenceInput: Wrong reference script hash." - _ -> testFailure, - testCase "phase 1 - fail if using a reference script with 'txSkelSomeRedeemer'" $ - testFailsInPhase1 $ do + _ -> testFailure + ) + def + $ do + scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysFalseValidator + oref : _ <- + validateTxSkel' + txSkelTemplate + { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], + txSkelSigners = [wallet 1] + } + void $ + validateTxSkel + txSkelTemplate + { txSkelIns = Map.singleton oref (someRedeemerAndReferenceScript scriptOref ()), + txSkelSigners = [wallet 1] + }, + testCase "phase 1 - fail if using a reference script with 'someRedeemer'" + $ testFailsFrom + def + ( \case + MCEValidationError Ledger.Phase1 _ -> testSuccess + _ -> testFailure + ) + def + $ do scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysTrueValidator oref : _ <- validateTxSkel' @@ -240,7 +268,7 @@ tests = void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref (txSkelSomeRedeemer ()), + { txSkelIns = Map.singleton oref (someRedeemer ()), txSkelInsReference = Set.singleton scriptOref, txSkelSigners = [wallet 1], txSkelOpts = def {txOptAutoReferenceScripts = False} diff --git a/tests/Cooked/WithdrawalsSpec.hs b/tests/Cooked/WithdrawalsSpec.hs index 8d5ed9345..029a4ee92 100644 --- a/tests/Cooked/WithdrawalsSpec.hs +++ b/tests/Cooked/WithdrawalsSpec.hs @@ -38,7 +38,7 @@ testWithdrawingScript n1 n2 = validateTxSkel $ txSkelTemplate { txSkelSigners = [wallet 1], - txSkelWithdrawals = scriptWithdrawal checkWithdrawalVersionedScript (txSkelSomeRedeemer (n1 * 1_000 :: Integer)) $ Script.Lovelace $ n2 * 1_000 + txSkelWithdrawals = scriptWithdrawal checkWithdrawalVersionedScript (someRedeemer (n1 * 1_000 :: Integer)) $ Script.Lovelace $ n2 * 1_000 } tests :: TestTree From b66c5f132a094c9980363f9a21b4ba9ac4bae696 Mon Sep 17 00:00:00 2001 From: mmontin Date: Sun, 1 Sep 2024 01:00:43 +0200 Subject: [PATCH 64/71] renaming and refactoring of redeemers --- CHANGELOG.md | 12 +++--- src/Cooked/MockChain/Balancing.hs | 2 +- src/Cooked/MockChain/GenerateTx/Witness.hs | 6 +-- src/Cooked/Skeleton.hs | 46 +++++++++++----------- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d45887598..b8685d79d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,11 @@ constrained yet. - PrettyCooked option `pcOptPrintLog`, which is a boolean, to turn on or off the log display in the pretty printer. The default value is `True`. - +- Reference inputs with proper reference scripts are now automatically attached + to redeemers. This can be turned off using `txOptAutoReferenceScripts`, in + which case the helper `withReferenceInput` can be used on a redeemer to + automatically attach a reference input. + ### Removed - `positivePart` and `negativePart` in `ValueUtils.hs`. Replaced by `Api.split`. @@ -33,10 +37,8 @@ - GHC bumped to 9.6.6 - Internal representation of redeemers have changed, and are similar for any supported script purpose (minting, spending or proposing). -- Redeemers should now be built using one of the four following smart - constructors: `txSkelSomeRedeemer`, `txSkelEmptyRedeemer`, - `txSkelSomeRedeemerAndReferenceScript`, - `txSkelEmptyRedeemerAndReferenceScript`. +- Redeemers should now be built using one of the two following smart + constructors: `someTxSkelRedeemer`, `emptyTxSkelRedeemer` - `mkProposingScript` changed to `mkScript` - `withDatumHashed` changed to `withUnresolvedDatumHash` - `paysScriptDatumHashed` changed to `paysScriptUnresolvedDatumHash` diff --git a/src/Cooked/MockChain/Balancing.hs b/src/Cooked/MockChain/Balancing.hs index 676d1bbf7..6c4612eee 100644 --- a/src/Cooked/MockChain/Balancing.hs +++ b/src/Cooked/MockChain/Balancing.hs @@ -398,5 +398,5 @@ computeBalancedTxSkel balancingWallet balancingUtxos txSkel@TxSkel {..} (Script. -- a new output at the end of the list, to keep the order intact. (txOutRefs, val) <- getOptimalCandidate candidatesRaw balancingWallet balancingError return (txOutRefs, txSkelOuts ++ [paysPK balancingWallet val]) - let newTxSkelIns = txSkelIns <> Map.fromList ((,emptyRedeemer) <$> additionalInsTxOutRefs) + let newTxSkelIns = txSkelIns <> Map.fromList ((,emptyTxSkelRedeemer) <$> additionalInsTxOutRefs) return $ (txSkel & txSkelOutsL .~ newTxSkelOuts) & txSkelInsL .~ newTxSkelIns diff --git a/src/Cooked/MockChain/GenerateTx/Witness.hs b/src/Cooked/MockChain/GenerateTx/Witness.hs index 09fc7103f..5b0549fd8 100644 --- a/src/Cooked/MockChain/GenerateTx/Witness.hs +++ b/src/Cooked/MockChain/GenerateTx/Witness.hs @@ -74,10 +74,10 @@ toScriptWitness (toVersionedScript -> script@(Script.Versioned _ version)) (TxSk in case version of Script.PlutusV1 -> (\x -> Cardano.PlutusScriptWitness Cardano.PlutusScriptV1InConway Cardano.PlutusScriptV1 x datum scriptData Ledger.zeroExecutionUnits) - <$> toPlutusScriptOrReferenceInput script txSkelReferenceScript + <$> toPlutusScriptOrReferenceInput script txSkelReferenceInput Script.PlutusV2 -> (\x -> Cardano.PlutusScriptWitness Cardano.PlutusScriptV2InConway Cardano.PlutusScriptV2 x datum scriptData Ledger.zeroExecutionUnits) - <$> toPlutusScriptOrReferenceInput script txSkelReferenceScript + <$> toPlutusScriptOrReferenceInput script txSkelReferenceInput Script.PlutusV3 -> (\x -> Cardano.PlutusScriptWitness Cardano.PlutusScriptV3InConway Cardano.PlutusScriptV3 x datum scriptData Ledger.zeroExecutionUnits) - <$> toPlutusScriptOrReferenceInput script txSkelReferenceScript + <$> toPlutusScriptOrReferenceInput script txSkelReferenceInput diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 3c0b865f2..d9e533e5b 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -59,7 +59,7 @@ module Cooked.Skeleton withStakingCredential, TxSkelRedeemer (..), Redeemer (..), - txSkelTypedRedeemer, + withReferenceInput, TxParameterChange (..), TxGovAction (..), TxSkelProposal (..), @@ -96,10 +96,8 @@ module Cooked.Skeleton txSkelValueInOutputs, txSkelReferenceScripts, txSkelReferenceTxOutRefs, - someRedeemer, - emptyRedeemer, - someRedeemerAndReferenceScript, - emptyRedeemerAndReferenceScript, + someTxSkelRedeemer, + emptyTxSkelRedeemer, ) where @@ -427,25 +425,27 @@ instance Eq Redeemer where data TxSkelRedeemer = TxSkelRedeemer { txSkelRedeemer :: Redeemer, - txSkelReferenceScript :: Maybe Api.TxOutRef + -- An optional input containing a reference script + txSkelReferenceInput :: Maybe Api.TxOutRef } deriving (Show, Eq) -someRedeemer :: (RedeemerConstrs redeemer) => redeemer -> TxSkelRedeemer -someRedeemer a = TxSkelRedeemer (SomeRedeemer a) Nothing +-- Two helpers to create skeleton redeemers +someTxSkelRedeemer :: (RedeemerConstrs redeemer) => redeemer -> TxSkelRedeemer +someTxSkelRedeemer a = TxSkelRedeemer (SomeRedeemer a) Nothing -emptyRedeemer :: TxSkelRedeemer -emptyRedeemer = TxSkelRedeemer EmptyRedeemer Nothing +emptyTxSkelRedeemer :: TxSkelRedeemer +emptyTxSkelRedeemer = TxSkelRedeemer EmptyRedeemer Nothing -someRedeemerAndReferenceScript :: (RedeemerConstrs redeemer) => Api.TxOutRef -> redeemer -> TxSkelRedeemer -someRedeemerAndReferenceScript outRef a = TxSkelRedeemer (SomeRedeemer a) (Just outRef) +-- Additional helper to specify a given reference script. As reference scripts +-- are automatically attached during transaction generation by default, there +-- are only 2 cases where this can be useful: +-- - A wrong reference script somehow needs to be attached +-- - The automated attachement of reference has been disabled using the +-- `txOptAutoReferenceScripts` option -emptyRedeemerAndReferenceScript :: Api.TxOutRef -> TxSkelRedeemer -emptyRedeemerAndReferenceScript outRef = TxSkelRedeemer EmptyRedeemer (Just outRef) - -txSkelTypedRedeemer :: (Api.FromData (Script.RedeemerType a)) => TxSkelRedeemer -> Maybe (Script.RedeemerType a) -txSkelTypedRedeemer (TxSkelRedeemer (SomeRedeemer red) _) = Api.fromData . Api.toData $ red -txSkelTypedRedeemer _ = Nothing +withReferenceInput :: TxSkelRedeemer -> Api.TxOutRef -> TxSkelRedeemer +withReferenceInput red ref = red {txSkelReferenceInput = Just ref} -- * Description of the Governance actions (or proposal procedures) @@ -617,7 +617,7 @@ txSkelWithdrawalsScripts :: TxSkel -> [Script.Versioned Script.Script] txSkelWithdrawalsScripts = fst . partitionEithers . (fst <$>) . Map.toList . txSkelWithdrawals pkWithdrawal :: (ToPubKeyHash pkh) => pkh -> Script.Ada -> TxSkelWithdrawals -pkWithdrawal pkh amount = Map.singleton (Right $ toPubKeyHash pkh) (emptyRedeemer, amount) +pkWithdrawal pkh amount = Map.singleton (Right $ toPubKeyHash pkh) (emptyTxSkelRedeemer, amount) scriptWithdrawal :: (ToVersionedScript script) => script -> TxSkelRedeemer -> Script.Ada -> TxSkelWithdrawals scriptWithdrawal script red amount = Map.singleton (Left $ toVersionedScript script) (red, amount) @@ -1045,7 +1045,7 @@ data TxSkel where -- specifying how to spend it. You must make sure that -- -- - On 'TxOutRef's referencing UTxOs belonging to public keys, you use - -- the 'emptyRedeemer' smart constructor. + -- the 'emptyTxSkelRedeemer' smart constructor. -- -- - On 'TxOutRef's referencing UTxOs belonging to scripts, you must make -- sure that the type of the redeemer is appropriate for the script. @@ -1149,11 +1149,11 @@ txSkelReferenceTxOutRefs TxSkel {..} = -- direct reference inputs Set.toList txSkelInsReference -- reference inputs in inputs redeemers - <> mapMaybe txSkelReferenceScript (Map.elems txSkelIns) + <> mapMaybe txSkelReferenceInput (Map.elems txSkelIns) -- reference inputs in proposals redeemers - <> mapMaybe (txSkelReferenceScript . snd) (mapMaybe txSkelProposalWitness txSkelProposals) + <> mapMaybe (txSkelReferenceInput . snd) (mapMaybe txSkelProposalWitness txSkelProposals) -- reference inputs in mints redeemers - <> mapMaybe (txSkelReferenceScript . fst . snd) (Map.toList txSkelMints) + <> mapMaybe (txSkelReferenceInput . fst . snd) (Map.toList txSkelMints) -- | All `TxOutRefs` known by a given transaction skeleton. This includes -- TxOutRef`s used as inputs of the skeleton and `TxOutRef`s used as reference From 1fd5f56364c486e6bd74fa1a0063ec8f71508aea Mon Sep 17 00:00:00 2001 From: mmontin Date: Sun, 1 Sep 2024 01:17:39 +0200 Subject: [PATCH 65/71] adapting tests --- src/Cooked/Skeleton.hs | 3 ++- tests/Cooked/Attack/DatumHijackingSpec.hs | 4 ++-- tests/Cooked/Attack/DoubleSatSpec.hs | 16 ++++++++-------- tests/Cooked/Attack/DupTokenSpec.hs | 14 +++++++------- tests/Cooked/BalancingSpec.hs | 6 +++--- tests/Cooked/BasicUsageSpec.hs | 4 ++-- tests/Cooked/InitialDistributionSpec.hs | 2 +- tests/Cooked/InlineDatumsSpec.hs | 4 ++-- tests/Cooked/ProposingScriptSpec.hs | 4 ++-- tests/Cooked/ReferenceInputsSpec.hs | 4 ++-- tests/Cooked/ReferenceScriptsSpec.hs | 14 +++++++------- tests/Cooked/WithdrawalsSpec.hs | 2 +- 12 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index d9e533e5b..c759d01d5 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -439,7 +439,8 @@ emptyTxSkelRedeemer = TxSkelRedeemer EmptyRedeemer Nothing -- Additional helper to specify a given reference script. As reference scripts -- are automatically attached during transaction generation by default, there --- are only 2 cases where this can be useful: +-- are 3 cases where this can be useful: +-- - The reliance on a reference script needs to be made explicit -- - A wrong reference script somehow needs to be attached -- - The automated attachement of reference has been disabled using the -- `txOptAutoReferenceScripts` option diff --git a/tests/Cooked/Attack/DatumHijackingSpec.hs b/tests/Cooked/Attack/DatumHijackingSpec.hs index de70bcee6..a3a340730 100644 --- a/tests/Cooked/Attack/DatumHijackingSpec.hs +++ b/tests/Cooked/Attack/DatumHijackingSpec.hs @@ -56,7 +56,7 @@ lockTxSkel :: Api.TxOutRef -> Script.TypedValidator DHContract -> TxSkel lockTxSkel o v = txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, - txSkelIns = Map.singleton o emptyRedeemer, + txSkelIns = Map.singleton o emptyTxSkelRedeemer, txSkelOuts = [paysScriptInlineDatum v FirstLock lockValue], txSkelSigners = [wallet 1] } @@ -73,7 +73,7 @@ relockTxSkel :: Script.TypedValidator DHContract -> Api.TxOutRef -> TxSkel relockTxSkel v o = txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, - txSkelIns = Map.singleton o $ someRedeemer (), + txSkelIns = Map.singleton o $ someTxSkelRedeemer (), txSkelOuts = [paysScriptInlineDatum v SecondLock lockValue], txSkelSigners = [wallet 1] } diff --git a/tests/Cooked/Attack/DoubleSatSpec.hs b/tests/Cooked/Attack/DoubleSatSpec.hs index 9c9fdbfcd..623bbbdae 100644 --- a/tests/Cooked/Attack/DoubleSatSpec.hs +++ b/tests/Cooked/Attack/DoubleSatSpec.hs @@ -149,7 +149,7 @@ tests = skelIn :: [(ARedeemer, Api.TxOutRef)] -> TxSkel skelIn aInputs = txSkelTemplate - { txSkelIns = Map.fromList $ map (second someRedeemer . swap) aInputs, + { txSkelIns = Map.fromList $ map (second someTxSkelRedeemer . swap) aInputs, txSkelOuts = [paysPK (wallet 2) (Script.lovelaceValueOf 2_500_000)], txSkelSigners = [wallet 1] } @@ -173,13 +173,13 @@ tests = if | aOref == fst aUtxo1 -> return - [ (someRedeemer ARedeemer2, toDelta bOref $ someRedeemer BRedeemer1) + [ (someTxSkelRedeemer ARedeemer2, toDelta bOref $ someTxSkelRedeemer BRedeemer1) | (bOref, bOut) <- bUtxos, outputValue bOut == Script.lovelaceValueOf 123 -- not satisfied by any UTxO in 'dsTestMockChain' ] | aOref == fst aUtxo2 -> return - [ (someRedeemer ARedeemer2, toDelta bOref $ someRedeemer BRedeemer1) + [ (someTxSkelRedeemer ARedeemer2, toDelta bOref $ someTxSkelRedeemer BRedeemer1) | (bOref, _) <- bUtxos, bOref == fst bUtxo1 ] @@ -189,10 +189,10 @@ tests = ( \(bOref, _) -> if | bOref == fst bUtxo1 -> - [(someRedeemer ARedeemer2, toDelta bOref $ someRedeemer BRedeemer1)] + [(someTxSkelRedeemer ARedeemer2, toDelta bOref $ someTxSkelRedeemer BRedeemer1)] | bOref == fst bUtxo2 -> - [ (someRedeemer ARedeemer2, toDelta bOref $ someRedeemer BRedeemer1), - (someRedeemer ARedeemer3, toDelta bOref $ someRedeemer BRedeemer2) + [ (someTxSkelRedeemer ARedeemer2, toDelta bOref $ someTxSkelRedeemer BRedeemer1), + (someTxSkelRedeemer ARedeemer3, toDelta bOref $ someTxSkelRedeemer BRedeemer2) ] | otherwise -> [] ) @@ -219,13 +219,13 @@ tests = txSkelIns = Map.fromList ( ( \(bRedeemer, (bOref, _)) -> - (bOref, someRedeemer bRedeemer) + (bOref, someTxSkelRedeemer bRedeemer) ) <$> bInputs ) <> Map.fromList ( ( \(aRedeemer, aOref) -> - (aOref, someRedeemer aRedeemer) + (aOref, someTxSkelRedeemer aRedeemer) ) <$> aInputs ), diff --git a/tests/Cooked/Attack/DupTokenSpec.hs b/tests/Cooked/Attack/DupTokenSpec.hs index 0c5f2ee46..7981eed15 100644 --- a/tests/Cooked/Attack/DupTokenSpec.hs +++ b/tests/Cooked/Attack/DupTokenSpec.hs @@ -49,7 +49,7 @@ dupTokenTrace :: (MonadBlockChain m) => Script.Versioned Script.MintingPolicy -> dupTokenTrace pol tName amount recipient = void $ validateTxSkel skel where skel = - let mints = txSkelMintsFromList [(pol, emptyRedeemer, tName, amount)] + let mints = txSkelMintsFromList [(pol, emptyTxSkelRedeemer, tName, amount)] mintedValue = txSkelMintsValue mints in txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, @@ -74,8 +74,8 @@ tests = txSkelTemplate { txSkelMints = txSkelMintsFromList - [ (pol1, emptyRedeemer, tName1, 5), - (pol2, emptyRedeemer, tName2, 7) + [ (pol1, emptyTxSkelRedeemer, tName1, 5), + (pol2, emptyTxSkelRedeemer, tName2, 7) ], txSkelOuts = [ paysPK (wallet 1) (Script.assetClassValue ac1 1 <> Script.lovelaceValueOf 1234), @@ -92,8 +92,8 @@ tests = { txSkelLabel = Set.singleton $ TxLabel DupTokenLbl, txSkelMints = txSkelMintsFromList - [ (pol1, emptyRedeemer, tName1, v1), - (pol2, emptyRedeemer, tName2, v2) + [ (pol1, emptyTxSkelRedeemer, tName1, v1), + (pol2, emptyTxSkelRedeemer, tName2, v2) ], txSkelOuts = [ paysPK (wallet 1) (Script.assetClassValue ac1 1 <> Script.lovelaceValueOf 1234), @@ -133,7 +133,7 @@ tests = ac2 = quickAssetClass "preExistingToken" skelIn = txSkelTemplate - { txSkelMints = txSkelMintsFromList [(pol, emptyRedeemer, tName1, 1)], + { txSkelMints = txSkelMintsFromList [(pol, emptyTxSkelRedeemer, tName1, 1)], txSkelOuts = [paysPK (wallet 1) (Script.assetClassValue ac1 1 <> Script.assetClassValue ac2 2)], txSkelSigners = [wallet 2] } @@ -142,7 +142,7 @@ tests = ( Script.assetClassValue ac1 1, txSkelTemplate { txSkelLabel = Set.singleton $ TxLabel DupTokenLbl, - txSkelMints = txSkelMintsFromList [(pol, emptyRedeemer, tName1, 2)], + txSkelMints = txSkelMintsFromList [(pol, emptyTxSkelRedeemer, tName1, 2)], txSkelOuts = [ paysPK (wallet 1) (Script.assetClassValue ac1 1 <> Script.assetClassValue ac2 2), paysPK attacker (Script.assetClassValue ac1 1) diff --git a/tests/Cooked/BalancingSpec.hs b/tests/Cooked/BalancingSpec.hs index 7a9b86709..0ac9daca7 100644 --- a/tests/Cooked/BalancingSpec.hs +++ b/tests/Cooked/BalancingSpec.hs @@ -51,7 +51,7 @@ spendsScriptUtxo :: (MonadBlockChain m) => Bool -> m (Map Api.TxOutRef TxSkelRed spendsScriptUtxo False = return Map.empty spendsScriptUtxo True = do (scriptOutRef, _) : _ <- runUtxoSearch $ utxosAtSearch $ alwaysTrueValidator @MockContract - return $ Map.singleton scriptOutRef emptyRedeemer + return $ Map.singleton scriptOutRef emptyTxSkelRedeemer testingBalancingTemplate :: (MonadBlockChain m) => @@ -78,7 +78,7 @@ testingBalancingTemplate toBobValue toAliceValue spendSearch balanceSearch colla let skel = txSkelTemplate { txSkelOuts = List.filter ((/= mempty) . (^. txSkelOutValueL)) [paysPK bob toBobValue, paysPK alice toAliceValue], - txSkelIns = additionalSpend <> Map.fromList ((,emptyRedeemer) <$> toSpendUtxos), + txSkelIns = additionalSpend <> Map.fromList ((,emptyTxSkelRedeemer) <$> toSpendUtxos), txSkelOpts = optionsMod def @@ -136,7 +136,7 @@ noBalanceMaxFee = do validateTxSkel $ txSkelTemplate { txSkelOuts = [paysPK bob (Script.lovelace (30_000_000 - maxFee))], - txSkelIns = Map.singleton txOutRef emptyRedeemer, + txSkelIns = Map.singleton txOutRef emptyTxSkelRedeemer, txSkelOpts = def { txOptBalancingPolicy = DoNotBalance, diff --git a/tests/Cooked/BasicUsageSpec.hs b/tests/Cooked/BasicUsageSpec.hs index 351b844c3..8c086fe9e 100644 --- a/tests/Cooked/BasicUsageSpec.hs +++ b/tests/Cooked/BasicUsageSpec.hs @@ -36,7 +36,7 @@ mintingQuickValue = void $ validateTxSkel $ txSkelTemplate - { txSkelMints = txSkelMintsFromList [(Script.Versioned quickCurrencyPolicy Script.PlutusV3, emptyRedeemer, "banana", 10)], + { txSkelMints = txSkelMintsFromList [(Script.Versioned quickCurrencyPolicy Script.PlutusV3, emptyTxSkelRedeemer, "banana", 10)], txSkelOuts = [paysPK alice (quickValue "banana" 10)], txSkelSigners = [alice], txSkelOpts = def {txOptEnsureMinAda = True} @@ -58,7 +58,7 @@ consumeAlwaysTrueValidator = do void $ validateTxSkel $ txSkelTemplate - { txSkelIns = Map.fromList [(outref, someRedeemer ())], + { txSkelIns = Map.fromList [(outref, someTxSkelRedeemer ())], txSkelOuts = [paysPK alice (Script.ada 10)], txSkelSigners = [alice] } diff --git a/tests/Cooked/InitialDistributionSpec.hs b/tests/Cooked/InitialDistributionSpec.hs index 85352da98..86fe0e19a 100644 --- a/tests/Cooked/InitialDistributionSpec.hs +++ b/tests/Cooked/InitialDistributionSpec.hs @@ -42,7 +42,7 @@ spendReferenceAlwaysTrueValidator = do validateTxSkel $ txSkelTemplate { txSkelOuts = [paysPK alice (Script.ada 2)], - txSkelIns = Map.singleton scriptTxOutRef (someRedeemerAndReferenceScript referenceScriptTxOutRef ()), + txSkelIns = Map.singleton scriptTxOutRef $ someTxSkelRedeemer () `withReferenceInput` referenceScriptTxOutRef, txSkelSigners = [bob] } diff --git a/tests/Cooked/InlineDatumsSpec.hs b/tests/Cooked/InlineDatumsSpec.hs index 5bc7423d1..fc41342f1 100644 --- a/tests/Cooked/InlineDatumsSpec.hs +++ b/tests/Cooked/InlineDatumsSpec.hs @@ -127,7 +127,7 @@ spendOutputTestTrace useInlineDatum validator = do validateTxSkel txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, - txSkelIns = Map.singleton theTxOutRef $ someRedeemer (), + txSkelIns = Map.singleton theTxOutRef $ someTxSkelRedeemer (), txSkelSigners = [wallet 1] } @@ -152,7 +152,7 @@ continuingOutputTestTrace datumKindOnSecondPayment validator = do validateTxSkel txSkelTemplate { txSkelOpts = def {txOptEnsureMinAda = True}, - txSkelIns = Map.singleton theTxOutRef $ someRedeemer (), + txSkelIns = Map.singleton theTxOutRef $ someTxSkelRedeemer (), txSkelOuts = [ ( case datumKindOnSecondPayment of OnlyHash -> paysScriptUnresolvedDatumHash validator SecondPaymentDatum diff --git a/tests/Cooked/ProposingScriptSpec.hs b/tests/Cooked/ProposingScriptSpec.hs index c1ad8b607..3977d004f 100644 --- a/tests/Cooked/ProposingScriptSpec.hs +++ b/tests/Cooked/ProposingScriptSpec.hs @@ -35,7 +35,7 @@ testProposingScript script govAction = validateTxSkel txSkelTemplate { txSkelSigners = [wallet 1], - txSkelProposals = [simpleTxSkelProposal (wallet 1) govAction `withWitness` (script, emptyRedeemer)] + txSkelProposals = [simpleTxSkelProposal (wallet 1) govAction `withWitness` (script, emptyTxSkelRedeemer)] } testProposingRefScript :: (MonadBlockChain m) => Script.Versioned Script.Script -> TxGovAction -> m () @@ -50,7 +50,7 @@ testProposingRefScript script govAction = do validateTxSkel $ txSkelTemplate { txSkelSigners = [wallet 1], - txSkelProposals = [simpleTxSkelProposal (wallet 1) govAction `withWitness` (script, emptyRedeemerAndReferenceScript pOutRef)] + txSkelProposals = [simpleTxSkelProposal (wallet 1) govAction `withWitness` (script, emptyTxSkelRedeemer `withReferenceInput` pOutRef)] } tests :: TestTree diff --git a/tests/Cooked/ReferenceInputsSpec.hs b/tests/Cooked/ReferenceInputsSpec.hs index 4698bbe9e..e3668578b 100644 --- a/tests/Cooked/ReferenceInputsSpec.hs +++ b/tests/Cooked/ReferenceInputsSpec.hs @@ -116,7 +116,7 @@ trace1 = do void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton txOutRefBar $ someRedeemer (), + { txSkelIns = Map.singleton txOutRefBar $ someTxSkelRedeemer (), txSkelInsReference = Set.singleton txOutRefFoo, txSkelOuts = [paysPK (wallet 4) (Script.ada 5)], txSkelSigners = [wallet 3] @@ -138,7 +138,7 @@ trace2 = do validateTxSkel $ txSkelTemplate { txSkelSigners = [wallet 1], - txSkelIns = Map.singleton scriptORef (someRedeemer ()), + txSkelIns = Map.singleton scriptORef (someTxSkelRedeemer ()), txSkelInsReference = Set.singleton refORef } diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index 949399edc..f393c2c84 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -99,7 +99,7 @@ checkReferenceScriptOnOref expectedScriptHash refScriptOref = do void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref $ someRedeemer (), + { txSkelIns = Map.singleton oref $ someTxSkelRedeemer (), txSkelInsReference = Set.singleton refScriptOref, txSkelSigners = [wallet 1] } @@ -116,7 +116,7 @@ useReferenceScript spendingSubmitter theScript = do void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref $ someRedeemerAndReferenceScript scriptOref (), + { txSkelIns = Map.singleton oref $ someTxSkelRedeemer () `withReferenceInput` scriptOref, txSkelSigners = [spendingSubmitter] } @@ -131,7 +131,7 @@ referenceMint mp1 mp2 n autoRefScript = do void $ validateTxSkel $ txSkelTemplate - { txSkelMints = txSkelMintsFromList [(mp2, if autoRefScript then emptyRedeemer else emptyRedeemerAndReferenceScript mpOutRef, "banana", 3)], + { txSkelMints = txSkelMintsFromList [(mp2, if autoRefScript then emptyTxSkelRedeemer else emptyTxSkelRedeemer `withReferenceInput` mpOutRef, "banana", 3)], txSkelOuts = [paysPK (wallet 1) (Script.ada 2 <> Script.assetClassValue (Script.AssetClass (Script.scriptCurrencySymbol mp2, "banana")) 3)], txSkelSigners = [wallet 1] } @@ -218,13 +218,13 @@ tests = validateTxSkel' txSkelTemplate { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], - txSkelIns = Map.singleton consumedOref emptyRedeemer, + txSkelIns = Map.singleton consumedOref emptyTxSkelRedeemer, txSkelSigners = [wallet 1] } void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref (someRedeemerAndReferenceScript consumedOref ()), + { txSkelIns = Map.singleton oref (someTxSkelRedeemer () `withReferenceInput` consumedOref), txSkelSigners = [wallet 1] }, testCase "fail from transaction generation for mismatching reference scripts" @@ -246,7 +246,7 @@ tests = void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref (someRedeemerAndReferenceScript scriptOref ()), + { txSkelIns = Map.singleton oref (someTxSkelRedeemer () `withReferenceInput` scriptOref), txSkelSigners = [wallet 1] }, testCase "phase 1 - fail if using a reference script with 'someRedeemer'" @@ -268,7 +268,7 @@ tests = void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref (someRedeemer ()), + { txSkelIns = Map.singleton oref (someTxSkelRedeemer ()), txSkelInsReference = Set.singleton scriptOref, txSkelSigners = [wallet 1], txSkelOpts = def {txOptAutoReferenceScripts = False} diff --git a/tests/Cooked/WithdrawalsSpec.hs b/tests/Cooked/WithdrawalsSpec.hs index 029a4ee92..95eec8622 100644 --- a/tests/Cooked/WithdrawalsSpec.hs +++ b/tests/Cooked/WithdrawalsSpec.hs @@ -38,7 +38,7 @@ testWithdrawingScript n1 n2 = validateTxSkel $ txSkelTemplate { txSkelSigners = [wallet 1], - txSkelWithdrawals = scriptWithdrawal checkWithdrawalVersionedScript (someRedeemer (n1 * 1_000 :: Integer)) $ Script.Lovelace $ n2 * 1_000 + txSkelWithdrawals = scriptWithdrawal checkWithdrawalVersionedScript (someTxSkelRedeemer (n1 * 1_000 :: Integer)) $ Script.Lovelace $ n2 * 1_000 } tests :: TestTree From 785cc175ab6a06b3c86f56267c15df41a14c4811 Mon Sep 17 00:00:00 2001 From: mmontin Date: Tue, 10 Sep 2024 14:50:54 +0200 Subject: [PATCH 66/71] post merge fix --- tests/Cooked/ReferenceScriptsSpec.hs | 108 ++++++++++----------------- 1 file changed, 39 insertions(+), 69 deletions(-) diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index f393c2c84..e6722d58e 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -194,70 +194,42 @@ tests = validateTxSkel' txSkelTemplate { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], - txSkelIns = Map.singleton consumedOref txSkelEmptyRedeemer, + txSkelIns = Map.singleton consumedOref emptyTxSkelRedeemer, txSkelSigners = [wallet 1] } void $ validateTxSkel txSkelTemplate - { txSkelIns = Map.singleton oref (txSkelSomeRedeemerAndReferenceScript consumedOref ()), + { txSkelIns = Map.singleton oref (someTxSkelRedeemer () `withReferenceInput` consumedOref), txSkelSigners = [wallet 1] } ) `withErrorPred` \case MCEUnknownOutRefError "lookupUtxos: unknown TxOutRef" _ -> testSuccess - _ -> testFailure - ) - def - $ do - (consumedOref, _) : _ <- - runUtxoSearch $ - utxosAtSearch (wallet 1) - `filterWithPred` ((`Script.geq` Script.lovelaceValueOf 42_000_000) . outputValue) - oref : _ <- - validateTxSkel' - txSkelTemplate - { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], - txSkelIns = Map.singleton consumedOref emptyTxSkelRedeemer, - txSkelSigners = [wallet 1] - } - void $ - validateTxSkel - txSkelTemplate - { txSkelIns = Map.singleton oref (someTxSkelRedeemer () `withReferenceInput` consumedOref), - txSkelSigners = [wallet 1] - }, - testCase "fail from transaction generation for mismatching reference scripts" - $ testFailsFrom - def - ( \case + _ -> testFailure, + testCase "fail from transaction generation for mismatching reference scripts" $ + testToProp $ + mustFailTest + ( do + scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysFalseValidator + oref : _ <- + validateTxSkel' + txSkelTemplate + { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], + txSkelSigners = [wallet 1] + } + void $ + validateTxSkel + txSkelTemplate + { txSkelIns = Map.singleton oref (someTxSkelRedeemer () `withReferenceInput` scriptOref), + txSkelSigners = [wallet 1] + } + ) + `withErrorPred` \case MCEGenerationError err -> err .==. GenerateTxErrorGeneral "toPlutusScriptOrReferenceInput: Wrong reference script hash." - _ -> testFailure - ) - def - $ do - scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysFalseValidator - oref : _ <- - validateTxSkel' - txSkelTemplate - { txSkelOuts = [paysScript (alwaysTrueValidator @MockContract) () (Script.ada 42)], - txSkelSigners = [wallet 1] - } - void $ - validateTxSkel - txSkelTemplate - { txSkelIns = Map.singleton oref (someTxSkelRedeemer () `withReferenceInput` scriptOref), - txSkelSigners = [wallet 1] - }, - testCase "phase 1 - fail if using a reference script with 'someRedeemer'" - $ testFailsFrom - def - ( \case - MCEValidationError Ledger.Phase1 _ -> testSuccess - _ -> testFailure - ) - def - $ do + _ -> testFailure, + testCase "phase 1 - fail if using a reference script with 'someRedeemer'" $ + testFailsInPhase1 $ do scriptOref <- putRefScriptOnWalletOutput (wallet 3) alwaysTrueValidator oref : _ <- validateTxSkel' @@ -284,26 +256,24 @@ tests = testGroup "referencing minting policies" [ testCase "succeed if given a reference minting policy" $ - testSucceeds def $ + testSucceeds $ referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0 False, testCase "succeed if relying on automated finding of reference minting policy" $ - testSucceeds def $ - referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0 True, - testCase "fail if given the wrong reference minting policy" - $ testFails - def - ( \case + testToProp $ + mustSucceedTest (referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0 True) + `withJournalPred` testBool + . any (\case MCLogAddedReferenceScript {} -> True; _ -> False), + testCase "fail if given the wrong reference minting policy" $ + testToProp $ + mustFailTest (referenceMint permanentCurrencyPolicyV3 quickCurrencyPolicyV3 0 False) + `withErrorPred` \case MCEGenerationError (GenerateTxErrorGeneral err) -> err .==. "toPlutusScriptOrReferenceInput: Wrong reference script hash." - _ -> testFailure - ) - $ referenceMint permanentCurrencyPolicyV3 quickCurrencyPolicyV3 0 False, - testCase "fail if referencing the wrong utxo" - $ testFails - def - ( \case + _ -> testFailure, + testCase "fail if referencing the wrong utxo" $ + testToProp $ + mustFailTest (referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 1 False) + `withErrorPred` \case MCEGenerationError (GenerateTxErrorGeneral err) -> err .==. "toPlutusScriptOrReferenceInput: Can't resolve reference script utxo." _ -> testFailure - ) - $ referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 1 False ] ] From d6a23e0498dee4f2ee04f4aea3fa083d52201c01 Mon Sep 17 00:00:00 2001 From: mmontin Date: Tue, 10 Sep 2024 15:18:19 +0200 Subject: [PATCH 67/71] CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d45887598..eb91a353d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ constrained yet. - PrettyCooked option `pcOptPrintLog`, which is a boolean, to turn on or off the log display in the pretty printer. The default value is `True`. +- Capability to test the result of a mockchain run based on the log entries. ### Removed @@ -52,6 +53,7 @@ * it now displays when the user specifies useless collateral utxos. * it is not visible from outside of `cooked-validators` - Dependency to cardano-api bumped to 8.46. +- The whole testing API has been revamped ### Fixed From 3121e023a45f22bef06b2e2aab59e7a00b16bc1f Mon Sep 17 00:00:00 2001 From: mmontin Date: Wed, 11 Sep 2024 17:26:40 +0200 Subject: [PATCH 68/71] post merge change --- tests/Cooked/ReferenceScriptsSpec.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index 6a7730553..40cbf8dcb 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -263,8 +263,7 @@ tests = testCase "succeed if relying on automated finding of reference minting policy" $ testToProp $ mustSucceedTest (referenceMint quickCurrencyPolicyV3 quickCurrencyPolicyV3 0 True) - `withJournalPred` testBool - . any (\case MCLogAddedReferenceScript {} -> True; _ -> False), + `withJournalPred` (testBool . any (\case MCLogAddedReferenceScript {} -> True; _ -> False)), testCase "fail if given the wrong reference minting policy" $ testToProp $ mustFailTest (referenceMint permanentCurrencyPolicyV3 quickCurrencyPolicyV3 0 False) From cf7be33fc8e19999aa05ba3fe9f479b9dbf4fe20 Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 12 Sep 2024 08:40:33 +0200 Subject: [PATCH 69/71] integrating review comments --- CHANGELOG.md | 9 +++++---- src/Cooked/MockChain/AutoReferenceScripts.hs | 13 +++++++------ src/Cooked/Pretty/Cooked.hs | 2 +- src/Cooked/Skeleton.hs | 16 ++++++++-------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39ad49347..b26bd862c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,10 +22,11 @@ constrained yet. - PrettyCooked option `pcOptPrintLog`, which is a boolean, to turn on or off the log display in the pretty printer. The default value is `True`. -- Reference inputs with proper reference scripts are now automatically attached - to redeemers. This can be turned off using `txOptAutoReferenceScripts`, in - which case the helper `withReferenceInput` can be used on a redeemer to - automatically attach a reference input. +- Reference inputs with the right reference scripts are now automatically + attached to redeemers when such input exists.. This can be turned off using + `txOptAutoReferenceScripts`, in which case the helper `withReferenceInput` can + be used on a redeemer to manually attach a reference input (which does not + necessarily) have to contain the right reference script). - Capability to test the result of a mockchain run based on the log entries. ### Removed diff --git a/src/Cooked/MockChain/AutoReferenceScripts.hs b/src/Cooked/MockChain/AutoReferenceScripts.hs index 676dc79ff..af5d815d0 100644 --- a/src/Cooked/MockChain/AutoReferenceScripts.hs +++ b/src/Cooked/MockChain/AutoReferenceScripts.hs @@ -1,5 +1,6 @@ -- | This module provides a function to ensure that each redeemer used in a --- skeleton is attached a reference scripts when a known utxos contains it. +-- skeleton is attached a reference input with the right reference script when +-- it exists in the index. module Cooked.MockChain.AutoReferenceScripts (toTxSkelWithReferenceScripts) where import Control.Monad @@ -16,11 +17,11 @@ import PlutusLedgerApi.V3 qualified as Api -- | Searches through the known utxos for a utxo containing a reference script -- with a given script hash, and returns the first such utxo found, if any. retrieveReferenceScript :: (MonadBlockChain m, ToScriptHash s) => s -> m (Maybe Api.TxOutRef) -retrieveReferenceScript script = listToMaybe . (fst <$>) <$> runUtxoSearch (referenceScriptOutputsSearch script) +retrieveReferenceScript = (listToMaybe . (fst <$>) <$>) . runUtxoSearch . referenceScriptOutputsSearch --- | Attempts to attach a reference script to a redeemer when it can be found in --- the context. Will not override any existing reference script. If this results --- in the addition of a reference script, will log the event. +-- | Attempts to find in the index a utxo containing a reference script with the +-- given script hash, and attaches it to a redeemer when it does not yet have a +-- reference input, in which case an event is logged. updateRedeemer :: (MonadBlockChain m, ToScriptHash s) => s -> TxSkelRedeemer -> m TxSkelRedeemer updateRedeemer script txSkelRed@(TxSkelRedeemer red Nothing) = do oRefM <- retrieveReferenceScript script @@ -32,7 +33,7 @@ updateRedeemer script txSkelRed@(TxSkelRedeemer red Nothing) = do updateRedeemer _ redeemer = return redeemer -- | Goes through the various parts of the skeleton where a redeemer can appear, --- and attempt to attach a reference script to each of them, following the rules +-- and attempt to attach a reference input to each of them, following the rules -- from `updateRedeemer` toTxSkelWithReferenceScripts :: (MonadBlockChain m) => TxSkel -> m TxSkel toTxSkelWithReferenceScripts txSkel = do diff --git a/src/Cooked/Pretty/Cooked.hs b/src/Cooked/Pretty/Cooked.hs index ae5d004b4..eb29654a3 100644 --- a/src/Cooked/Pretty/Cooked.hs +++ b/src/Cooked/Pretty/Cooked.hs @@ -163,7 +163,7 @@ instance PrettyCooked MockChainLogEntry where <> prettyCookedOpt opts n <> " elements has been disregarded because the transaction does not require collaterals" prettyCookedOpt opts (MCLogAddedReferenceScript red oRef sHash) = - "A reference script sitting at " + "A reference script located in " <> prettyCookedOpt opts oRef <> " has been automatically associated to redeemer " <> ( case red of diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index c759d01d5..3313fb3de 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -358,10 +358,10 @@ data TxOpts = TxOpts -- -- Default is 'AnchorResolutionLocal Map.Empty' txOptAnchorResolution :: AnchorResolution, - -- | Whether to automatically fill up reference scripts in redeemers when - -- possible. This will imply going through all the known utxos with - -- reference scripts and compare their hashes, thus will slightly reduce - -- performance, especially when handling a lot of utxos. + -- | Whether to automatically fill up reference inputs in redeemers when + -- they contain the right reference script. This will imply going through + -- all the known utxos with reference scripts and compare their hashes, thus + -- will slightly reduce performance. -- -- Defaut is 'True'. txOptAutoReferenceScripts :: Bool @@ -437,12 +437,12 @@ someTxSkelRedeemer a = TxSkelRedeemer (SomeRedeemer a) Nothing emptyTxSkelRedeemer :: TxSkelRedeemer emptyTxSkelRedeemer = TxSkelRedeemer EmptyRedeemer Nothing --- Additional helper to specify a given reference script. As reference scripts --- are automatically attached during transaction generation by default, there --- are 3 cases where this can be useful: +-- Additional helper to specify a given reference input. As reference inputs are +-- automatically attached during transaction generation when they contain the +-- right scripts by default, there are only 3 cases where this can be useful: -- - The reliance on a reference script needs to be made explicit -- - A wrong reference script somehow needs to be attached --- - The automated attachement of reference has been disabled using the +-- - The automated attachement of reference inputs has been disabled using the -- `txOptAutoReferenceScripts` option withReferenceInput :: TxSkelRedeemer -> Api.TxOutRef -> TxSkelRedeemer From a2320b37bc5fe01c2f6c417468cd2d62c9a040ff Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 12 Sep 2024 16:55:20 +0200 Subject: [PATCH 70/71] txOptAutoReferenceScript set to False by default --- src/Cooked/Skeleton.hs | 4 ++-- tests/Cooked/ReferenceScriptsSpec.hs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Cooked/Skeleton.hs b/src/Cooked/Skeleton.hs index 3313fb3de..4d119a45f 100644 --- a/src/Cooked/Skeleton.hs +++ b/src/Cooked/Skeleton.hs @@ -363,7 +363,7 @@ data TxOpts = TxOpts -- all the known utxos with reference scripts and compare their hashes, thus -- will slightly reduce performance. -- - -- Defaut is 'True'. + -- Defaut is 'False'. txOptAutoReferenceScripts :: Bool } deriving (Eq, Show) @@ -396,7 +396,7 @@ instance Default TxOpts where txOptEmulatorParamsModification = Nothing, txOptCollateralUtxos = def, txOptAnchorResolution = def, - txOptAutoReferenceScripts = True + txOptAutoReferenceScripts = False } -- * Redeemers for transaction inputs diff --git a/tests/Cooked/ReferenceScriptsSpec.hs b/tests/Cooked/ReferenceScriptsSpec.hs index 40cbf8dcb..29f868ef3 100644 --- a/tests/Cooked/ReferenceScriptsSpec.hs +++ b/tests/Cooked/ReferenceScriptsSpec.hs @@ -133,7 +133,8 @@ referenceMint mp1 mp2 n autoRefScript = do txSkelTemplate { txSkelMints = txSkelMintsFromList [(mp2, if autoRefScript then emptyTxSkelRedeemer else emptyTxSkelRedeemer `withReferenceInput` mpOutRef, "banana", 3)], txSkelOuts = [paysPK (wallet 1) (Script.ada 2 <> Script.assetClassValue (Script.AssetClass (Script.scriptCurrencySymbol mp2, "banana")) 3)], - txSkelSigners = [wallet 1] + txSkelSigners = [wallet 1], + txSkelOpts = def {txOptAutoReferenceScripts = autoRefScript} } tests :: TestTree From dc06fc241cbc71087cc980a9dc0554057dadcbea Mon Sep 17 00:00:00 2001 From: mmontin Date: Thu, 12 Sep 2024 16:56:45 +0200 Subject: [PATCH 71/71] updating readme --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b26bd862c..02442ac52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,10 +23,10 @@ - PrettyCooked option `pcOptPrintLog`, which is a boolean, to turn on or off the log display in the pretty printer. The default value is `True`. - Reference inputs with the right reference scripts are now automatically - attached to redeemers when such input exists.. This can be turned off using - `txOptAutoReferenceScripts`, in which case the helper `withReferenceInput` can + attached to redeemers when such input exists. This can be turned on using + `txOptAutoReferenceScripts`. If disabled, the helper `withReferenceInput` can be used on a redeemer to manually attach a reference input (which does not - necessarily) have to contain the right reference script). + necessarily have to contain the right reference script). - Capability to test the result of a mockchain run based on the log entries. ### Removed