|
| 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/>↓<br/> |
| 33 | +> 2. Check program(s) for certain errors |
| 34 | +> <br/>↓<br/> |
| 35 | +> 3. Compile to a lower-level *target* language |
| 36 | +> <br/>↓<br/> |
| 37 | +> 4. Optimise the compiled code (*optional*) |
| 38 | +> <br/>↓<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/>↓<br/> |
| 60 | +> 5. Write code to output (*optional*) |
| 61 | +> <br/>↓<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 | +— 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 — 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` — the language which actually *runs on the chain* — |
| 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* — assuming same program |
| 173 | +and cost model — 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 | + |
| 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 — |
| 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). |
0 commit comments