Sui Release .28 Migration Guide

Sui release .28 introduces many Breaking changes that require updates or changes to your app or implementation to continue functioning as expected. This guide provides migration steps to help you make the required updates. It also includes descriptions of some new features included in the release.

Sui Move updates

The steps in this section provide guidance on making updates related to the changes to Sui Move.

Sui Framework split into two Move packages

Updated 3/27/23

The Sui Framework (sui-framework) Move package contains modules central to Sui Move, including object, transfer and dynamic_field. The framework package is often a dependency of application packages developed on Sui. Previously, sui-framework included a governance folder that defined a number of modules related to the operations of Sui’s system, such as validator_set and staking_pool. These modules are fundamentally different from the other library modules within the framework, and are not commonly used by developers. To simplify, this release splits sui-framework into two Sui Move packages to improve modularity, usability and upgrading.

In PR 9618, the sui-framework crate now contains three Move packages in the packages directory: move-stdlib, sui-framework and sui-system:

  • sui-system contains modules that were in the sui-framework/sources/governance directory, including all the validator management and staking related functions, published at 0x3 with named address sui_system.
  • sui-framework contains all other modules that were not in the governance folder. The framework provides library and utility modules for Sui Move developers. It is still published at 0x2 with named address sui.
  • move-stdlib contains a copy of the Move standard library that used to be in the sui-framework/deps folder. It is still published at 0x1 with named address std.

If you develop Sui Move code depending on sui-framework, the Move.toml file of your Move package has to change to reflect the path changes:

Prior to this release:

[package]
name = "Example"
version = "0.0.1"
published-at = "0x42"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir="crates/sui-framework/", rev = "devnet" }

[addresses]
example = "0x42"

Updated for this release:

[package]
name = "Example"
version = "0.0.1"
published-at = "0x42"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir="crates/sui-framework/packages/sui-framework/", rev = "devnet" }

[addresses]
example = "0x42"

If your Sui Move code uses a module in the governance folder:

genesis.move, sui_system.move, validator_cap.move, voting_power.move, stake_subsidy.move, sui_system_state_inner.move, validator_set.move, staking_pool.move, validator.move, or validator_wrapper.move

The modules are now in the sui-system package. You must list SuiSystem as a dependency, and access these modules via 0x3 or the sui_system named address.

Erecover and verify

In this release, ecdsa_k1::ecrecover and ecdsa_k1::secp256k1_verify now require you to input the raw message instead of a hashed message.

  • ecdsa_k1::ecrecover(sig, hashed_msg, hash_function) is updated to ecdsa_k1::secp256k1_ecrecover(sig, msg, hash_function)

  • ecdsa_k1::secp256k1_verify(sig, pk, hashed_msg) is updated to ecdsa_k1::secp256k1_verify(sig, pk, msg, hash_function)

When you call these APIs, you must provide the raw message instead of the hashed message for verification or EC recover. You must also provide the hash_function name represented by u8. See the source code for more information:

ID Leak

UIDs must now be statically deemed “fresh” when you construct an object. This means that the UID must come from object::new (or test_scenario::new_object during tests). To migrate an existing project, any function that previously took a UID as an argument to construct an object now requires the TxContext to generate new IDs.

For example, prior to release .28, to construct an object:

fun new(id: UID): Counter {
    Counter { id, count: 0 }
}

With release .28, to construct an object:

fun new(ctx: &mut TxContext): Counter {
    Counter { id: object::new(ctx), count: 0 }
}

Publisher

This is not a breaking change, but Publisher is an important addition that can be helpful. The Publisher object requires an OTW and can be claimed in any module within a package via the sui::package::claim call:

module example::dummy {
    use sui::package;
    use sui::tx_context::TxContext;

    struct DUMMY has drop {}

    fun init(otw: DUMMY, ctx: &mut TxContext) {
        // creates a Publisher object and sends to the `sender`
        package::claim_and_keep(otw, ctx)
    }
}

To learn more about Publisher, see Publisher.

Sui Object Display Standard

This release includes the Sui Object Display standard, a new way to describe objects of a single type using a set of named templates to standardize their display off-chain. The Sui API also supports the new standard.

