Skip to content

Latest commit

 

History

History
446 lines (359 loc) · 17.5 KB

builtin-data.md

File metadata and controls

446 lines (359 loc) · 17.5 KB

What is Data/BuiltinData?

This document serves as a guide for understanding and working with BuiltinData/Data. It's primarily meant for Pluto users and Plutarch developers/contributors.

Note: If you spot any mistakes/have any related questions that this guide lacks the answer to, please don't hesitate to raise an issue. The goal is to have high quality documentation for Pluto and Plutarch users!

Friendly Reminder: For general Plutarch usage, you won't need to use ANY of the methods here. There are safer interfaces provided to do that (see: PIsDataRepr, PAsData).

This is what BuiltinData looks like-

data Data
  = Constr Integer [Data]
  | Map [(Data, Data)]
  | List [Data]
  | I Integer
  | B ByteString

Aside: The direct Plutarch synonym to BuiltinData/Data is PData. However, you should prefer PAsData as it also preserves type information. The functions you need to manually work with these are exported from Plutarch.Builtin.

We discuss each of these constructors, and how to work with them, in the following sections.

Common Plutarch imports: Plutarch, Plutarch.Builtin, qualified PlutusCore as PLC

What is Constr?

Constr is responsible for representing most Haskell ADTs. It's a sum of products representation. Constr 0 [] - designates the 0th constructor with no fields. Each field is represented as a Data value.

For example, when you implement IsData for your Haskell ADT using-

import PlutusTx

data Foo = Bar Integer | Baz ByteString

