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.
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, assumingv2/A.daml is a valid upgrade of v1/A.daml.
B.
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.
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.
x1 before p instead of adding it at the end of the parameter sequence.
x1.
x1 from Int to Text. Text is not a valid upgrade of Int.
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 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 parameterx2 at the end of the parameter sequence.
x2 before x1 instead of adding it at the end of the parameter sequence.
x2 before x1 instead of adding it at the end of the parameter sequence.
x1.
x1 from Int to Text. Text is not a valid upgrade of Int.
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 ().
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.
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.
A from record type to variant type.
A.
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.
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.
x2 before x1 instead of adding it at the end of the field sequence.
x2.
x1 from Int to Text. Text is not a valid upgrade of Int.
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.
B.
C before B instead of adding it at the end of the constructor sequence.
B.
B’s argument from Text to Bool. Bool is not a valid upgrade of Text.
B which didn’t have one before.
T as defined on the left is an enum because none of its constructors have arguments.
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: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.
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 q-1.0.0: | In q-2.0.0: |
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.
Data Types - Builtin Types
Builtin scalar types likeInt, 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 Types - Applied Parameterized Data Types
A type constructor applicationT' 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.
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.
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 interfaceI with view type IView and a method m.
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.
I for template T3.
I for template T2.
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 toTrue.
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
toInterfaceto a template value. At runtime, they are represented by a pair consisting of:
For instance, if- a record: the contract;
- a template type: the runtime type of that record.
cis a contract of typeTandTimplements the interfaceI, thentoInterface cevaluates 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.
iv = (c, T). Then fromInterface @U iv evaluates as follow.
Let us assume an interface value
- If
UupgradesT, then it evaluates toSome c'wherec'is the result of transformingcinto a value of typeU.- Otherwise, it evaluates to
None.
iv = (c, T) and an interface type I. Then create @I iv evaluates as follow.
Example 1 Assume two versions of a package called dep, defining a template U and its upgrade.
- If
Tdoes not implementIthen an error is thrown.- Otherwise
create @T cis evaluated.
In dep-1.0.0: | In dep-2.0.0: |
q which depends on version 1.0.0 of dep.
q defines a template S with a choice that fetches a contract of type U.
S written by q and a contract of type U written by dep-2.0.0.
| Contract ID | Type | Contract |
|---|---|---|
4321 | q:T | T { p = 'Alice' } |
8765 | dep-2.0.0:U | U { p = 'Bob', t = None } |
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.
T that implements I.
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.TupgradesV1.TsofromInterfaceproceeds 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 packagep with two versions. The new version adds an optional text field.
In p-1.0.0: | In p-2.0.0: |
T written by p-1.0.0, and another contract of written by p-2.0.0.
| Contract ID | Type | Contract |
|---|---|---|
1234 | p-1.0.0:T | T { p = 'Alice' } |
5678 | p-2.0.0:T | T { p = 'Bob', t = Some "Hello" } |
- Fetching contract
1234with package preferencep-1.0.0retrieves the contract and leaves it unchanged, returningT { p = 'Alice' }. - Fetching contract
1234with package preferencep-2.0.0retrieves the contract and successfully transforms it to the target template type, returningT { p = 'Alice', t = None }. - Fetching contract
5678with package preferencep-1.0.0retrieves the contract and fails to downgrade it to the target template type, returning an error. - Fetching contract
5678with package preferencep-2.0.0retrieves the contract and leaves it unchanged, returningT { p = 'Bob', t = Some "Hello" }.
I with a choice GetInt
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 inst-1.0.0: | In inst-2.0.0: |
client which defines a template whose choice Go exercises choice GetInt by interface.
Inst written by inst-1.0.0, and a contract of type Client written by client.
| Contract ID | Type | Contract |
|---|---|---|
0123 | inst-1.0.0:Inst | Inst { p = 'Alice' } |
0456 | client:Client | Client { p = 'Alice' } |
- When exercising choice
Goon contract0456with package preferenceinst-1.0.0, we trigger an exercise by interface of contract0123. Becauseinst-1.0.0is prefered, contract0123is upgraded to a value of typeinst-1.0.0::Instand itsgetIntmethod is executed. The result of the exercise is thus the value1. - When exercising choice
Goon contract0456but with package preferenceinst-2.0.0this time,inst-2.0.0:Instis picked as the target template for0123and thus the exercise returns the value2. Note that the fact that the exercise stored on the ledger is of typeinst-1.0.0:Instbears no incidence on thegetIntmethod that is eventually executed.
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 r-1.0.0: | In r-2.0.0: |
V written by r-1.0.0.
| Contract ID | Type | Contract |
|---|---|---|
9101 | r-1.0.0:V | V { p = 'Alice' } |
- Exercising
C with i=1on contract9101with package preferencer-2.0.0will execute the code ofCas defined inr-2.0.0. The parameter sequencei=1is thus transformed into the parameter sequencei=1, j=Noneto 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.0of packagerwould expect a value of typeRetand would thus transform the valueRet with j=Noneback toRet. - Exercising
C with i=1on contract9101with package preferencer-1.0.0will execute the code ofCas defined inr-1.0.0. The parameter sequence requires therefore no transformation. The exercise returns the valueRet. - Exercising
C with i=1 j=Some 2on contract9101with package preferencer-2.0.0will execute the code ofCas 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 2on contract9101with package preferencer-1.0.0will fail with a runtime error as the parameter sequencei=1 j=Some 2cannot be downgraded to the parameter sequence ofCas 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 asT-v1 and T-v2 in the following,
- A
T-v1valueT { x1 = v1, ..., xn = vn }is upgraded to aT-v2value by setting the additional fields to None and upgradingv1...vnrecursively. The transformation results in a valueT { x1 = v1', ..., xn = vn', y1 = None, ..., ym = None }, wherev1'... vn'is the result of upgradingv1...vntoT1' ... Tn'. - A
T-v2value of the shapeT { x1 = v1, ..., xn = vn, y1 = None, ..., ym = None }is downgraded to aT-v1value by dropping additional fields and downgradingv1...vnrecursively. The transformation results in a valueT { x1 = v1', ..., xn = vn' }wherev1'... vn'is the result of downgradingv1 ... vntoT1 ... Tn. - Attempting to downgrade a
T-v2value where at least oneyiis aSome _results in a runtime error.
Variants and Enums
Given a variant type and its upgrade, referred to respectively asV-v1 and V-v2 in the following,
- A
V-v1valueCi viis upgraded to aV-v2value by upgradingvirecursively. The transformation results in a valueCi vi'wherevi'is the result of upgradingvitoTi'. - A
V-v2valueCi viis downgraded to aV-v1value by downgradingvirecursively. The transformation results in a valueCi vi'wherevi'is the result of downgradingvitoTi. - Attempting to downgrade a
V-v2value of the formDj vjresults in a runtime error.
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);
- their signatories are equal;
- their stakeholders are equal;
In p-1.0.0: | In p-2.0.0: |
T written by p-1.0.0.
| Contract ID | Type | Contract |
|---|---|---|
1234 | p-1.0.0:T | T { sig = ['Alice'] } |
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 p-1.0.0: | In p-2.0.0: |
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 toFalse.
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.
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 interfaceI with view type IView and a method m.
In p-1.0.0: | In p-2.0.0: |
T written by p-1.0.0.
| Contract ID | Type | Contract |
|---|---|---|
1234 | p-1.0.0:T | T { p = 'Alice', i = 42 } |
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.