To read a detailed description and the motivation behind the standard, see the Sui Object Display proposal.

In Sui Move, to claim a Display object, call display::new<T>(&Publisher). As stated in the signature, it requires the Publisher object. Once acquired, Display can be modified by adding new fields (templates) to it, and when it’s ready to be published, a display::update_version(&mut Display) call is required to publish and make it available. All further additions / edits in the Display should also be applied by calling update_version again.

Fields that we suggest to use in Display are:

  • name: a displayable name
  • link: a link to an object in an application / external link
  • description: a broader displayable description
  • image_url: an URL or a blob with an image
  • project_url: a link to a website
  • creator: mentions the creator in any way (text, link, address etc)

See additional information and examples in Display.

API and SDK changes

The steps in this section provide guidance on making updates related to the changes to Sui APIs and SDKs.

Reading objects

The sui_getObject endpoint now takes an additional configuration parameter of type SuiObjectDataOptions to control which fields the endpoint retrieves. By default, the endpoint retrieves only object references unless the client request explicitly specifies other data, such as typeowner, or bcs.

TypeScript Migration

import { JsonRpcProvider } from "@mysten/sui.js";
const provider = new JsonRpcProvider();

// Prior to release .28
const txn = await provider.getObject(
  "0xcff6ccc8707aa517b4f1b95750a2a8c666012df3"
);
const txns = await provider.getObjectBatch([
  "0xcff6ccc8707aa517b4f1b95750a2a8c666012df3",
  "0xdff6ccc8707aa517b4f1b95750a2a8c666012df3",
]);

// Updated for release .28
const txn = await provider.getObject({
  id: "0xcff6ccc8707aa517b4f1b95750a2a8c666012df3",
  // fetch the object content field and display
  options: {
    showContent: true,
    showDisplay: true,
  },
});
const txns = await provider.multiGetObjects({
  ids: [
    "0xcff6ccc8707aa517b4f1b95750a2a8c666012df3",
    "0xdff6ccc8707aa517b4f1b95750a2a8c666012df3",
  ],
  // only fetch the object type
  options: { showType: true },
});

JSON RPC Migration

    # Prior to release .28
    curl --location --request POST 'https://fullnode.devnet.sui.io:443' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "sui_getObject",
        "params": {
            "object_id": "0x08240661f5504c9bb4a487d9a28e7e9d6822abf692801f2a750d67a44d0b2340",
        }
    }'

    # Updated for release .28

    curl --location --request POST 'https://fullnode.devnet.sui.io:443' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "sui_getObject",
        "params": {
            "object_id": "0x08240661f5504c9bb4a487d9a28e7e9d6822abf692801f2a750d67a44d0b2340",
            "options": {
                "showContent": true,
                            "showOwner": true,
            }
        }
    }'

    # If you use sui_getRawObject, enable the showBcs option to retrieve it
    curl --location --request POST 'https://fullnode.devnet.sui.io:443' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "sui_getObject",
        "params": {
            "object_id": "0x08240661f5504c9bb4a487d9a28e7e9d6822abf692801f2a750d67a44d0b2340",
            "options": {
                "showBcs": true
            }
        }
    }'

Display

To get a Display for an object, pass an additional flag to the sui_getObject call.

{
  showDisplay: true;
}

The returned value is the processed template for a type. For example, for Sui Capys it could be:

{
  "name": "Capy - one of many",
  "description": "Join our Capy adventure",
  "link": "https://capy.art/capy/0x00000000....",
  "image_url": "https://api.capy.art/capys/0x000adadada..../svg",
  "project_url": "https://capy.art/",
  "creator": "Capybara Lovers"
}

Reading transactions

The sui_getTransactionBlockand sui_multiGetTransaction functions now take an additional optional parameter called options. Use options to specify which fields to retrieve, such as transaction, effects, or events. By default, it returns only the transaction digest.

import { JsonRpcProvider } from "@mysten/sui.js";
const provider = new JsonRpcProvider();

