Skip to content

Commit 0900feb

Browse files
committed
how to evaluate compiled code: user guide doc + example
1 parent 36ed5c9 commit 0900feb

File tree

6 files changed

+352
-9
lines changed

6 files changed

+352
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
---
2+
sidebar_position: 12
3+
---
4+
5+
# Evaluating CompiledCode for testing
6+
7+
The `CompiledCode` is intended to be evaluated by Cardano nodes when
8+
transaction validation occurs. For this purpose, it is serialized and included in a transaction.
9+
10+
However, it is also possible to evaluate `CompiledCode` in a test environment.
11+
This is useful for testing and troubleshooting. By doing so, Plinth developers can
12+
not only get the immediate result of the code but also obtain the traces emitted
13+
during the evaluation, as well as the consumed execution budget.
14+
15+
Let's consider the following example Plinth program:
16+
<LiteralInclude
17+
file="Example/Evaluation/Main.hs"
18+
language="haskell"
19+
title="Example Plinth code"
20+
start="-- BEGIN Plinth"
21+
end="-- END Plinth" />
22+
23+
In order to evaluate this code, we need to add the `plutus-tx:plutus-tx-testlib`
24+
dependency to our cabal file:
25+
```cabal
26+
build-depends:
27+
, plutus-tx:plutus-tx-testlib
28+
```
29+
30+
So that we can import the necessary functionality:
31+
32+
```haskell
33+
import PlutusTx.Test (
34+
CodeResult,
35+
applyLifted,
36+
evaluateCompiledCode,
37+
prettyCodeResult
38+
)
39+
```
40+
41+
Our example code represents a function that expects an `Integer` argument
42+
and returns the `Integer` value.
43+
44+
It is possible to evaluate this compiled code without applying it to any arguments,
45+
and evaluation will succeed, returning the value of type `Integer -> Integer`:
46+
47+
```haskell
48+
result :: CodeResult
49+
result = evaluateCompiledCode compiledCode
50+
```
51+
52+
The `CodeResult` type is declared as follows:
53+
54+
```haskell
55+
data CodeResult = CodeResult
56+
{ codeResult
57+
:: Either
58+
(CekEvaluationException NamedDeBruijn DefaultUni DefaultFun)
59+
(NTerm DefaultUni DefaultFun ())
60+
, codeBudget :: ExBudget
61+
, codeTraces :: [Text]
62+
}
63+
deriving stock (Show)
64+
```
65+
66+
The `codeResult` field contains the result of the evaluation, which can either be a successful evaluation or an error.
67+
68+
The `codeBudget` field contains the execution budget used during the evaluation,
69+
which includes the CPU and memory usage.
70+
71+
The `codeTraces` field contains the traces emitted during the evaluation.
72+
73+
One can use its `Show` instance to print the result of the evaluation:
74+
```haskell
75+
CodeResult
76+
{ codeResult =
77+
Right
78+
( LamAbs
79+
()
80+
(NamedDeBruijn{ndbnString = "x", ndbnIndex = 0})
81+
( Apply
82+
()
83+
( Apply
84+
()
85+
(Builtin () AddInteger)
86+
( Apply
87+
()
88+
( Apply
89+
()
90+
( Force
91+
()
92+
(Builtin () Trace)
93+
)
94+
( Constant
95+
()
96+
(Some (ValueOf DefaultUniString "Evaluating x"))
97+
)
98+
)
99+
(Var () (NamedDeBruijn{ndbnString = "x", ndbnIndex = 1}))
100+
)
101+
)
102+
( Apply
103+
()
104+
( Apply
105+
()
106+
(Force () (Builtin () Trace))
107+
(Constant () (Some (ValueOf DefaultUniString "Evaluating constant")))
108+
)
109+
(Constant () (Some (ValueOf DefaultUniInteger 2)))
110+
)
111+
)
112+
)
113+
, codeBudget =
114+
ExBudget
115+
{ exBudgetCPU = ExCPU 16100
116+
, exBudgetMemory = ExMemory 200
117+
}
118+
, codeTraces = []
119+
}
120+
```
121+
122+
The output is quite verbose and not very readable.
123+
To make it more readable, we can use the `prettyCodeResult` function,
124+
which formats the output in a prettier way:
125+
126+
```
127+
Evaluation was SUCCESSFUL, result is:
128+
\x ->
129+
addInteger
130+
(force trace "Evaluating x" x)
131+
(force trace "Evaluating constant" 2)
132+
133+
Execution budget spent:
134+
CPU 16,100
135+
MEM 200
136+
137+
No traces were emitted
138+
```
139+
140+
This output reveals that the evaluation was successful, and the resultng UPLC
141+
value is an (un-applied) lambda abstraction.
142+
143+
The [Lifting Values into CompiledCode](./lifting.md) section contains and example
144+
of applying the `CompiledCode` to an argument.
145+
146+
Alternatively, we can use the imported `applyLifted` function like this:
147+
148+
<LiteralInclude
149+
file="Example/Evaluation/Main.hs"
150+
language="haskell"
151+
title="How to apply compiled function to a lifted argument"
152+
start="-- BEGIN AppliedResult"
153+
end="-- END AppliedResult" />
154+
155+
Lets print the result of the evaluation:
156+
157+
<LiteralInclude
158+
file="Example/Evaluation/Main.hs"
159+
language="haskell"
160+
title="Pretty-printng the result"
161+
start="-- BEGIN main"
162+
end="-- END main" />
163+
164+
```
165+
Evaluation was SUCCESSFUL, result is:
166+
4
167+
168+
Execution budget spent:
169+
CPU 508,304
170+
MEM 1,966
171+
172+
Evaluation traces:
173+
1. Evaluating x
174+
2. Evaluating constant
175+
```
176+
177+
Nice! Now we can see that calculating `2 + 2 = 4` using the CEK Machine (UPLC interpreter)
178+
requires 508,304 CPU and 1,966 MEM units.
179+
180+
For the sake of completeness, here is an example of an evaluation that fails:
181+
182+
```
183+
Evaluation FAILED:
184+
An error has occurred:
185+
The machine terminated because of an error,
186+
either from a built-in function or from an explicit use of 'error'.
187+
188+
Execution budget spent:
189+
CPU 220,304
190+
MEM 166
191+
192+
Evaluation traces:
193+
1. Evaluating x
194+
2. Evaluating constant
195+
```
196+
197+
Both example outputs include execution traces emited during the evaluation.
198+
These can come in handy when debugging your Plinth code.
199+
200+
Caveat: traces add up to the script size, so make sure to remove them
201+
when you are done debugging.

