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 programs (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 by the Plutus Tx compiler. 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 PlutusTx.TH.compile is TExpQ a TExpQ (CompiledCode a). This is just what we already said:

  • TExpQ a is a quoted representing a program of type a.

  • TExpQ (CompiledCode a) is quote representing a compiled Plutus Core program.

Note

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

Writing basic Plutus Tx programs

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

{-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:target-version=1.0.0 #-}

module BasicPlutusTx where

import PlutusCore.Default qualified as PLC
import PlutusCore.Version (plcVersion100)
-- Main Plutus Tx module.
import PlutusTx
-- Additional support for lifting.
import PlutusTx.Lift
-- Builtin functions.
import PlutusTx.Builtins
-- The Plutus Tx Prelude, discussed further below.
import PlutusTx.Prelude

-- Setup for doctest examples.

-- $setup
-- >>> import Tutorial.PlutusTx
-- >>> import PlutusTx
-- >>> import PlutusCore
-- >>> import 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 Integer
{- 'compile' turns the 'TExpQ Integer' into a
  'TExpQ (CompiledCode 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 Integer at compile time, which we spliced into our Haskell program. We can inspect the generated program 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 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 (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 '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 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 (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 (EndDate -> Integer -> Bool)
pastEnd = $$(compile [|| \(end::EndDate) (current::Integer) -> case end of
    Fixed n -> n `lessThanEqualsInteger` 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 (EndDate -> Integer -> Bool)
pastEnd' = $$(compile [|| \(end::EndDate) (current::Integer) -> case end of
    Fixed n -> n < current
    Never   -> False
  ||])

The Plutus Tx Prelude

The 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 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 PlutusTx.Builtins.error built-in deserves a special mention. 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 (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 Integer
addOneToN n =
    addOne
    -- 'unsafeApplyCode' applies one 'CompiledCode' to another.
    `unsafeApplyCode`
    -- 'liftCode' lifts the argument 'n' into a
    -- 'CompiledCode Integer'. It needs a version to tell it what
    -- Plutus Core language version to target, if you don't care you
    -- can use 'liftCodeDef'
    liftCode plcVersion100 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 PlutusTx.liftCode function. To use this, a type must have an instance of the PlutusTx.Lift class. For your own datatypes you should generate these with the PlutusTx.makeLift TH function from PlutusTx.Lift.

Note

PlutusTx.liftCode is relatively unsafe because it ignores any errors that might occur from lifting something that might not be supported. There is a 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 Bool
pastEndAt end current =
    pastEnd
    `unsafeApplyCode`
    liftCode plcVersion100 end
    `unsafeApplyCode`
    liftCode plcVersion100 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))
)
-}

Plutus Tx Compiler Options

A number of options can be passed to the Plutus Tx compiler. See Plutus Tx Compiler Options for details.