// Prior to release .28
const provider = new JsonRpcProvider();
const txn = await provider.getTransactionWithEffects(
  "6mn5W1CczLwitHCO9OIUbqirNrQ0cuKdyxaNe16SAME="
);
// You can also fetch multiple transactions in one batch request
const txns = await provider.getTransactionWithEffectsBatch([
  "6mn5W1CczLwitHCO9OIUbqirNrQ0cuKdyxaNe16SAME=",
  "7mn5W1CczLwitHCO9OIUbqirNrQ0cuKdyxaNe16SAME=",
]);

// Updated for release .28
const provider = new JsonRpcProvider();
const txn = await provider.getTransactionBlock({
  digest: "6mn5W1CczLwitHCO9OIUbqirNrQ0cuKdyxaNe16SAME=",
  // only fetch the effects field
  options: { showEffects: true },
});
// You can also fetch multiple transactions in one batch request
const txns = await provider.multiGetTransactionBlocks({
  digests: [
    "6mn5W1CczLwitHCO9OIUbqirNrQ0cuKdyxaNe16SAME=",
    "7mn5W1CczLwitHCO9OIUbqirNrQ0cuKdyxaNe16SAME=",
  ],
  // fetch both the input transaction data as well as effects
  options: { showInput: true, showEffects: true },
});

Reading Events

This release makes the following changes related to reading events:

  • Removes System events such as Publish, TransferObject, NewObject and keeps only MoveEvents.
  • Adds an object_changes and balance_changes field in SuiTransactionBlockResponse
import { JsonRpcProvider } from "@mysten/sui.js";
const provider = new JsonRpcProvider();

// Prior to release .28
provider.getEvents({ Sender: toolbox.address() }, null, 2);

// Updated for release .28
const events = provider.queryEvents({
  query: { Sender: toolbox.address() },
  limit: 2,
});

// Subscribe events

// Prior to release .28
const subscriptionId = await provider.subscribeEvent(
  { SenderAddress: "0xbff6ccc8707aa517b4f1b95750a2a8c666012df3" },
  (event: SuiEventEnvelope) => {
    // handle subscription notification message here. This function is called once per subscription message.
  }
);

// later, to unsubscribe
// calls RPC method 'sui_unsubscribeEvent' with params: [ subscriptionId ]
const subFoundAndRemoved = await provider.unsubscribeEvent(subscriptionId);

// Updated for release .28
// calls RPC method 'sui_subscribeEvent' with params:
// [ { Sender: '0xbff6ccc8707aa517b4f1b95750a2a8c666012df3' } ]
const subscriptionId = await provider.subscribeEvent({
  filter: { Sender: "0xbff6ccc8707aa517b4f1b95750a2a8c666012df3" },
  onMessage(event: SuiEvent) {
    // handle subscription notification message here. This function is called once per subscription message.
  },
});

// later, to unsubscribe
// calls RPC method 'sui_unsubscribeEvent' with params: [ subscriptionId ]
const subFoundAndRemoved = await provider.unsubscribeEvent({
  id: subscriptionId,
});

Pagination

This release changes the Page definition.

Prior to release .28

    pub struct Page<T, C> {
        pub data: Vec<T>,
        pub next_cursor: Option<C>,
    }

Updated for release .28

    pub struct Page<T, C> {
        pub data: Vec<T>,
        pub next_cursor: Option<C>,
        pub has_next_page: bool,
    }

Additionally:

  • next_cursor is no longer inclusive and now exclusive, it always points to the last item of data if data is not empty;
  • To check if the current page is the last page or not, instead of doing next_cursor.is_none(), now you can simply use has_next_page

If you use Page to read pages one by one, now you do not have to manually handle the returned None value of next_cursor when the reading process hits the latest page, instead you can always use the returned next_cursor as the input argument of next read. Before this release, the reading process will start from genesis when it hits latest and the None value is not properly handled.

Building and executing transaction

The previous transaction builder methods on the Signer, and the SignableTransaction interface have been removed, and replaced with a new Transaction builder class. This new transaction builder takes full advantage of Programmable Transactions.

// Construct a new transaction:
const tx = new Transaction();

// Example replacement for a SUI token transfer:
const [coin] = tx.splitCoins(tx.gas, [tx.pure(1000)]);
tx.transferObjects([coin], tx.pure(keypair.getPublicKey().toSuiAddress()));

