Marlowe

Marlowe is a simple programming language embedded in Cardano. It is native to the Cardano Computation layer, but it is built on top of Plutus. Every Marlowe command is realised with Plutus commands, which it executes to get the job done. You can dive right in with Marlowe, and its a good place to start, but you will find it easier to get going if you know a bit about functional programming languages first, so you can see what it is all built on. If you build your contract in Marlowe there is nothing stopping you converting it into Plutus later. Marlowe is useful because it is simpler, and can be viewed and built graphically. The graphical tool is called Meadow. The official, and pretty good, tutorial for Marlowe is definitely somewhere you should either look next, or have already seen.

Here I will just get you started by walking you through an example explaining the concepts, while trying to keep it simple.

A quick tutorial (Step 1 of 9):

Here is an piece of code from a Marlowe program, note that the reserved words are all capitalised. We are going to create a simple money transfer contract, with money going from person 1 to person 2, but we need person 3 to be around to approve. First we add a commitment, to get money into the contract. We use the CommitCash function followed by the id of the sender, the amount, the time to wait for the money, and the time to hold the money. If 1000 ADA hasn't arrived by time 15 (5 minutes), or if the next steps don't execute by time 180 (60 minutes), the contract will execute the Null statement, which does nothing, so it will close. You have to get used to the strong typing. By that I mean each term has to be the right type. Here the first term has to be an Id which I get using IdentCC. This is a function which returns the Id which has committed cash to a wallet. Times are just numbers of blocks, but money needs to the the right type so we use ConstMoney 1000 to convert the number 1000 into an amount of ADA:

(CommitCash (IdentCC w1) w1 (ConstMoney 1000) 15 180
    (this_happens_if_the_money_arrives)
    Null
)

Now I need a When clause, to define the conditions I am waiting for. You use a When clause if you want to include a condition with a timeout. Here is the first of the things that might happen next, person 1 says no then either person 2 or person 3 says no. You can see the AndObs and OrObs clauses being used to work out if a set of conditions has been met; person 1 and either 2 or 3 have said no. I am using c1, c2 and c3 to reference the three choices to be offered, one to each person. The PersonChoseThis function needs an Id, which I can get using IdentChoice which returns the Id of the person who was given a choice. I am also using w1, w2, w3, c1, c2, c3, no, and yes in my code. This is for readability, I will define them at the end:

(CommitCash (IdentCC w1) w1 (ConstMoney 1000) 15 180
    (When
        (AndObs
            (PersonChoseThis (IdentChoice c1) c1 no)
            (OrObs
                (PersonChoseThis (IdentChoice c2) c2 no)
                (PersonChoseThis (IdentChoice c3) c3 no)
            )
        )
        time_to_wait
        (this_happens_if_people_voted)
        (this_happens_if_it_timed_out)
    )
    Null
)

I know it is getting a bit wordy, but we need to define all the different solutions which will allow the contract to continue. So we add an OrObs and repeat the AndObs we saw in step 2 for the other solutions. You can see everything is properly nested, so the structure is in the form (a and (b or c)) with two terms on each condition:

(CommitCash (IdentCC w1) w1 (ConstMoney 1000) 15 180
    (When
        (OrObs
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 no)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 no)
                        (PersonChoseThis (IdentChoice c3) c3 no)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 no)
                    (PersonChoseThis (IdentChoice c3) c3 no)
                )
            )
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 yes)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 yes)
                        (PersonChoseThis (IdentChoice c3) c3 yes)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 yes)
                    (PersonChoseThis (IdentChoice c3) c3 yes)
                )
            )
        )
        time_to_wait
        (this_happens_if_people_voted)
        (this_happens_if_it_timed_out)
    )
    Null
)

Ok, I hope you are following. Now we add the timeout; this is the second term in the When clause telling it how long to wait before executing the fail function. You always need a timeout in a smart contract, otherwise the money could get locked in:

