55{-# LANGUAGE LambdaCase #-}
66{-# LANGUAGE MultiParamTypeClasses #-}
77{-# LANGUAGE NamedFieldPuns #-}
8- {-# LANGUAGE NumericUnderscores #-}
98{-# LANGUAGE OverloadedStrings #-}
109{-# LANGUAGE PolyKinds #-}
1110{-# LANGUAGE RankNTypes #-}
1817{-# LANGUAGE UndecidableSuperClasses #-}
1918
2019module Cardano.Ledger.Plutus.Evaluate (
21- PlutusDebugOverrides (.. ),
2220 PlutusWithContext (.. ),
2321 ScriptFailure (.. ),
2422 ScriptResult (.. ),
2523 scriptPass ,
2624 scriptFail ,
2725 PlutusDebugInfo (.. ),
26+ PlutusDebugOverrides (.. ),
27+ defaultPlutusDebugOverrides ,
2828 debugPlutus ,
29+ debugPlutusUnbounded ,
2930 runPlutusScript ,
3031 runPlutusScriptWithLogs ,
3132 evaluatePlutusWithContext ,
@@ -66,25 +67,28 @@ import Cardano.Ledger.Plutus.Language (
6667 withSamePlutusLanguage ,
6768 )
6869import Cardano.Ledger.Plutus.TxInfo
69- import Control.DeepSeq ( NFData (.. ), force )
70- import Control.Exception ( evaluate )
71- import Control.Monad (join , unless )
70+ import Codec.Extras.SerialiseViaFlat ( DeserialiseFailureInfo (.. ), DeserialiseFailureReason ( .. ) )
71+ import Control.DeepSeq ( NFData ( .. ), deepseq , ($!!) )
72+ import Control.Monad (unless )
7273import Data.ByteString (ByteString )
7374import qualified Data.ByteString.Base16 as B16
7475import qualified Data.ByteString.Base64 as B64
7576import qualified Data.ByteString.Char8 as BSC
7677import qualified Data.ByteString.Short as SBS
7778import qualified Data.ByteString.UTF8 as BSU
7879import Data.Either (fromRight )
80+ import Data.Functor ((<&>) )
7981import Data.Int (Int64 )
8082import Data.List.NonEmpty (NonEmpty (.. ))
8183import Data.Maybe (fromMaybe )
8284import Data.Text (Text , pack )
8385import GHC.Generics (Generic )
86+ import GHC.Stack (HasCallStack )
8487import Numeric.Natural (Natural )
8588import qualified PlutusLedgerApi.Common as P (
86- EvaluationError (CodecError ),
89+ EvaluationError (.. ),
8790 ExBudget ,
91+ ScriptDecodeError (.. ),
8892 VerboseMode (.. ),
8993 )
9094import Prettyprinter (Pretty (.. ))
@@ -231,20 +235,80 @@ data PlutusDebugInfo
231235 -- be executed within 5 second limit or there is a problem with decoding plutus script
232236 -- itself.
233237 (Maybe P. ExBudget )
238+ | -- | Script did not terminate within the imposed limit
239+ DebugTimedOut
240+ -- | Wall clock limit in microseconds that was imposed on the script execution.
241+ Int
234242 deriving (Show )
235243
244+ instance NFData PlutusDebugInfo where
245+ rnf = \ case
246+ DebugBadHex str -> rnf str
247+ DebugCannotDecode str -> rnf str
248+ DebugSuccess logs exBudget -> logs `deepseq` rnf exBudget
249+ DebugFailure logs evalError pwc mExBudget ->
250+ let
251+ -- TODO: Upstream `NFData` instance for `EvaluationError`
252+ seqEvalError = \ case
253+ P. CekError exc -> deepseq exc
254+ P. DeBruijnError err -> deepseq err
255+ P. CodecError err -> deepseqCodecError err
256+ P. CostModelParameterMismatch -> id
257+ P. InvalidReturnValue -> id
258+ -- TODO: Upstream `NFData` instance for `CodecError` and `MajorProtocolVersion`
259+ deepseqCodecError = \ case
260+ P. CBORDeserialiseError failureInfo -> deepseqDeserialiseFailureInfo failureInfo
261+ P. RemainderError bsl -> deepseq bsl
262+ P. LedgerLanguageNotAvailableError pll ipv tpv -> pll `deepseq` ipv `seq` (tpv `seq` )
263+ P. PlutusCoreLanguageNotAvailableError pll ipv tpv -> pll `deepseq` ipv `seq` (tpv `seq` )
264+ -- TODO: Upstream `NFData` instance for `DeserialiseFailureInfo`
265+ deepseqDeserialiseFailureInfo = \ case
266+ DeserialiseFailureInfo bo reason ->
267+ bo `deepseq`
268+ ( -- TODO: Upstream `NFData` instance for `DeserialiseFailureReason`
269+ case reason of
270+ EndOfInput -> id
271+ ExpectedBytes -> id
272+ OtherReason str -> deepseq str
273+ )
274+ in
275+ logs `deepseq` evalError `seqEvalError` pwc `deepseq` rnf mExBudget
276+ DebugTimedOut t -> rnf t
277+
278+ -- | Various overrides that can be supplied to `plutusDebug` and `plutusDebugUnbouded`
236279data PlutusDebugOverrides = PlutusDebugOverrides
237280 { pdoScript :: ! (Maybe ByteString )
281+ -- ^ Hex encoded version of the script
238282 , pdoProtocolVersion :: ! (Maybe Version )
283+ -- ^ Protocol version to be used for decoding and exection
239284 , pdoLanguage :: ! (Maybe Language )
285+ -- ^ Plutus ledger language version
240286 , pdoCostModelValues :: ! (Maybe [Int64 ])
287+ -- ^ Cost model to be used for deciding execution units
241288 , pdoExUnitsMem :: ! (Maybe Natural )
289+ -- ^ Memory execution units to be used for execution
242290 , pdoExUnitsSteps :: ! (Maybe Natural )
291+ -- ^ CPU execution units to be used for execution
292+ , pdoExUnitsEnforced :: ! Bool
293+ -- ^ Setting this flag to True will disable reporting expected execution units upon a failure,
294+ -- which would protect against a potentially unbounded script execution.
243295 }
244296 deriving (Show )
245297
298+ defaultPlutusDebugOverrides :: PlutusDebugOverrides
299+ defaultPlutusDebugOverrides =
300+ PlutusDebugOverrides
301+ { pdoScript = Nothing
302+ , pdoProtocolVersion = Nothing
303+ , pdoLanguage = Nothing
304+ , pdoCostModelValues = Nothing
305+ , pdoExUnitsMem = Nothing
306+ , pdoExUnitsSteps = Nothing
307+ , pdoExUnitsEnforced = False
308+ }
309+
246310-- TODO: Add support for overriding arguments.
247- overrideContext :: PlutusWithContext -> PlutusDebugOverrides -> PlutusWithContext
311+ overrideContext :: HasCallStack => PlutusWithContext -> PlutusDebugOverrides -> PlutusWithContext
248312overrideContext PlutusWithContext {.. } PlutusDebugOverrides {.. } =
249313 -- NOTE: due to GADTs, we can't do a record update here and need to
250314 -- copy all the fields. Otherwise GHC will greet us with
@@ -254,6 +318,7 @@ overrideContext PlutusWithContext {..} PlutusDebugOverrides {..} =
254318 , pwcScript = overrideScript
255319 , pwcExUnits = overrideExUnits
256320 , pwcCostModel = overrideCostModel
321+ , pwcScriptHash = overrideSriptHash
257322 , ..
258323 }
259324 where
@@ -266,38 +331,52 @@ overrideContext PlutusWithContext {..} PlutusDebugOverrides {..} =
266331 mkCostModel
267332 (fromMaybe (getCostModelLanguage pwcCostModel) pdoLanguage)
268333 (fromMaybe (getCostModelParams pwcCostModel) pdoCostModelValues)
269- overrideScript =
334+ (overrideSriptHash, overrideScript) =
270335 case pdoScript of
271- Nothing -> pwcScript
272- Just script ->
273- either error (Left . Plutus . PlutusBinary . SBS. toShort) . B16. decode $ BSC. filter (/= ' \n ' ) script
336+ Nothing -> (pwcScriptHash, pwcScript)
337+ Just hexScript ->
338+ case Plutus . PlutusBinary . SBS. toShort <$> B16. decode (BSC. filter (/= ' \n ' ) hexScript) of
339+ Left err -> error $ " Failed hex decoding of the custom script: " <> err
340+ Right script -> (hashPlutusScript script, Left script)
341+
342+ -- | Execute a hex encoded script with the context that was produced within the ledger predicate
343+ -- failure. Using `PlutusDebugOverrides` it is possible to override any part of the execution.
344+ debugPlutus :: HasCallStack => String -> Int -> PlutusDebugOverrides -> IO PlutusDebugInfo
345+ debugPlutus scriptsWithContext limit opts =
346+ timeout limit (pure $!! debugPlutusUnbounded scriptsWithContext opts)
347+ <&> \ case
348+ Nothing -> DebugTimedOut limit
349+ Just res -> res
274350
275- debugPlutus :: String -> PlutusDebugOverrides -> IO PlutusDebugInfo
276- debugPlutus scriptsWithContext opts =
351+ -- | This is just like `debugPlutus`, except it is pure and if a supplied script contains an
352+ -- infinite loop or a very expensive computation, it might not terminate within a reasonable
353+ -- timeframe.
354+ debugPlutusUnbounded :: HasCallStack => String -> PlutusDebugOverrides -> PlutusDebugInfo
355+ debugPlutusUnbounded scriptsWithContext opts =
277356 case B64. decode (BSU. fromString scriptsWithContext) of
278- Left e -> pure $ DebugBadHex ( show e)
357+ Left e -> DebugBadHex e
279358 Right bs ->
280359 case Plain. decodeFull' bs of
281- Left e -> pure $ DebugCannotDecode $ show e
360+ Left e -> DebugCannotDecode $ show e
282361 Right pwcOriginal ->
283362 let pwc = overrideContext pwcOriginal opts
284363 cm = getEvaluationContext $ pwcCostModel pwc
285364 eu = transExUnits $ pwcExUnits pwc
286- onDecoderError err = pure $ DebugFailure [] err pwc Nothing
365+ onDecoderError err = DebugFailure [] err pwc Nothing
287366 in withRunnablePlutusWithContext pwc onDecoderError $ \ plutusRunnable args ->
288- let toDebugInfo = \ case
289- (logs, Left err@ (P. CodecError {})) -> pure $ DebugFailure logs err pwc Nothing
290- (logs, Left err) -> do
291- mExpectedExUnits <-
292- timeout 5_000_000 $ do
293- let res =
294- evaluatePlutusRunnableBudget (pwcProtocolVersion pwc) P. Verbose cm plutusRunnable args
295- case snd res of
296- Left {} -> pure Nothing
297- Right exUnits -> Just <$> evaluate (force exUnits)
298- pure $ DebugFailure logs err pwc (join mExpectedExUnits)
299- (logs, Right ex) -> pure $ DebugSuccess logs ex
300- in toDebugInfo $
367+ let toDebugInfo logs = \ case
368+ Left err@ (P. CodecError {}) -> DebugFailure logs err pwc Nothing
369+ Left err | pdoExUnitsEnforced opts -> DebugFailure logs err pwc Nothing
370+ Left err ->
371+ let res =
372+ evaluatePlutusRunnableBudget (pwcProtocolVersion pwc) P. Verbose cm plutusRunnable args
373+ mExpectedExUnits =
374+ case snd res of
375+ Left {} -> Nothing
376+ Right exUnits -> Just exUnits
377+ in DebugFailure logs err pwc mExpectedExUnits
378+ Right ex -> DebugSuccess logs ex
379+ in uncurry toDebugInfo $
301380 evaluatePlutusRunnable (pwcProtocolVersion pwc) P. Verbose cm eu plutusRunnable args
302381
303382runPlutusScript :: PlutusWithContext -> ScriptResult
0 commit comments