DubheSuiTutorialsContract Migration

Use dubhe to migrate a contract

⚠️ This tutorial uses the Dubhe v1 API (Schema, counter_schema, dapp__version, etc.) and is no longer accurate for Dubhe v2. In v2, storage is split into DappStorage and UserStorage; version bumping is handled automatically by dubhe upgrade --bump-version; and migrate_to_vN is generated by the toolchain rather than written by hand.

For the current upgrade workflow, see Contract Upgrading.


Because there is always the possibility of bugs or new logic being added to a written contract, it becomes necessary to upgrade the contract.

This section describes how to use Dubhe for versioning and upgrading contracts. Take the 101 contract as an example of a contract upgrade

Setting up a project

➜  pnpm create dubhe
Downloading registry.npmjs.org/create-dubhe/0.0.11: 8.98 MB/8.98 MB, done
../Library/pnpm/store/v3/tmp/dlx-54343   | +149 +++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: /Library/pnpm/store/v3
  Virtual store is at:             ../Library/pnpm/store/v3/tmp/dlx-54343/node_modules/.pnpm
../Library/pnpm/store/v3/tmp/dlx-54343   | Progress: resolved 149, reused 146, downloaded 3, added 149, done
βœ” Input your projectName. … dubhe-template-project
βœ” Pick your chain. β€Ί sui
βœ” Pick your platform. β€Ί 101
 
============================================================
πŸŽ‰ Project creation successful!
πŸ“ Project location: /Project/dubhe-template-project
------------------------------------------------------------
Next steps:
 
  cd dubhe-template-project
  pnpm install
 
============================================================
cd dubhe-template-project && pnpm install

Start a Local Node

➜  pnpm dubhe node
 
Welcome to Dubhe
                         --from team@obelisk
   ________  ___  ___  ________  ___  ___  _______
  |\   ___ \|\  \|\  \|\   __  \|\  \|\  \|\  ___ \
  \ \  \_|\ \ \  \\\  \ \  \|\ /\ \  \\\  \ \   __/|
   \ \  \ \\ \ \  \\\  \ \   __  \ \   __  \ \  \_|/__
    \ \  \_\\ \ \  \\\  \ \  \|\  \ \  \ \  \ \  \_|\ \
     \ \_______\ \_______\ \_______\ \__\ \__\ \_______\
      \|_______|\|_______|\|_______|\|__|\|__|\|_______|
 
 
πŸš€ Starting Local Node...
  β”œβ”€ Faucet: Enabled
  └─ Force Regenesis: Yes
  └─ HTTP server: http://127.0.0.1:9000/
  └─ Faucet server: http://127.0.0.1:9123/
πŸ“Accounts
==========
  β”Œβ”€ Account #0: 0xe7f93ad7493035bcd674f287f78526091e195a6df9d64f23def61a7ce3adada9(1000 SUI)
  └─ Private Key: suiprivkey1qq3ez3dje66l8pypgxynr7yymwps6uhn7vyczespj84974j3zya0wdpu76v
  β”Œβ”€ Account #1: 0x492404a537c32b46610bd6ae9f7f16ba16ff5a607d272543fe86cada69d8cf44(1000 SUI)
  └─ Private Key: suiprivkey1qp6vcyg8r2x88fllmjmxtpzjl95gd9dugqrgz7xxf50w6rqdqzetg7x4d7s
  β”Œβ”€ Account #2: 0xd27e203483700d837a462d159ced6104619d8e36f737bf2a20c251153bf39f24(1000 SUI)
  └─ Private Key: suiprivkey1qpy3a696eh3m55fwa8h38ss063459u4n2dm9t24w2hlxxzjp2x34q8sdsnc
  β”Œβ”€ Account #3: 0x018f1f175c9b6739a14bc9c81e7984c134ebf9031015cf796fefcef04b8c4990(1000 SUI)
  └─ Private Key: suiprivkey1qzxwp29favhzrjd95f6uj9nskjwal6nh9g509jpun395y6g72d6jqlmps4c
  β”Œβ”€ Account #4: 0x932f6aab2bc636a25374f99794dc8451c4e27c91e87083e301816ed08bc98ed0(1000 SUI)
  └─ Private Key: suiprivkey1qzhq4lv38sesah4uzsqkkmeyjx860xqjdz8qgw36tmrdd5tnle3evxpng57
  β”Œβ”€ Account #5: 0x9a66b2da3036badd22529e3de8a00b0cd7dbbfe589873aa03d5f885f5f8c6501(1000 SUI)
  └─ Private Key: suiprivkey1qzez45sjjsepjgtksqvpq6jw7dzw3zq0dx7a4sulfypd73acaynw5jl9x2c