(CommitCash (IdentCC w1) w1 (ConstMoney 1000) 15 180
    (When
        (OrObs
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 no)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 no)
                        (PersonChoseThis (IdentChoice c3) c3 no)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 no)
                    (PersonChoseThis (IdentChoice c3) c3 no)
                )
            )
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 yes)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 yes)
                        (PersonChoseThis (IdentChoice c3) c3 yes)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 yes)
                    (PersonChoseThis (IdentChoice c3) c3 yes)
                )
            )
        )
        360
        (this_happens_if_people_voted)
        (this_happens_if_it_timed_out)
    )
    Null
)

Now for the bit which executes if the When clause succeeds, so it will either pay out or return the money to sender. We will use a Choice clause. This is like When but it doesn't have a timeout, instead it has a condition followed by two commands, one for if it was true and one for if it was false. In the Choice clause we repeat the conditions for paying out:

(CommitCash (IdentCC w1) w1 (ConstMoney 1000) 15 180
    (When
        (OrObs
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 no)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 no)
                        (PersonChoseThis (IdentChoice c3) c3 no)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 no)
                    (PersonChoseThis (IdentChoice c3) c3 no)
                )
            )
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 yes)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 yes)
                        (PersonChoseThis (IdentChoice c3) c3 yes)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 yes)
                    (PersonChoseThis (IdentChoice c3) c3 yes)
                )
            )
        )
        360
        (Choice
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 yes)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 yes)
                        (PersonChoseThis (IdentChoice c3) c3 yes)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 yes)
                    (PersonChoseThis (IdentChoice c3) c3 yes)
                )
            )
            (this_happens_if_people_said_yes)
            (this_happens_if_people_said_no)
        )
        (this_happens_if_it_timed_out)
    )
    Null
)

Time to pay up. If the Choice comes out true we will execute a Pay clause. Pay clauses have six terms, first is the Id of the person who will make payment, then are the two wallets (from wallet and to wallet). Fourth is how much, which is all the money from the person who committed cash into wallet 1. Fifth is a timeout to allow the payment to be processed. Finally what happens if the payment fails. The payment shouldn't fail, but the contract has to include the possibility because it doesn't have complete control of the wallets, the process could fail so we have to allow for it. Here I have allowed 60 minutes, which is quite a lot:

(CommitCash (IdentCC w1) w1 (ConstMoney 1000) 15 180
    (When
        (OrObs
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 no)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 no)
                        (PersonChoseThis (IdentChoice c3) c3 no)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 no)
                    (PersonChoseThis (IdentChoice c3) c3 no)
                )
            )
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 yes)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 yes)
                        (PersonChoseThis (IdentChoice c3) c3 yes)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 yes)
                    (PersonChoseThis (IdentChoice c3) c3 yes)
                )
            )
        )
        360
        (Choice
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 yes)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 yes)
                        (PersonChoseThis (IdentChoice c3) c3 yes)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 yes)
                    (PersonChoseThis (IdentChoice c3) c3 yes)
                )
            )
            (Pay
                (IdentPay w1) w1 w2
                (AvailableMoney (IdentCC w1))
                180
                (RedeemCC (IdentCC w1) Null)
            )
            (this_happens_if_people_said_no)
        )
        (this_happens_if_it_timed_out)
    )
    Null
)

The rules of the contract are now finished, we add the clause which executes when people didn't vote yes, and the last one for the When which executes if the 360 timeout is exceeded. In both cases the money is given back to the person who committed cash to wallet w1 and the contract closes:

(CommitCash (IdentCC w1) w1 (ConstMoney 1000) 15 180
    (When
        (OrObs
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 no)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 no)
                        (PersonChoseThis (IdentChoice c3) c3 no)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 no)
                    (PersonChoseThis (IdentChoice c3) c3 no)
                )
            )
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 yes)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 yes)
                        (PersonChoseThis (IdentChoice c3) c3 yes)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 yes)
                    (PersonChoseThis (IdentChoice c3) c3 yes)
                )
            )
        )
        360
        (Choice
            (OrObs
                (AndObs
                    (PersonChoseThis (IdentChoice c1) c1 yes)
                    (OrObs
                        (PersonChoseThis (IdentChoice c2) c2 yes)
                        (PersonChoseThis (IdentChoice c3) c3 yes)
                    )
                )
                (AndObs
                    (PersonChoseThis (IdentChoice c2) c2 yes)
                    (PersonChoseThis (IdentChoice c3) c3 yes)
                )
            )
            (Pay
                (IdentPay w1) w1 w2
                (AvailableMoney (IdentCC w1))
                100
                (RedeemCC (IdentCC w1) Null)
            )
            (RedeemCC (IdentCC w1) Null)
        )
        (RedeemCC (IdentCC w1) Null)
    )
    Null
)

