Writing Your First Upgrade
Documentation Index
Fetch the complete documentation index at: https://docs.canton.network/llms.txt Use this file to discover all available pages before exploring further.
Writing Your First Upgrade
Step-by-step tutorial for creating a v2 Daml package with backward-compatible changes
This tutorial walks you through creating a v2 version of a Daml package. You’ll start with a simple template, add an optional field and a new choice, then verify that the upgrade compiles and that existing contracts work with the new code.
Prerequisites
- A working
dpminstallation with the Daml SDK - Familiarity with Daml templates and choices (Module 3)
- A text editor or Daml Studio
Step 1: Create the v1 Package
Start by scaffolding a new project. The dpm new command creates the directory structure and a daml.yaml file:
dpm new com-example-licensing
For this tutorial, set up the directory structure manually to keep things explicit:
mkdir -p daml/v1/daml
Create daml/v1/daml.yaml:
# daml/v1/daml.yaml
sdk-version: 3.4.9
name: com-example-licensing
version: 1.0.0
source: daml
dependencies:
- daml-prim
- daml-stdlib
Create daml/v1/daml/Main.daml:
-- daml/v1/daml/Main.daml
module Main where
data LicenseInfo = LicenseInfo
with
holder : Party
issuer : Party
product : Text
deriving (Eq, Show)
template License
with
info : LicenseInfo
where
signatory info.issuer
observer info.holder
choice Revoke : ()
controller info.issuer
do pure ()
Build and verify:
cd daml/v1
dpm build
Step 2: Create the v2 Package
Copy the package — cp -r v1 v2 — and bump the version. The package name must stay the same — this is how Daml knows it’s an upgrade:
# daml/v2/daml.yaml
sdk-version: 3.4.9
name: com-example-licensing
version: 2.0.0
source: daml
dependencies:
- daml-prim
- daml-stdlib
upgrades: ../v1/.daml/dist/com-example-licensing-1.0.0.dar
The upgrades field points to the v1 DAR. This is what tells dpm build to validate that v2 is a compatible upgrade of v1.
Now make backward-compatible changes. Add an Optional field to the record and a new choice to the template:
-- daml/v2/daml/Main.daml
module Main where
data LicenseInfo = LicenseInfo
with
holder : Party
issuer : Party
product : Text
expiryDate : Optional Time -- NEW: optional expiry date
deriving (Eq, Show)
template License
with
info : LicenseInfo
where
signatory info.issuer
observer info.holder
choice Revoke : ()
controller info.issuer
do pure ()
-- NEW: choice to renew the license with an expiry date
choice Renew : ContractId License
with
newExpiry : Time
controller info.issuer
do create this with
info = info with expiryDate = Some newExpiry
The changes follow SCU compatibility rules:
expiryDateis anOptionalfield with implicitNonedefault for v1 contractsRenewis a new choice (doesn’t exist in v1, so no backward-compatibility issue)
Step 3: Verify Compatibility
Build the v2 package:
cd daml/v2
dpm build
If the build succeeds, the compiler has verified that v2 is a valid upgrade of v1. The upgrades field in daml.yaml triggers this check — without it, dpm build compiles v2 in isolation and performs no cross-version validation. The compiler checks all the SCU rules: no removed fields, no type changes, new fields are Optional, etc.
If you’ve introduced a breaking change, the compiler will report an error telling you what rule was violated.
Step 4: Test Approximated Cross-Version Behavior
To verify the upgrade path, add a test script to the v2 package. First, add the daml-script dependency to daml/v2/daml.yaml:
# daml/v2/daml.yaml
sdk-version: 3.4.9
name: com-example-licensing
version: 2.0.0
source: daml
dependencies:
- daml-prim
- daml-stdlib
- daml-script
upgrades: ../v1/.daml/dist/com-example-licensing-1.0.0.dar
Then create a test script that simulates a v1 contract (with expiryDate = None) and exercises the new v2 Renew choice:
-- daml/v2/daml/UpgradeTest.daml
module UpgradeTest where
import Main
import Daml.Script
import DA.Date (Month(..), datetime)
testUpgradePath : Script ()
testUpgradePath = do
issuer <- allocateParty "Issuer"
holder <- allocateParty "Holder"
-- Create a contract with no expiryDate (simulating a v1 contract)
licenseCid <- submit issuer do
createCmd License with
info = LicenseInfo with
holder
issuer
product = "Widget Pro"
expiryDate = None
-- Exercise the new v2 Renew choice
newLicenseCid <- submit issuer do
exerciseCmd licenseCid Renew with
newExpiry = datetime 2026 Dec 31 0 0 0
-- Verify the renewed license has the expiry set
Some renewed <- queryContractId holder newLicenseCid
assertMsg "Should have expiry"
(renewed.info.expiryDate == Some (datetime 2026 Dec 31 0 0 0))
Run the test from the v2 package directory:
cd daml/v2
dpm test
Step 5: Deploy Both Versions
In a real deployment, you may have both DAR files uploaded to your validator. The order matters: upload v1 first (if not already uploaded), then v2. For new validators, they only need to upload v2 provided that it is SCU compatible with v1.
# Upload v1 (if not already on the ledger)
curl -X POST "http://localhost:7575/v2/packages" \
-H "Content-Type: application/octet-stream" \
--data-binary @daml/v1/.daml/dist/com-example-licensing-1.0.0.dar
# Upload v2
curl -X POST "http://localhost:7575/v2/packages" \
-H "Content-Type: application/octet-stream" \
--data-binary @daml/v2/.daml/dist/com-example-licensing-2.0.0.dar
Once v2 is uploaded and vetted on all stakeholders’ validators, the new choice becomes available on existing v1 contracts.
What Happens Under the Hood
When a validator receives a v2 DAR:
- If automatic vetting is enabled, the validator node vets the new package alongside v1. Otherwise, vetting must be manually done.
- Both packages remain active — v1 contracts aren’t affected
- When v2 code fetches a v1 contract, the runtime fills
Optionalfields withNone - When v1 code fetches a v2 contract where
Optionalfields areNone, the fetch succeeds (the fields are simply ignored) - When v1 code fetches a v2 contract where an
Optionalfield has a non-Nonevalue, the fetch fails to prevent data loss
This design ensures that mixed-version operation is safe: no data is silently lost, and incompatible interactions fail explicitly rather than corrupting state.
See Package Selection to learn how different versions are resolved at runtime.
Next Steps
- Upgrade Compatibility — Full reference of allowed and disallowed changes
- Testing Upgrades — Comprehensive upgrade testing strategies
- Deploying Upgrades — Rolling out upgrades across environments
Mirrored from Canton Network official documentation (CC-BY-4.0) by CC Privacy Club for learning purposes.