Plutus Tuto – Part 2

After having a quick look at some basic functionalities of the Wallet API, let’s have a look at some actual Plutus code, and how your wallet will interact with the blockchain. In this part, we will have a look at the “Game” tutorial available on the playground.

Here is the code:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
module Language.PlutusTx.Coordination.Contracts.Game where

import qualified Language.PlutusTx            as PlutusTx
import qualified Language.PlutusTx.Prelude    as P
import           Ledger
import           Ledger.Validation
import           Wallet
import           Playground.Contract

import qualified Data.ByteString.Lazy.Char8   as C

data HashedString = HashedString ByteString

PlutusTx.makeLift ''HashedString

-- create a data script for the guessing game by hashing the string
-- and lifting the hash to its on-chain representation
mkDataScript :: String -> DataScript
mkDataScript word =
    let hashedWord = plcSHA2_256 (C.pack word)
    in  DataScript (Ledger.lifted (HashedString hashedWord))

data ClearString = ClearString ByteString

PlutusTx.makeLift ''ClearString

-- create a redeemer script for the guessing game by lifting the
-- string to its on-chain representation
mkRedeemerScript :: String -> RedeemerScript
mkRedeemerScript word =
    let clearWord = C.pack word
    in RedeemerScript (Ledger.lifted (ClearString clearWord))