doc/docusaurus/docusaurus-examples.cabal

+20
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@ executable example-cip57
5757
, plutus-tx ^>=1.45
5858
, plutus-tx-plugin ^>=1.45
5959

60+
executable example-evaluation
61+
import: lang, ghc-version-support, os-support
62+
main-is: Example/Evaluation/Main.hs
63+
hs-source-dirs: static/code
64+
default-language: Haskell2010
65+
other-modules: Paths_docusaurus_examples
66+
build-depends:
67+
, base ^>=4.18
68+
, plutus-tx ^>=1.45
69+
, plutus-tx-plugin ^>=1.45
70+
, plutus-tx:plutus-tx-testlib
71+
, pretty-simple
72+
, text
73+
74+
ghc-options:
75+
-Wno-missing-signatures -fno-full-laziness
76+
-fno-ignore-interface-pragmas -fno-omit-interface-pragmas
77+
-fno-spec-constr -fno-specialise -fno-strictness
78+
-fno-unbox-small-strict-fields -fno-unbox-strict-fields
79+
6080
executable quickstart
6181
import: lang, ghc-version-support, os-support
6282
main-is: QuickStart.hs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{-# LANGUAGE DataKinds #-}
2+
{-# LANGUAGE ImportQualifiedPost #-}
3+
{-# LANGUAGE NoImplicitPrelude #-}
4+
{-# LANGUAGE OverloadedStrings #-}
5+
{-# LANGUAGE ScopedTypeVariables #-}
6+
{-# LANGUAGE TypeApplications #-}
7+
{-# OPTIONS_GHC -fplugin PlutusTx.Plugin #-}
8+
{-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:target-version=1.1.0 #-}
9+
10+
module Main where
11+
12+
import Data.Proxy (Proxy (..))
13+
import Data.Text.IO qualified as Text
14+
import PlutusTx
15+
import PlutusTx.Plugin (plc)
16+
import PlutusTx.Prelude
17+
import Prelude (IO)
18+
19+
import PlutusTx.Test (CodeResult, applyLifted, evaluateCompiledCode, prettyCodeResult)
20+
21+
-- BEGIN Plinth
22+
plinthCode :: Integer -> Integer
23+
plinthCode x = trace "Evaluating x" x + trace "Evaluating constant" 2
24+
25+
compiledCode :: CompiledCode (Integer -> Integer)
26+
compiledCode = plc (Proxy @"plinth") plinthCode
27+
-- END Plinth
28+
29+
{-
30+
CodeResult
31+
{ codeResult = Right
32+
( Constant ()
33+
( Some
34+
( ValueOf DefaultUniInteger 4 )
35+
)
36+
)
37+
, codeBudget = ExBudget
38+
{ exBudgetCPU = ExCPU 508304
39+
, exBudgetMemory = ExMemory 1966
40+
}
41+
, codeTraces =
42+
[ "Evaluating x"
43+
, "Evaluating constant"
44+
]
45+
}
46+
-}
47+
48+
-- BEGIN AppliedResult
49+
result :: CodeResult
50+
result = evaluateCompiledCode $ compiledCode `applyLifted` 2
51+
-- END AppliedResult
52+
53+
-- BEGIN main
54+
main :: IO ()
55+
main = Text.putStrLn $ prettyCodeResult result
56+
-- END main

plutus-tx/plutus-tx.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ library plutus-tx-testlib
175175
build-depends:
176176
, base >=4.9 && <5
177177
, flat ^>=0.6
178+
, formatting
178179
, hedgehog
179180
, lens
180181
, mtl

0 commit comments

Comments
 (0)