I need the exercise different choices on a contract using keys and based on JSON files. Using the JSON API this is practically trivial. Can I do that in a uniformed way using DAML Script?
For example let’s assume I have these two JSON files:
FileA.json
:
{
"exerciser": "Alice",
"templateId": "My.Example:Template",
"key": {
"_1": "Some",
"_2": "Value"
},
"choice": "A",
"argument": { args for choice A }
}
and
FileB.json
:
{
"exerciser": "Alice",
"templateId": "My.Example:Template",
"key": {
"_1": "Some",
"_2": "Value"
},
"choice": "B",
"argument": { args for choice B }
}
It’s not that hard to create a DAML Script to exercise on of these specific choices and make it accept a JSON file. For example this pseudo code would work:
data ArgsA = ArgsA with
exerciser: Party
key: (Party, Party)
argument: A
exerciseFromJson : ArgsA -> Script ()
exerciseFromJson args = error "Not implemented."
How would I go about it if I do not wish to hardcode the template, the choice etc?
1 Like
DAML is, by design, a strongly, statically typed language. This means there is no easy way to, at runtime, turn a random string into a choice, because strings and choices are not the same type.
If you’re willing to narrow down your goal a little bit, and instead of trying to be able to turn any arbitrary string into a choice, you’re willing to accept a known set of possible choices, you could go for something along the lines of:
data Arg = Arg with
exerciser: Party
choice: MyChoice
data MyChoice =
ChoiceA { key: (Party, Party), arg1: Int, arg2: String }
| ChoiceB { key: Party, arg: String }
exerciseFromJson : Arg -> Script ()
exerciseFromJson a = case a.choice of
ChoiceA { key = k, arg1 = a, arg2 = b } -> undefined
ChoiceB { key = k, arg = a } -> undefined
and the corresponding JSON would have to look something like
{
"exerciser": "Alice",
"choice": {
"tag": "ChoiceA",
"value": {
"key": {
"_1": "Some",
"_2": "Value"
},
"arg1": 12,
"arg2": "string"
}
}
}
What you can do is to write the logic of your script polymorphically and then instantiate it to concrete choices of template, choice and choice return type, e.g.,
data ArgsA tpl chc ret = ArgsA with
exerciser: Party
key: (Party, Party)
argument: chc
exerciseFromJson : forall tpl chc ret. (TemplateKey tpl (Party, Party), Choice tpl chc ret)=> ArgsA tpl chc ret -> Script ()
exerciseFromJson args = do
submit args.exerciser (exerciseByKeyCmd @tpl args.key args.argument)
pure ()
script1 : ArgsA T1 C1 R1 -> Script ()
script1 = exerciseFromJson
script2 : ArgsA T2 C2 R2 -> Script ()
script2 = exerciseFromJson
I understand the strictness of the type system and it’s one the reasons we like DAML.
I was not trying to convert strings to choices. I’m trying to rely on the JSON parsing capabilities of DAML Script.
The approach you suggested would definitely work. However, it would not be compatible with the. JSON API, meaning I could not send such a JSON as is the /v1/exercise
endpoint.
Ideally what I’m looking for is something like:
exerciseFromJson : ExerciseByKeyCommand t k c a => Script()
Note that the JSON format is ambiguous without knowing the corresponding type so there is no way we can magically choose which one you meant.
How is that ambiguous? There is an identifier, key, choice and args. See my idea of ExerciseByKeyCommand
.
It might not be ambiguous here but it can be ambiguous in general. E.g., ["abc"]
can be decoded to a record with a single field of type Text
or [Text]
. DAML Script accepts arbitrary LF values here so only the argument
part matches the JSON API.
So, it seems this is pretty much as far as I can go with this, right?
exerciser: Party
key: (Party, Party)
templateId: Text
choice: Text
argument: A
exerciseByKeyScript_A : ArgsA -> Script ()
exerciseByKeyScript_A args = exerciseByKeyScript args
data ArgsB = ArgsB with
exerciser: Party
key: (Party, Party)
templateId: Text
choice: Text
argument: B
exerciseByKeyScript_B : ArgsB -> Script ()
exerciseByKeyScript_B args = exerciseByKeyScript args
exerciseByKeyScript : (HasFromAnyChoice MyTemplate c a, HasToAnyChoice MyTemplate c a, HasExercise MyTemplate c a, DA.Internal.Record.HasField "argument" r c, DA.Internal.Record.HasField "choice" r Text, DA.Internal.Record.HasField "exerciser" r Party, DA.Internal.Record.HasField "key" r (Party, Party), DA.Internal.Record.HasField "templateId" r Text) => r -> Script ()
exerciseByKeyScript args = do
debug ("Exercising " <> args.choice <> " on " <> args.templateId <> "@" <> show args.key)
_ <- args.exerciser `submit` exerciseByKeyCmd @MyTemplate args.key args.argument
debug "Exercised successfully."
I’d recommend the approach I’ve described above instead where you factor out Args
and parametrize it. Makes it a bit less verbose and more importantly it scales to types that cannot easily be described by a couple of HasField
constraints.
Sorry, missed that. That looks nice. I will try it out later. Thanks.