==========
⚠️WARNING: These accounts, and their private keys, are publicly known.
Any funds sent to them on Mainnet or any other live network WILL BE LOST.
 

Check the current main code

import { DubheConfig, storage } from '@0xobelisk/sui-common';
 
export const dubheConfig = {
  name: 'counter',
  description: 'counter contract',
  schemas: {
    value: storage('u32')
  },
  events: {
    increment: { value: 'u32' }
  },
  errors: {
    invalid_increment: "Number can't be incremented, must be more than 0"
  }
} as DubheConfig;
module counter::counter_system {
    use counter::counter_schema::Schema;
    use counter::counter_events::increment_event;
    use counter::counter_errors::invalid_increment_error;
 
    public entry fun inc(schema: &mut Schema, number: u32) {
        // Check if the increment value is valid.
        invalid_increment_error(number > 0 && number < 100);
        let value = schema.value()[];
        schema.value().set(value + number);
        increment_event(number);
    }
}
module counter::counter_migrate {
  use counter::counter_schema::Schema;
  const ON_CHAIN_VERSION: u32 = 1;
 
  public fun on_chain_version(): u32 {
    ON_CHAIN_VERSION
  }
}

Add version checking code

dubhe.config.ts
import { DubheConfig, storage } from '@0xobelisk/sui-common';
 
export const dubheConfig = {
  name: 'counter',
  description: 'counter contract',
  schemas: {
    value: storage('u32')
  },
  events: {
    increment: { value: 'u32' }
  },
  errors: {
    invalid_increment: "Number can't be incremented, must be more than 0",
    invalid_version: 'The version must be up to date'
  }
} as DubheConfig;

Execute the generate command

pnpm dubhe generate

Determine if the version is up to date

counter_system.move
module counter::counter_system {
    use counter::counter_schema::Schema;
    use counter::counter_events::increment_event;
    use counter::counter_errors::{invalid_increment_error, invalid_version_error};
    use counter::counter_migrate::on_chain_version;
 
    public entry fun inc(schema: &mut Schema, number: u32) {
        // Check if it is the latest version
        invalid_version_error(schema.dapp__version()[] == on_chain_version());
        // Check if the increment value is valid.
        invalid_increment_error(number > 0 && number < 100);
        let value = schema.value()[];
        schema.value().set(value + number);
        increment_event(number);
    }
}
➜  pnpm dubhe publish
 
πŸš€ Starting Contract Publication...
  β”œβ”€ Project: /Volumes/project/dubhe/packages/sui-cli/contracts/dubhe-framework
  β”œβ”€ Network: localnet
  └─ Account: 0x018f1f175c9b6739a14bc9c81e7984c134ebf9031015cf796fefcef04b8c4990
 
πŸ“¦ Building Contract...
 
πŸ”„ Publishing Contract...
  β”œβ”€ Package ID: 0x47948f7ec4622cbf3ec0fa7f2621a55980587102bf928d39d8bc03100ef03b4e
  β”œβ”€ Upgrade Cap: 0x86048d1548551a6a025c49c6e50fc71eacd4057064299fa8ffdd45db39f436fd
  └─ Transaction: A5UXiyLjjQ7d8G2vGsys31zUvZx2zVKvFCfSAdJ7ZWG
Update deploy log: /Volumes/project/dubhe/packages/sui-cli/contracts/dubhe-framework/.history/sui_localnet/latest.json
 
βœ… Dubhe Framework deployed successfully
Updated Dubhe dependency in /Volumes/project/dubhe/packages/sui-cli/contracts/counter/Move.toml for localnet.
 
πŸš€ Starting Contract Publication...
  β”œβ”€ Project: /Volumes/project/dubhe/packages/sui-cli/contracts/counter
  β”œβ”€ Network: localnet
  β”œβ”€ ChainId: 90dbabed
  β”œβ”€ Validating Environment...
  └─ Account: 0x018f1f175c9b6739a14bc9c81e7984c134ebf9031015cf796fefcef04b8c4990
 
πŸ“¦ Building Contract...
 
πŸ”„ Publishing Contract...
  β”œβ”€ Processing publication results...
  β”œβ”€ Upgrade Cap: 0x20bcea3558938d4c1b03a8cab5d26ec9656f90a82fa49397d8ce6123573608d6
  β”œβ”€ Package ID: 0xd45e866f9d87b197913c10dcc1b16c311787a267447d9ded36c96bbca1534f33
  └─ Transaction: 8jSgk1dNCuaG1eBFRPFg69Jstip9PKi168b3AXEdEn4t
 
⚑ Executing Deploy Hook...
  β”œβ”€ Hook execution successful
  β”œβ”€ Transaction: 54MNe2PRRoQHSuCCQ3jvjTHZQYjuB9NFcxv9pg8KysSY
 
