Skip to content

Latest commit

 

History

History
266 lines (190 loc) · 10.5 KB

plutus-tx.rst

File metadata and controls

266 lines (190 loc) · 10.5 KB

Using Plutus Tx

Plutus applications are written as a single Haskell program, which describes both the code that runs off the chain (on a user’s computer, or in their wallet, for example), and on the chain as part of transaction validation.

The parts of the program that describe the on-chain code are still just Haskell, but they are compiled into :term:`Plutus Core`, rather than into the normal compilation target language. We refer to them as :term:`Plutus Tx` (where Tx indicates that these components usually go into transactions).

Warning

Strictly speaking, while the majority of simple Haskell will work, only a subset of Haskell is supported inside Plutus Tx blocks. The Plutus Tx compiler will tell you if you are attempting to use an unsupported component.

The key technique that we use to implement Plutus Tx is called staged metaprogramming, which means that the main Haskell program generates another program (in this case, the Plutus Core program that will run on the blockchain). Plutus Tx is the mechanism we use to write those programs, but since Plutus Tx is just part of the main Haskell program, we can share types and definitions between the two.

Template Haskell preliminaries

Plutus Tx uses Haskell's metaprogramming support, Template Haskell, for two main reasons:

  • Template Haskell enables us to work at compile time, which is when we do Plutus Tx compilation.
  • It allows us to wire up the machinery that invokes the Plutus Tx compiler.

Template Haskell is very versatile, but we only use a few features.

Template Haskell begins with quotes. A Template Haskell quote is a Haskell expression e inside special brackets [|| e ||]. It has type Q (TExp a) where e has type a. TExp a is a representation an expression of type a, i.e. the syntax of the actual Haskell expression that was quoted. The quote lives in the type Q of quotes, which isn’t very interesting for us.

Note

There is also an abbreviation TExpQ a for Q (TExp a), which avoids some parentheses.

You can splice a quote into your program using the $$ operator. This inserts the syntax represented by the quote into the program at the point where the splice is written.

Simply put, a quote allows us to talk about Haskell programs as values.

The Plutus Tx compiler compiles Haskell expressions (not values!), so naturally it takes a quote (representing an expression) as an argument. The result is a new quote, this time for a Haskell program that represents the compiled program. In Haskell, the type of :hsobj:`Language.PlutusTx.TH.compile` is TExpQ a → TExpQ (CompiledCode PLC.DefaultUni () a). This is just what we already said:

  • TExpQ a is a quoted representing a program of type a.
  • TExprQ (CompiledCode PLC.DefaultUni () a) is quote representing a compiled Plutus Core program.

Note

:hsobj:`Language.PlutusTx.CompiledCode` also has a type parameter a, which corresponds to the type of the original expression. This lets us "remember" the type of the original Haskell program we compiled.

Since :hsobj:`Language.PlutusTx.TH.compile` produces a quote, to use the result we need to splice it back into our program. The Plutus Tx compiler runs when compiling the main program, and the compiled program will be inserted into the main program.

This is all you need to know about the Template Haskell! We often use the same simple pattern: make a quote, immediately call :hsobj:`Language.PlutusTx.TH.compile`, and then splice the result back in.

Writing basic PlutusTx programs

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK1
   :end-before: BLOCK2

This simple program just evaluates to the integer 1.

Note

The examples that show the Plutus Core generated from compilation include doctests. The syntax of Plutus Core might look unfamiliar, since this syntax is used for the 'assembly language, which means you don't need to inspect the compiler's output. But for the purpose of this tutorial, it is useful to understand what is happening.

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK2
   :end-before: BLOCK3

We can see how the metaprogramming works: the Haskell program 1 was turned into a CompiledCode PLC.DefaultUni () Integer at compile time, which we spliced into our Haskell program. We can inspect at runtime to see the generated Plutus Core (or to put it on the blockchain).

We also see the standard usage pattern: a TH quote, wrapped in a call to :hsobj:`Language.PlutusTx.TH.compile`, wrapped in a $$ splice. This is how all our Plutus Tx programs are written.

This is a slightly more complex program. It includes the identity function on integers.

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK3
   :end-before: BLOCK4

Functions and datatypes

You can use functions inside your expression. In practice, you will usually want to define the entirety of your Plutus Tx program as a definition outside the quote, and then simply call it inside the quote.

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK4
   :end-before: BLOCK5

