🌱
Dev Compendium
  • Ethereum
    • Solidity
      • EVM
      • Architecture
      • Execution Context
      • Transactions
      • Gas
      • Calldata, Memory & Storage
      • Gas Optimisation
      • Function Declarations
      • receive() & fallback()
      • CALL vs. DELEGATE CALL
    • Yul
      • Introduction
      • Types
      • Basic Operations
      • Storage
      • Memory
        • Arrays
        • Structs
        • Tuples, revert, keccak256
        • Logs and Events
        • Gotchas
        • abi.encode
      • Calldata
        • External Calls
        • Dynamic Length Inputs
        • Transferring Value
        • Receiving Contract Calls
      • Contracts in Yul
      • Other Yul Functions
    • Foundry
    • Security
      • Common Vulnerabilities
      • Best Practices
      • Development Workflow
      • Contract Migration
    • Auditing Tools
      • Slither
      • Mythril
      • Fuzzing
    • Upgradable Contracts
      • Upgrade Patterns
      • ERC-1967 Implementation
      • Deployment
    • MEV
    • Tooling
      • Chainlink
      • IPFS
      • Radicle
    • Frontend
      • Contract Hooks
      • Wallet Connection
        • wagmi.sh
        • Rainbow Kit
      • thirdweb
    • Protocol Research
      • Uniswap v2
      • Uniswap v3
      • Curve
      • GMX
  • Starkware
    • Fundamentals
    • Account Abstraction
    • Universal Deployer
    • Cairo 1.0
    • starknet.js
    • Security Model
  • Zero Knowledge
    • Group Theory
    • ECDSA
  • Rust
    • Basic Operations
    • Set up
    • Primitives
    • Control Flow
    • Mutability & Shadowing
    • Adding Behavior
    • Lifetimes
    • Std Library
  • SUI
    • Architecture
    • Consensus Mechanism
    • Local Node Setup
    • Sui Client CLI
    • Move Contracts
      • Move
      • Move.toml
      • Move.lock
      • Accessing Time in Sui Move
      • Set up Development Framework
      • Debug & Publish
      • Package Upgrades
      • Sui Move Library
      • Difference from Core Move
    • Object Programming
      • Object Basics
      • Using Objects
      • Immutable Objects
      • Object Wrapping
      • Dynamic Fields
      • Collections
      • Unit Testing
      • Deployment with CLI
  • NEAR
    • Architecture
    • Contract Standards
      • Fungible Token (NEP-141)
      • Non-Fungible Token (NEP-171)
      • Storage Management (NEP-145)
      • Events (NEP-297)
      • Meta-Transactions
    • Rust Contracts
      • Development Workflow
      • Smart Contract Layout
      • Storage Management
      • Events & Meta-transactions
      • Method Types
      • Upgrading Contracts
      • Unit Testing
    • NEAR Libraries
    • Environment Variables
    • Serialisation
    • Security Concepts
    • Collections
    • JS SDK
Powered by GitBook
On this page
  • Requirements
  • Upgrading
  • Example
  1. SUI
  2. Move Contracts

Package Upgrades

PreviousDebug & PublishNextSui Move Library

Last updated 2 years ago

Sui smart contracts are represented by immutable package objects consisting of a collection of Move modules. Because the packages are immutable, transactions can safely access smart contracts without full consensus (fast-path transactions). If someone could change these packages, they would become , which would require full consensus before completing a transaction.

The inability to change package objects, however, becomes a problem when considering the iterative nature of code development. Builders require the ability to update their code and pull changes from other developers while still being able to reap the benefits of fast-path transactions. Fortunately, the Sui network provides a method of upgrading your packages while still retaining their immutable properties.

This topic examines how to upgrade packages using the Sui Client CLI. Move developers can reference the for options on working with package upgrades on a code level.

Requirements

To upgrade a package, your package must satisfy the following requirements:

  • You must have an UpgradeTicket for the package you want to upgrade. The Sui network issues UpgradeCaps when you publish a package, then you can issue UpgradeTickets as the owner of that UpgradeCap. The Sui Client CLI handles this requirement automatically.

  • Your changes must be layout-compatible with the previous version.

    • Existing public function signatures and struct layouts must remain the same.

    • You can add new structs and functions.

    • You can add abilities to existing structs.

    • You can remove generic type constraints from existing functions (public or otherwise).

    • You can change function implementations.

    • You can change non-public function signatures, including friend and entry function signatures.

