Addressing sets of contracts from within a Daml choice

What patterns are available or encouraged within DAML for addressing sets of contracts within the execution of a choice? Most or all of the Daml facilities for retrieving or exercising choices on contracts (fetch, fetchByKey, exercise… ) appear to be defined to work on single contracts only.

The cases for this that come to mind are querying a set of contracts for information or exercising a choice on each of a set of contracts.

The best I’ve been able to come up with is a top level contract that maintains a list of keys that can be used to look up each subordinate contract in the set. For example, I have a Daml model of todo lists, where each list has a set of items, each modeled as a contract.

One of the operations in the model is that a list can iterate over all of the items it contains and update the ownership of each. The code to do this is as follows, and depends on a list of item ID’s (itemIds) that’s maintained in the list contract.

    nonconsuming choice TodoList_UpdateItemOwnership : ()
      with
        party : Party
        existingOwners : [ Party ]
      controller party
      do
        forA itemIds (\itemId ->  do
          (cid, m) <- fetchByKey @TodoItem (existingOwners, itemId)
          exercise cid TodoItem_SetOwnership
            with
                party
                newOwners = owners)
        return ()

My concern with this approach, though, is that it duplicates information in the model. Each todo list has a list of item ID’s and each toto item knows what list it’s a part of. Adding an item to a list therefore means both creating the item contract and updating the list contract with the new item ID.

There’s a similar issue with respect to querying a set of contracts. It’s possible to write code similar to the above that uses itemId to fetch each item in a list, but again, that implies that the list contract needs to have that list of item IDs.

Is there a way to do this sort of thing with out explicitly maintaining lists of keys? Some sort of ‘fetch multiple’ or ‘exercise multiple’.

Would appreciate thoughts or guidance on best practices here. (Even if the current guidance is that this is currently an inappropriate design choice.)

For the sake of completeness, this is the code from the above model that adds an item to a list. This is a choice defined on the todo list contract that’s used to add an item. As you can see, the majority of its job is updating the itemIds list.

    choice TodoList_AddItem : (ContractId TodoList, ContractId TodoItem)
      with
        party : Party
        itemId : Text
        itemDescription : Text
      controller party
      do
        lcid <- create TodoList with
          listId
          name
          owners
          itemIds = itemIds ++ [ itemId ]

        icid <- create TodoItem with
          itemId = itemId
          description = itemDescription
          listId = listId
          owners = owners

        return (lcid, icid)
2 Likes

When designing Daml, one of the choices we have made is that the submitter of a command should know exactly what the consequences of that command will be, if it gets accepted. This design choice is pervasive in the Daml model, and one of its consequences is that there are no facilities for querying the state of the ledger from within a transaction (i.e. within a choice body).

Therefore, we require the submitter to do the querying and submit the list of affected contracts as an argument to the choice being executed. This may result in more contention (if any of these contracts have changed in-between querying and execution), but it gives more predictability, and that is the tradeoff we’ve chosen.

So you have basically two options:

  1. You maintain a list of contracts on the ledger, as you’ve described. The “head” contract could contain a list of contract IDs or a list of keys; I’d favour the former, for the same reproducibility objective.
  2. You delegate to clients the responsibility of querying and submitting the list of contracts.

As an aside, I don’t think it’s a problem per se to maintain bidirectional links between contracts, as Daml code always executes in a transaction. You do need to be careful not to create a broken (out-of-sync) state, though.

1 Like