Move on Sui 环境以及项目搭建流程
Sui VsCode 环境配置
- 安装 VsCode 后, 在扩展商店中下载
move-analyzer
插件 - 安装 move-analyzer
- git clone Move存储库并从源代码构建move-analyzer
- 需要先安装 Rust 和 Cargo 环境
curl --proto '=https' --tlsv1.2 -sSf [<https://sh.rustup.rs>](<https://sh.rustup.rs/>) | sh
- 后面需要升级 Rust 版本, 可以使用:
rustup update stable
- Linux 系统需要先确保依赖已经安装
sudo apt-get install curl git-all cmake gcc libssl-dev pkg-config libclang-dev libpq-dev build-essential
- 安装 Sui 二进制文件 (时间有些长, 耐心等待)
cargo install --git https://github.com/move-language/move move-analyzer
- 安装的二进制文件位于
~/.cargo/bin
下
- 需要先安装 Rust 和 Cargo 环境
- git clone Move存储库并从源代码构建move-analyzer
- 目前插件还不完善, 代码提示和代码格式化比较弱
Sui 项目搭建
创建 package
- 执行:
sui move new my_first_package
-
目录结构如下, 包含一个 Move.toml 文件以及 sources 目录
❯ tree my_first_package my_first_package ├── Move.toml └── sources
-
默认的 Move.toml 文件(随着 sui 的更新, 默认文件可能也会随之更新)
-
目前比较坑的点是, 要手动在 [package] 下添加
version = "0.0.1”
, 否则 vscode move 插件无法提供代码提示和跳转❯ cat my_first_package/Move.toml [package] name = "my_first_package" version = "0.0.1" # edition = "2024.alpha" # To use the Move 2024 edition, currently in alpha # license = "" # e.g., "MIT", "GPL", "Apache 2.0" # authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"] [dependencies] Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } # For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`. # Revision can be a branch, a tag, and a commit hash. # MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" } # For local dependencies use `local = path`. Path is relative to the package root # Local = { local = "../path/to" } # To resolve a version conflict and force a specific version for dependency # override use `override = true` # Override = { local = "../conflicting/version", override = true } [addresses] my_first_package = "0x0" # Named addresses will be accessible in Move as `@name`. They're also exported: # for example, `std = "0x1"` is exported by the Standard Library. # alice = "0xA11CE" [dev-dependencies] # The dev-dependencies section allows overriding dependencies for `--test` and # `--dev` modes. You can introduce test-only dependencies here. # Local = { local = "../path/to/dev-build" } [dev-addresses] # The dev-addresses section allows overwriting named addresses for the `--test` # and `--dev` modes. # alice = "0xB0B"
-
定义 module
-
在 sources 目录下新建一个 move 文件:
touch my_first_package/sources/my_module.move
- 内容如下( 现在不需要关心太多语法, 下一篇会实践语法)
module my_first_package::my_module { // Part 1: Imports use sui::object::{Self, UID}; use sui::transfer; use sui::tx_context::{Self, TxContext}; // Part 2: Struct definitions struct Sword has key, store { id: UID, magic: u64, strength: u64, } struct Forge has key, store { id: UID, swords_created: u64, } // Part 3: Module initializer to be executed when this module is published fun init(ctx: &mut TxContext) { let admin = Forge { id: object::new(ctx), swords_created: 0, }; // Transfer the forge object to the module/package publisher transfer::public_transfer(admin, tx_context::sender(ctx)); } // Part 4: Accessors required to read the struct attributes public fun magic(self: &Sword): u64 { self.magic } public fun strength(self: &Sword): u64 { self.strength } public fun swords_created(self: &Forge): u64 { self.swords_created } // Part 5: Public/entry functions (introduced later in the tutorial) // Part 6: Private functions (if any) }
Build package
- 执行: 在 my_first_package 目录的根目录下, 执行:
sui move build
-
成功 build 之后, 会在 console 打印出下方的内容:
UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git INCLUDING DEPENDENCY Sui INCLUDING DEPENDENCY MoveStdlib BUILDING my_first_package
-
测试(test) package
-
使用
#[test]
声明是一个测试 -
在上方提供的代码的基础上, 添加下方的内容:
#[test] public fun test_sword_create() { // Create a dummy TxContext for testing let ctx = tx_context::dummy(); // Create a sword let sword = Sword { id: object::new(&mut ctx), magic: 42, strength: 7, }; // Check if accessor functions return correct values assert!(magic(&sword) == 42 && strength(&sword) == 7, 1); }
-
使用
sui move test
命令执行带有#[test]
标记的函数-
测试成功的结果如下:
BUILDING Sui BUILDING MoveStdlib BUILDING my_first_package Running Move unit tests Test result: OK. Total tests: 0; passed: 0; failed: 0
-
-
篇幅有限, 本文只是带领大家熟悉一遍流程, 更详细的操作参考官方文档: https://docs.sui.io/guides/developer/first-app/build-test
发布(publish) package
- 使用
sui client publish
命令发布一个 package - 使用
sui client call
命令调用 package 中的功能
Debug
- 由于 Move 目前还没有调试器, 因此使用
std::debug
模块进行调试 - 常用操作:
use std::debug;
: 导入调试模块debug::print(&v);
debug::print(v);
: 在 v 已经是引用的情况下, 直接传入 vdebug::print_stack_trace();
: 打印当前堆栈跟踪
客户端应用程序
例如连接钱包, 使用 Sui RPC 查询数据
完整代码
module my_first_package::my_module {
// Part 1: Imports
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
// Part 2: Struct definitions
struct Sword has key, store{
id: UID,
magic: u64,
strength: u64,
}
struct Forge has key, store {
id: UID,
swords_created: u64,
}
// Part 3: Module initializer to be executed when this module is published
fun init(ctx: &mut TxContext) {
let admin = Forge {
id: object::new(ctx),
swords_created: 0,
};
// Transfer the forge object to the module/package publisher
transfer::public_transfer(admin, tx_context::sender(ctx));
}
// Part 4: Accessors required to read the struct attributes
public fun magic(self: &Sword): u64 {
self.magic
}
public fun strength(self: &Sword): u64 {
self.strength
}
public fun swords_created(self: &Forge): u64 {
self.swords_created
}
public fun sword_create(magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) {
use sui::transfer;
// create a sword
let sword = Sword {
id: object::new(ctx),
magic: magic,
strength: strength,
};
// transfer the sword
transfer::transfer(sword, recipient);
}
public fun sword_transfer(sword: Sword, recipient: address, _ctx: &mut TxContext) {
use sui::transfer;
// transfer the sword
transfer::transfer(sword, recipient);
}
public fun new_sword (
forge: &mut Forge,
magic: u64,
strength: u64,
ctx: &mut TxContext,
): Sword {
forge.swords_created = forge.swords_created + 1;
let sword = Sword {
id: object::new(ctx),
magic: magic,
strength: strength,
};
sword
}
// Part 5: Public/entry functions (introduced later in the tutorial)
// Part 6: Private functions (if any)
#[test]
public fun test_sword_create() {
use sui::transfer;
// Create a dummy TxContext for testing
let ctx = tx_context::dummy();
// Create a sword
let sword = Sword {
id: object::new(&mut ctx),
magic: 42,
strength: 7,
};
// Check if accessor functions return correct values
assert!(magic(&sword) == 42 && strength(&sword) == 7, 1);
// Create a dummy address and transfer the sword
let dummy_address = @0xCAFE;
transfer::transfer(sword, dummy_address);
}
#[test]
fun test_sword_transactions() {
use sui::test_scenario;
// create test addresses representing users
let admin = @0xBABE;
let initial_owner = @0xCAFE;
let final_owner = @0xFACE;
// first transaction to emulate module initialization
let scenario_val = test_scenario::begin(admin);
let scenario = &mut scenario_val;
{
init(test_scenario::ctx(scenario));
};
// second transaction executed by admin to create the sword
test_scenario::next_tx(scenario, admin);
{
// create the sword and transfer it to the initial owner
sword_create(42, 7, initial_owner, test_scenario::ctx(scenario));
};
// third transaction executed by the initial sword owner
test_scenario::next_tx(scenario, initial_owner);
{
// extract the sword owned by the initial owner
let sword = test_scenario::take_from_sender<Sword>(scenario);
// transfer the sword to the final owner
sword_transfer(sword, final_owner, test_scenario::ctx(scenario))
};
// fourth transaction executed by the final sword owner
test_scenario::next_tx(scenario, final_owner);
{
// extract the sword owned by the final owner
let sword = test_scenario::take_from_sender<Sword>(scenario);
// verify that the sword has expected properties
assert!(magic(&sword) == 42 && strength(&sword) == 7, 1);
// return the sword to the object pool
test_scenario::return_to_sender(scenario, sword)
// or uncomment the line below to destroy the sword instead
// test_utils::destroy(sword)
};
test_scenario::end(scenario_val);
}
#[test_only] use sui::test_scenario as ts;
#[test_only] const ADMIN: address = @0xAD;
#[test]
public fun test_module_init() {
let ts = ts::begin(@0x0);
// first transaction to emulate module initialization.
{
ts::next_tx(&mut ts, ADMIN);
init(ts::ctx(&mut ts));
};
// second transaction to check if the forge has been created
// and has initial value of zero swords created
{
ts::next_tx(&mut ts, ADMIN);
// extract the Forge object
let forge: Forge = ts::take_from_sender(&mut ts);
// verify number of created swords
assert!(swords_created(&forge) == 0, 1);
// return the Forge object to the object pool
ts::return_to_sender(&mut ts, forge);
};
ts::end(ts);
}
}
加入组织, 一起交流/学习!
- Sui 中文开发群(TG)
- 企鹅群: 79489587