Issue with The Delegation Pattern
I am trying to recreate the example shown in the delegation pattern explained here[The Delegation Pattern — Daml SDK 2.2.0 documentation].
I am facing the following issue, even though Bob is making the request, daml is suggesting that Bob’s Authorisation is missing.
Script execution failed on commit at Main:93:3:
2: create of Main:TransferProposal at DA.Internal.Template.Functions:231:3
failed due to a missing authorization from 'Bob'
Ledger time: 1970-01-01T00:00:00Z
Partial transaction:
Failed exercise (unknown source):
exercises Transfer on #1:1 (Main:Coin)
with
newOwner = 'Bob'
Sub-transactions:
0
└─> 'Bob' exercises TransferCoin on #2:0 (Main:CoinPoA)
with
coinId = #1:1; newOwner = 'Bob'
children:
1
└─> 'Alice' exercises Transfer on #1:1 (Main:Coin)
with
newOwner = 'Bob'
children:
2
└─> create Main:TransferProposal
with
newOwner = 'Bob'
Committed transactions:
TX 0 1970-01-01T00:00:00Z (Main:75:16)
#0:0
│ consumed by: #1:0
│ referenced by #1:0
│ disclosed to (since): 'Alice' (0)
└─> create Main:Coin
with
owner = 'Alice'; issuer = 'Alice'; amount = 10.0000000000; delegates = ['Alice']
TX 1 1970-01-01T00:00:00Z (Main:82:25)
#1:0
│ disclosed to (since): 'Alice' (1)
└─> 'Alice' exercises Disclose on #0:0 (Main:Coin)
with
p = 'Bob'
children:
#1:1
│ disclosed to (since): 'Alice' (1), 'Bob' (1)
└─> create Main:Coin
with
owner = 'Alice';
issuer = 'Alice';
amount = 10.0000000000;
delegates = ['Bob', 'Alice']
TX 2 1970-01-01T00:00:00Z (Main:85:19)
#2:0
│ disclosed to (since): 'Alice' (2), 'Bob' (2)
└─> create Main:CoinPoA
with
attorney = 'Bob'; principal = 'Alice'
Here is the code, most code is from the document, i have just created some dummy templates to fill the gaps
module Main where
import Daml.Script
template Coin
with
owner: Party
issuer: Party
amount: Decimal
delegates : [Party]
where
signatory issuer, owner
observer delegates
choice Disclose : ContractId Coin
with p : Party
controller owner
do create this with delegates = p :: delegates
choice Transfer : ContractId TransferProposal
with
newOwner :Party
controller owner
do create TransferProposal with..
template TransferProposal
with
newOwner : Party
where
signatory newOwner
template CoinPoA
with
attorney: Party
principal: Party
where
signatory principal
observer attorney
choice WithdrawPoA
: ()
controller principal
do return ()
nonconsuming choice TransferCoin
: ContractId TransferProposal
with
coinId: ContractId Coin
newOwner: Party
controller attorney
do
exercise coinId Transfer with newOwner
setup : Script()
setup = script do
-- user_setup_begin
alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
aliceId <- validateUserId "alice"
bobId <- validateUserId "bob"
createUser (User aliceId (Some alice)) [CanActAs alice]
createUser (User bobId (Some bob)) [CanActAs bob]
-- user_setup_end
aliceCoin <- submit alice do
createCmd Coin with
issuer = alice
owner = alice
amount = 10.0
delegates =[alice]
aliceCoinDisclosed <- submit alice do
exerciseCmd aliceCoin Disclose with p = bob
aliceCoinPoa <- submit alice do
createCmd CoinPoA with
attorney = bob
principal = alice
submit bob do
exerciseCmd aliceCoinPoa TransferCoin with newOwner = bob, coinId = aliceCoinDisclosed
return()
In the body of your Transfer choice you have authorization from the signatories & controllers so the issuer & owner. However, creating the TransferProposal requires authorization from the newOwner. There are different ways of fixing this. If you want to go for a proposal workflow the proposal should be signed by the old owner and the newOwner is only an observer. Alternatively, you can require that both the old owner & new owner authorize the transfer and skip the proposal.
Here’s a sketch of the latter:
template Coin
with
owner: Party
issuer: Party
amount: Decimal
delegates : [Party]
where
signatory issuer, owner
observer delegates
choice Disclose : ContractId Coin
with p : Party
controller owner
do create this with delegates = p :: delegates
choice Transfer : ContractId Coin
with
newOwner :Party
controller owner, newOwner
do create this with owner = newOwner
template CoinPoA
with
attorney: Party
principal: Party
where
signatory principal
observer attorney
choice WithdrawPoA
: ()
controller principal
do return ()
nonconsuming choice TransferCoin
: ContractId Coin
with
coinId: ContractId Coin
newOwner: Party
controller attorney, newOwner
do coin <- fetch coinId
coin.owner === principal
exercise coinId Transfer with newOwner
To check my understanding, would it be safe to remove the attorney from the controllers list of choice TransferCoin? It still seems to work if you do so I wonder if this is required given that attorney is a signatory? Thanks.
Controllers serve two functions:
- They provide additional authority that can be used in the choice body (where you have authorization from both signatories & controllers).
- They specify who is allowed to exercise that choice: all controllers need to agree to exercise a choice.
If the controller is also a signatory (as in this example) 1 clearly doesn’t do much. However it can still be very important for 2: Consider the example here, if you drop the attorney from the list of controllers you are saying any newOwner can transfer coins to themselves without the attorney or anyone else needing to agree. That’s almost certainly not what you want.
Makes sense, thanks! I was thinking that an arbitrary newOwner wouldn’t be able to see that contract? Of course reliance on that alone may not be best practice, which I guess is why you have gone for the two controllers approach?
Yeah I would recommend against relying on visibility to control authorization.