Evaluating CompiledCode
CompiledCode
is intended to be evaluated by Cardano nodes during transaction validation. For this purpose, it is serialized and included in a transaction.
However, it is also possible to evaluate CompiledCode
without running a node. The Plutus evaluator, called the CEK Machine, can be used independently of the Cardano node for testing and troubleshooting. By evaluating Plinth programs locally, developers can not only obtain the immediate result of the code but also access the traces emitted during evaluation and the consumed execution budget.
Let's consider the following example Plinth program:
LoadingThis code defines a function that expects an Integer
argument and returns an Integer
value.
To compile it, use the compile
function as described earlier in the Compiling Plinth code section:
To evaluate compiledCode
, add the plutus-tx
and plutus-ledger-api
dependencies to your cabal file:
build-depends: plutus-tx, plutus-ledger-api
This allows you to import the necessary functionality:
LoadingYou can evaluate this compiled code without applying it to any arguments. The evaluation will succeed, returning a value of type Integer -> Integer
:
result :: EvalResult
result = evaluateCompiledCode compiledCode
The EvalResult
type is declared as follows:
data EvalResult = EvalResult
{ evalResult
:: Either
(CekEvaluationException NamedDeBruijn DefaultUni DefaultFun)
(NTerm DefaultUni DefaultFun ())
, evalResultBudget :: ExBudget
, evalResultTraces :: [Text]
}
deriving stock (Show)
The evalResult
field contains the result of the evaluation, which can be either a successful result or an error.
The evalResultBudget
field contains the execution budget used during evaluation, including CPU and memory usage.
The evalResultTraces
field contains any traces emitted during evaluation.
The evaluateCompiledCode
function is the main workhorse of the evaluation process. Under the hood, it uses the Plutus Core evaluator (CEK machine) configured with the latest cost model stored statically in the Plutus repository.
The execution budget reported by evaluateCompiledCode
is not guaranteed to exactly match the execution budget spent by Cardano mainnet nodes. This discrepancy arises because the cost model used by evaluateCompiledCode
may differ from the cost model active on the Cardano chain at a specific moment. The definitive values for on-chain cost calculations are protocol parameters, which are part of the chain's state. In practice, these parameters are typically derived from the cost model stored in the Plutus repository at some earlier point, though this is not guaranteed. During on-chain evaluation, the ledger provides a cost model to the Plutus Core evaluator.
The companion function evaluateCompiledCode'
is a more general version of evaluateCompiledCode
, allowing you to specify the cost model via the MachineParameters
type. This function is useful for testing, but in most cases, you can use evaluateCompiledCode
without worrying about these details.
To use it, supply a MachineParameters
value like this:
You can use the Show
instance of EvalResult
to print the result of the evaluation:
Show raw EvalResult
output
EvalResult
{ evalResult =
Right
( LamAbs
()
(NamedDeBruijn{ndbnString = "x", ndbnIndex = 0})
( Apply
()
( Apply
()
(Builtin () AddInteger)
( Apply
()
( Apply
()
( Force
()
(Builtin () Trace)
)
( Constant
()
(Some (ValueOf DefaultUniString "Evaluating x"))
)
)
(Var () (NamedDeBruijn{ndbnString = "x", ndbnIndex = 1}))
)
)
( Apply
()
( Apply
()
(Force () (Builtin () Trace))
(Constant () (Some (ValueOf DefaultUniString "Evaluating constant")))
)
(Constant () (Some (ValueOf DefaultUniInteger 2)))
)
)
)
, evalResultBudget =
ExBudget
{ exBudgetCPU = ExCPU 16100
, exBudgetMemory = ExMemory 200
}
, evalResultTraces = []
}
However, there is a dedicated function, displayEvalResult
, that prints the result in a more concise and human-readable format:
Evaluation was SUCCESSFUL, result is:
\x ->
addInteger
(force trace "Evaluating x" x)
(force trace "Evaluating constant" 2)
Execution budget spent:
CPU 16,100
MEM 200
No traces were emitted
This output shows that the evaluation was successful, and the resulting UPLC value is an (unapplied) lambda abstraction.
To apply the compiled lambda abstraction to an argument, you need a compiled argument. There are several ways to obtain it from a Haskell value known at compile time:
-
"Lift" it into
LoadingCompiledCode
. See the Lifting Values into CompiledCode section for more details. -
Use
Loading$(compile [|| ... ||])
in the same way as when compiling the function itself.
Once you have an argument of type CompiledCode a
, you can apply it to the compiled function using either the applyCode
function
or the unsafeApplyCode
function.
Let's print the result of the evaluation:
LoadingSimulating latest Plutus Ledger Language and
the latest Protocol Version evaluation:
--------------------------------------------
Evaluation was SUCCESSFUL, result is:
4
Execution budget spent:
CPU 508,304
MEM 1,966
Nice! Now we can see that calculating 2 + 2 = 4
using the CEK Machine (UPLC interpreter) requires 508,304 CPU and 1,966 MEM units.
For completeness, here is an example of a failed evaluation:
Evaluation FAILED:
An error has occurred:
The machine terminated because of an error,
either from a built-in function or from an explicit use of 'error'.
Execution budget spent:
CPU 220,304
MEM 166
Evaluation traces:
1. Evaluating x
2. Evaluating constant
Both example outputs include execution traces emitted during evaluation. These can be helpful when debugging your Plinth code.
Caveat: traces add to the script size, so make sure to remove them when you are done debugging.
You can find the complete example code in Example/Evaluation/Main.hs
in the Plutus repository.