The Smart Contract Upgrade Model in Depth¶
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.
In the following validity check, packages whose LF version does not support upgrades (1.15 and earlier) and utility packages are ignored.
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 Keys¶
The key of the new version of a template must be a valid upgrade of the prior version’s key.
Adding or removing a key leads to a validation error.
Changing the key type to one that is not a valid upgrade of the original type leads to a validation error.
Examples
Below, the template on the right is a valid upgrade of the template on the left because the key type of the template on the right is a valid upgrade of the key type of the template on the left:
data MyKey = MyKey
with
p : Party
template T
with
p : Party
where
signatory p
key MyKey p : MyKey
maintainer key.p
|
data MyKey = MyKey
with
p : Party
i : Optional Int
template T
with
p : Party
where
signatory p
key MyKey p None : MyKey
maintainer key.p
|
Below, the template on the right is not a valid upgrade of the template on the left because it adds a key.
template T
with
p : Party
k : Text
where
signatory p
|
template T
with
p : Party
k : Text
where
signatory p
key (p, k): (Party, Text)
maintainer (fst key)
|
Below, the template on the right is not a valid upgrade of the template on the left because it deletes its key.
template T
with
p : Party
k : Text
where
signatory p
key (p, k): (Party, Text)
maintainer (fst key)
|
template T
with
p : Party
k : Text
where
signatory p
|
Below, the template on the right is not a valid upgrade of the
template on the left because it changes the key type for a type that
is not a valid upgrade of (Party, Text)
.
template T
with
p : Party
k : Text
where
signatory p
key (p, k): (Party, Text)
maintainer (fst key)
|
template T
with
p : Party
k : Text
where
signatory p
key (p, 2): (Party, Int)
maintainer (fst key)
|
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;
- Either the LF versions or p1 and p2 both support upgrades and package p2 is a valid upgrade of package p1, or p2 and p1 are the exact same package.
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
with LF version 1.17, and that the latter is a valid upgrade of
the former.
In q-1.0.0 : |
In q-2.0.0 : |
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
|
Suppose now that q-1.0.0 and q-2.0.0 are both compiled to LF version 1.15 (which does not support upgrades). Then below, the module on the right is not a valid upgrade of the module on the left because the references to U on each side resolve to packages with different IDs.
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
|
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.
Adding or deleting an interface 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 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
|
Below, the template on the right is not a valid upgrade of the
template on the left because 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
|
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, key,
maintainers) 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 dep-1.0.0 : |
In dep-2.0.0 : |
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 |
---|---|---|
4321 |
q:T |
T { p = 'Alice' } |
8765 |
dep-2.0.0:U |
U { p = 'Bob', t = None } |
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 p-1.0.0 : |
In p-2.0.0 : |
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 |
---|---|---|
1234 |
p-1.0.0:T |
T { p = 'Alice' } |
5678 |
p-2.0.0:T |
T { p = 'Bob', t = Some "Hello" } |
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 inst-1.0.0 : |
In inst-2.0.0 : |
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 |
---|---|---|
0123 |
inst-1.0.0:Inst |
Inst { p = 'Alice' } |
0456 |
client:Client |
Client { p = 'Alice' } |
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 r-1.0.0 : |
In r-2.0.0 : |
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 |
---|---|---|
9101 |
r-1.0.0:V |
V { p = 'Alice' } |
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 contract key;
- The maintainers of the contract key.
The metadata of two contracts are equivalent if and only if:
- their signatories are equal;
- their stakeholders are equal;
- their keys, after transformation to the maximum version of the two contracts, are equal;
- their maintainers 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 1
Below the template on the right is a valid upgrade of the template on the left.
In p-1.0.0 : |
In p-2.0.0 : |
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 |
---|---|---|
1234 |
p-1.0.0:T |
T { sig = ['Alice'] } |
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 p-1.0.0 : |
In p-2.0.0 : |
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.
Example 2
Below the module on the right is a valid upgrade of the module on the left:
In p-1.0.0 : |
In p-2.0.0 : |
module M where
data MyKey = MyKey
with
p : Party
template T
with
sig : Party
where
signatory sig
key MyKey p : MyKey
maintainer key.p
|
module M where
data MyKey = MyKey
with
p : Party
i : Optional Int
template T
with
sig : Party
i : Optional Int
where
signatory sig
key MyKey p i : MyKey
maintainer key.p
|
Assume a ledger that contains a contract of type T
written by
p-1.0.0
.
Contract ID | Contract Type | Contract Key | Contract |
---|---|---|---|
1234 |
p-1.0.0:T |
MyKey { p = 'Alice' } |
T { sig = 'Alice' } |
Fetching contract 1234
with package preference p-2.0.0
retrieves the
contract and successfully transforms it into a value of type p-2.0.0:T
: T
{ sig = 'Alice', i = None }
. The key of this transformed contract is then
computed using the expression MyKey p i
, which evaluates to the value
MyKey { p = 'Alice', i = None }
. This value is then compared against the key
of the original contract after transformation to a value of type
p-2.0.0:MyKey
: MyKey { p = 'Alice', i = None }
. The two values match and
thus the upgrade is valid.
On the other hand, below, the module on the right is not a valid upgrade of the module on the left:
In p-1.0.0 : |
In p-2.0.0 : |
module M where
data MyKey = MyKey
with
p : Party
template T
with
sig : Party
where
signatory sig
key MyKey p : MyKey
maintainer key.p
|
module M where
data MyKey = MyKey
with
p : Party
i : Optional Int
template T
with
sig : Party
where
signatory sig
key MyKey p (Some 0) : MyKey
maintainer key.p
|
Assume the same ledger as above. Fetching contract 1234
with package
preference p-2.0.0
retrieves the the contract and again successfully
transforms it into the value T { sig = 'Alice' }
. The key of this
transformed contract is then computed using the expression MyKey p (Some 0)
,
which evaluates to the value MyKey { p = 'Alice', i = Some 0 }
. This value is
then compared against the original contract’s key after transformation to a
value of type p-2.0.0:MyKey
: MyKey { p = 'Alice', i = None }
. The two
values 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 is not allowed to change between two versions of a contract. When a contract is fetched or exercised by interface, its view according to the code of the target template is compared to its view according to the code of the template that was used when the contract was created. It is a runtime error if the two views differ.
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
In that case, the template on the right below is a valid upgrade of the template on the left.
In p-1.0.0 : |
In p-2.0.0 : |
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
|
Assume a ledger that contains a contract of type T
written by
p-1.0.0
.
Contract ID | Type | Contract |
---|---|---|
1234 |
p-1.0.0:T |
T { p = 'Alice', i = 42 } |
Fetching contract 1234
by interface with package preference p-2.0.0
retrieves the contract and computes its view according to p-1.0.0
: IView
42
. The contract is then transformed into a value of type p-2.0.0:T
:
T { sig = 'Alice', i = 42, j = None }
and its view is computed again, this
time according to p-2.0.0
: IView 42
. Because the two views agree, the
fetch is successful.
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 : |
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 the same ledger as above. Fetching contract 1234
by interface with
package preference p-2.0.0
again retrieves the contract and computes its
view according to p-1.0.0
: IView 42
. The contract is then transformed
into a value of type p-2.0.0:T
: T { sig = 'Alice', i = 42 }
and its view
is computed again, this time according to p-2.0.0
: IView 43
. Because the
two views differ, the fetch is rejected at runtime.
Key Transformation in FetchByKey and LookupByKey¶
When fetching or looking up a contract by key in the body of a choice, the type of the key expression is known at compile time. Let us call it the target type. The returned contract, if any, verifies that its key, after transformation to the target type, matches the key used for querying.
Example
Below the module on the right is a valid upgrade of the module on the left:
In p-1.0.0 : |
In p-2.0.0 : |
module M where
data MyKey = MyKey
with
p : Party
template T
with
sig : Party
where
signatory sig
key MyKey p : MyKey
maintainer key.p
|
module M where
data MyKey = MyKey
with
p : Party
i : Optional Int
template T
with
sig : Party
i : Optional Int
where
signatory sig
key MyKey p i : MyKey
maintainer key.p
|
Assume a ledger that contains a contract of type T
written by
p-1.0.0
.
Contract ID | Contract Type | Contract Key | Contract |
---|---|---|---|
1234 |
p-1.0.0:T |
MyKey { p = 'Alice' } |
T { sig = 'Alice' } |
Finally, assume a third package which depends on p-2.0.0
and defines a
choice involving the following lookup by key:
choice C : ()
controller ctl
do
cid <- lookupByKey @T (MyKey alice None)
...
The static type of the key being looked up is thus p-2.0.0:MyKey
. The key of
contract 1234
, once transformed to a value of type p-2.0.0:MyKey
,
becomes MyKey { p = 'Alice', i = None }
. This matches the key being looked
up, so cid
is bound to Some 1234
.