gameValidator :: ValidatorScript
gameValidator = ValidatorScript (Ledger.fromCompiledCode $(PlutusTx.compile [||
    \(ClearString guess) (HashedString actual) (p :: PendingTx') ->

    if $(P.equalsByteString) actual ($(P.sha2_256) guess)
    then ()
    else $(P.traceH) "WRONG!" ($(P.error) ())

    ||]))

gameAddress :: Address'
gameAddress = Ledger.scriptAddress gameValidator

lock :: String -> Value -> MockWallet ()
lock word value = payToScript_ gameAddress value (mkDataScript word)

guess :: String -> MockWallet ()
guess word = collectFromScript gameValidator (mkRedeemerScript word)

-- | Tell the wallet to start watching the address of the game script
startGame :: MockWallet ()
startGame = startWatching gameAddress

$(mkFunction 'lock)
$(mkFunction 'guess)
$(mkFunction 'startGame)

That looks already a bit more complicated than what we used to have in the previous example for sure, and we can now do more things!

What we are doing is quite basic and it happens in 2 steps:

  1. The first player decides of the word and commit a certain amount with the hash of this world on the blockchain inside a script.
  2. The second player submits a word to the blockchain, where it will be hash and compared to the block currently stored. If it succeeds, it will collect the reward, otherwise an error is raised by the contract.

There are a few the functions and datatype to look at first, to be able to understand what is going on here.


1
2
3
4
5
6
fromCompiledCode :: CompiledCode -> Script
lifted :: Lift a => a -> Script

newtype DataScript = DataScript { getDataScript :: Script }  
newtype RedeemerScript = RedeemerScript { getRedeemer :: Script }
newtype ValidatorScript = ValidatorScript { getValidator :: Script }

Ok so, what is all of this telling us? First thing, the central element is the type Script. You can’t create this type by yourself in this case, but you can create elements of this types through 2 functions : fromCompiledCode and lifted. The first will enable you to actually write Plutus code, compile it and then put it on the chain. The second one will enable you to lift/promote any Haskell types to the blockchain and make it available to use in other scripts.

The Script in the case of Plutus is in fact an abstract object that you will manipulate through some pre-defined function, and are somewhat more abstracted than things you could have seen in Solidity. 

The reason is the following: in the case of Ethereum, you will first write a smart contract in Solidity, compile it and commit on the blockchain. If you want to interact with it, you will have to use libraries such as web3.js and write again the same kind of code but this time in Javascript. This means you are duplicating a lot of your code in 2 different languages, increasing the risk that the software contains a bug. In the case of Plutus, you are able to re-use the same code on both on-chain and off-chain code, reducing the surface of your code. 


1
2
3
4
5
plcSHA2_256 :: ByteArrayAccess a => a -> ByteString
payToScript_ :: (Monad m, WalletAPI m) => Address' -> Value -> DataScript -> m ()
collectFromScript :: (Monad m, WalletAPI m) => ValidatorScript -> RedeemerScript -> m ()
scriptAddress :: ValidatorScript -> Address (Digest SHA256)
startWatching :: Address' -> m ()

These functions are the rest of those we didn’t explain until here but which will help to to get informations from the blockchain and to be able to interact with the different scripts we will create.

Important note about the Plutus model:
In Solidity, you have the same kind of structure you would have in a C code – you don’t have a clear separation between the code and the data storage structure. In Plutus, the DataScript structure is actually attached to a transaction when you are calling the payToScript functionality. It means that you can, later one, when calling the ValidationScript on a specific transaction, access the data you stored (It means as well that when you’ll store data, it won’t be stored in the Plutus contract itself, but the data will be part of the UTxO available on this address).

Let’s go now in detail of what is going on!


data HashedString = HashedString ByteString

PlutusTx.makeLift ''HashedString

The first line here describe a classic Haskell datatype. The seconde line makes this type available  on the blockchain and then enables you to lift value of this type on the blockchain. If you don’t do this, the compiler will complain when you’ll try to lift data onto the ledger.


mkDataScript :: String -> DataScript
mkDataScript word =
    let hashedWord = plcSHA2_256 (C.pack word)
    in  DataScript (Ledger.lifted (HashedString hashedWord))

In this function, we first create the hash of the word that we want to commit as player 1. For this, we use plcSHA2_256. The call to C.pack with the signature : pack :: String -> ByteString is because of some differences between the different kind of string representation in Haskell. If you want to know more about this, you can look at this article which explains why we have different kind of strings and how to go from one to another: https://mmhaskell.com/blog/2017/5/15/untangling-haskells-strings

Then, once we computed our hash, we can then create a new value of the type HashedString, lift this value to the Ledger, and then flag it as a DataScript(which is just a special kind of Script that needs to be used when you are trying to pay to a script – cf the signature of the payToScript_ function). More specifically, you attach this DataScript to the transaction itself.


mkRedeemerScript :: String -> RedeemerScript
mkRedeemerScript word =
    let clearWord = C.pack word
    in RedeemerScript (Ledger.lifted (ClearString clearWord))

The mkRedeemerScript is kind of the reciprocal of the mkDataScript – it creates a script that will be used to retrieve the unspent transactions associated to specific ValidationScript address. As you can see here, we lift the clear word to the blockchain, and it will then be hashed by the validation script in order to check if the word is indeed the good one.


gameValidator :: ValidatorScript
gameValidator = ValidatorScript (Ledger.fromCompiledCode $(PlutusTx.compile [||
    \(ClearString guess) (HashedString actual) (p :: PendingTx') ->

    if $(P.equalsByteString) actual ($(P.sha2_256) guess)
    then ()
    else $(P.traceH) "WRONG!" ($(P.error) ())

    ||]))

Here is where the actual magic happens! A ValidatorScript is just basically a lambda function, taking a RedeemerScript, a DataScript and the data of the current transaction that called the ValidationScript. We will see in a next article more advance features of these attributes, and what can be done with them. We can see that there is as well some specific notation here: “$”. This symbol is used to wrap every Plutus function (in this case P.equalsByteString for example), which are part of the Prelude. (More detail on the Plutus compilation here: https://github.com/input-output-hk/plutus/blob/master/plutus-tx/tutorial/Tutorial.md).

In the case of this example, the only thing the ValidationScript is doing is to check if the hash of the guess submitted by the second player matches the word submitted by the first player). If they are equal, then the payment is done. Otherwise the script raises an error, which cancel the transaction.


gameAddress :: Address'
gameAddress = Ledger.scriptAddress gameValidator

lock :: String -> Value -> MockWallet ()
lock word value = payToScript_ gameAddress value (mkDataScript word)

guess :: String -> MockWallet ()
guess word = collectFromScript gameValidator (mkRedeemerScript word)

The last 3 functions are kind of self explanatory :

  1. We fist get the address of the ValidatorScript
  2. We lock some funds at the address of the ValidatorScript through a DataScript containing the word to be guess.
  3. We try to guess by collect all unspent transactions at the address of the ValidatorScript, but in order to do that, the ValidatorScript is ran with the RedeemerScript data.

Here is how it looks like when ran in the playground:

Actions triggered by users
Outputs generated during the scenario. We can notice that the fund have been transfert one of the player guessed correctly….
….which is reflected on the balances.

We have now seen some basic use usage of Plutus and will through the other examples available on the playground, which are going deeper.

4 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *