Your first CSL contract

In this section, we will guide you through the formulation of your first contract specification. Our goal is to describe a scenario in which the participant “Bob” sells one bicycle to “Alice” for 100 euros.

Specifying events

The first step in specifying our contract is to understand exactly what the scenario we want to describe actually contains:

  • Is the bicycle delivered to Alice or must she pick it up?

  • If it is delivered, is there a time limit for how long Alice wants to wait?

  • If a third party, “Eve”, comes along and offers 200 USD for the bicycle, will she get it instead of Alice?

  • Is there one single bicycle, or will Bob be able to sell more bicycles?

  • When does payment occur?

One could come up with many more considerations than the above for a real-world scenario. For didactic purposes we shall limit ourselves to a simplified bike shop:

  • Alice orders – and pays for – a bicycle, and then it is delivered to her with no time constraints;

  • No other parties than Alice and Bob exist;

  • Bob has one bicycle and when he sells it he closes down his shop.

Our idealized transaction thus boils down to the following:

  1. Alice orders a bicycle by transferring 100 euros to Bob;

  2. Bob delivers one bicycle to Alice.

Encoding events

In the preceding section, we have established that our contract considers two separate types of events: a BikeOrder event and a BikeDelivery event. The BikeOrder event encodes the order that Alice places for a bicycle, and it should therefore include the price she is willing to pay and whom she orders a bike from. We specify this in CSL as follows:

type BikeOrder : Event {
  amount : Int, // The amount of euros that Alice will pay for a bicycle.
  recipient : Agent // The recipient of the order.
}

This CSL code specifies that a BikeOrder is a type of event which has the two fields amount and recipient in addition to the standard fields of events (which we shall see in a bit). Note that everything on a line that comes after // is treated as a comment and is ignored by CSL. CSL is not whitespace-sensitive, so we are free to arrange the code as we please.

The BikeDelivery event represents the delivery of an item to a receiver. Hence, we include as extra fields in this event the recipient of the bicycle:

type BikeDelivery : Event {
  recipient : Agent // The recipient of the bicycle
}

Both BikeOrder and BikeDelivery are specified as a subtype of an event type called Event in CSL. This means that our newly defined types are events that we can use in contracts. Any event type we want to use in contracts has to be declared as a subtype of Event like this. In CSL we refer to this relationship as inheritance: our BikeOrder/BikeDelivery event inherits the properties of Event. An event is a subtype of another event if there is a so-called “is-a relationship” between them: A BikeOrder is an Event and a BikeDelivery is an Event.

Being an Event means that it has two fields agent and timestamp next to whatever else you specify. The Event type is defined as follows:

type Event {
  agent : Agent,
  timestamp : Instant
}

Notice that Event does not inherit from any other events. Event is the root of the event hierarchy.

Specifying the desired behaviour

To specify the basic structure of our sales contract, we define a contract named bikeSale with the contract keyword. We also mark the contract as an entrypoint to allow us to instantiate it – see the next chapter. For now, we just encode the order in which the events must occur: One BikeOrder event followed by one BikeDelivery event.

contract entrypoint bikeSale0 = \() ->
  <*> BikeOrder // Someone sends a BikeOrder
  then
  <*> BikeDelivery // Someone sends a BikeDelivery
  // The contract is done

The syntax used here needs a bit of elaboration.

  • The body of a contract declaration (the right-hand of the = sign) is a contract function and has the form \param -> contractbody. The param is a parameter that can be passed to the function and be used in the contract body. It is () when no parameters are accepted.

  • A prefix contract is a contract that requires some given event to occur. Prefixes are written using the syntax <*> Event. The initial <*> means that the event may come from anyone, and the following Event is the actual type of the expected event.

  • Combining a prefix contract with another contract with then results in a contract that requires the specified event to occur, followed by whatever the other contract requires.

Clearly, this contract is too generic. The only thing we have specified so far is the order of events and the fact that the contract is finished once the order and delivery have taken place. It therefore allows many sequences of events that should not be accepted: it is for instance quite possible for Bob to send a BikeOrder event and for Alice (or even Bob himself) to send the BikeDelivery event. To address this, we write the agents next to the events. In principle, we want the BikeOrder to come from Alice, and BikeDelivery to come from Bob – but this can be naturally generalised to any buyer and seller, respectively. In fact, in CSL we do not hardcode the names of agents in contracts. Instead, we make the buyer and the seller abstract in the contract, which is quite easily done by adding two parameters, buyer and seller:

contract entrypoint bikeSale1 = \(buyer, seller) ->
  <buyer> BikeOrder // Buyer places a bike order
  then
  <seller> BikeDelivery // Seller delivers a bicycle
  // The contract is done

These parameters are bound to concrete values once the contract is instantiated.

We have now used a different format for the prefix construct. If one writes <buyer> instead of <*> it means that the event must originate from an agent given by the argument buyer. Hence, our contract now specifies that it is the buyer that must issue the BikeOrder event which must be followed by a BikeDelivery event from the seller. This is still too general: Our informal contract specification states that Alice places an order to Bob worth 100 euros and that Bob delivers a bicycle to Alice. Since we declared the BikeOrder and BikeDelivery events to include information about price, receiver, etc., we may use these properties to place restrictions on allowed events in our contract:

contract entrypoint bikeSale2 = \(buyer, seller) ->
  // Buyer sends an order worth 100 euros to seller
  <buyer> order: BikeOrder where
      order.amount = 100 &&
      order.recipient = seller
  then
  // Seller delivers a bicycle to buyer
  <seller> delivery: BikeDelivery where
      delivery.recipient = buyer

The prefix format is extended here to associate a name for a particular received event, making it possible to formulate a predicate on it. This predicate follows the keyword where, and the event is only accepted if it evaluates to True. The BikeOrder event is bound to a variable named order, and we use the where clause to check that the fields of order have the desired values, using the connective && which is the logical “and” operator. In this case, the event order is accepted only if the field order.price is set to 100 and the field order.recipient is set to seller. Likewise, the BikeDelivery event is bound to the variable delivery and is only accepted if delivery.recipient is buyer.

Generalizing the contract

Our contract can now be instantiated to a specification of what we described at the beginning of this section: The sale of a bicycle for 100 euros from Bob to Alice – by setting the buyer parameter to alice and the seller parameter to bob. The contract is still quite limited, though, as it only models this particular idealized sale of one single bicycle for a fixed price. In this section, we will demonstrate how to generalize the contract to handle a wider range of scenarios.

We currently have a contract that specifies the sale of one bicycle from some seller to some buyer. Now, what if it was not a bicycle but a hammer that was the transaction’s object? Clearly, there is no substantial difference between a sales transaction that involves one object or the other, safe for the price. However, our way of modeling a transaction with BikeOrder and BikeDelivery events fixates the objects to be a bicycle. We must therefore extend our events a bit to be able to account for orders and deliveries of other items:

type Order : Event {
  amount : Int, // The amount of euros is offered for the item.
  recipient : Agent, // The recipient of the order.
  item : String // The item that is ordered
}
type Delivery : Event {
  recipient : Agent, // The recipient of the item
  item : String // The item that is delivered
}

Now we can formulate a more general sales contract (call it Sale), by abstracting the item and the price out as parameters:

contract entrypoint sale0 = \(buyer, seller, amount, item) ->
  // Some buyer orders an item for some price from a seller
  <buyer> order: Order where
      order.amount = amount &&
      order.recipient = seller &&
      order.item = item
  then
  // The seller delivers that item
  <seller> delivery: Delivery where
      delivery.item = item &&
      delivery.recipient = buyer

Exercise: Pay on delivery

Write a variant of the sale0 contract where the payment comes after the delivery (solution).

There is a glaring problem with our new contract specification: Nowhere is it specified that certain items have certain prices! According to the contract in its current formulation, a buyer just has to offer any amount of euros for any item, including even a negative amount or zero, and she will receive it! This is not a desirable state of affairs: we must model some basic notion of an inventory that associates prices to items.

// Accept offer if the item and offered price fit.
val acceptOffer =
  \ "Bike" -> (\p -> p >= 100)
  | "Hammer" -> (\p -> p >= 30)
  | "Saw" -> (\p -> p >= 40)
  | _ -> \p -> False // Seller does not have this item; reject.

contract entrypoint sale1 = \(buyer, seller, amount, item) ->
  // Some buyer orders an item for some price from a seller
  <buyer> order: Order where
      order.amount = amount &&
      order.recipient = seller &&
      order.item = item
  then
  // The seller delivers that item
  <seller> delivery: Delivery where
      acceptOffer order.item order.amount &&
      delivery.item = order.item &&
      delivery.recipient = buyer

