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.