New Move CLI Flag Gives Enhanced Error Context

Developers using the new error flag in Sui 1.42 will have an easier time debugging code.

New Move CLI Flag Gives Enhanced Error Context

The release of the --verify-compatibility flag in the Sui CLI makes package upgrades much easier to debug. This new flag, detailed in the PR Enable upgrade compatibility checks for package upgrades #20342, gives you greater context on errors which can occur during your app upgrades. Using this flag while running your app in a local simulation helps you catch errors and refine your code without spending SUI on the live network.

A primer on upgrades

Sui provides facilities for upgrading your contract. Upon the creation of a “package” you receive an upgrade capability (cap) that allows you to upgrade your contract. You can either choose to store this object for use in later maintenance or mark your contract immutable upon publication. Once you are satisfied your package is stable and needs no further upgrades, your package can be made immutable by discarding the upgrade cap. This ensures that your contract will no longer change, thereby increasing trust among users.

If you store your upgrade cap, there are a few “policies” available to you for upgrades, ordered from least strict to most: 

  • Compatible: Allows adding new types and functions, changing non-public interfaces and function bodies, updating dependencies.
  • Additive: Same as compatible except that existing declarations become immutable.
  • Dependency Only: Allows upgrading dependencies.

There are a few caveats regarding constants and constraints. You can read more about the rules surrounding each policy type in the Custom Upgrade Policies section of the Sui documentation.

Local verification

Most developers will want the option to issue fixes to their contracts and will choose a ‘compatible’ policy. However, if you attempt to publish an incompatible upgrade, you will encounter errors when using the --verify-compatibility flag. These error messages are shown in each of the following CLI examples. If you don't use this flag, failed transactions will offer no context, making them more difficult to debug.

// removing a struct from a package
error[Compatibility E01001]: missing public declaration
   ┌─ UpgradeErrors.move:4:18
   │
4  │ module upgrades::upgrades {
   │                  ^^^^^^^ struct 'MyStruct' is missing
   │
 = Structs are part of a module's public interface and cannot be removed or changed during a 'compatible' upgrade.
 = Add missing struct 'MyStruct' back to the module 'upgrades'.

Changing the signature of a function or the content of a public type will result in errors which indicate how the signature was altered and what changes are required to fix your package.

Note: Structs are required to be public, making them effectively immutable.

// removing a field from a struct
error[Compatibility E01002]: type mismatch
   ┌─ UpgradeErrors.move:34:19
   │
34 │     public struct MyStruct {
   │                   ^^^^^^^^^^^ Incorrect number of fields: expected    
   │                               2, found 1
   │
   = Structs are part of a module's public interface and cannot be removed or changed during an upgrade.
   = Restore the original struct's fields for struct 'MyStruct' including the ordering.

An additive policy is useful for developers who want to guarantee that their existing functions will not change while still providing migration paths within their package. Migration is typically accomplished by exchanging old types for new ones that use newly published functions. The additive policy increases restrictions by disallowing changes to the body of existing functions.

error[Compatibility E03001]: function signature mismatch
   ┌─ UpgradeErrors.move:18:31
   │
18 │     fun my_func(): u32 { // changed to u32
   │                    ^^^ Unexpected return type 'u32', expected 'u64'
   │
   = Functions cannot be removed or changed during an 'additive' or 'dependency only' upgrade.
   = Restore the original function's return type for function 'my_func'.

A dependency-only policy is the most restrictive choice which still allows upgrades. It is useful for developers who have thoroughly audited their package and only wish to update packages their contract depends on. Adding any new types or functionality is restricted.

error[Compatibility E01010]: new declaration
  ┌─ UpgradeErrors.move:4:18
  │
4 │ module upgrades::upgrades {
  │                  ^^^^^^^^ New unexpected enum 'MyEnum'.
  │
  = Enums cannot be added during a 'dependency only' upgrade.
  = Remove the enum 'MyEnum' from its module.

Immutable contracts continue to be a great option when you have the time to fully audit both your package and its dependencies, or plan to deprecate your previous contract in favor of a newly published package. In this case, errors are unaffected and typically appear as object errors.

Could not find upgrade capability at 0x123

These changes are a major improvement over previously provided on-chain errors. Without using the --verify-compatiblility flag, you will experience the old behavior which returns a failed transaction without any context around where the issue occurred within your package.

Error executing transaction '2Ve6boZzub62kK7BYMcGCvu3mtnUtDQExFVAwWea8FxN': PackageUpgradeError { upgrade_error: IncompatibleUpgrade } in command 1

Sui version 1.42 includes the upgrade errors flag, and we look forward to feedback from the community as we roll out this feature to ensure stability. This is an opt-in feature currently. Please reach out with any suggestions or issues you find to our Discord or through GitHub issues.