This version of the contract introduces a new concept of CSL: functions. The basic idea with this addition is to specify an “inventory function” acceptOffer that takes an item and a price and returns either True or False depending on whether the offered price for the item is acceptable. This example introduces a new construct: Functions. It’s not entirely new though, as we’ve been using its close cousin the contract function in the previous examples too. Like for their contract counterparts, functions have the form \param -> body where param is a name that can be used inside of body as the value that was given when the function was called. In CSL, functions always take one single argument, so how do we handle the case where we need a function to take multiple arguments, like here where we need both the name of the item ("Bike" etc.) and the offered price? One solution is to use a technique called currying, where we write our function such that it returns another function that takes the remaining argument: \arg1 -> \arg2 -> body. Functions may have separate bodies for separate kinds of input: This is written in CSL as \form1 -> body1 | form2 -> body2. We call this pattern matching, and this is what we have used in the acceptOffer above where each body is a new function that takes the price p and checks that is above some threshold. In fact, the simple form \p -> body also uses pattern matching: the name p is a pattern that matches any value and binds it to the name p inside of body where it can then be used. Note that when pattern matching, the symbol _ is a special pattern that behaves like a name but does not result in a new binding in the function’s body, so \_ -> body is like above except that body cannot refer to the value that the function was called with. Functions are an important part of the CSL language, and they are discussed in much more detail in the detailed description in the value language reference.

Our new version of the contract allows three items to be sold at three different minimum prices (the buyer is free to offer more), and it protects the seller from accidentally selling items at an unacceptable discount. However, the contract is once again restricted to a certain set of items! What we really should do here is to abstract away the shop’s inventory.

First, how will an arbitrary shop’s inventory be represented? We can either create a separate checking function for every shop, or use some data structure to represent any shop’s inventory, and provide a function which, given an inventory, a name of an item, and the price that the buyer is offering, either accepts or rejects the offer. Here we show the latter of the two approaches.

What data structure should we use to represent the shop’s inventory? It can simply be a dictionary: a list of tuples containing the name of the item and a minimal acceptable price. For example, the bike shop’s inventory can be represented as:

val bikeShopInventory =
  [("Bike", 100),
   ("Brakes", 20),
   ("Helmet", 30)]

Next, we need to write a function which decides if an offer can be accepted. It should search the inventory for the given item and check that the price is acceptable. This is one possibility of an implementation:

val checkOffer = \inventory -> \(item  : String) -> \(price : Int) ->
  // If the item is listed in the inventory and the price is right,
  // then accept the offer
  // 'List::any' returns True if there is an element of the list
  // that satisfies the predicate
  List::any
    (\(name, acceptPrice) -> name = item  && price >= acceptPrice)
    inventory

We use a standard library function List::any, which traverses a list, looking for an element which satisfies the given predicate. It returns True if it finds one and False otherwise. In our case, the list in question is the inventory, and the predicate states that the item is in the inventory, and the proposed price is accepted. Notice that we need to explicitly say that the item argument is of String type and that price is an Int. This is because not all types in CSL are comparable using = or <= operators.

Now we can finish writing the sale contract:

// The 'inventory' parameter is a list of items available in
// the store, together with their prices.
contract entrypoint sale2 = \(buyer, seller, amount, item, inventory) ->
  // Some buyer orders an item for some price from a seller
  <buyer> order: Order where
      order.amount = amount &&
      order.recipient = seller &&
      order.item = item
  then
  // The seller delivers that item
  <seller> delivery: Delivery where
      // Use the provided inventory to determine whether
      // the seller has the item.
      checkOffer inventory order.item order.amount &&
      delivery.item = order.item &&
      delivery.recipient = buyer

With this formulation we could instantiate the contract to be bikeshop-specific, bookshop-specific, etc., by defining different inventories for each shop:

// Items in a bike shop are bicycles or other gear.
val bikeShopInventory =
  [ ("Bike", 100),
    ("Brakes", 20),
    ("Helmet",30)] // It's a small bike shop.

// Items in our book shop are just the authors.
val bookShopInventory =
  [ ("Joyce", 10),
    ("Proust", 2),
    ("Hugo", 13)] // We just sell three books

// Items in this shop are the allowed ingredients in brunch.
val brunchShopInventory =
  [ ("Pancake", 1),
    ("Bacon", 2),
    ("Egg", 1)] // Nothing else goes in a brunch.

Providing fail-safe mechanisms

