Closed13

【Sui】パッケージのアップグレードを試してみる

おにぎりおにぎり

アップグレードの要件

パッケージをアップグレードするには、パッケージが以下の要件を満たしている必要がある

  • Suiネットワークでは、パッケージを公開すると UpgradeCap を発行し、その UpgradeCap の所有者として UpgradeTicket を発行することができる
  • 変更は、以前のバージョンとレイアウト互換でなければならない
    • 既存の public 関数のシグネチャはそのままでなければならない
    • 既存の構造体レイアウト(構造体能力を含む)はそのままでなければならない
    • 新しい構造体や関数を追加することができる
    • 既存の関数(public またはそれ以外)からジェネリック型制約を削除することができる
    • 関数の実装を変更することはできる
    • publick(package) 関数のシグネチャや entry 関数のシグネチャなど、 public でない関数シグネチャを変更することができる
おにぎりおにぎり

気になっている点

要件を見ればできることできないことは大体わかるが、1つ気になっていることがあるため検証する

「アップグレード前に発行するNFTと、アップグレード後に発行するNFTのpackage idは変わってしまうのか?」

という点だ。具体的には、

OnigiriNFT というNFTがあったとして、アプグレード前に1つ作成し、アップグレード後に2つ目を作成したときに、

0xaaa::nft::OnigiriNFT
0xbbb::nft::OnigiriNFT

のように、package idが異なるOnigiriNFTが存在してしまうのか?ということが気になったので検証したい

おにぎりおにぎり

まずは空のパッケージを作成する

まずは下記コマンドで空のパッケージを作成する

sui move new upgrade_test

初期状態のコード

Move.toml

[package]
name = "upgrade_test"
edition = "2024.beta"

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

[addresses]
upgrade_test = "0x0"

[dev-dependencies]

[dev-addresses]

mint_nft.move

module upgrade_test::mint_nft;
おにぎりおにぎり

シンプルなNFTの定義とミントの関数を作成する

mint_nft.move

module upgrade_test::mint_nft;

public struct OnigiriNFT has key, store {
    id: UID,
}

entry fun mint(ctx: &mut TxContext) {
    transfer::public_transfer(OnigiriNFT {
        id: object::new(ctx),
    }, ctx.sender());
}

OnigiriNFT: NFTオブジェクトの構造体
mint: NFTを作成し送信する関数

おにぎりおにぎり

パッケージを"アップグレード"する (失敗ver)

本題に入る前に、アップグレード要件を確認したいため、invalidなコードでアップグレードを試してみる

module upgrade_test::mint_nft;

public struct OnigiriNFT has key, store {
    id: UID,
    number: u64 // added invalid code
}

entry fun mint(ctx: &mut TxContext) {
    transfer::public_transfer(OnigiriNFT {
        id: object::new(ctx),
        number: 1
    }, ctx.sender());
}

OnigiriNFTnumber というフィールドを追加してみる。構造体の変更は要件を満たしていないため、エラーになるはずだ

アップグレードは下記のコマンドで実行できる。 <UPGRADE-CAP-ID> はパッケージ公開時に発行されているのでそれを指定する

sui client upgrade --upgrade-capability <UPGRADE-CAP-ID>

実際に実行した結果がこちら

sui client upgrade --upgrade-capability 0x182b8b02f9eab8bf431c09bc76417330e9b6ffc5ba296b8ea3903831fddb2638
[warn] Client/Server api version mismatch, client api version : 1.30.1, server api version : 1.31.1
UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING upgrade_test
Successfully verified dependencies on-chain against source.
Error executing transaction 'GQLum3QUXgZ8L1GV1b2roiMhHaMJMorR8qpWfKjd9jLP': PackageUpgradeError { upgrade_error: IncompatibleUpgrade } in command 1

PackageUpgradeError { upgrade_error: IncompatibleUpgrade } ということで、互換性がないアップグレードをしようとしているよ!ということで怒られた

おにぎりおにぎり

パッケージを"アップグレード"する (本題)

以下のようにコードを変更してみた

module upgrade_test::mint_nft;

public struct OnigiriNFT has key, store {
    id: UID,
}

public struct SushiNFT has key, store {
    id: UID,
}

entry fun mint(ctx: &mut TxContext) {
    transfer::public_transfer(OnigiriNFT {
        id: object::new(ctx),
    }, ctx.sender());

    transfer::public_transfer(SushiNFT {
        id: object::new(ctx),
    }, ctx.sender());
}
  • SushiNFT 構造体の追加: 構造体の追加は可能
  • mint 関数の実装の変更: 関数のシグネチャの変更はできないが、実装の変更は可能
    • OnigiriNFTだけでなくSushiNFTも作成するように変更してみた

この状態で sui client upgrade --upgrade-capability <UPGRADE-CAP-ID> を実行すると成功!

Versionがそれぞれ1と2になっていることがわかった。
このVersionの値をコード内でうまく管理すると、例えば最新のVersionのコードしか動かないようにすることができるらしい。現状はVersion管理していないので、1と2の両方のコントラクトが動くようになっている

おにぎりおにぎり

検証結果

0xafda68421613777765b629b16432675a5c981675d3152c590a772756aff5c4c6::mint_nft::OnigiriNFT
0xcafe84933135f2bfded1baccbe5cf6c53780ab630bfa82a502f5c3589993a84a::mint_nft::SushiNFT

  • 1つ目のパッケージで定義したNFTは、1つ目のpackage idのままである
  • 2つ目のパッケージで定義したNFTは、2つ目のpackage idが割り当てられる

つまり、最初の疑問点であった「package idが異なるOnigiriNFTが存在してしまうのか?」は否であることがわかった!

ShizukuShizuku

既存のパッケージにinit関数を追記してアップグレードしようとすると下記のようなエラーが出てIDは一致しているにも関わらずアップグレードできないようです。
PackageUpgradeError { upgrade_error: PackageIDDoesNotMatch

おにぎりおにぎり

検証ありがとうございます! init 関数の変更はできないみたいですね
ドキュメントにも記載がありました💡

https://docs.sui.io/concepts/sui-move-concepts/packages/upgrade#upgrade-considerations

例えば、モジュールのイニシャライザはパッケージのアップグレードでは再実行されません。 最初のパッケージを公開するとき、Moveはあなたがパッケージ用に定義したinit関数を、公開イベントの時に一度だけ(そして一度だけ)実行します。 それ以降のバージョンのパッケージに含まれる可能性のあるinit関数は無視されます。

このスクラップは7日前にクローズされました