What is the best or most accepted pattern to have a template with a signatory that can be one amongst a few potential signatories? (one amongst Jean, Pierre or Paul could be a signatory of a contract - is that feasible?)
Yes, that’s possible, and there is a blog post on the topic. TL;DR:
import DA.Next.Set as S template NofM with sig : Party n : Int m : Int controllers: Set Party where ensure S.size controllers == m signatory sig observer controllers choice C : () with actors : Set Party controller actors do assertMsg "Unauthorized actors" (not (S.null (S.difference actors controllers))) assertMsg "Too few actors" (S.size actors < n) -- Do something return ()
@cocreature pointed out to me that I may have misread your question. Is this what you are after or are you looking for a way to restrict what parties that can go into the
The latter is possible, but I’d consider it a hack. There’s a discussion of the topic here. TL;DR:
import DA.Optional template T with sig : Party where signatory sig ensure sig `elem` mapOptional partyFromText ["Jean", "Pierre", "Paul"]
It’s a hack because you are hardcoding the string party identifiers into the template. That will make the template highly non-portable.
Thanks a lot for this. It does not solve the problem. Not sure that I can be given DAML’s structure.
Expressing these sorts of model-constraints in DAML is always interesting. In order to allow a DAML system to scale, the DAML ledger model imposes a couple of strict limitations (system-constraints) on how you express constraints. In my experience, while this can be frustrating, thinking through and accommodating these within your design generally improves both your understanding of your business problem, and more importantly, makes your final design less brittle and more adaptable to future requirement changes.
The only thing a DAML transaction knows about is what it can read from on the ledger, and the only way it can know of something on the ledger is to be explicitly told about it. This means that with exactly one exception, all constraints in DAML must be expressed existentially. The only universally quantified constraint in DAML is the key-uniqueness constraint—and even that required introducing the whole
maintainersemantic and key maintenance protocol.
With the single exception of key-uniqueness (again), all constraints on the creation of a new contract must be evaluated within the static-local scope of the contract itself.
Both of these ensure that a creation/exercise of a contract/choice can be evaluated and validated with reference to a bounded set of contracts
In order to permit DAML to scale to large numbers of parties, all constraints in DAML must be expressed existentially A constraint on a contracts, and that therefore the submitter can never be surprised at the result of a transaction by being informed of the presence of a contract about which they were not aware*. This is important to allow DAML to scale effectively as a distributed system.
So, to think about your problem:
templates don’t have signatories, only contracts do. So for the rest of this answer I’m going to assume you want to ensure “a contract with a signatory amongst …”.
Recall system-constraint 1. The only thing that DAML knows about is what it can read from the ledger. That means that this idea of “parties from which the signatories must be drawn” must exist on the ledger somehow. Note that DAML is a symmetric multi-party system. Unlike traditional architectures there is no privileged perspective, no super-user, no a priori centre (pulling on this thread ends up with us buried in post-modern deconstructionism, so I’m going to stop there).
The result is that a DAML model (ie. template) can’t express the idea that you are privileged in anyway, that any model that can express the constraint Jean restricts contracts to “Jean, Pierre, or Paul” can also express the constraint Andrae restricts contracts to “John, Peter, or Paula”. Because to do otherwise would be to express a universal property of the ledger, and we are restricted to existential predication.
So we need to existentially model the context of our constraint. Specifically, the perspective of the predicating speaker (Jean or Andrae) must be explicitly modelled. As in all modelling this can be achieved in at least two ways, intensionally or extensionally. In my experience DAML is at its best when used intensionally so I’ll cover that first.
The intensional model is focused on behaviour and implication. We don’t model state as data, but rather as the set of behaviours (choices) and constraints on that behaviour implied by previous acts. This approach sees DAML as a means of achieving coherency of praxis in a symmetric distributed system while denying a central authority (and pulling on this thread leads into semiotic pragmatics, so again I’ll shelve it).
What this means is that if there is a contract on the ledger then there was at some point a consensus on potential acts one of which has caused the creation of this contract.
Ie. If there is a contract:
-- (Note the explicit modelling of the `predicator`, there is no implicit authority you can appeal to, there is nothing that is not on the ledger). template OneOfN with predicator: Party signer: Party where signatory predicator, signer
Then there must have been the right to create this contract…
template RightToCreateOneOfN with predicator: Party potentialSigners: Set Party where signatory predicator nonconsuming choice CreateOneOfN: ContractId OneOfN with signer: Party controller signer do create OneOfN with predicator; signer
…which allows you to now utter the predicate:
Jean requires that all
OneOfN contracts within their authority must be signed by one of the specified signers. Note that the constraint itself is not modelled as data on the ledger, but as a choice, a right to act that has been authorised by whatever prior choice/right-to-act led to the creation of that contract.
Yes, intensionally it’s turtles all the way down until you get to self-authorising unilateral speech acts that therefore do not require or represent consensus.
The alternative is to model this extensionally, This represents the state of the system as data, and constraints on the system as constraints on the permitted values of that data. So naturally this has to comply with the system-constraint 2. One way of rewording constraint 1 is “All intensional constraints must only be existentially predicated”; similarly constraint 2 can be expressed: “All extensional constraints must be contract local”.
So if we want to constrain the signatory of a contract to a set of parties, that predicate must be expressed exclusively in terms of data in that contract. So:
template OneOfNLocal with signer: Party potentialSigners: Party where signatory signer ensure signatory elem potentialSigners
Now, you will immediately observe that while this constraint does encode the letter of the requirement, it falls well short of the spirit. As mentioned above, DAML prefers to be an intensional modelling language. The root of this difficulty is the lack of a central authority. So as a single-signatory template, this represents not consensus on predicated state, but rather a unilateral speech act by
signer, and here we see Eco’s observation that any system that can express a truth can by necessity express a lie.
It is for this reason that
ensure clauses are rare in DAML modelling; however, I didn’t want to ignore them in this discussion as there are times when this sort of constraint is appropriate — generally to guarantee preconditions on the contract’s choices (ie. a list/set is non-empty, a number is positive or non-zero; etc).
Summary: DAML is symmetric and un-centred, as such modelling must be explicit about authority, existentially predicated, and invariably intensional. This is a significant departure from traditional modelling which invariably has an implicit central authority, admits a range of universally quantified predicates, and is oriented around manipulation of extensional data — but this departure is a good thing and essential for scalable, distributed application of the resulting models.
Thanks a lot for this very long yet very precise answer. I do think however that you are splitting hair with my terminology. If people should not refer to template but rather to contract, please change the keyword template to contract. That would surely make communication easier.
@Jean_Safar Was your question whether the following is possible?
template NofM with sigSet : Set Party where signatory (any one from sigSet) s : Scenario () = do sigs@[jean, pierre, paul] <- mapA getParty ["Jean", "Pierre", "Paul"] let sigSet = Set.fromList sigs submit jean do create MofN with sigSet submit pierre do create MofN with sigSet submit paul do create MofN with sigSet return ()
If so, the answer is no. The signatories of a contract have to be computable from the contract arguments only. The
signatory declaration really just desugars to a function
signatory: MofN -> [Party]. This is true for all template annotations:
agreement are all just functions from the contract arguments to something. In other words, contracts on the ledger are not annotated with this information, but this information is computed from the contract.
Thanks a lot @bernhard … The best way I can express what I am after (and apologies in advance for the potential stupidity my pre-weekend thoughts …) is that I am attempting to craft a template/contract where the choice controllers are not parties (Party) but but roles. And the set of parties (Party) that can have that role at any given time is completely dynamic. That yields very complex designs.
For example, I would like to have a template/contract (say Trade) in which a given choice, for example bookTrade, would necessitate a second pair of eyes to approve the trade. I would therefore create a TradeApprovalRequest, but I would need to one approver within my organisation to be able to approve or cancel that trade booking.
So in Summary, I am looking for parties (Party) to be more complex and have a notion of RBAC in them … and maybe that’s completely not kosher, but I think that’s what I am facing, phrased a general manner.
Does it make sense at all?
Ok, if I now understood you correctly, this sort of thing can be implemented on the authorization side, but it’s currently tricky on the privacy/visibility side as changes to the group/role have to be propagated through to the (immutable) contracts that such changes affect.
Ignoring visibility issues, we could implement this:
template TradeApproverGroup with admins : Set Party members : Set Party groupName : Text where signatory admins observer members key (admins, groupName) : (Set Party, Text) maintainer key._1 choice AddMember ... choice RemoveMember ... choice PromoteMember ... choice DemoteAdmin ... template TradeApprovalRequest with group : TradeApproverGroup requester : Party where signatory requester observer group.members choice Accept : ContractId TradeApproval with approver : Party do (groupCid, actualGroup) <- fetchByKey @TradeApproverGroup (group.admins, group.name) assert (approver /= requester && group == actualGroup) create TradeApproval with ..
The problem in the above is: If the group gets updated whilst there is an outstanding TradeApprovalRequest, how do you update the group on the request?
A proposed solution is to allocate parties for such groups:
template TradeApproverGroup with admins : Set Party groupParty : Party members : Set Party where signatory admins, groupParty observer members key groupParty : Party maintainer key choice AddMember ... choice RemoveMember ... choice PromoteMember ... choice DemoteAdmin ... template TradeApprovalRequest with group : Party requester : Party where signatory requester observer group choice Accept : ContractId TradeApproval with approver : Party do (groupCid, actualGroup) <- fetchByKey @TradeApproverGroup group assert (approver /= requester && approver `elem` actualGroup.members) create TradeApproval with ..
The feature that’s missing to make that work is for all members to be able to read as the group party over the API so that they can see the
TradeApprovalRequest. Sadly, this is not possible yet so for now one has to keep the membership relatively stable, or propagate changes through to the observers.
You could conceivably side-step this issue by not mandating that
group == actualGroup, but instead just that
(elem approver actualGroup) && (elem requester actualGroup), i.e. it does not really matter that more people have been added or that unrelated people have been removed, as long as at the time of approval the approver is authorized to approve (and the requester is still authorized to request).
Perhaps the biggest shift in perspective moving to DAML from more traditional approaches is that, when creating a DAML model (= set of templates), you are setting up potential relationships between parties. At runtime, when the application is live, those relationships will be brought to life in the form of contracts recorded on the ledger, which themselves represent that some of the participating parties have committed to specific agreements with other parties.
You are not setting up one application, but the possibility for many peer-to-peer applications. Therefore, in the DAML code itself, you cannot (really: should not) express any global property about the parties. In that sense, what you’re asking for is not (easily) supported: you are not expected to use DAML to define a template and, at the code-definition level, mandate that only the specific parties Jean, Pierre and Paul are able to create contracts based on those templates.
What you can do, as @bernhard’s response illustrates, is set up the templates such that, at runtime, some (larger) group of parties can express that they trust some presumably smaller group of users to do some things.
It’s really hard to be more precise without knowing more about exactly what you wanted Paul, Pierre and Jean to be able to do.