Reason about choice controllers with multi-party submission

Before the multi-party submission feature it was obvious who are the controllers of a choice, in other words who can exercise that choice.

For example take a look the following code:

template Example
  with
    owner : Party
    observers : Set Party
  where
    signatory owner
    observer observers

    nonconsuming choice GetAnswer : Int
      with
        actor : Party
      controller actor
        do pure 42

Without multi-party submission one could state that only the stakeholders can exercise GetAnswer, id est the owner or any of the observers.

That reasoning could be made purely by looking at the Daml code.

With multi-party submission the following is possible:

example_test : Script ()
example_test = do
  alice <- allocateParty "Alice"
  bob <- allocateParty "Bob"

  example <- alice `submit` createCmd Example with owner = alice, observers = Set.singleton alice

  submitMustFail bob $ exerciseCmd example GetAnswer with actor = bob

  answer <- submitMulti [bob] [alice] $ exerciseCmd example GetAnswer with actor = bob
  42 === answer

So, although “Bob” does not have direct access to the contract (submitMustFail succeeds), he can exercise the choice if is granted indirect visibility from “Alice”.

How should I think about this? Is it true that I cannot reason about the visibility of a Daml model any more just by looking at the code? :thinking:

3 Likes

Interesting question!

This is not quite right. Even without multi-party submissions, a non-stakeholder can execute the choice. The prerequisite for that is that the actor has seen the contract. This is clearly the case if they are a stakeholder but it can also be achieved via divulgence. This does appear somewhere in the code but it can happen in a separate package and Daml generally operates in an open world assumption (meaning you don’t know what future code will be added).

So multi-party submissions don’t change anything fundamentally here. They just avoid the need to have to divulge the contract beforehand or add the other party as an observer beforehand.

Here’s a full example of achieving an exercise as a non-stakeholder via divulgence:

module Main where

import Daml.Script

import qualified DA.Set as Set
import DA.Set (Set)

template Example
  with
    owner : Party
    observers : Set Party
  where
    signatory owner
    observer observers

    nonconsuming choice GetAnswer : Int
      with
        actor : Party
      controller actor
        do pure 42

template Divulger
  with
    sig : Party
    obs : Party
  where
    signatory sig
    observer obs
    nonconsuming choice Divulge : Example
      with
        cid : ContractId Example
      controller obs
      do fetch cid

test = script do
  alice <- allocateParty "Alice"
  bob <- allocateParty "Bob"
  cid <- submit alice $
    createCmd (Example alice Set.empty)
  submitMustFail bob $
    exerciseCmd cid (GetAnswer bob)
  divulger <- submit bob $
    createCmd (Divulger bob alice)
  submit alice $
    exerciseCmd divulger (Divulge cid)
  submit bob $
    exerciseCmd cid (GetAnswer bob)
  pure ()
3 Likes