Exercise choices dynamically with DAML Script

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.