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.

1 Like

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.

Could you share the compiler output too?

1 Like

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 ‘=’
1 Like

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.

1 Like

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.

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.

1 Like

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?

1 Like

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.

1 Like

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]

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.

2 Likes

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.

1 Like

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.

1 Like

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.

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 guild exists at all, and
  • That the guild is the same as signedproposal.guild.

(Also that signedporposal exists and is signed.)

2 Likes

Thank you, yes I understand about the validations.

1 Like

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 --
1 Like

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:

  1. 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
  1. Insufficient indentation for the fields of Item.
    You need to indent those fields further than the fields of Proposal, e.g.,
        item = Item with
          unitdesignation = "kilos"
          priceperunit = 5
  1. 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
  1. A few missing template fields
    ProposalGuildApproval expects a guild field but you’re not passing one. You can pass the guildcreation we 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
  1. Assertion failure in FinalizeApproval
    alice is part of approvedmembers since you add her when creating the proposal. However, she is not part of the members of the guild.member. One option for fixing that is to change create_guild to add the creator to approvedmembers.
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.

2 Likes

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.

3 Likes