Contract life cycle

After having formulated a contract template, it is time to “instantiate” it. Instantiating a contract template corresponds to running a computer program, where the contract specification corresponds to the program executable (or the source code). It is making operational the relation between events that is accepted by a contract and the contract specification.

A residual contract denotes what is remaining of an instantiated contract, where “remaining” means which events the contract still accepts. If a contract specifies that some event is currently expected and that event is applied, the contract is evolved to the residual contract that represents the remaining expected events after the occurrence of the just given event. If a contract no longer accepts any events, i.e. it is reduced to success or failure, it is said to be complete.

Instantiated contracts are stored in a contract store, which for now can be considered a container for keeping instantiated contracts and the events that have been applied to them.

The life cycle of a contract can roughly be summarized as follows:

  1. Instantiate the contract template with required arguments.
  2. Submit an event to the instantiated contract.
  3. If the event applies to the contract, compute the residual contract after applying the submitted event.
  4. Repeat from (2) until the residual contract is complete.

These steps are described in more detail below.

Instantiating contracts

When you instantiate a contract template, an artifact is put into the contract store that represents the contract after zero events have been applied. The precise nature of this “artifact” is not important (and is also dependent on the underlying contract store technology), it is just a piece of data that represents a running contract, much like a process in an operating system represents a running computer program. After a contract has been instantiated, a new entity exists in the contract store. We may instantiate contract templates as often as we want -– a desirable property as we can describe different contracts using the agreed common underlying structure, like reusing a general sales contract for different sales.

Deon Digital maintains a prototype GUI that allows users to interact with a contract storage platform by writing and instantiating contract specifications as well as submitting events to instantiated contracts.

Submitting events to contracts

Two things happen when you submit an event to a contract:

  • First, the system tests whether the event applies to the contract in its current state. This means that the issuing agent and the event fields must match the predicates given in the contract. That is, if a contract expects an event from agent alice, any event from agent bob will be rejected.
  • Secondly, if the event can be accepted by the contract in its current state, the event is applied to the contract, evolving it to a residual version.

We illustrate this using the example contract developed in the preceding section, whose definitions we repeat here:

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
}

val bikeShopInventory =
  [("Bike", 100),
   ("Brakes", 20),
   ("Helmet", 30)] // It's a small bike shop.

val acceptOffer = \inventory -> \(item  : String) -> \(price : Int) ->
  // If the item is listed in the inventory and the price is right,
  // then accept the offer
  List::any
    (\(name, acceptPrice) -> name = item  && price >= acceptPrice)
    inventory

template Sale(buyer, seller, amount, item, inventory, maxDays) =
  // 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 inventory order.item order.amount &&
      delivery.item = order.item &&
      delivery.recipient = buyer &&
      // 'DateTime::addDays t d' creates a new timestamp that
      // is 'd' days after timestamp 't'.
      delivery.timestamp <= DateTime::addDays order.timestamp maxDays
    or
    // The seller tries to cheat
    <seller> delivery: Delivery where
      acceptOffer inventory order.item order.amount &&
      (not (delivery.item = order.item) ||
      not (delivery.recipient = buyer))
    then failure
  )

template BikeSale(buyer, seller) =
  Sale(buyer, seller, 100, "Bike", bikeShopInventory, 3)

In the last line, we instantiated our Sale contract to specify the sale of one bicycle for 100 euros from the seller to the buyer:

Sale(buyer, seller, 100, "Bike", bikeShopInventory, 3)

Notice that we have passed the bikeShopInventory list as the inventory and that we set the maximum delay between the delivery and the order to three days. The buyer and seller parameters remain abstract in the new BikeSale template.

The instantiated contract now corresponds to the following snippet:

<buyer> order: Order where
  order.amount = 100 && // 'amount' is set to 100
  order.recipient = seller &&
  order.item = "Bike" // 'item' is set to "Bike"
then (
  <seller> delivery: Delivery where
    // 'inventory' is set to 'bikeShopInventory'
    acceptOffer bikeShopInventory order.item order.amount &&
    delivery.item = order.item &&
    delivery.recipient = buyer &&
    // 'maxDays' is set to 3
    delivery.timestamp <= DateTime::addDays order.timestamp 3
  or
  <seller> delivery: Delivery where
     acceptOffer bikeShopInventory order.item order.amount &&
     (not (delivery.item = order.item) ||
      not (delivery.recipient = buyer))
  then failure
)

The following is an example of an event that would be accepted:

Order {
  agent = alice,                      // The 'buyer' is now set to 'alice'
  // This is arbitrary, as there are no constraints on the 'timestamp'
  //  of an 'Order' event in the contract.
  timestamp = #2017-12-24T16:00:00Z#,
  amount = 100,
  recipient = bob,                    // The 'seller' is now set to 'bob'
  item = "Bike"
}

Note that the timestamp field is not specified in the first part of the contract, so any value would be accepted. The value of the timestamp field in the Order event does influence the acceptable values of the timestamp in the Delivery, however, as we have instantiated the contract such that it requires that no more than three days pass between an Order and a Delivery.

When this event is applied to the contract, it evolves into something equivalent to the following:

<bob> delivery: Delivery where // 'seller' is set to Bob
  acceptOffer bikeShopInventory "Bike" 100 &&
  delivery.item = "Bike" &&
  delivery.recipient = alice && // 'buyer' is set to Alice
  // 'maxDays' is set to 3
  delivery.timestamp <= DateTime::addDays #2017-12-24T16:00:00Z# 3