We can use normal Haskell datatypes and pattern matching freely:

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK5
   :end-before: BLOCK6

Unlike functions, datatypes do not need any kind of special annotation to be used inside a quote, hence we can use types like Maybe from the Haskell Prelude. This works for your own datatypes too!

Here’s a small example with a datatype representing a potentially open-ended end date.

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK6
   :end-before: BLOCK7

We could also have defined the pastEnd function as a separate INLINABLE binding and just referred to it in the quote, but in this case, it’s small enough to just write in place.

Typeclasses

So far we have used functions like lessThanEqInteger for comparing Integer s, which is much less convenient than < from the standard Haskell Ord typeclass.

Plutus Tx does support typeclasses, but we cannot use many of the standard typeclasses, since we require their class methods to be INLINABLE, and the implementations for types such as Integer use the Plutus Tx built-ins.

Redefined versions of many standard typeclasses are available in the Plutus Tx Prelude. As such, you should be able to use most typeclass functions in your Plutus Tx programs.

For example, here is a version of the pastEnd function using <. This will be compiled to exactly the same code as the previous definition.

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK7
   :end-before: BLOCK8

The Plutus Tx Prelude

The :hsmod:`Language.PlutusTx.Prelude` module is a drop-in replacement for the normal Haskell Prelude, with some redefined functions and typeclasses that makes it easier for the Plutus Tx compiler to handle (i.e.``INLINABLE``).

Use the Plutus Tx Prelude for code that you expect to compile with the Plutus Tx compiler. All the definitions in the Plutus Tx Prelude include working Haskell definitions, which means that you can use them in normal Haskell code too, although the Haskell Prelude versions will probably perform better.

To use the Plutus Tx Prelude, use the NoImplicitPrelude language pragma and import :hsmod:`Language.PlutusTx.Prelude`.

Plutus Tx includes some built-in types and functions for working with primitive data (integers and bytestrings), as well as a few special functions. These types are also exported from the Plutus Tx Prelude.

The :hsobj:`Language.PlutusTx.Builtins.error` built-in deserves a special mention. :hsobj:`Language.PlutusTx.Builtins.error` causes the transaction to abort when it is evaluated, which is one way to trigger a validation failure.

Lifting values

So far we’ve seen how to define pieces of code statically (when you compile your main Haskell program), but you might want to generate code dynamically (that is, when you run your main Haskell program). For example, you might be writing the body of a transaction to initiate a crowdfunding smart contract, which would need to be parameterized by data determining the size of the goal, the campaign start and end times, etc.

We can do this in the same way that we parameterize code in functional programming: write the static code as a function and provide the argument later to configure it.

In our case, there is a slight complication: we want to make the argument and apply the function to it at runtime. Plutus Tx addresses this through lifting. Lifting enables the use of the same types, both inside your Plutus Tx program and in the external code that uses it.

Note

In this context, runtime means the runtime of the main Haskell program, not when the Plutus Core runs on the chain. We want to configure our code when the main Haskell program runs, as that is when we will be getting user input.

In this example, we add an add-one function.

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK8
   :end-before: BLOCK9

Now, suppose we want to apply this to 4 at runtime, giving us a program that computes to 5. We need to lift the argument (4) from Haskell to Plutus Core, and then we need to apply the function to it.

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK9
   :end-before: BLOCK10

We lifted the argument using the :hsobj:`Language.PlutusTx.liftCode` function. To use this, a type must have an instance of the :hsobj:`Language.PlutusTx.Lift` class. In practice, you should generate these with the :hsobj:`Language.PlutusTx.makeLift` TH function from :hsmod:`Language.PlutusTx.Lift`.

Note

:hsobj:`Language.PlutusTx.liftCode` is relatively unsafe because it ignores any errors that might occur from lifting something that might not be supported. There is a :hsobj:`Language.PlutusTx.safeLiftCode` if you want to explicitly handle these occurrences.

The combined program applies the original compiled lambda to the lifted value (notice that the lambda is a bit complicated now, since we have compiled the addition into a built-in).

Here’s an example with our custom datatype. The output is the encoded version of False.

.. literalinclude:: PlutusTx.hs
   :start-after: BLOCK10
   :end-before: BLOCK11