PlutusTx.makeIsDataIndexed
  ''Foo
  [ ('Bar, 0),
    ('Baz, 1)
  ]

It essentially means that PlutusTx.toData (Bar 42) translates to Constr 0 [PlutusTx.toData 42]. Whereas PlutusTx.toData (Baz "A") translates to Constr 1 [PlutusTx.toData "A"].

Aside: The integer literal, list literal, and (byte-)string literals you see in that Haskell code are the Plutus Tx builtin types.

Let's look at the IsData implementation for the Maybe type-

import PlutusTx

PlutusTx.makeIsDataIndexed ''Maybe [('Just,0),('Nothing,1)]

This means that PlutusTx.toData Nothing translates to Constr 1 [] - and PlutusTx.toData (Just x) translates to Constr 0 [PlutusTx.toData x].

IMPORTANT: newtype constructors (generally) don't persist when you do toData. Their inner value's toData result is yielded instead.

Working with Constr

Now, when you receive a BuiltinData/Data inside your function - if you are sure what you have received is a Constr - you can work with it accordingly.

The builtin function used to take apart a Constr data value, is UnConstrData.

Pluto Usage

-- test.pluto
UnConstrData (data sigma0.[1, 0x4d])

This will yield a pair, the first member of which, is an integer representing the constructor id. The second member is a list of fields associated with the constructor.

$ pluto run test.pluto
Constant () (Some (ValueOf pair (integer) (list (data)) (0,[I 1,B "M"])))

You can extract the constructor id using FstPair, you must force it twice first-

-- test.pluto
let
  x = UnConstrData (data sigma0.[1, 0x4d])
in ! ! FstPair x
$ pluto run test.pluto
Constant () (Some (ValueOf integer 0))

As you would expect, retrieving the fields is similar. All you need is SndPair, which also needs two forces.

-- test.pluto
let
  x = UnConstrData (data sigma0.[1, 0x4d])
in ! ! SndPair x
$ pluto run test.pluto
Constant () (Some (ValueOf list (data) [I 1,B "M"]))

It results in a builtin list of Data elements. See Working with Builtin Lists.

How about we load it up in Haskell? Let's make a Pluto function that returns the constructor id of the given ADT!

-- test.pluto
\x -> ! ! FstPair (UnConstrData x)

Load it up and bind it to a variable!

plutoSc :: Script
> [PlutusTx.toData (Nothing :: Maybe Integer)] `evalWithArgs` plutoSc
Right (ExBudget {exBudgetCPU = ExCPU 597830, exBudgetMemory = ExMemory 1164},[],Constant () (Some (ValueOf integer 1)))
> [PlutusTx.toData (Just 1 :: Maybe Integer)] `evalWithArgs` plutoSc
Right (ExBudget {exBudgetCPU = ExCPU 597830, exBudgetMemory = ExMemory 1164},[],Constant () (Some (ValueOf integer 0)))

Plutarch Usage

In Plutarch, pasConstr is the synonym to UnConstrData-

pasConstr :: Term s (PData :--> PBuiltinPair PInteger (PBuiltinList PData))
pasConstr = punsafeBuiltin PLC.UnConstrData

This will yield a pair, the first member of which, is the constructor id. You can extract the constructor id using pfstBuiltin, which is a synonym to FstPair-

pfstBuiltin :: Term s (PBuiltinPair a b :--> a)
pfstBuiltin = phoistAcyclic $ pforce . pforce . punsafeBuiltin $ PLC.FstPair

Aside: Recall that FstPair requires 2 forces.

Here's a Plutarch function that takes in a BuiltinData/Data, assumes it's a Constr, and returns its constructor id-

import Plutarch.Builtin
import Plutarch.Integer
import Plutarch.Prelude

constructorIdOf :: Term s (PData :--> PInteger)
constructorIdOf = plam $ \x -> pfstBuiltin #$ pasConstr # x

As you would expect, retrieving the fields is also trivial once you're armed with the above knowledge-

fieldsOf :: Term s (PData :--> PBuiltinList PData)
fieldsOf = plam $ \x -> psndBuiltin #$ pasConstr # x

Let's test those functions!

> constructorIdOf `evalWithArgsT` [PlutusTx.toData (Nothing :: Maybe ())]
Right (Program () (Version () 1 0 0) (Constant () (Some (ValueOf integer 1))))

Aside: You can find the definition of evalWithArgsT above - Compiling and Running.

That's a roundabout way of saying "1". But you get the idea. In this case, the constructor id of Nothing, is indeed 1.

> fieldsOf `evalWithArgsT` [PlutusTx.toData (Nothing :: Maybe ())]
Right (Program () (Version () 1 0 0) (Constant () (Some (ValueOf list (data) []))))

And that's another roundabout way of saying []! That is, no fields associated with the constructor.

Extracting fields from values they would actually be present in, is also straight-forward now-

> fieldsOf `evalWithArgsT` [PlutusTx.toData (Just 1 :: Maybe Integer)]
Right (Program () (Version () 1 0 0) (Constant () (Some (ValueOf list (data) [I 1]))))

There we go! [I 1] - We'll discuss the I constructor in its own section below. But it's just a BuiltinData/Data value. It's not Constr though! It's I.

Building Constr data

Pluto

You can create Constr data values using sigma literals.

You can also use the ConstrData builtin function to create Constr data values. It takes 2 arguments - the constructor id, and its fields as a list of Data elements.

ConstrData 0 (MkNilData ())

is the same as data sigma0.[].

Aside: What's that MkNilData ()? That's how you create a nil list of Data elements! MkNilData takes in a unit and returns a nil list of Data. You can add more Data elements to it using MkCons. See Working with Builtin Lists.

Plutarch

You can manually build Constr data values using the ConstrData builtin like above-

pconstrData :: Term s (PInteger :--> PBuiltinList PData :--> PData)
pconstrData = punsafeBuiltin PLC.ConstrData

Or, you can avoid a builtin function call and build the PData directly using pconstant-

import PlutusTx (Data (Constr))

> pconstant @PData (Constr 0 [])

What is Map?

The Map constructor is for """Haskell maps""". In the Plutus world, maps are apparently just assoc lists. You've seen assoc lists already; they're just a list of pairs. These pairs consist of two Data values.

The common example of this is Value. But anytime you see Plutus Assoc Maps - you can be sure that it's actually going to end up as a Map data.

Working with Map

You can unwrap the Map data value to obtain the inner builtin list of builtin pairs with the UnMapData builtin function. You can then work with the resulting builtin lists. It contains pairs of Data. See Working with Builtin Lists.

Pluto Usage

-- test.pluto
UnMapData (data { 1 = 0xfe })
$ pluto run test.pluto
Constant () (Some (ValueOf list (pair (data) (data)) [(I 1,B "\254")]))

Plutarch Usage

In Plutarch, pasMap is the synonym to UnMapData-

pasMap :: Term s (PData :--> PBuiltinList (PBuiltinPair PData PData))
pasMap = punsafeBuiltin PLC.UnMapData

Building Map data

Pluto

You can build Map data values using map literals.

You can also use the MapData builtin function to create Map data values. It takes in a builtin list of builtin pairs of Data.

MapData (MkNilPairData ())

This is the same as data {}.

Aside: What's that MkNilPairData ()? Similar to MkNilData. This one is for creating a nil list of Data pairs. You can add more Data pairs to it using MkCons. See Working with Builtin Lists.

Plutarch

Much like above, and in the case of Constr, you can use the MapData builtin. Or you can use pconstant.

import PlutusTx (Data (Map, I, B))

> pconstant @PData (Map [(I 1, B "x")])

What is List?

The List constructor is a wrapper around a builtin list of Data. Notice that it is specifically a monomorphic list. Its elements are of type Data. PlutusTx.toData [1, 2, 3] translates to List [I 1, I 2, I 3]. Those elements are I data values.

One interesting thing to note here is that when you convert a Haskell list to a Data value, and it ends up as a List data value, all the elements within the builtin list will be the same "species" of Data. What does that mean? Well, Haskell lists are homogenous, e.g- [Int], turning [Int] into a Data consists of two steps-

  • Map PlutusTx.toData over all elements of the list.
  • Wrap the list into a List data value.

PlutusTx.toData on an Int value will just yield an I data value. Due to the fact that lists are homogenous, all of those Int elements will just be I data value, so in the end - the Data representation of [1, 2, 3] looks like - List [I 1, I 2, I 3]. The data values have the same "species"! It is totally and completely valid to create a List [I 1, B "f", Constr 0 []] in Plutus Core - but you're not going to get that botched version from a Haskell list (and therefore, most of your data types)!

Working with List

You can unwrap the List data value to obtain the inner builtin list using the UnListData builtin function. Then, you can use the resultant builtin list with the builtin functions that work on lists. See Working with Builtin Lists.

Pluto Usage

-- test.pluto
UnListData (data [1, 0xab, { 42 = [1, 2] }])
$ pluto run test.pluto
Constant () (Some (ValueOf list (data) [I 1,B "\171",Map [(I 42,List [I 1,I 2])]]))

Plutarch Usage

In Plutarch, pasList is the synonym to UnListData.

pasList :: Term s (PData :--> PBuiltinList PData)
pasList = punsafeBuiltin PLC.UnListData

Building List data

Pluto

You can build List data values using list literals.

You can also use the ListData builtin function to create List data values. It takes in a builtin list of Data elements.

ListData (MkNilData ())

This is the same as data [].

Plutarch

Much like before, you can use the ListData builtin. Or you can use pconstant.

import PlutusTx (Data (List, I))

> pconstant @PData (List [I 1, I 2])

What is I?

The I constructor wraps a builtin integer to create a Data value. When you do PlutusTx.toData 123 - you obtain an I data.

Working with I

You can unwrap an I data value to obtain the inner builtin integer using the UnIData builtin function.

Pluto Usage

-- test.pluto
UnIData (data 42)
$ pluto run test.pluto
Constant () (Some (ValueOf integer 1))

Plutarch Usage

In Plutarch, pasInt is the synonym to UnIData.

pasInt :: Term s (PData :--> PInteger)
pasInt = punsafeBuiltin PLC.UnIData

Building I data

Pluto

You can build I data values using integer literals preceded by data.

You can also use the IData builtin function to create I data values. It takes in a builtin integer.

IData 42

This is the same as data 42.

Plutarch

Much like before, you can use the IData builtin. Or you can use pconstant.

import PlutusTx (Data (I))

> pconstant @PData (I 42)

What is B?

Similar to I, the B constructor wraps a builtin bytestring to create a Data value.

Working with B

You can unwrap a B data value to obtain the inner builtin bytestring using the UnBData builtin function.

Pluto Usage

-- test.pluto
UnBData (data 0x4f)
$ pluto run test.pluto
Constant () (Some (ValueOf bytestring "O"))

Plutarch Usage

In Plutarch, pasByteStr is the synonym to UnBData.

pasByteStr :: Term s (PData :--> PByteString)
pasByteStr = punsafeBuiltin PLC.UnBData

Building B data

Pluto

You can build B data values using integer literals preceded by data.

You can also use the BData builtin function to create B data values. It takes in a builtin integer.

BData 0x42

This is the same as data 0x42.

Plutarch

Much like before, you can use the BData builtin. Or you can use pconstant.

import PlutusTx (Data (B))

> pconstant @PData (B 42)

Wild Card!

What happens when you don't know what kind of Data you have? In many cases, you know the exact structure of the Data you receive (e.g ScriptContext structure is known). But if you have no way to know whether the Data is a Constr, or Map, or List, and so on - you can use the ChooseData builtin function. It takes one force!

Pluto Usage

-- test.pluto
! ChooseData (data 42) 0 1 2 3 4
$ pluto run test.pluto
Constant () (Some (ValueOf integer 3))

Each argument corresponds to a branch. Details are discussed at Plutus Core builtin functions reference.

In this case, the Data value had an I constructor (data 42 creates an I data). That corresponds to the 5th argument, which was 3.

Aside: As is the case for all other function calls, all those arguments will be evaluated strictly. You should use delay to avoid this.

Plutarch Usage

Here's how you could implement a chooseData synonym-

pchooseData :: Term s (PBuiltinData -> a -> a -> a -> a -> a -> a)
pchooseData = phoistAcyclic $ pforce $ punsafeBuiltin PLC.ChooseData

It works all the same as above!

Useful Links