Make a delicious sandwich on Sui!
Sandwich 是 Sui 官方 Examples 里的案例之一:
https://github.com/MystenLabs/sui/tree/main/sui_programmability/examples
https://github.com/NonceGeek/Web3-dApp-Camp/tree/main/move-dapp/sui/sandwich
Sandwich 是一个很好的「MVP」案例,帮助我们理解 Sui 合约的基本结构。
我们可以抽象地把合约分为三个部分:
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
/// Example of objects that can be combined to create
/// new objects
module basics::sandwich {
// packages: 包
// structs: 结构体
// consts: 常量
// functions: 函数
}
包含Ham
、Bread
、Sandwich
、Grocery
与 GroceryOwnerCapability
这几个组成部分:
struct Ham has key {
id: UID
}
struct Bread has key {
id: UID
}
struct Sandwich has key {
id: UID,
}
// This Capability allows the owner to withdraw profits
struct GroceryOwnerCapability has key {
id: UID
}
// Grocery is created on module init
struct Grocery has key {
id: UID,
profits: Balance<SUI>
}
关于 Sui 中的基础类型,可见:
https://docs.sui.io/build/programming-with-objects/ch1-object-basics
https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/object.move
UID
类型的值相等,也即它们全局唯一。换句话说,对于任意两个值 id1: UID
和 id2: UID
,id1
!= id2
。TxContext
派生。UID
没有 drop
能力,所以删除 UID
需要调用 delete
。在模块初始化的时候会调取的函数,在本例中,init 函数会创建一个grocery
。
/// On module init, create a grocery
fun init(ctx: &mut TxContext) {
transfer::share_object(Grocery {
id: object::new(ctx),
profits: balance::zero<SUI>()
});
transfer::transfer(GroceryOwnerCapability {
id: object::new(ctx)
}, tx_context::sender(ctx));
}
transfer::share_object
函数https://docs.sui.io/devnet/build/move/sui-move-library#shared
要使 obj
共享,可以调用:
transfer::share_object(obj);
在这个调用之后,obj
保持可变,但被所有人共享,即任何人都可以发送交易来改变这个对象。 但是,这样的对象不能作为字段传输或嵌入到另一个obj
中。 有关详细信息,请参阅共享对象文档:
transfer::transfer
函数https://docs.sui.io/devnet/build/move/sui-move-library#owned-by-an-address
Transfer 模块提供了操作对象所有权所需的 API。
最常见的情况是将obj
传输到地址。 例如,当一个新obj
被创建时,它通常被转移到一个地址,这样该地址就拥有该obj
。 要将对象 obj
传输到地址的方法如下:
use sui::transfer;
transfer::transfer(obj, recipient);
此调用将完全消耗该obj
,使其在当前交易中不再可访问。 一旦一个地址拥有一个obj
,对于这个obj
的任何未来使用(读取或写入),交易的签名者必须是该obj
的所有者。
object::new
函数https://docs.sui.io/build/programming-with-objects/ch1-object-basics#create-sui-object
既然我们已经学会了如何定义一个 Sui 对象类型,那么我们如何创建/实例化一个 Sui 对象呢? 为了根据其类型创建一个新的 Sui 对象,我们必须为每个字段分配一个初始值,包括 id。 为 Sui 对象创建新 UID 的唯一方法是调用 object::new。 新函数将当前交易内容作为生成唯一 ID 的参数。 交易内容是 &mut TxContext 类型,应该从入口函数(可以直接从事务中调用的函数)向下传递。以ColorObject
为例定义构造函数:
// object creates an alias to the object module, which allows us call
// functions in the module, such as the `new` function, without fully
// qualifying, e.g. `sui::object::new`.
use sui::object;
// tx_context::TxContext creates an alias to the the TxContext struct in tx_context module.
use sui::tx_context::TxContext;
fun new(red: u8, green: u8, blue: u8, ctx: &mut TxContext): ColorObject {
ColorObject {
id: object::new(ctx),
red,
green,
blue,
}
}
init
函数做了什么?回顾init
函数,我们就可以给其加上注释了:
/// On module init, create a grocery
fun init(ctx: &mut TxContext) {
/// 将 Grocery 对象设置为共享状态
transfer::share_object(Grocery {
id: object::new(ctx),
profits: balance::zero<SUI>()
});
/// transfer 一个 GroceryOwnerCapability 对象给交易发送人
transfer::transfer(GroceryOwnerCapability {
id: object::new(ctx)
}, tx_context::sender(ctx));
}
和 aptos 一样,public entry fun
的函数即是可以外部访问的。
/// Exchange `c` for some ham
public entry fun buy_ham(
grocery: &mut Grocery,
c: Coin<SUI>,
ctx: &mut TxContext
) {
let b = coin::into_balance(c);
assert!(balance::value(&b) >= HAM_PRICE, EInsufficientFunds);
balance::join(&mut grocery.profits, b);
transfer::transfer(Ham { id: object::new(ctx) }, tx_context::sender(ctx))
}
/// Exchange `c` for some bread
public entry fun buy_bread(
grocery: &mut Grocery,
c: Coin<SUI>,
ctx: &mut TxContext
) {
let b = coin::into_balance(c);
assert!(balance::value(&b) >= BREAD_PRICE, EInsufficientFunds);
balance::join(&mut grocery.profits, b);
transfer::transfer(Bread { id: object::new(ctx) }, tx_context::sender(ctx))
}
coin::into_balance
函数https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/coin.move#L122
销毁一枚 Coin 的 wrapper (unwrapper) 并返回其余额。
balance::join
函数https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/balance.move#L71
将两个余额合并到一起。
buy_ham
函数做了什么?加上注释:
/// Exchange `c` for some ham
public entry fun buy_ham(
grocery: &mut Grocery,
c: Coin<SUI>,
ctx: &mut TxContext
) {
let b = coin::into_balance(c); /// 销毁 Coin 并获得其余额
assert!(balance::value(&b) >= HAM_PRICE, EInsufficientFunds); // 判断是否支付了正确的金额
balance::join(&mut grocery.profits, b); // 合并现有余额和 b
transfer::transfer(Ham { id: object::new(ctx) }, tx_context::sender(ctx)) // 转让 Ham
}
销毁 Ham 和 Bread 两个obj
,然后转让一个 Sandwich obj
给交易发送人。
/// Combine the `ham` and `bread` into a delicious sandwich
public entry fun make_sandwich(
ham: Ham, bread: Bread, ctx: &mut TxContext
) {
let Ham { id: ham_id } = ham;
let Bread { id: bread_id } = bread;
object::delete(ham_id);
object::delete(bread_id);
transfer::transfer(Sandwich { id: object::new(ctx) }, tx_context::sender(ctx))
}
关于 Funcs 类型详细的描述可见:
/// See the profits of a grocery
public fun profits(grocery: &Grocery): u64 {
balance::value(&grocery.profits)
}
/// Owner of the grocery can collect profits by passing his capability
public entry fun collect_profits(_cap: &GroceryOwnerCapability, grocery: &mut Grocery, ctx: &mut TxContext) {
let amount = balance::value(&grocery.profits);
assert!(amount > 0, ENoProfits);
// Take a transferable `Coin` from a `Balance`
let coin = coin::take(&mut grocery.profits, amount, ctx);
transfer::transfer(coin, tx_context::sender(ctx));
}
coin::take
函数从 Balance
中取出价值 value
的 Coin
,如果 value > balance.value
则中止。
CLI 的安装以及基础指令参考:
$ sui move build
$ sui client publish ./ --gas 0x82db13db77f034873cf3f1f2e43fc1237e08664e --gas-budget 30000 --verify-dependencies
https://explorer.sui.io/transaction/2U84nEgz9QM94zH5mZ79Az1EVypF2WyzdWsCM14qAJZH
💡 Tips:sui client 地址相关用例。
$ sui client new-address secp256k1 | ed25519 # 新建地址
$ sui client addresses # 查看所有地址
$ sui client switch --address [addr] # 切换地址
💡 Tips:sui client 转账相关用例。
$ sui client gas
$ sui client transfer-sui --to 0x181bd292dbe70628479b85e873460caa3e180fe2 --sui-coin-object-id 0x82db13db77f034873cf3f1f2e43fc1237e08664e --gas-budget 30000
$ sui client gas
$ sui client call --function buy_bread --module sandwich --package 0x08204ed92afcfdf9d0f6727a2c7d40db93a059d8 --args 0xca3f4ad2b0dea3264f38878db34cecb75a6336e0 0x520e1cecf280effc4f2129d5109f11be0c000d35 --gas [one_gas_object] --gas-budget 30000
对应函数源码:
public entry fun buy_bread(
grocery: &mut Grocery,
c: Coin<SUI>,
ctx: &mut TxContext
) {
……
}
Grocery 是合约的obj
,因此我们填入 4.1.1
中查看到的 obj_id
到参数1,再填入一个 4.2.1
中所查看的 gas obj
到参数 2。
我们可以在浏览器中查看到调用记录:
https://explorer.sui.io/transaction/5sK6AecDrwhjCpYGw7gEmkYMMihq5QCMH9SXXkjRtpmZ
点击 Created,我们可以看到成功创建了一片bread
:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!