// Merge a list of coins into a primary coin:
tx.mergeCoin(tx.object("0xcoinA"), [
  tx.object("0xcoinB"),
  tx.object("0xcoinC"),
]);

// Make a move call:
tx.moveCall({
  target: `${packageObjectId}::nft::mint`,
  arguments: [tx.pure("Example NFT")],
});

// Execute a transaction:
const result = await signer.signAndExecuteTransaction({ transaction: tx });

Transaction now support providing a list of gas coins as payment for a transaction. By default, the transaction builder automatically determines the gas budget and coins to use as payment for a transaction. You can also set these values, for example to set your own budget, change the gas price, or do your own gas selection:

// Set an explicit gas price. By default, uses the current reference gas price:
tx.setGasPrice(100);
// Change the gas budget (in SUI). By default, this executes a dry run and uses the gas consumed from that as the budget.
tx.setGasBudget(customBudgetDefined);
// Set the vector of gas objects to use as the gas payment.
tx.setGasPayment([coin1, coin2]);

Staking changes

The steps in this section provide guidance on making updates related to the changes to Sui staking.

Locked coin staking removed

Previously users could stake either their Coin<SUI> or their LockedCoin<SUI> with a validator. This release removes support for staking locked coins so stake functions can now take only Coin<SUI>.

StakedSui object layout changed

Prior to this release, StakedSui struct had the following definition:

    struct StakedSui has key {
        id: UID,
        /// The validator we are staking with.
        validator_address: address,
        /// The epoch at which the staking pool started operating.
        pool_starting_epoch: u64,
        /// The epoch at which the delegation is requested.
        delegation_request_epoch: u64,
        /// The staked SUI tokens.
        principal: Balance<SUI>,
        /// If the stake comes from a Coin<SUI>, this field is None. If it comes from a LockedCoin<SUI>, this
        /// field will record the original lock expiration epoch, to be used when unstaking.
        sui_token_lock: Option<EpochTimeLock>,
    }

With the removal of locked coin staking and changes to the Sui staking flow, the new struct definition is:

    struct StakedSui has key {
        id: UID,
        /// ID of the staking pool we are staking with.
        pool_id: ID,
        /// The validator we are staking with.
        validator_address: address,
        /// The epoch at which the stake becomes active.
        stake_activation_epoch: u64,
        /// The staked SUI tokens.
        principal: Balance<SUI>,
    }

Changes to stake deposit / withdraw APIs

This release includes the following changes related to stake deposit and withdrawal requests:

  • Removes the request_switch_delegation function
  • Renames all delegation functions to use staking instead of delegation.

Prior to release .28, the function names were:

    /// Add delegated stake to a validator's staking pool using multiple coins and amount.
    #[method(name = "requestAddDelegation")]
    async fn request_add_delegation(
        &self,
        /// the transaction signer's Sui address
        signer: SuiAddress,
        /// Coin<SUI> or LockedCoin<SUI> object to delegate
        coins: Vec<ObjectID>,
        /// delegation amount
        amount: Option<u64>,
        /// the validator's Sui address
        validator: SuiAddress,
        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
        gas: Option<ObjectID>,
        /// the gas budget, the transaction will fail if the gas cost exceed the budget
        gas_budget: u64,
    ) -> RpcResult<TransactionBlockBytes>;

    /// Withdraw a delegation from a validator's staking pool.
    #[method(name = "requestWithdrawDelegation")]
    async fn request_withdraw_delegation(
        &self,
        /// the transaction signer's Sui address
        signer: SuiAddress,
        /// Delegation object ID
        delegation: ObjectID,
        /// StakedSui object ID
        staked_sui: ObjectID,
        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
        gas: Option<ObjectID>,
        /// the gas budget, the transaction will fail if the gas cost exceed the budget
        gas_budget: u64,
    ) -> RpcResult<TransactionBlockBytes>;

    /// Switch delegation from the current validator to a new one.
    #[method(name = "requestSwitchDelegation")]
    async fn request_switch_delegation(
        &self,
        /// the transaction signer's Sui address
        signer: SuiAddress,
        /// Delegation object ID
        delegation: ObjectID,
        /// StakedSui object ID
        staked_sui: ObjectID,
        /// Validator to switch to
        new_validator_address: SuiAddress,
        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
        gas: Option<ObjectID>,
        /// the gas budget, the transaction will fail if the gas cost exceed the budget
         gas_budget: u64,
    ) -> RpcResult<TransactionBlockBytes>;

