Skip to main content

Clauses

A Clause is a component of the Contract that will employ defined Functions to complete actions - the Clause is the glue between input conditions, functional logic and the creation of outputs, and is where all of the features and rules of operating the financial instrument will be stored. Multiple Clauses can be defined in a Contract, but each Clause will be evaluated and run on the engine one at a time and in the order they’ve been defined in the Template.

How Clauses work

Clauses can be triggered in a few different ways, here are the methods that can be used

In any case, clauses can be set to run a specific number of times, via the occurences argument

class MyTemplate(Template):
counter = Variable(type_=int, value=0)

@clause(occurrences=2)
def some_clause(self):
self.counter += 1

1. From an Event

  • The Event will need to be pre-defined and imported into the Contract in order to be used. Once defined, the event is available via the events api
  • Once an event has been registered on the engine, the Clause will also be placed on the engine to be evaluated at the next tick.
  • For example, the below code snippet describes a Clause that is triggered on the ad_hoc_payment event.
  • The Clause itself is used to execute a transfer of funds between two participants using the simple_transfer function
  • Event payloads can be any serializable types - effectively, the same set of types as Parameters
  • Note that payload must be a dictionary, and the values of the keys can be any type, per above
    @clause(
on=Event(name="ad_hoc_payment", payload={"amount": Decimal, "more_things": SomeLunaModel}),
description="make payment when triggered",
)
def ad_hoc_payment(self, ctx, event):
simple_transfer(
ctx,
src_participant=self.receiver if self.reverse_direction else self.sender,
dst_participant=self.sender if self.reverse_direction else self.receiver,
amount=Decimal(event.payload.amount),
unit=BalanceSheetUnit[self.currency_unit],
)

2. At a defined point in time

  • Users are able to define when a Clause should be evaluated by using time intervals or specifying the exact time/day to run - similar to a cron job.
  • Inputs can take the format of the following:
- cron, "* * 1 * *"
- english timedelta, "1 day, 12 seconds"
- timedelta object, timedelta(days=1, seconds=12)
  • For example, the below code snippet describes a Clause that is triggered every 30 seconds.
  • The Clause itself is another payment mechanism that uses the simple_transfer function to send funds regularly between two Participants.
    @clause(on=Every("30 seconds"), description="regular payment stream")
def payment(self, ctx):
simple_transfer(
ctx,
src_participant=self.receiver if self.reverse_direction else self.sender,
dst_participant=self.sender if self.reverse_direction else self.receiver,
amount=self.amount_to_use,
unit=BalanceSheetUnit[self.currency_unit],
)

3. At Contract initiation

  • A Clause will be evaluated when the Contract moves to an executing status.
  • For example, the below code snippet describes a Clause that is triggered on Contract initiation.
  • The Clause itself is used to set out the initial values that will govern a payment function.
    @clause(on_init=True)
def initialize(self, ctx):
self.amount_to_use = self.amount
self.reverse_direction = False

For every Clause, a User can define how many times it should be evaluated - this is particularly helpful for instances where it only needs to be run once.

Luna Function Wrapper

The Luna Function wrapper (luna.function) enables an external function to be added to the audit and logging process within the Contract Runtime Engine.

It allows us to write functions outside of Template classes, which can be reused

For example, the below code snippet demonstrates an allocation process which is used by multiple Templates, but maintains its own logging behaviours.

from typing import Dict, Tuple
from decimal import Decimal
import luna
from luna.internal import ContractHandle


@luna.function()
def allocation_function(
*,
total_to_allocate: Decimal,
subscriptions_to_pay: Dict[str, Decimal],
subscription_agreements: list[ContractHandle],
) -> Tuple[Dict[str, Decimal], Decimal]:
"""
gather all of the child "subscription contracts" and get the `sub_amount` parameter.

Pro-rata `amount` over `sub_amount` and `Funder's remainder`
push a `sub_allocation_event` to each child subscription contract (payload: `allocated_amount`).
["sub" here meaning subscription, not "below" but it lines up nicely].
"""

total_allocated = Decimal("0")
for subscription_agreement in subscription_agreements:
if subscription_agreement.contract.subscription_accepted_flag:
percentage = Decimal(
subscription_agreement.contract.subscription_asset_percentage
)
allocation_amount = Decimal(percentage * total_to_allocate)
total_allocated += allocation_amount

if subscription_agreement.contract_id in subscriptions_to_pay:
allocation_amount += subscriptions_to_pay[
subscription_agreement.contract_id
]

subscriptions_to_pay[subscription_agreement.contract_id] = allocation_amount

return subscriptions_to_pay, total_allocated