Writing Your First Plutus Smart Contract

img

9) Locking Your Funds With A PIN Number

All of the pieces have been built and now it is time for us to put them all together to create a fully working smart contract.

Our ValidatorScript has two inputs we are interested in. submittedPIN and myPIN.

1
2
3
myFirstValidator :: ValidatorScript
myFirstValidator = ValidatorScript (fromCompiledCode $$(PlutusTx.compile
    [|| \(submittedPIN :: Int) (myPIN :: Int) (p :: PendingTx') -> ()  ||]))

The submittedPIN is the RedeemerScript which is sent with our withdrawADA call, while myPIN is the DataScript that is attached to the ada sent with our depositADA function. What we need to do now is to create a check so that if the submittedPIN is equal to myPIN then the smart contract successfully executes. Otherwise, we need to throw an error that the two are not equal, thus stopping the withdrawADA call from succeeding.

1
2
3
4
5
6
7
myFirstValidator :: ValidatorScript
myFirstValidator = ValidatorScript (fromCompiledCode $$(PlutusTx.compile
    [|| \(submittedPIN :: Int) (myPIN :: Int) (p :: PendingTx') ->
     if ...
     then ...
     else ...
     ||]))

We first begin with creating an if then else. Our expression after the if needs to evaluate to a Bool, thus this is where we will do the equality check between the two PIN numbers.

1
2
3
4
5
6
7
myFirstValidator :: ValidatorScript
myFirstValidator = ValidatorScript (fromCompiledCode $$(PlutusTx.compile
    [|| \(submittedPIN :: Int) (myPIN :: Int) (p :: PendingTx') ->
     if submittedPIN == myPIN
     then ...
     else ...
     ||]))

If the two PIN numbers are equal, then we wish for the smart contract to finish executing successfully. We return unit, (), to represent this.

1
2
3
4
5
6
7
myFirstValidator :: ValidatorScript
myFirstValidator = ValidatorScript (fromCompiledCode $$(PlutusTx.compile
    [|| \(submittedPIN :: Int) (myPIN :: Int) (p :: PendingTx') ->
     if submittedPIN == myPIN
     then ()
     else ...
     ||]))

Otherwise if the two PIN numbers are not equal we will throw an error. However before we proceed to write the error code, we need but one more import.

1
import qualified Language.PlutusTx.Prelude as P

This is the standard Prelude/standard library for Plutus which holds many of the basic functions you will need when writing smart contracts. In our case we need it to throw the error if our equality check evaluates to false. (the else case)

1
2
3
4
5
6
7
myFirstValidator :: ValidatorScript
myFirstValidator = ValidatorScript (fromCompiledCode $$(PlutusTx.compile
    [|| \(submittedPIN :: Int) (myPIN :: Int) (p :: PendingTx') ->
     if submittedPIN == myPIN
     then ()
     else $$(P.error) ()
     ||]))

When using Plutus functions from the Prelude, or when using any other external Plutus functions that are not defined in your ValidatorScript, you will need to use the splicing operator $$(). As our error function is P.error, we will need to call it using $$(P.error). It takes a single input, and in our case we simply provide it with unit to throw a default error.

However, before we finish up, let’s add an error message to make this more user-friendly.

1
2
3
4
5
6
7
myFirstValidator :: ValidatorScript
myFirstValidator = ValidatorScript (fromCompiledCode $$(PlutusTx.compile
    [|| \(submittedPIN :: Int) (myPIN :: Int) (p :: PendingTx') ->
     if submittedPIN == myPIN
     then ()
     else $$(P.error) ($$(P.traceH) "Please supply the correct PIN number to withdraw ada." ())
     ||]))

Instead of feeding our error function (), we provide it with the result of another function, P.traceH. P.traceH allows us to supply an error message which tags along with the error to be reported in the log.

Now whenever a withdrawADA call is used with the incorrect PIN number, an error similar to the following will be displayed.

Validation failed for transaction: nxhdGHgYPxhwGOcYdxiIGOQYlhi3CBhlGOQYphizGGAYRhg0GGoY4BjtChg8GB0YvBgYGMIY0RjLGNEYfxh8/w==
 Ledger.Index.ScriptFailure ["Please supply the correct PIN number to withdraw ada."]

And with that, you have finished writing your very first smart contract! Congratulations!

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
module MyFirstPlutusSmartContract where

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


myFirstValidator :: ValidatorScript
myFirstValidator = ValidatorScript (fromCompiledCode $$(PlutusTx.compile
    [|| \(submittedPIN :: Int) (myPIN :: Int) (p :: PendingTx') ->
     if submittedPIN == myPIN
     then ()
     else $$(P.error) ($$(P.traceH) "Please supply the correct PIN number to withdraw ada." ())
     ||]))
     
smartContractAddress :: Address'
smartContractAddress = scriptAddress myFirstValidator

watchSmartContract :: MockWallet ()
watchSmartContract = startWatching smartContractAddress

depositADA :: Int -> Value -> MockWallet ()
depositADA pin val = payToScript_ smartContractAddress val (DataScript (lifted pin))

withdrawADA :: Int -> MockWallet ()
withdrawADA pin = collectFromScript myFirstValidator (RedeemerScript (lifted pin))

$(mkFunction 'watchSmartContract)
$(mkFunction 'depositADA)
$(mkFunction 'withdrawADA)

Type this code into the Plutus Playground editor, compile, and experiment with the smart contract that you have just finished making. If you aren’t the experimenting type of person, here are the Action Sequences to display how the contract works.

Success:

  1. Wallet 1 calls watchSmartContract.
  2. Wallet 2 calls watchSmartContract.
  3. Wallet 1 calls depositADA with PIN number 1234 and deposits 5 ada.
  4. Wallet 2 calls withdrawADA with PIN number 1234.
  5. Wallet 2 successfully entered the correct PIN number, thus collects the 5 ada.

Failure:

  1. Wallet 1 calls watchSmartContract.
  2. Wallet 2 calls watchSmartContract.
  3. Wallet 1 calls depositADA with PIN number 3421 and deposits 5 ada.
  4. Wallet 2 calls withdrawADA with PIN number 1234.
  5. Wallet 2 failed to enter the correct PIN number, they receive an error message.