In several cases I’ve noticed that it is convenient to keep a ContractId
to a contract, and more simply the contract itself (determined via this
) as a parameter of another contract. I’m fairly confident that these holders will be archived before the contract that they keep a reference to, so things “should work”.
- Are there other things to consider?
- When my parameter is a template, what am I actually storing? A copy of the data portion of the contract.
- What if I were to use this parameter as part of the contract key?
Thanks,
3 Likes
What am I actually storing?
When the type of the parameter is a template, then you are storing a copy of the data of some contract. (Templates can also be used as records.) You can use it to see what the contract data is, but you cannot check whether the contract is active or to exercise choices.
When the type of the parameter is a contract id, then you are storing a contract id (without the actual contract data). You can use it to check whether the contract is active (i.e. fetch) and to exercise choices.
What if I use this parameter as part of the contract key?
When the type of the parameter is a template, it behaves as if you add a field of record type to the contract key.
The runtime team is working very hard to allow you adding contract ids to contract keys. I am not sure whether it has already been released, but it is coming soon.
Other things to consider:
Unlike for foreign keys in a database, the referenced contract can get archived before the referencing contract. We cannot guarantee integrity of foreign keys, because that would give opportunities for DOS attacks. I.e., if you are the controller of a consuming choice, we want to allow you to exercise it any time. If we enforced foreign key integrity, you could not do that.
EDIT: Expanded the answer to explain how you can use the contract data / id.
2 Likes
One more thing. If you include contract ids into templates you will likely run into visibility issues. (A party gets a contract id, but it cannot exercise choices on it, because it does not have the contract data.) To deal with that you need to understand divulgence.
https://docs.daml.com/concepts/ledger-model/ledger-privacy.html#divulgence-when-non-stakeholders-see-contracts
1 Like
Thank you @MatthiasSchmalz that clarifies things.
One alternative to storing the ContractID could be to store the key data, via something like:
template Foo
with
fooParty : Party
someString : Text
forKeyInt : Int
where
signatory fooParty
key (fooParty, forKeyInt) : (Party, Int)
maintainer key._1
controller fooParty can
nonconsuming ChannelYourFoo : ()
do
let () = traceRaw (someString <> " " <> show forKeyInt) ()
return ()
template Bar
with
barParty : Party
foo : Foo
where
signatory barParty
controller barParty can
nonconsuming BarFoo : ()
do
(fooId, latestFoo) <- fetchByKey @Foo (key foo)
-- latestFoo == foo, true only the first time.
exercise fooId ChannelYourFoo
return ()
fooBar = scenario do
alice <- getParty "Alice"
f <- submit alice do create Foo with fooParty = alice, someString = "Data", forKeyInt = 10
b <- submit alice do
foo <- fetch f
create Bar with barParty = alice, foo
submit alice do exercise b BarFoo
submit alice do archive f
submit alice do create Foo with fooParty = alice, someString = "New Data", forKeyInt = 10
submit alice do exercise b BarFoo
return ()
What do you think?
1 Like
Storing a contract key is similar to storing a contract ID. The following differences come to my mind:
- A fetch needs to be authorized by at least one stakeholder. A fetchByKey needs to be authorized by at least one maintainer.
- Visibility is harder to manage. Whenever you update the contract referenced by the key, you need to disclose it again. If you are not familiar with divulgence, I would first practice without using contract keys.
Here is an example:
template Foo
with
m : Party
o : Party
where
signatory m
observer o
key m : Party
maintainer key
-- Use this to dislose a Foo with signatory s to a party r
template DiscloseFoo
with
s : Party
r : Party
where
signatory s
observer r
controller s can
Disclose : ContractId Foo
do
(fooId, _) <- fetchByKey @Foo s
return fooId
-- Contract where s authorizes arbitrary parties to fetch Foo contracts
template Baz -- Contract
with
s : Party
o : Party
where
signatory s
observer o
nonconsuming choice BazFoo : ()
with
c : Party
m : Party
controller c
do
(fooId, _) <- fetchByKey @Foo m
return ()
demo = scenario do
alice <- getParty "Alice"
bob <- getParty "Bob"
bobFooId <- submit bob do create Foo with m = bob, o = bob
-- Now let's do some delegation
aliceBazId <- submit alice do create Baz with s = alice, o = bob
bobBazId <- submit bob do create Baz with s = bob, o = alice
-- Succeeds as expected because all involved contracts belong to bob
_ <- submit bob do exercise bobBazId BazFoo with c = bob, m = bob
-- Fails because alice cannot see bobFooId.
_ <- submitMustFail alice do exercise bobBazId BazFoo with c = alice, m = bob
-- Disclose bobFooId to alice
discloseId <- submit bob do create DiscloseFoo with s = bob, r = alice
_ <- submit bob do exercise discloseId Disclose
-- Now succeeds, as bobFooId has been disclosed to alice.
_ <- submit alice do exercise bobBazId BazFoo with c = alice, m = bob
-- Fails because alice is not a maintainer of bobFooId
_ <- submitMustFail alice do exercise aliceBazId BazFoo with c = alice, m = bob
-- Now archive the contract.
_ <- submit bob do archive bobFooId
-- Fails because bobFooId is no longer active.
_ <- submitMustFail alice do exercise bobBazId BazFoo with c = alice, m = bob
-- Create a new version of bob's Foo
bobFooId2 <- submit bob do create Foo with m = bob, o = bob
-- Fails again because alice cannot see bobFooId.
_ <- submitMustFail alice do exercise bobBazId BazFoo with c = alice, m = bob
-- Disclose bobFooId to alice, again
discloseId <- submit bob do create DiscloseFoo with s = bob, r = alice
_ <- submit bob do exercise discloseId Disclose
-- Now succeeds, as bobFooId has been disclosed to alice, again.
_ <- submit alice do exercise bobBazId BazFoo with c = alice, m = bob
return ()
1 Like