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.
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:
This website was created by Kevmate. Its all my own work. Contact me by emailing me at kev@kevmate.com