Effective with release .28, the function names are:

    /// Add stake to a validator's staking pool using multiple coins and amount.
    #[method(name = "requestAddStake")]
    async fn request_add_stake(
        &self,
        /// the transaction signer's Sui address
        signer: SuiAddress,
        /// Coin<SUI> object to stake
        coins: Vec<ObjectID>,
        /// stake amount
        amount: Option<u64>,
        /// the validator's Sui address
        validator: SuiAddress,
        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
        gas: Option<ObjectID>,
        /// the gas budget, the transaction will fail if the gas cost exceed the budget
        gas_budget: u64,
    ) -> RpcResult<TransactionBlockBytes>;

    /// Withdraw stake from a validator's staking pool.
    #[method(name = "requestWithdrawStake")]
    async fn request_withdraw_stake(
        &self,
        /// the transaction signer's Sui address
        signer: SuiAddress,
        /// Delegation object ID
        delegation: ObjectID,
        /// StakedSui object ID
        staked_sui: ObjectID,
        /// gas object to be used in this transaction, node will pick one from the signer's possession if not provided
        gas: Option<ObjectID>,
        /// the gas budget, the transaction will fail if the gas cost exceed the budget
        gas_budget: u64,
    ) -> RpcResult<TransactionBlockBytes>;

Changes to getDelegatedStakes

The getDelegatedStakes function has been renamed to getStakes. The getStakes function returns all of the stakes for an address grouped by validator staking pools, as well as the estimated staking rewards earned so far:

    {
        "jsonrpc": "2.0",
        "result": [
            {
                "validatorAddress": "0x8760b337dcb641811414daff8f98e6824caf7e5ca28530c4248557057ddc9004",
                "stakingPool": "0x628ffd0e51e9a6ea32c13c2739a31a8f344b557d3429e057b377a9c499b9bb13",
                "stakes": [
                    {
                        "stakedSuiId": "0xa3cc3319d355dc92afee3669cd8f545de98c5ee380b6e1275b891bebdd82ad28",
                        "stakeRequestEpoch": 3,
                        "stakeActiveEpoch": 4,
                        "principal": 99999999998977,
                        "status": "Active",
                        "estimatedReward": 998
                    }
                ]
            },
            {
                "validatorAddress": "0x6b34f8d6d70676db526017b03ec35f8f74ec67ee10426e2a3049a42045c90913",
                "stakingPool": "0xd5d9aa879b78dc1f516d71ab979189086eff752f65e4b0dea15829e3157962e1",
                "stakes": [
                    {
                        "stakedSuiId": "0x5ee438610276e8fcfe0c0615caf4a2ca7c408569f47c5a622be58863c35b357b",
                        "stakeRequestEpoch": 2,
                        "stakeActiveEpoch": 3,
                        "principal": 100000000000000,
                        "status": "Active",
                        "estimatedReward": 1998
                    },
                    {
                        "stakedSuiId": "0x9eac8bd615977f8d635dc2054d13c463829161be41425d701ba8f9a444ca69e9",
                        "stakeRequestEpoch": 5,
                        "stakeActiveEpoch": 6,
                        "principal": 100000000000000,
                        "status": "Pending"
                    }
                ]
            }
        ],
        "id": 1
    }

Add getStakesByIds function

With the new getStakesByIds it's possible to query the delegated stakes using a vector of staked SUI IDs. The function returns all of the stakes queried, grouped by validator staking pools, as well as the estimated staking rewards earned so far.

Secp256k1 derive keypair

Match Secp256k1.deriveKeypair with Ed25519 on a function signature takes in a mnemonics string and an optional path string instead of a required path string and a mnemonics string. See PR 8542 for details.

Last update 3/28/2023, 2:02:51 AM

Contributor(s)