To get this contract to run in the full Marlowe environment we also need some definitions. These could be considered optional, since instead of w1 and c1 I could have put numbers, but to make things readable I used codes, now I add the definitions at the bottom. You will notice that each one just returns a number, so if you like you can just put the numbers into the contract and leave this bit out, its just a bit less readable. I noticed that if you paste this contract into meadow it doesn't work with the codes, you have to put numbers in - so 1 instead of yes, etc.:

(CommitCash (IdentCC w1) w1 (ConstMoney 1000) 15 180
    (when_clause_went_here)
    Null
)

-- Wallets
w1, w2, w3 :: Integer
w1 = 1
w2 = 2
w3 = 3

-- Choices
c1, c2, c3 :: Integer
c1 = 1
c2 = 2
c3 = 3

-- Options
yes, no :: Integer
yes = 1
no = 0

Summary of useful Marlowe functions we (should) have learned:

(CommitCash Id wallet amount delay_time timeout if_received if_timed_out)
Request money to be committed into the contract by a person. There is an Id followed by a reference for the wallet. The wallet holds the money inside the contract. In our example there are three wallets and three Ids, since each person only has one wallet. For the amount we want a number of the right type, so we use ConstMoney (see below). The next two arguments are the delay before the next step in the contract and the delay before timing out. Finally the two functions to be executed if the money arrives, and if it doesn't arrive.
(ConstMoney number)
To make sure you don't make mistakes, Marlowe makes sure the value you supply is the right type. So if you want to specify an amount of money using a number you have to use ConstMoney.
(IdentCC wallet)
To refer to a person you need an Id. IdentCC returns the Id of the person who has committed (or is committing) cash to a selected wallet. In our case we have three people and three wallets, so its simple.
(When condition timeout if_true if_false if_timed_out)
A When clause has a condition which will evaluate, but if some of the choices haven't been made yet it will wait for a defined time before executing the if_timed_out clause.
(Choice condition if_true if_false)
The Choice evaluates a condition and executes either the true function or the false function.
(OrObs observation1 observation2)
OrObs takes two observations and returns a value of true if either of them are true. It is the same as Or, except it will only accept the result of an observation as an input, meaning the observed behaviour of an Id. Observed things can have a delay since the person may not have chosen yet.
(AndObs observation1 observation2)
AndObs is similar to OrObs, but both observations have to be true.
(RedeemCC Id then_do_this)
Money that remains in any wallets that the selected Id has committed cash to is returned to them. This always succeeds. Then a function is executed.
(AvailableMoney Id)
Returns the amount of money in all the wallets which is available for withdrawal by a selected Id.
(Pay Id from_wallet to_wallet amount timeout if_failed)
The Pay function transfers money which is controlled by an Id from one wallet to another. Note that we use IdentPay rather than IdentCC to get the Id for wallet w1.
(IdentPay wallet)
Similar to IdentCC in that it returns the Id of the person attached to a wallet, but will return the Id which has permission to pay from the wallet which might not be the person who committed cash to it.
(PersonChoseThis Id choice chosen)
This function makes an observation, checking if a selected Id has chosen one of the options for a choice they were offered. In our example there are two options, 0 meaning no and 1 meaning yes. Note that we have to supply an Id, and we do this using IdentChoice (see below).
(IdentChoice choice)
This function returns the Id of the person who has been given a choice. In our case there are three people and three choices so choice c1 will return the first Id, but we could have defined more choices in a more complex contract.
(Null)
Null is an empty function. In our case we use it to allow the contract to reach the end and terminate. We need it because in languages which have strong typing you always have to supply a function if one is expected, it helps reduce accidental mistakes.

This website was created by Kevmate. Its all my own work. Contact me by emailing me at kev@kevmate.com