Contracts
In this section you can see a selection of example CSL contracts. Some of these will be directly copy-pasteable as-is while others assume that they occur in some context with the proper event types defined. It will be clear from the examples whether you need to insert the appropriate missing bits or not.
Many contracts use the recurring event types Payment
and Delivery
, defined as follows:
type Payment : Event {
amount : Int, // Amount of money in euros
receiver : Agent
}
type Delivery : Event {
goods : String
}
Recall that the base Event
type is defined as:
type Event {
agent : Agent,
timestamp : Instant
}
Delivery deadline relative to payment
In this example, a payment from Alice
is required before a delivery from Bob
.
The delivery has a deadline of “two days after receiving payment”:
contract entrypoint deliveryDeadline = \(alice, bob) ->
<alice> p: Payment
then
<bob> d: Delivery where
d.timestamp < Instant::addDays 2 p.timestamp
The expression uses the strictly-less-than comparison operator <
and as a consequence, the delivery cannot be exactly two days later than the payment but only until, but not including, two days later.
Had we instead used the <=
operator, the delivery would be allowed to occur two days after payment on the second.
Late payment
The following is a specification of a contract in which Alice
must pay 100 euros before some deadline and once this has occurred, Bob
has 2 days to perform a delivery of "SomeBike"
.
If Alice
pays after the deadline, she has to pay 110 euros in order to get Bob
to deliver the "SomeBike"
.
Bob
still has to deliver "SomeBike"
before two days after payment when Alice
pays too late.
contract entrypoint latePayment =
\(paymentDeadline: Instant, alice: Agent, bob: Agent) ->
<alice> p: Payment where
p.amount = 100 &&
p.timestamp <= paymentDeadline
then
<bob> d: Delivery where
d.timestamp < Instant::addDays 2 p.timestamp &&
d.goods = "SomeBike"
or
<alice> p: Payment where
p.amount = 110 &&
p.timestamp >= paymentDeadline
then
<bob> d: Delivery where
d.timestamp < Instant::addDays 2 p.timestamp &&
d.goods = "SomeBike"
The contract uses the or
combinator to combine the contracts for payment on time and late payment.
Partial payments
In the previous contracts, the full amount had to be paid in one go. It is possible to write a contract that allows payments to occur over several events:
contract entrypoint partialPayment = \(deadline,alice,bob) ->
let
contract rec payPartially = \remainingAmount ->
<alice> p: Payment where
p.timestamp < deadline &&
p.amount < remainingAmount
then
// She paid only part of the amount, so await the remaining sum.
payPartially (remainingAmount - p.amount)
or
<alice> p: Payment where
p.timestamp < deadline &&
p.amount = remainingAmount
val deliveryDeadline = Instant::addDays 2 deadline
in
payPartially 100 // Alice must pay 100 euros in total
then
<bob> d: Delivery where
d.timestamp < deliveryDeadline &&
d.goods = "SomeBike"
In this example we make essential use of recursive contracts.
We define a local recursive contract called payPartially
which takes as its single parameter the remaining amount of money that has to be paid.
The contract arising from this contract function uses an or
-combination of a recursive call to the contract function itself and another prefix contract to handle the following to cases:
Alice
pays an amount that is smaller than what is owed. The contract function is used to create a new contract that handles the remaining amount.Alice
pays the entire amount and the contract completes successfully.
When the payPartially
-instantiated contract finishes it can only be because Alice
has sent one or more events with payments of 100 euros.
We omit the concept of change here, so the contract only supports exact payments.
At that point, the “top-level” contract is simply:
// .. deliveryDeadline defined earlier ...
(
success
)
then
<bob> d: Delivery where
d.timestamp < deliveryDeadline &&
d.goods = "SomeBike"
One can always simplify a contract success then c
to just c
, so the residual contract is:
// ... deliveryDeadline defined earlier ...
<bob> d: Delivery where
d.timestamp < deliveryDeadline &&
d.goods = "SomeBike"
Only a delivery is expected now.
Escrow
Below is a version of the standard sale-of-bike contract repeated for the sake of example:
contract entrypoint bikeSale = \(seller, buyer) ->
<buyer> p: Payment where
p.amount = 100 &&
p.receiver = seller
then
<seller> d: Delivery where
d.goods = "SomeBike" &&
p.receiver = buyer
One could consider it problematic that the buyer
is obligated to perform a payment before receiving the bike as maybe the bike was not in good condition upon arrival.
However, requiring delivery before payment is also unreasonable because the seller would not like to deliver a bike without knowing that it will be paid for.
Instead we introduce a trusted third-party agent to the contract:
contract entrypoint bikeSaleEscrow = \(trustedParty, seller, buyer, deliveryDeadline) ->
<buyer> p: Payment where
p.receiver = trustedParty &&
p.amount = 100
then (
<seller> d: Delivery where
p.receiver = buyer &&
d.goods = "SomeBike" &&
p.timestamp < deliveryDeadline
then
<trustedParty> tp: Payment where
tp.receiver = seller &&
tp.amount = 100
or
<trustedParty> bp: Payment where
bp.receiver = buyer &&
bp.amount = 100 &&
bp.timestamp > deliveryDeadline
)
The idea is that trustedParty
is a third-party agent that both seller
and buyer
trusts enough to hold the payment in escrow.
Initially, the buyer
submits a Payment
to the trustedParty
of the expected amount.
After that, either the seller
will perform a Delivery
after which the trustedParty
is obliged to perform a Payment
to the seller
.
If the seller does not perform a Delivery
within the deadline, the trustedParty
must to pay back the money to buyer
.
An important thing to consider when entering into the escrow contract is that the resource-flows are as expected.
After successful completion of the contract, either the seller
has one SomeBike
fewer than before and buyer
has 100 fewer euros or no party in the contract have any change in resource holdings.
Transferable contract
The following is an example of a car-ownership/rental contract where the car can be rented out to other parties and where the owner may transfer ownership of the car completely:
type Open : Event {} // Open the car doors
type Close : Event {} // Close the car doors
type Return : Event {} // Return car to owner
type TransferOwnership : Event {
newOwner : Agent
}
type ReserveCar : Event {
renter : Agent,
rentalStart : Instant,
rentalEnd : Instant
}
// When does user stop having the rights to a car
type EndUsage
| Ends Instant // At this point in time.
| Never // The user owns the car.
val mayStillUseCar = \time ->
\ Ends endTime -> time <= endTime
| Never -> True
contract carUsage = \user -> \start -> \endUsage ->
<user> o: Open where
o.timestamp > start &&
mayStillUseCar o.timestamp endUsage
then
<user> Close
contract rec carRental = \renter -> \rentalStart -> \rentalEnd ->
carUsage renter rentalStart (Ends rentalEnd)
then carRental renter rentalStart rentalEnd
or
<renter> Return
contract rec entrypoint carOwnership = \(owner, start) ->
<owner> t: TransferOwnership then carOwnership (t.newOwner, t.timestamp)
or
carUsage owner start Never then carOwnership (owner, start)
or
<owner> r: ReserveCar
then
carRental r.renter r.rentalStart r.rentalEnd
then
carOwnership (owner, start)
Ownership of the car is modeled by the contract carOwnership
.
An owner can do one of the following:
Give the car to someone else by issuing a
TransferOwnership
. This causes the next valid sequence of events to be modeled by a new instance of thecarOwnership
contract with a new owner.Use the car. Car usage is modeled in a simplified way where a user can just open and close the car doors if she is allowed to at that point in time. The
carOwnership
contract makes sure that the owner of the car is always allowed to use the car by setting theendUsage
toNever
.Rent out the car to someone else, temporarily giving the renter the rights to use the car but requiring that she returns it back again. The renter may only use the car until a certain point in time. This is achieved by setting the
endUsage
toEnds rentalEnd
.
This contract also demonstrates the usefulness of sum types.
EndUsage
expresses that either there is an expiration date for the car or the car can be used as long as the user likes, reflecting the situations where the car is either used by the renter or the owner.
We also introduced a convenience function mayStillUseCar
that compares a timestamp to a value of type EndUsage
.
Either the EndUsage
is an Ends d
where d
is an Instant
, in which case we just compare the timestamps to see if the user may use the car, or the EndUsage
is a Never
, in which case the user may always use the car and the function returns True
.
Signing contracts
In the car ownership/rental example above, the car owner basically gets to decide whether a new owner should have the car or not. This is unlikely to model a realistic transfer of ownership, as the recipient might want the chance to either accept or reject the offer. We can extend our contract from above with a notion of signing:
// ... events 'Close', 'Return', etc. defined as before
// ... contracts 'carUsage', 'carRental', defined as before
type Sign : Event {}
type Reject : Event {}
template [theContract, default] SignedContract party deadline =
<party> s: Sign where
s.timestamp < deadline
then theContract
or
<party> Reject then default
contract rec entrypoint carOwnershipSigned = \(owner, start) ->
<owner> t: TransferOwnership
then
SignedContract[
// Transfer ownership if the new owner accepts the transfer and signs.
carOwnershipSigned (t.newOwner, t.timestamp),
// Keep ownership if the new owner rejects the transfer.
carOwnershipSigned (owner, start)
] t.newOwner (Instant::addDays 2 t.timestamp)
// New owner must sign before two days
or
carUsage owner start Never then carOwnershipSigned (owner, start)
or
<owner> r: ReserveCar
then
carRental (r.renter) (r.rentalStart) (r.rentalEnd)
then
carOwnershipSigned (owner, start)
After being offered a car with the TransferOwnership
event, the recipient can choose to either accept or reject the offer with a Sign
or Reject
event, respectively.
We use the contract template SignedContract
with two contract parameters to model the general pattern where two different things might happen depending on whether the user signs or not.
In the carOwnershipSigned
contract we then simply instantiate the SignedContract
with contracts reflecting the cases where the recipient accepts or rejects the car.
For the purposes of this example, the only requirements that are checked on the signature is that it happened before a certain deadline.
Pattern matching contracts
Contract functions can pattern match on their argument. This allows you to write contract specifications that can change behaviour depending on their instantiation arguments or the value of events they accept.
Coupon Payments
With pattern matching and recursive contracts, you can dynamically construct a sequence of prefix-contracts from an input list. For example, many financial products have a list of coupon payments associated with them. Given a list of coupon payment dates and their values, we can use pattern matching and iteration to write a contract that accepts the coupon payments with the right criteria.
// Signifies a payment of a coupon.
type Payment : Event { amount: Float, recipient: Agent }
// Specification of a coupon payment by its date and value.
typealias CouponPaymentSpec = Tuple Instant Float
// Make multiple coupon payments.
// Assumes `couponPaymentDates` is sorted by the first component of the tuple.
contract entrypoint couponPayments = \(
payingAgent: Agent,
recipient: Agent,
couponPaymentDates: List CouponPaymentSpec
) -> let
// Pattern match on a list of CouponPaymentSpec.
// If the list is empty, we're not expecting any more coupon payments.
// If not, we create the contract that waits for a coupon payment
// before continuing with the rest of the list.
contract rec couponPaymentsHelper =
\ Nil -> success
| Cons (nextCouponDate, nextCouponAmount) restCoupons ->
<payingAgent> p : Payment where
p.timestamp = nextCouponDate &&
p.amount = nextCouponAmount &&
p.recipient = recipient
then
couponPaymentsHelper restCoupons
in
couponPaymentsHelper couponPaymentDates
Dynamic Bike Delivery
Pattern matching can also be used to alter the behaviour of a contract depending on its instantiation parameters. For example, when buying a bicycle, the customer might have a choice on how it is delivered. Depending on the customer’s choice, the contract can alter how it must be completed.
// A type modeling how the bike is to be delivered.
type DeliveryOptions
| InPerson Instant // Pick up in store at a specified date.
| DeliveryToHome String // Send to home address provided.
| DeliveryToPackageShop String // Send to a selected package shop.
// Models that a customer has chosen a product and how it should be delivered.
type SelectProduct : Event {
product : String,
deliveryOption : DeliveryOptions
}
// These events each correspond to a constructor of DeliveryOptions above.
type PickUp : Event { product : String }
type Deliver : Event { address : String, product : String }
type DeliverToShop : Deliver {}
// Used to model notification of the recipient that they can pick up their bike.
type Notify : Event { recipient: Agent }
// Pattern matches on its fourth argument, which is of type DeliveryOptions.
// Depending on this value, the contract that is returned accepts a different type of event.
contract delivery = \seller -> \buyer -> \product ->
\InPerson date ->
<buyer> pu : PickUp where pu.timestamp = date && pu.product = product
| DeliveryToHome address ->
<seller> d: Deliver where d.address = address && d.product = product
| DeliveryToPackageShop address ->
<seller> d : DeliverToShop where d.address = address && d.product = product
then
<seller> n : Notify where n.recipient = buyer
// Models a customer selecting a product and their preferred delivery method.
// The contract dynamically accepts the correct delivery event depending on their choice.
contract entrypoint sale = \seller ->
<*> sp : SelectProduct
then
delivery seller sp.agent sp.product sp.deliveryOption