The Finance Library — Part 1
This is the second part of my DAML Masterclass series. See the other parts in the list below:
In this series I reverse engineer a number of advanced reference applications, written by DAML experts, available on the DAML Marketplace, in order to get inspiration for my own projects.
If you start to dissect some financial refapps, you will notice that some of them utilize a library called the Finance Library, available in the DAML Marketplace. So first I will take a good look at this one.
The Finance Library features a number of advanced ideas I wouldn’t have thought of before reading about them, so I picked the ones I found most useful, and called them Big Ideas.
I have identified 5 Big Ideas in the Finance Library, and have divided them into two parts for readability. In this post I will guide you through the first three ones.
Big Idea #1: semantically rich identifiers
One thing you will instantly notice in the Finance Library is that contracts have a complex id with several fields, including the contract’s signatories.
The Id data type is like an ID card, which allows one to inspect directly basic properties of a subject, rather than just providing an ID string serving as a database index to look the properties up.
This also means that the properties contained in the id of some financial object (like an account or an asset) are immutable (within the scope of one specific version), and the id can also be used as a contract key for the contract instances. The Id data type contains the following fields: signatories, label, version.
The Id data record is used as a value given to the id
field of a specific asset or Account
record. You will see eg. later on, that an AssetDeposit
contract, which has an Account
record as the value of its account
field, derives its signatories from its account.id.signatories
field.
You can also notice that the Finance Library uses sets rather than lists as a collection type (imported from the DA.Next.Set module of the standard library), when the order of the elements is not specified and no duplication is allowed, like in the case of the signatories of a contract.
Big Idea #2: modularity
Sometimes it is useful to model one type of legal relationship by a mandatory contract plus one or more optional supplementary contracts. This modular approach gives you more flexibility for specifying the choices available to the participants of said relationship.
An example for the modular approach is the following:
The ownership of some asset, deposited to an account, is represented by an AssetDeposit
contract. AssetDeposit
contracts have the same signatories as their underlying accounts, specified in the contract’s account.id.signatories
field.
By default, the account owner only has the right to split and merge, but not to transfer their asset deposits. This corresponds to real-life situations when an asset’s transferability needs to be restricted, for example if an asset vests over time. Nor can by default any external party credit the account.
(By observing the respective choices, you will notice that you are not limited to split an asset into two pieces, nor are you limited to merge two assets into one, but you can specify an unlimited number of quantities for splitting one asset, or an unlimited number of different assets to be merged into one asset. The choices use the stQty
(= set quantity) utility function to accomplish these operations, defined in the let
block of the template. The choices also use two advanced library functions, namely the mapA
function, which serves to atomically create a series of ledger updates, and the foldlA
function, which serves to combine a series of fetch and archive update steps into one create or update step. I will go into the details of the mechanics of the mapA
function towards the end of this post, where I explain how it is used to process a multistep asset settlement chain.)
The options to transfer the deposited asset, and/or allowing another party to credit the account, are added to the account by an AssetSettlementRule
contract. The asset deposit and the asset settlement rule is connected through their underlying account id.
The AssetSettlementRule
contract contains the following choices:
- The
AssetSettlement_Debit
choice which archives and returns anAsset
, which is deposited to the respectiveAccount
. - The
AssetSettlement_Credit
choice, which creates an asset deposit. This operation requires that the target account has an activeAssetSettlementRule
contract, with the controller of this choice in itsctrls
(controllers) field. - The
AssetSettlement_Transfer
choice, which combines the two above choices atomically.
The way how the AssetDeposit
and the AssetSettlementRule
contract complements each other is shown by this diagram of the README of the Finance Library (the drawing displays a former name for the AssetSettlementRule
contract, but this doesn’t affect the message):
Big Idea #3: multistep settlement chain
In real-life situations we sometimes need a multistep settlement chain rather than direct transfer.
The Finance Library contains a tutorial in the form of a series of scenarios of gradually increasing complexity, starting with a simple intra-bank transfer, all the way up to an interbank transfer involving Central Bank Digital Currency (DBDC).
Interbank transfers already require multiple settlement steps up and down in an account hierarchy in order to achieve the state transition from “Alice having a 1000 USD deposit held with Acme Bank” to “Bob having a 1000 USD deposit held with Genco Bank”.
(On the topic of how DAML is a suitable tool to implement CDBC see two recent posts on the DAML Driven blog: What is a Central Bank Digital Currency and why should people prefer CBDC over bank accounts and Why DAML is great to represent digital currency.)
DAML is a functional language, and for this reason it doesn’t have for
loops or while
loops to process multistep settlement chains.
(For those of you who are not super familiar with the difference between functional and procedural programming languages: Functions in functional languages like DAML or Haskell cannot have inner state. This excludes the use of loops, while the variable containing the index of the actual step of the iteration would be an inner state variable. Furthermore, functions in functional languages are declarative, which means they declare the result of the computations, and not the steps leading to that result.)
As a compensation for the lack of loops, DAML (similarly to other functional languages) has a wide range of functions to process lists. The main operations are slicing, filtering, mapping functions onto lists, combining lists in various ways, and declaring operations in an atomic manner through monads. The bonus is a higher degree of compactness and readability compared to procedural languages, as we’ll see shortly.
I will show you some examples for such functions in the following explanation of the DAML implementation of a settlement chain, defined in the DA.Finance.Trade.SettlementInstruction module.
The basic mechanism of the settlement chain is determined by the fact that
in order to make sure that the settlement chain can be processed without interruption as part of an atomic Delivery vs Payment process, the funds (the respective AssetDeposit contracts) need to be allocated at every step of the chain in advance.
The settlement chain implementation can be best understood by the following questions:
- How the individual steps and the sequence of the settlement chain are represented
- How the validity of the sequence is checked
- How the intermediary parties allocate funds to the steps where they want to act as senders
- How the chain as a whole gets processed in an atomic way
Representation of the individual steps and the sequence
The representation of the individual steps of the settlement chain is the SettlementDetails
data type, which has the following fields:
- a mandatory sender account,
- a mandatory receiver account and
- an optional asset deposit contract id.
The asset deposit contract id is optional, because it gets only filled in when a specific asset deposit contract gets allocated by an intermediary party in the asset allocation phase through the AllocateNext
choice (see later).
The sequence of the individual steps is represented by the SettlementInstruction
contract. The asset type and quantity which needs to be moved along the chain and finally settled (therefore it is uniform along the whole sequence) is specified in the asset
field. The senders, receivers, and — optionally — the allocated asset deposit contracts of each step are contained in the steps
field, as a list of SettlementDetails
records.
Validity check for the sequence of steps
The precondition of a continuous “relay run” is that the sender of the transfer in one step is equal to the receiver in the previous step. This is checked by the following function in the ensure
field of the SettlementInstruction
template:
The function uses the combination of the tail
function from the DA.List module (which cuts off the first element of the steps
list) and the zip
standard library function (which creates pairs from the elements of two lists) to create a list a tuples in the following way:
[step1, step2, step3, …] → [(step1, step2), (step2,step3), …]
and then checks if the senders and receivers match with each other as described above.
If you really want to understand the mechanics working here, I would suggest to try a simplified version of this function in the DAML REPL interactive environment, representing parties with numbers, and steps with tuples of parties:
daml> import DA.Listdaml> let parties = [1..5]daml> tail parties[2,3,4,5]daml> let steps = zip parties $ tail partiesdaml> steps[(1,2),(2,3),(3,4),(4,5)]daml> let chain = zip steps $ tail stepsdaml> chain[((1,2),(2,3)),((2,3),(3,4)),((3,4),(4,5))]daml>
In the list of steps we represent that party1
transfers to party2
, party2
transfers to party3
, etc.
In the list called chain
we put together adjacent steps to be able to check the gapless “relay run” condition. This is a list of tuples, where the first and second element of a tuple are themselves tuples of parties, representing a transfer step from one party to the other party.
Now we can check, with a simplified version of the above ensure
function if the parties to the steps fulfill the gapless “relay run” condition needed for a settlement chain (which is, of course, true because that’s how we defined the steps in the first place):
daml> all (\(s1,s2) -> snd s1 == fst s2) $ zip steps $ tail stepsTruedaml>
Of course, if we mess up the list of steps eg. by swapping party2
to party6
as the sender of the second step, the result of the check will be negative:
daml> let steps' = [(1,2),(6,3),(3,4),(4,5)]daml> zip steps' $ tail steps'[((1,2),(6,3)),((6,3),(3,4)),((3,4),(4,5))]daml> all (\(s1,s2) -> snd s1 == fst s2) $ zip steps' $ tail steps'Falsedaml>
Asset allocation
The intermediary parties can allocate their funds in advance through the SettlementInstruction_Allocatenex
choice. This choice can be exercised at every step by “the next sender” of the asset allocation process.
The “next sender” is either exists or not:
- If the sequence of steps is not fully alocated yet, the “next sender” is that party which is the sender of the first step, where the asset deposit contract id field is
None
. - If the sequence of steps is already fully allocated, the “next sender” doesn’t exist.
The uncertainty that the “next sender” may or may not exist, is handled by the find
monadic function, in the nextSender
function.
Again, if you want to really understand what’s going on, I suggest to check out the following simplified example.
The below example is based on how the find
function works. It has an optional return value, expressing the possibility that it doesn’t find anything which corresponds to the search criteria. In the example below, we are looking for an odd value in the steps
and the steps'
list. The result of the search in the steps
list is Some 5
, and in the steps'
list is None
, because steps'
doesn’t contain any odd values.
If we combine the find
function returning an optional value with the (*2)
function which multiplies every numeric value by 2, in a way, that it only multiplies existing values (using the <$>
operator), we get a monadic function, which returns the double of the first odd value if it exists, and None
, if it doesn’t.
daml> import DA.Listdaml> let steps = [2,4,5,6,8,10]daml> find (\x -> x%2 == 1) stepsSome 5daml> let steps' = [2,4,6,8,10]daml> find (\x -> x%2 == 1) steps'Nonedaml> (*2) <$> find (\x -> x%2 == 1) stepsSome 10daml> (*2) <$> find (\x -> x%2 == 1) steps'Nonedaml>
Now we can understand the following part of the nextSender
function by analogy:
(.senderAccount.owner) <$> find (\step -> isNone step.depositCid) steps
Now let’s examine the SettlementInstruction_AllocateNext
choice:
The magic happens in the following line:
let (done, curr :: next) = break (\x -> isNone x.depositCid) steps
In order to understand this line, we need to take a peek at the break
functin.
The break
function, applied to a filtering function and a list, returns a tuple where
- the first element of the tuple contains that slice of the list, which precedes the first matching result, and
- the second element of the tuple is the rest of the list, starting with the first occurrence of the matching result.
As it is already our habit, we will examine a simplified example.
Let’s assume, we want to modify the first element of a list, which mathes some search. Let’s see the below example, where we have a list, in which we want to multiply by 100 the first element, which is positive. In order to do this
- first, we isolate via pattern matching and the
break
function the non-positive part, the first positive element, and the rest of the list, and - second, we recreate the list in a way, that we leave the non-positive part and the rest unchanged, only changing the first positive element.
daml> import DA.Listdaml> let steps = [0,0,0,0,1,2,3,4]daml> let (zeros,firstPositive::rest) = break (>0) stepsdaml> zeros[0,0,0,0]daml> firstPositive1daml> rest[2,3,4]daml> zeros ++ 100*firstPositive :: rest[0,0,0,0,100,2,3,4]daml>
So now, by analogy, we can understand the following line:
create this with steps = done ++ currNew :: next
We can see several times the triple equation ===
in the code, which is the shortcut to an assert
function checking the equality of two expressions, defined in the DA.Assert module.
Processing of the whole sequence atomically
This is where the magic of atomic transaction processing happens. “Atomic” means that either all transfers of the allocated funds are successful, or neither of them, so the system cannot get stuck in an incorrect state. A transfer can be unsuccessful if an intermediary party, after allocating an asset deposit contract, spends the asset in another transaction.
The atomic transaction handling in the SettlementInstruction_Process
choice gets pulled off by the mapA
function.
In order to better understand what the mapA
function does, let’s say we have an optionalHalf
function which returns an optional value, doing the following:
- If an integer is even, it halves it, and returns the result as a
Some n
value, but - if an integer is odd, it returns
None
.
We have seen another function returning an optional value earlier.
The mapA
function maps the optionalHalf
function to a list so, that
- If all list items are even, than the function halves all of them, and returns the list of halved values of the original list as a
Some xs
value, - If any of the original list items is odd, the function returns
None
.
daml> let optionalHalf n = if n % 2 == 0 then Some (n / 2) else Nonedaml> optionalHalf 10Some 5daml> optionalHalf 11Nonedaml> mapA optionalHalf [2,4,6,8,10]Some [1,2,3,4,5]daml> mapA optionalHalf [2,4,5,8,10]Nonedaml>
The SettlementInstruction_Process
choice works analogously:
- a successful transfer of a previously allocated asset deposit corresponds to the successful halving of an even number,
- a ledger update consisting of all the transfers corresponds to the returning of the element-wise halved list.
Further instruction and templates
You can find further instructions and references to more templates and triggers to implement and automate an actual DvP workflow in the Delivery vs Payment (DvP) Trades section of the README.