From a94bffd58be201748c850fc399382802bb5ec837 Mon Sep 17 00:00:00 2001 From: effectfully Date: Wed, 19 Jun 2024 00:15:32 +0200 Subject: [PATCH] [Builtins] Replace 'EvaluationResult' with 'BuiltinResult' (#5926) This replaces several `Emitter (EvaluationResult a)` occurrences with `BuiltinResult`, something that I missed the last [time](https://github.com/IntersectMBO/plutus/pull/5728). In addition to that, it also replaces `EvaluationResult` with `BuiltinResult` in general. It doesn't matter performance-wise (modulo a regression that we didn't notice some time ago), but `BuiltinResult`, unlike `EvaluationResult`, allows one to attach an error message to a failure, which we do in this PR as well, meaning we now get better error messages. And we also now respect the operational vs structural evaluation errors distinction. The PR also replaces `Emitter` with `BuiltinResult`. And makes the GHC Core of builtins smaller by making error-throwing functions (not) inline (see `Note [INLINE and OPAQUE on error-related definitions]` for details). --- ...ace_EvaluationResult_with_BuiltinResult.md | 3 + .../budgeting-bench/Benchmarks/Nops.hs | 19 +- .../examples/PlutusCore/Examples/Builtins.hs | 38 +- .../src/PlutusCore/Builtin/KnownType.hs | 42 ++- .../src/PlutusCore/Builtin/Meaning.hs | 3 +- .../src/PlutusCore/Builtin/Result.hs | 32 +- .../src/PlutusCore/Builtin/Runtime.hs | 6 + .../src/PlutusCore/Crypto/Utils.hs | 5 - .../src/PlutusCore/Default/Builtins.hs | 346 ++++++++++-------- .../src/PlutusCore/Evaluation/Error.hs | 10 +- .../PlutusCore/Evaluation/ErrorWithCause.hs | 5 + .../Evaluation/Machine/Exception.hs | 2 + .../src/PlutusCore/Evaluation/Result.hs | 1 + .../Generators/Hedgehog/Interesting.hs | 8 +- .../Golden/List/headList-empty.err.golden | 4 +- .../Golden/List/tailList-empty.err.golden | 4 +- ...xByteString-out-of-bounds-empty.err.golden | 4 +- ...eString-out-of-bounds-non-empty.err.golden | 4 +- .../Plugin/Basic/9.6/ifOptEval.eval.golden | 2 +- 19 files changed, 322 insertions(+), 216 deletions(-) create mode 100644 plutus-core/changelog.d/20240618_023306_effectfully_replace_EvaluationResult_with_BuiltinResult.md diff --git a/plutus-core/changelog.d/20240618_023306_effectfully_replace_EvaluationResult_with_BuiltinResult.md b/plutus-core/changelog.d/20240618_023306_effectfully_replace_EvaluationResult_with_BuiltinResult.md new file mode 100644 index 00000000000..2ba2a33de8f --- /dev/null +++ b/plutus-core/changelog.d/20240618_023306_effectfully_replace_EvaluationResult_with_BuiltinResult.md @@ -0,0 +1,3 @@ +### Changed + +- Forbade using `EvaluationResult` in the builtins code in favor of `BuiltinResult` in #5926, so that builtins throw errors with more helpful messages. diff --git a/plutus-core/cost-model/budgeting-bench/Benchmarks/Nops.hs b/plutus-core/cost-model/budgeting-bench/Benchmarks/Nops.hs index 213960ccd23..92c57db4528 100644 --- a/plutus-core/cost-model/budgeting-bench/Benchmarks/Nops.hs +++ b/plutus-core/cost-model/budgeting-bench/Benchmarks/Nops.hs @@ -22,6 +22,7 @@ import PlutusCore.Evaluation.Machine.BuiltinCostModel hiding (BuiltinCostModel) import PlutusCore.Evaluation.Machine.ExBudgetingDefaults import PlutusCore.Evaluation.Machine.ExMemoryUsage (ExMemoryUsage) import PlutusCore.Evaluation.Machine.MachineParameters +import PlutusCore.Evaluation.Result (evaluationFailure) import PlutusCore.Pretty import PlutusPrelude import UntypedPlutusCore.Evaluation.Machine.Cek @@ -132,12 +133,12 @@ nopCostParameters = infixr >: (>:) :: uni ~ DefaultUni => SomeConstant uni Integer - -> EvaluationResult Integer - -> EvaluationResult Integer + -> BuiltinResult Integer + -> BuiltinResult Integer n >: k = case n of SomeConstant (Some (ValueOf DefaultUniInteger _)) -> k - _ -> EvaluationFailure + _ -> evaluationFailure {- | The meanings of the builtins. Each one takes a number of arguments and returns a result without doing any other work. A builtin can process its @@ -225,27 +226,27 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni NopFun where -- Integers unlifted via SomeConstant toBuiltinMeaning _semvar Nop1c = makeBuiltinMeaning - (\c1 -> c1 >: EvaluationSuccess 11) + (\c1 -> c1 >: BuiltinSuccess 11) (runCostingFunOneArgument . paramNop1) toBuiltinMeaning _semvar Nop2c = makeBuiltinMeaning - (\c1 c2 -> c1 >: c2 >: EvaluationSuccess 22) + (\c1 c2 -> c1 >: c2 >: BuiltinSuccess 22) (runCostingFunTwoArguments . paramNop2) toBuiltinMeaning _semvar Nop3c = makeBuiltinMeaning - (\c1 c2 c3 -> c1 >: c2 >: c3 >: EvaluationSuccess 33) + (\c1 c2 c3 -> c1 >: c2 >: c3 >: BuiltinSuccess 33) (runCostingFunThreeArguments . paramNop3) toBuiltinMeaning _semvar Nop4c = makeBuiltinMeaning - (\c1 c2 c3 c4 -> c1 >: c2 >: c3 >: c4 >: EvaluationSuccess 44) + (\c1 c2 c3 c4 -> c1 >: c2 >: c3 >: c4 >: BuiltinSuccess 44) (runCostingFunFourArguments . paramNop4) toBuiltinMeaning _semvar Nop5c = makeBuiltinMeaning - (\c1 c2 c3 c4 c5 -> c1 >: c2 >: c3 >: c4 >: c5 >: EvaluationSuccess 55) + (\c1 c2 c3 c4 c5 -> c1 >: c2 >: c3 >: c4 >: c5 >: BuiltinSuccess 55) (runCostingFunFiveArguments . paramNop5) toBuiltinMeaning _semvar Nop6c = makeBuiltinMeaning - (\c1 c2 c3 c4 c5 c6 -> c1 >: c2 >: c3 >: c4 >: c5 >: c6 >: EvaluationSuccess 66) + (\c1 c2 c3 c4 c5 c6 -> c1 >: c2 >: c3 >: c4 >: c5 >: c6 >: BuiltinSuccess 66) (runCostingFunSixArguments . paramNop6) -- Opaque Integers toBuiltinMeaning _semvar Nop1o = diff --git a/plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs b/plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs index 96755b93c7a..0207daa7a43 100644 --- a/plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs +++ b/plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs @@ -24,6 +24,7 @@ import PlutusCore.Data import PlutusCore.Evaluation.Machine.BuiltinCostModel import PlutusCore.Evaluation.Machine.ExBudget import PlutusCore.Evaluation.Machine.ExBudgetStream +import PlutusCore.Evaluation.Result (evaluationFailure) import PlutusCore.Pretty import PlutusCore.StdLib.Data.ScottList qualified as Plc @@ -277,31 +278,31 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni ExtensionFun where idAssumeCheckBoolPlc whatever where - idAssumeCheckBoolPlc :: Opaque val Bool -> EvaluationResult Bool + idAssumeCheckBoolPlc :: Opaque val Bool -> BuiltinResult Bool idAssumeCheckBoolPlc val = case asConstant val of - Right (Some (ValueOf DefaultUniBool b)) -> EvaluationSuccess b - _ -> EvaluationFailure + Right (Some (ValueOf DefaultUniBool b)) -> pure b + _ -> evaluationFailure toBuiltinMeaning _semvar IdSomeConstantBool = makeBuiltinMeaning idSomeConstantBoolPlc whatever where - idSomeConstantBoolPlc :: SomeConstant uni Bool -> EvaluationResult Bool + idSomeConstantBoolPlc :: SomeConstant uni Bool -> BuiltinResult Bool idSomeConstantBoolPlc = \case - SomeConstant (Some (ValueOf DefaultUniBool b)) -> EvaluationSuccess b - _ -> EvaluationFailure + SomeConstant (Some (ValueOf DefaultUniBool b)) -> pure b + _ -> evaluationFailure toBuiltinMeaning _semvar IdIntegerAsBool = makeBuiltinMeaning idIntegerAsBool whatever where - idIntegerAsBool :: SomeConstant uni Integer -> EvaluationResult (SomeConstant uni Integer) + idIntegerAsBool :: SomeConstant uni Integer -> BuiltinResult (SomeConstant uni Integer) idIntegerAsBool = \case - con@(SomeConstant (Some (ValueOf DefaultUniBool _))) -> EvaluationSuccess con - _ -> EvaluationFailure + con@(SomeConstant (Some (ValueOf DefaultUniBool _))) -> pure con + _ -> evaluationFailure toBuiltinMeaning _semvar IdFInteger = makeBuiltinMeaning @@ -380,8 +381,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni ExtensionFun where whatever where unsafeCoerceElPlc - :: SomeConstant DefaultUni [a] - -> EvaluationResult (SomeConstant DefaultUni [b]) + :: SomeConstant DefaultUni [a] -> BuiltinResult (SomeConstant DefaultUni [b]) unsafeCoerceElPlc (SomeConstant (Some (ValueOf uniList xs))) = do DefaultUniList _ <- pure uniList pure $ fromValueOf uniList xs @@ -398,7 +398,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni ExtensionFun where toBuiltinMeaning _semvar ErrorPrime = makeBuiltinMeaning - EvaluationFailure + (evaluationFailure :: forall a. BuiltinResult a) whatever toBuiltinMeaning _semvar Comma = @@ -422,7 +422,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni ExtensionFun where :: SomeConstant uni a -> SomeConstant uni b -> SomeConstant uni (a, b) - -> EvaluationResult (SomeConstant uni (a, b)) + -> BuiltinResult (SomeConstant uni (a, b)) biconstPairPlc (SomeConstant (Some (ValueOf uniA x))) (SomeConstant (Some (ValueOf uniB y))) @@ -439,7 +439,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni ExtensionFun where where swapPlc :: SomeConstant uni (a, b) - -> EvaluationResult (SomeConstant uni (b, a)) + -> BuiltinResult (SomeConstant uni (b, a)) swapPlc (SomeConstant (Some (ValueOf uniPairAB p))) = do DefaultUniPair uniA uniB <- pure uniPairAB pure $ fromValueOf (DefaultUniPair uniB uniA) (snd p, fst p) @@ -452,7 +452,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni ExtensionFun where -- The type reads as @[(a, Bool)] -> [(Bool, a)]@. swapElsPlc :: SomeConstant uni [SomeConstant uni (a, Bool)] - -> EvaluationResult (SomeConstant uni [SomeConstant uni (Bool, a)]) + -> BuiltinResult (SomeConstant uni [SomeConstant uni (Bool, a)]) swapElsPlc (SomeConstant (Some (ValueOf uniList xs))) = do DefaultUniList (DefaultUniPair uniA DefaultUniBool) <- pure uniList let uniList' = DefaultUniList $ DefaultUniPair DefaultUniBool uniA @@ -462,10 +462,10 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni ExtensionFun where -- See Note [Builtin semantics variants] toBuiltinMeaning semvar ExtensionVersion = makeBuiltinMeaning - @(() -> EvaluationResult Integer) - (\(_ :: ()) -> EvaluationSuccess $ case semvar of - ExtensionFunSemanticsVariantX -> 0 - ExtensionFunSemanticsVariantY -> 1) + @(() -> Integer) + (\_ -> case semvar of + ExtensionFunSemanticsVariantX -> 0 + ExtensionFunSemanticsVariantY -> 1) whatever -- We want to know if the CEK machine releases individual budgets after accounting for them and diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs index 20ac23f9cf8..3c4b7a79cf9 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs @@ -248,9 +248,8 @@ typeMismatchError uniExp uniAct = , "expected: " ++ displayBy botRenderContext (SomeTypeIn uniExp) , "; actual: " ++ displayBy botRenderContext (SomeTypeIn uniAct) ] --- Just for tidier Core to get generated, we don't care about performance here, since it's just a --- failure message and evaluation is about to be shut anyway. -{-# NOINLINE typeMismatchError #-} +-- See Note [INLINE and OPAQUE on error-related definitions]. +{-# OPAQUE typeMismatchError #-} -- Normally it's a good idea for an exported abstraction not to be a type synonym, since a @newtype@ -- is cheap, looks good in error messages and clearly emphasize an abstraction barrier. However we @@ -322,11 +321,6 @@ readKnownSelf readKnownSelf val = fromRightM (throwBuiltinErrorWithCause val) $ readKnown val {-# INLINE readKnownSelf #-} -instance MakeKnownIn uni val a => MakeKnownIn uni val (EvaluationResult a) where - makeKnown EvaluationFailure = evaluationFailure - makeKnown (EvaluationSuccess x) = makeKnown x - {-# INLINE makeKnown #-} - instance MakeKnownIn uni val a => MakeKnownIn uni val (BuiltinResult a) where makeKnown res = res >>= makeKnown {-# INLINE makeKnown #-} @@ -338,24 +332,38 @@ instance MakeKnownIn uni val a => MakeKnownIn uni val (BuiltinResult a) where -- I.e. it would essentially allow us to catch errors and handle them in a programmable way. -- We forbid this, because it complicates code and isn't supported by evaluation engines anyway. instance - ( TypeError ('Text "‘EvaluationResult’ cannot appear in the type of an argument") + ( TypeError ('Text "‘BuiltinResult’ cannot appear in the type of an argument") + , uni ~ UniOf val + ) => ReadKnownIn uni val (BuiltinResult a) where + readKnown _ = throwUnderTypeError + {-# INLINE readKnown #-} + +instance + ( TypeError ('Text "Use ‘BuiltinResult’ instead of ‘EvaluationResult’") + , uni ~ UniOf val + ) => MakeKnownIn uni val (EvaluationResult a) where + makeKnown _ = throwUnderTypeError + {-# INLINE makeKnown #-} + +instance + ( TypeError ('Text "Use ‘BuiltinResult’ instead of ‘EvaluationResult’") , uni ~ UniOf val ) => ReadKnownIn uni val (EvaluationResult a) where - readKnown _ = throwing _StructuralUnliftingError "Panic: 'TypeError' was bypassed" - -- Just for 'readKnown' not to appear in the generated Core. + readKnown _ = throwUnderTypeError {-# INLINE readKnown #-} -instance MakeKnownIn uni val a => MakeKnownIn uni val (Emitter a) where - makeKnown a = case runEmitter a of - (x, logs) -> withLogs logs $ makeKnown x +instance + ( TypeError ('Text "Use ‘BuiltinResult’ instead of ‘Emitter’") + , uni ~ UniOf val + ) => MakeKnownIn uni val (Emitter a) where + makeKnown _ = throwUnderTypeError {-# INLINE makeKnown #-} instance - ( TypeError ('Text "‘Emitter’ cannot appear in the type of an argument") + ( TypeError ('Text "Use ‘BuiltinResult’ instead of ‘Emitter’") , uni ~ UniOf val ) => ReadKnownIn uni val (Emitter a) where - readKnown _ = throwing _StructuralUnliftingError "Panic: 'TypeError' was bypassed" - -- Just for 'readKnown' not to appear in the generated Core. + readKnown _ = throwUnderTypeError {-# INLINE readKnown #-} instance HasConstantIn uni val => MakeKnownIn uni val (SomeConstant uni rep) where diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs index 2c04f75ee56..f7260d203d7 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs @@ -30,7 +30,6 @@ import PlutusCore.Evaluation.Machine.ExBudgetStream import PlutusCore.Evaluation.Machine.ExMemoryUsage import PlutusCore.Name.Unique -import Control.Monad.Except (throwError) import Data.Array import Data.Kind qualified as GHC import Data.Proxy @@ -244,7 +243,7 @@ instance (Typeable res, KnownTypeAst TyName (UniOf val) res, MakeKnown val res) -- either a budgeting failure or a budgeting success with a cost and a 'BuiltinResult' -- computation inside, but that would slow things down a bit and the current strategy is -- reasonable enough. - (BuiltinCostedResult (ExBudgetLast mempty) . throwError) + builtinRuntimeFailure (\(x, cost) -> BuiltinCostedResult cost $ makeKnown x) {-# INLINE toMonoF #-} diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Result.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Result.hs index 44a44e1fa34..b1685d6b5fb 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/Result.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Result.hs @@ -1,9 +1,11 @@ +-- editorconfig-checker-disable-file {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE StrictData #-} {-# LANGUAGE TemplateHaskell #-} module PlutusCore.Builtin.Result @@ -21,6 +23,7 @@ module PlutusCore.Builtin.Result , _StructuralUnliftingError , _OperationalUnliftingError , throwNotAConstant + , throwUnderTypeError , withLogs , throwing , throwing_ @@ -39,13 +42,14 @@ import Data.Bitraversable import Data.DList (DList) import Data.String (IsString) import Data.Text (Text) +import Data.Text qualified as Text import Prettyprinter -- | The error message part of an 'UnliftingEvaluationError'. newtype UnliftingError = MkUnliftingError { unUnliftingError :: Text } deriving stock (Show, Eq) - deriving newtype (IsString, Semigroup, NFData) + deriving newtype (IsString, Semigroup, Monoid, NFData) -- | When unlifting of a PLC term into a Haskell value fails, this error is thrown. newtype UnliftingEvaluationError = MkUnliftingEvaluationError @@ -55,7 +59,7 @@ newtype UnliftingEvaluationError = MkUnliftingEvaluationError -- | The type of errors that 'readKnown' and 'makeKnown' can return. data BuiltinError - = BuiltinUnliftingEvaluationError !UnliftingEvaluationError + = BuiltinUnliftingEvaluationError UnliftingEvaluationError | BuiltinEvaluationFailure deriving stock (Show, Eq) @@ -143,6 +147,10 @@ instance MonadEmitter BuiltinResult where emit txt = BuiltinSuccessWithLogs (pure txt) () {-# INLINE emit #-} +instance MonadFail BuiltinResult where + fail err = BuiltinFailure (pure $ Text.pack err) BuiltinEvaluationFailure + {-# INLINE fail #-} + instance Pretty UnliftingError where pretty (MkUnliftingError err) = fold [ "Could not unlift a value:", hardline @@ -155,6 +163,21 @@ instance Pretty BuiltinError where pretty (BuiltinUnliftingEvaluationError err) = "Builtin evaluation failure:" <+> pretty err pretty BuiltinEvaluationFailure = "Builtin evaluation failure" +{- Note [INLINE and OPAQUE on error-related definitions] +We mark error-related definitions such as prisms like '_StructuralUnliftingError' and regular +functions like 'throwNotAConstant' with @INLINE@, because this produces significantly less cluttered +GHC Core. Not doing so results in 20+% larger Core for builtins. + +However in a few specific cases we use @OPAQUE@ instead to get tighter Core. @OPAQUE@ is the same as +@NOINLINE@ except the former _actually_ prevents GHC from inlining the definition unlike the latter. +See this for details: https://github.com/ghc-proposals/ghc-proposals/blob/5577fd008924de8d89cfa9855fa454512e7dcc75/proposals/0415-opaque-pragma.rst + +It's hard to predict where @OPAQUE@ instead of @INLINE@ will help to make GHC Core tidier, so it's +mostly just looking into the Core and seeing where there's obvious duplication that can be removed. +Such cases tend to be functions returning a value of a concrete error type (as opposed to a type +variable). +-} + -- See Note [Ignoring context in OperationalEvaluationError]. -- | Construct a prism focusing on the @*EvaluationFailure@ part of @err@ by taking -- that @*EvaluationFailure@ and @@ -181,6 +204,10 @@ throwNotAConstant :: MonadError BuiltinError m => m void throwNotAConstant = throwing _StructuralUnliftingError "Not a constant" {-# INLINE throwNotAConstant #-} +throwUnderTypeError :: MonadError BuiltinError m => m void +throwUnderTypeError = throwing _StructuralUnliftingError "Panic: 'TypeError' was bypassed" +{-# INLINE throwUnderTypeError #-} + -- | Prepend logs to a 'BuiltinResult' computation. withLogs :: DList Text -> BuiltinResult a -> BuiltinResult a withLogs logs1 = \case @@ -242,6 +269,7 @@ instance MonadError BuiltinError BuiltinResult where (OperationalEvaluationError (MkUnliftingError operationalErr))) -> pure operationalErr _ -> mempty + {-# INLINE throwError #-} -- Throwing logs out is lame, but embedding them into the error would be weird, since that -- would change the error. Not that any of that matters, we only implement this because it's a diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs index a77378a4218..805962c2c62 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs @@ -10,6 +10,7 @@ import PlutusCore.Builtin.KnownType import PlutusCore.Evaluation.Machine.ExBudgetStream import Control.DeepSeq +import Control.Monad.Except (throwError) import NoThunks.Class -- | A 'BuiltinRuntime' represents a possibly partial builtin application, including an empty @@ -78,6 +79,11 @@ instance (Bounded fun, Enum fun) => NoThunks (BuiltinsRuntime fun val) where wNoThunks ctx (BuiltinsRuntime env) = allNoThunks $ map (wNoThunks ctx . env) enumerate showTypeOf = const "PlutusCore.Builtin.Runtime.BuiltinsRuntime" +builtinRuntimeFailure :: BuiltinError -> BuiltinRuntime val +builtinRuntimeFailure = BuiltinCostedResult (ExBudgetLast mempty) . throwError +-- See Note [INLINE and OPAQUE on error-related definitions]. +{-# OPAQUE builtinRuntimeFailure #-} + -- | Look up the runtime info of a built-in function during evaluation. lookupBuiltin :: fun -> BuiltinsRuntime fun val -> BuiltinRuntime val lookupBuiltin fun (BuiltinsRuntime env) = env fun diff --git a/plutus-core/plutus-core/src/PlutusCore/Crypto/Utils.hs b/plutus-core/plutus-core/src/PlutusCore/Crypto/Utils.hs index a67300c0310..bb80ce83dcb 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Crypto/Utils.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Crypto/Utils.hs @@ -12,11 +12,6 @@ import Data.Kind (Type) import Data.Text (Text) import Text.Printf (printf) --- TODO: Something like 'failWithMessage x y *> foo' should really fail with --- 'EvaluationFailure' without evaluating 'foo', but currently it will. This --- requires a fix to how Emitter and EvaluationResult work, and since we don't --- expect 'failWithMessage' to be used this way, we note this for future --- reference only for when such fixes are made. failWithMessage :: forall (a :: Type). Text -> Text -> BuiltinResult a failWithMessage location reason = do emit $ location <> ": " <> reason diff --git a/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs b/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs index ecc6bc4f5f0..e4a51c5e2d0 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs @@ -1,4 +1,5 @@ -- editorconfig-checker-disable-file +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE FlexibleInstances #-} @@ -22,7 +23,6 @@ import PlutusCore.Evaluation.Machine.BuiltinCostModel import PlutusCore.Evaluation.Machine.ExBudgetStream (ExBudgetStream) import PlutusCore.Evaluation.Machine.ExMemoryUsage (ExMemoryUsage, LiteralByteSize (..), memoryUsage, singletonRose) -import PlutusCore.Evaluation.Result (EvaluationResult (..)) import PlutusCore.Pretty (PrettyConfigPlc) import PlutusCore.Bitwise qualified as Bitwise @@ -34,10 +34,11 @@ import PlutusCore.Crypto.Hash qualified as Hash import PlutusCore.Crypto.Secp256k1 (verifyEcdsaSecp256k1Signature, verifySchnorrSecp256k1Signature) import Codec.Serialise (serialise) +import Control.Monad (unless) import Data.ByteString qualified as BS import Data.ByteString.Lazy qualified as BSL import Data.Ix (Ix) -import Data.Text (Text, pack) +import Data.Text (Text) import Data.Text.Encoding (decodeUtf8', encodeUtf8) import Flat hiding (from, to) import Flat.Decoder (Get, dBEBits8) @@ -178,22 +179,36 @@ instance Pretty DefaultFun where instance ExMemoryUsage DefaultFun where memoryUsage _ = singletonRose 1 + {-# INLINE memoryUsage #-} --- | Turn a function into another function that returns 'EvaluationFailure' when --- its second argument is 0 or calls the original function otherwise and wraps --- the result in 'EvaluationSuccess'. Useful for correctly handling `div`, +-- | Turn a function into another function that 'fail's when its second argument is @0@ or calls the +-- original function otherwise and wraps the result in 'pure'. Useful for correctly handling `div`, -- `mod`, etc. nonZeroSecondArg - :: (Integer -> Integer -> Integer) -> Integer -> Integer -> EvaluationResult Integer -nonZeroSecondArg _ _ 0 = EvaluationFailure -nonZeroSecondArg f x y = EvaluationSuccess $ f x y - --- | Turn a function returning 'Either' into another function that emits an --- error message and returns 'EvaluationFailure' in the 'Left' case and wraps --- the result in 'EvaluationSuccess' in the 'Right' case. -eitherToEmitter :: Show e => Either e r -> Emitter (EvaluationResult r) -eitherToEmitter (Left e) = (emit . pack . show $ e) >> pure EvaluationFailure -eitherToEmitter (Right r) = pure . pure $ r + :: (Integer -> Integer -> Integer) -> Integer -> Integer -> BuiltinResult Integer +-- If we match against @IS 0#@ instead of @0@, GHC will generate tidier Core for some reason. It +-- probably doesn't really matter performance-wise, but would be easier to read. We don't do it out +-- of paranoia and because it requires importing the 'IS' constructor, which is in different +-- packages depending on the GHC version, so requires a bunch of irritating CPP. +-- +-- We could also replace 'div' with 'integerDiv' (and do the same for other division builtins) at +-- the call site of this function in order to avoid double matching against @0@, but that also +-- requires CPP. Perhaps we can afford one additional pattern match for division builtins for the +-- time being, since those aren't particularly fast anyway. +-- +-- The bang is to communicate to GHC that the function is strict in both the arguments just in case +-- it'd want to allocate a thunk for the first argument otherwise. +nonZeroSecondArg _ !_ 0 = + -- See Note [Operational vs structural errors within builtins]. + fail "Cannot divide by zero" +nonZeroSecondArg f x y = pure $ f x y +{-# INLINE nonZeroSecondArg #-} + +-- | Turn a function returning 'Either' into another function that 'fail's in the 'Left' case and +-- wraps the result in 'pure' in the 'Right' case. +eitherToBuiltinResult :: Show e => Either e r -> BuiltinResult r +eitherToBuiltinResult = either (fail . show) pure +{-# INLINE eitherToBuiltinResult #-} {- Note [Constants vs built-in functions] A constant is any value of a built-in type. For example, 'Integer' is a built-in type, so anything @@ -259,7 +274,7 @@ it within the @ToBuiltinMeaning uni DefaultFun@ instance. The general pattern is Here's a specific example: - toBuiltinMeaning _semvar AddInteger = + toBuiltinMeaning _ AddInteger = let addIntegerDenotation :: Integer -> Integer -> Integer addIntegerDenotation = (+) {-# INLINE addIntegerDenotation #-} @@ -383,29 +398,29 @@ There's a number of ways a builtin can fail: - as we've just seen a type conversion can fail due to an unsuccessful bounds check - if the builtin expects, say, a 'Text' argument, but gets fed an 'Integer' argument - if the builtin expects any constant, but gets fed a non-constant -- if its denotation runs in the 'EvaluationResult' and an 'EvaluationFailure' gets returned +- if its denotation runs in the 'BuiltinResult' monad and an 'evaluationFailure' gets returned Most of these are not a concern to the user defining a built-in function (conversions are handled within the builtin application machinery, type mismatches are on the type checker and the person -writing the program etc), however explicitly returning 'EvaluationFailure' from a builtin is +writing the program etc), however explicitly returning 'evaluationFailure' from a builtin is something that happens commonly. One simple example is a monomorphic function matching on a certain constructor and failing in all other cases: toBuiltinMeaning _ UnIData = - let unIDataDenotation :: Data -> EvaluationResult Integer + let unIDataDenotation :: Data -> BuiltinResult Integer unIDataDenotation = \case - I i -> EvaluationSuccess i - _ -> EvaluationFailure + I i -> pure i + _ -> evaluationFailure {-# INLINE unIDataDenotation #-} in makeBuiltinMeaning unIDataDenotation -The inferred type of the denotation is +The type of the denotation is - Data -> EvaluationResult Integer + Data -> BuiltinResult Integer and the Plutus type of the builtin is @@ -413,32 +428,31 @@ and the Plutus type of the builtin is because the error effect is implicit in Plutus. -Returning @EvaluationResult a@ for a type variable @a@ is also fine, i.e. it doesn't matter whether +Returning @BuiltinResult a@ for a type variable @a@ is also fine, i.e. it doesn't matter whether the denotation is monomorphic or polymorphic w.r.t. failing. But note that - 'EvaluationResult' MUST BE EXPLICITLY USED FOR ANY FAILING BUILTIN AND THROWING AN EXCEPTION + 'BuiltinResult' MUST BE EXPLICITLY USED FOR ANY FAILING BUILTIN AND THROWING AN EXCEPTION VIA 'error' OR 'throw' OR ELSE IS NOT ALLOWED AND CAN BE A HUGE VULNERABILITY. MAKE SURE THAT NONE OF THE FUNCTIONS THAT YOU USE TO DEFINE A BUILTIN THROW EXCEPTIONS -An argument of a builtin can't have 'EvaluationResult' in its type -- only the result. +An argument of a builtin can't have 'BuiltinResult' in its type -- only the result. -5. A builtin can emit log messages. For that it needs to run in the 'Emitter' monad. The ergonomics -are the same as with 'EvaluationResult': 'Emitter' can't appear in the type of an argument and -polymorphism is fine. For example: +5. A builtin can emit log messages. For that its denotation needs to run in the 'BuiltinResult' as +in case of failing. The ergonomics are the same. For example: toBuiltinMeaning _ Trace = - let traceDenotation :: Text -> a -> Emitter a + let traceDenotation :: Text -> a -> BuiltinResult a traceDenotation text a = a <$ emit text {-# INLINE traceDenotation #-} in makeBuiltinMeaning traceDenotation -The inferred type of the denotation is +The type of the denotation is - forall a. Text -> a -> Emitter a + forall a. Text -> a -> Builtin a and the Plutus type of the builtin is @@ -447,10 +461,6 @@ and the Plutus type of the builtin is because just like with the error effect, whether a function logs anything or not is not reflected in its type. -'makeBuiltinMeaning' allows one to nest 'EvaluationResult' inside of 'Emitter' and vice versa, -but as always nesting monads inside of each other without using monad transformers doesn't have good -ergonomics, since computations of such a type can't be chained with a simple @(>>=)@. - This concludes the list of simple cases. Before we jump to the hard ones, we need to talk about how polymorphism gets elaborated, so read Note [Elaboration of polymorphism] next. -} @@ -466,7 +476,7 @@ In Note [How to add a built-in function: simple cases] we defined the following ifThenElseDenotation -whose inferred Haskell type is +whose Haskell type is forall a. Bool -> a -> a -> a @@ -565,11 +575,11 @@ It's of course allowed to have multiple type variables, e.g. in the following sn constDenotation -the Haskell type of 'const' gets inferred as +the Haskell type of 'const' is forall a b. a -> b -> a -and the elaboration machinery turns that into +which the elaboration machinery turns into Opaque val Var0 -> Opaque val Var1 -> Opaque val Var0 @@ -584,28 +594,27 @@ the elaboration machinery wouldn't make a fuss about that. As a final simple example, consider toBuiltinMeaning _ Trace = - let traceDenotation :: Text -> a -> Emitter a + let traceDenotation :: Text -> a -> BuiltinResult a traceDenotation text a = a <$ emit text {-# INLINE traceDenotation #-} in makeBuiltinMeaning traceDenotation -from [How to add a built-in function: simple cases]. The inferred type of the denotation is +from [How to add a built-in function: simple cases]. The type of the denotation is - forall a. Text -> a -> Emitter a + forall a. Text -> a -> BuiltinResult a which elaborates to - Text -> Opaque val Var0 -> Emitter (Opaque val Var0) + Text -> Opaque val Var0 -> BuiltinResult (Opaque val Var0) -Elaboration machinery is able to look under 'Emitter' and 'EvaluationResult' even if there's a type -variable inside that does not appear anywhere else in the type signature, for example the inferred -type of the denotation in +Elaboration machinery is able to look under 'BuiltinResult' even if there's a type variable inside +that does not appear anywhere else in the type signature, for example the type of the denotation in toBuiltinMeaning _ ErrorPrime = - let errorPrimeDenotation :: EvaluationResult a - errorPrimeDenotation = EvaluationFailure + let errorPrimeDenotation :: BuiltinResult a + errorPrimeDenotation = evaluationFailure {-# INLINE errorPrimeDenotation #-} in makeBuiltinMeaning errorPrimeDenotation @@ -613,11 +622,11 @@ type of the denotation in is - forall a. EvaluationResult a + forall a. BuiltinResult a which gets elaborated to - EvaluationResult (Opaque val Var0) + BuiltinResult (Opaque val Var0) from which the final Plutus type of the builtin is computed: @@ -671,10 +680,10 @@ reason, wanted to have 'Opaque' in the type signature of the denotation, but sti argument as a 'Bool', we could do that: toBuiltinMeaning _ IdAssumeCheckBool = - let idAssumeCheckBoolDenotation :: Opaque val Bool -> EvaluationResult Bool + let idAssumeCheckBoolDenotation :: Opaque val Bool -> BuiltinResult Bool idAssumeCheckBoolDenotation val = asConstant val of - Right (Some (ValueOf DefaultUniBool b)) -> EvaluationSuccess b - _ -> EvaluationFailure + Right (Some (ValueOf DefaultUniBool b)) -> pure b + _ -> evaluationFailure {-# INLINE idAssumeCheckBoolDenotation #-} in makeBuiltinMeaning idAssumeCheckBoolDenotation @@ -682,7 +691,7 @@ argument as a 'Bool', we could do that: Here in the denotation we unlift the given value as a constant, check that its type tag is 'DefaultUniBool' and return the unlifted 'Bool'. If any of that fails, we return an explicit -'EvaluationFailure'. +'evaluationFailure'. This achieves almost the same as 'IdBool', which keeps all the bookkeeping behind the scenes, but there is a minor difference: in case of error its message is ignored. It would be easy to allow for @@ -708,10 +717,10 @@ wrapper around a constant. 'SomeConstant' allows one to automatically unlift an built-in function as a constant with all 'asConstant' business kept behind the scenes, for example: toBuiltinMeaning _ IdSomeConstantBool = - let idSomeConstantBoolDenotation :: SomeConstant uni Bool -> EvaluationResult Bool + let idSomeConstantBoolDenotation :: SomeConstant uni Bool -> BuiltinResult Bool idSomeConstantBoolDenotation = \case - SomeConstant (Some (ValueOf DefaultUniBool b)) -> EvaluationSuccess b - _ -> EvaluationFailure + SomeConstant (Some (ValueOf DefaultUniBool b)) -> pure b + _ -> evaluationFailure {-# INLINE idSomeConstantBoolDenotation #-} in makeBuiltinMeaning idSomeConstantBoolDenotation @@ -732,26 +741,24 @@ However it's not always possible to use automatic unlifting, see next. nullListDenotation -we'll get an error, saying that a polymorphic built-in type can't be applied to -a type variable. It's not impossible to make it work, see Note [Unlifting a -term as a value of a built-in type], but not in the general case, plus it has to -be very inefficient. +we'll get an error, saying that a polymorphic built-in type can't be applied to a type variable. +It's not impossible to make it work, see Note [Unlifting a term as a value of a built-in type], but +not in the general case, plus it has to be very inefficient. Instead we have to use 'SomeConstant' to automatically unlift the argument as a constant and then check that the value inside of it is a list (by matching on the type tag): toBuiltinMeaning _ NullList = - let nullListDenotation :: SomeConstant uni [a] -> EvaluationResult Bool + let nullListDenotation :: SomeConstant uni [a] -> BuiltinResult Bool nullListDenotation (SomeConstant (Some (ValueOf uniListA xs))) = do - DefaultUniList _ <- pure uniListA - pure $ null xs + case uniListA of + DefaultUniList _ -> pure $ null xs + _ -> throwing _StructuralUnliftingError "Expected a list but got something else" {-# INLINE nullListDenotation #-} in makeBuiltinMeaning nullListDenotation -('EvaluationResult' has a 'MonadFail' instance allowing us to use the @ <- pure @ idiom) - As before, we have to match on the type tag, because there's no relation between @rep@ from @SomeConstant uni rep@ and the constant that the built-in function actually receives at runtime (someone could generate Untyped Plutus Core directly and apply 'nullPlc' to an 'Integer' or @@ -761,10 +768,13 @@ in any way. Here's a similar built-in function: toBuiltinMeaning _ FstPair = - let fstPairDenotation :: SomeConstant uni (a, b) -> EvaluationResult (Opaque val a) + let fstPairDenotation :: SomeConstant uni (a, b) -> BuiltinResult (Opaque val a) fstPairDenotation (SomeConstant (Some (ValueOf uniPairAB xy))) = do - DefaultUniPair uniA _ <- pure uniPairAB -- [1] - pure . fromValueOf uniA $ fst xy -- [2] + case uniPairAB of + DefaultUniPair uniA _ -> -- [1] + pure . fromValueOf uniA $ fst xy -- [2] + _ -> + throwing _StructuralUnliftingError "Expected a pair but got something else" {-# INLINE fstPairDenotation #-} in makeBuiltinMeaning fstPairDenotation @@ -778,15 +788,17 @@ Note that it's fine to mix automatic unlifting for polymorphism not related to b manual unlifting for arguments having non-monomorphized polymorphic built-in types, for example: toBuiltinMeaning _ ChooseList = - let chooseListDenotation :: SomeConstant uni [a] -> b -> b -> EvaluationResult b + let chooseListDenotation :: SomeConstant uni [a] -> b -> b -> BuiltinResult b chooseListDenotation (SomeConstant (Some (ValueOf uniListA xs))) a b = do - DefaultUniList _ <- pure uniListA - pure $ case xs of - [] -> a - _ : _ -> b + case uniListA of + DefaultUniList _ -> pure $ case xs of + [] -> a + _ : _ -> b + _ -> throwing _StructuralUnliftingError "Expected a list but got something else" {-# INLINE chooseListDenotation #-} in makeBuiltinMeaning chooseListDenotation + (runCostingFunThreeArguments . paramChooseList) Here @a@ appears inside @[]@, which is a polymorphic built-in type, and so we have to use @@ -798,13 +810,17 @@ Our final example is this: toBuiltinMeaning _ MkCons = let mkConsDenotation - :: SomeConstant uni a -> SomeConstant uni [a] -> EvaluationResult (Opaque val [a]) + :: SomeConstant uni a -> SomeConstant uni [a] -> BuiltinResult (Opaque val [a]) mkConsDenotation (SomeConstant (Some (ValueOf uniA x))) (SomeConstant (Some (ValueOf uniListA xs))) = do - DefaultUniList uniA' <- pure uniListA -- [1] - Just Refl <- pure $ uniA `geq` uniA' -- [2] - pure . fromValueOf uniListA $ x : xs -- [3] + case uniListA of + DefaultUniList uniA' -> case uniA `geq` uniA' of -- [1] + Just Refl -> -- [2] + pure . fromValueOf uniListA $ x : xs -- [3] + _ -> throwing _StructuralUnliftingError + "The type of the value does not match the type of elements in the list" + _ -> throwing _StructuralUnliftingError "Expected a list but got something else" {-# INLINE mkConsDenotation #-} in makeBuiltinMeaning mkConsDenotation @@ -827,8 +843,8 @@ Plutus type of the builtin: get the (Plutus) kind of a builtin head and check two builtin heads for equality 3. Plutus type normalization tears partially or fully instantiated built-in types (such as @[Integer]@) apart and creates a Plutus type application for each Haskell type application -4. 'Emitter' and 'EvaluationResult' do not appear on the Plutus side, since the logging and failure - effects are implicit in Plutus as was discussed above +4. 'BuiltinResult' does not appear on the Plutus side, since the logging and failure effects are + implicit in Plutus as was discussed above 5. 'Opaque' and 'SomeConstant' both carry a Haskell @rep@ type argument representing some Plutus type to be used for Plutus type checking @@ -840,10 +856,10 @@ actually does. Let's look at some examples. toBuiltinMeaning _ IdIntegerAsBool = let idIntegerAsBoolDenotation - :: SomeConstant uni Integer -> EvaluationResult (SomeConstant uni Integer) + :: SomeConstant uni Integer -> BuiltinResult (SomeConstant uni Integer) idIntegerAsBoolDenotation = \case - con@(SomeConstant (Some (ValueOf DefaultUniBool _))) -> EvaluationSuccess con - _ -> EvaluationFailure + con@(SomeConstant (Some (ValueOf DefaultUniBool _))) -> pure con + _ -> evaluationFailure {-# INLINE idIntegerAsBoolDenotation #-} in makeBuiltinMeaning idIntegerAsBoolDenotation @@ -1054,7 +1070,7 @@ Finally, is representable (because we can require arguments to be constants carrying universes with them, which we can use to construct the resulting universe), but is still a lie, because instantiating that builtin with non-built-in types is possible and so the PLC type checker won't throw on such -an instantiation, which will become 'EvalutionFailure' at runtime the moment unlifting of a +an instantiation, which will become 'evalutionFailure' at runtime the moment unlifting of a non-constant is attempted when a constant is expected. So could we still get @nil@ or a safe version of @comma@ somehow? Well, we could have this @@ -1081,6 +1097,12 @@ This was investigated in https://github.com/IntersectMBO/plutus/pull/4337 but we do it quite yet, even though it worked (the Plutus Tx part wasn't implemented). -} +{- Note [Operational vs structural errors within builtins] +See the Haddock of 'EvaluationError' to understand why we sometimes use 'fail' (to throw an +"operational" evaluation error) and sometimes use @throwing _StructuralUnliftingError@ (to throw a +"structural" evaluation error). Please respect the distinction when adding new built-in functions. +-} + instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where type CostingPart uni DefaultFun = BuiltinCostModel @@ -1127,7 +1149,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where (runCostingFunTwoArguments . paramMultiplyInteger) toBuiltinMeaning _semvar DivideInteger = - let divideIntegerDenotation :: Integer -> Integer -> EvaluationResult Integer + let divideIntegerDenotation :: Integer -> Integer -> BuiltinResult Integer divideIntegerDenotation = nonZeroSecondArg div {-# INLINE divideIntegerDenotation #-} in makeBuiltinMeaning @@ -1135,7 +1157,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where (runCostingFunTwoArguments . paramDivideInteger) toBuiltinMeaning _semvar QuotientInteger = - let quotientIntegerDenotation :: Integer -> Integer -> EvaluationResult Integer + let quotientIntegerDenotation :: Integer -> Integer -> BuiltinResult Integer quotientIntegerDenotation = nonZeroSecondArg quot {-# INLINE quotientIntegerDenotation #-} in makeBuiltinMeaning @@ -1143,7 +1165,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where (runCostingFunTwoArguments . paramQuotientInteger) toBuiltinMeaning _semvar RemainderInteger = - let remainderIntegerDenotation :: Integer -> Integer -> EvaluationResult Integer + let remainderIntegerDenotation :: Integer -> Integer -> BuiltinResult Integer remainderIntegerDenotation = nonZeroSecondArg rem {-# INLINE remainderIntegerDenotation #-} in makeBuiltinMeaning @@ -1151,7 +1173,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where (runCostingFunTwoArguments . paramRemainderInteger) toBuiltinMeaning _semvar ModInteger = - let modIntegerDenotation :: Integer -> Integer -> EvaluationResult Integer + let modIntegerDenotation :: Integer -> Integer -> BuiltinResult Integer modIntegerDenotation = nonZeroSecondArg mod {-# INLINE modIntegerDenotation #-} in makeBuiltinMeaning @@ -1239,11 +1261,14 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where (runCostingFunOneArgument . paramLengthOfByteString) toBuiltinMeaning _semvar IndexByteString = - let indexByteStringDenotation :: BS.ByteString -> Int -> EvaluationResult Word8 + let indexByteStringDenotation :: BS.ByteString -> Int -> BuiltinResult Word8 indexByteStringDenotation xs n = do - -- TODO: fix this mess with @indexMaybe@ from @bytestring >= 0.11.0.0@. - guard $ n >= 0 && n < BS.length xs - EvaluationSuccess $ BS.index xs n + unless (n >= 0 && n < BS.length xs) $ + -- See Note [Operational vs structural errors within builtins]. + -- The arguments are going to be printed in the "cause" part of the error + -- message, so we don't need to repeat them here. + fail "Index out of bounds" + pure $ BS.index xs n {-# INLINE indexByteStringDenotation #-} in makeBuiltinMeaning indexByteStringDenotation @@ -1374,8 +1399,8 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where (runCostingFunOneArgument . paramEncodeUtf8) toBuiltinMeaning _semvar DecodeUtf8 = - let decodeUtf8Denotation :: BS.ByteString -> EvaluationResult Text - decodeUtf8Denotation = reoption . decodeUtf8' + let decodeUtf8Denotation :: BS.ByteString -> BuiltinResult Text + decodeUtf8Denotation = eitherToBuiltinResult . decodeUtf8' {-# INLINE decodeUtf8Denotation #-} in makeBuiltinMeaning decodeUtf8Denotation @@ -1401,7 +1426,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where -- Tracing toBuiltinMeaning _semvar Trace = - let traceDenotation :: Text -> a -> Emitter a + let traceDenotation :: Text -> a -> BuiltinResult a traceDenotation text a = a <$ emit text {-# INLINE traceDenotation #-} in makeBuiltinMeaning @@ -1410,20 +1435,26 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where -- Pairs toBuiltinMeaning _semvar FstPair = - let fstPairDenotation :: SomeConstant uni (a, b) -> EvaluationResult (Opaque val a) + let fstPairDenotation :: SomeConstant uni (a, b) -> BuiltinResult (Opaque val a) fstPairDenotation (SomeConstant (Some (ValueOf uniPairAB xy))) = do - DefaultUniPair uniA _ <- pure uniPairAB - pure . fromValueOf uniA $ fst xy + case uniPairAB of + DefaultUniPair uniA _ -> pure . fromValueOf uniA $ fst xy + _ -> + -- See Note [Operational vs structural errors within builtins]. + throwing _StructuralUnliftingError "Expected a pair but got something else" {-# INLINE fstPairDenotation #-} in makeBuiltinMeaning fstPairDenotation (runCostingFunOneArgument . paramFstPair) toBuiltinMeaning _semvar SndPair = - let sndPairDenotation :: SomeConstant uni (a, b) -> EvaluationResult (Opaque val b) + let sndPairDenotation :: SomeConstant uni (a, b) -> BuiltinResult (Opaque val b) sndPairDenotation (SomeConstant (Some (ValueOf uniPairAB xy))) = do - DefaultUniPair _ uniB <- pure uniPairAB - pure . fromValueOf uniB $ snd xy + case uniPairAB of + DefaultUniPair _ uniB -> pure . fromValueOf uniB $ snd xy + _ -> + -- See Note [Operational vs structural errors within builtins]. + throwing _StructuralUnliftingError "Expected a pair but got something else" {-# INLINE sndPairDenotation #-} in makeBuiltinMeaning sndPairDenotation @@ -1431,64 +1462,74 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where -- Lists toBuiltinMeaning _semvar ChooseList = - let chooseListDenotation :: SomeConstant uni [a] -> b -> b -> EvaluationResult b + let chooseListDenotation :: SomeConstant uni [a] -> b -> b -> BuiltinResult b chooseListDenotation (SomeConstant (Some (ValueOf uniListA xs))) a b = do - DefaultUniList _ <- pure uniListA - pure $ case xs of - [] -> a - _ : _ -> b + case uniListA of + DefaultUniList _ -> pure $ case xs of + [] -> a + _ : _ -> b + -- See Note [Operational vs structural errors within builtins]. + _ -> throwing _StructuralUnliftingError "Expected a list but got something else" {-# INLINE chooseListDenotation #-} in makeBuiltinMeaning chooseListDenotation (runCostingFunThreeArguments . paramChooseList) toBuiltinMeaning _semvar MkCons = + let mkConsDenotation - :: SomeConstant uni a -> SomeConstant uni [a] -> EvaluationResult (Opaque val [a]) + :: SomeConstant uni a -> SomeConstant uni [a] -> BuiltinResult (Opaque val [a]) mkConsDenotation (SomeConstant (Some (ValueOf uniA x))) (SomeConstant (Some (ValueOf uniListA xs))) = do - DefaultUniList uniA' <- pure uniListA - -- Checking that the type of the constant is the same as the type of the elements - -- of the unlifted list. Note that there's no way we could enforce this statically - -- since in UPLC one can create an ill-typed program that attempts to prepend - -- a value of the wrong type to a list. - -- Should that rather give us an 'UnliftingError'? For that we need - -- https://github.com/IntersectMBO/plutus/pull/3035 - Just Refl <- pure $ uniA `geq` uniA' - pure . fromValueOf uniListA $ x : xs + -- See Note [Operational vs structural errors within builtins]. + case uniListA of + DefaultUniList uniA' -> case uniA `geq` uniA' of + Just Refl -> pure . fromValueOf uniListA $ x : xs + _ -> throwing _StructuralUnliftingError + "The type of the value does not match the type of elements in the list" + _ -> throwing _StructuralUnliftingError "Expected a list but got something else" {-# INLINE mkConsDenotation #-} in makeBuiltinMeaning mkConsDenotation (runCostingFunTwoArguments . paramMkCons) toBuiltinMeaning _semvar HeadList = - let headListDenotation :: SomeConstant uni [a] -> EvaluationResult (Opaque val a) + let headListDenotation :: SomeConstant uni [a] -> BuiltinResult (Opaque val a) headListDenotation (SomeConstant (Some (ValueOf uniListA xs))) = do - DefaultUniList uniA <- pure uniListA - x : _ <- pure xs - pure $ fromValueOf uniA x + -- See Note [Operational vs structural errors within builtins]. + case uniListA of + DefaultUniList uniA -> case xs of + [] -> fail "Expected a non-empty list but got an empty one" + x : _ -> pure $ fromValueOf uniA x + _ -> throwing _StructuralUnliftingError "Expected a list but got something else" {-# INLINE headListDenotation #-} in makeBuiltinMeaning headListDenotation (runCostingFunOneArgument . paramHeadList) toBuiltinMeaning _semvar TailList = - let tailListDenotation :: SomeConstant uni [a] -> EvaluationResult (Opaque val [a]) + let tailListDenotation :: SomeConstant uni [a] -> BuiltinResult (Opaque val [a]) tailListDenotation (SomeConstant (Some (ValueOf uniListA xs))) = do - DefaultUniList _ <- pure uniListA - _ : xs' <- pure xs - pure $ fromValueOf uniListA xs' + -- See Note [Operational vs structural errors within builtins]. + case uniListA of + DefaultUniList _ -> case xs of + [] -> fail "Expected a non-empty list but got an empty one" + _ : xs' -> pure $ fromValueOf uniListA xs' + _ -> throwing _StructuralUnliftingError "Expected a list but got something else" {-# INLINE tailListDenotation #-} in makeBuiltinMeaning tailListDenotation (runCostingFunOneArgument . paramTailList) toBuiltinMeaning _semvar NullList = - let nullListDenotation :: SomeConstant uni [a] -> EvaluationResult Bool + let nullListDenotation :: SomeConstant uni [a] -> BuiltinResult Bool nullListDenotation (SomeConstant (Some (ValueOf uniListA xs))) = do - DefaultUniList _ <- pure uniListA - pure $ null xs + case uniListA of + DefaultUniList _ -> pure $ null xs + _ -> + -- See Note [Operational vs structural errors within builtins]. + throwing _StructuralUnliftingError "Expected a list but got something else" {-# INLINE nullListDenotation #-} in makeBuiltinMeaning nullListDenotation @@ -1550,50 +1591,55 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where (runCostingFunOneArgument . paramBData) toBuiltinMeaning _semvar UnConstrData = - let unConstrDataDenotation :: Data -> EvaluationResult (Integer, [Data]) + let unConstrDataDenotation :: Data -> BuiltinResult (Integer, [Data]) unConstrDataDenotation = \case - Constr i ds -> EvaluationSuccess (i, ds) - _ -> EvaluationFailure + Constr i ds -> pure (i, ds) + -- See Note [Operational vs structural errors within builtins]. + _ -> fail "Expected the Constr constructor but got a different one" {-# INLINE unConstrDataDenotation #-} in makeBuiltinMeaning unConstrDataDenotation (runCostingFunOneArgument . paramUnConstrData) toBuiltinMeaning _semvar UnMapData = - let unMapDataDenotation :: Data -> EvaluationResult [(Data, Data)] + let unMapDataDenotation :: Data -> BuiltinResult [(Data, Data)] unMapDataDenotation = \case - Map es -> EvaluationSuccess es - _ -> EvaluationFailure + Map es -> pure es + -- See Note [Operational vs structural errors within builtins]. + _ -> fail "Expected the Map constructor but got a different one" {-# INLINE unMapDataDenotation #-} in makeBuiltinMeaning unMapDataDenotation (runCostingFunOneArgument . paramUnMapData) toBuiltinMeaning _semvar UnListData = - let unListDataDenotation :: Data -> EvaluationResult [Data] + let unListDataDenotation :: Data -> BuiltinResult [Data] unListDataDenotation = \case - List ds -> EvaluationSuccess ds - _ -> EvaluationFailure + List ds -> pure ds + -- See Note [Operational vs structural errors within builtins]. + _ -> fail "Expected the List constructor but got a different one" {-# INLINE unListDataDenotation #-} in makeBuiltinMeaning unListDataDenotation (runCostingFunOneArgument . paramUnListData) toBuiltinMeaning _semvar UnIData = - let unIDataDenotation :: Data -> EvaluationResult Integer + let unIDataDenotation :: Data -> BuiltinResult Integer unIDataDenotation = \case - I i -> EvaluationSuccess i - _ -> EvaluationFailure + I i -> pure i + -- See Note [Operational vs structural errors within builtins]. + _ -> fail "Expected the I constructor but got a different one" {-# INLINE unIDataDenotation #-} in makeBuiltinMeaning unIDataDenotation (runCostingFunOneArgument . paramUnIData) toBuiltinMeaning _semvar UnBData = - let unBDataDenotation :: Data -> EvaluationResult BS.ByteString + let unBDataDenotation :: Data -> BuiltinResult BS.ByteString unBDataDenotation = \case - B b -> EvaluationSuccess b - _ -> EvaluationFailure + B b -> pure b + -- See Note [Operational vs structural errors within builtins]. + _ -> fail "Expected the B constructor but got a different one" {-# INLINE unBDataDenotation #-} in makeBuiltinMeaning unBDataDenotation @@ -1683,8 +1729,8 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where toBuiltinMeaning _semvar Bls12_381_G1_uncompress = let bls12_381_G1_uncompressDenotation - :: BS.ByteString -> Emitter (EvaluationResult BLS12_381.G1.Element) - bls12_381_G1_uncompressDenotation = eitherToEmitter . BLS12_381.G1.uncompress + :: BS.ByteString -> BuiltinResult BLS12_381.G1.Element + bls12_381_G1_uncompressDenotation = eitherToBuiltinResult . BLS12_381.G1.uncompress {-# INLINE bls12_381_G1_uncompressDenotation #-} in makeBuiltinMeaning bls12_381_G1_uncompressDenotation @@ -1692,8 +1738,8 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where toBuiltinMeaning _semvar Bls12_381_G1_hashToGroup = let bls12_381_G1_hashToGroupDenotation - :: BS.ByteString -> BS.ByteString -> Emitter (EvaluationResult BLS12_381.G1.Element) - bls12_381_G1_hashToGroupDenotation = eitherToEmitter .* BLS12_381.G1.hashToGroup + :: BS.ByteString -> BS.ByteString -> BuiltinResult BLS12_381.G1.Element + bls12_381_G1_hashToGroupDenotation = eitherToBuiltinResult .* BLS12_381.G1.hashToGroup {-# INLINE bls12_381_G1_hashToGroupDenotation #-} in makeBuiltinMeaning bls12_381_G1_hashToGroupDenotation @@ -1744,8 +1790,8 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where toBuiltinMeaning _semvar Bls12_381_G2_uncompress = let bls12_381_G2_uncompressDenotation - :: BS.ByteString -> Emitter (EvaluationResult BLS12_381.G2.Element) - bls12_381_G2_uncompressDenotation = eitherToEmitter . BLS12_381.G2.uncompress + :: BS.ByteString -> BuiltinResult BLS12_381.G2.Element + bls12_381_G2_uncompressDenotation = eitherToBuiltinResult . BLS12_381.G2.uncompress {-# INLINE bls12_381_G2_uncompressDenotation #-} in makeBuiltinMeaning bls12_381_G2_uncompressDenotation @@ -1753,8 +1799,8 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where toBuiltinMeaning _semvar Bls12_381_G2_hashToGroup = let bls12_381_G2_hashToGroupDenotation - :: BS.ByteString -> BS.ByteString -> Emitter (EvaluationResult BLS12_381.G2.Element) - bls12_381_G2_hashToGroupDenotation = eitherToEmitter .* BLS12_381.G2.hashToGroup + :: BS.ByteString -> BS.ByteString -> BuiltinResult BLS12_381.G2.Element + bls12_381_G2_hashToGroupDenotation = eitherToBuiltinResult .* BLS12_381.G2.hashToGroup {-# INLINE bls12_381_G2_hashToGroupDenotation #-} in makeBuiltinMeaning bls12_381_G2_hashToGroupDenotation diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Error.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Error.hs index 0c3e5317e70..84c29ae36da 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Error.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Error.hs @@ -30,11 +30,11 @@ import Data.Bitraversable {- | The type of errors that can occur during evaluation. There are two kinds of errors: 1. Operational ones -- these are errors that are indicative of the _logic_ of the program being - wrong. For example, 'Error' was executed, 'tailList' was applied to an empty list or evaluation + wrong. For example, 'error' was executed, 'tailList' was applied to an empty list or evaluation ran out of gas. 2. Structural ones -- these are errors that are indicative of the _structure_ of the program being - wrong. For example, a free variable was encountered during evaluation, or a non-function was - applied to an argument. + wrong. For example, a free variable was encountered during evaluation, a non-function was + applied to an argument or 'tailList' was applied to a non-list. On the chain both of these are just regular failures and we don't distinguish between them there: if a script fails, it fails, it doesn't matter what the reason was. However in the tests it does @@ -62,19 +62,23 @@ mtraverse makeClassyPrisms instance Bifunctor EvaluationError where bimap f _ (OperationalEvaluationError err) = OperationalEvaluationError $ f err bimap _ g (StructuralEvaluationError err) = StructuralEvaluationError $ g err + {-# INLINE bimap #-} instance Bifoldable EvaluationError where bifoldMap f _ (OperationalEvaluationError err) = f err bifoldMap _ g (StructuralEvaluationError err) = g err + {-# INLINE bifoldMap #-} instance Bitraversable EvaluationError where bitraverse f _ (OperationalEvaluationError err) = OperationalEvaluationError <$> f err bitraverse _ g (StructuralEvaluationError err) = StructuralEvaluationError <$> g err + {-# INLINE bitraverse #-} -- | A raw evaluation failure is always an operational error. instance AsEvaluationFailure operational => AsEvaluationFailure (EvaluationError operational structural) where _EvaluationFailure = _OperationalEvaluationError . _EvaluationFailure + {-# INLINE _EvaluationFailure #-} instance ( HasPrettyDefaults config ~ 'True diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/ErrorWithCause.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/ErrorWithCause.hs index f63f2588506..146d74d8b75 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/ErrorWithCause.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/ErrorWithCause.hs @@ -32,9 +32,11 @@ data ErrorWithCause err cause = ErrorWithCause instance Bifunctor ErrorWithCause where bimap f g (ErrorWithCause err cause) = ErrorWithCause (f err) (g <$> cause) + {-# INLINE bimap #-} instance AsEvaluationFailure err => AsEvaluationFailure (ErrorWithCause err cause) where _EvaluationFailure = iso _ewcError (flip ErrorWithCause Nothing) . _EvaluationFailure + {-# INLINE _EvaluationFailure #-} instance (Pretty err, Pretty cause) => Pretty (ErrorWithCause err cause) where pretty (ErrorWithCause e c) = pretty e <+> "caused by:" <+> pretty c @@ -63,6 +65,7 @@ throwingWithCause :: forall exc e t term m x. (exc ~ ErrorWithCause e term, MonadError exc m) => AReview e t -> t -> Maybe term -> m x throwingWithCause l t cause = reviews l (\e -> throwError $ ErrorWithCause e cause) t +{-# INLINE throwingWithCause #-} -- | "Prismatically" throw a contentless error and its (optional) cause. 'throwingWithCause_' is to -- 'throwingWithCause' as 'throwing_' is to 'throwing'. @@ -71,6 +74,7 @@ throwingWithCause_ :: forall exc e term m x. (exc ~ ErrorWithCause e term, MonadError exc m) => AReview e () -> Maybe term -> m x throwingWithCause_ l = throwingWithCause l () +{-# INLINE throwingWithCause_ #-} -- | Attach a @cause@ to a 'BuiltinError' and throw that. -- Note that an evaluator might require the cause to be computed lazily for best performance on the @@ -86,3 +90,4 @@ throwBuiltinErrorWithCause cause = \case throwingWithCause _UnliftingEvaluationError unlErr $ Just cause BuiltinEvaluationFailure -> throwingWithCause_ _EvaluationFailure $ Just cause +{-# INLINE throwBuiltinErrorWithCause #-} diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Exception.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Exception.hs index c56a4e6faed..8dfa83c1abf 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Exception.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Exception.hs @@ -72,9 +72,11 @@ mtraverse makeClassyPrisms instance structural ~ MachineError fun => AsMachineError (EvaluationError operational structural) fun where _MachineError = _StructuralEvaluationError + {-# INLINE _MachineError #-} instance AsUnliftingError (MachineError fun) where _UnliftingError = _UnliftingMachineError + {-# INLINE _UnliftingError #-} type EvaluationException operational structural = ErrorWithCause (EvaluationError operational structural) diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Result.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Result.hs index 43321ea4c88..caf61ddca47 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Result.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Result.hs @@ -74,6 +74,7 @@ instance AsEvaluationFailure (EvaluationResult a) where _EvaluationFailure = prism (const EvaluationFailure) $ \case a@EvaluationSuccess{} -> Left a EvaluationFailure -> Right () + {-# INLINE _EvaluationFailure #-} -- This and the next one are two instances that allow us to write the following: -- diff --git a/plutus-core/testlib/PlutusCore/Generators/Hedgehog/Interesting.hs b/plutus-core/testlib/PlutusCore/Generators/Hedgehog/Interesting.hs index 509f5faaa25..427279aa8da 100644 --- a/plutus-core/testlib/PlutusCore/Generators/Hedgehog/Interesting.hs +++ b/plutus-core/testlib/PlutusCore/Generators/Hedgehog/Interesting.hs @@ -233,15 +233,15 @@ genApplyAdd2 = do return . TermOf term $ iv + jv -- | Check that division by zero results in 'Error'. -genDivideByZero :: TermGen (EvaluationResult Integer) +genDivideByZero :: TermGen (BuiltinResult Integer) genDivideByZero = do op <- Gen.element [DivideInteger, QuotientInteger, ModInteger, RemainderInteger] TermOf i _ <- genTermLoose $ typeRep @Integer let term = mkIterAppNoAnn (Builtin () op) [i, mkConstant @Integer () 0] - return $ TermOf term EvaluationFailure + return $ TermOf term evaluationFailure -- | Check that division by zero results in 'Error' even if a function doesn't use that argument. -genDivideByZeroDrop :: TermGen (EvaluationResult Integer) +genDivideByZeroDrop :: TermGen (BuiltinResult Integer) genDivideByZeroDrop = do op <- Gen.element [DivideInteger, QuotientInteger, ModInteger, RemainderInteger] let typedInt = typeRep @@ -252,7 +252,7 @@ genDivideByZeroDrop = do [ mkConstant @Integer () iv , mkIterAppNoAnn (Builtin () op) [i, mkConstant @Integer () 0] ] - return $ TermOf term EvaluationFailure + return $ TermOf term evaluationFailure -- | Apply a function to all interesting generators and collect the results. fromInterestingTermGens diff --git a/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/List/headList-empty.err.golden b/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/List/headList-empty.err.golden index 71c540c808a..0f8c3121e2d 100644 --- a/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/List/headList-empty.err.golden +++ b/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/List/headList-empty.err.golden @@ -1,3 +1,5 @@ An error has occurred: The machine terminated because of an error, either from a built-in function or from an explicit use of 'error'. -Caused by: (force headList []) \ No newline at end of file +Caused by: (force headList []) +Logs were: +Expected a non-empty list but got an empty one \ No newline at end of file diff --git a/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/List/tailList-empty.err.golden b/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/List/tailList-empty.err.golden index 679ca697721..7d9ddbf5dc9 100644 --- a/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/List/tailList-empty.err.golden +++ b/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/List/tailList-empty.err.golden @@ -1,3 +1,5 @@ An error has occurred: The machine terminated because of an error, either from a built-in function or from an explicit use of 'error'. -Caused by: (force tailList []) \ No newline at end of file +Caused by: (force tailList []) +Logs were: +Expected a non-empty list but got an empty one \ No newline at end of file diff --git a/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/String/indexByteString-out-of-bounds-empty.err.golden b/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/String/indexByteString-out-of-bounds-empty.err.golden index 89c63ce9144..7bc851206a0 100644 --- a/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/String/indexByteString-out-of-bounds-empty.err.golden +++ b/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/String/indexByteString-out-of-bounds-empty.err.golden @@ -1,3 +1,5 @@ An error has occurred: The machine terminated because of an error, either from a built-in function or from an explicit use of 'error'. -Caused by: (indexByteString # 0) \ No newline at end of file +Caused by: (indexByteString # 0) +Logs were: +Index out of bounds \ No newline at end of file diff --git a/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/String/indexByteString-out-of-bounds-non-empty.err.golden b/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/String/indexByteString-out-of-bounds-non-empty.err.golden index fddc0becff1..0347aa759b2 100644 --- a/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/String/indexByteString-out-of-bounds-non-empty.err.golden +++ b/plutus-core/untyped-plutus-core/test/Evaluation/Builtins/Golden/String/indexByteString-out-of-bounds-non-empty.err.golden @@ -1,3 +1,5 @@ An error has occurred: The machine terminated because of an error, either from a built-in function or from an explicit use of 'error'. -Caused by: (indexByteString #68656c6c6f20776f726c64 12) \ No newline at end of file +Caused by: (indexByteString #68656c6c6f20776f726c64 12) +Logs were: +Index out of bounds \ No newline at end of file diff --git a/plutus-tx-plugin/test/Plugin/Basic/9.6/ifOptEval.eval.golden b/plutus-tx-plugin/test/Plugin/Basic/9.6/ifOptEval.eval.golden index 8f62cdc8e64..31522decfd6 100644 --- a/plutus-tx-plugin/test/Plugin/Basic/9.6/ifOptEval.eval.golden +++ b/plutus-tx-plugin/test/Plugin/Basic/9.6/ifOptEval.eval.golden @@ -3,4 +3,4 @@ The machine terminated because of an error, either from a built-in function or f Caused by: (divideInteger 1 0) Final budget: ({cpu: 132030 | mem: 101}) -Logs: \ No newline at end of file +Logs: Cannot divide by zero \ No newline at end of file