template PoolInvite
with
poolKey: PoolKey
newParticipant: Party
where
signatory poolKey._1 -- The pool owner is the creator of the invite
observer newParticipant
key(poolKey, newParticipant): InviteKey
maintainer key._1._1 -- Pool owner
postconsuming choice AcceptInvite : ContractId Pool
controller newParticipant
do
exerciseByKey @Pool poolKey InviteAccept with
invite = self
(in the above snippet, assume newParticipant is not an observer/stakeholder of the Pool)
should already “technically” allow the newParticipant to exercise InviteAccept because the owner is the signatory of the Invite? BUT because of the visibility issue you describe, newParticipant is not able to “find” the contract and thus the exercise fails due to “cannot find contract”?
The first of the blog speaks directly to the use case I was describing.
give that example, my understanding of the pattern is:
click here for full code
module ExpensePoolChain where
{-
Example of using authorization chains from Pool > PoolInvite > PoolMember > Expense.
Where the "org" signatory of the pool is passed into each ~sub-contract (from a business process perspective).
The org (or equivalent) would be a stable/unchanging party.
-}
import Daml.Script
type PoolKey = (Party, Text)
type MemberKey = (Party, Text, Party)
template Pool
with
org: Party
poolMemberRole: Party
owner: Party
poolId: Text -- Never changes
name: Text
where
signatory org
observer owner, poolMemberRole
key(org, poolId): PoolKey
maintainer key._1
nonconsuming choice InviteNewMember : ContractId PoolInvite
with
newMember: Party
controller owner
do
create PoolInvite with
org
poolId
newMember
nonconsuming choice AddExpense : ContractId Expense
with
member: Party
expense: Int
controller member
do
membership <- fetchByKey @PoolMember (org, poolId, member)
create Expense with
poolKey = key(this)
poolMemberRole = poolMemberRole
member
expense
-- Expected to be created through a choice in Pool
template Expense
with
poolKey: PoolKey
poolMemberRole: Party
member: Party
expense: Int
where
signatory poolKey._1, member -- Org and Member
-- Expected to be created through a choice in Pool
template PoolInvite
with
org: Party
poolId: Text
newMember: Party
where
signatory org
observer newMember
choice AcceptInvite : ContractId PoolMember
controller newMember
do
create PoolMember with
org
poolId
member = newMember
-- Expected to be created through a choice in PoolInvite (where pool invite was created through the pool)
template PoolMember
with
org: Party
poolId: Text
member: Party
where
signatory org, member
key(org, poolId, member): MemberKey
maintainer key._1
normalFlow = script do
[alice, bob, org, pool_member] <- mapA allocateParty ["Alice", "Bob", "Org", "POOL_MEMBER"]
let poolId = "pool-1234"
-- Create a pool:
poolCid <- submitMulti [org] [] do
createCmd Pool with
org
poolMemberRole = pool_member
owner = alice
poolId = poolId
name = "Pool1"
-- Invite Bob to the Pool:
invite1Cid <- submitMulti [alice] [] do
exerciseCmd poolCid InviteNewMember with
newMember = bob
-- Bob accepts invite:
poolMemberCid <- submitMulti [bob] [] do
exerciseCmd invite1Cid AcceptInvite
-- Test comparing signatories:
Some (bobsMemCid, bobsMem) <- queryContractKey @PoolMember bob (org, poolId, bob)
Some (bobsPoolCid, bobsPool) <- queryContractKey @Pool [org] (org, poolId)
let sigsMatch: Bool = signatory(bobsMem) == signatory(bobsPool) -- A pool and membership have same signatories
-- Querying for the pool as a "pool_member"
Some (bobsPoolAsMemberCid, bobsPoolAsMember) <- queryContractKey @Pool [bob, pool_member] (org, poolId)
-- Because the IAM would provide bob with the pool_member readAs pool_member permission.
-- Permission would have been determined by bob having an active PoolMember contract signed by the Pool org.
bobsExpense <- submitMulti [bob] [pool_member] do
exerciseByKeyCmd @Pool (org, poolId) AddExpense with
member = bob
expense = 22
return ()
If this example, the org is passed as a signatory into each “sub-contract” in the business process: Pool > Pool Invite > PoolMember > Expense.
The choices are allowing the re-use and passing of the signatories into the sub-contracts.
The IAM could/would check if the user/party has an active contract for PoolMember which was signed by Org and if yes then grant the readAs pool_member permission/party in the JWT.
Validation of each sub-contract would always be based on the org signatory which can only come from the parent contract choices (unless created directly by org as some sort of admin function).
Assuming this flow makes sense and is correct @cocreature ?
I understood the authorization patterns as described in the docs, but i feel this pattern / power/implications of how you can re-use signatories to sign sub contracts is something that is not well articulated in the docs and tutorials.
I am looking at triggers as a way to inspect the PoolMember Contract and generate rights based on that contract: My understanding is:
Have to inspect the PoolMember
lookup the Pool using the Key, derived by the data in the PoolMember (the signatory of the member is the “pool”)
BUT how would you restrict the ability of other actors to crreate a pool and memberships but reference the same role-party? if they generated an equiv tree of contracts, the result would be adding the same party-role. If you create pool-specific party-roles this would potentially lead to massive number of parties? (down side to this?)
Please ask new questions in separate threads, that makes it easier for us to keep track of what has already been answered and easier for others to discover if there is already an answer to an issue they’re having.