- Overview
- Tutorials
- Getting started
- Get started with Canton and the JSON Ledger API
- Get Started with Canton, the JSON Ledger API, and TypeScript
- Get started with Canton Network App Dev Quickstart
- Get started with smart contract development
- Basic contracts
- Test templates using Daml scripts
- Build the Daml Archive (.dar) file
- Data types
- Transform contracts using choices
- Add constraints to a contract
- Parties and authority
- Compose choices
- Handle exceptions
- Work with dependencies
- Functional programming 101
- The Daml standard library
- Test Daml contracts
- Next steps
- Application development
- Getting started
- Development how-tos
- Component how-tos
- Explanations
- References
- Application development
- Smart contract development
- Daml language cheat sheet
- Daml language reference
- Daml standard library
- DA.Action.State.Class
- DA.Action.State
- DA.Action
- DA.Assert
- DA.Bifunctor
- DA.Crypto.Text
- DA.Date
- DA.Either
- DA.Exception
- DA.Fail
- DA.Foldable
- DA.Functor
- DA.Internal.Interface.AnyView.Types
- DA.Internal.Interface.AnyView
- DA.List.BuiltinOrder
- DA.List.Total
- DA.List
- DA.Logic
- DA.Map
- DA.Math
- DA.Monoid
- DA.NonEmpty.Types
- DA.NonEmpty
- DA.Numeric
- DA.Optional
- DA.Record
- DA.Semigroup
- DA.Set
- DA.Stack
- DA.Text
- DA.TextMap
- DA.Time
- DA.Traversable
- DA.Tuple
- DA.Validation
- GHC.Show.Text
- GHC.Tuple.Check
- Prelude
- Smart contract upgrading reference
- Glossary of concepts
Note
This page is a work in progress. It may contain incomplete or incorrect information.
Smart contract upgrading reference¶
This document describes in detail what rules govern package validation upon upload and how contracts, choice arguments and choice results are upgraded or downgraded at runtime. These topics are for a large part covered in Smart Contract Upgrade. This document acts as a thorough reference.
Static Checks¶
Upgrade static checks are performed once alongside other validity checks when a DAR is uploaded to a participant. DARs deemed invalid for upgrades are rejected.
DAR upgrade checks are broken down into package-level checks, which are in turn broken down into module, template and data type-level checks.
Packages¶
Definition: A utility package is a package with no template definition, no interface definition, no exception definition, and only non-serializable data type definitions. A utility package typically consists of helper functions and constants, but no type definitions.
A DAR is checked against previously uploaded DARs for upgrade validity on upload to a participant. Specifically, for every package with name p and version v present in the uploaded DAR:
The participant looks up versions v_prev and v_next of p in its package database, such that v_prev is the greatest version of p smaller than v, and v_next is the smallest version of p greater than v. Note that they may not always exist.
The participant checks that version v of p is a valid upgrade of version v_prev of p, if it exists.
The participant checks that version v_next of p is a valid upgrade of version v of p, if it exists.
Therefore “being a valid upgrade” for a DAR is context dependent: it depends on what packages are already uploaded on the participant. It also modular: checks are performed at the package level. That is, a new version of a package is rejected as soon as it contains some element which doesn’t properly upgrade its counterpart in the old package, even if some other elements do. Similarly, all packages in a DAR must be pass the check for the DAR to be accepted. If one of the packages fails the check, the entire DAR is rejected.
Modules¶
The modules of the new version of a package must form a superset of the modules of the prior version of that package. In other words, it is valid to add new modules but deleting a module leads to a validation error.
Examples
In the file tree below, package v2 is a potentially valid upgrade of
package v1, assuming v2/A.daml
is a valid upgrade of v1/A.daml
.
├── v1
│ ├── daml
│ │ └── A.daml
│ └── daml.yaml
└── v2
├── daml
│ ├── A.daml
│ └── B.daml
└── daml.yaml
In the file tree below, package v2 cannot possibly be a valid upgrade of
v1 because it doesn’t define module B
.
├── v1
│ ├── daml
│ │ ├── A.daml
│ │ └── B.daml
│ └── daml.yaml
└── v2
├── daml
│ └── A.daml
└── daml.yaml
Templates¶
The templates of the new version of a package must form a superset of the templates of the prior version of that package. In other words, it is valid to add new templates but deleting a template leads to a validation error.
Examples
Below, the module on the right is a valid upgrade of the module on the
left. But the module on the left is not a valid upgrade of the
module on the right because it lacks a definition for template T2
.
module M where
template T1
with
p : Party
where
signatory p
|
module M where
template T1
with
p : Party
where
signatory p
template T2
with
p : Party
where
signatory p
|
Template Parameters¶
The new version of a template may add new optional parameters at the end of the parameter sequence of the prior version of the template. The types of the parameters that the new template has in common with the prior template must be pairwise valid upgrades of the original types.
Deleting a parameter leads to a validation error.
Adding a parameter in the middle of the parameter sequence leads to a validation error.
As a special case of the two points above, renaming a parameter leads to a validation error.
Adding a non-optional parameter at the end of the parameter leads to a validation error.
Examples
Below, the template on the right is a valid upgrade of the template on
the left. It adds an optional parameter x1
at the end of the parameter
sequence.
template T
with
p : Party
where
signatory p
|
template T
with
p : Party
x1 : Optional Int
where
signatory p
|
Below, the template on the right is not a valid upgrade of the
template on the left because it adds a new parameter x1
before p
instead
of adding it at the end of the parameter sequence.
template T
with
p : Party
where
signatory p
|
template T
with
x1 : Optional Int
p : Party
where
signatory p
|
Below, the template on the right is not a valid upgrade of the
template on the left because it drops parameter x1
.
template T
with
p : Party
x1 : Int
where
signatory p
|
template T
with
p : Party
where
signatory p
|
Below, the template on the right is not a valid upgrade of the
template on the left because it changes the type of x1
from Int
to Text
.
Text
is not a valid upgrade of Int
.
template T
with
p : Party
x1 : Int
where
signatory p
|
template T
with
p : Party
x1 : Text
where
signatory p
|
Template Choices¶
The choices of the new version of a template must form a superset of the choices of the prior version of the template template. In other words, it is valid to add new choices but deleting a choice leads to a validation error.
Examples
Below, the template on the right is a valid upgrade of the template on
the left. It adds a choice C
to the previous version of the template.
But the template on the left is not a valid upgrade of the template
on the right as it deletes a choice.
template T
with
p : Party
where
signatory p
|
template T
with
p : Party
where
signatory p
choice C : ()
controller p
do
return ()
|
Template Choices - Parameters¶
As with template parameters, the new version of a choice may add new optional parameters at the end of the parameter sequence of the prior version of that choice. The types of the parameters that the new choice has in common with the prior choice must be pairwise valid upgrades of the original types.
Deleting a parameter leads to a validation error.
Adding a parameter in the middle of the parameter sequence leads to a validation error.
As a special case of the two points above, renaming a parameter leads to a validation error.
Adding a non-optional parameter at the end of the parameter sequence leads to a validation error.
Example
Below, the choice on the right is a valid upgrade of the choice on the
left. It adds an optional parameter x2
at the end of the parameter
sequence.
choice C : ()
with
x1 : Int
controller p
do
return ()
|
choice C : ()
with
x1 : Int
x2 : Optional Text
controller p
do
return ()
|
Below, the choice on the right is not a valid upgrade of the choice
on the left because it adds a new parameter x2
before x1
instead of
adding it at the end of the parameter sequence.
choice C : ()
with
x1 : Int
controller p
do
return ()
|
choice C : ()
with
x2 : Optional Text
x1 : Int
controller p
do
return ()
|
Below, the choice on the right is not a valid upgrade of the choice
on the left because it adds a new field x2
before x1
instead of adding
it at the end of the parameter sequence.
choice C : ()
with
x1 : Int
controller p
do
return ()
|
choice C : ()
with
x2 : Optional Text
x1 : Int
controller p
do
return ()
|
Below, the choice on the right is not a valid upgrade of the choice
on the left because it drops parameter x1
.
choice C : ()
with
x1 : Int
controller p
do
return ()
|
choice C : ()
with
controller p
do
return ()
|
Below, the choice on the right is not a valid upgrade of the choice
on the left because it changes the type of x1
from Int
to Text
. Text
is
not a valid upgrade of Int
.
choice C : ()
with
x1 : Int
controller p
do
return ()
|
choice C : ()
with
controller p
do
return ()
|
Template Choices - Return Type¶
The return type of the new version of a choice must be a valid upgrade of the return type of the prior version of that choice.
Changing the return type of a choice for a non-valid upgrade leads to a validation error.
Examples
Below, the choice on the right is not a valid upgrade of the choice
on the left because it changes its return type from ()
to Int
. Int
is
not a valid upgrade of ()
.
choice C : ()
controller p
do
return ()
|
choice C : Int
controller p
do
return 1
|
Data Types¶
The serializable data types of the new version of a module must form a superset of the serializable data types of the prior version of that package. In other words, it is valid to add new data types but deleting a data type leads to a validation error.
Changing the variety of a serializable data type leads to a validation error. For instance, one cannot change a record type into a variant type.
Non-serializable data types are inexistent from the point of view of the upgrade validity check. Turning a non-serializable data type into a serializable one amounts to adding a new data type, which is valid. Turning a serializable data type into a non-serializable one amounts to deleting this data type, which is invalid.
Examples
Below, the module on the right is a valid upgrade of the module on the
left. It defines an additional serializable data type B
.
module M where
data A = A
|
module M where
data A = A
data B = B
|
Below, the module on the right is a valid upgrade of the module on the
left. It turns the non-serializable type A
into a serializable one. The
non-serializable type is invisible to the upgrade validity check so this
amounts to adding a new data type to the module on the right.
module M where
data A = A
with
x : Int -> Int
|
module M where
data A = A
with
|
Below, the module on the right is not a valid upgrade of the module
on the left because it changes the variety of A
from record type to
variant type.
module M where
data A = A
with
|
module M where
data A = A | B
|
Below, the module on the right is not a valid upgrade of the module
on the left because it drops the serializable data type A
.
module M where
data A = A
|
module M where
|
Below, the module on the right is not a valid upgrade of the module
on the left because although it adds an optional field to the record
type A
, it also turns A
into a non-serializable type, which amounts to
deleting A
from the point of view of the upgrade validity check.
module M where
data A = A
with
|
module M where
data A = A
with
x : Optional (Int -> Int)
|
Data Types - Records¶
The new version of a record may add new optional fields at the end of the field sequence of the prior version of that record. The types of the fields that the new record has in common with the prior record must be pairwise valid upgrades of the original types.
Deleting a field leads to a validation error.
Adding a field in the middle of the field sequence leads to a validation error.
As a special case of the two points above, renaming a field leads to a validation error.
Adding a non-optional field at the end of the field sequence leads to a validation error.
Examples
Below, the record on the right is a valid upgrade of the module on the
left. It adds an optional field x2
at the end of the field sequence.
data T = T with
x1 : Int
|
data T = T with
x1 : Int
x2 : Optional Text
|
Below, the record on the right is not a valid upgrade of the record
on the left because it adds a new field x2
before x1
instead of adding
it at the end of the field sequence.
data T = T with
x1 : Int
|
data T = T with
x2 : Optional Text
x1 : Int
|
Below, the record on the right is not a valid upgrade of the record
on the left because it drops field x2
.
data T = T with
x1 : Int
x2 : Text
|
data T = T with
x1 : Int
|
Below, the record on the right is not a valid upgrade of the record
on the left because it changes the type of x1
from Int
to Text
.
Text
is not a valid upgrade of Int
.
data T = T with
x1 : Int
|
data T = T with
x1 : Text
|
Data Types - Variants¶
The new version of a variant may add new constructors at the end of the constructor sequence of the old version of that variant. The argument types of the constructors that the new variant has in common with the prior variant must be pairwise valid upgrades of the original types. This last rule also applies to constructors whose arguments are unnamed records, in which case the rules about record upgrade apply.
Adding an argument to a constructor without arguments leads to a validation error. In particular, adding an optional field to a constructor that previously had no arguments is not allowed.
Adding a constructor in the middle of the constructor sequence leads to a validation error.
Changing the order or the name of the constructor sequence leads to a validation error.
Removing a constructor leads to a validation error.
Enums cannot get upgraded to variants: adding a constructor with an argument at the end of the constructor sequence of an enum leads to a validation error.
Examples
Below, the variant on the right is a valid upgrade of the variant on the
left. It adds a new constructor C
at the end of the constructor
sequence.
data T =
A Int | B Text
|
data T =
A Int | B Text | C Bool
|
Below, the variant on the right is a valid upgrade of the variant on the
left. It adds a new optional field to constructor B
.
data T =
A | B { x : Int }
|
data T =
A | B { x : Int, y : Optional Text }
|
Below, the variant on the right is not a valid upgrade of the
variant on the left because it adds a new constructor C
before B
instead
of adding it at the end of the constructor sequence.
data T =
A Int | B Text
|
data T =
A Int | C Bool | B Text
|
Below, the variant on the right is not a valid upgrade of the variant on the left because it changes the order of its constructors.
data T =
A Int | B Text
|
data T =
B Text | A Int
|
Below, the variant on the right is not a valid upgrade of the
variant on the left because it drops constructor B
.
data T =
A Int | B Text
|
data T =
A Int
|
Below, the variant on the right is not a valid upgrade of the
variant on the left because it changes the type of B
’s argument from
Text
to Bool
. Bool
is not a valid upgrade of Text
.
data T =
A Int | B Text
|
data T =
A Int | B Bool
|
Below, the variant on the right is not a valid upgrade of the
variant on the left because it adds an argument to constructor B
which
didn’t have one before.
data T =
A Int | B
|
data T =
A Int | B { x : Optional Text }
|
Below, the variant on the right is not a valid upgrade of the
enum on the left. Enums cannot get upgraded to variants and T
as defined
on the left is an enum because none of its constructors have arguments.
data T =
A | B
|
data T =
A | B | C Int
|
Data Types - Enums¶
For the purpose of upgrade validation, enums can be treated as a special case of variants. The rules of the section on variants apply, only without constructor arguments.
Data Types - Type References¶
A type reference is an identifier that resolves to a type. For instance, consider the following module definitions, from two different packages:
-- In package q
module Dep where
data U = U with x : Int
type A = U
-- In package p
module M where
import qualified Dep
data T = T with x : Dep.A
In the definition of T
, Dep.A
is a type reference that resolves to the
type with qualified name Dep.U
in package q
.
A reference r2 to a data type upgrades a reference r1 to a data type if and only if:
r2 resolves to a type t2 with qualified name q2 in package p2;
r1 resolves to a type t1 with qualified name q1 in package p1;
The qualified names q2 and q1 are the same;
Package p2 is a valid upgrade of package p1.
It is worth noting that even when t2 upgrades t1, r2 only upgrades r1 provided that package p2 is a valid upgrade of package p1 as a whole.
Examples
In these examples we assume the existence of packages q-1.0.0
and
q-2.0.0
and that the latter is a valid upgrade of the former.
In |
In |
module Dep where
data U = C1
data V = V
|
module Dep where
data U = C1 | C2
data V = V
|
Then below, the module on the right is a valid upgrade of the module on the left.
module Main where
-- imported from q-1.0.0
import qualified Dep
data T = T Dep.U
|
module Main where
-- imported from q-2.0.0
import qualified Dep
data T = T Dep.U
|
However below, the module on the right is not a valid upgrade of the
module on the left because Dep.V
on the right belongs to package q-1.0.0
which is not a valid upgrade of package p-2.0.0
, even though the two
definitions of V
are the same.
module Main where
-- imported from q-2.0.0
import qualified Dep
data T = T Dep.V
|
module Main where
-- imported from q-1.0.0
import qualified Dep
data T = T Dep.V
|
Data Types - Builtin Types¶
Builtin scalar types like Int
, Text
, Party
, etc. only upgrade
themselves. In other words, it is never valid to replace them with another
type.
Data Types - Parameterized Data Types¶
The upgrade validation for parameterized data types follows the same rules as non-parameterized data types, but also compares type variables. Type variables may be renamed.
Example
Below, the parameterized data type on the right is a valid upgrade of the parameterized data type on the left. As is valid with any record type, it adds an optional field.
data Tree a =
Tree with
label : a
children : [Tree a]
|
data Tree b =
Tree with
label : b
children : [Tree b]
cachedSize : Optional Int
|
Data Types - Applied Parameterized Data Types¶
A type constructor application T' U1' .. Un'
upgrades
T U1 .. Un
if and only if T'
upgrades T
and
each Ui'
upgrades the corresponding Ui
.
Examples
Below, the module on the right is a valid upgrade of the module on the left.
The record type T
on the right upgrades the record type T
on the left.
As a result, the type constructor application List T
on the right upgrades
the type constructor application List T
on the left. Same goes for List
and Optional
.
module M where
data T = T {}
data Demo = Demo with
field1 : List T
field2 : Map T T
field3 : Optional T
|
module M where
data T = T { i : Optional Int }
data Demo = Demo with
field1 : List T
field2 : Map T T
field3 : Optional T
|
Below, the module on the right is a valid upgrade of the module on the left.
The parameterized type C
on the right upgrades the parameterized type C
on the left.
As a result, the type constructor application C Int
on the right upgrades
the type constructor application C Int
on the left.
module M where
data C a = C { x : a }
data Demo = Demo with
field1 : C T
|
module M where
data C a = C { x : a, y : Optional Int }
data Demo = Demo with
field1 : C T
|
Interface and Exception Definitions¶
Neither interface definitions nor exception definitions can be upgraded. We strongly discourage uploading a package that defines interfaces or exceptions alongside templates, as these templates cannot benefit from smart contract upgrade in the future. Instead, we recommend declaring interfaces and exceptions in a package of their own that defines no template.
Interface Instances¶
Interface instances may be upgraded. Note however that the type signature of their methods and view cannot change between two versions of an instance since they are fixed by the interface definition, which is non-upgradable. Hence, the only thing that can change between two versions of an instance is the bodies of its methods and view.
Interface instances may be added to a template.
Deleting an interface instance from a template leads to a validation error.
Examples
Assume an interface I
with view type IView
and a method m
.
data IView = IView { i : Int }
interface I where
viewtype IView
Then, below, the instance of I
for template T
on the right is a valid
upgrade of the instance on the left. It changes the view
expression and the
body of method m
.
template T
with
p : Party
i : Int
where
signatory p
interface instance I for T where
view = IView i
m = i
|
template T
with
p : Party
i : Int
j : Optional Int
where
signatory p
interface instance I for T where
view = IView (fromOptional i j)
m = fromOptional i j
|
Below, the template on the right is a valid upgrade of the
template on the left. It adds a new instance of I
for template T3
.
template T3
with
p : Party
i : Int
where
signatory p
|
template T3
with
p : Party
i : Int
where
signatory p
interface instance I for T3 where
view = IView i
m = i
|
Below, the template on the right is not a valid upgrade of the
template on the left because it removes the instance of I
for template
T2
.
template T2
with
p : Party
i : Int
where
signatory p
interface instance I for T2 where
view = IView i
m = i
|
template T2
with
p : Party
i : Int
where
signatory p
|
Data Transformation: Runtime Semantics¶
A template version is selected whenever a contract is fetched, a choice is exercised, or an interface value is converted to a template value, according to a set of rules detailed below. We call this template the target template.
The contract is then transformed into a value that fits the type of the target
template. Then, its metadata (signatories, stakeholders) is recomputed using the
code of the target template and compared against the existing metadata stored on
the ledger: it is not allowed to change. The ensure clause of the contract is
also re-evaluated: it must evaluate to True
.
In addition, when a choice is exercised, its arguments are transformed into values that fit the type signature of the choice in the target template. The result of the exercise is then possibly transformed back to some other target type by the client (e.g. the generated java client code).
Below, we detail the rules governing target template selection, then explain how transformations are performed, and finally detail the rules of metadata re-computation.
Static Target Template Selection¶
In a non-interface fetch or exercise triggered by the body of a choice, the target template is determined by the dependencies of the package that defines the choice. In other words, it is statically known.
Interface fetches and exercises, on the other hand, are subject to dynamic target template selection, as detailed in the next section. However, operations acting on interface values — as opposed to IDs — are static. Their mode of operation is detailed below.
Daml contracts are represented by one of two sorts of values at runtime: template values or interface values.
Template values are those whose concrete template type is statically known. They are obtained by directly constructing a template record, or by a call to
fetch
. Their runtime representation is a simple record.Interface values are those whose concrete template type is not fully statically known, aside from the fact that it implements a given interface. They are obtained by applying
toInterface
to a template value. At runtime, they are represented by a pair consisting of:a record: the contract;
a template type: the runtime type of that record.
For instance, if
c
is a contract of typeT
andT
implements the interfaceI
, thentoInterface c
evaluates to the pair(c, T)
.Note that the type of interface values is opaque: while it is useful to conceptualize interface values as pairs for defining the runtime semantics of the language, their actual implementation may vary and is not exposed to the user.
Let us assume an interface value iv
= (c, T)
. Then
fromInterface @U iv
evaluates as follow.
If
U
upgradesT
, then it evaluates toSome c'
wherec'
is the result of transformingc
into a value of typeU
.Otherwise, it evaluates to
None
.
Let us assume an interface value iv
= (c, T)
and an interface type
I
. Then create @I iv
evaluates as follow.
If
T
does not implementI
then an error is thrown.Otherwise
create @T c
is evaluated.
Example 1
Assume two versions of a package called dep, defining a template U and its upgrade.
In |
In |
module Dep where
template U
with
p : Party
where
signatory p
|
module Dep where
template U
with
p : Party
t : Optional Text
where
signatory p
|
Assume then some package q
which depends on version 1.0.0
of dep
.
[...]
name: q
version: 1.0.0
data-dependencies:
- dep-1.0.0.dar
Package q
defines a template S
with a choice that fetches a contract of
type U
.
import qualified Dep
template S
with
p : Party
where
signatory p
choice GetU : Dep.U
with
cid : ContractId Dep.U
where
controller p
do fetch cid
Finally assume a ledger that contains a contract of type S
written by q
and a contract of type U
written by dep-2.0.0
.
Contract ID |
Type |
Contract |
---|---|---|
|
|
|
|
|
|
When exercising choice GetU 8765
on contract 4321
with package
preference dep-2.0.0
, we trigger a fetch of contract 5678
. Because
package q
depends on version 1.0.0
of dep
, the target type for U
is the one defined in package dep-1.0.0
. Contract 5678
is thus
downgraded to U { p = 'Bob'}
upon retrieval. Note that the command
preference for version 2.0.0
of package dep
bears no incidence here.
Example 2
Assume an interface I
with view type IView
and a method m
.
data IView = IView {}
interface I where
viewtype IView
Assume then two versions of a template T
that implements I
.
template T
with
p : Party
where
signatory p
interface instance I for T where
view = IView {}
|
template T
with
p : Party
i : Optional Int
where
signatory p
interface instance I for T where
view = IView {}
|
Finally, assume that the module defining the first version of T
is imported
as V1
, and the module defining the second version of T
is imported as
V2
. The expression fromInterface @V2.T (toInterface @I (V1.T 'Alice'))
evaluates as follows:
toInterface @I (@V1.T alice)
evaluates to the interface value(V1.T { p = 'Alice' }, V1.T)
.The type
V2.T
upgradesV1.T
sofromInterface
proceeds to transform(V1.T { p = 'Alice' })
into a value of typeV2.T
The entire expression thus evaluates to
V2.T { p = 'Alice', i = None }
.
Dynamic Target Template Selection¶
In a top-level exercise triggered by a Ledger API command, or in an interface fetch or exercise triggered from the body of a choice, the rules of package preference detailed in dynamic package resolution determine the target template at runtime.
Example 1
Assume a package p
with two versions. The new version adds an optional text
field.
In |
In |
template T
with
p : Party
where
signatory p
|
template T
with
p : Party
t : Optional Text
where
signatory p
|
Also assume a ledger that contains a contract of type T
written by
p-1.0.0
, and another contract of written by p-2.0.0
.
Contract ID |
Type |
Contract |
---|---|---|
|
|
|
|
|
|
Then
Fetching contract
1234
with package preferencep-1.0.0
retrieves the contract and leaves it unchanged, returningT { p = 'Alice' }
.Fetching contract
1234
with package preferencep-2.0.0
retrieves the contract and successfully transforms it to the target template type, returningT { p = 'Alice', t = None }
.Fetching contract
5678
with package preferencep-1.0.0
retrieves the contract and fails to downgrade it to the target template type, returning an error.Fetching contract
5678
with package preferencep-2.0.0
retrieves the contract and leaves it unchanged, returningT { p = 'Bob', t = Some "Hello" }
.
Example 2
Assume an interface I
with a choice GetInt
data IView = IView {}
interface I where
viewtype IView
getInt : Int
choice GetInt : Int
with
p : Party
controller p
do
pure (getInt this)
Now, assume two versions of a package called inst
, defining a template
Inst
and its upgrade. The two versions of the template instantiate
interface I
, but their getInt
method return different values.
In |
In |
template Inst
with
p : Party
where
signatory p
interface instance I for T where
view = IView
getInt = 1
|
template Inst
with
p : Party
where
signatory p
interface instance I for T where
view = IView
getInt = 2
|
Assume then some package client
which defines a template whose choice Go
exercises choice GetInt
by interface.
template Client
with
p : Party
icid : ContractId I
where
signatory p
choice Go : Int
controller p
do
exercise icid (GetInt p)
Finally assume a ledger that contains a contract of type Inst
written by
inst-1.0.0
, and a contract of type Client
written by client
.
Contract ID |
Type |
Contract |
---|---|---|
|
|
|
|
|
|
Then:
When exercising choice
Go
on contract0456
with package preferenceinst-1.0.0
, we trigger an exercise by interface of contract0123
. Becauseinst-1.0.0
is prefered, contract0123
is upgraded to a value of typeinst-1.0.0::Inst
and itsgetInt
method is executed. The result of the exercise is thus the value1
.When exercising choice
Go
on contract0456
but with package preferenceinst-2.0.0
this time,inst-2.0.0:Inst
is picked as the target template for0123
and thus the exercise returns the value2
. Note that the fact that the exercise stored on the ledger is of typeinst-1.0.0:Inst
bears no incidence on thegetInt
method that is eventually executed.
Example 3
Assume now a package r
with two versions. They define a template with a
choice, and version 2.0.0
adds an optional field to the parameters of the
choice. The return type of the choice is also upgraded.
In |
In |
module M where
data Ret = Ret with
template V
with
p : Party
where
signatory p
choice C : Ret
with
i : Int
controller p
do return Ret
|
module M where
data Ret = Ret with
j : Optional Int
template V
with
p : Party
where
signatory p
choice C : Ret
with
i : Int
j : Optional Int
controller p
do return Ret with j = j
|
Also assume a ledger that contains a contract of type V
written by
r-1.0.0
.
Contract ID |
Type |
Contract |
---|---|---|
|
|
|
Then:
Exercising
C with i=1
on contract9101
with package preferencer-2.0.0
will execute the code ofC
as defined inr-2.0.0
. The parameter sequencei=1
is thus transformed into the parameter sequencei=1, j=None
to match its parameter types. The exercise then returns the valueRet with j=None
. It is up to the client code (e.g. the caller of the ledger API) to transform this to a value that fits the return type it expects. For instance, a client which only knows about version1.0.0
of packager
would expect a value of typeRet
and would thus transform the valueRet with j=None
back toRet
.Exercising
C with i=1
on contract9101
with package preferencer-1.0.0
will execute the code ofC
as defined inr-1.0.0
. The parameter sequence requires therefore no transformation. The exercise returns the valueRet
.Exercising
C with i=1 j=Some 2
on contract9101
with package preferencer-2.0.0
will execute the code ofC
as defined inr-2.0.0
. Again, the parameter sequence no transformation. The exercise returns the valueRet with j=Some 2
.Exercising
C with i=1 j=Some 2
on contract9101
with package preferencer-1.0.0
will fail with a runtime error as the parameter sequencei=1 j=Some 2
cannot be downgraded to the parameter sequence ofC
as defined inr-1.0.0
.
Transformation Rules¶
Once the target type has been determined, the data transformation rules themselves follow the upgrading rules of protocol buffers.
Records and Parameters¶
Given a record type and its upgrade, referred to respectively as T-v1
and T-v2
in the following,
data T = T with
x1 : T1
...
xn : Tn
|
data T = T with
x1 : T1'
...
xn : Tn'
y1 : Optional U1
...
ym : Optional Um
|
A
T-v1
valueT { x1 = v1, ..., xn = vn }
is upgraded to aT-v2
value by setting the additional fields to None and upgradingv1...vn
recursively. The transformation results in a valueT { x1 = v1', ..., xn = vn', y1 = None, ..., ym = None }
, wherev1'... vn'
is the result of upgradingv1...vn
toT1' ... Tn'
.A
T-v2
value of the shapeT { x1 = v1, ..., xn = vn, y1 = None, ..., ym = None }
is downgraded to aT-v1
value by dropping additional fields and downgradingv1...vn
recursively. The transformation results in a valueT { x1 = v1', ..., xn = vn' }
wherev1'... vn'
is the result of downgradingv1 ... vn
toT1 ... Tn
.Attempting to downgrade a
T-v2
value where at least oneyi
is aSome _
results in a runtime error.
The same transformation rules apply to template parameters and choice parameters.
Variants and Enums¶
Given a variant type and its upgrade, referred to respectively as V-v1
and V-v2
in the following,
data V =
= C1 T1
| ...
| Cn Tn
|
data V =
= C1 T1'
| ...
| Cn Tn'
| D1 U1
| ...
| Dm Um
|
A
V-v1
valueCi vi
is upgraded to aV-v2
value by upgradingvi
recursively. The transformation results in a valueCi vi'
wherevi'
is the result of upgradingvi
toTi'
.A
V-v2
valueCi vi
is downgraded to aV-v1
value by downgradingvi
recursively. The transformation results in a valueCi vi'
wherevi'
is the result of downgradingvi
toTi
.Attempting to downgrade a
V-v2
value of the formDj vj
results in a runtime error.
The same transformation rules apply to enum types, constructor arguments aside.
Other Types¶
Types that aren’t records or variants are “pass-through” for the upgrade and downgrade transformations:
Values of scalar types are trivially transformed to themselves.
The payload of an Optional is recursively transformed.
The elements of Lists are recursively transformed.
The keys and values of Maps are recursively transformed.
Metadata¶
For a given contract, metadata is every information outside of the contract parameters that is stored on the ledger for this contract. Namely:
The contract signatories;
The contract stakeholders (the union of signatories and observers);
The metadata of two contracts are equivalent if and only if:
their signatories are equal;
their stakeholders are equal;
Upon retrieval and after conversion, the metadata of a contract is recomputed using the code of the target template. It is a runtime error if the recomputed metadata is not equivalent to that of the original contract.
Note: A given implementation may choose to perform the equivalence check differently from what is described above, as long as the result is semantically equivalent.
Example
Below the template on the right is a valid upgrade of the template on the left.
In |
In |
template T
with
sig : Party
where
signatory sig
|
template T
with
sig : Party
additionalSig : Optional Party
where
signatory sig, fromOptional [] additionalSig
|
Assume a ledger that contains a contract of type T
written by
p-1.0.0
.
Contract ID |
Type |
Contract |
---|---|---|
|
|
|
Fetching contract 1234
with target type p-2.0.0:T
retrieves the
contract and successfully transforms it into a value of type p-2.0.0:T
: T
{ sig = 'Alice', additionalSig = None }
. The signatories of this transformed
contract are then computed using the expression sig, fromOptional []
additionalSig
, which evaluate to the list ['Alice']
. This list is then
compared to signatories of the original contract stored on the ledger:
['Alice']
. They match and thus the upgrade is valid.
On the other hand, below, the template on the right is not a valid upgrade of the template on the left.
In |
In |
template T
with
sig : Party
where
signatory sig
|
template T
with
sig : Party
where
signatory sig, sig
|
Assume the same ledger as above. Fetching contract 1234
with target type
p-2.0.0:T
retrieves the contract and again successfully
transforms it into the value T { sig = 'Alice', additionalSig = None }
. The
signatories of this transformed contract are then computed using the expression
sig, sig
, which evaluate to the list ['Alice', 'Alice']
. This list is
then compared to signatories of the original contract stored on the ledger:
['Alice']
. They do not match and thus the upgrade is rejected at runtime.
Ensure Clause¶
Upon retrieval and after conversion, the ensure clause of a contract is
recomputed using the code of the target template. It is a runtime error if the
recomputed ensure clause evaluates to False
.
Examples
Below, the template on the right is not a valid upgrade of the template on
the left because its ensure clause will evaluate to False
for contracts that
have been written using the template on the left with n = 0
.
template T
with
sig : Party
n : Int
where
signatory sig
ensure n >= 0
|
template T
with
sig : Party
n : Int
where
signatory sig
ensure n > 0
|
Interface Views¶
The view for a given interface instance may change between two versions of a contract. When a contract is fetched or exercised by interface, its view is recopmuted according to the code of the target template.
Example
Assume an interface I
with view type IView
and a method m
.
data IView = IView { i : Int }
interface I where
viewtype IView
m : Int
Below, the template on the right is a valid upgrade of the template on the left.
In |
In |
template T
with
p : Party
i : Int
where
signatory p
interface instance I for T where
view = IView i
m = i
|
template T
with
p : Party
i : Int
where
signatory p
interface instance I for T where
view = IView (i+1)
m = i
|
Assume a ledger that contains a contract of type T
written by
p-1.0.0
.
Contract ID |
Type |
Contract |
---|---|---|
|
|
|
Fetching contract 1234
by interface with package preference p-2.0.0
retrieves the contract and transforms it into a value of type p-2.0.0:T
:
T { sig = 'Alice', i = 42, j = None }
. Then its view is computed time
according to p-2.0.0
: IView 43
.
Values in the Ledger API¶
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
, andenum_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.