diff --git a/plutus-core/changelog.d/20240117_230746_effectfully_expose_BuiltinResult.md b/plutus-core/changelog.d/20240117_230746_effectfully_expose_BuiltinResult.md new file mode 100644 index 00000000000..87c62158fa6 --- /dev/null +++ b/plutus-core/changelog.d/20240117_230746_effectfully_expose_BuiltinResult.md @@ -0,0 +1,3 @@ +### Changed + +- #5728 added `BuiltinResult` and leveraged in places where we used to use `Emitter (EvaluationResult Smth)`. diff --git a/plutus-core/plutus-core.cabal b/plutus-core/plutus-core.cabal index 4076306f403..50742f31a2a 100644 --- a/plutus-core/plutus-core.cabal +++ b/plutus-core/plutus-core.cabal @@ -113,6 +113,7 @@ library PlutusCore.Default PlutusCore.Default.Builtins PlutusCore.Error + PlutusCore.Evaluation.ErrorWithCause PlutusCore.Evaluation.Machine.BuiltinCostModel PlutusCore.Evaluation.Machine.Ck PlutusCore.Evaluation.Machine.CostingFun.Core @@ -205,6 +206,7 @@ library PlutusCore.Builtin.KnownTypeAst PlutusCore.Builtin.Meaning PlutusCore.Builtin.Polymorphism + PlutusCore.Builtin.Result PlutusCore.Builtin.Runtime PlutusCore.Builtin.TestKnown PlutusCore.Builtin.TypeScheme diff --git a/plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs b/plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs index 33866402b6d..7aafc4335d5 100644 --- a/plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs +++ b/plutus-core/plutus-core/examples/PlutusCore/Examples/Builtins.hs @@ -24,7 +24,6 @@ import PlutusCore.Data import PlutusCore.Evaluation.Machine.BuiltinCostModel import PlutusCore.Evaluation.Machine.ExBudget import PlutusCore.Evaluation.Machine.ExBudgetStream -import PlutusCore.Evaluation.Machine.Exception import PlutusCore.Pretty import PlutusCore.StdLib.Data.ScottList qualified as Plc diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin.hs index ec07bb6200a..ba73d2d989d 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin.hs @@ -11,6 +11,7 @@ import PlutusCore.Builtin.KnownType as Export import PlutusCore.Builtin.KnownTypeAst as Export import PlutusCore.Builtin.Meaning as Export import PlutusCore.Builtin.Polymorphism as Export +import PlutusCore.Builtin.Result as Export import PlutusCore.Builtin.Runtime as Export import PlutusCore.Builtin.TestKnown as Export import PlutusCore.Builtin.TypeScheme as Export diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Convert.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Convert.hs index d37cba8e6ba..939d270cc88 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/Convert.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Convert.hs @@ -13,6 +13,9 @@ module PlutusCore.Builtin.Convert ( byteStringToInteger ) where +import PlutusCore.Builtin (BuiltinResult, emit) +import PlutusCore.Evaluation.Result (evaluationFailure) + import ByteString.StrictBuilder (Builder) import ByteString.StrictBuilder qualified as Builder import Control.Monad (guard) @@ -22,18 +25,15 @@ import Data.ByteString qualified as BS import Data.Text (pack) import Data.Word (Word64, Word8) import GHC.ByteOrder (ByteOrder (BigEndian, LittleEndian)) -import PlutusCore.Builtin.Emitter (Emitter, emit) -import PlutusCore.Evaluation.Result (EvaluationResult (EvaluationFailure)) -- | Wrapper for 'integerToByteString' to make it more convenient to define as a builtin. -integerToByteStringWrapper :: - Bool -> Integer -> Integer -> Emitter (EvaluationResult ByteString) +integerToByteStringWrapper :: Bool -> Integer -> Integer -> BuiltinResult ByteString integerToByteStringWrapper endiannessArg lengthArg input -- Check that we are within the Int range on the non-negative side. | lengthArg < 0 || lengthArg >= 536870912 = do emit "integerToByteString: inappropriate length argument" emit $ "Length requested: " <> (pack . show $ input) - pure EvaluationFailure + evaluationFailure -- As this builtin hasn't been costed yet, we have to impose a temporary limit of 10KiB on requested -- sizes via the padding argument. This shouldn't be necessary long-term, as once this function is -- costed, this won't be a problem. @@ -42,7 +42,7 @@ integerToByteStringWrapper endiannessArg lengthArg input | lengthArg > 10240 = do emit "integerToByteString: padding argument too large" emit "If you are seeing this, it is a bug: please report this!" - pure EvaluationFailure + evaluationFailure | otherwise = let endianness = endiannessArgToByteOrder endiannessArg in -- We use fromIntegral here, despite advice to the contrary in general when defining builtin -- denotations. This is because, if we've made it this far, we know that overflow or truncation @@ -54,15 +54,15 @@ integerToByteStringWrapper endiannessArg lengthArg input -- This does work proportional to the size of input. However, we're in a failing case -- anyway, and the user's paid for work proportional to this size in any case. emit $ "Input: " <> (pack . show $ input) - pure EvaluationFailure + evaluationFailure NotEnoughDigits -> do emit "integerToByteString: cannot represent Integer in given number of bytes" -- This does work proportional to the size of input. However, we're in a failing case -- anyway, and the user's paid for work proportional to this size in any case. emit $ "Input: " <> (pack . show $ input) emit $ "Bytes requested: " <> (pack . show $ lengthArg) - pure EvaluationFailure - Right result -> pure . pure $ result + evaluationFailure + Right result -> pure result -- | Wrapper for 'byteStringToInteger' to make it more convenient to define as a builtin. byteStringToIntegerWrapper :: @@ -82,8 +82,7 @@ data IntegerToByteStringError = -- -- For performance and clarity, the endianness argument uses -- 'ByteOrder', and the length argument is an 'Int'. -integerToByteString :: - ByteOrder -> Int -> Integer -> Either IntegerToByteStringError ByteString +integerToByteString :: ByteOrder -> Int -> Integer -> Either IntegerToByteStringError ByteString integerToByteString requestedByteOrder requestedLength input | input < 0 = Left NegativeInput | input == 0 = Right . BS.replicate requestedLength $ 0x00 diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Emitter.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Emitter.hs index 1862f79dde5..84d813d0f0f 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/Emitter.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Emitter.hs @@ -1,7 +1,7 @@ module PlutusCore.Builtin.Emitter ( Emitter (..) , runEmitter - , emit + , MonadEmitter (..) ) where import Control.Monad.Trans.Writer.Strict (Writer, runWriter, tell) @@ -17,6 +17,10 @@ runEmitter :: Emitter a -> (a, DList Text) runEmitter = runWriter . unEmitter {-# INLINE runEmitter #-} -emit :: Text -> Emitter () -emit = Emitter . tell . pure -{-# INLINE emit #-} +-- | A type class for \"this monad supports logging\". +class MonadEmitter m where + emit :: Text -> m () + +instance MonadEmitter Emitter where + emit = Emitter . tell . pure + {-# INLINE emit #-} diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/HasConstant.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/HasConstant.hs index 6d9825394a7..9442a6e7698 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/HasConstant.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/HasConstant.hs @@ -4,7 +4,7 @@ {-# LANGUAGE TypeOperators #-} module PlutusCore.Builtin.HasConstant - ( KnownTypeError (..) + ( BuiltinError (..) , throwNotAConstant , HasConstant (..) , HasConstantIn @@ -12,8 +12,8 @@ module PlutusCore.Builtin.HasConstant , fromValue ) where +import PlutusCore.Builtin.Result import PlutusCore.Core -import PlutusCore.Evaluation.Machine.Exception import PlutusCore.Name import Universe @@ -35,7 +35,7 @@ class HasConstant term where -- Switching from 'MonadError' to 'Either' here gave us a speedup of 2-4%. -- | Unwrap from a 'Constant'-like constructor throwing an 'UnliftingError' if the provided -- @term@ is not a wrapped Haskell value. - asConstant :: term -> Either KnownTypeError (Some (ValueOf (UniOf term))) + asConstant :: term -> Either BuiltinError (Some (ValueOf (UniOf term))) -- | Wrap a Haskell value as a @term@. fromConstant :: Some (ValueOf (UniOf term)) -> term diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs index aef7cfa8618..ed62aac3038 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownType.hs @@ -16,15 +16,15 @@ {-# LANGUAGE StrictData #-} module PlutusCore.Builtin.KnownType - ( KnownTypeError - , throwKnownTypeErrorWithCause + ( BuiltinError + , throwBuiltinErrorWithCause , KnownBuiltinTypeIn , KnownBuiltinType - , MakeKnownM (..) + , BuiltinResult (..) , ReadKnownM + , MakeKnownIn (..) , liftReadKnownM , readKnownConstant - , MakeKnownIn (..) , MakeKnown , ReadKnownIn (..) , ReadKnown @@ -37,17 +37,15 @@ import PlutusPrelude import PlutusCore.Builtin.Emitter import PlutusCore.Builtin.HasConstant import PlutusCore.Builtin.Polymorphism +import PlutusCore.Builtin.Result import PlutusCore.Core -import PlutusCore.Evaluation.Machine.Exception +import PlutusCore.Evaluation.ErrorWithCause import PlutusCore.Evaluation.Result import PlutusCore.Pretty -import Control.Lens.TH (makeClassyPrisms) import Control.Monad.Except -import Data.DList (DList) import Data.Either.Extras import Data.String -import Data.Text (Text) import GHC.Exts (inline, oneShot) import GHC.TypeLits import Universe @@ -67,7 +65,7 @@ It's critically important that 'readKnown' runs in the concrete 'Either' rather https://github.com/IntersectMBO/plutus/pull/4307 Replacing the @AsUnliftingError err, AsEvaluationFailure err@ constraints with the dedicated -'KnownTypeError' data type gave us a speedup of up to 4%. +'BuiltinError' data type gave us a speedup of up to 4%. All the same considerations apply to 'makeKnown': https://github.com/IntersectMBO/plutus/pull/4421 @@ -241,16 +239,16 @@ Lifting is allowed to the following classes of types: one, and for another example define an instance for 'Void' in tests -} --- | Attach a @cause@ to a 'KnownTypeError' and throw that. +-- | 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 -- happy path, hence this function must not force its first argument. -- TODO: wrap @cause@ in 'Lazy' once we have it. -throwKnownTypeErrorWithCause +throwBuiltinErrorWithCause :: (MonadError (ErrorWithCause err cause) m, AsUnliftingError err, AsEvaluationFailure err) - => cause -> KnownTypeError -> m void -throwKnownTypeErrorWithCause cause = \case - KnownTypeUnliftingError unlErr -> throwingWithCause _UnliftingError unlErr $ Just cause - KnownTypeEvaluationFailure -> throwingWithCause _EvaluationFailure () $ Just cause + => cause -> BuiltinError -> m void +throwBuiltinErrorWithCause cause = \case + BuiltinUnliftingError unlErr -> throwingWithCause _UnliftingError unlErr $ Just cause + BuiltinEvaluationFailure -> throwingWithCause _EvaluationFailure () $ Just cause typeMismatchError :: PrettyParens (SomeTypeIn uni) @@ -266,76 +264,6 @@ typeMismatchError uniExp uniAct = fromString $ concat -- failure message and evaluation is about to be shut anyway. {-# NOINLINE typeMismatchError #-} --- | The monad that 'makeKnown' runs in. --- Equivalent to @ExceptT KnownTypeError Emitter@, except optimized in two ways: --- --- 1. everything is strict --- 2. has the 'MakeKnownSuccess' constructor that is used for returning a value with no logs --- attached, which is the most common case for us, so it helps a lot not to construct and --- deconstruct a redundant tuple --- --- Moving from @ExceptT KnownTypeError Emitter@ to this data type gave us a speedup of 8% of total --- evaluation time. --- --- Logs are represented as a 'DList', because we don't particularly care about the efficiency of --- logging, since there's no logging on the chain and builtins don't emit much anyway. Otherwise --- we'd have to use @text-builder@ or @text-builder-linear@ or something of this sort. -data MakeKnownM a - = MakeKnownFailure (DList Text) KnownTypeError - | MakeKnownSuccess a - | MakeKnownSuccessWithLogs (DList Text) a - -makeClassyPrisms ''MakeKnownM - -instance AsEvaluationFailure (MakeKnownM a) where - _EvaluationFailure = _MakeKnownFailure . _EvaluationFailureVia (pure KnownTypeEvaluationFailure) - {-# INLINE _EvaluationFailure #-} - --- | Prepend logs to a 'MakeKnownM' computation. -withLogs :: DList Text -> MakeKnownM a -> MakeKnownM a -withLogs logs1 = \case - MakeKnownFailure logs2 err -> MakeKnownFailure (logs1 <> logs2) err - MakeKnownSuccess x -> MakeKnownSuccessWithLogs logs1 x - MakeKnownSuccessWithLogs logs2 x -> MakeKnownSuccessWithLogs (logs1 <> logs2) x -{-# INLINE withLogs #-} - -instance Functor MakeKnownM where - fmap _ (MakeKnownFailure logs err) = MakeKnownFailure logs err - fmap f (MakeKnownSuccess x) = MakeKnownSuccess (f x) - fmap f (MakeKnownSuccessWithLogs logs x) = MakeKnownSuccessWithLogs logs (f x) - {-# INLINE fmap #-} - - -- Written out explicitly just in case (see @fmap@ above for what the case might be). - _ <$ MakeKnownFailure logs err = MakeKnownFailure logs err - x <$ MakeKnownSuccess _ = MakeKnownSuccess x - x <$ MakeKnownSuccessWithLogs logs _ = MakeKnownSuccessWithLogs logs x - {-# INLINE (<$) #-} - -instance Applicative MakeKnownM where - pure = MakeKnownSuccess - {-# INLINE pure #-} - - MakeKnownFailure logs err <*> _ = MakeKnownFailure logs err - MakeKnownSuccess f <*> a = fmap f a - MakeKnownSuccessWithLogs logs f <*> a = withLogs logs $ fmap f a - {-# INLINE (<*>) #-} - - -- Better than the default implementation, because the value in the 'MakeKnownSuccess' case - -- doesn't need to be retained. - MakeKnownFailure logs err *> _ = MakeKnownFailure logs err - MakeKnownSuccess _ *> a = a - MakeKnownSuccessWithLogs logs _ *> a = withLogs logs a - {-# INLINE (*>) #-} - -instance Monad MakeKnownM where - MakeKnownFailure logs err >>= _ = MakeKnownFailure logs err - MakeKnownSuccess x >>= f = f x - MakeKnownSuccessWithLogs logs x >>= f = withLogs logs $ f x - {-# INLINE (>>=) #-} - - (>>) = (*>) - {-# INLINE (>>) #-} - -- 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 -- make 'ReadKnownM' a type synonym for convenience: that way we don't need to derive all the @@ -343,12 +271,12 @@ instance Monad MakeKnownM where -- user code), which can be non-trivial for such performance-sensitive code (see e.g. 'coerceVia' -- and 'coerceArg') and there is no abstraction barrier anyway. -- | The monad that 'readKnown' runs in. -type ReadKnownM = Either KnownTypeError +type ReadKnownM = Either BuiltinError --- | Lift a 'ReadKnownM' computation into 'MakeKnownM'. -liftReadKnownM :: ReadKnownM a -> MakeKnownM a -liftReadKnownM (Left err) = MakeKnownFailure mempty err -liftReadKnownM (Right x) = MakeKnownSuccess x +-- | Lift a 'ReadKnownM' computation into 'BuiltinResult'. +liftReadKnownM :: ReadKnownM a -> BuiltinResult a +liftReadKnownM (Left err) = BuiltinFailure mempty err +liftReadKnownM (Right x) = BuiltinSuccess x {-# INLINE liftReadKnownM #-} -- See Note [Unlifting values of built-in types]. @@ -363,15 +291,15 @@ readKnownConstant val = asConstant val >>= oneShot \case -- optimize some of the matching away. case uniExp `geq` uniAct of Just Refl -> pure x - Nothing -> Left . KnownTypeUnliftingError $ typeMismatchError uniExp uniAct + Nothing -> throwing _UnliftingError $ typeMismatchError uniExp uniAct {-# INLINE readKnownConstant #-} -- See Note [Performance of ReadKnownIn and MakeKnownIn instances]. class uni ~ UniOf val => MakeKnownIn uni val a where -- | Convert a Haskell value to the corresponding PLC value. -- The inverse of 'readKnown'. - makeKnown :: a -> MakeKnownM val - default makeKnown :: KnownBuiltinType val a => a -> MakeKnownM val + makeKnown :: a -> BuiltinResult val + default makeKnown :: KnownBuiltinType val a => a -> BuiltinResult val -- Everything on evaluation path has to be strict in production, so in theory we don't need to -- force anything here. In practice however all kinds of weird things happen in tests and @val@ -- can be non-strict enough to cause trouble here, so we're forcing the argument. Looking at the @@ -400,9 +328,9 @@ type ReadKnown val = ReadKnownIn (UniOf val) val -- | Same as 'makeKnown', but allows for neither emitting nor storing the cause of a failure. makeKnownOrFail :: MakeKnownIn uni val a => a -> EvaluationResult val makeKnownOrFail x = case makeKnown x of - MakeKnownFailure _ _ -> EvaluationFailure - MakeKnownSuccess val -> EvaluationSuccess val - MakeKnownSuccessWithLogs _ val -> EvaluationSuccess val + BuiltinFailure _ _ -> EvaluationFailure + BuiltinSuccess val -> EvaluationSuccess val + BuiltinSuccessWithLogs _ val -> EvaluationSuccess val {-# INLINE makeKnownOrFail #-} -- | Same as 'readKnown', but the cause of a potential failure is the provided term itself. @@ -411,7 +339,7 @@ readKnownSelf , AsUnliftingError err, AsEvaluationFailure err ) => val -> Either (ErrorWithCause err val) a -readKnownSelf val = fromRightM (throwKnownTypeErrorWithCause val) $ readKnown val +readKnownSelf val = fromRightM (throwBuiltinErrorWithCause val) $ readKnown val {-# INLINE readKnownSelf #-} instance MakeKnownIn uni val a => MakeKnownIn uni val (EvaluationResult a) where @@ -419,6 +347,10 @@ instance MakeKnownIn uni val a => MakeKnownIn uni val (EvaluationResult a) where makeKnown (EvaluationSuccess x) = makeKnown x {-# INLINE makeKnown #-} +instance MakeKnownIn uni val a => MakeKnownIn uni val (BuiltinResult a) where + makeKnown res = res >>= makeKnown + {-# INLINE makeKnown #-} + -- Catching 'EvaluationFailure' here would allow *not* to short-circuit when 'readKnown' fails -- to read a Haskell value of type @a@. Instead, in the denotation of the builtin function -- the programmer would be given an explicit 'EvaluationResult' value to handle, which means diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownTypeAst.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownTypeAst.hs index 8de9d3c78bc..fb1f47cde87 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownTypeAst.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/KnownTypeAst.hs @@ -33,6 +33,7 @@ module PlutusCore.Builtin.KnownTypeAst import PlutusCore.Builtin.Emitter import PlutusCore.Builtin.KnownKind import PlutusCore.Builtin.Polymorphism +import PlutusCore.Builtin.Result import PlutusCore.Core import PlutusCore.Evaluation.Result import PlutusCore.Name @@ -223,6 +224,13 @@ instance KnownTypeAst tyname uni a => KnownTypeAst tyname uni (EvaluationResult toTypeAst _ = toTypeAst $ Proxy @a {-# INLINE toTypeAst #-} +instance KnownTypeAst tyname uni a => KnownTypeAst tyname uni (BuiltinResult a) where + type IsBuiltin _ (BuiltinResult a) = 'False + type ToHoles _ (BuiltinResult a) = '[TypeHole a] + type ToBinds uni acc (BuiltinResult a) = ToBinds uni acc a + toTypeAst _ = toTypeAst $ Proxy @a + {-# INLINE toTypeAst #-} + instance KnownTypeAst tyname uni a => KnownTypeAst tyname uni (Emitter a) where type IsBuiltin _ (Emitter a) = 'False type ToHoles _ (Emitter a) = '[TypeHole a] diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs index 597c2e00c01..aed5d9f618c 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Meaning.hs @@ -229,24 +229,24 @@ instance (Typeable res, KnownTypeAst TyName (UniOf val) res, MakeKnown val res) KnownMonotype val '[] res where knownMonotype = TypeSchemeResult - -- We need to lift the 'ReadKnownM' action into 'MakeKnownM', + -- We need to lift the 'ReadKnownM' action into 'BuiltinResult', -- hence 'liftReadKnownM'. toMonoF = either -- Unlifting has failed and we don't care about costing at this point, since we're about -- to terminate evaluation anyway, hence we put 'mempty' as the cost of the operation. -- - -- Note that putting the cost inside of 'MakeKnownM' is not an option, since forcing - -- the 'MakeKnownM' computation is exactly forcing the builtin application, which we + -- Note that putting the cost inside of 'BuiltinResult' is not an option, since forcing + -- the 'BuiltinResult' computation is exactly forcing the builtin application, which we -- can't do before accounting for the cost of the application, i.e. the cost must be - -- outside of 'MakeKnownM'. + -- outside of 'BuiltinResult'. -- - -- We could introduce a level of indirection and say that a 'BuiltinResult' is either - -- a budgeting failure or a budgeting success with a cost and a 'MakeKnownM' computation - -- inside, but that would slow things down a bit and the current strategy is + -- We could introduce a level of indirection and say that a 'BuiltinCostedResult' is + -- 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. - (BuiltinResult (ExBudgetLast mempty) . MakeKnownFailure mempty) - (\(x, cost) -> BuiltinResult cost $ makeKnown x) + (BuiltinCostedResult (ExBudgetLast mempty) . BuiltinFailure mempty) + (\(x, cost) -> BuiltinCostedResult cost $ makeKnown x) {-# INLINE toMonoF #-} {- Note [One-shotting runtime denotations] diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Result.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Result.hs new file mode 100644 index 00000000000..771cc66ed4c --- /dev/null +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Result.hs @@ -0,0 +1,155 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} + +module PlutusCore.Builtin.Result + ( UnliftingError (..) + , BuiltinError (..) + , BuiltinResult (..) + , AsUnliftingError (..) + , AsBuiltinError (..) + , AsBuiltinResult (..) + , throwNotAConstant + , withLogs + , throwing + , throwing_ + ) where + +import PlutusPrelude + +import PlutusCore.Builtin.Emitter +import PlutusCore.Evaluation.Result + +import Control.Lens +import Control.Monad.Error.Lens (throwing, throwing_) +import Control.Monad.Except +import Data.DList (DList) +import Data.String (IsString) +import Data.Text (Text) +import Prettyprinter + +-- | When unlifting of a PLC term into a Haskell value fails, this error is thrown. +newtype UnliftingError = MkUnliftingError + { unUnliftingError :: Text + } deriving stock (Show, Eq) + deriving newtype (IsString, Semigroup, NFData) + +-- | The type of errors that 'readKnown' and 'makeKnown' can return. +data BuiltinError + = BuiltinUnliftingError !UnliftingError + | BuiltinEvaluationFailure + deriving stock (Show, Eq) + +-- | The monad that 'makeKnown' runs in. +-- Equivalent to @ExceptT BuiltinError Emitter@, except optimized in two ways: +-- +-- 1. everything is strict +-- 2. has the 'BuiltinSuccess' constructor that is used for returning a value with no logs +-- attached, which is the most common case for us, so it helps a lot not to construct and +-- deconstruct a redundant tuple +-- +-- Moving from @ExceptT BuiltinError Emitter@ to this data type gave us a speedup of 8% of total +-- evaluation time. +-- +-- Logs are represented as a 'DList', because we don't particularly care about the efficiency of +-- logging, since there's no logging on the chain and builtins don't emit much anyway. Otherwise +-- we'd have to use @text-builder@ or @text-builder-linear@ or something of this sort. +data BuiltinResult a + = BuiltinFailure (DList Text) BuiltinError + | BuiltinSuccess a + | BuiltinSuccessWithLogs (DList Text) a + deriving stock (Show, Foldable) + +mtraverse makeClassyPrisms + [ ''UnliftingError + , ''BuiltinError + , ''BuiltinResult + ] + +instance AsUnliftingError BuiltinError where + _UnliftingError = _BuiltinUnliftingError + {-# INLINE _UnliftingError #-} + +instance AsEvaluationFailure BuiltinError where + _EvaluationFailure = _EvaluationFailureVia BuiltinEvaluationFailure + {-# INLINE _EvaluationFailure #-} + +-- >>> evaluationFailure :: BuiltinResult Bool +-- BuiltinFailure (fromList []) BuiltinEvaluationFailure +-- +-- >>> import Control.Lens +-- >>> let res = BuiltinFailure (pure mempty) evaluationFailure :: BuiltinResult Bool +-- >>> matching _EvaluationFailure res +-- Right () +-- +-- >>> matching _BuiltinFailure $ BuiltinSuccess True +-- Left (BuiltinSuccess True) +instance AsEvaluationFailure (BuiltinResult a) where + _EvaluationFailure = _BuiltinFailure . prism (\_ -> pure evaluationFailure) (\_ -> Right ()) + {-# INLINE _EvaluationFailure #-} + +instance MonadEmitter BuiltinResult where + emit txt = BuiltinSuccessWithLogs (pure txt) () + {-# INLINE emit #-} + +instance Pretty UnliftingError where + pretty (MkUnliftingError err) = fold + [ "Could not unlift a value:", hardline + , pretty err + ] + +instance Pretty BuiltinError where + pretty (BuiltinUnliftingError err) = "Builtin evaluation failure:" <+> pretty err + pretty BuiltinEvaluationFailure = "Builtin evaluation failure" + +throwNotAConstant :: MonadError BuiltinError m => m void +throwNotAConstant = throwError $ BuiltinUnliftingError "Not a constant" +{-# INLINE throwNotAConstant #-} + +-- | Prepend logs to a 'BuiltinResult' computation. +withLogs :: DList Text -> BuiltinResult a -> BuiltinResult a +withLogs logs1 = \case + BuiltinFailure logs2 err -> BuiltinFailure (logs1 <> logs2) err + BuiltinSuccess x -> BuiltinSuccessWithLogs logs1 x + BuiltinSuccessWithLogs logs2 x -> BuiltinSuccessWithLogs (logs1 <> logs2) x +{-# INLINE withLogs #-} + +instance Functor BuiltinResult where + fmap _ (BuiltinFailure logs err) = BuiltinFailure logs err + fmap f (BuiltinSuccess x) = BuiltinSuccess (f x) + fmap f (BuiltinSuccessWithLogs logs x) = BuiltinSuccessWithLogs logs (f x) + {-# INLINE fmap #-} + + -- Written out explicitly just in case. + _ <$ BuiltinFailure logs err = BuiltinFailure logs err + x <$ BuiltinSuccess _ = BuiltinSuccess x + x <$ BuiltinSuccessWithLogs logs _ = BuiltinSuccessWithLogs logs x + {-# INLINE (<$) #-} + +instance Applicative BuiltinResult where + pure = BuiltinSuccess + {-# INLINE pure #-} + + BuiltinFailure logs err <*> _ = BuiltinFailure logs err + BuiltinSuccess f <*> a = fmap f a + BuiltinSuccessWithLogs logs f <*> a = withLogs logs $ fmap f a + {-# INLINE (<*>) #-} + + -- Better than the default implementation, because the value in the 'BuiltinSuccess' case + -- doesn't need to be retained. + BuiltinFailure logs err *> _ = BuiltinFailure logs err + BuiltinSuccess _ *> b = b + BuiltinSuccessWithLogs logs _ *> b = withLogs logs b + {-# INLINE (*>) #-} + +instance Monad BuiltinResult where + BuiltinFailure logs err >>= _ = BuiltinFailure logs err + BuiltinSuccess x >>= f = f x + BuiltinSuccessWithLogs logs x >>= f = withLogs logs $ f x + {-# INLINE (>>=) #-} + + (>>) = (*>) + {-# INLINE (>>) #-} diff --git a/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs b/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs index 2ee12fe1084..08f647a2919 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Builtin/Runtime.hs @@ -18,18 +18,18 @@ import NoThunks.Class -- Applying or type-instantiating a builtin peels off the corresponding constructor from its -- 'BuiltinRuntime'. -- --- 'BuiltinResult' contains the cost (an 'ExBudget') and the result (a @MakeKnownM val@) of the --- builtin application. The cost is stored strictly, since the evaluator is going to look at it +-- 'BuiltinCostedResult' contains the cost (an 'ExBudget') and the result (a @BuiltinResult val@) of +-- the builtin application. The cost is stored strictly, since the evaluator is going to look at it -- and the result is stored lazily, since it's not supposed to be forced before accounting for the --- cost of the application. If the cost exceeds the available budget, the evaluator discards the --- the result of the builtin application without ever forcing it and terminates with evaluation +-- cost of the application. If the cost exceeds the available budget, the evaluator discards the the +-- result of the builtin application without ever forcing it and terminates with evaluation -- failure. Allowing the user to compute something that they don't have the budget for would be a -- major bug. -- -- Evaluators that ignore the entire concept of costing (e.g. the CK machine) may of course force -- the result of the builtin application unconditionally. data BuiltinRuntime val - = BuiltinResult ExBudgetStream ~(MakeKnownM val) + = BuiltinCostedResult ExBudgetStream ~(BuiltinResult val) | BuiltinExpectArgument (val -> BuiltinRuntime val) | BuiltinExpectForce (BuiltinRuntime val) @@ -37,11 +37,11 @@ instance NoThunks (BuiltinRuntime val) where wNoThunks ctx = \case -- Unreachable, because we don't allow nullary builtins and the 'BuiltinArrow' case only -- checks for WHNF without recursing. Hence we can throw if we reach this clause somehow. - BuiltinResult _ _ -> pure . Just $ ThunkInfo ctx + BuiltinCostedResult _ _ -> pure . Just $ ThunkInfo ctx -- This one doesn't do much. It only checks that the function stored in the 'BuiltinArrow' -- is in WHNF. The function may contain thunks inside of it. Not sure if it's possible to do - -- better, since the final 'BuiltinResult' contains a thunk for the result of the builtin - -- application anyway. + -- better, since the final 'BuiltinCostedResult' contains a thunk for the result of the + -- builtin application anyway. BuiltinExpectArgument f -> noThunks ctx f BuiltinExpectForce runtime -> noThunks ctx runtime diff --git a/plutus-core/plutus-core/src/PlutusCore/Crypto/Ed25519.hs b/plutus-core/plutus-core/src/PlutusCore/Crypto/Ed25519.hs index e50b48378a4..cf7a513a5c1 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Crypto/Ed25519.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Crypto/Ed25519.hs @@ -6,6 +6,7 @@ module PlutusCore.Crypto.Ed25519 ( verifyEd25519Signature_V2 ) where +import PlutusCore.Builtin.KnownType (BuiltinResult) import PlutusCore.Crypto.Utils import Cardano.Crypto.DSIGN.Class qualified as DSIGN @@ -14,8 +15,6 @@ import Crypto.ECC.Ed25519Donna (publicKey, signature, verify) import Crypto.Error (CryptoFailable (..)) import Data.ByteString qualified as BS import Data.Text (Text, pack) -import PlutusCore.Builtin.Emitter (Emitter) -import PlutusCore.Evaluation.Result (EvaluationResult) -- | Ed25519 signature verification -- This will fail if the key or the signature are not of the expected length. @@ -24,13 +23,13 @@ verifyEd25519Signature_V1 :: BS.ByteString -- ^ Public Key (32 bytes) -> BS.ByteString -- ^ Message (arbitrary length) -> BS.ByteString -- ^ Signature (64 bytes) - -> Emitter (EvaluationResult Bool) + -> BuiltinResult Bool verifyEd25519Signature_V1 pubKey msg sig = case verify <$> publicKey pubKey <*> pure msg <*> signature sig - of CryptoPassed r -> pure $ pure r + of CryptoPassed r -> pure r CryptoFailed err -> failWithMessage loc $ pack (show err) where loc :: Text @@ -44,14 +43,14 @@ verifyEd25519Signature_V2 :: BS.ByteString -- ^ Public Key (32 bytes) -> BS.ByteString -- ^ Message (arbitrary length) -> BS.ByteString -- ^ Signature (64 bytes) - -> Emitter (EvaluationResult Bool) + -> BuiltinResult Bool verifyEd25519Signature_V2 pk msg sig = case DSIGN.rawDeserialiseVerKeyDSIGN @Ed25519DSIGN pk of Nothing -> failWithMessage loc "Invalid verification key." Just pk' -> case DSIGN.rawDeserialiseSigDSIGN @Ed25519DSIGN sig of Nothing -> failWithMessage loc "Invalid signature." Just sig' -> - pure . pure $ + pure $ case DSIGN.verifyDSIGN () pk' msg sig' of Left _ -> False Right () -> True diff --git a/plutus-core/plutus-core/src/PlutusCore/Crypto/Secp256k1.hs b/plutus-core/plutus-core/src/PlutusCore/Crypto/Secp256k1.hs index ef16f3ae123..1a73ec5af5c 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Crypto/Secp256k1.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Crypto/Secp256k1.hs @@ -6,6 +6,7 @@ module PlutusCore.Crypto.Secp256k1 ( verifySchnorrSecp256k1Signature ) where +import PlutusCore.Builtin.Result import PlutusCore.Crypto.Utils import Cardano.Crypto.DSIGN.Class qualified as DSIGN @@ -13,8 +14,6 @@ import Cardano.Crypto.DSIGN.EcdsaSecp256k1 (EcdsaSecp256k1DSIGN, toMessageHash) import Cardano.Crypto.DSIGN.SchnorrSecp256k1 (SchnorrSecp256k1DSIGN) import Data.ByteString qualified as BS import Data.Text (Text) -import PlutusCore.Builtin.Emitter (Emitter) -import PlutusCore.Evaluation.Result (EvaluationResult) -- | Verify an ECDSA signature made using the SECP256k1 curve. -- @@ -42,7 +41,7 @@ verifyEcdsaSecp256k1Signature :: BS.ByteString -- ^ Public key (33 bytes) -> BS.ByteString -- ^ Message hash (32 bytes) -> BS.ByteString -- ^ Signature (64 bytes) - -> Emitter (EvaluationResult Bool) + -> BuiltinResult Bool verifyEcdsaSecp256k1Signature pk msg sig = case DSIGN.rawDeserialiseVerKeyDSIGN @EcdsaSecp256k1DSIGN pk of Nothing -> failWithMessage loc "Invalid verification key." @@ -50,7 +49,7 @@ verifyEcdsaSecp256k1Signature pk msg sig = Nothing -> failWithMessage loc "Invalid signature." Just sig' -> case toMessageHash msg of Nothing -> failWithMessage loc "Invalid message hash." - Just msg' -> pure . pure $ case DSIGN.verifyDSIGN () pk' msg' sig' of + Just msg' -> pure $ case DSIGN.verifyDSIGN () pk' msg' sig' of Left _ -> False Right () -> True where @@ -78,13 +77,13 @@ verifySchnorrSecp256k1Signature :: BS.ByteString -- ^ Public key (32 bytes) -> BS.ByteString -- ^ Message (arbitrary length) -> BS.ByteString -- ^ Signature (64 bytes) - -> Emitter (EvaluationResult Bool) + -> BuiltinResult Bool verifySchnorrSecp256k1Signature pk msg sig = case DSIGN.rawDeserialiseVerKeyDSIGN @SchnorrSecp256k1DSIGN pk of Nothing -> failWithMessage loc "Invalid verification key." Just pk' -> case DSIGN.rawDeserialiseSigDSIGN @SchnorrSecp256k1DSIGN sig of Nothing -> failWithMessage loc "Invalid signature." - Just sig' -> pure . pure $ case DSIGN.verifyDSIGN () pk' msg sig' of + Just sig' -> pure $ case DSIGN.verifyDSIGN () pk' msg sig' of Left _ -> False Right () -> True where diff --git a/plutus-core/plutus-core/src/PlutusCore/Crypto/Utils.hs b/plutus-core/plutus-core/src/PlutusCore/Crypto/Utils.hs index d8ebf8ec11b..a67300c0310 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Crypto/Utils.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Crypto/Utils.hs @@ -1,27 +1,26 @@ {-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-} -module PlutusCore.Crypto.Utils (failWithMessage, byteStringAsHex) -where +module PlutusCore.Crypto.Utils (failWithMessage, byteStringAsHex) where + +import PlutusCore.Builtin.Emitter (emit) +import PlutusCore.Builtin.Result (BuiltinResult) +import PlutusCore.Evaluation.Result (evaluationFailure) import Data.ByteString (ByteString, foldr') import Data.Kind (Type) import Data.Text (Text) import Text.Printf (printf) -import PlutusCore.Builtin.Emitter (Emitter, emit) -import PlutusCore.Evaluation.Result (EvaluationResult (EvaluationFailure)) - -- 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 -> Emitter (EvaluationResult a) +failWithMessage :: forall (a :: Type). Text -> Text -> BuiltinResult a failWithMessage location reason = do emit $ location <> ": " <> reason - pure EvaluationFailure + evaluationFailure byteStringAsHex :: ByteString -> String byteStringAsHex bs = "0x" ++ (Prelude.concat $ foldr' (\w s -> (printf "%02x" w):s) [] bs) diff --git a/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs b/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs index 2891e233da4..8fd9daf8dec 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs @@ -1286,10 +1286,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where toBuiltinMeaning semvar VerifyEd25519Signature = let verifyEd25519SignatureDenotation - :: BS.ByteString - -> BS.ByteString - -> BS.ByteString - -> Emitter (EvaluationResult Bool) + :: BS.ByteString -> BS.ByteString -> BS.ByteString -> BuiltinResult Bool verifyEd25519SignatureDenotation = case semvar of DefaultFunSemanticsVariant1 -> verifyEd25519Signature_V1 @@ -1320,10 +1317,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where toBuiltinMeaning _semvar VerifyEcdsaSecp256k1Signature = let verifyEcdsaSecp256k1SignatureDenotation - :: BS.ByteString - -> BS.ByteString - -> BS.ByteString - -> Emitter (EvaluationResult Bool) + :: BS.ByteString -> BS.ByteString -> BS.ByteString -> BuiltinResult Bool verifyEcdsaSecp256k1SignatureDenotation = verifyEcdsaSecp256k1Signature {-# INLINE verifyEcdsaSecp256k1SignatureDenotation #-} in makeBuiltinMeaning @@ -1332,10 +1326,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where toBuiltinMeaning _semvar VerifySchnorrSecp256k1Signature = let verifySchnorrSecp256k1SignatureDenotation - :: BS.ByteString - -> BS.ByteString - -> BS.ByteString - -> Emitter (EvaluationResult Bool) + :: BS.ByteString -> BS.ByteString -> BS.ByteString -> BuiltinResult Bool verifySchnorrSecp256k1SignatureDenotation = verifySchnorrSecp256k1Signature {-# INLINE verifySchnorrSecp256k1SignatureDenotation #-} in makeBuiltinMeaning @@ -1810,7 +1801,7 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where -- Conversions toBuiltinMeaning _semvar IntegerToByteString = - let integerToByteStringDenotation :: Bool -> Integer -> Integer -> Emitter (EvaluationResult BS.ByteString) + let integerToByteStringDenotation :: Bool -> Integer -> Integer -> BuiltinResult BS.ByteString integerToByteStringDenotation = integerToByteStringWrapper in makeBuiltinMeaning integerToByteStringDenotation diff --git a/plutus-core/plutus-core/src/PlutusCore/Default/Universe.hs b/plutus-core/plutus-core/src/PlutusCore/Default/Universe.hs index aab47cac94f..ad1b0fbbd1e 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Default/Universe.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Default/Universe.hs @@ -49,7 +49,6 @@ import PlutusCore.Crypto.BLS12_381.G1 qualified as BLS12_381.G1 import PlutusCore.Crypto.BLS12_381.G2 qualified as BLS12_381.G2 import PlutusCore.Crypto.BLS12_381.Pairing qualified as BLS12_381.Pairing import PlutusCore.Data -import PlutusCore.Evaluation.Machine.Exception import PlutusCore.Evaluation.Result import PlutusCore.Pretty.Extra diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/ErrorWithCause.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/ErrorWithCause.hs new file mode 100644 index 00000000000..15d3123338e --- /dev/null +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/ErrorWithCause.hs @@ -0,0 +1,58 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} + +module PlutusCore.Evaluation.ErrorWithCause + ( ErrorWithCause (..) + , throwingWithCause + ) where + +import PlutusPrelude + +import PlutusCore.Evaluation.Result +import PlutusCore.Pretty + +import Control.Lens +import Control.Monad.Except +import Prettyprinter + +-- | An error and (optionally) what caused it. +data ErrorWithCause err cause = ErrorWithCause + { _ewcError :: !err + , _ewcCause :: !(Maybe cause) + } deriving stock (Eq, Functor, Foldable, Traversable, Generic) + deriving anyclass (NFData) + +instance Bifunctor ErrorWithCause where + bimap f g (ErrorWithCause err cause) = ErrorWithCause (f err) (g <$> cause) + +instance AsEvaluationFailure err => AsEvaluationFailure (ErrorWithCause err cause) where + _EvaluationFailure = iso _ewcError (flip ErrorWithCause Nothing) . _EvaluationFailure + +instance (Pretty err, Pretty cause) => Pretty (ErrorWithCause err cause) where + pretty (ErrorWithCause e c) = pretty e <+> "caused by:" <+> pretty c + +instance (PrettyBy config cause, PrettyBy config err) => + PrettyBy config (ErrorWithCause err cause) where + prettyBy config (ErrorWithCause err mayCause) = + "An error has occurred: " <+> prettyBy config err <> + case mayCause of + Nothing -> mempty + Just cause -> hardline <> "Caused by:" <+> prettyBy config cause + +instance (PrettyPlc cause, PrettyPlc err) => + Show (ErrorWithCause err cause) where + show = render . prettyPlcReadableDebug + +deriving anyclass instance (PrettyPlc cause, PrettyPlc err, Typeable cause, Typeable err) => + Exception (ErrorWithCause err cause) + +-- | "Prismatically" throw an error and its (optional) cause. +throwingWithCause + -- Binds @exc@ so it can be used as a convenient parameter with @TypeApplications@. + :: 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 diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Ck.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Ck.hs index 7b33ee7e898..847f7e731cf 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Ck.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Ck.hs @@ -74,10 +74,10 @@ evalBuiltinApp -> BuiltinRuntime (CkValue uni fun) -> CkM uni fun s (CkValue uni fun) evalBuiltinApp term runtime = case runtime of - BuiltinResult _ getX -> case getX of - MakeKnownFailure logs err -> emitCkM logs *> throwKnownTypeErrorWithCause term err - MakeKnownSuccess x -> pure x - MakeKnownSuccessWithLogs logs x -> emitCkM logs $> x + BuiltinCostedResult _ getX -> case getX of + BuiltinFailure logs err -> emitCkM logs *> throwBuiltinErrorWithCause term err + BuiltinSuccess x -> pure x + BuiltinSuccessWithLogs logs x -> emitCkM logs $> x _ -> pure $ VBuiltin term runtime ckValueToTerm :: CkValue uni fun -> Term TyName Name uni fun () 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 521a4cc7103..2ffd78de907 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Exception.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/Exception.hs @@ -17,7 +17,7 @@ module PlutusCore.Evaluation.Machine.Exception ( UnliftingError (..) , AsUnliftingError (..) - , KnownTypeError (..) + , BuiltinError (..) , MachineError (..) , AsMachineError (..) , EvaluationError (..) @@ -34,30 +34,16 @@ module PlutusCore.Evaluation.Machine.Exception import PlutusPrelude +import PlutusCore.Builtin.Result +import PlutusCore.Evaluation.ErrorWithCause import PlutusCore.Evaluation.Result import PlutusCore.Pretty import Control.Lens -import Control.Monad.Error.Lens (throwing, throwing_) -import Control.Monad.Except import Data.Either.Extras -import Data.String (IsString) -import Data.Text (Text) import Data.Word (Word64) import Prettyprinter --- | When unlifting of a PLC term into a Haskell value fails, this error is thrown. -newtype UnliftingError - = UnliftingErrorE Text - deriving stock (Show, Eq) - deriving newtype (IsString, Semigroup, NFData) - --- | The type of errors that 'readKnown' and 'makeKnown' can return. -data KnownTypeError - = KnownTypeUnliftingError !UnliftingError - | KnownTypeEvaluationFailure - deriving stock (Eq) - -- | Errors which can occur during a run of an abstract machine. data MachineError fun = NonPolymorphicInstantiationMachineError @@ -91,17 +77,10 @@ data EvaluationError user internal deriving anyclass (NFData) mtraverse makeClassyPrisms - [ ''UnliftingError - , ''KnownTypeError - , ''MachineError + [ ''MachineError , ''EvaluationError ] -instance AsUnliftingError KnownTypeError where - _UnliftingError = _KnownTypeUnliftingError -instance AsEvaluationFailure KnownTypeError where - _EvaluationFailure = _EvaluationFailureVia KnownTypeEvaluationFailure - instance internal ~ MachineError fun => AsMachineError (EvaluationError user internal) fun where _MachineError = _InternalEvaluationError instance AsUnliftingError internal => AsUnliftingError (EvaluationError user internal) where @@ -111,37 +90,9 @@ instance AsUnliftingError (MachineError fun) where instance AsEvaluationFailure user => AsEvaluationFailure (EvaluationError user internal) where _EvaluationFailure = _UserEvaluationError . _EvaluationFailure --- | An error and (optionally) what caused it. -data ErrorWithCause err cause = ErrorWithCause - { _ewcError :: !err - , _ewcCause :: !(Maybe cause) - } deriving stock (Eq, Functor, Foldable, Traversable, Generic) - deriving anyclass (NFData) - -instance Bifunctor ErrorWithCause where - bimap f g (ErrorWithCause err cause) = ErrorWithCause (f err) (g <$> cause) - -instance AsEvaluationFailure err => AsEvaluationFailure (ErrorWithCause err cause) where - _EvaluationFailure = iso _ewcError (flip ErrorWithCause Nothing) . _EvaluationFailure - -instance (Pretty err, Pretty cause) => Pretty (ErrorWithCause err cause) where - pretty (ErrorWithCause e c) = pretty e <+> "caused by:" <+> pretty c - type EvaluationException user internal = ErrorWithCause (EvaluationError user internal) -throwNotAConstant :: MonadError KnownTypeError m => m void -throwNotAConstant = throwError $ KnownTypeUnliftingError "Not a constant" -{-# INLINE throwNotAConstant #-} - --- | "Prismatically" throw an error and its (optional) cause. -throwingWithCause - -- Binds exc so it can be used as a convenient parameter with TypeApplications - :: 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 - {- Note [Ignoring context in UserEvaluationError] The UserEvaluationError error has a term argument, but extractEvaluationResult just discards this and returns @@ -172,12 +123,6 @@ unsafeExtractEvaluationResult -> EvaluationResult a unsafeExtractEvaluationResult = unsafeFromEither . extractEvaluationResult -instance Pretty UnliftingError where - pretty (UnliftingErrorE err) = fold - [ "Could not unlift a value:", hardline - , pretty err - ] - instance (HasPrettyDefaults config ~ 'True, Pretty fun) => PrettyBy config (MachineError fun) where prettyBy _ NonPolymorphicInstantiationMachineError = @@ -213,18 +158,3 @@ instance [ "User error:", hardline , pretty err ] - -instance (PrettyBy config cause, PrettyBy config err) => - PrettyBy config (ErrorWithCause err cause) where - prettyBy config (ErrorWithCause err mayCause) = - "An error has occurred: " <+> prettyBy config err <> - case mayCause of - Nothing -> mempty - Just cause -> hardline <> "Caused by:" <+> prettyBy config cause - -instance (PrettyPlc cause, PrettyPlc err) => - Show (ErrorWithCause err cause) where - show = render . prettyPlcReadableDebug - -deriving anyclass instance - (PrettyPlc cause, PrettyPlc err, Typeable cause, Typeable err) => Exception (ErrorWithCause err cause) diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Result.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Result.hs index 15ce995df01..d45c768a58e 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Result.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Result.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} @@ -60,6 +61,20 @@ data EvaluationResult a deriving stock (Show, Eq, Generic, Functor, Foldable, Traversable) deriving anyclass (NFData) +-- >>> evaluationFailure :: EvaluationResult Bool +-- EvaluationFailure +-- +-- >>> import Control.Lens +-- >>> matching _EvaluationFailure (EvaluationFailure :: EvaluationResult Bool) +-- Right () +-- +-- >>> matching _EvaluationFailure $ EvaluationSuccess True +-- Left (EvaluationSuccess True) +instance AsEvaluationFailure (EvaluationResult a) where + _EvaluationFailure = prism (const EvaluationFailure) $ \case + a@EvaluationSuccess{} -> Left a + EvaluationFailure -> Right () + -- This and the next one are two instances that allow us to write the following: -- -- >>> import Control.Monad.Error.Lens @@ -71,9 +86,11 @@ instance AsEvaluationFailure () where instance MonadError () EvaluationResult where throwError () = EvaluationFailure + {-# INLINE throwError #-} catchError EvaluationFailure f = f () catchError x _ = x + {-# INLINE catchError #-} instance Applicative EvaluationResult where pure = EvaluationSuccess @@ -83,17 +100,24 @@ instance Applicative EvaluationResult where EvaluationFailure <*> _ = EvaluationFailure {-# INLINE (<*>) #-} + EvaluationSuccess _ *> b = b + EvaluationFailure *> _ = EvaluationFailure + {-# INLINE (*>) #-} + instance Monad EvaluationResult where EvaluationSuccess x >>= f = f x EvaluationFailure >>= _ = EvaluationFailure {-# INLINE (>>=) #-} + (>>) = (*>) + {-# INLINE (>>) #-} + instance Alternative EvaluationResult where empty = EvaluationFailure {-# INLINE empty #-} - EvaluationSuccess x <|> _ = EvaluationSuccess x - EvaluationFailure <|> a = a + a@EvaluationSuccess{} <|> _ = a + EvaluationFailure <|> b = b {-# INLINE (<|>) #-} instance MonadFail EvaluationResult where diff --git a/plutus-core/plutus-core/src/PlutusCore/TypeCheck/Internal.hs b/plutus-core/plutus-core/src/PlutusCore/TypeCheck/Internal.hs index 991bbe0420b..466ba2a8c07 100644 --- a/plutus-core/plutus-core/src/PlutusCore/TypeCheck/Internal.hs +++ b/plutus-core/plutus-core/src/PlutusCore/TypeCheck/Internal.hs @@ -31,7 +31,6 @@ import PlutusPrelude import Control.Lens import Control.Monad (when) -import Control.Monad.Error.Lens import Control.Monad.Except (MonadError) -- Using @transformers@ rather than @mtl@, because the former doesn't impose the 'Monad' constraint -- on 'local'. diff --git a/plutus-core/plutus-core/test/Evaluation/Spec.hs b/plutus-core/plutus-core/test/Evaluation/Spec.hs index e29bbe8de22..204ded40fe0 100644 --- a/plutus-core/plutus-core/test/Evaluation/Spec.hs +++ b/plutus-core/plutus-core/test/Evaluation/Spec.hs @@ -36,7 +36,7 @@ import Type.Reflection type Term uni fun = PLC.Term TyName Name uni fun () {- | Evaluating a builtin function should never throw any exception (the evaluation is allowed - to fail with a `KnownTypeError`, of course). + to fail with a `BuiltinError`, of course). The test covers both succeeding and failing evaluations and verifies that in either case no exception is thrown. The failing cases use arbitrary `Term` arguments (which doesn't @@ -119,20 +119,20 @@ prop_builtinEvaluation :: (fun -> Gen [Term uni fun]) -> -- | A function that takes a builtin function, a list of arguments, and the evaluation -- outcome, and decides whether to pass or fail the property. - (fun -> [Term uni fun] -> Either SomeException (MakeKnownM (Term uni fun)) -> PropertyT IO ()) -> + (fun -> [Term uni fun] -> Either SomeException (BuiltinResult (Term uni fun)) -> PropertyT IO ()) -> Property prop_builtinEvaluation runtimes bn mkGen f = property $ do args0 <- forAllNoShow $ mkGen bn let - eval :: [Term uni fun] -> BuiltinRuntime (Term uni fun) -> MakeKnownM (Term uni fun) - eval [] (BuiltinResult _ getX) = + eval :: [Term uni fun] -> BuiltinRuntime (Term uni fun) -> BuiltinResult (Term uni fun) + eval [] (BuiltinCostedResult _ getX) = getX eval (arg : args) (BuiltinExpectArgument toRuntime) = eval args (toRuntime arg) eval args (BuiltinExpectForce runtime) = eval args runtime eval _ _ = - -- TODO: can we make this function run in @GenT MakeKnownM@ and generate arguments + -- TODO: can we make this function run in @GenT BuiltinResult@ and generate arguments -- on the fly to avoid this error case? error $ "Wrong number of args for builtin " <> display bn <> ": " <> display args0 runtime0 = lookupBuiltin bn runtimes diff --git a/plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs b/plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs index b81e4b0d511..01a0c4021ce 100644 --- a/plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs +++ b/plutus-core/plutus-ir/src/PlutusIR/Transform/EvaluateBuiltins.hs @@ -53,20 +53,20 @@ evaluateBuiltins conservative binfo costModel = transformOf termSubterms process :: BuiltinRuntime (Term tyname name uni fun ()) -> AppContext tyname name uni fun a -> Maybe (Term tyname name uni fun ()) - eval (BuiltinResult _ getX) AppContextEnd = + eval (BuiltinCostedResult _ getX) AppContextEnd = case getX of - MakeKnownSuccess v -> Just v + BuiltinSuccess v -> Just v -- Evaluates successfully, but does logging. If we're being conservative -- then we should leave these in, so we don't remove people's logging! -- Otherwise `trace "hello" x` is a prime candidate for evaluation! - MakeKnownSuccessWithLogs _ v -> if conservative then Nothing else Just v + BuiltinSuccessWithLogs _ v -> if conservative then Nothing else Just v -- Evaluation failure. This can mean that the evaluation legitimately -- failed (e.g. `divideInteger 1 0`), or that it failed because the -- argument terms are not currently in the right form (because they're -- not evaluated, we're in the middle of a term here!). Since we can't -- distinguish these, we have to assume it's the latter case and just leave -- things alone. - MakeKnownFailure{} -> Nothing + BuiltinFailure{} -> Nothing eval (BuiltinExpectArgument toRuntime) (TermAppContext arg _ ctx) = -- Builtin evaluation does not work with annotations, so we have to throw -- the argument annotation away here diff --git a/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/Cek/Internal.hs b/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/Cek/Internal.hs index 867fe23c104..d310786a018 100644 --- a/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/Cek/Internal.hs +++ b/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/Cek/Internal.hs @@ -652,14 +652,14 @@ evalBuiltinApp -> BuiltinRuntime (CekValue uni fun ann) -> CekM uni fun s (CekValue uni fun ann) evalBuiltinApp fun term runtime = case runtime of - BuiltinResult budgets getX -> do + BuiltinCostedResult budgets getX -> do spendBudgetStreamCek (BBuiltinApp fun) budgets case getX of - MakeKnownFailure logs err -> do + BuiltinFailure logs err -> do ?cekEmitter logs - throwKnownTypeErrorWithCause term err - MakeKnownSuccess x -> pure x - MakeKnownSuccessWithLogs logs x -> ?cekEmitter logs $> x + throwBuiltinErrorWithCause term err + BuiltinSuccess x -> pure x + BuiltinSuccessWithLogs logs x -> ?cekEmitter logs $> x _ -> pure $ VBuiltin fun term runtime {-# INLINE evalBuiltinApp #-} diff --git a/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/SteppableCek/Internal.hs b/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/SteppableCek/Internal.hs index ba2c23dfa1b..5cf72765816 100644 --- a/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/SteppableCek/Internal.hs +++ b/plutus-core/untyped-plutus-core/src/UntypedPlutusCore/Evaluation/Machine/SteppableCek/Internal.hs @@ -436,14 +436,14 @@ evalBuiltinApp -> BuiltinRuntime (CekValue uni fun ann) -> CekM uni fun s (CekValue uni fun ann) evalBuiltinApp fun term runtime = case runtime of - BuiltinResult budgets getX -> do + BuiltinCostedResult budgets getX -> do spendBudgetStreamCek (BBuiltinApp fun) budgets case getX of - MakeKnownFailure logs err -> do + BuiltinFailure logs err -> do ?cekEmitter logs - throwKnownTypeErrorWithCause term err - MakeKnownSuccess x -> pure x - MakeKnownSuccessWithLogs logs x -> ?cekEmitter logs $> x + throwBuiltinErrorWithCause term err + BuiltinSuccess x -> pure x + BuiltinSuccessWithLogs logs x -> ?cekEmitter logs $> x _ -> pure $ VBuiltin fun term runtime {-# INLINE evalBuiltinApp #-} diff --git a/plutus-metatheory/src/Builtin.lagda.md b/plutus-metatheory/src/Builtin.lagda.md index 5b5eb7278f9..9edd723e6b0 100644 --- a/plutus-metatheory/src/Builtin.lagda.md +++ b/plutus-metatheory/src/Builtin.lagda.md @@ -484,19 +484,20 @@ postulate {-# FOREIGN GHC import PlutusCore.Crypto.Ed25519 #-} {-# FOREIGN GHC import PlutusCore.Crypto.Secp256k1 #-} --- The Vasil verification functions return results wrapped in Emitters, which +-- The Vasil verification functions return results wrapped in BuiltinResult, which -- may perform a side-effect such as writing some text to a log. The code below --- provides an adaptor function which turns an Emitter (EvaluationResult r) into +-- provides an adaptor function which turns a BuiltinResult r into -- Just r, where r is the real return type of the builtin. -- TODO: deal directly with emitters in Agda? -{-# FOREIGN GHC import PlutusCore.Builtin (runEmitter) #-} -{-# FOREIGN GHC import PlutusCore.Evaluation.Result (EvaluationResult (EvaluationSuccess, EvaluationFailure)) #-} -{-# FOREIGN GHC emitterResultToMaybe = \e -> case fst e of {EvaluationSuccess r -> Just r; EvaluationFailure -> Nothing} #-} +{-# FOREIGN GHC import PlutusPrelude (reoption) #-} +{-# FOREIGN GHC import PlutusCore.Builtin (BuiltinResult) #-} +{-# FOREIGN GHC builtinResultToMaybe :: BuiltinResult a -> Maybe a #-} +{-# FOREIGN GHC builtinResultToMaybe = reoption #-} -{-# COMPILE GHC verifyEd25519Sig = \k m s -> emitterResultToMaybe . runEmitter $ verifyEd25519Signature_V2 k m s #-} -{-# COMPILE GHC verifyEcdsaSecp256k1Sig = \k m s -> emitterResultToMaybe . runEmitter $ verifyEcdsaSecp256k1Signature k m s #-} -{-# COMPILE GHC verifySchnorrSecp256k1Sig = \k m s -> emitterResultToMaybe . runEmitter $ verifySchnorrSecp256k1Signature k m s #-} +{-# COMPILE GHC verifyEd25519Sig = \k m s -> builtinResultToMaybe $ verifyEd25519Signature_V2 k m s #-} +{-# COMPILE GHC verifyEcdsaSecp256k1Sig = \k m s -> builtinResultToMaybe $ verifyEcdsaSecp256k1Signature k m s #-} +{-# COMPILE GHC verifySchnorrSecp256k1Sig = \k m s -> builtinResultToMaybe $ verifySchnorrSecp256k1Signature k m s #-} {-# COMPILE GHC ENCODEUTF8 = encodeUtf8 #-} {-# COMPILE GHC DECODEUTF8 = eitherToMaybe . decodeUtf8' #-} @@ -550,4 +551,4 @@ unquoteDef showBuiltin = defShow (quote Builtin) showBuiltin ``` builtinList : List Builtin unquoteDef builtinList = defListConstructors (quote Builtin) builtinList -``` \ No newline at end of file +``` diff --git a/plutus-tx/plutus-tx.cabal b/plutus-tx/plutus-tx.cabal index 952ac9fe247..8c09307759f 100644 --- a/plutus-tx/plutus-tx.cabal +++ b/plutus-tx/plutus-tx.cabal @@ -115,7 +115,6 @@ library , text , th-abstraction , th-compat - , transformers default-extensions: Strict diff --git a/plutus-tx/src/PlutusTx/Builtins/Internal.hs b/plutus-tx/src/PlutusTx/Builtins/Internal.hs index 5c82e07b9ad..0b3c5d977a6 100644 --- a/plutus-tx/src/PlutusTx/Builtins/Internal.hs +++ b/plutus-tx/src/PlutusTx/Builtins/Internal.hs @@ -18,7 +18,6 @@ module PlutusTx.Builtins.Internal where import Codec.Serialise import Control.DeepSeq (NFData (..)) -import Control.Monad.Trans.Writer.Strict (runWriter) import Data.ByteArray qualified as BA import Data.ByteString qualified as BS import Data.ByteString.Lazy qualified as BSL @@ -30,8 +29,8 @@ import Data.Kind (Type) import Data.Text as Text (Text, empty) import Data.Text.Encoding as Text (decodeUtf8, encodeUtf8) import GHC.Generics +import PlutusCore.Builtin (BuiltinResult (..)) import PlutusCore.Builtin.Convert qualified as Convert -import PlutusCore.Builtin.Emitter (Emitter (Emitter)) import PlutusCore.Crypto.BLS12_381.G1 qualified as BLS12_381.G1 import PlutusCore.Crypto.BLS12_381.G2 qualified as BLS12_381.G2 import PlutusCore.Crypto.BLS12_381.Pairing qualified as BLS12_381.Pairing @@ -39,8 +38,7 @@ import PlutusCore.Crypto.Ed25519 qualified import PlutusCore.Crypto.Hash qualified as Hash import PlutusCore.Crypto.Secp256k1 qualified import PlutusCore.Data qualified as PLC -import PlutusCore.Evaluation.Result (EvaluationResult (EvaluationFailure, EvaluationSuccess)) -import PlutusCore.Pretty (Pretty (..)) +import PlutusCore.Pretty (Pretty (..), display) import PlutusTx.Utils (mustBeReplaced) import Prettyprinter (viaShow) @@ -258,10 +256,10 @@ keccak_256 (BuiltinByteString b) = BuiltinByteString $ Hash.keccak_256 b verifyEd25519Signature :: BuiltinByteString -> BuiltinByteString -> BuiltinByteString -> BuiltinBool verifyEd25519Signature (BuiltinByteString vk) (BuiltinByteString msg) (BuiltinByteString sig) = case PlutusCore.Crypto.Ed25519.verifyEd25519Signature_V1 vk msg sig of - Emitter f -> case runWriter f of - (res, logs) -> traceAll logs $ case res of - EvaluationFailure -> mustBeReplaced "Ed25519 signature verification errored." - EvaluationSuccess b -> BuiltinBool b + BuiltinFailure logs err -> traceAll (logs <> pure (display err)) $ + mustBeReplaced "Ed25519 signature verification errored." + BuiltinSuccess b -> BuiltinBool b + BuiltinSuccessWithLogs logs b -> traceAll logs $ BuiltinBool b {-# NOINLINE verifyEcdsaSecp256k1Signature #-} verifyEcdsaSecp256k1Signature :: @@ -271,10 +269,10 @@ verifyEcdsaSecp256k1Signature :: BuiltinBool verifyEcdsaSecp256k1Signature (BuiltinByteString vk) (BuiltinByteString msg) (BuiltinByteString sig) = case PlutusCore.Crypto.Secp256k1.verifyEcdsaSecp256k1Signature vk msg sig of - Emitter f -> case runWriter f of - (res, logs) -> traceAll logs $ case res of - EvaluationFailure -> mustBeReplaced "ECDSA SECP256k1 signature verification errored." - EvaluationSuccess b -> BuiltinBool b + BuiltinFailure logs err -> traceAll (logs <> pure (display err)) $ + mustBeReplaced "ECDSA SECP256k1 signature verification errored." + BuiltinSuccess b -> BuiltinBool b + BuiltinSuccessWithLogs logs b -> traceAll logs $ BuiltinBool b {-# NOINLINE verifySchnorrSecp256k1Signature #-} verifySchnorrSecp256k1Signature :: @@ -284,10 +282,10 @@ verifySchnorrSecp256k1Signature :: BuiltinBool verifySchnorrSecp256k1Signature (BuiltinByteString vk) (BuiltinByteString msg) (BuiltinByteString sig) = case PlutusCore.Crypto.Secp256k1.verifySchnorrSecp256k1Signature vk msg sig of - Emitter f -> case runWriter f of - (res, logs) -> traceAll logs $ case res of - EvaluationFailure -> mustBeReplaced "Schnorr SECP256k1 signature verification errored." - EvaluationSuccess b -> BuiltinBool b + BuiltinFailure logs err -> traceAll (logs <> pure (display err)) $ + mustBeReplaced "Schnorr SECP256k1 signature verification errored." + BuiltinSuccess b -> BuiltinBool b + BuiltinSuccessWithLogs logs b -> traceAll logs $ BuiltinBool b traceAll :: forall (a :: Type) (f :: Type -> Type) . (Foldable f) => f Text -> a -> a @@ -694,10 +692,10 @@ integerToByteString :: BuiltinByteString integerToByteString (BuiltinBool endiannessArg) paddingArg input = case Convert.integerToByteStringWrapper endiannessArg paddingArg input of - Emitter f -> case runWriter f of - (result, logs) -> traceAll logs $ case result of - EvaluationFailure -> mustBeReplaced "Integer to ByteString conversion errored." - EvaluationSuccess bs -> BuiltinByteString bs + BuiltinFailure logs err -> traceAll (logs <> pure (display err)) $ + mustBeReplaced "Integer to ByteString conversion errored." + BuiltinSuccess bs -> BuiltinByteString bs + BuiltinSuccessWithLogs logs bs -> traceAll logs $ BuiltinByteString bs {-# NOINLINE byteStringToInteger #-} byteStringToInteger ::