Skip to main content

Marlowe step by step

Marlowe has six ways of building contracts. Five of these -- Pay, Let, If, When and Assert -- build a complex contract from simpler contracts, and the sixth, Close, is a simple contract. At each step of execution, as well as returning a new state and continuation contract, it is possible that effects -- payments -- and warnings can be generated too.

In explaining these contracts we will also explain Marlowe values, observations and actions, which are used to supply external information and inputs to a running contract to control how it will evolve.

Pay

A payment contract Pay a p t v cont will make a payment of value v of token t from the account a to a payee p, which will be one of the contract participants or another account in the contract. Warnings will be generated if the value v is negative, or if there is not enough in the account to make the payment in full (even if there are positive balances of other tokens in the account). In the latter case, a partial payment (of all the money available) is made. The continuation contract is the one given in the contract: cont.

Close

A contract Close provides for the contract to be closed (or terminated). The only action that it performs is to provide refunds to the owners of accounts that contain a positive balance. This is performed one account per step, but all accounts will be refunded in a single transaction.

Before discussing other forms of contracts, we need to describe values, observations and actions.

Values, observations and actions

Values include some quantities that change with time, including "the current slot number",1 "the current balance of some token in an account", and any choices that have already been made; we call these volatile values. Values can also be combined using addition, subtraction, negation, multiplication, and division, and can be conditional on an observation. Even though they are supported by Marlowe, the use of multiplication and division may render the process of static analysis intractable.

Observations are Boolean values derived by comparing values, and can be combined using the standard Boolean operators. It is also possible to observe whether any choice has been made (for a particular identified choice).

Observations will have a value at every step of execution. On the other hand, actions happen at particular points during execution. As noted earlier, actions can be

  • depositing money,
  • making a choice between various alternatives, including an oracle value (see next section), or
  • notifying an external value of some kind.

Oracles

Oracles are being developed for the Cardano blockchain in general, and will be available for use within Marlowe on Cardano. In the meantime, we have introduced an oracle prototype, which is implemented in the Marlowe Playground.

We model Oracles as choices that are made by a participant with a specific Oracle role: "kraken".

If a role in a contract is "kraken", and that role makes a choice such as "dir-adausd" then, in the Playground simulation, this choice will be pre-filled, based on data from Cryptowat.ch, with the current value of the direct ADA/USD conversion rate. You can find all supported currency pairs here:

https://api.cryptowat.ch/markets/kraken

It is also possible to obtain the inverse rates of currency pairs listed by adding the prefix inv- instead. For example, "inv-adausd" would return the value of the USD/ADA conversion rate.

Note, that we support only whole numbers as choice inputs. How then do we use current ADA/USD price, which might be $0.098924? We simply multiply the price by 10^8^, so the price would appear as 9892400. You can use DivValue with the resulting value after doing your calculations.

For example, you'd like to buy USDT for 12 ADA, using Oracle price.

Get the price:

When [Choice (ChoiceId "dir-adausdt" (Role "kraken") [Bound 1000000 10000000] ...

Calculate USDT amount

Let (ValueId "amount of USDT in microcents")
(MulValue
(Constant 12)
(ChoiceValue (ChoiceId "dir-adausdt" (Role "kraken"))))

Scale the result down by 10^6^ to get amount in USDT cents.

DivValue (UseValue (ValueId "amount of USDT in microcents")) (Constant 1000000)

If

The conditional If obs cont1 cont2 will continue as cont1 or cont2, depending on the Boolean value of the observation obs when this construct is executed.

When

This is the most complex constructor for contracts, with the form When cases timeout cont. It is a contract that is triggered on actions, which may or may not happen at any particular slot: what happens when various actions happen is described by the cases in the contract.

In the contract When cases timeout cont, the list cases contains a collection of cases. Each case has the form Case ac co where ac is an action and co a continuation (another contract). When a particular action, e.g. ac, happens, the state is updated accordingly and the contract will continue as the corresponding continuation co.

In order to make sure that the contract makes progress eventually, the contract When cases timeout cont will continue as cont once the timeout, a slot number, is reached.

Let

A let contract Let id val cont allows a contract to name a value using an identifier. In this case, the expression val is evaluated, and stored with the name id. The contract then continues as cont.

As well as allowing us to use abbreviations, this mechanism also means that we can capture and save volatile values that might be changing with time, e.g. the current price of oil, or the current slot number, at a particular point in the execution of the contract, to be used later on in contract execution.

Assert

An assert contract Assert obs cont does not have any effect on the state of the contract, it immediately continues as cont, but it issues a warning when the observation obs is false. It can be used to ensure that a property holds in any given point of the contract, since static analysis will fail if any execution causes a warning.


  1. The presentation here is a simplification of the concrete implementation, in which transactions are associated with a slot interval during which it is valid to add them to the blockchain. The reason for this is that in general it is difficult to predict the precise slot in which a transaction will be accepted for inclusion on the blockchain; it is therefore more robust to specify an interval in which the transaction should be accepted. The view presented here is a simplification in that effectively we consider only intervals of length one. So, a Marlowe contract is able to access the upper and lower bounds on the current slot interval, rather than the specific current slot value. Executing a contract can, in some circumstances, lead to an "ambiguous slot interval error", but we do not cover that any further here.