3. CEK Machine Tutorial : Plutus - wimsio/universities GitHub Wiki

Plutus CEK Machine Tutorial

Table of Contents

  1. Introduction

  2. Running the Machine

  3. Advanced Topics

  4. Exercises

  5. Exercise Solutions

  6. Glossary


1. Introduction

The CEK Machine is the virtual machine used by Plutus to evaluate Untyped Plutus Core (UPLC) smart contracts on Cardano.

  • It tracks execution costs (resource usage), supports various error types, and offers logging modes for debugging.
  • The CEK API lets you run scripts, analyze execution, and safely interact with on-chain smart contracts.

2. Running the Machine

Basic Evaluation

The main entry point is runCek:

runCek
  :: ThrowableBuiltins uni fun
  => MachineParameters CekMachineCosts fun (CekValue uni fun ann)
  -> ExBudgetMode cost uni fun
  -> EmitterMode uni fun
  -> Term Name uni fun ann
  -> (Either (CekEvaluationException Name uni fun) (Term Name uni fun ()), cost, [Text])
  • Purpose: Evaluates a Plutus Core term, tracks execution cost, and produces logs.
  • Returns: A tuple of (result, cost, logs).

Minimal Example

import UntypedPlutusCore.Evaluation.Machine.Cek
import UntypedPlutusCore.Core.Type (Term(..), Name(..))
-- ...other imports...

main = do
  let params = defaultCekParameters
      budgetMode = counting
      emitter = logEmitter
      term = ... -- your Plutus Core term here
  let (result, cost, logs) = runCek params budgetMode emitter term
  print result
  print cost
  mapM_ putStrLn logs

Logging and Emission

  • Emitter modes control what gets logged:

    • noEmitter: No logging.
    • logEmitter: Basic logs.
    • logWithTimeEmitter, logWithBudgetEmitter: Add timestamps/budget to logs.

Example:

runCek params counting logWithTimeEmitter myTerm

Costing

  • Costing modes track resource usage:

    • counting: Total budget spent.
    • tallying: Budget per operation.
    • restricting: Enforce a budget cap (fail if over).
    • restrictingLarge/restrictingEnormous: Pre-set very large budgets for benchmarks.

Example:

runCek params restrictingLarge noEmitter myTerm

Error Handling

Evaluation can fail for structural reasons (bad program) or operational reasons (runtime error, like division by zero):

case result of
  Left err -> putStrLn $ "Evaluation failed: " ++ show err
  Right val -> putStrLn $ "Success: " ++ show val

3. Advanced Topics

Costing Modes

  • counting: Track total cost (for analysis).
  • tallying: Track detailed per-operation cost.
  • restricting: Limit execution to a maximum budget (ExRestrictingBudget).

Set the mode in runCek:

runCek params tallying logEmitter term

Emitter Modes

  • noEmitter: No log output.
  • logEmitter: Basic logging.
  • logWithTimeEmitter: Logs with timestamps.
  • logWithBudgetEmitter: Logs with budget information.

Example:

runCek params counting logWithBudgetEmitter term

Working with DeBruijn Terms

For terms using DeBruijn indices (instead of named variables), use:

runCekDeBruijn :: ... -> NTerm uni fun ann -> (Either (CekEvaluationException NamedDeBruijn uni fun) (NTerm uni fun ()), cost, [Text])

This is useful for advanced scenarios, such as compiling or optimizing terms for on-chain deployment.


4. Exercises

Exercise 1:

Write a function to evaluate a simple Plutus Core integer constant using runCek and print the result and cost.


Exercise 2:

Modify your function to log each evaluation step using a different emitter mode.


Exercise 3:

Evaluate an invalid term (such as applying a constant as a function) and print the resulting error.


5. Exercise Solutions

Solution 1:

import UntypedPlutusCore.Evaluation.Machine.Cek
import UntypedPlutusCore.Core.Type (Term(..), Name(..))
import PlutusCore.Default

main = do
  let term = Constant () (Some (ValueOf DefaultUniInteger 42))
      params = defaultCekParameters
      (result, cost, logs) = runCek params counting noEmitter term
  print result   -- Should print: Right (Constant () ...)
  print cost     -- Shows execution cost

Solution 2:

main = do
  let term = Constant () (Some (ValueOf DefaultUniInteger 42))
      params = defaultCekParameters
      (result, cost, logs) = runCek params counting logEmitter term
  print result
  print cost
  mapM_ putStrLn logs  -- Prints logs for each step

Solution 3:

main = do
  let invalidTerm = Apply () (Constant () (Some (ValueOf DefaultUniInteger 1)))
                            (Constant () (Some (ValueOf DefaultUniInteger 2)))
      params = defaultCekParameters
      (result, cost, logs) = runCek params counting logEmitter invalidTerm
  print result   -- Should print: Left (CekEvaluationException ...)
  print cost
  mapM_ putStrLn logs

6. Glossary

Term Meaning
CEK Machine An abstract machine (evaluator) for Untyped Plutus Core; tracks costs and errors.
Emitter Mode Controls what gets logged during evaluation.
Costing Mode Controls how execution resource usage is tracked or restricted.
Term An Untyped Plutus Core expression to be evaluated.
ExBudget The resource budget (memory, CPU) for script execution.
MachineParameters The runtime parameters (cost models, builtins, etc) for the CEK machine.
CekEvaluationException An error thrown during evaluation (could be structural or operational).
DeBruijn Index A variable representation that avoids naming issues by using numeric indices.
NTerm A Plutus Core term in DeBruijn representation.
Constant A term representing a literal value (integer, bool, etc).
Apply Application of one term to another (function application).
Counting / Tallying / Restricting Different ways to measure or limit execution cost.