Sandwich合约源码解析 | Move dApp 极速入门(拾叁)
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 合约的基本结构。
0x01 合约结构
我们可以抽象地把合约分为三个部分:
// 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: 函数
}
0x02 Sandwich 的结构体
包含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
2.1 UID
https://github.com/MystenLabs/sui/blob/aa5fe5bf68b20cc2def0392cbab71f8bcdad0060/crates/sui-framework/sources/object.move#L35
- 定义存储中对象 ID 的全局唯一 ID。 对任何具备 Key 能力的 Struct 也即 Object 而言,必须将"id: UID"作为其第一个字段。
- 不会有两个
UID
类型的值相等,也即它们全局唯一。换句话说,对于任意两个值id1: UID
和id2: UID
,id1
!=id2
。 - 这是一个 privileged 类型,只能从
TxContext
派生。 UID
没有drop
能力,所以删除UID
需要调用delete
。
0x03 函数
3.1 初始化函数
在模块初始化的时候会调取的函数,在本例中,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));
}
3.1.1 transfer::share_object
函数
https://docs.sui.io/devnet/build/move/sui-move-library#shared
要使 obj
共享,可以调用:
transfer::share_object(obj);
在这个调用之后,obj
保持可变,但被所有人共享,即任何人都可以发送交易来改变这个对象。 但是,这样的对象不能作为字段传输或嵌入到另一个obj
中。 有关详细信息,请参阅共享对象文档:
https://docs.sui.io/devnet/build/move/sui-move-library#:~:text=details%2C%20see%20the-,shared%20objects,-documentation.
3.1.2 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
的所有者。
3.1.3 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,
}
}
3.1.4 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));
}
3.2 交易函数
和 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))
}
3.2.1 coin::into_balance
函数
https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/coin.move#L122
销毁一枚 Coin 的 wrapper (unwrapper) 并返回其余额。
3.2.2 balance::join
函数
https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/balance.move#L71
将两个余额合并到一起。
3.2.3 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
}
3.3 组合函数
销毁 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))
}
3.4 Profits 相关函数
关于 Funcs 类型详细的描述可见:
https://docs.sui.io/build/move#move-functions
https://mp.weixin.qq.com/s/OXLyiUKzpFzAzc-PVxLvTA
/// 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));
}
3.4.1 coin::take
函数
https://github.com/MystenLabs/sui/blob/b8ace6ff5e3045f6e3fdd9a7ff076dfd2c236a61/crates/sui-framework/sources/coin.move#L131
从 Balance
中取出价值 value
的 Coin
,如果 value > balance.value
则中止。
0x4 Sandwich 合约部署与调用实践
CLI 的安装以及基础指令参考:
https://mp.weixin.qq.com/s/jrz3p9x495HpAvQEYRNiZw
4.1 部署
$ sui move build
$ sui client publish ./ --gas 0x82db13db77f034873cf3f1f2e43fc1237e08664e --gas-budget 30000 --verify-dependencies
4.1.1 在浏览器中查看部署信息
https://explorer.sui.io/transaction/2U84nEgz9QM94zH5mZ79Az1EVypF2WyzdWsCM14qAJZH
Tips:sui client 地址相关用例。
$ sui client new-address secp256k1 | ed25519 # 新建地址
$ sui client addresses # 查看所有地址
$ sui client switch --address [addr] # 切换地址
4.2 调用
Tips:sui client 转账相关用例。
$ sui client gas
$ sui client transfer-sui --to 0x181bd292dbe70628479b85e873460caa3e180fe2 --sui-coin-object-id 0x82db13db77f034873cf3f1f2e43fc1237e08664e --gas-budget 30000
4.2.1 查看所有的 gas
$ sui client gas
4.2.2 构建调用方法
$ 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
:
评论