Reports

In this section we show some example CSL reports. We will focus primarily on reports using the Report Query Language. For some of the examples, we will re-use the contract declarations defined in the examples section.

Late payment

In the late payment contract, the price of a bike depends on whether it was paid for on time or late. By observing the amount paid, we can decide if the payment was late, without knowing the agreed deadline.

relation paymentRel(amount: Int, cid)
| Payment {amount = amount} @ cid

val when = \bexp -> \e -> if (bexp) e else id

val report wasPaymentLate = \(cid: ContractInstance) ->
  let val payment =
    (for (amount, c) in paymentRel do
      when (c=cid) (\_ -> Some amount)) None
  in Maybe::map (\x->x=110) payment

First we define a relation paymentRel, giving us access to the amount field of the Payment event, for a concrete contract instance denoted by cid. As each contract expects exactly one Payment event to occur, if we have found the right contract instance we can ignore the accumulator in the for expression and simply return the amount field. If no payment occurred, this code will return None, if a payment occurred it returns Some True when it was late, and Some False when it was on time.

Partial payments

When taking part in a recursive contract where a specific type of event can occur multiple times, such as the partial payments contract, it is often of interest to inspect the data load of all applied events. For instance in partial payments contract we may want to calculate how much money has already been paid. We can use the same relation as before, but this time we expect multiple payments, and we want to calculate the sum of all the amount fields in Payment events.

relation paymentRel(amount: Int, cid)
| Payment {amount = amount} @ cid

val when = \bexp -> \e -> if (bexp) e else id

val report paidTotal = \cid ->
  (for (amount, c) in paymentRel do
      when (c=cid) (\acc -> acc + amount)
  ) 0

We again focus on elements of paymentRel coming from a specific contract we have picked when calling the report, and add the amount field of each of them to an accumulator. We begin with the accumulator value of 0, as this is the correct total in a contract where no payments were yet made. Note that without the when (c=cid) conditional, the code above would instead calculate the sum of all payments observed by a contract manager, regardless of which contract they originated from.

If, instead of calculating the sum of payments, we simply want to list them in the order in which they were submitted, we have to change the paymentRel definition to also include timestamps.

relation paymentRel(timestamp : Instant, amount: Int, cid)
| Payment {timestamp = timestamp, amount = amount} @ cid

val when = \bexp -> \e -> if (bexp) e else id

val paidDates = \cid ->
  (for (dt, am, c) in paymentRel do when (c=cid) (Cons (dt, am))) Nil

val report sortedPaidDates = \cid ->
  List::sort (\x -> \y -> compareInstant (fst x) (fst y)) (paidDates cid)

paidDates report allows us to construct a list of tuples containing the payment date and the paid amount. We can then sort this list based on the value of the first component, as seen in sortedPaidDates report.

Periodic payments

Another typical scenario is a contract where some payments should occur periodically. Consider for instance a simple monthly payment scheme, where alice pays bob a pre-specified amount of money every 30 days. The first date of payment is given by bob in a TermsAndConditions event, the next ones follow every 30 days.

A typical query to such a contract may be: “When is the next payment due?”. To answer this question we need to know when was the last payment made, and add 30 days to it. If no payment has yet been made, the next payment is due on the date specified in terms and conditions, if that was issued.

type Payment : Event {
  amount : Int,
  receiver : Agent
}

type TermsAndConditions : Event {
  firstPayment : Instant
}

contract entrypoint main = \(alice, bob, amount) ->
  let contract rec recurringPayment = \nextPaymentDate ->
    <alice> p : Payment where
      p.amount = amount &&
      p.timestamp = nextPaymentDate &&
      p.receiver = bob
    then
    recurringPayment (Instant::addDays 30 nextPaymentDate)
  in
  <bob> tac : TermsAndConditions then recurringPayment tac.firstPayment

relation tocRel (timestamp : Instant, cid)
| TermsAndConditions {firstPayment = timestamp} @ cid

relation paymentRel(timestamp : Instant, amount: Int, cid)
| Payment {timestamp = timestamp, amount = amount} @ cid

val when = \bexp -> \e -> if (bexp) e else id

val headTail =
  \ Nil -> None
  | Cons x xs -> Some (x, xs)

val maximum = \lst -> \cmp ->
  let val minFnd =
    foldl (\x -> \y -> (
      \ Less -> y
      | Equal -> y
      | Greater -> x) (cmp x y))
  in Maybe::map (\(hd, tl) -> minFnd hd tl) (headTail lst)

val report paidDates = \cid ->
  (for (dt, _, c) in paymentRel do when (c=cid) (Cons dt)) Nil

val report firstPayment = \cid ->
  (for (dt, c) in tocRel do when (c=cid) (\_ -> Some dt)) None

val report nextPayment = \cid ->
  let val lastPaid = maximum (paidDates cid) compareInstant
      val nextToPay =
        Maybe::map (\dt -> Instant::addDays 30 dt) lastPaid
  in
  if (Maybe::isSome lastPaid) nextToPay else (firstPayment cid)

paidDates and firstPayment reports follow what we have already seen in previous examples. nextPayment is an example of combining reports – it tries to find the date of last payment (i.e. latest date within paidDates) and adds 30 days to it. If no payments have yet been made, both lastPaid and nextToPay will have a value of None. In this case, we resort to the firstPayment value – which again may be a None if the TermsAndConditions event has not been submitted yet. The helper report maximum finds a maximum of a given list using the comparison function provided, whereas the headTail report simply combines the functionality of List::head and List::tail reports from the standard library.