Variable not in scope: submitMulti

Following Indentation sensitivity, case sensitivity, and field names vs variable names - #5, I added member as signatory on the Guild template because ultimately I want all parties to agree before the guild can be created.

So, I understand that would then require all parties to authorise the creation of the contract. So for that I used SubmitMulti as outlined in the documentation.

Here is my code:

 guildCid <- submit martin do
    createCmd Individual
      with
        person = martin
        name = "Martin"
        guild
  submitMulti [martin, rita, john, michael, sam, james] [] do
    exerciseCmd guildCid CreateGuild
  pure()

But this gives me the following error

/Users/khurammalik/DevTree/Qirad Agent Network/app/daml/Qirad.daml:113:3: error:
    Variable not in scope:
      submitMulti
        : [Party] -> [a0] -> Commands (ContractId Guild) -> Script a1

I’m confused, because I have added all the parties that need to authorise this submission, I have also checked if they are all able to see this contract and I didn’t supply any arguments to ‘readAs’ as I didn’t feel they’re necessary. I’ve followed the syntax on how to submit this but it still doesn’t seem to be working?

1 Like

submitMulti is only available in SDK ≥ 1.9, you can see the addition in the corresponding release notes. I suspect you might be using an older SDK.

You also have to import Daml.Script but if you didn’t do that you would also get other errors about createCmd and exerciseCmd being out of scope so I don’t expect that this is the issue.

I think it’s also worth pointing out that while submitMulti is great for test cases, you often can’t rely on being able to use it in your actual application since it requires a single user to have authorization for multiple parties. For those cases, it is common to use a propose and accept workflow. You can find more information on that in our documentation.

1 Like

I’m running 1.10

1 Like

Would it be possible to share your full project so we can try to reproduce this? On SDK 1.10 this should definitely work.

Maybe double check that you have import Daml.Script in your file. As mentioned, I would expect that you get other errors as well if you don’t but still worth making sure it’s there.

1 Like

Sure. Actually, even though I have the latest SDK installed, my daml.yaml file actually showed it was running 1.8.1. ( I tried changing it to 1.10.0 to see if it made any difference)

I was indeed using the proposal-accept pattern which is what i was trying to test with the script.
Here is the template layout

module Qirad where

import Daml.Script
import qualified DA.List

-- THE GUILD
-- This is the most important aspect of Qirad

template Guild with
    guildname: Text
    creator: Party
    member: [Party]
  where
    signatory creator, member
    ensure length member >= 5 && DA.List.unique member


template Individual with
    person: Party
    name: Text
    guild: Guild
  where
    signatory person

    controller person can
      CreateGuild : ContractId Guild
        do
          create guild

and then the script


guildtest = do
  martin <- allocateParty "Martin"
  rita <- allocateParty "Rita"
  john <- allocateParty "John"
  michael <- allocateParty "Michael"
  sam <- allocateParty "Sam"
  james <- allocateParty "James"

  let
    guild = Guild with
      guildname = "Ahi"
      creator = martin
      member = [rita, john, michael, sam, james]

  guildCid <- submit martin do
    createCmd Individual
      with
        person = martin
        name = "Martin"
        guild
  submitMulti [martin, rita, john, michael, sam, james] [] do
    exerciseCmd guildCid CreateGuild
  pure()
1 Like

Hi @krmmalik,

With no changes to your templates, the following test works:

guildtest = do
  martin <- allocateParty "Martin"
  rita <- allocateParty "Rita"
  john <- allocateParty "John"
  michael <- allocateParty "Michael"
  sam <- allocateParty "Sam"
  james <- allocateParty "James"

  let
    guild = Guild with
      guildname = "Ahi"
      creator = martin
      member = [rita, john, michael, sam, james]

  guildCid <- submitMulti [martin, rita, john, michael, sam, james] [] do
    createCmd guild
  pure()

There seems to be a few potential misconceptions here, so allow me to mention some clarifications:

  • The let guild = ... block is not creating a contract on the ledger. This is just creating a data record in the running code that has the same shape a contract would have, but is not itself a contract (no contract id, no signature, and thus also no authority). This type of data record can be useful, of course, and in my test above I use as the argument to the actual create command, but it’s important to realize it is not a contract.
  • What you call guildCid in your test is the contract ID of an Individual contract signed only by Martin. This is were the difference between a data record and a contract is crucial: the guild field in an Individual contract is just pure data in your definition. It is not a contract and is thus not signed, and the Individual contract does not carry the authority of the guild members.
  • Under a recent SDK, your code does find submitMulti but fails on an authorization error. This is because the exerciseCmd guildCid CreateGuild action is “dropping privileges”, in a way: despite the submitMulti having authority as all the parties, the CreateGuild choice on a Individual contract only has the authority of the signatory of the Individual contract and the controller of the CreateGuild choice (which in this case are both Martin). But as you can see in my code, if you do have the authority of everyone in the submit, you can create the guild directly.

