How does variable scoping work in Daml? Getting “ambiguous occurrence” errors trying to pass values into an exerciseCmd in Script.
For example:
File: daml/Workflow.daml
Hidden: no
Range: 33:120-33:127
Source: typecheck
Severity: DsError
Message:
daml/Workflow.daml:33:120: error:
Ambiguous occurrence ‘painter’
It could refer to
either the field ‘painter’,
imported from ‘Paint’ at daml/Workflow.daml:6:1-12
(and originally defined at daml/Paint.daml:13:5-11)
or the field ‘painter’,
imported from ‘Paint’ at daml/Workflow.daml:6:1-12
(and originally defined at daml/Paint.daml:23:5-11)
or the field ‘painter’,
imported from ‘Paint’ at daml/Workflow.daml:6:1-12
(and originally defined at daml/Paint.daml:44:5-11)
ERROR: Creation of DAR file failed.
with the Script code being:
acceptOffer : Parties -> Script (ContractId AcceptByOwner)
acceptOffer parties = do
iouContracts <- fetch @Iou alice
let iouId = head ious
offerContracts <- fetch @OfferToPaintHouseByPainter alice
let offerContract = head offerContracts
acceptId <- submit parties.alice do
exerciseCmd offerContract AcceptByOwner with iouId = iouContract; bank = parties.bank; houseOwner = parties.alice; painter = parties.bob
pure (acceptId)
In this context, this usually means that AcceptByOwner
doesn’t have a field painter
. Otherwise, can you share enough to reproduce the error?
1 Like
OK. That helped fixed that. Was trying to pass too many parameters to choice execution.
I’m trying to perform the sequence in the standard Canton Examples (Iou and Paint) where it performs the same sequence as:
painterOffersAndHouseOwnerAccepts painter houseOwner bank =
script do
painter <- allocateParty painter
houseOwner <- allocateParty houseOwner
bank <- allocateParty bank
let price = Amount {value = 100.0; currency = "USD"}
iouId <- submit bank do
createCmd $ Iou with payer = bank; owner = houseOwner; amount = price; viewers = []
offerId <- submit painter do
createCmd $ OfferToPaintHouseByPainter with
painter = painter; houseOwner = houseOwner; bank = bank; amount = price
painterIouId <- submit houseOwner do
exerciseCmd offerId AcceptByOwner with iouId
submit painter do
exerciseCmd painterIouId Call
But in this case it is being done through Scripts being called with User context against separate participant nodes (So I have to look up contracts before being able to operate on them).
My equivalent of
painterIouId <- submit houseOwner do
exerciseCmd offerId AcceptByOwner with iouId
is now:
acceptOffer : Parties -> Script (ContractId AcceptByOwner)
acceptOffer parties = do
iouContracts <- query @Iou parties.alice
let iou = head iouContracts
let iouId = iou._1
offerContracts <- query @OfferToPaintHouseByPainter parties.alice
let offer = head offerContracts
let offerId = offer._1
acceptId <- submit parties.alice do
exerciseCmd offerId AcceptByOwner with iouId = iouId
pure (acceptId)
This is in the (soon to be public) repo DACH-NY/ex-secure-canton-infra
Your code states that, essentially, acceptId
has type ContractId AcceptByOwner
(see the type declaration of acceptOffer
). However, AcceptByOwner
is not a template type, and therefore cannot have a contract ID; it is the type of a choice argument.
By contrast, see in the original example function where AcceptByOwner
is a contract ID on which Call
is exercised, and the template on which Call
is available? Iou
. Which should be reflected in the return type of the AcceptByOwner
choice being defined to be ContractId Iou
.
OK. So the issue was the return type of the Script. I needed to return an Iou.
Thank you.
I guess there’s kind of a philosophical question here: do you need to return that Iou
? (This comment is about broader design ideas; feel free to ignore if you’re happy to just have solved the problem.)
Script
s are composable, in exactly the same way you can compose Update
s. You can split them into functions and have them return results; the core Script
functions like query
and submit
are themselves whole Script
s that you are just composing into your own bigger Script
. For example, I can factor your “query, take the head, take the contract ID” pattern into a script
assumeFirst: forall tpl p. (Template tpl, IsParties p) => p -> Script (ContractId tpl)
assumeFirst party = do
(headId, _) :: _ <- query @tpl party
pure headId
-- then you can write in acceptOffer
iouId <- assumeFirst @Iou parties.alice
offerId <- assumeFirst @OfferToPaintHouseByPainter parties.alice
-- then you can go back and add better error reporting
-- to assumeFirst if that seems useful
It’s obvious why query
and submit
are declared to have results; you want to put them on the right side of an <-
and use those results for something. It’s also obvious why assumeFirst
should have a result; you actually want the contract ID for something.
Is there such a use for acceptOffer
, i.e. is the contract ID it returns useful? If not, it’s probably best just to finish the script with pure ()
and declare the type as Script ()
.
I agree that no doubt the code could be refactored better. So far this was just to get the based secure deployment (PKI, JWT on a Canton domain with separate participants ) to work. I make no pretence that the Daml side of this is perfect (I am still learning).
I like the proposed function.
The code is in transition and I hadn’t decided whether returning the result and passing into the next function was preferrble to performing the lookup in the next function. So the return value still probably redundant, It does raise questions about how to best pass in parameters to Scripts - do you need a customer data structure or other ways to import required data set.
I don’t know about best, but right now if you want to pass a parameter to a [function that returns a] Script, you have two options:
- Your [function that returns a] Script is invoked in Daml code: it behaves just like any other function, you can pass Daml values to it normally.
- You want your [function that returns a] Script to be an entry point for
daml script
commands. In that case, it has to take exactly one argument, and that argument can be provided through --input-file
using its JSON representation (complete example here).