Contract life cycle

After having formulated a contract, it is time to “instantiate” it. Instantiating a contract 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 are 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 contract store provides an interface for submitting events to the contract and for querying its state and all past events that have been accepted by it so far.

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

  1. Instantiate the contract with required arguments. The contract specification must be marked with the modifier entrypoint as discussed here.

  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, 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 contracts 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.

Contracts that can be instantiated are marked with entrypoint in the CSL code. This modifier roughly means that the contract is “public” and that some additional restrictions on its input types must be fulfilled. This ensures that it’s always possible to construct values of the requested types from the client code that attempts to do the instantiation.

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

contract 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 &&
      // 'Instant::addDays t d' creates a new timestamp that
      // is 'd' days after timestamp 't'.
      delivery.timestamp <= Instant::addDays maxDays order.timestamp
    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
  )

contract entrypoint 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 contract.

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 <= Instant::addDays 3 order.timestamp
  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 <= Instant::addDays 3 #2017-12-24T16:00:00Z#
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

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.