Note, however, that, as @cocreature mentioned, this is not a propose/accept pattern, this is simulating a case where Martin somehow has the login credentials of everyone. It is unlikely to reflect a real use-case.

For reference, here is a guild creation with propose/accept. Note that no credential sharing is needed, i.e. all the submits are “non-multi”, i.e. each is done by one individual.

module Main where

import Daml.Script
import qualified DA.List

-- THE GUILD
-- This is the most important aspect of Qirad

template Guild with
    guildName: Text
    creator: Party
    members: [Party]
  where
    signatory creator, members
    ensure length members >= 4 && DA.List.unique members


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
        create this with agreedMembers = member::agreedMembers
                         requestedMembers = DA.List.delete member requestedMembers
    preconsuming choice Create: ContractId Guild
        controller creator
      do
        create Guild with guildName, creator, members = agreedMembers

guildtest = do
  martin <- allocateParty "Martin"
  rita <- allocateParty "Rita"
  john <- allocateParty "John"
  michael <- allocateParty "Michael"
  sam <- allocateParty "Sam"
  james <- allocateParty "James"

  propId1 <- submit martin do
    createCmd ProposeGuild with
      guildName = "Martin's guild"
      creator = martin
      requestedMembers = [rita, john, michael, sam, james]
      agreedMembers = []
  propId2 <- submit rita do
    exerciseCmd propId1 Agree with member = rita
  propId3 <- submit john do
    exerciseCmd propId2 Agree with member = john
  propId4 <- submit michael do
    exerciseCmd propId3 Agree with member = michael
  propId5 <- submit sam do
    exerciseCmd propId4 Agree with member = sam
  guildId <- submit martin do
    exerciseCmd propId5 Create
  pure()

A few more notes on this version:

  • I have decided to reduce the number from 5 to 4 to illustrate the option of having fewer accepting members than requested members.
  • Each prospective guild member is now adding their signature one at a time on the proposal. This is the core of the propose/accept pattern.
  • requestedMembers needs to be observer, as otherwise the invited members are not even aware of the proposal. The agreedMembers do not need to be observers because they are signatories and thus already know of the contract.
  • You may want to do more validation, for example that the proposal doesn’t have duplicates in its requestedMembers list, and that the requestedMembers list (+ agreedMembers) is at least as long as the minimal length for creating a guild. You could also add an explicit “refuse” option.

This is not a complete solution by any means, but hopefully it’s still helpful.

2 Likes

@Gary_Verhaegen Thank you so much. You are a real gem and the single greatest contributor to my understand of DAML and Smart Contracts. Thank you so much for putting in so much effort and giving me such detailed replies every time. It really means a lot to me.

Thank you for clarifying the difference between the data declarative usage and contract usage and why this isn’t a true propose-accept pattern. I followed an article (not written by someone from Digital Asset) which said this was the way to do it, but to be honest, I had my reservations at the time as well. I am glad you have clarified, because I had my doubts anyway.

Anyhow, I do need to keep the minimum Guild size to 5 but I see what you’re saying (I think).

Ultimately, I want the Guild to be an entity in its own right such as the “use case 2” example in this article from the DAML Blog because in many cases when a Qirad agent creates a proposal, rather than having to list all the people vouching for him, it will make more sense to say he is part of “xyz” guild and then just to specify the guild which will vouch for him as a whole (but of course, all members of the guild then have to sign off on the proposal and are responsible if he runs away with the money).

I started with the simple (what was my then understanding of it) proposal-accept pattern to learn how to use it so then i could use it as a building block to build up from there.

2 Likes

To further clarify the points in my previous post.

  1. The creator is also a member of the Guild so 1 creator and 4 members make 5 guild members, i wasnt sure how to reflect that the creator is also a member

  2. You asked about constraints. I wanted to ensure that someone that joins a guild is not a member of another guild. I have NO clue about how to even begin that process. Ultimately, what I would like to setup is that one person can only join another guild if they’re either not a member of a guild currently, or they have to first leave the other guild then they can join the new guild.

1 Like

Hi @krmmalik,

These are interesting constraints. Daml cannot meaningfully have the kind of global, implicit state required to express “a person can only be a member of a single guild” without additional context, because Daml is designed for distributed systems in which nobody may know the entire state of the system.

This is the same in the real world: there is no way to ensure that someone is not a member of two groups without some kind of referee entity that knows about both groups.

