Migrating from earlier versions of Marlowe¶
This tutorial explains how the latest version of Marlowe differs from earlier versions of the language.
We do not include a
Both construct in the latest version of Marlowe,
which makes all contracts sequential.
Since in none of the versions of Marlowe was there communication among
the branches of
Both, the only extra functionality provided by
Both in practice was the ability to wait for several money deposits
at the same time.
We take care of this functionality by updating the
Instead of having different branches wait for different inputs, we move
to a completely sequential and synchronous model, where we can wait for
one of several possible inputs at the same time (as in
The reason we removed this construct is that sequential programs are easier to analyse and easier to reason about, since there is no need for synchronisation and no opportunity for race conditions.
In earlier versions of Marlowe each commitment has its own timeout. This means that money deposited in a contract is not fungible, because it can be distinguished by its timeout. To achieve fungibility we have removed timeouts from individual constructs, and have a single timeout for all commitments. This contract lifetime is straightforward to infer from the timeouts in the contract.
We do, however, include accounts to organise the money deposited into the contract. This makes it more transparent how money flows within the contract, and in particular identifies to whom money is refunded when the contract terminates.
Each account is identified by a participant; the participant indicates
who will get the money in the account by default when
The reason we chose to include accounts is that without them we found that we were essentially keeping track of the accounts manually. Additionally, in every leaf of the AST, we found ourselves calculating how much we must return to every participant, cluttering the tree with repetitious “boilerplate”. Thus, having money organised in accounts can make contracts easier to reason about and less prone to error.
Note that we can provide full fungibility by using a single account. We
only need to write the appropriate
Pay commands in the leaves of the
contract. If all the money is paid to participants, then
no effect. 1
Discussion: Implicit vs Explicit Accounts¶
Many of the use cases for accounts – and all those that we can identify for ACTUS contracts – have one account per participant, and one participant per account (the “1-1 model”). This raises the question of whether we should give an implicit treatment of accounts, with each participant owning one account.
On the other hand, there are a variety of plausible scenarios for accounts which do not conform to the 1-1 model.
Examples where multiple participants use an account.
Alice owns an account to which she commits money for Bob to spend (think of Alice as Bob’s employer). Bob is able to spend up to the limit in the account, but after the commitment times out, Alice recovers anything remaining.
Alice owns an account to which she commits money for Bob and Carol to spend (think of Alice as Bob’s and Carol’s employer). They are able to spend (jointly) up to the limit in the account, but after the commitment times out, Alice recovers anything remaining.
On the other hand, they could each be given a separate account from which to spend: that would enforce individual limits as well as an aggregate limit.
If Bob [and Carol] want to spend money they can also add money to the account, but they should realise that anything unused will be refunded to Alice.
Examples of multiple accounts for one person:
Examples of underwriting would fit here. A person underwrites first-level risk, and second-level risk using different accounts. Only when the first level underwriting of all participants is spent will second level spending occur.
Since all contracts are sequential now, we can easily tell when a
contract terminates, i.e: when only
Null is left. We use this
opportunity to close the contract and to refund any money remaining in
the accounts; for this reason we have renamed
As noted earlier, there is no longer any explicit timeout in the
accounts, since all contracts will eventually reduce to
fact, we can statically and efficiently calculate an upper bound for
when this will happen, making this aspect of Marlowe analysable.
Pay is now immediate, and it has a single continuation, and fewer
parameters. 2 It allows payments from an account to a participant or
to another account. We discarded
PayAll, since it can be emulated as
a finite series of
Pay. In fact, we can define
payAll as a
Haskell function (see
It is a consequence of removing the
Both construct, that it is now
Pay goes first: they are all sequential, thus
aiding analysis. With the
Both construct we could potentially have
Pays happen in any order (since both sides of
Both are supposed
to run concurrently).
We have modified
When to include a set of possible actions that can
be input while
When waits. We call this approach “One of Many”,
because it accepts one action out of potentially many allowed actions.
When remains as follows:
When [Case] Timeout Contract
When will either wait for
Timeout and continue as
Contract, or continue as specified in one of the
whichever happens first.
Case is defined as:
data Case = Case Action Contract
data Action = Deposit Party Party Token Value | Choice ChoiceId [Bound] | Notify Observation
Case clause will be activated only if the corresponding
is produced, and it will continue as
Contract. In case of two
Actions matching, the first one in the list will be executed.
Three kinds of actions are supported:
Depositrepresents a deposit of money into an account; this was originally called
Choicerepresents a choice made by a participant from within a set of
Integervalues (specified by the list of
Notifywill wait for a
Notifyaction issued when the
Observationis true. We call it
Notifyin order to make it clear that we cannot just wait for
Observations, but that someone must trigger the contract in a moment when an
We have discarded adding observations to
since it would not be obvious whether the
Observation would be
evaluated before or after applying the action.
Observations and Values¶
We have discarded
Values that can be expressed
by combining others: like the general
AvailableMoney (for the whole
contract), or like
DepositedMoneyBy, that remembers the amount of
money deposited by a participant, since the contract can be restructured
to observe that, and supporting would require additional information in
the state (simplicity).
We have retained the
ChoseSomething observation, even though, in the
proposed semantics, every occurrence of
ChoseSomething can be
evaluated statically and efficiently by examining its context.
For example, in the following contract we can see that the first
ChoseSomething will evaluate to
True, and the
second one to
When [ Case (Choice (ChoiceId 1 Alice) [(1,1)]) (If (ChoseSomething (ChoiceId 1 Alice)) Close Close) , Case (Choice (ChoiceId 2 Bob) [(2,2)]) (If (ChoseSomething (ChoiceId 1 Alice)) Close Close)] 0 Close
Nevertheless, we have chosen to keep the construct for two reasons:
It allows for code reusability (convenience). For example, in the previous contract, we could define
let chosen1 = If (ChoseSomething (ChoiceId 1 1)) Close Close in When [ Case (Choice (ChoiceId 1 1) [(1,1)]) chosen1 , Case (Choice (ChoiceId 2 2) [(2,2)]) chosen1] 0 Close
But this would not be possible if we did not have the construct
ChoseSomething, since the value to which it reduces depends on the
It may no longer be the case that occurrences of the construct can be evaluated statically if we extend the
Whenconstruct to support “many of many” inputs.
Inclusion of SlotIntervals¶
The EUTxO specification provides validation scripts with slot-intervals
instead of with slot numbers. This is to promote determinism in
validation scripts. Nevertheless, we have kept the timeout of
(the only timeout) as a slot number. The way we deal with slot-intervals
is by requiring that the interval of a transaction does not include any
timeout over which the semantics has to make a choice. For example: if a
timeout is 10, a transaction with interval 5-15 will fail with
AmbiguousSlotInterval. Participants would have to issue a
transaction with interval 5-9 or 10-15 (or both).
Values, we provide the two constructs
SlotIntervalEnd. An alternative to
consider would be to modify the semantics so that Values are
non-deterministic, that way we could include a
and just invalidate transactions that are ambiguous, but this would
complicate the semantics and make them less predictable.
We can potentially provide a way of statically analysing the contract to check whether there can possibly be any money left in any account when
This means that payments now obey a “push” model rather than a “pull” model.
Nevertheless, triggering the contract for processing timeouts is not urgent as it is with
Notify, because while
Observationscan alternate between
False, timeouts can only happen once and, independently of whether they have been observed by the contract or not, they cannot be reversed.
Indeed, an explicit
Casecan no longer be issued after the timeout, even if the timeout has not been observed by the contract, since the timeout is checked before the
Inputs. However, a participant may want to trigger a timeout in cases where no other
Inputsare needed, in order to trigger one or more payments, for example. In the current implementation of the semantics that would be done by issuing a transaction with an empty list of