In CSL, sending an unexpected event to a contract does not cause this contract to fail. Instead, the contract simply waits for another event to arrive. This means that if the seller sends a Delivery event containing a different item than the one ordered, such an event is simply ignored by the contract, and the seller can try again.

Sometimes we want to react to a situation where something wrong happened, and have the contract fail, as it has been breached. For instance, if we ordered a bike and paid enough for it, we expect the seller to not try sending us a hammer instead. To account for the possibility of a contract failing, we use the contract combinator or and the primitive contract failure:

contract entrypoint sale3 = \(buyer, seller, amount, item, inventory) ->
  // Some buyer orders an item for some price from a seller
  <buyer> order: Order where
      order.amount = amount &&
      order.recipient = seller &&
      order.item = item
  then (
    // The seller delivers that item
    <seller> delivery: Delivery where
        checkOffer inventory order.item order.amount &&
        delivery.item = order.item &&
        delivery.recipient = buyer
  or
    // The seller tries to cheat
    <seller> delivery: Delivery where
        // The order was accepted
        checkOffer inventory order.item order.amount &&
        // But something isn't right with the delivery
        (not (delivery.item = order.item) ||
         not (delivery.recipient = buyer))
    then failure
  )

We only want the contract to fail if the seller tries to deliver the wrong item, or deliver it to the wrong person. To express those two possibilities in the where clause, we use the connective ||, which is the logical “or” operator, and the negation operator not. Combining two different events with an or combinator gives us two possibilities of how the contract can evolve. In both cases we wait for a Delivery event, we also expect that the order was accepted (inventoryFunction returning true). Whether the contract results in failure or not, depends then on the correctness of the delivery details.

Exercise: Pay or return

Continue the pay on delivery exercise by allowing a return of unwanted items.

Create an inventoryPrice function, which, for each item, returns its price. For simplification, you can assume that for items which are not explicitly listed, the price is some arbitrarily large number. The seller should now include the price of the item in the Delivery event, based on what the inventoryPrice function returns. The buyer then has two options: either pay what is asked of them, or return the item to the seller (solution).

Exercise: You need a bike and a helmet!

Write a contract in which the buyer orders both "Bike" and "Helmet", in any order. You can write it using only or and then, but it is easier if you use the and combinator instead (solution).

Adding time constraints

By now we have reached a fairly advanced contract, describing in an accurate way a number of sales-related scenarios. There is one serious omission in the contract formulation which we must address: The concept of time does not occur in the contract at all and therefore does not influence which events are allowed or not. Currently, it is perfectly legal behaviour for, e.g., Alice to place an order and then for Bob to wait ten years before delivering the ordered item.

What needs to be done is clear in light of the above sections. All events already have a timestamp field, simply by virtue of being an Event. We will use this timestamp to specify a maximum time period between when an order is placed and when the delivery takes place, i.e., the difference in the timestamp fields on the Order and Delivery events must not exceed some amount of days:

 1contract entrypoint sale = \(buyer, seller, amount, item, inventory, maxDays) ->
 2  // Some buyer orders an item for some price from a seller
 3  <buyer> order: Order where
 4    order.amount = amount &&
 5    order.recipient = seller &&
 6    order.item = item
 7  then (
 8    // The seller delivers that item
 9    <seller> delivery: Delivery where
10      checkOffer inventory order.item order.amount &&
11      delivery.item = order.item &&
12      delivery.recipient = buyer &&
13      // 'Instant::addDays d t' creates a new timestamp that
14      // is 'd' days after timestamp 't'.
15      delivery.timestamp <= Instant::addDays maxDays order.timestamp
16    or
17    // The seller tries to cheat
18    <seller> delivery: Delivery where
19        checkOffer inventory order.item order.amount &&
20        (not (delivery.item = order.item) ||
21         not (delivery.recipient = buyer))
22    then failure
23  )

The new clause that has been added on line 15 states that the Delivery must occur before maxDays has passed since the Order event occurred. We achieve this with a simple comparison of timestamps: The timestamp on the received Delivery event must be no later than a timestamp that is exactly maxDays in the future from the time of the Order event. The function Instant::addDays takes a timestamp and an integer denoting a number of days and creates a new timestamp that is the specified number of days added to the input timestamp (one day is 24 hours). This is another example of the usage of a function from the standard library.

Exercise: Late payment

Extend the pay on delivery exercise again, this time by giving a time limit of 3 days to pay for the delivered item. In case the payment is not received in time, the price increases by a fine, which is as a parameter to the contract (solution).