Note: If you have a package with a dependency, and that dependency is upgraded, your package does not automatically depend on the newer version. You must explicitly upgrade your own package to point to the new dependency.

Upgrading

Use the sui client upgrade command to upgrade packages that meet the previous requirements, providing values for the following flags:

  • --gas-budget: The maximum number of gas units that can be expended before the network cancels the transaction.

  • --cap: The ID of the UpgradeCap. You receive this ID as a return from the publish command.

Example

You develop a package named sui_package. Its manifest looks like the following:

[package]
name = "sui_package"
version = "0.0.0"

[addresses]
sui_package = "0x0"

When your package is ready, you publish it:

sui client publish --gas-budget <GAS-BUDGET-AMOUNT>

And receive the response:

INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING MyFirstPackage
Successfully verified dependencies on-chain against source.
----- Transaction Digest ----
2bn3EtHvbVY4bM1887MvFiGWnqq1YZ2RKmbeK7TrRbLL
----- Transaction Data ----
Transaction Signature: [Signature(Ed25519SuiSignature(Ed25519SuiSignature([0, 156, 133, 71, 156, 44, 204, 30, 31, 250, 204, 247, 60, 212, 249, 61, 112, 249, 148, 180, 83, 207, 236, 58, 99, 134, 5, 174, 115, 226, 41, 139, 192, 1, 183, 133, 38, 73, 254, 205, 190, 54, 210, 112, 144, 204, 137, 3, 8, 30, 165, 147, 120, 199, 227, 119, 53, 208, 28, 101, 34, 239, 102, 210, 1, 103, 111, 108, 165, 156, 100, 95, 13, 236, 27, 13, 127, 150, 50, 47, 155, 217, 27, 164, 61, 245, 254, 81, 182, 121, 231, 58, 150, 214, 46, 27, 222])))]
Transaction Kind : Programmable
Inputs: [Pure(SuiPureValue { value_type: Some(Address), value: "<PUBLISHER-ID>" })]
Commands: [
  Publish(_,,0x00000000000000000000000000000000000000000000000000000000000000010x0000000000000000000000000000000000000000000000000000000000000002),
  TransferObjects([Result(0)],Input(0)),
]

Sender: <PUBLISHER-ID>
Gas Payment: Object ID:, version: 0x6, digest: HLAcq3SFPZm4xvcPryXk5MjA718xGVnTYCdtWbFsaJpe 
Gas Owner: <PUBLISHER-ID>
Gas Price: 1
Gas Budget: <GAS-BUDGET-AMOUNT>

----- Transaction Effects ----
Status : Success
Created Objects:
  - ID: <ORIGINAL-PACKAGE-ID> , Owner: Immutable
  - ID: <UPGRADE-CAP-ID> , Owner: Account Address ( <PUBLISHER-ID> )
  - ID: <PUBLISHER-ID> , Owner: Account Address ( <PUBLISHER-ID> )
Mutated Objects:
  - ID: <GAS-COIN-ID> , Owner: Account Address ( <PUBLISHER-ID> )

----- Events ----
Array []
----- Object changes ----
Array [
    Object {
        "type": String("mutated"),
        "sender": String("<PUBLISHER-ID>"),
        "owner": Object {
            "AddressOwner": String("<PUBLISHER-ID>"),
        },
        "objectType": String("0x2::coin::Coin<0x2::sui::SUI>"),
        "objectId": String("<GAS-COIN-ID>"),
        "version": Number(7),
        "previousVersion": Number(6),
        "digest": String("6R39f68p4tGqJWJTakKCyL8tz2w2XTvJ3Mu5nGwxadda"),
    },
    Object {
        "type": String("published"),
        "packageId": String("<ORIGINAL-PACKAGE-ID>"),
        "version": Number(1),
        "digest": String("FrBhLF2Rn4jP3SUsss7aXqwDDRtoKxgGbPm8eVkH7jrQ"),
        "modules": Array [
            String("sui_package"),
        ],
    },
    Object {
        "type": String("created"),
        "sender": String("<PUBLISHER-ID>"),
        "owner": Object {
            "AddressOwner": String("<PUBLISHER-ID>"),
        },
        "objectType": String("0x2::package::UpgradeCap"),
        "objectId": String("<UPGRADE-CAP-ID>"),
        "version": Number(7),
        "digest": String("BoGQ63r27DFZDMC8p7YwRcDpToFpbZ9rG1R4o4uXkaUw"),
    },
    Object {
        "type": String("created"),
        "sender": String("<PUBLISHER-ID>"),
        "owner": Object {
            "AddressOwner": String("<PUBLISHER-ID>"),
        },
        "objectType": String("<ORIGINAL-PACKAGE-ID>::sui_package::<MODULE-NAME>"),
        "objectId": String("<PACKAGE-ID>"),
        "version": Number(7),
        "digest": String("BC3KeuATKJozLNipbUz2GWzoDXbodXH4HLm59TxJSmVd"),
    },
]
----- Balance changes ----
Array [
    Object {
        "owner": Object {
            "AddressOwner": String("<PUBLISHER-ID>"),
        },
        "coinType": String("0x2::sui::SUI"),
        "amount": String("-9328480"),
    },
]

