How to fetch a contract using its key - in a late binding fashion?

G-d willing

Hello,
I have a question regarding fetching contracts using an interface.
For simplicity, I will describe the project like so:
I have 2 daml packages a and b. Each package has a template A and B respectively.
Package b is dependent on package a. So, it means that template B can fetch template A, but not vice versa.
But, I need to exercise a choice within a contract of template A, that among the other things the choice does, it will also archive a contract of template B (according to its key value). In other words to do something similar like we do in late binding programming. So, for example, exercise the Archive choice of it, without knowing its type.
Basically, in this situation I would expect that if the choice I am exercising does not exist, an exception will be raised, but if such choice exists, DAML will know how to execute it.

Let me write a simple example, here is the definition of template A:

template A with
    admin: Party
    id : Int
    desc : Text
  where
    signatory admin

    choice DoSomething : ContractId A
      controller admin
        do
          -- need to archive contract of template B which its key is (admin, id)
          create this with desc = "Did something"

And here is the definition of template B:

template B with
    admin: Party
    id : Int
  where
    signatory admin
    key (admin, id) : (Party, Int)
    maintainer key._1

What do you think, is there a way to solve this? Maybe a nice workaround?

Hi Cohen,

At the moment, it’s not possible to act on an interface by key directly. This is something we’ll likely add in the future, but not the near future. There are two possible workarounds:

  1. Define an interface Archivable and pass the contract Id of B to the choice in A as a ContractId Archivable. Ie do the key lookup outside of DoSomething and pass in the result.
  2. Keep companion Reference contracts that allow you to do contract key lookups on interfaces via an indirection. Daml Finance demonstrates this. In short: define the same interface Archivable, but also a companion template Reference that has a contract key and stores a ContractId Archivable. Then you can archive B by first doing a fetchByKey @Reference followed by an exercise.

G-d willing

Thanks @bernhard for your response.
I didn’t understand exactly what you mean. Where can I do the key lookup?
Can you please give me a short code example for A?

Since in DAML Finance you used solution B, is there an advantage for it over the first workaround you offered?

The advantages and disadvantages are illustrated below. Option 1 works really well if your call site of DoSomething is somewhere where you have B in scope and can thus do a fetchByKey @B. I’ll leave it open here how you decide that @B is the right choice.

Option 1 does not work if your call site doesn’t have B in scope, eg if you are calling DoSomething from within package A. In that case, Option 2 is the better one. But it has the downside of having to manage the companion Reference contracts. And note that Reference can potentially reference all kinds of templates, but its key is globally unique. Ie if you had a template C with the same key as B and you also wanted to archive that, you’d need to start storing some type information on the Reference. And then you get back to the question: How does A know about types B and C? In Daml finance this works because the interfaces (eg Account ) are used merely as a means of separating API and implementation, not as a real layer of abstraction. In that case you can have a Reference per interface, ie an Account.Reference and safely assume that the key is indeed globally unique. In the pathological example here where the interface is just SomeTemplate, that doesn’t seem like a valid assumption.

module Main where

-- PACKAGE A
template A
  with
    admin: Party
    id : Int
    desc : Text
  where
    signatory admin

    choice DoSomething_Option1 : ContractId A
      with
        bToArchive : ContractId SomeTemplate
      controller admin
        do
          -- need to archive contract of template B which its key is (admin, id)
          archive bToArchive
          create this with desc = "Did something"
          
    choice DoSomething_Option2 : ContractId A
      with
      controller admin
        do
          -- need to archive contract of template B which its key is (admin, id)
          (refCid, ref) <- fetchByKey @Reference (admin, id)
          archive ref.target
          create this with desc = "Did something"

template Option2_CallSite_Template
  with
    admin : Party
  where
    signatory admin

    nonconsuming choice Option2_CallSite_Choice : ContractId A
      with
        aCid : ContractId A
      controller admin
      do
        exercise aCid DoSomething_Option2
        
          
-- PACKAGE B
template B 
  with
    admin: Party
    id : Int
  where
    signatory admin
    key (admin, id) : (Party, Int)
    maintainer key._1

    interface instance SomeTemplate for B where
      view = Empty

template Option1_CallSite_Template
  with
    admin : Party
  where
    signatory admin

    nonconsuming choice Option1_CallSite_Choice : ContractId A
      with
        aCid : ContractId A
      controller admin
      do
        a <- fetch aCid
        (bCid, _) <- fetchByKey @B (a.admin, a.id)
        exercise aCid DoSomething_Option1 with bToArchive = toInterfaceContractId bCid
        

-- PACKAGE C (INTERFACES)
data Empty = Empty with
  deriving (Eq, Show)

interface SomeTemplate
  where
    viewtype Empty

template Reference
  with
    admin : Party
    id : Int
    target : ContractId SomeTemplate
  where
    signatory admin
    key (admin, id) : (Party, Int)
    maintainer key._1
1 Like

G-d willing