or
<bob> delivery: Delivery where
  acceptOffer bikeShopInventory "Bike" 100 &&
  (not (delivery.item = "Bike") ||
  not (delivery.recipient = alice)
then failure

The following is an accepted event for the contract in this state:

Delivery {
  agent = bob,
  // <= 3 days after the 'timestamp' in the 'Order' event
  timestamp = #2017-12-25T17:00:00Z#,
  item = "Bike",
  recipient = alice
}

After this event has been applied, the contract is evolved to a finishing, successful state:

success

The contract store now holds the residual contract, the original specification, and the events that were applied to evolve the original instantiated contract to its current state. As this contract no longer accepts any events, it is complete.

Exercise: Selling a bike

In this and the next exercise, we assume you are using the provided GUI to interact with the system.

First, instantiate the BikeSale contract in the Composer tab of the web application. Next, check that the Viewer tab shows the correct instantiation of the contract. In the Actions tab, try applying an event that should not be accepted, for instance offer too little money for the bike. Verify that the contract hasn’t changed in the View tab. Now apply an event that is expected. Verify that this time the contract has evolved. Continue interacting with the contract to get it to be a success.

Can you get stuck in any way?

Exercise: Make a contract stuck

Wrong instantiation parameters may make it impossible for the contract to ever evolve to a success. Try changing the BikeSale contract in such a way that no chain of events will let it progress. For instance, see what happens if the price offered for the chosen item is too low in the contract instantiation.

Querying past state

Regardless of the state of an instantiated contract, it is possible to query both residual contract and past events from the contract store. The former is available directly from the contract store. For the latter we will use the special function getEvents that is provided in the CSL standard library. The basic idea is that each running contract in the contract store is associated with a unique id. getEvents takes this id as its argument and returns a list of events that have been applied to the corresponding contract in the contract store, sorted with older events before newer events.

In order to query the state of the current contract we will need to have the id of the contract available. Since the contract ids are assigned at instantiation, we need some way to refer to the id in the contract before it is assigned. To do this we take the current contract id as an instantiation argument. At instantiation time we can the use the special self value which will be resolved to the actual contract id.

Using getEvents we can rewrite the above bike example, this time accepting any Order event, but only allowing a Delivery if an Order arrived for a “Bike” for 100 Euro:

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
}
val isCorrectOrder = \contractId -> \buyer -> \seller -> \price -> \item ->
  List::any ( \(e:Event) ->            // List::any from the standard library
    type order = e of {
      Order ->                           // Only consider Order events
        order.recipient = seller &&
        order.amount = price &&
        order.agent = buyer &&
        order.item = item;
      _ -> False                         // All other events
    }) (getEvents contractId) // Function accessing past events

template Sale(self, buyer, seller, price, item) =
  <buyer> Order                          // Accept any Order event from buyer
  then
  <seller> delivery: Delivery where
    isCorrectOrder self buyer seller price item &&
    delivery.item = item &&
    delivery.recipient = buyer

template BikeSale(self, buyer, seller) = Sale(self, buyer, seller, 100, "Bike")

Note that this makes it possible to breach the contract by ordering anything other than a bike for exactly 100 Euro.

Ignoring some of the hairier syntax for type casing (see Value Expression Language for details), the isCorrectOrder is a function that takes the contract id, expected buyer and seller names, price, and item; and checks that there exists a past Order event on the contract with the given id matching these values. The List::any function is defined in the standard library and returns True if any of the elements of the list match the predicate.

Contracts observing the past of other contracts

In the previous section we reference events from the same contract using getEvents. By using the getEvents function with other contract ids we can observe events from other contracts in the contract store. Contract ids may be stored in variables just like other values, so we can provide contract ids to a contract by including them in the events that the contract accepts.

The following artificial example shows how one uses a contract id of a running contract to control the accepted events in another contract: PromiscuousContract is a very simple contract that does nothing but accept any events sent to it. Predicate isMyEvent is used to indicate whether an event is of the type MyEvent. This predicate is used in the contract DependentOnOther to ensure that an event e of the type CarriesContractId only gets accepted, if the contract associated with the id in the field e.contractId has previously accepted any event of the type MyEvent:

type MyEvent : Event {}
type CarriesContractId : Event {
  contractId : ContractId
}

// The "promiscuous contract" accepts any sequence of events.
template rec PromiscuousContract() = <*> Event then PromiscuousContract()

// This predicate takes any event and returns 'True' if and only if the
// event is a 'MyEvent' event, defined above.
val isMyEvent = \(event: Event) ->
  type e = event of {
    MyEvent -> True;
    _ -> False
  }

// This contract accepts a 'CarriesContractId' event only if the contract id
// in the event refers to a running contract in the contract store that has
// previously accepted an event of the type 'MyEvent'.
template DependentOnOther() =
  <*> e: CarriesContractId where
    List::any isMyEvent (getEvents e.contractId)

Let’s say we instantiate the contracts PromiscuousContract and DependentOnOther and that the auto-generated contract id for the instantiated PromiscuousContract is "42". If we supply the following event to DependentOnOther, it will only be accepted if the PromiscousContract has previously accepted an event of type MyEvent:

CarriesContractId {
  contractId = "42",
  timestamp = #2017-12-24T16:00:00Z#, // Arbitrary in this example.
  agent = alice // Arbitrary in this example.
}

In this example, the observed contract and the observing contract have been specified in the same set of declarations. In general, this is not necessary; the contract id supplied to getEvents must identify some instantiated contract in the contract store, but this could be a contract that was instantiated prior to the definition of the observing contract.