The result includes an Object changes section with two pieces of information you need for upgrading, an UpgradeCap ID and your package ID.

You can identify the different objects using the Object.objectType value in the response. The UpgradeCap entry has a value of String("0x2::package::UpgradeCap") and the objectType for the package reads String("<PACKAGE-ID>::sui_package::<MODULE-NAME>")

To make sure your other packages can use this package as a dependency, you must update the manifest (Move.toml file) for your package to include this information.

Update the alias address and add a new published-at entry in the [package] section, both pointing to the value of the on-chain ID:

[package]
name = "sui_package"
version = "0.0.0"
published-at = "<ORIGINAL-PACKAGE-ID>"

[addresses]
sui_package = "<ORIGINAL-PACKAGE-ID>"

The published-at value allows the Move compiler to verify dependencies against on-chain versions of those packages.

After a while, you decide to upgrade your sui_package to include some requested features. Before running the upgrade command, you need to edit the manifest again. In the [addresses] section, you update the sui_package address value to 0x0 again so the validator issues a new address for the upgrade package. You can leave the published-at value the same, because it is only read by the toolchain when publishing a dependent package. The saved manifest now resembles the following:

[package]
name = "sui_package"
version = "0.0.1"
published-at = "<ORIGINAL-PACKAGE-ID>"

[addresses]
sui_package = "0x0"

With the new manifest and code in place, it's time to use the sui client upgrade command to upgrade your package. Pass the UpgradeCap ID (the <UPGRADE-CAP-ID> value from the example) to the --upgrade-capability flag.

