Syntax for supplying arguments for multiple templates to a script
I need some syntax help (I think), either that, or I am not supplying enough arguments.
I have setup a script in which I am supplying arguments to a template that in turn is referencing another template. I don’t know how to supply multiple arguments to it in terms of syntax which is why I think the script seems to be getting a parser error.
Here is my template in question.
template ProposalGuildApproval with
signedproposal: Proposal
approvingmember: [Party]
And the Proposal template
template Proposal with
issuer: Party
investor: Party
guild: Guild
projectdescription: Text
unitsrequired: Int
marketingcost: Int
distributioncost: Int
additionalcost: Int
proposalId: Text -- Key
item: Item
And I believe this is the line where my script is resulting in a parser error on the submission
pending <- alice `submit` do
create ProposalGuildApproval with signedproposal; approvingmember = [alice]
The actual parser error is occurring with the following piece of code
guildapproval = script do (under the “=” sign) which is why I suspect I am not supplying enough arguments but i am not sure what is missing.
Here is the full script code inspired by the documentation
-- RUN_SCRIPT_PROPOSAL --
alice <- allocateParty "Alice"
hakan <- allocateParty "Hakan"
martin <- allocateParty "Martin"
rita <- allocateParty "Rita"
john <- allocateParty "John"
michael <- allocateParty "Michael"
sam <- allocateParty "Sam"
james <- allocateParty "James"
guildapproval = script do
let signedproposal = Proposal with
issuer = alice
investor = hakan
guild = Guild with
guildName = "Martin's Guild"
creator = martin
members = [martin, rita, john, michael, sam, james]
projectdescription = "test"
unitsrequired = 5
marketingcost = 5
distributioncost = 10
additionalcost = 10
proposalId = "p1"
--- because this was a data declaration
item = Item with
unitdesignation = "kilos"
priceperunit = 5
-- Parties cannot create a contract already signed by someone else
initialFailTest <- alice `submitMustFail` do
create ProposalGuildApproval with signedproposal; approvingmember = [alice, hakan]
-- Any party can create a Pending contract provided they list themselves as the only signatory
pending <- alice `submit` do
create ProposalGuildApproval with signedproposal; approvingmember = [alice]
-- Each signatory of the finalContract can Sign the Pending contract
pending <- hakan `submit` do
exercise pending Approve with approver = hakan
pending <- martin `submit` do
exercise pending Approve with approver = martin
pending <- rita `submit` do
exercise pending Approve with approver = rita
pending <- john `submit` do
exercise pending Approve with approver = john
pending <- michael `submit` do
exercise pending Approve with approver = michael
pending <- sam `submit` do
exercise pending Approve with approver = sam
pending <- james `submit` do
exercise pending Approve with approver = james
-- A party can't sign the Pending contract twice
pendingFailTest <- martin `submitMustFail` do
exercise pending Approve with approver = martin
-- A party can't sign on behalf of someone else
pendingFailTest <- martin `submitMustFail` do
exercise pending Approve with approver = rita
alice `submit` do
exercise pending FinaliseApproval with approver = alice
-- END_SCRIPT --
It could be an indentation issue, but I did check indentation as well.
Can you share the full templates including signatory/observer and the choices that are relevant to the Script that is failing? Also include the error message you’re seeing.
Basically the smallest version of everything that makes your code fail with the same error message that you’re currently seeing.
The actual parser error
Could you share the compiler output too?
It could be an indentation issue,
I don’t know if it’s an artifact of the copy-paste, but I would expect everything below the first do (around line 11) to be indented.
Sure. Full templates as below
template Guild with
guildName: Text
creator: Party
members: [Party]
where
signatory creator, members
ensure length members >= 4 && DA.List.unique members
-- initial proposal accept pattern for formation of Guild
template ProposeGuild with
guildName: Text
creator: Party
requestedMembers: [Party]
agreedMembers: [Party]
where
signatory creator, agreedMembers
observer requestedMembers
preconsuming choice Agree: ContractId ProposeGuild
with member: Party
controller member
do
assert $ member `elem` requestedMembers -- if member is in the requestedmembers list is = true
create this with agreedMembers = member::agreedMembers -- :: is cons operator to construct a list, so populate agreedmember list with member list
requestedMembers = DA.List.delete member requestedMembers -- now delete members from the requestedmember list cos we're done
preconsuming choice Create: ContractId Guild
controller creator
do
create Guild with guildName, creator, members = agreedMembers
template Proposal with
issuer: Party
investor: Party
guild: Guild
projectdescription: Text
unitsrequired: Int
marketingcost: Int
distributioncost: Int
additionalcost: Int
proposalId: Text -- Key
item: Item
where
signatory issuer, guild.members
observer investor
-- ensure issuer `elem` guild.members -- want to make sure the issuer is a member of the guild also
-- PROPOSAL_TEMPLATE_END
-- A unique key with a combination of the proposal and the issuer
key (issuer, proposalId) : (Party, Text)
maintainer key._1
template ProposalGuildApproval with
signedproposal: Proposal
approvingmember: [Party]
where
signatory approvingmember
observer guild.members
ensure
DA.List.unique approvingmember
-- The parties who need to approve is the guild.members with approvingmember filtered out
let toApprove = filter (`notElem` approvingmember) guild.members
preconsuming choice Approve : ContractId ProposalGuildApproval with
approver : Party
controller approver
do
-- Check the approver is in the toApprove list, and if they are, approve the ProposalGuildApproval contract
assert (approver `elem` toApprove)
create this with approvingmember = approver :: approvingmember
preconsuming choice FinaliseApproval : ContractId Proposal with
approver : Party
controller approver
do
-- Check that all the required signatories have signed Pending
assert (DA.List.sort approvingmember == DA.List.sort guild.members)
create signedproposal
And error message
/Users/khurammalik/DevTree/Qirad Agent Network/app/daml/Qirad.daml:210:17: error:
parse error on input ‘=’
Possibly an artefact as I’m indenting to the same level as other functioning code which seems to be working fine, but i’m not discounting it completely. I have tried indenting it which hasn’t made a difference.
guildapproval = script do
This is not allowed; the question is, what do you want to be in guildapproval? If you want it to be the result of a script, change = to <-. (Essentially, for example, allocateParty "James" is a little sub-script whose result you assign to the variable james.) If you want guildapproval to be a script, put a let before guildapproval.
You will then encounter the problem @Luciano already mentioned, an Empty 'do' block error. As you do not actually use the variable anywhere in this sample, it’s not exactly clear to me what you want guildapproval to be, which determines what should be in this do block, but it should be easier to correct this after you’ve corrected the first problem. Or, if you do not actually need the variable, you can simply delete this line.
This is not allowed; the question is, what do you want to be in
guildapproval?
I was reading this as guildapproval = script do is the start of the Script, which is certainly allowed. Party assignment needs to be moved below that declaration so it’s inside the do block.
Also I believe @krmmalik is working off of this guide based on some of the contents The Multiple Party Agreement Pattern — Daml SDK 2.7.6 documentation
Then there are other things to adjust in the code which I believe includes working through the process of creating the Guild via ProposeGuild before trying to create the signedproposal with it as we can’t just create a Guild without the signoff of its creator and each of its members.
Thanks for responding @anthony . I’ve moved the party allocation within the do block. I am indeed working off the multiple party agreement documentation.
So the issue with creating a Guild first is that I already have a script setup that goes through the process of setting up a Guild and that works perfectly fine. Now, it could be the way I have put together but when issuing a proposal I didnt want to have to setup a new Guild each time as it is not how I want the business logic to work, so do I need to change my code to reflect that, or is it that i require to do this in this instance but on an actual ledger it might not be necessary?
Could I do a simple let declaration and declare a guild with the required arguments or do I need to explicitly authorise the creation? because I just want to test the script that’s all.
When you run scripts as tests, I would recommend thinking of each script as a standalone workflow, i.e. imagine each is run against a different, brand new, empty ledger. If you have another script that creates a guild, that does not mean your ledger has a guild when guildapproval starts running.
One thing you can do, though, is make functions that return scripts and use those as steps in other scripts. Here is an example:
create_parties = script do
alice <- allocateParty "Alice"
hakan <- allocateParty "Hakan"
martin <- allocateParty "Martin"
rita <- allocateParty "Rita"
john <- allocateParty "John"
michael <- allocateParty "Michael"
sam <- allocateParty "Sam"
james <- allocateParty "James"
return (alice, hakan, martin, rita, john, michael, sam, james)
create_guild : Text -> Party -> [Party] -> Script (ContractId Guild)
create_guild name creator members = script do
prop <- submit creator do
createCmd ProposeGuild with guildName = "test"
creator = creator
requestedMembers = members
agreedMembers = []
prop <- DA.Action.foldlA (\prop member -> do
submit member do exerciseCmd prop Agree with member)
prop
members
submit creator do
exerciseCmd prop Create
test_create_guild = script do
(alice, hakan, martin, rita, john, michael, sam, james) <- create_parties
create_guild "test" alice [hakan, martin, rita, john]
Could I do a simple
letdeclaration and declare aguildwith the required arguments or do I need to explicitly authorise the creation? because I just want to test the script that’s all.
As written, your ProposalGuildApproval template does not accept a Guild at all. The answer to your question would depend on whether the ProposalGuildApproval takes a Guild (yes, you can just let a new Guild, no verification required), a ContractId Guild (you need to create a real contract) or a key for a guild (you don’t need a contract at creation time, but you should probably check for existence in each choice).
Note that if you just take a Guild, as you do in Proposal, the Daml engine will not make any attempt to verify whether or not a contract of type Guild with similar data exists on the ledger.
Overall, I would recommend growing your code more slowly. Add one thing at a time and make sure that one thing works before adding further. There are a couple errors in the code you have pasted here (indentation of the let and attempts to create parties outside of a script, as noted by Stephen and Anthony, but also missing guild field in ProposalGuildApproval resulting in errors on the guild.members constraints); I’d recommend striving to not have more than one error at a time, i.e. try to not add something else until what you have works.
I forgot add Guild back in on ProposalGuildApproval , I had taken it out to test something and forgot to put it back in.
Here’s what that template should look like
template ProposalGuildApproval with
signedproposal: Proposal
guild: Guild
approvingmember: [Party]
Does that change the Script you put together for me in any way? Thank you for the detailed response nonetheless.
Also, if you don’t mind, and even if it is just one line, please could you show me how do I call this script in another script? I’m unfamiliar with the syntax for that. Then I’ll give this a run and see what happens.
Also, if you don’t mind, and even if it is just one line, please could you show me how do I call this script in another script?
In my code sample, test_create_guild calls both create_parties, a Script, and create_guild, a function that returns a Script. That’s really all there is to it.
Does that change the Script you put together for me in any way?
No, because I did not use ProposalGuildApproval in my code snippet. I feel like I should warn you, though, that in this case you are missing out on two validations that I would assume you would want:
- That the
guildexists at all, and - That the
guildis the same assignedproposal.guild.
(Also that signedporposal exists and is signed.)
Thank you, yes I understand about the validations.
I think there’s an issue with the code I’ve been following from the documentation and that might be way I am still getting a parser error. I spotted exercise instead of exerciseCmd for example, and it’s possible there’s an equally trivial omission which causing the parser error. I have taken into consideration the validation errors such as indenting, allocation of parties within the do statement etc but I am still getting a parser error.
Here is the full code for my Script. I’d be grateful if anyone could take a look and spot something that I am unable to (unless my usage of the function is incorrect? but I did try it many different ways)
create_parties = script do
alice <- allocateParty "Alice"
hakan <- allocateParty "Hakan"
martin <- allocateParty "Martin"
rita <- allocateParty "Rita"
john <- allocateParty "John"
michael <- allocateParty "Michael"
sam <- allocateParty "Sam"
james <- allocateParty "James"
return (alice, hakan, martin, rita, john, michael, sam, james)
create_guild : Text -> Party -> [Party] -> Script (ContractId Guild)
create_guild name creator members = script do
prop <- submit creator do
createCmd ProposeGuild with guildName = "test"
creator = creator
requestedMembers = members
agreedMembers = []
prop <- DA.Action.foldlA (\prop member -> do
submit member do exerciseCmd prop Agree with member)
prop
members
submit creator do
exerciseCmd prop Create
test_create_guild = script do
(alice, hakan, martin, rita, john, michael, sam, james) <- create_parties
create_guild "test" alice [hakan, martin, rita, john]
-- RUN_SCRIPT_PROPOSAL --
guildapprovaltest = script do
let guildcreation = create_guild "test" alice [hakan, martin, rita, john]
let signedproposal = Proposal with
issuer = alice
investor = hakan
guild = guildcreation
projectdescription = "test"
unitsrequired = 5
marketingcost = 5
distributioncost = 10
additionalcost = 10
proposalId = "p1"
--- because this was a data declaration
item = Item with
unitdesignation = "kilos"
priceperunit = 5
-- Parties cannot create a contract already signed by someone else
initialFailTest <- alice `submitMustFail` do
createCmd ProposalGuildApproval with signedproposal; approvingmember = [alice, hakan]
-- Any party can create a Pending contract provided they list themselves as the only signatory
pending <- alice `submit` do
createCmd ProposalGuildApproval with signedproposal; approvingmember = [alice]
-- Each signatory of the finalContract can Sign the Pending contract
pending <- hakan `submit` do
exerciseCmd pending Approve with approver = hakan
pending <- martin `submit` do
exerciseCmd pending Approve with approver = martin
pending <- rita `submit` do
exerciseCmd pending Approve with approver = rita
pending <- john `submit` do
exerciseCmd pending Approve with approver = john
-- A party can't sign the Pending contract twice
pendingFailTest <- martin `submitMustFail` do
exerciseCmd pending Approve with approver = martin
-- A party can't sign on behalf of someone else
pendingFailTest <- martin `submitMustFail` do
exerciseCmd pending Approve with approver = rita
alice `submit` do
exerciseCmd pending FinaliseApproval with approver = alice
return()
-- END_SCRIPT --
think there’s an issue with the code I’ve been following from the documentation and that might be way I am still getting a parser error. I spotted
exerciseinstead ofexerciseCmdfor example, and it’s possible there’s an equally trivial omission which causing the parser error.
Could you share the documentation you’re following so we can make sure to fix it? This is probably not an omission but out of date documentation which is still explaining scenarios instead of Daml Script.
As for your script, there are a few errors:
- Script nested inside another script
test_create_guild = script do
(alice, hakan, martin, rita, john, michael, sam, james) <- create_parties
create_guild "test" alice [hakan, martin, rita, john]
guildapprovaltest = script do
let guildcreation = create_guild "test" alice [hakan, martin, rita, john]
It looks like the fourth line might be leftover from an earlier attempt, you can simply delete it to fix the issue.
2. Insufficient indentation in let
let signedproposal = Proposal with
issuer = alice
investor = hakan
guild = guildcreation
projectdescription = "test"
unitsrequired = 5
marketingcost = 5
distributioncost = 10
additionalcost = 10
proposalId = "p1"
--- because this was a data declaration
item = Item with
unitdesignation = "kilos"
priceperunit = 5
You need to indent the fields past the start of signedproposal so something like
let signedproposal = Proposal with
issuer = alice
investor = hakan
guild = guildcreation
projectdescription = "test"
unitsrequired = 5
marketingcost = 5
distributioncost = 10
additionalcost = 10
proposalId = "p1"
--- because this was a data declaration
item = Item with
unitdesignation = "kilos"
priceperunit = 5
- Insufficient indentation for the fields of
Item.
You need to indent those fields further than the fields ofProposal, e.g.,
item = Item with
unitdesignation = "kilos"
priceperunit = 5
- Mixed up contract arguments, contract ids and scripts producing contract ids for
guildcreation.
guildcreation has type Script (ContractId Guild) meaning that it is a script which when executed will produce a ContractId Guild. As a first step, you can change this to actually execute it by replacing = with <-. I’m also changing the name, we’ll see why in a second.
guildcreationId <- create_guild "test" alice [hakan, martin, rita, john]
Now we have a ContractId Guild. However, what you need below is a Guild. You can use queryContractId to get the contract associated with a contract id. This will return an Optional Guild. We can match on Some guildcreation to get a Guild
Some guildcreation <- queryContractId alice guildcreationId
- A few missing template fields
ProposalGuildApprovalexpects aguildfield but you’re not passing one. You can pass theguildcreationwe just got above
-- Parties cannot create a contract already signed by someone else
initialFailTest <- alice `submitMustFail` do
createCmd ProposalGuildApproval with signedproposal; approvingmember = [alice, hakan]; guild = guildcreation
-- Any party can create a Pending contract provided they list themselves as the only signatory
pending <- alice `submit` do
createCmd ProposalGuildApproval with signedproposal; approvingmember = [alice]; guild = guildcreation
- Assertion failure in
FinalizeApproval
aliceis part ofapprovedmemberssince you add her when creating the proposal. However, she is not part of the members of theguild.member. One option for fixing that is to changecreate_guildto add the creator toapprovedmembers.
create_guild : Text -> Party -> [Party] -> Script (ContractId Guild)
create_guild name creator members = script do
prop <- submit creator do
createCmd ProposeGuild with guildName = "test"
creator = creator
requestedMembers = members
agreedMembers = [creator]
prop <- DA.Action.foldlA (\prop member -> do
submit member do exerciseCmd prop Agree with member)
prop
members
submit creator do
exerciseCmd prop Create
I put the full working example at gist:555f2a5f4fa1ea8e0d27b08f278c8daf · GitHub.
Thank you, you guys @anthony, @cocreature and @Gary_Verhaegen are life-savers. You are really helping me continually improve my understanding of DAML. I really appreciate how you guys always go the extra-mile to help me. The code is working now, I will mark the final comment as a solution. Thank you once again.