Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.canton.network/llms.txt

Use this file to discover all available pages before exploring further.

This section was copied from existing reviewed documentation. Source: docs/replicated/daml/3.4/sdk/reference/smart-contract-upgrades.rst (section: Values in the Ledger API) Reviewers: Skip this section. Remove markers after final approval.
Commands and queries have relaxed validation rules for ingested values. Returned values are subject to normalization.

Value Validation in Commands

In the following examples, the target template of a command is the template or interface identified by the template_id field of the command after dynamic package resolution. A value featured in a command (e.g. create_arguments) has expected type T if the value needs to type-check against T in order to satisfy the type signatures of the target template of the command. Note that this definition necessarily extends to sub-values. In a record value of the form Constructor { field1 = v1, ..., fieldn = vn }, vi is a trailing None if for all n >= j >= i, vj = None. On submission of a command, the validation rules for values are relaxed as follows:
  • The record_id, variant_id, and enum_id fields of values, if present, are only checked against the module and type name of the expected type for that value. The package ID component of these fields is ignored.
  • In record values where all field names are provided, any fields of value None may be omitted.
  • In record values where not all field names are provided, fields must be provided in the same order as that of the record type definition, and trailing Nones may be omitted.
These rules apply for all sub-values. Example 1 Assume a package called example1-1.0.0 which defines a template called T in a module called Main.
module Main where

template T
  with
    p : Party
  where
    signatory p
Assume another package called other-1.0.0 which defines a different template also called T in a module also called Main.
module Main where

template T
  with
    s : Party
    i : Int
  where
    signatory s
Then the ledger API will accept Create commands for example1-1.0.0:Main.T whose create arguments are annotated with type other-1.0.0:T:Main.T, even though the type annotation is wrong. In other words, the following console commands succeed:
@ val createCmd = Command(
    command = Command.Command.Create(
      value = CreateCommand(
        templateId = Some(
          value = Identifier(packageId = packageIdExample1, moduleName = "Main", entityName = "T")),
        createArguments = Some(
          value = Record(
            recordId = Some(
              Identifier(
                packageId = packageIdOther,
                moduleName = "Main",
                entityName = "T"
              )
            ),
            fields = Seq(
              RecordField(
                label = "p",
                value = Some(
                  value = Value(sum = Value.Sum.Party(value = sandbox.adminParty.toLf))
                )
              )
            )
          )
        )
      )
    )
  )

@ sandbox.ledger_api.commands.submit(Seq(sandbox.adminParty), Seq(createCmd))
This is because the module and type names of the type annotation, Main and T, match those of the expected type: example1-1.0.0:Main.T. Example 2 Assume a package called example2-1.0.0 which defines a template with two optional fields: one in leading position, and one in trailing position.
module Main where

template T
  with
    i : Optional Int
    p : Party
    j : Optional Int
  where
    signatory p
Then submitting a Create command for example2-1.0.0:Main.T which only provides p by name and no other field succeeds:
@ val createCmd = Command(
    command = Command.Command.Create(
      value = CreateCommand(
        templateId = Some(value = Identifier(packageId = packageIdExample2, moduleName = "Main", entityName = "T")),
        createArguments = Some(
          value = Record(
            recordId = None,
            fields = Seq(
              RecordField(
                label = "p",
                value = Some(value = Value(sum = Value.Sum.Party(value = sandbox.adminParty.toLf)))
              )
            )
          )
        )
      )
    )
  ) 

@ sandbox.ledger_api.commands.submit(Seq(sandbox.adminParty), Seq(createCmd)) 
res13: com.daml.ledger.api.v1.transaction.TransactionTree = TransactionTree(
  ...
  eventsById = Map(
    "#122062d3d0b89f011ac651ea0139f381a73fe080ab215e5970a8c7bf804edeec932c:0" -> TreeEvent(
      kind = Created(
        value = CreatedEvent(
          ...
          createArguments = Some(
            value = Record(
              recordId = Some(value = Identifier(packageId = "627f4ad4df901b80bae208eded1c03932f38ed9c1f44c50468f27c88ef988e25", moduleName = "Main", entityName = "T")),
              fields = Vector(
                RecordField(label = "i", value = Some(value = Value(sum = Optional(value = Optional(value = None))))),
                RecordField(label = "p", value = Some(value = Value(sum = Party(value = "sandbox::1220077e3366037ffce33cba97d757506fc1c72ad957a9b86c6bf137404637c7fee3")))),
                RecordField(label = "j", value = Some(value = Value(sum = Optional(value = Optional(value = None)))))
              )
            )
          ),
          ...
        )
      )
    )
  ),
  ...
)
Submitting the same command but with no label for field p fails:
@ val createCmd = Command(
    command = Command.Command.Create(
      value = CreateCommand(
        templateId = Some(value = Identifier(packageId = packageIdExample2, moduleName = "Main", entityName = "T")),
        createArguments = Some(
          value = Record(
            recordId = None,
            fields = Seq(
              RecordField(
                label = "",
                value = Some(value = Value(sum = Value.Sum.Party(value = sandbox.adminParty.toLf)))
              )
            )
          )
        )
      )
    )
  ) 