Hi @bernhard, thanks again for your detailed answer and example.
As a matter of fact, I do need to have templates B & C.
Just for understanding, template A represents an account, and templates B & C represent requests on the account, such as withdrawal and Deposit.
The thing is that in certain situations all account requests should be removed (archived). However, each request has its unique ID, meaning that the value of a specific deposit ID can be the same as for a withdrawal ID.

You wrote:

Ie if you had a template C with the same key as B and you also wanted to archive that, you’d need to start storing some type information on the Reference .

How does that make a difference? Why should I store some information about it? Since I am exercising a choice that is available to the interface, I don’t see a problem with it. Can you please explain?

If you have contracts of types B and C with the same key, then References to B and C will have a key collision.

Let’s step back and focus on this statement:

all account requests should be removed

What does “all” range over? How could or would you get a complete list of all requests at any point?

  1. Is there a fixed set of request types, and at most one request of each type at each point in time? In this case the reference method might work by extending Reference to RequestReference which has an extra enum field RequestType which also becomes part of its type. You can then iterate through the enum in the Account.
  2. Is there an index of all requests somewhere? Could you keep one without introducing too much contention?

Secondly I’d question whether a choice in Account is the right place to archive all account requests. Account requests presumably act on Account so if you start also acting the other way, you get circular dependencies/calls. Possibly, but easily leads to spaghetti.
If the situation that requires that “all account requests should be removed” is itself the result of a request, why don’t you keep the management of requests within one package? Eg

{-# LANGUAGE AllowAmbiguousTypes #-}
module Main where

import DA.Foldable (forA_)

-- PACKAGE A
template Account
  with
    admin: Party
    id : Int
    desc : Text
  where
    signatory admin

    choice DoSomething : ContractId Account
      controller admin
        do
          create this with desc = "Did something"
 
-- PACKAGE B
archive_request : forall t k . (HasArchive t, HasLookupByKey t k)  => k -> Update ()
archive_request k = do
  oCid <- lookupByKey @t k
  forA_ oCid archive
archive_all_requests : (Party, Int) -> Update ()
archive_all_requests k = do
  archive_request @Request1 k
  archive_request @Request2 k
  -- ...

template Request1
  with
    admin: Party
    id : Int
  where
    signatory admin
    key (admin, id) : (Party, Int)
    maintainer key._1

    nonconsuming choice Execute_Request1 : ContractId Account
      with
        aCid : ContractId Account
      controller admin
      do
        archive_all_requests (key this)
        exercise aCid DoSomething

template Request2
  with
    admin: Party
    id : Int
  where
    signatory admin
    key (admin, id) : (Party, Int)
    maintainer key._1

    nonconsuming choice Execute_Request2 : ContractId Account
      with
        aCid : ContractId Account
      controller admin
      do
        archive_all_requests (key this)
        exercise aCid DoSomething

G-d willing

Hi @bernhard, let me explain it…

If you have contracts of types B and C with the same key, then References to B and C will have a key collision.

Well, it is simply an Int value that represents the request.
It is something like this:

template B with
    admin: Party
    id : Int
  where
    signatory admin
    key (admin, id) : (Party, Int)
    maintainer key._1

template C with
    admin: Party
    id : Int
  where
    signatory admin
    key (admin, id) : (Party, Int)
    maintainer key._1

Each id for each request is being handled separately.

As for:

How could or would you get a complete list of all requests at any point?

Since I didn’t need to do that before, and it is a new request, so. for that, I am considering adding another template which will be a metadata template that holds all the request details.

template A_Extra with
    admin: Party
    bRequsts : [Request]
    cRequsts : [Request]
  where
    signatory admin
      

This template will be placed on package A, since I need template A to call it and to be able to retrieve the information from it.
And the Request data type will simply store the key for the request, which is the pair values of admin and id. And also the Contract id of the interface that they will implement.

Is there a fixed set of request types

Yes, there are 3 types of requests

and at most one request of each type at each point in time?

No. I can have multiple requests of the same type, several requests of deposit and at the same time several request of withdrawals.

I hope this helps in understanding the situation.

Give you have A_Extra, I would do all the request archivals through A_Extra, not directly through A. There you can run iterate through the three lists and call archive on each Request.

G-d willing

Yes, that is my plan.
The way I wrote my initial question in here was a simplified version of my use case.
But let me go back to your previous concern about the existence of B & C.
Why do I need to keep an extra info about their types?

Let’s say you have a B with key ('admin', 1) and a C with key ('admin', 1). You can’t have a Reference for each one if they key of Reference would also be ('admin', 1). You’d need the key of Reference to be ('admin', 1, "B") and ('admin', 1, "C"). The “B” and “C” are the extra information I was talking about. If you can guarantee that there’ll never be a clash between a B and a C, then you don’t need it.

G-d willing

Hi @bernhard, well I don’t see why it is a problem since I will be fetching the contract according to its interface’ ContractId value, and not by its key value - Since it is not possible to fetch a contract according to its interface type and key value.
(By the way, regardless of this issue, you can see from my previous message that I am keeping each type of request on its own list - this way I know which one is what. )
But let’s say I wasn’t keeping them on separate lists, can you please show me, using a simple code snippet where without it I will have this problem?