HOH水分子社区,是一个专注于编程、教育和创新的社区
Sui 资源管理合约与基础操作
合约部分
如何使用合约结构来创建、存储和操作用户数据。
1. 模块与结构体定义
定义了一个名为 filling 的模块,该模块主要处理用户配置文件的创建与存储。Sui Move 允许我们定义资源类型(结构体),并为这些资源指定行为。
定义状态结构体 State
State 结构体用于保存系统的当前状态,包括所有已创建的用户信息。它包含一个 users 表,表中存储了用户地址和用户 ID 的映射关系。
//`id` 是一个唯一的标识符,表示这个 `State` 对象的 UID(对象 ID)。
//`users` 是一个表(`Table`),用于存储用户地址与其相关的唯一标识符。
public struct State has key {
id: UID,
users: Table<address, address>,
}
定义状态结构体 State
//`id` 是该用户配置文件的唯一标识符。`name` 和 `description` 分别表示用户的姓名和个人描述
public struct Profile has key {
id: UID,
name: String,
description: String,
}
定义事件 ProfileCreated
//当用户配置文件创建时,我们需要发出一个事件通知。`ProfileCreated` 结构体表示该事件,包含用户的 `profile` 地址和 `owner` 地址
public struct ProfileCreated has copy, drop {
profile: address,
owner: address,
}
2. 初始化函数 init
初始化函数用于初始化 State 对象,并将其共享到全局状态。它在部署合约时被调用。
//`object::new(ctx)` 创建一个新的唯一标识符(UID)并分配给 `State`。
//`table::new(ctx)` 创建一个新的空表,用于存储用户信息
fun init(ctx: &mut TxContext) {
transfer::share_object(State {
id: object::new(ctx),
users: table::new(ctx)
})
}
3. 创建用户配置文件的函数 create_profile
public entry fun create_profile(name: String, description: String, state: &mut State, ctx: &mut TxContext) {
let owner = tx_context::sender(ctx);
assert!(!table::contains(&state.users, owner), EProfileExist);
let uid = object::new(ctx);
let id = object::uid_to_inner(&uid);
let new_profile = Profile { id: uid, name, description };
transfer::transfer(new_profile, owner);
table::add(&mut state.users, owner, object::id_to_address(&id));
event::emit(ProfileCreated { profile: object::id_to_address(&id), owner });
}
-
tx_context::sender(ctx)获取当前交易的发送者地址。 -
assert!用于检查用户是否已经存在配置文件,如果存在则抛出EProfileExist错误。 -
object::new(ctx)创建一个新的 UID,用于标识新的Profile对象。 -
transfer::transfer(new_profile, owner)将新创建的Profile转移给用户。 -
table::add(&mut state.users, owner, object::id_to_address(&id))将用户与其Profile的关系添加到users表中。 -
event::emit(ProfileCreated)发出ProfileCreated事件,通知外部系统配置文件已成功创建。
4. 检查用户是否已有配置文件的函数 check_if_has_profile
public fun check_if_has_profile(user_address: address, state: &State): Option<address> {
if (table::contains(&state.users, user_address)) {
option::some(*table::borrow(&state.users, user_address))
} else {
option::none()
}
}
5.测试代码
create_profile 函数的正确性。测试代码使用了 test_scenario 和 assert_eq 工具来模拟交易并验证预期行为
#[test_only]
module filling::filling_tests;
use filling::filling::{Self, State, Profile};
// 引入需要测试的合约
use sui::test_scenario{Self};
// 引入测试场景模块
use std::string::{Self, String};
// 引入标准库中的 String 类型
use sui::test_utils::assert_eq;
// 引入用于比较测试结果的工具函数
#[test]
fun test_create_profile() {
let user = @0xa;
// 定义一个虚拟的用户地址,用于模拟合约调用者
let mut scenario_val = test_scenario::begin(user);
// 启动一个新的测试场景,传入虚拟用户地址作为交易发起者
let scenario = &mut scenario_val;
filling::init_for_testing(test_scenario::ctx(scenario));
// 调用合约中的初始化方法,准备好测试环境
test_scenario::next_tx(scenario, user);
// 在事务流中启动下一个交易
let name = string::utf8(b"Al17er");
let desc = string::utf8(b"Al17er test description!");
// 设置用户配置文件的名称和描述
{
let mut state = test_scenario::take_shared<State>(scenario);
// 从测试场景中获取共享的 State 对象
filling::create_profile(
name: name,
description: desc,
state: &mut state,
ctx: test_scenario::ctx(scenario),
);
// 调用合约中的 `create_profile` 方法,创建用户配置文件
test_scenario::return_shared(state);
// 将修改后的 `state` 返回给测试框架
let tx = test_scenario::next_tx(scenario, user);
let expected_no_events = 1;
// 设置期望的事件数量为 1
assert_eq!(
test_scenario::num_user_events(&tx),
expected_no_events
);
// 验证事务中是否触发了 1 个事件,确保创建配置文件的行为成功
{
let state = test_scenario::take_shared<State>(scenario);
let profile = test_scenario::take_from_sender<Profile>(scenario);
// 从测试场景中获取最新的 `State` 和 `Profile` 对象
assert!(
filling::check_if_has_profile(user, &state) ==
option::some(object::id_to_address(object::borrow_id(&profile))),
0
);
// 验证用户是否存在配置文件,检查是否与期望的配置文件地址一致
test_scenario::return_shared(state);
test_scenario::return_to_sender(scenario, profile);
// 返还 `state` 和 `profile` 对象
test_scenario::end(scenario_val);
// 结束测试场景
}
}
}