Heya, lately I’ve been having to write long sequential workflows for internal use-cases. So multiple desks and people are contributing, in turn, to what will ultimately be a single contract having dozens of fields and nearly as many signatories. My Daml ends up looking inelegant and redundant: my templates simply get longer and longer as participants add their data contributions.
Have people found a nicer way to do this?
Abstractly, I have this currently:
template Step1
with
allParties: AllTheDesks
where
signatory allParties.requester
controller allParties.firstContributor can
ContributeFirstThing: ContractId Step2
with
firstThing: FirstThing
do
assert $ firstThingIsLegit firstThing
create Step2 with ..
template Step2
with
allParties: AllTheDesks
firstThing: FirstThing
where
signatory allParties.requester, allParties.firstContributor
controller allParties.secondContributor can
ContributeSecondThing: ContractId Step3
with
secondThing: SecondThing
do
assert $ secondThingIsLegit secondThing
create Step3 with ..
...
...
...
template LastStep
with
allParties: AllTheDesks
firstThing: FirstThing
secondThing: SecondThing
...
..
nthThing: NthThing
where
signatory everybody allParties
I realize that I could make a datatype containing firstThing .. nthThing
, as all optional fields to reduce on verbosity but somehow that doesn’t smell right.
2 Likes
Is it crucial that all steps have all the signatures? I’d probably solve this something like this:
template Workflow
with
workflowId : Text
allParties: AllTheDesks
step : Int
where
ensure (step =< 42 && step >= 0)
signatory [nthContributor allParties n | n <- [0..step]]
observer (everybody allParties)
key (allParties.requester, workflowId) : (Party, Text)
maintainer key._1
choice ContributeFirstThing : ContractId FirstContribution
with
firstThing : FirstThing
controller allParties.firstContributor
do
assertMsg "Wrong step" (step == 0)
create this with step = 1
assert $ firstThingIsLegit firstThing
create FirstContribution with ..
choice ContributeSecondThing : ContractId SecondContribution
with
secondThing : SecondThing
controller allParties.secondContributor
do
assertMsg "Wrong step" (step == 1)
create this with step = 2
assert $ secondThingIsLegit secondThing
create SecondContribution with ..
...
...
...
choice ContributeLastThing : ContractId LastContribution
with
lastThing : LastThing
controller allParties.lastContributor
do
assertMsg "Wrong step" (step == 41)
create this with step = 42
assert $ lastThingIsLegit lastThing
create LastContribution with ..
template FirstContribution
with
workflowId : Text
allParties: AllTheDesks
firstThing: FirstThing
where
signatory [nthContributor allParties n | n <- [0..1]]
observer (everybody allParties)
key (allParties.requester, workflowId) : (Party, Text)
maintainer key._1
...
...
...
template LastContribution
with
workflowId : Text
allParties: AllTheDesks
lastThing: LastThing
where
signatory [nthContributor allParties n | n <- [0..41]]
observer (everybody allParties)
key (allParties.requester, workflowId) : (Party, Text)
maintainer key._1
This way you write each contribution to ledger only once, and you end-state is identical except for all contributions being signed by everybody. If that’s a requirement, you could pull everything together into a final thing signed by everybody in a last step. You could also keep track of the CIDs of the contributions on the Workflow
contract, which would mean malicious parties couldn’t switch them out.
1 Like
I like it! Further benefit is that except in trivial cases, the contributions really ought to have their own workflow; which we can do here. Somebody might object for the use of the step
variable to represent workflow state, but in reality the state is being represented by each individual contribution.
Cheers!
1 Like
Taking a more reductionist approach, you might wish to take advantage of the fact that every template X
implies a simple record type data X
that can be used as ordinary data.
template Step1
with
allParties: AllTheDesks
where
signatory allParties.requester
controller allParties.firstContributor can
ContributeFirstThing: ContractId Step2
with
firstThing: FirstThing
do
assert $ firstThingIsLegit firstThing
let prior = this
create Step2 with ..
template Step2
with
prior: Step1
firstThing: FirstThing
where
signatory prior.allParties.requester, prior.allParties.firstContributor
The prior
in Step2
does not exist as a contract on which choices can be exercised, any more than I can write the tuple (2, 3)
and insist “these are the prime factors of 5500”. It is merely the data therein.
The signatory
clause does add some problems to this approach, but there are a couple ways of cleaning this up, such as treating the signatories differently among the other data in each step, or defining a typeclass instance on each step (with every step after the first being inductive).
2 Likes