通过官方合约学习Sui Move语法知识1

  • rzexin
  • 更新于 2023-12-27 21:35
  • 阅读 1440

通过官方合约学习Sui Move语法知识

通过官方合约学习Sui Move语法知识1

合约1:Counter

1.1 合约代码

https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/basics/sources/counter.move

功能说明:

  • 任何人都可以创建并共享一个计数器

  • 任何人都可以将计数器增加1

  • 计数器的所有者可以将其重置为任何值

  • 计数器的所有者可以将计数器对象删除

module basics::counter {
    use sui::transfer;
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};

    /// 计数器对象类型定义
    struct Counter has key {
        id: UID,
        owner: address,
        value: u64
    }

    public fun owner(counter: &Counter): address {
        counter.owner
    }

    public fun value(counter: &Counter): u64 {
        counter.value
    }

    /// 创建并设置计数器对象为共享对象
    public entry fun create(ctx: &mut TxContext) {
        transfer::share_object(Counter {
            id: object::new(ctx),
            owner: tx_context::sender(ctx),
            value: 0
        })
    }

    /// 计数器自增1(任何人可以调用)
    public entry fun increment(counter: &mut Counter) {
        counter.value = counter.value + 1;
    }

    /// 设置计数器的值(只有合约创建者可以调用)
    public entry fun set_value(counter: &mut Counter, value: u64, ctx: &TxContext) {
        assert!(counter.owner == tx_context::sender(ctx), 0);
        counter.value = value;
    }

    /// 断言计数器的值
    public entry fun assert_value(counter: &Counter, value: u64) {
        assert!(counter.value == value, 0)
    }

    /// 删除计数器共享对象(只有合约创建者可以调用)
    public entry fun delete(counter: Counter, ctx: &TxContext) {
        assert!(counter.owner == tx_context::sender(ctx), 0);
        let Counter {id, owner:_, value:_} = counter;
        object::delete(id);
    }
}

1.2 知识点

(1)UID

  • 创建一个Sui object需要一个唯一ID

  • 可以根据当前TxContext中的信息,使用 sui::object::new 函数来创建一个新的ID

(2)类型能力

能力定义对象(Object)的行为。一个对象类型最多拥有4项能力,分别是:

  • Copy: 允许具有该类型的值被复制
  • Drop: 允许具有该类型的值被弹出或丢弃
  • Key: 允许该类型作为全局存储操作的键
  • Store: 允许具有该类型的值在全局存储中被存储

拥有 KeyStore 能力特性的定制类型被视为资产,资产可以在全局存储中存储,也可以在不同账号之间转移。

(3)共享对象

共享对象(Share object)可以通过 sui::transfer::share_object 被共享,从而让所有人都可以使用。

因为该对象可以被任何人使用, 因此需要根据需求在逻辑中设计额外的安全检查,如:示例中的set_valuedelete接口,只有合约创建者可以调用去设置计数器的值或删除计数器共享对象。

(4)TxContext

保存了当前执行交易的上下文信息。定义结构如下:

https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/sui-framework/sources/tx_context.move#L24

    struct TxContext has drop {
        /// The address of the user that signed the current transaction
        sender: address,
        /// Hash of the current transaction
        tx_hash: vector<u8>,
        /// The current epoch number
        epoch: u64,
        /// Timestamp that the epoch started at
        epoch_timestamp_ms: u64,
        /// Counter recording the number of fresh id's created while executing
        /// this transaction. Always 0 at the start of a transaction
        ids_created: u64
    }

在示例代码中,通过tx_context::sender(ctx)获取到当前交易的发起者地址,以便可以和合约中记录的创建者地址是否一致,用于一些特定接口调用的安全检查,来判断是否是合约创建者进行的调用。

相关汇总如下:

接口名 说明
tx_context::sender(ctx) 获取当前交易发起者(交易签署者)地址
tx_context::digest(ctx) 获取当前交易Hash
tx_context::epoch(ctx) 获取当前世代
tx_context::epoch_timestamp_ms(ctx) 以毫秒级别unix时间戳返回epoch开始时间