The approach you would need to use here is the same kind of structure you would have to set up in the real world: the guild cannot exist by itself, it needs to exist as part of a context; some sort of registry that can see the membership of every guild and thus enforce the constraint. Note that this means “every guild that is part of the same registry”, not “every guild in the whole world”.

Another way to look at this problem is that Daml makes trust decisions explicit: users have to choose who they trust. And by “making explicit” here I mean that the nebulous concept of “the system” that you would have to trust in most other software design approaches does not exist in a Daml system. So ultimately you need to trust someone (person or group) to tell you whether or not a person is trying to be a member of more than one guild, and that someone can only know up to the guilds they can see.

Does that make sense? @bernhard is better than I am at explaining this part.

2 Likes

Yes @Gary_Verhaegen , that makes sense, thank you. This has given me enough input to know how to address the situation. I’ll keep this in mind going forward. Thank you.

1 Like

A post was split to a new topic: Understanding preconsuming, assert, and this

I just wanted to go through each line that is new conceptually to me and see if i’ve understood it correctly just so that it can help me move forward. I did reference each line and piece of code against documentation but have some doubts about whether I fully understand what is going on with each line. I’d be grateful for a confirmation on each line to say whether I have understood it correctly or not?

It’s really the choices aspect only. I have my head around contract templates now but choices are completely new to me and this was a lot to take in one go.

so

preconsuming choice Agree: ContractId ProposeGuild

Here we are setting up a choice agree and setting it up for the ProposeGuild contract, yes?
I wasn’t sure why it was a preconsuming choice however. I mean, I understand that the contract is archived beforehand hence the word preconsuming, but I didn’t understand why that is necessary here?

with member: Party

member doesn’t appear in the ProposeGuild or Guild` template, so why do we set it up new here? Why not requestedMember or agreedmember? is it because he/she is neither agreed nor requested at this stage?

assert $ member `elem` requestedMembers

I hardly understood this line at all. Here we are asserting that the member will be mapped into requestedMember?

create this with agreedMembers = member::agreedMembers

Why are we using this ? I’ve not seen that in the creation of a contract before. What does it do? What are we creating here?

That’s my first round of questions, and then I have more on this choice you have setup if you don’t mind. I really appreciate you writing this out for me but I haven’t been able to get my head around what the code is doing exactly.

1 Like

Hi @krmmalik,

I’ll address one point at a time.

preconsuming choice Agree: ContractId ProposeGuild

Here we are setting up a choice agree and setting it up for the ProposeGuild contract, yes?

You can think of choices as functions acting on templates. Here we declare a new choice called Agree that acts on contracts of type ProposeGuild and has a return value of type ContractId ProposeGuild. In other words, when you later on write something like:

ret <- submit party do exerciseCmd proposeCid Agree with ..

the type of ret will be ContractId ProposeGuild.

I wasn’t sure why it was a preconsuming choice however.

A choice can have one of four qualifiers:

  • preconsuming archives the contract before the choice code is executed; this means that the choice code cannot fetch the current contract, because it has already been archived when the code runs. Note that this does not prevent you from accessing the payload (attributes) of the current contract, as those are still in scope.
  • postconsuming archives the contract after the choice has finished running. This means the choice code can fetch the contract if it needs to, but it also means you cannot create a new contract with the same key (because the current one has not been archived yet).
  • nonconsuming means the Daml engine will not automatically archive the contract for you. Note that this does not always mean that the choice does not archive the contract, as the choice code can still call archive self explicitly. This can be very useful if you want to do something before and after archiving, or if you only want to archive if some conditions are met.
  • The default behaviour, when no qualifier is present before the choice keyword, is what is known as a consuming choice in the documentation. Consuming choices have additional properties with respect to privacy that I do not fully understand, and therefore I tend to avoid them. This is why I default to preconsuming unless I have a good reason to use one of the others. In this specific case I think all three options (postconsuming, preconsuming and consuming) that end up archiving the contract would work equally well.
with member: Party

member doesn’t appear in the ProposeGuild or Guild` template, so why do we set it up new here? Why not requestedMember or agreedmember? is it because he/she is neither agreed nor requested at this stage?

This with ... syntax is defining the parameters of the choice, which you can think of as the function arguments. When you exercise the choice, you need to supply them; in this case, in order to call the choice with exerciseCmd, you will need to supply a party. We do not want to reuse a name that already exists here specifically because we want to be able to compare this member party provided by the caller of the API (who would be able to pass in whatever they want) with the parties saved in the contract (agreedMembers and requestedMembers).

assert $ member `elem` requestedMembers

I hardly understood this line at all. Here we are asserting that the member will be mapped into requestedMember?