πŸ“‹ Created Schemas:
  β”œβ”€ 0xd45e866f9d87b197913c10dcc1b16c311787a267447d9ded36c96bbca1534f33::counter_schema::Schema
     └─ ID: 0x9b884efc078de5f4ca9aeefa7203fa34b29be535f80b84c905549fe4b067c279
Update deploy log: /Volumes/project/dubhe/packages/sui-cli/contracts/counter/.history/sui_localnet/latest.json
 
βœ… Contract Publication Complete
 

Migrate a contract

Migrating contracts need to follow the basic guidelines of Sui Move upgrades, otherwise the upgrade will fail.

  1. Generate a new schema: value_v2, used to record the current number and the last number.
  2. Parameter number from 0-100 reduced to 0-10.
  3. Migrate value from the original value schema to the value_v2 schema.

Create a new schema

dubhe.config.ts
import { DubheConfig, storage } from '@0xobelisk/sui-common';
 
export const dubheConfig = {
  name: 'counter',
  description: 'counter contract',
  data: {
    Number: { last_number: 'u32', current_number: 'u32' }
  }
  schemas: {
    value: storage('u32'),
    value_v2: storage('Number'),
  },
  events: {
    increment: { value: 'u32' },
  },
  errors: {
    invalid_increment: "Number can't be incremented, must be more than 0",
    invalid_version: "The version must be up to date"
  },
} as DubheConfig;

Execute the generate command

pnpm dubhe generate

Modify the limitations of the number parameter, deprecate value in favor of value_2

counter_system.move
module counter::counter_system {
    use counter::counter_schema::Schema;
    use counter::counter_events::increment_event;
    use counter::counter_errors::{invalid_increment_error, invalid_version_error};
    use counter::counter_migrate::on_chain_version;
    use counter::counter_number;
 
    public entry fun inc(schema: &mut Schema, number: u32) {
        // Check if it is the latest version
        invalid_version_error(schema.dapp__version()[] == on_chain_version());
        // Check if the increment value is valid.
        invalid_increment_error(number > 0 && number < 10);
        let (_, current_number) = schema.value_v2()[].get();
        let number_v2 = counter_number::new(current_number, current_number + number);
        schema.value_v2().set(number_v2);
        increment_event(number);
    }
}

Writing migration scripts, Please note that the migrated method must have a version number after migrateto,

For example if you migrate from v1 to v2 then your migration method is migrate_to_v2, v2 to v3 is migrate_to_v3

  1. we change ON_CHAIN_VERSION from 1 to 2
  2. second step adds the migrate_to_v2 method
counter_migrate.move
module counter::counter_migrate {
  use counter::counter_schema::Schema;
  use counter::counter_number;
  const ON_CHAIN_VERSION: u32 = 2;
 
  public fun on_chain_version(): u32 {
    ON_CHAIN_VERSION
  }
 
  public entry fun migrate_to_v2(schema: &mut Schema, new_package_id: address, new_version: u32, ctx: &mut TxContext) {
    // This line of code should never be changed, it's the first step in the migration method, and it's followed by the logic you're writing
    schema.upgrade(new_package_id, new_version, ctx);
 
    // Customized Logic
    let current_number = schema.value()[];
    let number = counter_number::new(current_number, current_number);
    schema.value_v2().set(number);
    schema.value().remove();
  }
 
}

After modifying the contract, we upgrade.

➜  pnpm dubhe upgrade
INCLUDING DEPENDENCY Dubhe
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING counter
 
πŸš€ Starting Upgrade Process...
πŸ“‹ OldPackageId: 0xd45e866f9d87b197913c10dcc1b16c311787a267447d9ded36c96bbca1534f33
πŸ“‹ UpgradeCap Object Id: 0x20bcea3558938d4c1b03a8cab5d26ec9656f90a82fa49397d8ce6123573608d6
πŸ“‹ OldVersion: 1
Upgrade Transaction Digest: 4rVGakd166ujDBdz2BwgkPj8X4BwnFBTUZfMu8SF1yBv
counter new PackageId: 0xacdf1875de568829f0a6d4390369b293d3a737bddbf0fa77e16ba161b40896e1
counter new Version: 2
Update deploy log: /Volumes/project/dubhe/packages/sui-cli/contracts/counter/.history/sui_localnet/latest.json
 
πŸš€ Starting Migration Process...
πŸ“‹ Added Fields: {
  "schemaName": "value_v2",
  "fields": "StorageValue<Number>"
}
Migration Transaction Digest: CFsNjaqCWFtx4vWPcH18jb4jBGuBQevJw4U9vkXPaAYA

So far the upgrade steps have been completed. OldPackageId is currently not working properly, newPackageId will work properly according to our newly added logic.