6 Parties and authority¶
Daml is designed for distributed applications involving mutually distrusting parties. In a well-constructed contract model, all parties have strong guarantees that nobody cheats or circumvents the rules laid out by templates and choices.
In this section you will learn about Daml’s authorization rules and how to develop contract models that give all parties the required guarantees. In particular, you’ll learn how to:
- Pass authority from one contract to another
- Write advanced choices
- Reason through Daml’s Authorization model
Hint
Remember that you can load all the code for this section into a folder called intro6
by running daml new intro6 --template daml-intro-6
Preventing IOU revocation¶
The SimpleIou
contract from 4 Transforming data using choices and 5 Adding constraints to a contract has one major problem: The contract is only signed by the issuer
. The signatories are the parties with the power to create and archive contracts. If Alice gave Bob a SimpleIou
for $100 in exchange for some goods, she could just archive it after receiving the goods. Bob would have a record of such actions, but would have to resort to off-ledger means to get his money back.
template SimpleIou
with
issuer : Party
owner : Party
cash : Cash
where
signatory issuer
simple_iou_test = do
alice <- allocateParty "Alice"
bob <- allocateParty "Bob"
-- Alice and Bob enter into a trade.
-- Alice transfers the payment as a SimpleIou.
iou <- submit alice do
createCmd SimpleIou with
issuer = alice
owner = bob
cash = Cash with
amount = 100.0
currency = "USD"
passTime (days 1)
-- Bob delivers the goods.
passTime (minutes 10)
-- Alice just deletes the payment.
submit alice do
archiveCmd iou
For a party to have any guarantees that only those transformations specified in the choices are actually followed, they either need to be a signatory themselves, or trust one of the signatories to not agree to transactions that archive and re-create contracts in unexpected ways. To make the SimpleIou
safe for Bob, you need to add him as a signatory.
template Iou
with
issuer : Party
owner : Party
cash : Cash
where
signatory issuer, owner
choice Transfer
: ContractId Iou
with
newOwner : Party
controller owner
do
assertMsg "newOwner cannot be equal to owner." (owner /= newOwner)
create this with
owner = newOwner
There’s a new problem here: There is no way for Alice to issue or transfer this Iou
to Bob. To get an Iou
with Bob’s signature as owner
onto the ledger, his authority is needed.
iou_test = do
alice <- allocateParty "Alice"
bob <- allocateParty "Bob"
-- Alice and Bob enter into a trade.
-- Alice wants to give Bob an Iou, but she can't without Bob's authority.
submitMustFail alice do
createCmd Iou with
issuer = alice
owner = bob
cash = Cash with
amount = 100.0
currency = "USD"
-- She can issue herself an Iou.
iou <- submit alice do
createCmd Iou with
issuer = alice
owner = alice
cash = Cash with
amount = 100.0
currency = "USD"
-- However, she can't transfer it to Bob.
submitMustFail alice do
exerciseCmd iou Transfer with
newOwner = bob
This may seem awkward, but notice that the ensure
clause is gone from the Iou
again. The above Iou
can contain negative values so Bob should be glad that Alice
cannot put his signature on any Iou
.
You’ll now learn a couple of common ways of building issuance and transfer workflows for the above Iou
, before diving into the authorization model in full.
Use propose-accept workflows for one-off authorization¶
If there is no standing relationship between Alice and Bob, Alice can propose the issuance of an Iou to Bob, giving him the choice to accept. You can do so by introducing a proposal contract IouProposal
:
template IouProposal
with
iou : Iou
where
signatory iou.issuer
observer iou.owner
choice IouProposal_Accept
: ContractId Iou
controller iou.owner
do
create iou
Note how we have used the fact that templates are records here to store the Iou
in a single field.
iouProposal <- submit alice do
createCmd IouProposal with
iou = Iou with
issuer = alice
owner = bob
cash = Cash with
amount = 100.0
currency = "USD"
submit bob do
exerciseCmd iouProposal IouProposal_Accept
The IouProposal
contract carries the authority of iou.issuer
by virtue of them being a signatory. By exercising the IouProposal_Accept
choice, Bob adds his authority to that of Alice, which is why an Iou
with both signatories can be created in the context of that choice.
The choice is called IouProposal_Accept
, not Accept
, because propose-accept patterns are very common. In fact, you’ll see another one just below. As each choice defines a record type, you cannot have two choices of the same name in scope. It’s a good idea to qualify choice names to ensure uniqueness.
The above solves issuance, but not transfers. You can solve transfers exactly the same way, though, by creating a TransferProposal
:
template IouTransferProposal
with
iou : Iou
newOwner : Party
where
signatory (signatory iou)
observer (observer iou), newOwner
choice IouTransferProposal_Cancel
: ContractId Iou
controller iou.owner
do
create iou
choice IouTransferProposal_Reject
: ContractId Iou
controller newOwner
do
create iou
choice IouTransferProposal_Accept
: ContractId Iou
controller newOwner
do
create iou with
owner = newOwner
In addition to defining the signatories of a contract, signatory
can also be used to extract the signatories from another contract. Instead of writing signatory (signatory iou)
, you could write signatory iou.issuer, iou.owner
.
The IouProposal
had a single signatory so it could be cancelled easily by archiving it. Without a Cancel
choice, the newOwner
could abuse an open TransferProposal as an option. The triple Accept
, Reject
, Cancel
is common to most proposal templates.
To allow an iou.owner
to create such a proposal, you need to give them the choice to propose a transfer on the Iou
contract. The choice looks just like the above Transfer
choice, except that a IouTransferProposal
is created instead of an Iou
:
choice ProposeTransfer
: ContractId IouTransferProposal
with
newOwner : Party
controller owner
do
assertMsg "newOwner cannot be equal to owner." (owner /= newOwner)
create IouTransferProposal with
iou = this
newOwner
Bob can now transfer his Iou
. The transfer workflow can even be used for issuance:
charlie <- allocateParty "Charlie"
-- Alice issues an Iou using a transfer proposal.
tpab <- submit alice do
createCmd IouTransferProposal with
newOwner = bob
iou = Iou with
issuer = alice
owner = alice
cash = Cash with
amount = 100.0
currency = "USD"
-- Bob accepts the transfer from Alice.
iou2 <- submit bob do
exerciseCmd tpab IouTransferProposal_Accept
-- Bob offers Charlie a transfer.
tpbc <- submit bob do
exerciseCmd iou2 ProposeTransfer with
newOwner = charlie
-- Charlie accepts the transfer from Bob.
submit charlie do
exerciseCmd tpbc IouTransferProposal_Accept
Use role contracts for ongoing authorization¶
Many actions, like the issuance of assets or their transfer, can be pre-agreed. You can represent this succinctly in Daml through relationship or role contracts.
Jointly, an owner
and newOwner
can transfer an asset, as demonstrated in the script above. In 7 Composing choices, you will see how to compose the ProposeTransfer
and IouTransferProposal_Accept
choices into a single new choice, but for now, here is a different way. You can give them the joint right to transfer an IOU:
choice Mutual_Transfer
: ContractId Iou
with
newOwner : Party
controller owner, newOwner
do
create this with
owner = newOwner
Up to now, the controllers of choices were known from the current contract. Here, the newOwner
variable is part of the choice arguments, not the Iou
.
This is also the first time we have shown a choice with more than one controller. If multiple controllers are specified, the authority of all the controllers is needed. Here, neither owner
, nor newOwner
can execute a transfer unilaterally, hence the name Mutual_Transfer
.
template IouSender
with
sender : Party
receiver : Party
where
signatory receiver
observer sender
nonconsuming choice Send_Iou
: ContractId Iou
with
iouCid : ContractId Iou
controller sender
do
iou <- fetch iouCid
assert (iou.cash.amount > 0.0)
assert (sender == iou.owner)
exercise iouCid Mutual_Transfer with
newOwner = receiver
The above IouSender
contract now gives one party, the sender
the right to send Iou
contracts with positive amounts to a receiver
. The nonconsuming
keyword on the choice Send_Iou
changes the behaviour of the choice so that the contract it’s exercised on does not get archived when the choice is exercised. That way the sender
can use the contract to send multiple Ious.
Here it is in action:
-- Bob allows Alice to send him Ious.
sab <- submit bob do
createCmd IouSender with
sender = alice
receiver = bob
-- Charlie allows Bob to send him Ious.
sbc <- submit charlie do
createCmd IouSender with
sender = bob
receiver = charlie
-- Alice can now send the Iou she issued herself earlier.
iou4 <- submit alice do
exerciseCmd sab Send_Iou with
iouCid = iou
-- Bob sends it on to Charlie.
submit bob do
exerciseCmd sbc Send_Iou with
iouCid = iou4
Daml’s authorization model¶
Hopefully, the above will have given you a good intuition for how authority is passed around in Daml. In this section you’ll learn about the formal authorization model to allow you to reason through your contract models. This will allow you to construct them in such a way that you don’t run into authorization errors at runtime, or, worse still, allow malicious transactions.
In Choices in the Ledger Model you learned that a transaction is, equivalently, a tree of transactions, or a forest of actions, where each transaction is a list of actions, and each action has a child-transaction called its consequences.
Each action has a set of required authorizers – the parties that must authorize that action – and each transaction has a set of authorizers – the parties that did actually authorize the transaction.
The authorization rule is that the required authorizers of every action are a subset of the authorizers of the parent transaction.
The required authorizers of actions are:
- The required authorizers of an exercise action are the controllers on the corresponding choice. Remember that
Archive
andarchive
are just an implicit choice with the signatories as controllers. - The required authorizers of a create action are the signatories of the contract.
- The required authorizers of a fetch action (which also includes
fetchByKey
) are somewhat dynamic and covered later.
The authorizers of transactions are:
- The root transaction of a commit is authorized by the submitting party.
- The consequences of an exercise action are authorized by the actors of that action plus the signatories of the contract on which the action was taken.
An authorization example¶
Consider the transaction from the script above where Bob sends an Iou
to Charlie using a Send_Iou
contract.
It is authorized as follows, ignoring fetches:
- Bob submits the transaction so he’s the authorizer on the root transaction.
- The root transaction has a single action, which is to exercise
Send_Iou
on aIouSender
contract with Bob assender
and Charlie asreceiver
. Since the controller of that choice is thesender
, Bob is the required authorizer. - The consequences of the
Send_Iou
action are authorized by its actors, Bob, as well as signatories of the contract on which the action was taken. That’s Charlie in this case, so the consequences are authorized by both Bob and Charlie. - The consequences contain a single action, which is a
Mutual_Transfer
with Charlie asnewOwner
on anIou
withissuer
Alice andowner
Bob. The required authorizers of the action are theowner
, Bob, and thenewOwner
, Charlie, which matches the parent’s authorizers. - The consequences of
Mutual_Transfer
are authorized by the actors (Bob and Charlie), as well as the signatories on the Iou (Alice and Bob). - The single action on the consequences, the creation of an Iou with
issuer
Alice andowner
Charlie has required authorizers Alice and Charlie, which is a proper subset of the parent’s authorizers.
You can see the graph of this transaction in the transaction view of the IDE:
TX #12 1970-01-01T00:00:00Z (Parties:269:3)
#12:0
│ known to (since): 'Bob' (#12), 'Charlie' (#12)
└─> 'Bob' exercises Send_Iou on #10:0 (Parties:IouSender)
with
iouCid = #11:3
children:
#12:1
│ known to (since): 'Bob' (#12), 'Charlie' (#12)
└─> fetch #11:3 (Parties:Iou)
#12:2
│ known to (since): 'Bob' (#12), 'Alice' (#12), 'Charlie' (#12)
└─> 'Bob', 'Charlie' exercises Mutual_Transfer on #11:3 (Parties:Iou)
with
newOwner = 'Charlie'
children:
#12:3
│ known to (since): 'Charlie' (#12), 'Alice' (#12), 'Bob' (#12)
└─> create Parties:Iou
with
issuer = 'Alice';
owner = 'Charlie';
cash =
(Parties:Cash with
currency = "USD"; amount = 100.0)
Note that authority is not automatically transferred transitively.
template NonTransitive
with
partyA : Party
partyB : Party
where
signatory partyA
observer partyB
choice TryA
: ContractId NonTransitive
controller partyA
do
create NonTransitive with
partyA = partyB
partyB = partyA
choice TryB
: ContractId NonTransitive
with
other : ContractId NonTransitive
controller partyB
do
exercise other TryA
nt1 <- submit alice do
createCmd NonTransitive with
partyA = alice
partyB = bob
nt2 <- submit alice do
createCmd NonTransitive with
partyA = alice
partyB = bob
submitMustFail bob do
exerciseCmd nt1 TryB with
other = nt2
The consequences of TryB
are authorized by both Alice and Bob, but the action TryA
only has Alice as an actor and Alice is the only signatory on the contract.
Therefore, the consequences of TryA
are only authorized by Alice. Bob’s authority is now missing to create the flipped NonTransitive
so the transaction fails.
Next up¶
In 7 Composing choices you will put everything you have learned together to build a simple asset holding and trading model akin to that in the IOU Quickstart Tutorial. In that context you’ll learn a bit more about the Update
action and how to use it to compose transactions, as well as about privacy on Daml ledgers.