Not quite. assert is a special operation that takes one Boolean argument (True or False) and does nothing if it is True. In this case, the expression:

member `elem` requestedMember

is True if the member supplied by the person calling the API to exercise the Agree choice is present in the requestedMember list supplied by the person who created the ProposeGuild contract, and False if member is not an element of the requestedMembers list.

If the argument to assert is False, the Daml system interrupts the execution and cancels the current transaction, without having made any change to the ledger. So this line can be read as “proceed only if the person who is currently accepting is someone who has been asked and has not responded yet”.

create this with agreedMembers = member::agreedMembers

Why are we using this ?

The create function takes a payload for a contract and creates a corresponding entry on the ledger. The syntax for creating records (contract payloads are always records) in Daml is fairly varied. Assuming a record defined by:

data MyRecord = MyRecord with a: Int, b: Text

which, as you may recall, can be used directly in Daml, but is also what would be generated for a template:

template MyRecord
  with
    a: Int
    b: Text
  where
  ...

the most direct way to create a record of that type would be:

let mr = MyRecord with a = 1, b = "hello"

But if you already have one, there is a convenient notation to copy it then change only a few keys:

let mr2 = mr with a = 3 -- same as MyRecord with a = 3, b = "hello"

Getting back to our use-case, in the code of a choice you always have access to the special this variable that represents the payload of the contract the choice is being exercised on. So in this case the syntax:

this with agreedMembers = member::agreedMembers
                requestedMembers = DA.List.Delete member requestedMembers

is just a shorthand for:

ProposeGuild with guildName = guildName
                  creator = creator
                  agreedMembers = member::agreedMembers
                  requestedMembers = DA.List.Delete member 

Did I mention that the syntax to create records is very varied? Both of these would also have been equivalent in this context:

ProposeGuild with guildName -- if there is no "=", use the value with the
                  creator -- same name in the current scope
                  agreedMembers = member::agreedMembers
                  requestedMembers = DA.List.Delete member 
ProposeGuild with agreedMembers = member::agreedMembers
                  requestedMembers = DA.List.Delete member
                  .. -- .. means "use values from scope" for
                     -- all values that have the same name as a field

You may also have been confused by the expressions member::agreedMembers and DA.List.delete member requestedMembers. The first one,

member :: agreedMembers

is using the “cons” operator :: to construct a list. You can see this in operation at the repl:

$ daml repl
daml> let a = [1, 2]
daml> 3 :: a
[3,1,2]
daml> 

The function DA.List.delete elem list returns a new list that has the same elements as list except for the first occurrence of elem. Back to the repl:

daml> import DA.List
daml> DA.List.delete 1 [2, 3, 4]
[2,3,4]
daml> DA.List.delete 4 [2, 3, 4]
[2,3]
daml> DA.List.delete 4 [2, 3, 4, 5, 4]
[2,3,5,4]
daml> 

One important line you did not ask about is:

controller member

This tells Daml that only the party member is allowed to exercise this choice. In this case, the combination of these two lines:

        with member: Party
        controller member

means that the person (party) exercising the choice has to pass themselves as a parameter, as in:

  propId2 <- submit rita do
    exerciseCmd propId1 Agree with member = rita

In many cases the controller of a choice will be a single, fixed, known party identified directly on the contract (say, in this case, creator). But in some cases (like here) it is useful to make the controller of a choice an explicit parameter of that choice. Daml will ensure that only the party itself can submit that choice (i.e. Rita could not do exerciseCmd propId2 Agree with member = john), but we can then add more constraints on it if we want. In this case, it is useful to restrict further to only people who are invited.

Hope that helps clarify some of your questions; do not hesitate to ask more if needed.

2 Likes

Holy wowsers. Thank you so much for breaking this down for me. I’m going to take some time to study everything that you’ve said line by line and get back to you.

I was actually rather embarrassed to ask about some of the other stuff you mentioned that I didn’t so I’m really glad you covered it because it probably would have been my next set of questions.

Please allow me some time and I will get back to you.

1 Like

No need to be, none of us were born programmers.

1 Like

I appreciate you saying that. This means much more to me than you know. A lot of this journey – even though it’s just begun – has just been dealing with the sheer imposter syndrome of it all.

1 Like

Let me know when you get over it, I sure haven’t lol

1 Like

Aha! That’s ironically quite reassuring!

1 Like

Just circling back here @Gary_Verhaegen to let you know I finally managed to get back to working on this. Apologies for getting back to you after quite some time.

I went through each line, line by line and all of your explanations and I managed to understand exactly what is going on now, thanks to your explanations. So a big thank you!

1 Like

No worry at all and glad I could be of assistance!

2 Likes