Skip to content

Commit 292d0d7

Browse files
authored
docusaurus: plutus exe (#6743)
Co-authored-by: Nikolaos Bezirgiannis <bezirg@users.noreply.github.com>
1 parent 2e3b55c commit 292d0d7

File tree

6 files changed

+396
-7
lines changed

6 files changed

+396
-7
lines changed
Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
---
2+
sidebar_position: 40
3+
---
4+
5+
# CLI tool for Plutus
6+
7+
The `plutus` CLI tool allows you to:
8+
9+
- **check** statically your plutus-related (PIR/TPLC/UPLC) program for common errors.
10+
- **compile** (convert) between plutus-derived languages and code formats.
11+
- **optimise** the code.
12+
- **run** or interactively **debug** your program *locally* (without starting a Cardano Node).
13+
14+
A pre-built executable of the `plutus` CLI
15+
can be found on the [Latest Release](https://github.com/IntersectMBO/plutus/releases/latest) page in the repository. Alternatively, you can build the tool
16+
using Nix, specifically for your platform:
17+
18+
``` shell
19+
$ nix build ".#cabalProject.$(nix eval nixpkgs#stdenv.buildPlatform.system).hsPkgs.plutus-core.components.exes.plutus"
20+
```
21+
22+
To consult the tool's usage you can invoke `plutus --help` in your command line:
23+
24+
``` shell
25+
$ plutus --help
26+
USAGE: plutus [--run|--debug] [-OLEVEL] FILES... [-o FILE]
27+
```
28+
29+
In general, a *compiler tool* operates in a certain pattern:
30+
31+
> 1. Read (Parse or Deserialise) input program(s) of a *source* language
32+
> <br/>&darr;<br/>
33+
> 2. Check program(s) for certain errors
34+
> <br/>&darr;<br/>
35+
> 3. Compile to a lower-level *target* language
36+
> <br/>&darr;<br/>
37+
> 4. Optimise the compiled code (*optional*)
38+
> <br/>&darr;<br/>
39+
> 5. Write code to output (*optional*)
40+
41+
In case of `plutus` tool, *Step 1* is more or less straightforward:
42+
the input programs are read from the specified `FILES...` in the CLI and/or from Standard Input (`--stdin` option).
43+
44+
After reading the input program(s), the tool continues
45+
to run certain static **checks** on those programs (*Step 2*); currently there is no way to turn these static checks off.
46+
47+
In *Step 3* the tool will try to **compile** (convert) the higher-level source language (PIR, TPLC) of the input program to the lower-level target language (TPLC, UPLC).
48+
In case the source and target languages are the same, this step is a "no-op" (has to be for UPLC, since it is the lowest-level Plutus language after all).
49+
50+
*Step 4* (**Optimising** code) is optional and has to be manually turned on by using the option `-OLEVEL` where *LEVEL* is a number between zero and two.
51+
`-O0` specifies that no optimisations should be run (basically same as omitting the `-OLEVEL` option). `-O1` applies safe optimisations (guarantees no change to the program's semantics),
52+
whereas `-O2` applies aggressive / unsafe optimisations (may alter program's semantics).
53+
54+
*Step 5* writes the resulting (compiled and/or optimised) code to given output specified with `-o FILE` or `--stdout`.
55+
This step is optional: there is no program output when both options above are omitted. This is so that users can use the `plutus` tool as a background checker when developing a Plutus program
56+
or if they want to continue with an extra *Step 6*:
57+
58+
> ...
59+
> <br/>&darr;<br/>
60+
> 5. Write code to output (*optional*)
61+
> <br/>&darr;<br/>
62+
> 6. Run *OR* Debug code (*optional*)
63+
64+
Users can pass a `--run` *or* `--debug` as an extra option to **run** or **debug** the resulting program of the compilation,
65+
using the tool's built-in interpreter and debugger, respectively.
66+
67+
## Optimising UPLC with the CLI
68+
69+
In this section we only focus on UPLC; a prerequisite is that you have already acquired (extracted) the UPLC code corresponding to your high-level source program.
70+
The process to *extract* plutus code varies depending on the source language you are using (Plutus Tx, Aiken, ...);
71+
if Plutus Tx is the source language, you can follow the instructions on [how to inspect compiled code](./inspecting.md#inspecting-the-compiled-code).
72+
73+
Since UPLC is the lowest-level language, compiling (*Step 3*) is not applicable for UPLC input programs and thus omitted
74+
&mdash; for actual compiling (converting) between different intermediate languages (PIR, TPLC) or serialisation formats (e.g. Flat, CBOR)
75+
see the [Advanced section](#converting) on converting between Plutus languages & formats.
76+
Instead, we can use the `plutus` tool to check for certain static errors in our UPLC, as in the example:
77+
78+
``` shell
79+
$ echo '(program 1.1.0 (lam x (lam y z)))' > const_err.uplc
80+
$ plutus const_err.uplc
81+
Error from the PLC compiler:
82+
Variable 2 is free at ()
83+
```
84+
85+
After fixing the error:
86+
87+
```
88+
$ echo '(program 1.1.0 (lam x (lam y x)))' | plutus --stdin
89+
Compilation succeeded, but no output file was written; use -o or --stdout.
90+
```
91+
92+
We can try to turn on some optimisations and write the resulting optimised code to standard output:
93+
94+
``` shell
95+
# no optimisations by default
96+
$ echo '(program 1.1.0 (force (delay (con unit ()))))' | plutus --stdin --stdout
97+
(program 1.1.0 (force (delay (con unit ()))))
98+
99+
$ echo '(program 1.1.0 (force (delay (con unit ()))))' | plutus --stdin --stdout -O1
100+
(program 1.1.0 (con unit ()))
101+
```
102+
103+
When multiple input programs (files) are passed to the CLI, the `plutus` tool
104+
will **check**, **compile** (not applicable for UPLC), and (optionally) **optimise** each program separately.
105+
After this is done for *all* programs, the tool gathers the result of each compilation/optimisation and combines them into a single output program.
106+
This is done by placing (interposing) an `Apply` term between the code of each compiled/optimised program, similar
107+
to Haskell's juxtaposition of a function and its series of applied arguments.
108+
109+
``` shell
110+
$ echo '(program 1.1.0 (lam x (lam y x)))' > func.uplc
111+
$ echo '(program 1.1.0 (con string "OK"))' > arg1.uplc
112+
$ echo '(program 1.1.0 (con bool True))' > arg2.uplc
113+
$ plutus func.uplc arg1.uplc arg2.uplc --stdout -O2
114+
(program 1.1.0 [ [ (lam x (lam y x)) (con string "OK") ] (con bool True) ])
115+
```
116+
117+
In the example above, even with all optimisations turned on (`-O2`), the `plutus` tool will not reduce
118+
the obviously reducible function applications in the output program; this is because the input programs are optimised only *individually* (separately). You can
119+
instruct the tool to perform an **extra** optimisation pass of the whole (combined) program
120+
by passing `--whole-opt` to the CLI &mdash; the given `-OLEVEL` will be taken into account also for this final extra pass (`-O2` in this case).
121+
122+
``` shell
123+
$ plutus func.uplc arg1.uplc arg2.uplc --stdout -O2 --whole-opt
124+
(program 1.1.0 (con string "OK"))
125+
```
126+
127+
## Running UPLC with the CLI {#running}
128+
129+
Certain errors in `uplc` code cannot be caught by the static checks (*Step 2*)
130+
because the UPLC language is untyped:
131+
132+
``` shell
133+
# Pseudocode: 1 + True
134+
$ echo "(program 1.1.0 [(builtin addInteger) (con integer 1) (con bool True)])" > typ_err.uplc
135+
$ plutus typ_err.uplc
136+
Compilation succeeded, but no output file was written; use -o or --stdout.
137+
```
138+
139+
Alternatively we can try to run the program using the tool's built-in *interpreter* and look for any runtime (type) errors.
140+
The `--run` option will invoke the interpreter passing to it the final output program.
141+
An execution that raised an error will show information about the error and in which term
142+
the error happened:
143+
144+
``` shell
145+
$ plutus typ_err.uplc --run
146+
Running the program: An error has occurred:
147+
Could not unlift a value:
148+
Type mismatch: expected: integer; actual: bool
149+
Caused by: addInteger 1 True
150+
```
151+
152+
Other times catching such (type) errors at runtime is not even possible. Consider the following
153+
example program which contains a type error but the execution nevertheless succeeds with the final
154+
evaluated result (term):
155+
156+
``` shell
157+
# Pseudocode: if True then "" else unit
158+
$ echo '(program 1.1.0 [(force (builtin ifThenElse)) (con bool True) (con string "") (con unit ())])' > if.uplc
159+
$ plutus if.uplc --run
160+
Running the program: Execution succeeded, final term:
161+
(con string "")
162+
Used budget: ExBudget {exBudgetCPU = ExCPU 204149, exBudgetMemory = ExMemory 901}
163+
```
164+
165+
> :pushpin: **NOTE**
166+
> The above example demonstrates that `uplc` &mdash; the language which actually *runs on the chain* &mdash;
167+
> is low-level and more akin to assembly. Users that are concerned about the safety of their smart contracts
168+
> are advised instead to develop in a higher-level (typed) language (e.g. Plutus Tx) which compiles down to `uplc`.
169+
170+
After plutus program's execution is completed (either succeeded or failed), the final used budget will be printed as well.
171+
Because the CLI tool employs the same `uplc` interpreter as the one that the Cardano node runs, you can be sure
172+
that the program's execution result&budget match *precisely* &mdash; assuming same program
173+
and cost model &mdash; the result&budget computed by the chain.
174+
175+
You can pass a maximum *CPU* and/or *Memory* budget that is allowed to be spent with the `--budget=CPU` or `-budget=,MEM` or `--budget=CPU,MEM` options; if given budget runs out, the execution will fail and stop earlier.
176+
If there is no CPU and/or MEM limit given, the budget is practically unlimited.
177+
178+
``` shell
179+
$ plutus if.uplc --run --budget=204148,903
180+
Running the program: An error has occurred:
181+
The machine terminated part way through evaluation due to overspending the budget.
182+
The budget when the machine terminated was:
183+
({cpu: -1
184+
| mem: 2})
185+
Negative numbers indicate the overspent budget; note that this only indicates the budget that was needed for the next step, not to run the program to completion.
186+
187+
$ plutus if.uplc --run --budget=,903
188+
Running the program: Execution succeeded, final term:
189+
(con string "")
190+
Remaining budget: ExBudget {exBudgetCPU = ExCPU 9223372036854571658, exBudgetMemory = ExMemory 2}
191+
Used budget: ExBudget {exBudgetCPU = ExCPU 204149, exBudgetMemory = ExMemory 901}
192+
```
193+
194+
> :pushpin: **NOTE**
195+
> Attempting to run a `tplc` target will use the `tplc` interpreter. Although
196+
> the `tplc` interpreter behaves the same as the default `uplc` interpreter (for *type correct* programs),
197+
> it comes with caveats: cannot execute `uplc` code,
198+
> cannot have budget accounting and budget limits, runs way slower and your program must be fully type correct.
199+
> The last point is not necessarily a caveat, but it diverges from the on-chain behavior:
200+
> the `tplc` interpreter accepts less programs than the chain (and the default `uplc` interpreter) would accept.
201+
> PIR target programs cannot be directly executed.
202+
203+
## Debugging UPLC with the CLI *(Experimental)* {#debugging}
204+
205+
> :pushpin: **NOTE**
206+
> The debugger is in a *preliminary* , *experimental* state. What is described below is
207+
> subject to change when new features are added to the debugger.
208+
209+
Another way to check for runtime errors or the execution budget
210+
is by firing up the tool's built-in debugger. Again, the debugger utilises underneath the same UPLC interpreter
211+
as the one the Cardano node runs, so you can be sure about its execution results and budget costs.
212+
The difference compared to "running the code" is that with the debugger you can step by step progress over the execution
213+
over your UPLC program's sub-terms, or interactively decide to pause the execution on specific UPLC sub-term(s) of interest.
214+
215+
> :pushpin: **NOTE**
216+
> Unlike the `--run` option that can execute both UPLC *and* TPLC target programs, the `--debug` option works *exclusively* for UPLC targets.
217+
218+
The `--debug` option will launch the debugger after
219+
the prior checking/compilation/optimisation steps of your input program(s) have been completed, for example:
220+
221+
``` shell
222+
$ plutus if.uplc -O1 --debug
223+
```
224+
225+
The debugger has a Terminal User Interface (TUI) with three windows that are kept automatically updated: (1)
226+
the compiled/optimised target UPLC program, (2) the log/trace messages, and (3) the (current) return value.
227+
228+
![TUI Debugger Screenshot](../../static/img/tui_debugger_screenshot.png)
229+
230+
You can interact (issue **Commands**) to the debugger by pressing keyboard shortcuts:
231+
232+
|Keyboard Shortcut|Command|
233+
|-----|-----|
234+
|?|Show help dialog|
235+
|Esc|Quit help dialog or debugger|
236+
|Ctrl+up/down/left/right|Resize window|
237+
|Tab|Switch focus to other window|
238+
|s|Step once the interpreter|
239+
240+
Unlike the `--run` option, the `step` command does not execute the program
241+
to completion. Instead, the underlying `uplc` interpreter is moved one "budgeting" step forward &mdash;
242+
the smallest step possible that gets accounted for and subtracted from the current budget.
243+
244+
After every such `step`, the debugger
245+
highlights in window (1) the code region (sub-term) which will be executed in the future (next `step`);
246+
A footer in the TUI screen will update to show the remaining budget.
247+
You can combine `--debug` with the `--budget=CPU,MEM` option to limit the starting total budget:
248+
the debugger fails the execution the moment the budget runs out, similar as to what happens with `--run`.
249+
250+
## Advanced: Converting between languages & formats {#converting}
251+
252+
``` shell
253+
$ plutus IN_FILES... -o OUT_FILE
254+
```
255+
256+
The filename extensions of the input files and output file
257+
determine the language and format of the *sources* and *target*, respectively.
258+
You can take a look at the [Table of Filename Extensions](#extensions) recognised by the `plutus` tool.
259+
Alternatively, you can [manually set](#override) (or override) the language&format.
260+
261+
You are allowed to mix and match different input *sources* as long
262+
as that make sense: the *target* must be a lower- or equal-level language to the sources.
263+
All sources will be then compiled and combined to the given target, for example:
264+
265+
``` shell
266+
$ plutus func.pir arg1.tplc arg2.uplc -o fully_applied.uplc
267+
268+
$ plutus func.pir arg1.tplc -o partially_applied.tplc
269+
$ plutus partially_applied.tplc arg2.uplc -o also_fully_applied.uplc
270+
271+
$ plutus arg2.uplc -o arg2.tplc # does not make sense, cannot lift to a higher level language
272+
$ plutus -O2 func.pir -o func_optimised.pir # makes sense for check / optimise
273+
```
274+
275+
It is worth to re-iterate that the input files are checked/compiled/optimised **separately**;
276+
this means that although the input programs are *individually* type correct, when *combined* to a specific target
277+
they may become type incorrect:
278+
279+
``` shell
280+
$ echo "(program 1.1.0 (lam x (con integer) [(builtin addInteger) x (con integer 1)]))" > inc.pir
281+
$ echo "(program 1.1.0 (con bool True))" > true.pir
282+
283+
$ plutus inc.pir true.pir -o applied.uplc # NO TYPE ERROR because target is untyped (UPLC)
284+
$ echo applied.uplc
285+
(program
286+
1.1.0
287+
[ (lam i [ [ (builtin addInteger) (con integer 1) ] i ]) (con bool True) ]
288+
)
289+
290+
$ plutus inc.pir true.pir -o applied.pir # TYPE ERROR because PIR target
291+
$ plutus inc.pir true.pir -o applied.tplc # TYPE ERROR because TPLC target
292+
```
293+
294+
### Filename Extensions {#extensions}
295+
296+
The following table lists the recognized extensions.
297+
298+
|Filename Extension|Format Type|Description|
299+
|---|---|---|
300+
|\**NO-EXTENSION\**|Textual|Untyped Plutus Core with Names|
301+
|.uplc|Textual|Untyped Plutus Core with Names|
302+
|.tplc|Textual|Typed Plutus Core with Names|
303+
|.pir|Textual|PIR with Names|
304+
|.data|Binary|Values of `Data` serialised in CBOR|
305+
|.data-txt|Textual|Values of `Data` in Haskell's `Show` format|
306+
|.uplc-flat|Binary|Untyped PlutusCore with NamedDeBruijn serialised in Flat|
307+
|.uplc-cbor|Binary|Untyped PlutusCore with DeBruijn serialised in CBOR <br/> (the on-chain format)|
308+
309+
> :pushpin: **NOTE** If file has no extension or in case of `--stdin` / `--stdout`, the extension is assumed to be `.uplc`
310+
311+
### Manually set language&format {#override}
312+
313+
If the extension cannot be determined (missing / `--stdin` / `--stdout`)
314+
or you would like to override the recognised extension,
315+
you may use the `-x .EXTENSION` option (with or without the leading dot `.` taken from [Table](#extensions) above) to manually set the
316+
extension for the given file(s):
317+
318+
``` shell
319+
$ plutus file1 -x pir file2 file3
320+
```
321+
322+
Note `-x` is positional: it applies its effect to all files after the option till the end or another `-x` is reached.
323+
In the example above the effect (set to `pir`) will not apply to `file1`, but will apply to `file2`, `file3` **and** target.
324+
Possible filename extensions on `file2` and `file3` will be ignored. Using multiple invocations of `-x` may come handy
325+
for mixing different sources or setting the output target. The next example sets the first two files
326+
to be `pir`, the following two to `tplc`, and the target (the last invocation of `-x`) to `uplc`.
327+
328+
``` shell
329+
$ plutus -x pir Pir_File Also_Pir -x tplc Now_Tplc Also_Tplc -x uplc
330+
```
331+
332+
In case `-x` is not enough and the *format* is more complex because it contains a non-default variable-naming scheme or annotations,
333+
you may extra specify the `-n NAMING` and `-a ANNOTATION` options to override the defaults.
334+
335+
|-n Short Option|-n Long Option|Description|
336+
|---|---|---|
337+
|-n n|-n name|Use descriptive textual names for variables|
338+
|-n d|-n debruijn|Use debruijn indices for variables|
339+
|-n nd|-n named-debruijn|Use name with debruijn index for variables: "name-index"|
340+
341+
|-a Option|Description|
342+
|---|---|
343+
|-a unit|Code does not contain any annotations (default)|
344+
|-a srcspan|Code is annotated with source spans|
345+
346+
The `-n` and `-a` options are also positional.
347+
348+
### Pretty-printing output
349+
350+
If the output's format type is *textual* (see the [Filename Extensions Table](#extensions)) the compiled code
351+
will be printed to the designated output (file or stdout) in a "pretty" format.
352+
You can change how the output's looks by specifying a different `-p STYLE` style (defaults to `classic`).
353+
354+
|-p Option|Description|
355+
|---|---|
356+
|-p classic|Lisp-like syntax with unique variable names (default)|
357+
|-p classic-simple|Lisp-like syntax with ambiguous (no unique) variable names|
358+
|-p readable|Succinct syntax with unique variable names|
359+
|-p readable-simple|Succinct syntax with ambiguous (no unique) variable names|
360+
361+
362+
``` shell
363+
$ plutus inc.pir -x pir --stdout
364+
(program
365+
1.1.0 (lam x-0 (con integer) [ [ (builtin addInteger) x-0 ] (con integer 1) ])
366+
)
367+
368+
$ plutus inc.pir -x pir --stdout --pretty=readable-simple
369+
program 1.1.0 (\(x : integer) -> addInteger x 1)
370+
```
371+
372+
> :pushpin: **NOTE** Specifying a textual *output* with pretty style other than the default (classic) may not be possible to be read
373+
back again (as textual *input* this time) in the CLI.
374+
375+
Using the standard output to capture the (pretty-printed) output program is safe,
376+
because the tool's logs and error messages are sent by default to `stderr`.
377+
You can of course silence those messages using `--quiet` option, or instead
378+
increase their rate using `--verbose` (for tracing through the compilation process).
78.2 KB
Loading

0 commit comments

Comments
 (0)