【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を作成し送信する関数

パッケージを"公開"する
下記のコマンドでパッケージを公開する
sui client publish
- 公開時のトランザクション
-
package idは 0xafda68421613777765b629b16432675a5c981675d3152c590a772756aff5c4c6
-
0x2::package::UpgradeCap
が発行されていることがわかる

NFTをミントする
packageの画面から mint
を実行する
mint時のトランザクション
-
0xafda68421613777765b629b16432675a5c981675d3152c590a772756aff5c4c6::mint_nft::OnigiriNFT
が作成されていることがわかる。package idは当然だが公開時のidと同じ

パッケージを"アップグレード"する (失敗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());
}
OnigiriNFT
に number
というフィールドを追加してみる。構造体の変更は要件を満たしていないため、エラーになるはずだ
アップグレードは下記のコマンドで実行できる。 <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>
を実行すると成功!
-
アップグレード時のトランザクション
https://testnet.suivision.xyz/txblock/6nbNLeTUkNJUZ1ZaAipUz66cZqsq8pDrgLWCBLyBxJiK?tab=Changes -
0xcafe84933135f2bfded1baccbe5cf6c53780ab630bfa82a502f5c3589993a84a というpackage idで
Published
されていることがわかる
-
最初のパッケージとアップグレードのパッケージの比較
Versionがそれぞれ1と2になっていることがわかった。
このVersionの値をコード内でうまく管理すると、例えば最新のVersionのコードしか動かないようにすることができるらしい。現状はVersion管理していないので、1と2の両方のコントラクトが動くようになっている

アップグレード後のパッケージでNFTをミントする
新しいパッケージでmintを実行する
-
mint時のトランザクション
https://testnet.suivision.xyz/txblock/96ywyASGJRWwJjrsXruPz5pfhSSTCYjs996o5RLvdK7X?tab=Changes -
OnigiriNFTとSushiNFTが作成されていることがわかる。そして、肝心のpackage idを見ると....??

検証結果
0xafda68421613777765b629b16432675a5c981675d3152c590a772756aff5c4c6::mint_nft::OnigiriNFT
0xcafe84933135f2bfded1baccbe5cf6c53780ab630bfa82a502f5c3589993a84a::mint_nft::SushiNFT
- 1つ目のパッケージで定義したNFTは、1つ目のpackage idのままである
- 2つ目のパッケージで定義したNFTは、2つ目のpackage idが割り当てられる
つまり、最初の疑問点であった「package idが異なるOnigiriNFTが存在してしまうのか?」は否であることがわかった!

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

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