(5)对象删除

要删除一个Object,先要解包这个Object并且获取它的Object ID。在调用object::delete进行对象删除。

注:

  • 不关心的参数可以使用_以忽略

  • 解包的操作只能够在定义了这个Object的Module内进行

let Counter {id, owner:_, value:_} = counter;
object::delete(id);

合约2:clock

2.1 合约代码

https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/basics/sources/clock.move

功能说明:调用get_time接口,将通过事件形式抛出当前毫秒级别时间戳。

module basics::clock {
    use sui::clock::{Self, Clock};
    use sui::event;
    use sui::tx_context::TxContext;

    struct TimeEvent has copy, drop {
        timestamp_ms: u64,
    }

    /// Emit event with current time.
    public entry fun get_time(clock: &Clock, _ctx: &mut TxContext) {
        event::emit(TimeEvent { timestamp_ms: clock::timestamp_ms(clock) });
    }
}

2.2 知识点

(1)合约事件

事件对于区块链系统至关重要,它是追踪链上行为的主要方法。

Sui上面的事件也是以Objects的形式表示。有几种系统层级的事件, 包括:Move event、Publish event、Transfer object event等。

  • 事件定义

事件对象定义仅需要具有copydrop的能力,表明可以被复制并在作用域结束后被销毁。它不代表资产,我们只对其包含的数据感兴趣。

struct TimeEvent has copy, drop {
    timestamp_ms: u64,
}
  • 事件抛出
event::emit(TimeEvent { timestamp_ms: clock::timestamp_ms(clock) });

(2)预置对象

get_time接口第一个参数是sui::clock::ClockClock是系统已经预置的硬编码的对象只读引用实例,地址是:0x6,即:接口调用将0x6作为Clock参数的地址传入。

相关汇总如下:

https://github.com/MystenLabs/sui/blob/mainnet-v1.15.1/crates/sui-framework/packages/sui-framework/sources/object.move#L21

地址 说明
@0x5 Sui System State Object
@0x6 Clock Object
@0x7 AuthenticatorState Object
@0x8 Random Object

合约3:object_basics

3.1 合约代码

https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/basics/sources/object_basics.move

/// Test CTURD object basics (create, transfer, update, read, delete)
module basics::object_basics {
    use sui::event;
    use sui::object::{Self, UID};
    use sui::tx_context::{Self, TxContext};
    use sui::transfer;

    struct Object has key, store {
        id: UID,
        value: u64,
    }

    struct Wrapper has key {
        id: UID,
        o: Object
    }

    struct NewValueEvent has copy, drop {
        new_value: u64
    }

    public entry fun create(value: u64, recipient: address, ctx: &mut TxContext) {
        transfer::public_transfer(
            Object { id: object::new(ctx), value },
            recipient
        )
    }

    public entry fun transfer(o: Object, recipient: address) {
        transfer::public_transfer(o, recipient)
    }

    public entry fun freeze_object(o: Object) {
        transfer::public_freeze_object(o)
    }

    #[no_lint]
    public entry fun set_value(o: &mut Object, value: u64) {
        o.value = value;
    }

    // test that reading o2 and updating o1 works
    public entry fun update(o1: &mut Object, o2: &Object) {
        o1.value = o2.value;
        // emit an event so the world can see the new value
        event::emit(NewValueEvent { new_value: o2.value })
    }

    public entry fun delete(o: Object) {
        let Object { id, value: _ } = o;
        object::delete(id);
    }

    public entry fun wrap(o: Object, ctx: &mut TxContext) {
        transfer::transfer(Wrapper { id: object::new(ctx), o }, tx_context::sender(ctx))
    }

    public entry fun unwrap(w: Wrapper, ctx: &TxContext) {
        let Wrapper { id, o } = w;
        object::delete(id);
        transfer::public_transfer(o, tx_context::sender(ctx))
    }
}

3.2 知识点

