随笔小记—Move篇(1)

  • Ch1hiro
  • 发布于 1天前
  • 阅读 135

本篇笔记学习 Move 合约的父子对象和灵魂绑定

父子对象

​ 由于 Move 合约中没有区分一个对象转移给的是一个账户还是一个对象 ID,所以当一个对象转交给另一个对象时,这两个对象就形成了父子关系。

    // 定义父对象数据结构
    public struct Parent has key {
        id:UID,
        name: String,
    }

    // 定义子对象数据结构
    public struct Child has key {
        id:UID,
        description: String,
    } 

    // 创建父对象,并转移给发送者
    public entry fun new_parent(ctx: &mut TxContext) {
        transfer::transfer(Parent{
            id: object::new(ctx),
            name: string::utf8(b"Ch1hiro"),
            }, tx_context::sender(ctx));
    }

    // 创建子对象,并转移给父对象
    public entry fun new_child(parent_id: address, ctx: &mut TxContext) {
        transfer::transfer(Child{
            id: object::new(ctx),
            description: string::utf8(b"this is a description"),
            }, parent_id);
    }

​ 当对象属于某个账户时,可以通过上下文来访问该对象,如果对象属于某一个对象时,需要通过对象来访问。在嵌套对象中可以通过访问对象字段的方式去访问被嵌套对象。独有对象的值并不在对象中保存,自然不能像嵌套对象那样访问,那独有对象该怎么访问呢?

嵌套对象:被嵌套对象必须拥有 store 能力,直接或间接的的作为嵌套对象的一个字段

​ 在 Move 的 transfer 模块中提供了 receivepublic_receive 方法,

// transfer::receive()
public fun receive<T: key>(parent: &mut UID, to_receive: Receiving<T>): T {
    let Receiving { id, version } = to_receive;
    receive_impl(parent.to_address(), id, version)
}

该函数接收一个 可变引用的父对象 UID 和 一个具有 key 能力的对象,最终返回一个子对象

下面实际操作一下,用 receive 函数返回 Child 对象并修改字段值,最后再重新转移给父对象

    // to_receive 参数直接传入 Child 对象的 ID 即可
    public entry fun modify_child(parent: &mut Parent, to_receive: Receiving<Child>, _: &mut TxContext) {
        let mut child = transfer::receive<Child>(&mut parent.id, to_receive);
        child.description = string::utf8(b"have changed");
        transfer::transfer(child, object::uid_to_address(&parent.id));
    }

上面的 receive 函数只能用于父对象和子对象在同一个模块内,如果在不同模块则要使用 public_receive 函数

// transfer::public_receive()
public fun public_receive<T: key + store>(parent: &mut UID, to_receive: Receiving<T>): T {
    let Receiving { id, version } = to_receive;
    receive_impl(parent.to_address(), id, version)
}

public_receive函数接受一个具有 key 和 store 能力的对象,因为该对象需要能在全局存储,也就是能被在模块外访问

接下来在两个模块中分别创建父对象和子对象:

module object_study::object_child_study {
    use std::string::{ Self, String };

    // 定义子对象数据结构
    public struct Child has key, store {
        id:UID,
        description: String,
    }

    // 创建子对象
    public fun new(ctx: &mut TxContext): Child {
        Child{
            id: object::new(ctx),
            description: string::utf8(b"this is a description"),
        }
    }

    // 修改子对象的字段值
    public fun modify(child: &mut Child) {
        child.description = string::utf8(b"have changed");
    }

}
module object_study::object_parent_study {
    use std::string::{ Self, String };
    use sui::transfer::{ Receiving };
    use object_study::object_child_study::{ Child, new, modify };

    // 定义父对象数据结构
    public struct Parent has key {
        id:UID,
        name: String,
    }

    // 创建父对象,并转移给发送者
    public entry fun new_parent(ctx: &mut TxContext) {
        transfer::transfer(Parent{
            id: object::new(ctx),
            name: string::utf8(b"Ch1hiro"),
            }, tx_context::sender(ctx));
    }

    public entry fun new_child(parent: &Parent, ctx: &mut TxContext) {
        let child = new(ctx);
        transfer::public_transfer(child, object::uid_to_address(&parent.id));
    }

    // to_receive 参数直接传入 Child 对象的 ID 即可
    public entry fun modify_child(parent: &mut Parent, to_receive: Receiving<Child>, _: &mut TxContext) {
        let mut child = transfer::public_receive<Child>(&mut parent.id, to_receive);
        modify(&mut child);
        transfer::public_transfer(child, object::uid_to_address(&parent.id));
    } 

}

对象的创建和修改必须在定义的模块内进行,模块外只能调用相关的函数进行操作

灵魂绑定

下面介绍一种特殊的父子对象——灵魂绑定,可以从父对象中取出子对象访问,但是最后必须要归还给父对象

struct Child has key {
    id: UID,
}
//归还子对象的清单,没有 drop 能力,不允许自动析构
struct ReturnReceipt { 
    object_id: ID, 
    return_to: address,
}
public fun get_object(parent: &mut UID, soul_bound_ticket: Receiving<Child>): (Child, ReturnReceipt) {
    let soul_bound = transfer::receive(parent, soul_bound_ticket);
    let return_receipt = ReturnReceipt { 
        return_to: object::uid_to_address(parent),
        object_id: object::id(&soul_bound),
    };
    (soul_bound, return_receipt)
}
public fun return_object(soul_bound: Child, receipt: ReturnReceipt) {
    let ReturnReceipt { return_to, object_id }  = receipt;
    assert!(object::id(&soul_bound) == object_id, EWrongObject);
    sui::transfer::transfer(soul_bound, return_to);
}

​ 这里 ReturnReceipt 是一个 Hot Potato,用于记录灵魂绑定的对象和要归还的地址。get_object函数获取到一个子 ChildReturnReceipt,并在return_object函数中归还子对象并销毁 ReturnReceipt,这样父与子将永远绑定在一起。

Hot Potato(热土豆):一个没有任何能力的结构体,如果 A 函数返回一个 Hot Potato,B 函数销毁它,那么必须在函数 A 调用后调用 B 函数

参考:https://easy.sui-book.com/chapter_12.html

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ch1hiro
Ch1hiro
一名Web3初学者