@ sandbox.ledger_api.commands.submit(Seq(sandbox.adminParty), Seq(createCmd)) 

ERROR c.d.c.e.CommunityConsoleEnvironment - Request failed for sandbox.
  GrpcClientError: INVALID_ARGUMENT/COMMAND_PREPROCESSING_FAILED(8,b880be91): Missing non-optional field "p", cannot upgrade non-optional fields.
  Request: SubmitAndWaitTransactionTree(
  actAs = sandbox::1220077e3366...,
  readAs = Seq(),
  commandId = '',
  workflowId = '',
  submissionId = '',
  deduplicationPeriod = None(),
  applicationId = 'CantonConsole',
  commands = ...
)
...
However, providing all but the trailing optional field j suceeds, even without labels:
@ val createCmd = Command(
    command = Command.Command.Create(
      value = CreateCommand(
        templateId = Some(value = Identifier(packageId = packageIdExample2, moduleName = "Main", entityName = "T")),
        createArguments = Some(
          value = Record(
            recordId = None,
            fields = Seq(
              RecordField(label = "", value = Some(value = Value(sum = Value.Sum.Optional(value = Optional(value = None))))),
              RecordField(
                label = "",
                value = Some(value = Value(sum = Value.Sum.Party(value = sandbox.adminParty.toLf)))
              )
            )
          )
        )
      )
    )
  ) 

@ sandbox.ledger_api.commands.submit(Seq(sandbox.adminParty), Seq(createCmd)) 
res22: com.daml.ledger.api.v1.transaction.TransactionTree = TransactionTree(
  ...
  eventsById = Map(
    "#12203bb4082d4868c393ca2c969bb639757d21992cbac2a1abad271d688a30dbcae6:0" -> TreeEvent(
      kind = Created(
        value = CreatedEvent(
          ...
          createArguments = Some(
            value = Record(
              recordId = Some(value = Identifier(packageId = "627f4ad4df901b80bae208eded1c03932f38ed9c1f44c50468f27c88ef988e25", moduleName = "Main", entityName = "T")),
              fields = Vector(
                RecordField(label = "i", value = Some(value = Value(sum = Optional(value = Optional(value = None))))),
                RecordField(label = "p", value = Some(value = Value(sum = Party(value = "sandbox::1220077e3366037ffce33cba97d757506fc1c72ad957a9b86c6bf137404637c7fee3")))),
                RecordField(label = "j", value = Some(value = Value(sum = Optional(value = Optional(value = None)))))
              )
            )
          ),
          ...
        )
      )
    )
  ),
  ...
)

Value normalization in Ledger API responses

A Ledger API value (e.g. create_arguments in a CreatedEvent) is said to be in normal form if none of its sub-values (itself included) has trailing Nones. Starting with Daml 3.3.0, values in Ledger API non-verbose responses are subject to normalization. The normalization extends to all sub-values. Example Assume a package called example1-1.0.0 which defines a template T and a record Record in a module called Main.
module Main where

data Record = Record { ri : Optional Int, rj : Int, rk : Optional Int }
  deriving (Eq, Show)

template T
  with
    p : Party
    i : Optional Int
    r : Record
    j : Optional Int
  where
    signatory p
Also assume a ledger that contains a contract of type T written by example1-1.0.0 where all the optional fields are set to None.
val createCmd = ledger_api_utils.create(
  packageIdExample1, 
  "Main",
  "T", 
  Map(
    "p" -> sandbox.adminParty, 
    "i" -> None,
    "r" -> Map("ri" -> None, "rj" -> 1, "rk" -> None),
    "j" -> None))

sandbox.ledger_api.commands.submit(Seq(sandbox.adminParty), Seq(createCmd))
Then querying the ledger’s active contract set in non-verbose mode returns the following:
@ sandbox.ledger_api.acs.of_party(sandbox.adminParty, verbose=false) 
res15: Seq[com.digitalasset.canton.admin.api.client.commands.LedgerApiTypeWrappers.WrappedCreatedEvent] = List(
  WrappedCreatedEvent(
    event = CreatedEvent(
      ... 
      createArguments = Some(
        value = Record(
          recordId = None,
          fields = Vector(
            RecordField(
              label = "",
              value = Some(value = Value(sum = Party(value = "sandbox::122010fdef685011beecd318f03c9d82bf1e2d45950bdb0fceb3497a112ee17f9476")))
            ),
            RecordField(label = "", value = Some(value = Value(sum = Optional(value = Optional(value = None))))),
            RecordField(
              label = "",
              value = Some(
                value = Value(
                  sum = Record(
                    value = Record(
                      recordId = None,
                      fields = Vector(
                        RecordField(label = "", value = Some(value = Value(sum = Optional(value = Optional(value = None))))),
                        RecordField(label = "", value = Some(value = Value(sum = Int64(value = 1L))))
                      )
                    )
                  )
                )
              )
            )
          )
        )
      ),
      ... 
    )
  )
)
Note that not only has the third template argument (originally j) been omitted from the response, but also the third field of the nested record (originally rk). Note also that despite being optional fields of value None, the second template argument (originally i) and the first nested record field (originally ri) are present in the response because they are not in trailing positions.