Upgrading Cadence Contracts
In Cadence, you can upgrade deployed contracts by adding new functionality while preserving existing state and maintaining the same contract address. Unlike other blockchain platforms that require complex proxy patterns or complete redeployment, Cadence allows you to seamlessly extend your contracts with new functions and events through multiple incremental upgrades.
This tutorial demonstrates how to upgrade a deployed contract through two scenarios: first adding an event to notify users when the counter reaches an even number, then extending the contract with additional functionality like incrementing by 2 and checking if numbers are even.
Objectives
After completing this guide, you will be able to:
- Deploy a contract to Flow testnet using Flow CLI
- Perform incremental contract upgrades by adding new events and functions
- Update deployed contracts multiple times using the
flow accounts update-contract
command - Test upgraded functionality with Cadence transactions and scripts
- Understand what can and cannot be changed during contract upgrades
- Apply realistic upgrade scenarios based on user feedback and requirements
Prerequisites
- Flow CLI installed and configured
- Basic familiarity with Cadence and Flow accounts
- A funded testnet account to deploy and update contracts
- See Create accounts and Fund accounts in the Flow CLI commands
Contract Upgrade Overview
Cadence provides a sophisticated contract upgrade system that allows you to modify deployed contracts while ensuring data consistency and preventing runtime crashes. Understanding what you can and cannot change is crucial for successful upgrades.
What You CAN Upgrade
- Add new functions - Extend contract functionality with new methods
- Add new events - Emit additional events for monitoring and indexing
- Modify function implementations - Change how existing functions work
- Change function signatures - Update parameters and return types
- Remove functions - Delete functions that are no longer needed
- Change access modifiers - Update visibility of functions and fields
- Reorder existing fields - Field order doesn't affect storage
What You CANNOT Upgrade
- Add new fields - Would cause runtime crashes when loading existing data
- Change field types - Would cause deserialization errors
- Remove existing fields - Fields become inaccessible but data remains
- Change enum structures - Raw values must remain consistent
- Change contract name - Contract address must remain the same
Why These Restrictions Exist
The Cadence Contract Updatability documentation explains that these restrictions prevent:
- Runtime crashes from missing or garbage field values
- Data corruption from type mismatches
- Storage inconsistencies from structural changes
- Type confusion from enum value changes
The validation system ensures that existing stored data remains valid and accessible after upgrades.
Getting Started
Create a new Flow project for this tutorial:
_10# Create a new Flow project_10flow init upgrading-contracts-tutorial
Follow the prompts and create a Basic Cadence project (no dependencies)
then open the new project in your editor.
Create and Fund Testnet Account
You'll need a funded testnet account to deploy and update contracts. In a terminal in the root of your project folder:
_10# Create a testnet account_10flow accounts create --network testnet
When prompted:
- Account name: Enter
testnet-account
- Select
testnet
as the network when prompted
Fund your account with testnet FLOW tokens:
_10# Fund the account_10flow accounts fund testnet-account
This will open the faucet in your browser where you can request 100,000 testnet FLOW tokens.
The faucet provides free testnet tokens for development and testing purposes. These tokens have no real value and are only used on the testnet network.
Deploy the Initial Counter Contract
Let's start by deploying a simple Counter contract to testnet.
Open and review cadence/contracts/Counter.cdc
. This is a simple contract created with all projects:
_36access(all) contract Counter {_36_36 access(all) var count: Int_36_36 // Event to be emitted when the counter is incremented_36 access(all) event CounterIncremented(newCount: Int)_36_36 // Event to be emitted when the counter is decremented_36 access(all) event CounterDecremented(newCount: Int)_36_36 init() {_36 self.count = 0_36 }_36_36 // Public function to increment the counter_36 access(all) fun increment() {_36 self.count = self.count + 1_36 emit CounterIncremented(newCount: self.count)_36_36 // NEW: Also emit event if the result is even_36 if self.count % 2 == 0 {_36 emit CounterIncrementedToEven(newCount: self.count)_36 }_36 }_36_36 // Public function to decrement the counter_36 access(all) fun decrement() {_36 self.count = self.count - 1_36 emit CounterDecremented(newCount: self.count)_36 }_36_36 // Public function to get the current count_36 view access(all) fun getCount(): Int {_36 return self.count_36 }_36}
Configure Deployment
Add testnet deployment configuration to your flow.json
:
_10flow config add deployment
Follow the prompts:
- Network:
testnet
- Account:
testnet-account
- Contract:
Counter
- Deploy more contracts:
no
Your flow.json
will now include a testnet deployment section:
_10{_10 "deployments": {_10 "testnet": {_10 "testnet-account": ["Counter"]_10 }_10 }_10}
Deploy to Testnet
Deploy your Counter contract to testnet:
_10flow project deploy --network testnet
You should see output similar to:
_10Deploying 1 contracts for accounts: testnet-account_10_10Counter -> 0x9942a81bc6c3c5b7 (contract deployed successfully)_10_10🎉 All contracts deployed successfully
Test the Initial Contract
Use the provided transaction to test initial functionality:
Review cadence/transactions/TestCounter.cdc
. This transaction simply increments the counter:
_17import "Counter"_17_17transaction {_17_17 prepare(acct: &Account) {_17 // Authorizes the transaction_17 }_17_17 execute {_17 // Increment the counter_17 Counter.increment()_17_17 // Retrieve the new count and log it_17 let newCount = Counter.getCount()_17 log("New count after incrementing: ".concat(newCount.toString()))_17 }_17}
Cadence transactions are written in Cadence and can call one or more functions on one or more contracts, all with a single user signature. Check out our tutorial to learn how to Compose with Cadence Transactions to learn more!
Run the test transaction:
_10flow transactions send cadence/transactions/IncrementCounter.cdc --signer testnet-account --network testnet
You should see logs showing the counter incrementing and decrementing as expected.
_25Transaction ID: 251ee40a050b8c7298d33f1b73ed94996a9d99deae8559526d9dddae182f7752_25_25Block ID 25cdb14fcbaf47b3fb13e6ec43bdef0ede85a6a580caea758220c53d48493e17_25Block Height 284173579_25Status ✅ SEALED_25ID 251ee40a050b8c7298d33f1b73ed94996a9d99deae8559526d9dddae182f7752_25Payer adb1efc5826d3768_25Authorizers [adb1efc5826d3768]_25_25Proposal Key:_25 Address adb1efc5826d3768_25 Index 0_25 Sequence 1_25_25No Payload Signatures_25_25Envelope Signature 0: adb1efc5826d3768_25Signatures (minimized, use --include signatures)_25_25Events:_25 Index 0_25 Type A.adb1efc5826d3768.Counter.CounterIncremented_25 Tx ID 251ee40a050b8c7298d33f1b73ed94996a9d99deae8559526d9dddae182f7752_25 Values_25 - newCount (Int): 1
Upgrade the Contract - Part 1: Adding Event for Even Numbers
Let's start with a realistic scenario: What if we've realized it's very important to our users that they know when the counter reaches an even number, but we forgot to add an event for that case? Let's add that functionality first.
Modify the Counter Contract - First Upgrade
Update cadence/contracts/Counter.cdc
to add the new event and enhance the existing increment()
function:
_39access(all) contract Counter {_39_39 access(all) var count: Int_39_39 // Event to be emitted when the counter is incremented_39 access(all) event CounterIncremented(newCount: Int)_39_39 // Event to be emitted when the counter is decremented_39 access(all) event CounterDecremented(newCount: Int)_39_39 // NEW: Event to be emitted when the counter is incremented and the result is even_39 access(all) event CounterIncrementedToEven(newCount: Int)_39_39 init() {_39 self.count = 0_39 }_39_39 // Public function to increment the counter_39 access(all) fun increment() {_39 self.count = self.count + 1_39 emit CounterIncremented(newCount: self.count)_39_39 // NEW: Also emit event if the result is even_39 if self.count % 2 == 0 {_39 emit CounterIncrementedToEven(newCount: self.count)_39 }_39 }_39_39 // Public function to decrement the counter_39 access(all) fun decrement() {_39 self.count = self.count - 1_39 emit CounterDecremented(newCount: self.count)_39 }_39_39 // Public function to get the current count_39 view access(all) fun getCount(): Int {_39 return self.count_39 }_39}
Key Changes Made - Part 1
This first upgrade adds:
- New event:
CounterIncrementedToEven
to notify when incrementing results in an even number - Enhanced existing function: The
increment()
function now also emits the new event when appropriate - No new fields: We only use the existing
count
field to avoid validation errors
This demonstrates how you can enhance existing functionality by adding new events and modifying existing function behavior. The original CounterIncremented
event still works as before, ensuring backward compatibility.
Update the Deployed Contract - Part 1
Now let's update the deployed contract on testnet using the Flow CLI update command with our first upgrade.
Update the Contract
Use the Flow CLI update contract command to upgrade your deployed contract:
_10flow accounts update-contract ./cadence/contracts/Counter.cdc --signer testnet-account --network testnet
You should see output similar to:
_16Contract 'Counter' updated on account '0x9942a81bc6c3c5b7'_16_16Address 0x9942a81bc6c3c5b7_16Balance 99999999999.70000000_16Keys 1_16_16Key 0 Public Key [your public key]_16 Weight 1000_16 Signature Algorithm ECDSA_P256_16 Hash Algorithm SHA3_256_16 Revoked false_16 Sequence Number 2_16 Index 0_16_16Contracts Deployed: 1_16Contract: 'Counter'
The contract has been successfully updated! Notice that:
- The contract address remains the same (
0x9942a81bc6c3c5b7
) - The existing state (
count
) is preserved - New functionality is now available
Test the First Upgrade
Let's test the new event functionality. Create a simple transaction to test the enhanced increment()
function:
_10flow generate transaction TestEvenEvent
Replace the contents of cadence/scripts/CheckCounter.cdc
with:
_10import "Counter"_10_10access(all) fun main(): {String: AnyStruct} {_10 return {_10 "count": Counter.getCount(),_10 "isEven": Counter.isEven()_10 }_10}
Run the script to check the current state:
_10flow scripts execute cadence/scripts/CheckCounter.cdc --network testnet
You should see output showing the counter state:
_10Result: {"count": 1, "isEven": false}
Notice that:
- The original
count
value is preserved (showing the increment from our earlier test) - The new
isEven()
function works correctly (1 is odd, so it returns false)
Upgrade the Contract - Part 2: Adding More Functionality
Now that we've successfully added the even number event, let's add more functionality to our contract. This demonstrates how you can make multiple incremental upgrades to extend your contract's capabilities.
Modify the Counter Contract - Second Upgrade
Update cadence/contracts/Counter.cdc
to add the additional functionality:
_62access(all) contract Counter {_62_62 access(all) var count: Int_62_62 // Event to be emitted when the counter is incremented_62 access(all) event CounterIncremented(newCount: Int)_62_62 // Event to be emitted when the counter is decremented_62 access(all) event CounterDecremented(newCount: Int)_62_62 // Event to be emitted when the counter is incremented and the result is even_62 access(all) event CounterIncrementedToEven(newCount: Int)_62_62 // NEW: Event to be emitted when the counter is incremented by 2_62 access(all) event CounterIncrementedByTwo(newCount: Int)_62_62 // NEW: Event to be emitted when the counter is decremented by 2_62 access(all) event CounterDecrementedByTwo(newCount: Int)_62_62 init() {_62 self.count = 0_62 }_62_62 // Public function to increment the counter_62 access(all) fun increment() {_62 self.count = self.count + 1_62 emit CounterIncremented(newCount: self.count)_62_62 // Also emit event if the result is even_62 if self.count % 2 == 0 {_62 emit CounterIncrementedToEven(newCount: self.count)_62 }_62 }_62_62 // Public function to decrement the counter_62 access(all) fun decrement() {_62 self.count = self.count - 1_62 emit CounterDecremented(newCount: self.count)_62 }_62_62 // Public function to get the current count_62 view access(all) fun getCount(): Int {_62 return self.count_62 }_62_62 // NEW: Public function to increment the counter by 2_62 access(all) fun incrementByTwo() {_62 self.count = self.count + 2_62 emit CounterIncrementedByTwo(newCount: self.count)_62 }_62_62 // NEW: Public function to decrement the counter by 2_62 access(all) fun decrementByTwo() {_62 self.count = self.count - 2_62 emit CounterDecrementedByTwo(newCount: self.count)_62 }_62_62 // NEW: Public function to check if the current count is even_62 view access(all) fun isEven(): Bool {_62 return self.count % 2 == 0_62 }_62}
Key Changes Made - Part 2
This second upgrade adds:
- New functions:
incrementByTwo()
anddecrementByTwo()
that modify the existing counter by 2 - New events:
CounterIncrementedByTwo
andCounterDecrementedByTwo
for the new functionality - New view function:
isEven()
to check if the current count is even - Preserved existing functionality: All previous functionality remains intact
Update the Deployed Contract - Part 2
Now let's update the deployed contract with our second upgrade.
Update the Contract Again
Use the Flow CLI update contract command to upgrade your deployed contract with the additional functionality:
_10flow accounts update-contract ./cadence/contracts/Counter.cdc --signer testnet-account --network testnet
You should see output similar to:
_16Contract 'Counter' updated on account '0x9942a81bc6c3c5b7'_16_16Address 0x9942a81bc6c3c5b7_16Balance 99999999999.70000000_16Keys 1_16_16Key 0 Public Key [your public key]_16 Weight 1000_16 Signature Algorithm ECDSA_P256_16 Hash Algorithm SHA3_256_16 Revoked false_16 Sequence Number 3_16 Index 0_16_16Contracts Deployed: 1_16Contract: 'Counter'
The contract has been successfully updated again! Notice that:
- The contract address remains the same (
0x9942a81bc6c3c5b7
) - The existing state (
count
) is preserved - All previous functionality is still available
- New functionality is now available
Verify the Update
Let's verify that the existing functionality still works and the new functionality is available.
Create a script to check the current state:
_10flow generate script CheckCounter
Replace the contents of cadence/scripts/CheckCounter.cdc
with:
_10import "Counter"_10_10access(all) fun main(): {String: AnyStruct} {_10 return {_10 "count": Counter.getCount(),_10 "isEven": Counter.isEven()_10 }_10}
Run the script to check the current state:
_10flow scripts execute cadence/scripts/CheckCounter.cdc --network testnet
You should see output showing the counter state:
_10Result: {"count": 2, "isEven": true}
Notice that:
- The original
count
value is preserved (showing the increments from our earlier tests) - The new
isEven()
function works correctly (2 is even, so it returns true)
Test the New Functionality
Now let's create a transaction to test the new even counter functionality.
Create Test Transaction
Create a new transaction to test the upgraded functionality:
_10flow generate transaction TestNewCounter
Replace the contents of cadence/transactions/TestNewCounter.cdc
with:
_35import "Counter"_35_35transaction {_35 prepare(acct: &Account) {_35 // Authorizes the transaction_35 }_35_35 execute {_35 // Test the new functionality_35 log("Current count: ".concat(Counter.getCount().toString()))_35 log("Is even: ".concat(Counter.isEven().toString()))_35_35 // Test the new incrementByTwo function_35 Counter.incrementByTwo()_35 log("After incrementByTwo: ".concat(Counter.getCount().toString()))_35 log("Is even now: ".concat(Counter.isEven().toString()))_35_35 Counter.incrementByTwo()_35 log("After second incrementByTwo: ".concat(Counter.getCount().toString()))_35_35 // Test the new decrementByTwo function_35 Counter.decrementByTwo()_35 log("After decrementByTwo: ".concat(Counter.getCount().toString()))_35_35 // Verify original functionality still works and test the new event_35 Counter.increment()_35 log("After regular increment: ".concat(Counter.getCount().toString()))_35 log("Is even now: ".concat(Counter.isEven().toString()))_35_35 // Increment again to trigger the CounterIncrementedToEven event_35 Counter.increment()_35 log("After second increment: ".concat(Counter.getCount().toString()))_35 log("Is even now: ".concat(Counter.isEven().toString()))_35 }_35}
Run the Test Transaction
Execute the transaction to test the new functionality:
_10flow transactions send cadence/transactions/TestNewCounter.cdc --signer testnet-account --network testnet
You should see logs showing:
- The counter incrementing by 2 each time with
incrementByTwo()
- The counter decrementing by 2 with
decrementByTwo()
- The
isEven()
function working correctly - The original
increment()
function still working normally - The new
CounterIncrementedToEven
event being emitted when incrementing results in an even number
Verify Final State
Run the check script again to see the final state:
_10flow scripts execute cadence/scripts/CheckCounter.cdc --network testnet
You should see output similar to:
_10Result: {"count": 6, "isEven": true}
This confirms that:
- The new functions work correctly with the existing counter
- The original state was preserved during the upgrade
- The new functionality is fully operational
Understanding Contract Upgrades in Cadence
Cadence provides a sophisticated contract upgrade system that ensures data consistency while allowing controlled modifications. The Cadence Contract Updatability documentation provides comprehensive details about the validation rules and restrictions.
What You Can Upgrade
When upgrading Cadence contracts, you can:
- Add new state variables (like
countEven
) - Add new functions (like
incrementEven()
anddecrementEven()
) - Add new events (like
EvenCounterIncremented
) - Add new interfaces and resource types
- Modify function implementations (with careful consideration)
- Remove existing functions (they are not stored as data)
- Change function signatures (parameters, return types)
- Change access modifiers of fields and functions
- Reorder existing fields (order doesn't affect storage)
What You Cannot Change
There are important limitations to contract upgrades:
- Cannot add new fields to existing structs, resources, or contracts
- This would cause runtime crashes when loading existing data
- The initializer only runs once during deployment, not on updates
- Cannot change the type of existing state variables
- Would cause deserialization errors with stored data
- Cannot remove existing state variables (though they become inaccessible)
- Cannot change enum structures (raw values must remain consistent)
- Cannot change the contract name or address
Validation Goals
The contract update validation ensures that:
- Stored data doesn't change its meaning when a contract is updated
- Decoding and using stored data does not lead to runtime crashes
- Type safety is maintained across all stored values
The validation system focuses on preventing runtime inconsistencies with stored data. It does not ensure that programs importing the updated contract remain valid - you may need to update dependent code if you change function signatures or remove functions.
Advanced Upgrade Patterns
The #removedType
Pragma
For cases where you need to remove a type declaration (which is normally invalid), Cadence provides the #removedType
pragma. This allows you to "tombstone" a type, preventing it from being re-added with the same name:
_10access(all) contract Foo {_10 // Remove the resource R permanently_10 #removedType(R)_10_10 // Other contract code..._10}
This pragma:
- Prevents security issues from type confusion
- Cannot be removed once added (prevents circumventing restrictions)
- Only works with composite types, not interfaces
Enum Upgrade Restrictions
Enums have special restrictions due to their raw value representation:
- Can only add enum cases at the end of existing cases
- Cannot reorder, rename, or remove existing enum cases
- Cannot change the raw type of an enum
- Cannot change enum case names (would change stored values' meaning)
Best Practices
When upgrading contracts:
- Plan upgrades carefully - Consider future extensibility and avoid breaking changes
- Test thoroughly - Verify both old and new functionality work correctly
- Use events - Emit events for new functionality to enable monitoring and indexing
- Document changes - Keep track of what was added, removed, or modified in each upgrade
- Consider dependent code - Update any programs that import your contract if you change function signatures
- Use the
#removedType
pragma - When you need to permanently remove types - Validate enum changes - Ensure enum modifications follow the strict rules
- Test with existing data - Verify upgrades work with real stored state, not just empty contracts
Why This Matters
Cadence's contract upgrade model provides several advantages:
- No proxy patterns needed - Unlike Ethereum, you don't need complex proxy contracts
- State preservation - Existing data and functionality remain intact
- Address stability - Contract addresses don't change during upgrades
- Gas efficiency - Upgrades are more efficient than redeployment
- User experience - Applications continue working without interruption
This approach allows you to evolve your contracts over time, adding new features and capabilities while maintaining backward compatibility and preserving user data.
Conclusion
In this tutorial, you learned how to upgrade deployed Cadence contracts through multiple incremental upgrades by:
- Deploying an initial contract to Flow testnet
- Performing a first upgrade to add an event for even numbers based on user feedback
- Testing the first upgrade to verify the new event functionality works correctly
- Performing a second upgrade to add additional functions and events
- Testing the complete upgraded functionality with comprehensive transactions
- Verifying state preservation and backward compatibility across multiple upgrades
Now that you have completed the tutorial, you should be able to:
- Deploy contracts to Flow testnet using Flow CLI
- Perform incremental contract upgrades by adding new functions and events
- Update deployed contracts multiple times while preserving existing state
- Test upgraded functionality with Cadence transactions and scripts
- Understand what can and cannot be changed during contract upgrades
- Apply realistic upgrade scenarios based on user feedback and requirements
- Plan and execute multiple contract upgrades over time
This incremental upgrade model makes Cadence contracts more flexible and maintainable than traditional smart contract platforms, allowing you to evolve your applications over time based on real user needs without complex migration patterns or breaking changes. The ability to make multiple upgrades while preserving state and maintaining the same contract address provides a powerful foundation for long-term application development.