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 Plutus Core, rather than into the normal compilation target language. We refer to them as 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 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

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 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 Language.PlutusTx.TH.compile, and then splice the result back in.

Writing basic PlutusTx programs

-- Necessary language extensions for the Plutus Tx compiler to work.
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE NoImplicitPrelude   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}
module PlutusTx where

import qualified Language.PlutusCore.Universe as PLC
-- Main Plutus Tx module.
import           Language.PlutusTx
-- Additional support for lifting.
import           Language.PlutusTx.Lift
-- Builtin functions.
import           Language.PlutusTx.Builtins
-- The Plutus Tx Prelude, discussed further below.
import           Language.PlutusTx.Prelude

-- Setup for doctest examples.

-- $setup
-- >>> import Tutorial.PlutusTx
-- >>> import Language.PlutusTx
-- >>> import Language.PlutusCore
-- >>> import Language.PlutusCore.Evaluation.Machine.Ck
-- >>> import Data.Text.Prettyprint.Doc

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.

integerOne :: CompiledCode PLC.DefaultUni Integer
{- 'compile' turns the 'TExpQ Integer' into a
  'TExpQ (CompiledCode PLC.DefaultUni Integer)' and the splice
  inserts it into the program. -}
integerOne = $$(compile
    {- The quote has type 'TExpQ Integer'.
      We always use unbounded integers in Plutus Core, so we have to pin
      down this numeric literal to an ``Integer`` rather than an ``Int``. -}
    [|| (1 :: Integer) ||])

{- |
>>> pretty $ getPlc integerOne
(program 1.0.0
  (con 1)
)
-}

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 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.

integerIdentity :: CompiledCode PLC.DefaultUni (Integer -> Integer)
integerIdentity = $$(compile [|| \(x:: Integer) -> x ||])

{- |
>>> pretty $ getPlc integerIdentity
(program 1.0.0
  (lam ds (con integer) ds)
)
-}

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.

{- Functions which will be used in Plutus Tx programs should be marked
  with GHC’s 'INLINABLE' pragma. This is usually necessary for
  non-local functions to be usable in Plutus Tx blocks, as it instructs
  GHC to keep the information that the Plutus Tx compiler needs. While
  you may be able to get away with omitting it, it is good practice to
  always include it. -}
{-# INLINABLE plusOne #-}
plusOne :: Integer -> Integer
{- 'addInteger' comes from 'Language.PlutusTx.Builtins', and is
  mapped to the builtin integer addition function in Plutus Core. -}
plusOne x = x `addInteger` 1

{-# INLINABLE myProgram #-}
myProgram :: Integer
myProgram =
    let
        -- Local functions do not need to be marked as 'INLINABLE'.
        plusOneLocal :: Integer -> Integer
        plusOneLocal x = x `addInteger` 1

        localTwo = plusOneLocal 1
        externalTwo = plusOne 1
    in localTwo `addInteger` externalTwo

functions :: CompiledCode PLC.DefaultUni Integer
functions = $$(compile [|| myProgram ||])

{- We’ve used the CK evaluator for Plutus Core to evaluate the program
  and check that the result was what we expected. -}
{- |
>>> pretty $ unsafeEvaluateCk $ toTerm $ getPlc functions
(con 4)
-}

We can use normal Haskell datatypes and pattern matching freely:

matchMaybe :: CompiledCode PLC.DefaultUni (Maybe Integer -> Integer)
matchMaybe = $$(compile [|| \(x:: Maybe Integer) -> case x of
    Just n  -> n
    Nothing -> 0
  ||])

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.

-- | Either a specific end date, or "never".
data EndDate = Fixed Integer | Never

-- | Check whether a given time is past the end date.
pastEnd :: CompiledCode PLC.DefaultUni (EndDate -> Integer -> Bool)
pastEnd = $$(compile [|| \(end::EndDate) (current::Integer) -> case end of
    Fixed n -> n `lessThanEqInteger` current
    Never   -> False
  ||])

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.

-- | Check whether a given time is past the end date.
pastEnd' :: CompiledCode PLC.DefaultUni (EndDate -> Integer -> Bool)
pastEnd' = $$(compile [|| \(end::EndDate) (current::Integer) -> case end of
    Fixed n -> n < current
    Never   -> False
  ||])

The Plutus Tx Prelude

The 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 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 Language.PlutusTx.Builtins.error built-in deserves a special mention. 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.

addOne :: CompiledCode PLC.DefaultUni (Integer -> Integer)
addOne = $$(compile [|| \(x:: Integer) -> x `addInteger` 1 ||])

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.

addOneToN :: Integer -> CompiledCode PLC.DefaultUni Integer
addOneToN n =
    addOne
    -- 'applyCode' applies one 'CompiledCode' to another.
    `applyCode`
    -- 'liftCode' lifts the argument 'n' into a
    -- 'CompiledCode PLC.DefaultUni Integer'.
    liftCode n

{- |
>>> pretty $ getPlc addOne
(program 1.0.0
  [
    (lam
      addInteger
      (fun (con integer) (fun (con integer) (con integer)))
      (lam ds (con integer) [ [ addInteger ds ] (con 1) ])
    )
    (lam
      arg
      (con integer)
      (lam arg (con integer) [ [ (builtin addInteger) arg ] arg ])
    )
  ]
)
>>> let program = getPlc $ addOneToN 4
>>> pretty program
(program 1.0.0
  [
    [
      (lam
        addInteger
        (fun (con integer) (fun (con integer) (con integer)))
        (lam ds (con integer) [ [ addInteger ds ] (con 1) ])
      )
      (lam
        arg
        (con integer)
        (lam arg (con integer) [ [ (builtin addInteger) arg ] arg ])
      )
    ]
    (con 4)
  ]
)
>>> pretty $ unsafeEvaluateCk $ toTerm program
(con 5)
-}

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

Note

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 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.

-- 'makeLift' generates instances of 'Lift' automatically.
makeLift ''EndDate

pastEndAt :: EndDate -> Integer -> CompiledCode PLC.DefaultUni Bool
pastEndAt end current =
    pastEnd
    `applyCode`
    liftCode end
    `applyCode`
    liftCode current

{- |
>>> let program = getPlc $ pastEndAt Never 5
>>> pretty $ unsafeEvaluateCk $ toTerm program
(abs
  out_Bool (type) (lam case_True out_Bool (lam case_False out_Bool case_False))
)
-}