(1)对象包装

  • 对象包装指的是将一个对象嵌套在另一个对象内,例如上面的示例中就是将Object对象嵌套进入Wrapper对象
  • 通常Wrapper对象具有key能力,被包装的Object对象必须具有store的能力
  • 封装起来的Object对象就不再能单独根据object ID来获取,它会变成Wrapper对象的一部分
  • 被包装的Object对象不能被作为参数进行传递,而只能通过Wrapper对象进行获取
  • 可以被用作为限制一个对象在特定的合约调用之外不能被获取的方法

示例中,将Object对象包装进入Wrapper对象后,转移给交易发起者:

    public entry fun wrap(o: Object, ctx: &mut TxContext) {
        transfer::transfer(Wrapper { id: object::new(ctx), o }, tx_context::sender(ctx))
    }

解包后,将Wrapper对象删除后,将被包装的Object对象转移给交易发起者:

    public entry fun unwrap(w: Wrapper, ctx: &TxContext) {
        let Wrapper { id, o } = w;
        object::delete(id);
        transfer::public_transfer(o, tx_context::sender(ctx))
    }

(2)transfer

  • 同时拥有 keystore 的对象才可以被随意转移
  • 只拥有 key 能力的对象的所有权是不可以被随意转移的, 想要使只有 key 限制符对象可以变更所有权,必须创建一个自定义的转移函数

(3)冻结对象

  • 在示例中,通过transfer::public_freeze_object(o)可以冻结Object对象
  • 冻结后,将使得Object对象变得不可变,将不可再被转移和改变

合约4:managed fungible_tokens

4.1 合约代码

https://github.com/MystenLabs/sui/blob/mainnet-v1.15.1/sui_programmability/examples/fungible_tokens/sources/managed.move

module fungible_tokens::managed {
    use std::option;
    use sui::coin::{Self, Coin, TreasuryCap};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};

    struct MANAGED has drop {}

    #[allow(unused_function)]
    fun init(witness: MANAGED, ctx: &mut TxContext) {
        let (treasury_cap, metadata) = coin::create_currency<MANAGED>(witness, 2, b"MANAGED", b"", b"", option::none(), ctx);
        transfer::public_freeze_object(metadata);
        transfer::public_transfer(treasury_cap, tx_context::sender(ctx))
    }

    public entry fun mint(
        treasury_cap: &mut TreasuryCap<MANAGED>, amount: u64, recipient: address, ctx: &mut TxContext
    ) {
        coin::mint_and_transfer(treasury_cap, amount, recipient, ctx)
    }

    public entry fun burn(treasury_cap: &mut TreasuryCap<MANAGED>, coin: Coin<MANAGED>) {
        coin::burn(treasury_cap, coin);
    }
}

4.2 知识点

(1)Capability

  • Capability是一种常用的调整获取权限的设计模式
  • 在示例中,mintburn方法第一个参数是treasury_cap,表示只有拥有TreasuryCap<MANAGED>的人可以调用
  • 如果不需要使用,参数可以传入_,表示立即进行消耗

(2)见证者模式

  • 用于证明有关的一个资源或类型A,在短暂的witness资源被消耗后只能启动一次
  • witness资源在使用后必须立即被消耗或丢弃,确保它不能被重复使用以创建A的多个实例

(3)一次性见证

  • 实例中的代码是一次性见证(One Time Witness(OTW)),它是Witness模式的一个子模式:利用模块init函数来确保只创建一个witness资源的实例。

  • TreasuryCap是一种资产,通过一次性见证这种模式保证它是一个单体对象

  • 如果一个类型的定义具有以下属性,那么它就被认为是一个OTW。

    • 该类型是以模块的名字命名的,但大写字母。
    • 该类型只具有drop的能力

参考资料

https://examples.sui-book.com/

https://move-language.github.io/

https://intro-zh.sui-book.com/

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

0 条评论

请先 登录 后评论
rzexin
rzexin
0x2Dc5...52c1
江湖只有他的大名,没有他的介绍。