sui client upgrade --gas-budget <GAS-BUDGET-AMOUNT> --upgrade-capability <UPGRADE-CAP-ID>
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING MyFirstPackage
Successfully verified dependencies on-chain against source.
----- Transaction Digest ----
HZdnGWE2VoqDWwBhoBwe17tDFn7uYgfBpK5nk75Rmh5z
----- Transaction Data ----
Transaction Signature: [Signature(Ed25519SuiSignature(Ed25519SuiSignature([0, 108, 166, 235, 244, 238, 72, 232, 143, 49, 225, 180, 55, 63, 131, 155, 146, 126, 50, 158, 138, 213, 174, 71, 162, 222, 62, 198, 245, 219, 224, 171, 82, 43, 197, 56, 16, 252, 186, 83, 154, 109, 104, 90, 212, 236, 122, 78, 175, 173, 107, 9, 2, 10, 30, 74, 101, 138, 228, 251, 170, 39, 25, 242, 8, 103, 111, 108, 165, 156, 100, 95, 13, 236, 27, 13, 127, 150, 50, 47, 155, 217, 27, 164, 61, 245, 254, 81, 182, 121, 231, 58, 150, 214, 46, 27, 222])))]
Transaction Kind : Programmable
Inputs: [Object(ImmOrOwnedObject { object_id: <UPGRADE-CAP-ID>, version: SequenceNumber(9), digest: o#Bvy7R33o4ogLuyfzM76nmM1RqKnEALQrbd34CLWZhf5Y }), Pure(SuiPureValue { value_type: Some(U8), value: 0 }), Pure(SuiPureValue { value_type: Some(Vector(U8)), value: [202,122,179,32,64,155,14,236,160,5,75,17,159,202,125,114,234,36,182,41,159,84,56,222,99,121,250,82,206,19,212,5] })]
Commands: [
  MoveCall(0x0000000000000000000000000000000000000000000000000000000000000002::package::authorize_upgrade(,Input(0),Input(1)Input(2))),
  Upgrade(Result(0),,0x00000000000000000000000000000000000000000000000000000000000000010x0000000000000000000000000000000000000000000000000000000000000002, <ORIGINAL-PACKAGE-ID>, _)),
  MoveCall(0x0000000000000000000000000000000000000000000000000000000000000002::package::commit_upgrade(,Input(0)Result(1))),
]

Sender: <PUBLISHER-ID>
Gas Payment: Object ID: <GAS-COIN-ID>, version: 0x9, digest: 84ZKQcZZLTCmyAoRp9QhDrxxZ7nzGtdoBw18UbNm26ad 
Gas Owner: <PUBLISHER-ID>
Gas Price: 1
Gas Budget: <GAS-BUDGET-AMOUNT>

----- Transaction Effects ----
Status : Success
Created Objects:
  - ID: <MODULE-ID> , Owner: Immutable
Mutated Objects:
  - ID: <GAS-COIN-ID> , Owner: Account Address ( <PUBLISHER-ID> )
  - ID: <UPGRADE-CAP-ID> , Owner: Account Address ( <PUBLISHER-ID> )

----- Events ----
Array []
----- Object changes ----
Array [
    Object {
        "type": String("mutated"),
        "sender": String("<PUBLISHER-ID>"),
        "owner": Object {
            "AddressOwner": String("<PUBLISHER-ID>"),
        },
        "objectType": String("0x2::coin::Coin<0x2::sui::SUI>"),
        "objectId": String("<GAS-COIN-ID>"),
        "version": Number(10),
        "previousVersion": Number(9),
        "digest": String("EvfMLHBDXFRUeMd7vgmAMaacnwZbGFHg8d7Kov3fTt9L"),
    },
    Object {
        "type": String("mutated"),
        "sender": String("<PUBLISHER-ID>"),
        "owner": Object {
            "AddressOwner": String("<PUBLISHER-ID>"),
        },
        "objectType": String("0x2::package::UpgradeCap"),
        "objectId": String("<UPGRADE-CAP-ID>"),
        "version": Number(10),
        "previousVersion": Number(9),
        "digest": String("FZ9AruCAnhjW8zrozUMgtsY79SggTiHr3suwZNe5eMnM"),
    },
    Object {
        "type": String("published"),
        "packageId": String("<UPGRADED-PACKAGE-ID>"),
        "version": Number(2),
        "digest": String("8RDsE6kFND2V2gxGiytwxa815mctwxNh7A8YqRS4AJME"),
        "modules": Array [
            String("<MODULE-NAME>"),
        ],
    },
]
----- Balance changes ----
Array [
    Object {
        "owner": Object {
            "AddressOwner": String("<PUBLISHER-ID>"),
        },
        "coinType": String("0x2::sui::SUI"),
        "amount": String("-6350420"),
    },
]

The result provides a new ID for the upgraded package. As was the case before the upgrade, you need to include that information in your manifest so any of your other packages that depend on your sui_package know where to find the on-chain bytecode for verification. Edit your manifest once again to provide the upgraded package ID for the published-at value, and return the original sui_package ID value in the [addresses] section:

[package]
name = "sui_package"
version = "0.0.1"
published-at = "<UPGRADED-PACKAGE-ID>"

[addresses]
sui_package = "<ORIGINAL-PACKAGE-ID>"

The published-at value changes with every upgrade. The ID for the sui_package in the [addresses] section always points to the original package ID after upgrading. You must always change that value back to 0x0, however, before running the upgrade command so the validator knows to create a new ID for the upgrade.

Developers upgrading packages using Move code have access to types and functions to define custom upgrade policies. For example, a Move developer might want to disallow upgrading a package, regardless of the current package owner. The is available to them to create this behavior. More advanced policies using available types like UpgradeTicket and Upgrade Receipt are also possible. For an example, see this on GitHub.

When you use the Sui Client CLI, the upgrade command handles generating the upgrade digest, authorizing the upgrade with the UpgradeCap to get an UpgradeTicket, and updating the UpgradeCap with the UpgradeReceipt after a successful upgrade. To learn more about these processes, see the Move documentation for the .

The console alerts you if the new package doesn't satisfy , otherwise the compiler publishes the upgraded package to the network and returns its result:

shared objects
package module documentation
make_immutable function
custom upgrade policy
package module
requirements