HOH SUI 共学笔记(二)

296 阅读4分钟

HOH

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_scenarioassert_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);
            // 结束测试场景
        }
    }
}