move 语言中的witness 模式,

witnes 的例子解释

hoh社区中 uvd 对这个模式使用了一个比较有趣的类比.比如要建房子,

  • 建房子需要找政府申请建房许可.
  • 建好房子之后,这个建房许可就会被销毁

image-20241123093733016

witness合约代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
module book::witness;
use sui::coin::{Self,Coin};
use sui::sui::SUI;
use std::string::{Self,String};
const PASSPORT_FEE:u64 = 50000;

public struct ShareInfo has key{
id :UID,
owner:address,

}
fun init(ctx:&mut TxContext){
let share_info = ShareInfo{
id:object::new(ctx),
owner:ctx.sender(),
};
transfer::freeze_object(share_info);
}

public struct House has key{
id:UID,
name:String,
}

public struct Passport has drop{ }

/// 花钱购买一个Passport
public fun requirePassport(mut c:Coin<SUI>,share_info: &ShareInfo, ctx : &mut TxContext) : Passport{
//分割出来收费
let fee = coin::split(&mut c, PASSPORT_FEE, ctx);
transfer::public_transfer(fee,share_info.owner);
//归还找零的钱
transfer::public_transfer(c,ctx.sender());
//返回Passport
Passport{}
}

///获得Passport 才能创建House , Passport 每次使用之后都会消耗掉
public fun create_house(_:Passport, name:String, ctx:&mut TxContext){
let house = House{
id:object::new(ctx),
name:name,
};
//转让房子给交易发起者
transfer::transfer(house,ctx.sender());
}

/// 为了在模块外测试代码调用init函数
#[test_only]
public fun test_init(ctx:&mut TxContext){
init(ctx);
}
///为了模块外测试代码获取本模块常量, 因为常量不是public的
#[test_only]
public fun get_passport_fee():u64{
PASSPORT_FEE
}

单元测试代码

我们提供两个测试用例

  • 测试获取passport, 测试创建房子 test_witness_flow
  • 测试获取passport,因为金额不够失败 test_witness_flow_no_enough

相关测试代码的使用了test_scenario 模拟合约的多次调用,校验合约的效果( coin的转让 ,对象的转让)

相关使用可以参考 https://learnblockchain.cn/article/8066

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#[test_only]
module book::witness_tests ;
use sui::test_scenario::{Self, Scenario};
use sui::coin::{Self, Coin};
use sui::sui::SUI;
use std::string;
use book::witness::{Self, ShareInfo, House, Passport};
use book::witness::get_passport_fee;

#[test]
fun test_witness_flow() {
let owner = @0xA;
let user = @0xB;
// 第一步:初始化场景
let mut scenario = test_scenario::begin(owner);
// 初始化合约,创建 ShareInfo
test_scenario::next_tx(&mut scenario, owner);
{
witness::test_init(test_scenario::ctx(&mut scenario));
};

// 用户购买 Passport
test_scenario::next_tx(&mut scenario, user);
{
// 创建测试用的 SUI coin
let coin = coin::mint_for_testing<SUI>(100000, test_scenario::ctx(&mut scenario));
let share_info = test_scenario::take_immutable<ShareInfo>(&scenario);

let passport = witness::requirePassport(
coin,
&share_info,
test_scenario::ctx(&mut scenario)
);

// 使用 Passport 创建 House
witness::create_house(
passport,
string::utf8(b"My House"),
test_scenario::ctx(&mut scenario)
);

test_scenario::return_immutable(share_info);
};

// 验证 House 是否创建成功
test_scenario::next_tx(&mut scenario, user);
{
let coin = test_scenario::take_from_address<Coin<SUI>>(&scenario, owner);
assert!(coin.value() == get_passport_fee());
test_scenario::return_to_address(owner, coin);

let coin = test_scenario::take_from_address<Coin<SUI>>(&scenario, user);
assert!(coin.value() == 100000 - get_passport_fee());
test_scenario::return_to_address(user, coin);


assert!(test_scenario::has_most_recent_for_sender<House>(&scenario));
};

test_scenario::end(scenario);
}


#[test]
#[expected_failure(abort_code = sui::balance::ENotEnough)]
fun test_witness_flow_no_enough() {

let owner = @0xA;
let user = @0xB;

// 第一步:初始化场景
let mut scenario = test_scenario::begin(owner);

// 初始化合约,创建 ShareInfo
test_scenario::next_tx(&mut scenario, owner);
{
witness::test_init(test_scenario::ctx(&mut scenario));
};

// 用户购买 Passport
test_scenario::next_tx(&mut scenario, user);
{
let fee = get_passport_fee() - 1;
let coin = coin::mint_for_testing<SUI>(fee, test_scenario::ctx(&mut scenario));
let share_info = test_scenario::take_immutable<ShareInfo>(&scenario);
//这里会因为fee 不够而失败
let passport = witness::requirePassport(
coin,
&share_info,
test_scenario::ctx(&mut scenario)
);
test_scenario::return_shared(share_info);
};

test_scenario::end(scenario);
}

相关代码在:

https://github.com/nextuser/move-examples

附录

最近在参见HOH社区的学习

💧 HOH水分子公众号

🌊 HOH水分子X账号

📹 课程B站账号

💻 Github仓库 https://github.com/move-cn/letsmove

使用hexo搭建个人主页

目标:

搭建个人网站, 不购买服务器, 搭建个人 blog.

步骤:

  • 购买域名 , 本例从porkbun.com 购买了域名walrus.mov
  • 不买服务器,直接使用github page功能
  1. 安装hexo ,hexo是一个博客管理工具,

  2. 将hexo 管理的网页发布到一个github project中

  3. 将 github project 配置成github page
    获得 nextuser.github.io 网站, 配置github page 的域名为www.walrus.mov

刚开始是不会校验通过的.

  1. 配置购买的dns 地址 ,关联到github 网站. (这一步配置好后,第3步才会配置成功)

安装hexo

1
npmg i -g hexo

初始化目录

1
npx hexo init  blog
  • 问题 ../../../package.json: No license field

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fatal: unable to access 'https://github.com/hexojs/hexo-starter.git/': GnuTLS recv error (-110): The TLS connection was non-properly terminated.
    WARN git clone failed. Copying data instead
    INFO Install dependencies
    warning ../../../package.json: No license field
    error Error: certificate has expired
    at TLSSocket.onConnectSecure (node:_tls_wrap:1539:34)
    at TLSSocket.emit (node:events:513:28)
    at TLSSocket._finishInit (node:_tls_wrap:953:8)
    at TLSWrap.ssl.onhandshakedone (node:_tls_wrap:734:12)

  • 对策

    • 修改package.json,增加license 描述
    • 执行 npm install
{.line-numbers}
1
2
3
4
5
6
  # package.json
{
"name": "hexo-site",
"version": "0.0.0",
"private": true,
"license": "ISC",

执行编译和开启服务

1
2
npx hexo g
npx hexo s

可以在 localhost:4000查看页面

发布到git

修改_config.yml 配置和git关联

1
2
3
4
deploy:
type: git
repository: git@github.com:nextuser/hexo_blog.git
branch: main

安装配套插件

1
npm i -save hexo-deployer-git

执行部署deploy命令

1
npx hexo d

会将public目录的文件提交到github执行的项目目录下.

在github project->settings 配置github page

image-20241118232126863

配置 dns => github

在porkbun.com 配置我的dns域名和github的关联关系: dns 和github page关联

  • 进入域名管理

    image-20241118232306366

编辑 dns record

  • dns 关联到github

    image-20241118232507380

  • 设置 dns和github 地址的对应关系

    image-20241118232644409

  • 需要下载 CNAME 文件放到 blog/public 目录中

日常编写博客使用步骤

  1. 使用typora 或vs code ,在 source/_posts 目录下建立markdown日记文件

  2. 编写完毕之后,执行生成日记功能

1
npx hexo g
  1. 打开服务,测试 (可选,如果服务已经打开,不需要执行此服务)
1
npx hexo s

访问 http://localhost:4000 查看网页是否正常

  1. 部署到github
1
npx hexo d
  1. 访问我定制的网站 www.walrus.mov
  • 问题 : markdown 文件引用的 _post/images 文件不会拷贝到public,会导致图片不能正常显示。
    • 据说_config.yml 配置如下
      1
      post_asset_folder: true

下载如下插件

1
npm install https://github.com/CodeFalling/hexo-asset-image --save

2024 -11-11 第一周Sui Move训练营课程答疑问题

2024 -11-11 第一周Sui Move训练营课程答疑问题

1 github 下载问题,编译时不能访问github路径

77cab47def96df0bfa0bc2000f6533c

  • 对策:

修改Move.toml 中的github成gitee

1
2
3
[dependencies]
#Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
Sui = { git = "https://gitee.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }

2 git pull pull不下来,报错过早文件结束

2.1 如果是 github下载问题,更换是gitee

1
2
3
[dependencies]
#Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
Sui = { git = "https://gitee.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }

2.2 gitee下载存在问题

  • 对策:

    • windows: 使用git bash 执行以下命令,将代码下载到本地

    • 采用 ssh 下载

      1
      git clone git@gitee.com:mystenLabs/sui.git
  • 方法一 :代码可以使用百度网盘下载

    提取码task

    1
    https://pan.baidu.com/s/1LQwwlPz6Q2DVAod-DhD1UA

    image-20241111124128818

    解压到本地路径

    • linux如下命令
1
2
3
4
5
mkdir ~/sui-local
cd ~/sui-local
git clone git@gitee.com:mystenLabs/sui.git
git checkout framework/testnet
git pull

2.3 修改 Move.toml ,sui框架依赖本地代码

  • 确定 用户的主目录

    {.line-numbers}
    1
    2
    3
    ljl@ljl-i5-14400:~/work/sui/demo/table_example$ cd ~
    ljl@ljl-i5-14400:~$ pwd
    /home/ljl
  • 例子中的/home/ljl 是我的主目录,你可以更换成你下载的sui 代码路径

1
2
3
4
[dependencies]
#Sui = { git = "https://gitee.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
Sui = {local="/home/ljl/sui-local/sui/crates/sui-framework/packages/sui-framework" }

问题3. 每次编译都提示, fail to resolve dependencies 问题

eee52dccc6c1235a42e6b2025f3accc

3.1 原因

github网络访问比较困难

3.2 对策

  • 方法1 修改工程
{.line-numbers}
1
2
3
#Move.toml
[dependencies]
Sui = { git = "https://gitee.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
  • 方法2

    {.line-numbers}
    1
    sui move --skip-fetch-latest-git-deps  build

    可以写一个suib的脚本, 后续执行 suib即可

    {.line-numbers}
    1
    2
    3
    ljl@ljl-i5-14400:~/work/sui/demo/bb$ cat /usr/bin/suib
    sui move build --skip-fetch-latest-git-deps

4 publish 失败的原因, 不能找到 clien/server api version mismatch

b751b6d5fe797767bdc15617707d711

这个是因为sui client的版本比较老,testnet已经是1.37.1

  • 方法一

应该是安装的sui 二进制文件版本比较老,需要从服务器下载更新的sui 二进制文件

https://github.com/MystenLabs/sui/releases/tag/testnet-v1.37.1

也有可能是 gitee上的代码比较老, 需要更换Move.toml中dependencies 的以来路径

  • 方法二
1
2
sui client publish --skip-dependency-verification

5. publish问题:没有找到Move.toml (unable to find package manifest)

4c9df0a037a48da78f174af4c63b80a

1
2
D:\work\sui>sui client publish
Unable to find package manifest at '\\?\D:\work\sui/Move.toml' or in its parents

image-20241109195541791

5.1 原因

这是因为当前路径没有找到Move.toml 文件, 因为没有cd 到项目代码中.

如下图, sui move new 新建项目目录之后,需要cd 到项目目录里面

image-20241109194128504

参见上图中步骤,第三部需要cd 创建的项目的目录, sui move build 会在当前目录查找Move.toml

6. pubish 失败,没有gas

6.1 publish失败,gas不足

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
D:\work\sui\task1>sui client balance
No coins found for this address.

D:\work\sui\task1>sui client faucet
Request successful. It can take up to 1 minute to get the coin. Run sui client gas to check your gas coins.

D:\work\sui\task1>sui client balance
╭────────────────────────────────────────╮
│ Balance of coins owned by this address │
├────────────────────────────────────────┤
│ ╭─────────────────────────────────╮ │
│ │ coin balance (raw) balance │ │
│ ├─────────────────────────────────┤ │
│ │ Sui 1000000000 1.00 SUI │ │
│ ╰─────────────────────────────────╯ │
╰────────────────────────────────────────╯

7. 提示 需要 –gas-budget

1
Could not automatically determine the gas budget. Please supply one using the --gas-budget flag.

8983d211b2b1dceaf57ffe22183c955

对策:提供 gas-budget 参数

1
sui client publish --gas-budget 40000000

8 .提问预编译的下载包如何使用

8.1 下载sui 编译包

目前的sui 预编译下载包,在

https://github.com/MystenLabs/sui/releases

根据不同操作系统,下载对应的包, 下载之后,需要解压文件

8.2 环境变量设置

8.2.1 windows 的解压和path变量设置

windows 使用 7-zip能解压到比如 d:\app\sui 文件目录

image-20241110213627080

image-20241110213815476

需要在windows设置path

image-20241110214212301

image-20241110214031742

8.2.2 linux的解压和path 变量设置
8.2.2.1 linux 下载文件后解压

下载文件到 ~/ 目录 ,下面的代码将 tgz 文件解压到sui 目录, 如果你需要

1
2
3
4
5
6
7
8
9
10
ljl@ljl-i5-14400:~$ ls *.tgz
sui-testnet-v1.36.2-ubuntu-x86_64.tgz sui-testnet-v1.37.1-ubuntu-x86_64.tgz
ljl@ljl-i5-14400:~$ mkdir sui
ljl@ljl-i5-14400:~$ cd sui
ljl@ljl-i5-14400:~/sui$ tar xzf ../sui-testnet-v1.37.1-ubuntu-x86_64.tgz
ljl@ljl-i5-14400:~/sui$ ls
move-analyzer sui-bridge sui-data-ingestion sui-faucet sui-node sui-tool
sui sui-bridge-cli sui-debug sui-graphql-rpc sui-test-validator
ljl@ljl-i5-14400:~/sui$ pwd
/home/ljl/sui
  • 注意$ 之前的是命令行提示

  • 最后的pwd看到的目录和你本人用户主目录有关.

    8.2.2.2 设置 path
  • 可以修改/etc/profile 或 ~/.bashrc 文件的尾部,增加一行

  • 注意你将/home/ljl/sui 修改成你的sui 解压的路径

1
export PATH=$PATH:/home/ljl/sui
  • 执行 source ~/.bashrc
8.2.3 mac环境变量设置
mac 的安装类似linux, 最后路径设置需要配置在 /etc/profile

9 otw 有什么用

  • 问题: 见证者对象有什么作用,我在好几个地方看到,只知道怎么用,不知道为什么要这么写

otw 在coin发币的时候用得很多, 因为otw对象智能由package构造,

而参与增加货币相关的方法,都是由一个 参数,这个类型参数是一个OTW参数. 使用OTW 类型做泛型的对象,

只在OTW 模块初始化的时候才能创建,以后都无法创建. One time witness

注意

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Balance<T>{}
struct Supply<T>{}
struct Coin<T>{}
struct TreasureCap<T>{}
/// unique instance of CoinMetadata<T> that stores the metadata for this coin type.
public struct CoinMetadata<phantom T> has key, store {
...
}



module pkg::hk{
public struct HK has drop {},

init(hk:&mut HK, ctx:mut TxContext){
let (cap,meta) = coin::create_currency(hk,&mut ctx);
}
}

创建的货币沾染了一个T, 这个T 是一个OTW struct, 无法创建,无法在外部使用

image-20241111221059544

image-20241111221218590

  • supply对象提供了增长货币余额balance的方法,

  • balance可以转换成coin.

  • sui::balance::create_supply 是最关键的创建货币的方法

  • sui::balance::create_supply 需要一个otw对象做参数.

create_supply 需要一个otw对象. 创建货币 需要Balance,创建Balance 需要Supply, 创建Supply 需要一个otw对象hk

1
2
3
4
5
6
7
8
9
10
11
12
module sui::balance;
public fun balance::create_supply<T: drop>(_: T): Supply<T> {
Supply { value: 0 }
}


public fun increase_supply<T>(self: &mut Supply<T>, value: u64): Balance<T> {
assert!(value < (18446744073709551615u64 - self.value), EOverflow);
self.value = self.value + value;
Balance { value }
}

sui::balance 这个Supply 和Balance 都需要一个OTW类

1
2
3
4
5
6
7
8
9
public struct Supply<phantom T> has store {
value: u64,
}

/// Storable balance - an inner struct of a Coin type.
/// Can be used to store coins which don't need the key ability.
public struct Balance<phantom T> has store {
value: u64,
}

sui::coin 也有一个type arg 需要一个OTW 类

1
2
3
4
5
/// A coin of type `T` worth `value`. Transferable and storable
public struct Coin<phantom T> has key, store {
id: UID,
balance: Balance<T>,
}

我们创建货币之后,查看到的TreasuryCap,持有一个Supply对象,因此持有了增长货币的能力.

image-20241111110333165

货币创建,如下图中的HK 不能手工创建,后面创建的货币,都使用HK 类型参数

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module coin_owner::hk {
use sui::coin::create_currency;
use sui::tx_context::{TxContext, sender};

public struct HK has drop {}

fun init(hk: HK, ctx: &mut TxContext) {
let (treasury_cap, coin_metadata) =
create_currency(
hk,
8,
b"HK",
b"HK made in hongkong",
b"HK made in hongkong",
option::none(),
ctx);
transfer::public_freeze_object(coin_metadata);
let my_address = sender(ctx);
transfer::public_transfer(treasury_cap, my_address)
}
}

货币创建方法 coin::create_currency, 里面需要第一个参数 witness是 One time witness

image-20241111114413049

以下推论:

  1. 定义货币的package publish之后,获得对象id ,成为pkg_id

  2. publish的时候,临时创建了一个 pkg_id::hk::HK struct的对象

  3. coin::create_currency 创建出来了

    Supply<pkg_id::hk::HK> 对象
    这个对象能生成 Balance<pkg_id::hk::HK> 对象
    能生成 Coin<pkg_id::hk::HK>

4 .只有持有Supply<pkg_id::hk::HK> 对象才能发币, 以后无法手工创建Supply<pkg_id::hk::HK> 对象,因为

public fun balance::create_supply<T: drop>(_: T): Supply<T> 需要一个pkg_id::hk::HK 类型的对象做输入参数, package发布之后,无法通过编程创建.

  1. TreasuryCap 只是Supply 对象的一种链上存储形式.

  2. coin::create_currency 需要一个otw 对象, 这个方法才提供了CoinMetadata,才能当做真正的货币.

    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public fun create_currency<T: drop>(
    witness: T,
    decimals: u8,
    symbol: vector<u8>,
    name: vector<u8>,
    description: vector<u8>,
    icon_url: Option<Url>,
    ctx: &mut TxContext,
    ): (TreasuryCap<T>, CoinMetadata<T>) {
    // Make sure there's only one instance of the type T
    assert!(sui::types::is_one_time_witness(&witness), EBadWitness);

    (
    TreasuryCap {
    id: object::new(ctx),
    total_supply: balance::create_supply(witness),
    },
    CoinMetadata {
    id: object::new(ctx),
    decimals,
    name: string::utf8(name),
    symbol: ascii::string(symbol),
    description: string::utf8(description),
    icon_url,
    },
    )
    }

  3. 虽然也可以用balance::createSupply创建其他类型的balance 和coin,但是会当做其他对象看待.

    下文中不使用otw创建的Supply,可以用来创建balance和coin.

    不同用户都可以调用这个mint_to 方法. 创建出来的coin 不能视为代币,使用 sui client balance 不能看到. 因为没有对应的CoinMetadata

    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module strange_coin::jp;
    use sui::balance::{Supply,Balance};
    public struct Other has drop{}
    public struct SupplyHold has key,store{
    id : UID,
    supply :Supply<Other>,
    }
    public entry fun mint_to(amount :u64,to:address,ctx : &mut TxContext){
    let other = Other{};
    let mut supply = sui::balance::create_supply(other);
    let balance = supply.increase_supply(amount);
    let coin = sui::coin::from_balance(balance, ctx);
    let hold = SupplyHold{
    id : object::new(ctx),
    supply,
    };
    transfer::public_transfer(hold,tx_context::sender(ctx));
    transfer::public_transfer(coin,to);
    }

10. move table 对象的链上查看问题

  • 问题查看

move 中 Table 是通过 dynamic field 实现的,在浏览器中似乎没有办法查到 table 中所有的数据,table 对应的那个 object id 在sui浏览器中也无法访问。只是浏览器不支持,还是说不能实现在链下查到 table 里所有的数据。

  • 答复,可以看到动态字段

    10.1 直接保存的table对象

    • 相关代码
    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    module table_example::table_example;
    use sui::table::{Self,Table};
    use std::string::String;

    fun init(ctx : &mut TxContext){
    let mut tb = table::new<String,u32>(ctx);
    tb.add(b"abc".to_string(),1);
    tb.add(b"def".to_string(),2);
    transfer::public_transfer(tb,tx_context::sender(ctx));
    }
    • 查看publish上去的对象

      1
      sui client publish --skip-fetch-latest-git-deps  

      相关log

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
      │ Object Changes │
      ├──────────────────────────────────────────────────────────────────────────────────────────────────┤
      │ Created Objects: │
      │ ┌── │
      │ │ ObjectID: 0x007c0c0b8d4139290a27523bd2645833853bfa12b6278573083abf9a8a6f8f5c │
      │ │ Sender: 0x540105a7d2f5f54a812c630f2996f1790ed0e60d1f9a870ce397f03e4cec9b38 │
      │ │ Owner: Object ID: ( 0x2e05841c8910b5450efc2744ad379a2986c73cd451fcac469833d0083ccacf9c ) │
      │ │ ObjectType: 0x2::dynamic_field::Field<0x1::string::String, u32> │
      │ │ Version: 206232477 │
      │ │ Digest: 3NMXpcaGLVMa81CPJmR6Da7pKB9xdksv3qTcQzwT3Bxb │
      │ └── │
      │ ┌── │
      │ │ ObjectID: 0x2e05841c8910b5450efc2744ad379a2986c73cd451fcac469833d0083ccacf9c │
      │ │ Sender: 0x540105a7d2f5f54a812c630f2996f1790ed0e60d1f9a870ce397f03e4cec9b38 │
      │ │ Owner: Account Address ( 0x540105a7d2f5f54a812c630f2996f1790ed0e60d1f9a870ce397f03e4cec9b38 ) │
      │ │ ObjectType: 0x2::table::Table<0x1::string::String, u32> │
      │ │ Version: 206232477 │
      │ │ Digest: Ei39HsytUhDxrxWPX5uYohJ7CsSWbysxqvbTCEVKhZEf │
      │ └──
    • suiscan浏览

      1
      https://suiscan.xyz/testnet/object/0x2e05841c8910b5450efc2744ad379a2986c73cd451fcac469833d0083ccacf9c

image-20241110234724177

  • 相关字段
{.line-numbers}
1
2
3
4
5
6
7
8
/// Adds a key-value pair to the table `table: &mut Table<K, V>`
/// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with
/// that key `k: K`.
public fun add<K: copy + drop + store, V: store>(table: &mut Table<K, V>, k: K, v: V) {
field::add(&mut table.id, k, v);
table.size = table.size + 1;
}

dynamic field, 通过table address 和 属性名,一起计算字段.

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public fun add<Name: copy + drop + store, Value: store>(
// we use &mut UID in several spots for access control
object: &mut UID,
name: Name,
value: Value,
) {
let object_addr = object.to_address();
let hash = hash_type_and_key(object_addr, name);
assert!(!has_child_object(object_addr, hash), EFieldAlreadyExists);
let field = Field {
id: object::new_uid_from_hash(hash),
name,
value,
};
add_child_object(object_addr, field)
}

table对象作为字段在 suivision 可以查看

Sui Object

1
https://testnet.suivision.xyz/object/0x0a6a64b85ce448afa134c0634487bdc7bb6d4f5a621237dc92e3f1671a2467fd
1
2
3
4
5
6
public struct MyStore  has key ,store{
id:UID,
total_balance : Balance<SUI>,
received: Table<address,u64> ,
}

image-20241111141423819

相关对象定义:

{.line-numbers}
1
2
3
4
5
6
public struct MyStore  has key ,store{
id:UID,
total_balance : Balance<SUI>,
received: Table<address,u64> ,
}

但是相关动态字段的确看不到

11 sui 钱包地址和client地址

钱包地址与 sui 客户端地址的关系

  • 两个不同得地址,可以认为你有两个钱包,每个钱包有不同得密码, 一般大家采用帮助记忆的12个单词来记忆

  • 可以做钱包A中的coin 转移 到钱包B

    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #客户端钱包的地址,
    export C_ADDR="`sui client active-address`"
    echo $C_ADDR

    # 浏览器钱包的地址,可以修改成你的钱包地址
    export W_ADDR=0xafe36044ef56d22494bfe6231e78dd128f097693f2d974761ee4d649e61f5fa2
    echo $W_ADDR

    # 可以查到C_ADDR 的coin
    sui client gas $C_ADDR

    ljl@ljl-i5-14400:~$ sui client gas $C_ADDR
    ╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
    │ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
    ├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
    │ 0xa957fcfae2a624189f4ee5e24d07be6f2e492668c8a8a3a810bb684c7ae25300 │ 3667910060 │ 3.66 │
    │ 0xb2fbb77f3168b61d21ac6c005086a1747ce451df29ade1be057c4eefcd17493b │ 1000000000 │ 1.00 │
    │ 0xda2e7328acd44f9167e306f18debf134f4200f9339241d904bc2bdd18266b00d │ 1000000000 │ 1.00 │
    ╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

    sui client balance $W_ADDR
    # 根据输出的gasCoinId 发送给W_ADDR
    export COIN=0xda2e7328acd44f9167e306f18debf134f4200f9339241d904bc2bdd18266b00d

    sui client transfer --to $W_ADDR --object-id $COIN

    # 查看钱包中的coin

    sui client gas $W_ADDR
    ╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
    │ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
    ├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
    │ 0x2cbbd85ae56bb9d95746797283a6ef045fdad20d76bfe5690097f6467f96693a │ 997666048 │ 0.99 │
    │ 0x761035a215b60f6d3c08dff837b85a67f1e2620ba63a0904785924d37095c201 │ 922766720 │ 0.92 │
    │ 0xda2e7328acd44f9167e306f18debf134f4200f9339241d904bc2bdd18266b00d │ 1000000000 │ 1.00 │


    sui client balance $W_ADDR

我们每次做publish或move-call,都需要区块链网络计算和存储,这些计算和存储需要消耗gas值。

image-20241110231811470

12 查看当前支持的网络

1
2
3
4
5
6
7
8
9
 sui client envs
╭─────────┬─────────────────────────────────────┬────────╮
alias │ url │ active │
├─────────┼─────────────────────────────────────┼────────┤
│ testnet │ https://fullnode.testnet.sui.io:443 │ * │
│ devnet │ https://fullnode.devnet.sui.io:443 │ │
local │ http://127.0.0.1:9000 │ │
│ mainnet │ https://fullnode.mainnet.sui.io:443 │ │
╰─────────┴─────────────────────────────────────┴────────╯

配置网络devnet

1
sui client new-env --alias devnet --rpc https://fullnode.devnet.sui.io:443

如果没有devnet

1
sui client switch --env  devnet

11 自动publish到了devnet上面,不知道怎么回事,scan上面在devnet上能搜到id,但是source code显示不出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#  查看当前的环境
sui client envs
╭─────────┬─────────────────────────────────────┬────────╮
│ alias │ url │ active │
├─────────┼─────────────────────────────────────┼────────┤
│ testnet │ https://fullnode.testnet.sui.io:443 │ │
│ devnet │ https://fullnode.devnet.sui.io:443 │ * │
│ local │ http://127.0.0.1:9000 │ │
│ mainnet │ https://fullnode.mainnet.sui.io:443 │ │
╰─────────┴─────────────────────────────────────┴────────╯
# 带星号的是当前的环境
sui client active-env
# 切换环境到testnet
sui client switch --env testnet
Active environment switched to [testnet]

13 ability

  • 问题: move的基础语法不熟悉,比如::是什么意思,drop、key这些都是什么含义等等,感觉可以边helloworld边讲解一下语法,每个词对应的意思,为什么需要这些

参看 move-book.com ,可以使用浏览器翻译查看

对象的4中能力(abliity)

这四种能力是:

  • copy

    • 允许复制具有此功能的类型的值。
  • drop

    • 允许跳出作用域自动删除
  • store

    • 允许具有此功能的类型的值存在于 链上中的值中。 存在sui 网络上的对象的字段
    • 对于 Sui,控制对象内部可以存储哪些数据。 还控制哪些类型可以在其定义模块之外传
  • key

    • 允许类型用作存储的 “key”。表面上,这意味着该值可以是 存储中的顶级价值;换句话说,它不需要包含在另一个值中,以 在仓库里。

    • 对于 Sui,用于表示对象

13.1 对象的几种存储状态:

13.1.1 销毁:
  • 离开作用域自动销毁,具有drop能力

    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    public struct IgnoreMe has drop{
    a:u32,
    b:u32
    }

    #[test]
    fun test_ignore(){
    let im = IgnoreMe{a:3,b:4};
    print(&im);
    }

    上文定义的IgnoreMe 对象im 具有自动析构的能力has drop,在离开函数的作用域时候自动析构.

  • 编码主动析构

{.line-numbers}
1
2
3
4
5
6
7
8
9
public struct NoDrop{ value :u64 }

#[test]
fun test_nodrop(){
let no_drop = NoDrop{ value :34};
print(&no_drop);

let NoDrop{ value: _ } = no_drop;
}

这个例子 NoDrop类型没有drop能力,对象离开作用域,需要析构,或者将对象的所有权转移.

  • 第8行是析构对象的代码
13.1.2 转移:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
fun useNoDrop(o : NoDrop ) : NoDrop{
std::debug::print(&o);
o
}

#[test]
fun testUseNoDrop(){
let o = NoDrop{value :4};
let d = useNoDrop(o);
NoDrop{value:_} = d;
}
  • 第1行函数UseNoDrop获得了对象o
  • 第3行,函数将N哦Drop对象o 返回出去,将所有权转移出去.
  • 第10 行,显示代码析构这个NoDrop对象
13.1.3 key:独立存储在链上:

对象独立存储在链上,必须具有key能力 has key

image-20241114085958845

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
public struct Keyable has key{
id : UID,
}

#[test]
fun test_key(){
let mut ctx = tx_context ::dummy();
let k = Keyable{ id: object::new(&mut ctx)};
std::debug::print(&k);
transfer::transfer(k,ctx.sender());
}

这个例子中Keyable是具有key能力的,可以通过transfer传递到sui网络上存储, 指定owner地址.

13.1.4 对象作为 共享对象存储在链上,需要具有key能力

image-20241114091033160

13.1.4 对象作为一个对象的一部分存储,需要具有store能力
{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use std::string::String;
public struct Grandson has store{
name : String,
}
public struct Child has store{
name : String,
child : Grandson,
}
public struct Parent has key{
id: UID,
child: Child,
}
#[test]
fun test_store_child(){
let mut ctx = tx_context::dummy();
let foo = Parent {
id : object::new(&mut ctx),
child: Child {
name : b"one child".to_string(),
child: Grandson{
name : b"a grandson".to_string(),

},
}
};
std::debug::print(&foo);
transfer::freeze_object(foo);
}
  • Parent是独立存储,具有key能力

  • Child 都是作为独立存储对象Parent的字段

  • Grandson 作为Child的字段存储在链上,

  • Child 和Grandson 都具有store能力

image-20241114095326972

原文链接:https://blog.csdn.net/ifadai/article/details/136046263
类型技能 - Move 参考

13.2 四种能力

这四种能力是:

  • copy

    • 允许复制具有此功能的类型的值。
  • drop

    • 允许跳出作用域自动删除
  • store

    • 允许具有此功能的类型的值存在于 链上中的值中。 存在sui 网络上的对象的字段
    • 对于 Sui,控制对象内部可以存储哪些数据。 还控制哪些类型可以在其定义模块之外传
  • key

    • 允许类型用作存储的 “key”。表面上,这意味着该值可以是 存储中的顶级价值;换句话说,它不需要包含在另一个值中,以 在仓库里。

    • 对于 Sui,用于表示对象

13.2.1 如果有copy能力,赋值就是拷贝成一个新对象

image-20241111194804064

  • 上图中27行,c对象被拷贝到d,c和d指向的不是同一个对象,

  • 28 行修改了d,但是c没有被修改

  • 32行和33行中,c和d取值不同.

13.2 如果没有copy 能力,赋值就是移动对象

image-20241111194357890

13.3 如果没有ability,就必须在module内提供方法去析构它

1

image-20241114080921239

image-20241111213310276

  • 定义闪电贷的出借方,在borrow的时候获得Coin和 Loan对象,
  • 在repay的时候才能销毁Loan对象, 如果借出放不调用repay,无法销毁Loan对象,交易会回滚.
  • replay的时候需要校验传入的coin 要多于borrow的coin, 多出来的部分作为回报.
    • 下面是 贷方代码的例子
{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
module book::loan;
//hot potato pattern
public struct Loan{
feedback: u64, //还钱数
}

public struct Coin has key,store{
id:UID,
amount:u64
}

//借出钱
public fun borrow(amount:u64,ctx:&mut TxContext) :(Coin,Loan){
let feedback = amount * 103 /100;
let c = Coin{ id: object::new(ctx),amount};
(c, Loan{feedback})
}

const ErrNotEnoughFeedback:u64 = 43;
const OWNER_ADDR :address = @0xafe36044ef56d22494bfe6231e78dd128f097693f2d974761ee4d649e61f5fa2;//todo

public fun repay(coin: Coin,loan:Loan){
assert!(coin.amount >= loan.feedback,ErrNotEnoughFeedback);
transfer::public_transfer(coin,OWNER_ADDR);
Loan{ feedback:_} = loan;
}

``` {.line-numbers}

下面是借方模块的代码,使用测试代码演示

```c++
#[test_only]
module book::test_loan;
use book::loan::Coin;

////todo 贷款后赚钱的业务逻辑,这里没有实现
fun earn_money(coin : Coin ) :Coin{
coin
}


#[test]
#[expected_failure(abort_code=book::loan::ErrNotEnoughFeedback)]
fun borrow_test(){
let mut ctx = tx_context::dummy();
let (coin,loan) = book::loan::borrow(233,&mut ctx);
let new_money = earn_money(coin);
book::loan::repay(new_money,loan);
//todo 赚钱了,可以把feedback-amount 的钱,存入自己钱包
}

13.5 key ability

目前其他模块存到链上的对象,都需要key+store能力

transfer::public_transfer

transfer::public_freeze_object

transfer::public_share_object

image-20241111213712296

store

可以作为存在链上对象的一个字段.

image-20241111213346673

11 切割coin

下例 从当前的余额中分割出来 0.05 个sui , 然后传给当前地址

{.line-numbers}
1
2
3
4
5
6
7
export C_ADDR=`sui client  active-address`

sui client ptb \
--split-coins gas [1000] \
--assign coin \
--transfer-objects [coin] @$C_ADDR \
--gas-budget 50000000

相关问题

2024年11月10日 11:49 adadadadadadad 吾妻天依美如画
2024年11月10日 11:52 完全按照视频中操作,却与视频中得到的结果不一样,可能是版本和系统不一样吧,但自己又不会调试
2024年11月10日 11:57 小白提问预编译的下载包如何使用 krypton
2024年11月10日 12:00 见证者对象有什么作用,我在好几个地方看到,只知道怎么用,不知道为什么要这么写 柠栀gardenia
2024年11月10日 14:02 move 中 Table 是通过 dynamic field 实现的,在浏览器中似乎没有办法查到 table 中所有的数据,table 对应的那个 object id 在sui浏览器中也无法访问。只是浏览器不支持,还是说不能实现在链下查到 table 里所有的数据。 Allen
2024年11月10日 16:17 钱包地址与 sui 客户端地址的关系 罗基
2024年11月10日 21:05 git pull pull不下来,报错过早文件结束 啊吧啊吧
2024年11月11日 12:41 自动publish到了devnet上面,不知道怎么回事,scan上面在devnet上能搜到id,但是source code显示不出 洗头就是爽
2024年11月11日 14:15 move的基础语法不熟悉,比如::是什么意思,drop、key这些都是什么含义等等,感觉可以边helloworld边讲解一下语法,每个词对应的意思,为什么需要这些 周雨阳

附录:

最近在参加HOH 共学活动,

💧 HOH水分子公众号

🌊 HOH水分子X账号

📹 课程B站账号

💻 Github仓库 https://github.com/move-cn/letsmove

2024 -11-20 sui 存钱罐的权限控制和pacakge升级带来的风险

1.概述

  • 假设写一个存钱罐合约,可以从合约中存入货币,提取货币,
    • 存钱不控制权限,每个人都能存
    • 只有拥有AdminCap对象的人才能取钱。
      • 取钱函数有一个AdminCap参数,sui 判断谁拥有Admincap 对象才能调用 withdraw
    • 合约构造者A可以将AdminCap对象 转让给B
      • 表面上只有B 能获得权限取钱
  • 风险
    • 合约构造者还拥有升级合约的权限,可能通过升级合约来获取新的取钱权限

2. 合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
module upgrade_demo::upgrade_demo ;
use sui::object::{Self, UID};
use sui::transfer;
use sui::balance::{Self, Balance};
use sui::coin::{Self, Coin};
use sui::sui::SUI;
use sui::tx_context::{Self, TxContext};

// 错误码
const ENotAdmin: u64 = 0;
const EInsufficientBalance: u64 = 1;

// 管理员权限凭证
public struct AdminCap has key, store {
id: UID
}

// 金库结构
public struct Treasury has key {
id: UID,
balance: Balance<SUI>
}

// 初始化函数 - 创建管理员凭证和金库
fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap {
id: object::new(ctx)
};

let treasury = Treasury {
id: object::new(ctx),
balance: balance::zero(),
};

// 将AdminCap转移给部署合约的地址
transfer::transfer(admin_cap, tx_context::sender(ctx));
// 共享金库对象
transfer::share_object(treasury);
}

// 存入SUI代币
public fun deposit(treasury: &mut Treasury, payment: Coin<SUI>) {
sui::balance::join(&mut treasury.balance, payment.into_balance());

}

// 管理员提取SUI代币
public fun withdraw(
treasury: &mut Treasury,
amount: u64,
_admin_cap: &AdminCap,
ctx: &mut TxContext
): Coin<SUI> {
// 检查余额是否充足
assert!(balance::value(&treasury.balance) >= amount, EInsufficientBalance);

// 从金库中提取代币
coin::from_balance(balance::split(&mut treasury.balance, amount), ctx)
}

// 查询金库余额
public fun balanceOf(treasury: &Treasury): u64 {
balance::value(&treasury.balance)
}

3. 发布合约

  • 获得对象
1
sui move   --skip-fetch-latest-git-deps  publish
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes │
├────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects: │
│ ┌── │
│ │ ObjectID: 0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c ) │
│ │ ObjectType: 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89::upgrade_demo::AdminCap │
│ │ Version: 245323791 │
│ │ Digest: BLp2DjwNMD1fZ4Y4HxNMRz1AQq28afgMQ57pNFt4BZoa │
│ └──

│ ┌── │
│ │ ObjectID: 0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0 │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c ) │
│ │ ObjectType: 0x2::package::UpgradeCap │
│ │ Version: 245323791 │
│ │ Di


┌── │
│ │ ObjectID: 0x830243c107ff3ad19e4506d9a61d04af4a0c35771445e468c3a7befbc6d9293c │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Shared( 245323791 ) │
│ │ ObjectType: 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89::upgrade_demo::Treasury │
│ │ Version: 245323791 │
│ │ Digest: Enq9zenKrY4r3fXYEWr9XJCcA8FixSTZhKbVqXqDueNM
  • 对象说明
对象名 取值 说明
package address 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89 合约包的地址
upgradeCap 0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0 控制升级的权限
adminCap 0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf 控制提取Treasury余额
owner 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c 目前合约发布者拥有adminCap,upgradeCap
treasury 0x830243c107ff3ad19e4506d9a61d04af4a0c35771445e468c3a7befbc6d9293c 存钱罐
  • 查看当前用户的coin

    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    sui client gas
    ╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
    │ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
    ├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
    │ 0x09119914c43f80a486b30cc5d4f1382d0b57929eabd70f116ae4e918d9c89bf0 │ 470927364 │ 0.47 │
    │ 0xae3da3cb598610db16ea040ad6b09cfd324e146a2a7e5b18ccfa76b34b8d3b22 │ 497664604 │ 0.49 │
    │ 0xeda78a0ba9b2ef153a209f51380d8e77af0f35c60eb8ea26d4239c639844680d │ 995399888 │ 0.99 │
    ╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

4. A用户存钱

  • 配置一些变量,方便后面的脚本使用
1
2
3
4
5
6
export PKG=0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89
export ADMIN_CAP=0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf
export UPGRADE_CAP=0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0
export TREASURY=0x830243c107ff3ad19e4506d9a61d04af4a0c35771445e468c3a7befbc6d9293c
export ADDR_A=0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c
export ADDR_B=0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看当前用户有哪些coin
sui client switch --address $ADDR_A
sui client gas
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x09119914c43f80a486b30cc5d4f1382d0b57929eabd70f116ae4e918d9c89bf0 │ 470927364 │ 0.47 │
│ 0xae3da3cb598610db16ea040ad6b09cfd324e146a2a7e5b18ccfa76b34b8d3b22 │ 497664604 │ 0.49 │
│ 0xeda78a0ba9b2ef153a209f51380d8e77af0f35c60eb8ea26d4239c639844680d │ 995399888 │ 0.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

# 根据前面的输出,设置需要存入的coin
export COIN_A=0xae3da3cb598610db16ea040ad6b09cfd324e146a2a7e5b18ccfa76b34b8d3b22
# 存入货币
sui client ptb --move-call $PKG::upgrade_demo::deposit "@$TREASURY,@$COIN_A"

5. A 用户取钱

1
$ sui client ptb --move-call $PKG::upgrade_demo::deposit   "@$TREASURY @0xae3da3cb598610db16ea040ad6b09cfd324e146a2a7e5b18ccfa76b34b8d3b22"
  • 取回5000单位

    1
    2
    3

    sui client ptb --move-call $PKG::upgrade_demo::withdraw "@$TREASURY 50000 @$ADMIN_CAP"
    sui client ptb --move-call $PKG::upgrade_demo::withdraw "@$TREASURY 50000 @$ADMIN_CAP" --assign coin1 --transfer-objects [coin1] @$ADDR_A
  • 查看当前A用户拥有的coin

1
2
3
4
5
6
7
8
sui client gas $ADDR_A
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x09119914c43f80a486b30cc5d4f1382d0b57929eabd70f116ae4e918d9c89bf0 │ 466808880 │ 0.46 │
│ 0xd91123cfc325ba1eff95e839837a1bf443046cc00a72da5192a005ffea1a7117 │ 50000 │ 0.00 │
│ 0xeda78a0ba9b2ef153a209f51380d8e77af0f35c60eb8ea26d4239c639844680d │ 995399888 │ 0.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

6. A 用户转移adminCap 给B

1
2
3
4
5
6
7
8
9
10
11
12
sui client ptb --transfer-objects [@$ADMIN_CAP] @$ADDR_B

# 显示ADMIN_CAP 对象的owner是0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432

│ ┌── │
│ │ ObjectID: 0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432 ) │
│ │ ObjectType: 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89::upgrade_demo::AdminCap │
│ │ Version: 245323796 │
│ │ Digest: 6QpfZqgrTFjTcaySHPrfS5cDE1K1L5xvuWDQRxh1vumS │
│ └──

7. A用户 再次取款,预期会失败

1
2
sui client ptb --move-call $PKG::upgrade_demo::withdraw "@$TREASURY 50000 @$ADMIN_CAP" --assign coin1 --transfer-objects [coin1] @$ADDR_A --gas-budget 30000000
RPC call failed: ErrorObject { code: ServerError(-32002), message: "Transaction validator signing failed due to issues with transaction inputs, please review the errors and try again:\n- Transaction was not signed by the correct sender: Object 0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf is owned by account address 0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432, but given owner/signer address is 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c\n- Could not find the referenced object 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89 at version None", data: None }

8. 切换B 用户,取款成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 切换到ADDR_B
$ sui client switch --address $ADDR_B
Active address switched to 0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432
# 调用withdraw取款
$ sui client ptb --move-call $PKG::upgrade_demo::withdraw "@$TREASURY 50000 @$ADMIN_CAP" --assign coin1 --transfer-objects [coin1] @$ADDR_B

# 查看获得50000 mist的coin
$ sui client gas
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x015a3cb7a04cdcd7631e4056f56e8a55b77a1a7d05dc7da6f30e9ac705d19b7c │ 50000 │ 0.00 │
│ 0x418760c10d6fe9ecaa2594803848412c834f58983f1be03b26962a22dd84f9e5 │ 995949216 │ 0.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

9. A 用户发起攻击,升级合约包, 新建AdminCap对象adminCap2

9.1 修改合约代码,新构造AdminCap对象

1
2
3
public fun mintCap(ctx: &mut TxContext): AdminCap {
AdminCap{id: object::new(ctx)}
}

9.2 升级合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 切换为A
sui client switch --address $ADDR_A
Active address switched to 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c
# A 升级合约
sui client upgrade --upgrade-capability $UPGRADE_CAP --gas-budget 100000000 --skip-fetch-latest-git-deps
# 升级对象变更,package版本增长 ,版本id 变成了 0xc163e33f376cae7a90b0c56b49af9a0aa387c210792d6c76aba0a12a22de8869
老的版本id是0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89
│ ┌── │
│ │ ObjectID: 0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0 │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c ) │
│ │ ObjectType: 0x2::package::UpgradeCap │
│ │ Version: 245323797 │
│ │ Digest: 64rX9ChLmX5mupim2UqRGUoKnHD3wTiiYg4uXxzjvppc │
│ └── │
│ Published Objects: │
│ ┌── │
│ │ PackageID: 0xc163e33f376cae7a90b0c56b49af9a0aa387c210792d6c76aba0a12a22de8869 │
│ │ Version: 2 │
│ │ Digest: 8aWACYMu9nLjAG3MHQGteXMsNiBUY5xCt6KhKmMEmrTX │
│ │ Modules: upgrade_demo │
│ └──
  • 新发布的的包 id发生改变

    0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0

9.3 发起攻击

用户用新创建的adminCap2 来取钱

初始的package

image-20241120130414556

  • 1 pacakge object id
  • 2 版本
  • 3 代码中的包地址。

升级后的packageid发生变更

image-20241120125933911

  • 1 package object id
  • 2 版本
  • 3 代码中的package地址

但是从代码看,package地址 还是最初发布的地址,version=2, 代码module后面的地址不变。 但是objectid发生变更(图1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$  export PKG2=0xc163e33f376cae7a90b0c56b49af9a0aa387c210792d6c76aba0a12a22de8869
$ sui client ptb --move-call $PKG2::upgrade_demo::mintCap --assign new_cap --transfer-objects ["new_cap" ] @$ADDR_A

Created Objects: │
│ ┌── │
│ │ ObjectID: 0xb71d4b11e35d769bcdefa42f9706af118b76b2ab2896fe2a294639e19f52d816 │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c ) │
│ │ ObjectType: 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89::upgrade_demo::AdminCap │
│ │ Version: 245323798 │
│ │ Digest: 8xio19V93oLgmF8jLxWKoCfgCpSitFJqYLLLTbN9Wc77 │
│ └──
$ export ADMIN_CAP2=0xb71d4b11e35d769bcdefa42f9706af118b76b2ab2896fe2a294639e19f52d816
$ sui client ptb --move-call $PKG2::upgrade_demo::withdraw "@$TREASURY 6000 @$ADMIN_CAP2" \
--assign coin2 --transfer-objects [coin2] @$ADDR_A
# 偷取成功,获得一个6000mist的coin
$ sui client gas $ADDR_A
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x09119914c43f80a486b30cc5d4f1382d0b57929eabd70f116ae4e918d9c89bf0 │ 450920664 │ 0.45 │
│ 0x33233ad9e71a8ef079b7c1c58ac9c99a263d04620f585d37e1ae94b4bd64d538 │ 6000 │ 0.00 │
│ 0x64502383e84a24dda2e531ec0cac4d8123c9c4d7820451a1b42a02eae1888bea │ 50000 │ 0.00 │
│ 0xd91123cfc325ba1eff95e839837a1bf443046cc00a72da5192a005ffea1a7117 │ 50000 │ 0.00 │
│ 0xeda78a0ba9b2ef153a209f51380d8e77af0f35c60eb8ea26d4239c639844680d │ 995399888 │ 0.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
1
2
3
4
export PKG=0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89
export UPGRADE_CAP=0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915嗯嗯

$ sui client upgrade --upgrade-capability $UPGRADE_CAP --package-id $PKG --gas-budget 100000000

对策:

方法1

  • 转移权限时,将UpgradeCap也转移

方法2

  • 在 Treasury 中添加adminCap的id。

  • 在取款的时候做校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

// 金库结构
public struct Treasury has key {
id: UID,
balance: Balance<SUI>,
+ admin_id : ID,
}

// 初始化函数 - 创建管理员凭证和金库
fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap {
id: object::new(ctx)
};

let treasury = Treasury {
id: object::new(ctx),
balance: balance::zero(),
+ admin_id: *admin_cap.id.as_inner(),
}
...


// 管理员提取SUI代币
public fun withdraw(
treasury: &mut Treasury,
amount: u64,
admin_cap: &AdminCap,
ctx: &mut TxContext
): Coin<SUI> {
+ assert!(admin_cap.id.as_inner() == treasury.admin_id, ENotAdmin);
...

附录:

最近在参加HOH 共学活动,

💧 HOH水分子公众号

🌊 HOH水分子X账号

📹 课程B站账号

💻 Github仓库 https://github.com/move-cn/letsmove

sui coin 的一些概念 设计思想和练习

1. 货币具有如下特征, sui coin 实现对应的特征

1.1 货币的特征

  • 发币权限

    • 控制发布权限
      • 一般来说,我们需要控制货币的发行量, 一般限制特定的人或组织才能发型货币.
        • 若货币无限制发型, 货币就没有价值
    • sui 使用TreasuryCap 对象的所有权来控制货币的发行权
    • 共享发币权限—faucet
      • 这种情况只用作测试 本文不做讨论。
  • 货币的总供应量

    • sui 采用 Supply
      • 能增加发行量, 提供新的余额Balance , increase_supply
      • 能回收货币余额,减少货币供应 decrease_supply
  • coin货币的余额(balance) , 也就是:面值几块钱

    • 数字货币,余额可以变动
    • 可以3块的货币,拆成 2.5元 和0.5元 两个coin
  • coin 货币有特征, 人民币之所以是人民币,不是美元,是因为人民币有独特的图形

    • 对应 CoinMetadata

1.2 Sui Coin相关对象类型关系图

image-20241116221238292

- create_currency 是货币创建的函数, 返回一个TreasuryCap 对象和一个CoinMetadata对象
- CoinMetadata 表示货币的小数位,名称,图标.
- TreasuryCap 持有一个Supply对象,这个Supply是实现货币增长和减少的关键对象.
- Supply 能提供货币余额 Balance, 同时增加总货币供应量.
- Coin 表示可以保存在链上的货币
- Balance 可以转换成Coin 存储到链上,转移给某个用户
- Coin也可以转换成Balance  余额,Balance没有key能力, 但是具有store能力,可以作为其他对象的字段.
- Coin 和Balance 可以通过join(两个币合成一个),split( 一个币分成2个)

1.3 CoinMetadata ,

可以认为是一种货币的展现形式, 例如没有,就是这个样子的.

通过这个数据结构,告诉我的货币是美元,就能展现成美元的形式.

电影《无双》中为什么画家需要手绘美元母版? - 知乎

或者日元:

img

1.4 我们定义的货币Coin , 和CoinMetadata 是怎么关联上的呢**?

  • 是根据他们的泛型参数T 关联的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/// A coin of type `T` worth `value`. Transferable and storable
public struct Coin<phantom T> has key, store {
id: UID,
balance: Balance<T>,
}

/// Each Coin type T created through `create_currency` function will have a
/// unique instance of CoinMetadata<T> that stores the metadata for this coin type.
public struct CoinMetadata<phantom T> has key, store {
id: UID,
/// Number of decimal places the coin uses.
/// A coin with `value ` N and `decimals` D should be shown as N / 10^D
/// E.g., a coin with `value` 7002 and decimals 3 should be displayed as 7.002
/// This is metadata for display usage only.
decimals: u8,
/// Name for the token
name: string::String,
/// Symbol for the token
symbol: ascii::String,
/// Description of the token
description: string::String,
/// URL for the token logo
icon_url: Option<Url>,
}

2. 货币的诞生:

2.1 货币诞生相关函数的启发

参看 0x2:sui::coin 的创建货币的代码,需要一个otw对象.
image-20241111221059544

货币诞生需要这个泛型参数witness丢想是一个One Time Witness struct 对象

  • 这个结构体只能在模块同名大写, 并且由系统控制只构造一次

    例如 0x2::sui::SUI
    0X433 :: rmb::RMB

  • 不能通过编码构造这个对象

  • 避免通过升级package,增加一个函数 来构造otw 对象

这个例子中 witness 对象 只会在是一个OTW 对象,这个对象在模块初始化的时候构建,传入这个函数之后,在这个函数结束后销毁,因为它是移动所有权传递(没有copy ability), 同时具有drop ability.

这个方法执行的结果,会让这个货币相关查到Coin,CoinMetadata ,TreasuryCap ,Supply,Balance 这一系列对象,都有个类型参数type-argwitness参数的类型.

2.2 示例:发行一种日元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module coin_jp::jp{
public struct JP has drop{ }
const JpUrl:vector<u8> = b"https://tse4-mm.cn.bing.net/th/id/OIP-C.XVkbV--98d7_YfeLR2a_fAHaHa";

fun init( jp :JP , ctx : &mut TxContext)
{
let (treasuryCap,coinMetadata) = sui::coin::create_currency(
jp,
0,
b"YEN",
b"YEN of Japan",
b"Japanese money",
option::some(sui::url::new_unsafe_from_bytes(JpUrl)),
ctx);

transfer::public_transfer(treasuryCap,ctx.sender());
transfer::public_freeze_object(coinMetadata);
}
}
  • 注意模块名jp和 struct JP 的对应关系,OTW 结构体是模块名的大写. 这个结构体JP ,就是一个OTW 结构体,
  • init函数传入的jp对象,是package在部署的时候自动构造出来的对象.
  • 以后无法通过升级package来重新构建一个JP 类型的对象.这就保证了铸币能力 TreasuryCap.total_supply的唯一性.

这个定义,等于设置了一个日元的外观定义(CoinMetadata), 日元的增发权限 TreasuryCap , 以后发行的日元,余额, 都有特性花纹:

1
coin_jp::jp::JP

这个coin_jp是地址,当move package应该在发布的时候,确定其地址取值.

2.3 新建货币流程

image-20241116225504184

发布这个合约就是诞生了一种货币,我们其实只是定义了一种铸币权(包括货币增加 减少的能力), 和货币的样式 CoinMetadata, (方便在suiscan上浏览它)

3.货币的增发

增加货币(mint)流程

image-20241116231338675

1. 客户端发起mint_and_transfer 的rpc调用 。 客户端需要拥有访问TreasuryCap对象的权利 (增发货币的权力)
1. TreasuryCap 调用mint 方法还增加货币
1. Treasury 调用它的total_supply对象的increase_supply 方法
1. Supply 在balance 模块,能构建同一模块的Balance对象
1. Supply获得Balance对象
1. 返回Balance对象给TreasuryCap对象
1. TreasuryCap对象,用Balance对象来构造 coin模块中的Coin对象
1. 返回Coin对象
1. 通过public_transfer方法,讲Coin存到链上,归属给mint_and_transfer 参数中的地址。#

4. 发布合约,增发货币的调用脚本:

4.1 发布合约 脚本

1
sui client publish --skip-fetch-latest-git-deps --skip-dependency-verification
1
2
3
4
5
6
7
8
查看输出结果中的 0x2::coin::TreasuryCap 对象
│ ObjectID: 0x8e1bebaac6dd33bf486fb294981f13fb6946051f465db0c25401b61b01f7421e │
│ │ Sender: 0x540105a7d2f5f54a812c630f2996f1790ed0e60d1f9a870ce397f03e4cec9b38 │
│ │ Owner: Account Address ( 0x540105a7d2f5f54a812c630f2996f1790ed0e60d1f9a870ce397f03e4cec9b38 ) │
│ │ ObjectType: 0x2::coin::TreasuryCap<0x65a0995fd59c97b94fd975a3fb42c745cdff54711c1caae2934858e6eb99adb4::jp::JP> │
│ │ Version: 236167313 │
│ │ Digest: BnpekhzZQWQnR77hpGMZyhafRrMe57AyJainuFX8xEjg

相关对象标识, 为后续增发提供变量.

对象名 export name 对象值
package地址 PKG 0x65a0995fd59c97b94fd975a3fb42c745cdff54711c1caae2934858e6eb99adb4
0x2::coin::TreasuryCap 对象id JP_CAP 0x8e1bebaac6dd33bf486fb294981f13fb6946051f465db0c25401b61b01f7421e

4.2 调用合约 增发货币

1
2
3
4
5
6
7
8
9
10
11
12

# 根据前面的package地址和
export PKG=0x65a0995fd59c97b94fd975a3fb42c745cdff54711c1caae2934858e6eb99adb4
export JP_CAP=0x8e1bebaac6dd33bf486fb294981f13fb6946051f465db0c25401b61b01f7421e

# 获取当前激活的地址
export C_ADDR=`sui client active-address`

# 执行铸币,transfer给本地址
sui client call --package 0x2 --module coin --function mint_and_transfer --type-args $PKG::jp::JP --args $JP_CAP 8800000 $C_ADDR


查看输出结果,有创建的0x2::coin::Coin 对象.0x95245c704b124aa96f49ab1cdd459c7219d1dc1165faaeae80a514524e8308e2

1
2
3
4
5
6
7
8
9
10
11
 Created Objects:                                                                                                   │
│ ┌── │
│ │ ObjectID: 0x95245c704b124aa96f49ab1cdd459c7219d1dc1165faaeae80a514524e8308e2 │
│ │ Sender: 0x540105a7d2f5f54a812c630f2996f1790ed0e60d1f9a870ce397f03e4cec9b38 │
│ │ Owner: Account Address ( 0x540105a7d2f5f54a812c630f2996f1790ed0e60d1f9a870ce397f03e4cec9b38 ) │
│ │ ObjectType: 0x2::coin::Coin<0x65a0995fd59c97b94fd975a3fb42c745cdff54711c1caae2934858e6eb99adb4::jp::JP> │
│ │ Version: 236167314 │
│ │ Digest: Gh2shitikPnbxxffpPj5mnWwrUCNJChEPyCNaBB2YHMV



4.3 使用命令查看余额

1
2
3
# 查看获得货币
sui client balance $C_ADDR

1
2
3
4
5
6
7
8
9
10
11
ljl@ljl-i5-14400:~/work/sui/move-cn/letsmove/mover/nextuser/code$ sui client balance
[warn] Client/Server api version mismatch, client api version : 1.37.1, server api version : 1.37.2
╭──────────────────────────────────────────────╮
│ Balance of coins owned by this address │
├──────────────────────────────────────────────┤
│ ╭──────────────────────────────────────────╮ │
│ │ coin balance (raw) balance │ │
│ ├──────────────────────────────────────────┤ │
│ │ Sui 3637300968 3.63 SUI │ │
│ │ YEN of Japan 8800000 8.80M YEN │ │
│ ╰──────────────────────────────────────────╯ │

4.4 sui scan上查看coin对象

https://suiscan.xyz/testnet/object/0x95245c704b124aa96f49ab1cdd459c7219d1dc1165faaeae80a514524e8308e2

image-20241116223844785

1 . 能看到这个泛型的T 参数是模型名jp和结构体名JP的相似性

  1. owner是铸币时传递的地址
  2. 显示了货币的余额与图标

5. 附录:

最近在参加HOH 共学活动,

💧 HOH水分子公众号

🌊 HOH水分子X账号

📹 课程B站账号

💻 Github仓库 https://github.com/move-cn/letsmove

Move 对象的有4种能力(abliity) 设计考虑

Move 对象的有4种能力(abliity)

1. 对象的几种存储状态:

  • 新建的对象, 进入未曾存储状态,一个交易结束之后,对象要么被销毁,要么被存储到网络的链上存储。

  • 链上存储的对象,也可以借用引用参与交易运算

  • 运行态的对象,可以通过share或freeze 编程共享对象。freeze的对象是只读的。

  • 链上存储对象,可以通过函数调用作为输入参数,移动成运行态,进入运行态之后,又可以重新进入析构或重新存到链上。

image-20241114154521620

2. 对象具有的四种ability (能力)

这四种能力是:

  • copy

    • 允许复制具有此功能的类型的值。
    • 当赋值或传参数时,会自动拷贝成新对象
    • 如果没有实现copy能力, 赋值的时候,是对象被移动
  • drop

    • 允许跳出作用域自动删除
  • store

    • 允许具有此功能的类型的值存在于 链上中的值中。 存在sui 网络上的对象的字段
    • 对于 Sui,控制对象内部可以存储哪些数据。 还控制哪些类型可以在其定义模块之外传
  • key

    • 允许类型用作存储的 “key”。表面上,这意味着该值可以是 存储中的顶级价值;换句话说,它不需要包含在另一个值中,以 在仓库里。

    • 对于 Sui,用于表示对象

2.1 如果有copy能力,赋值就是拷贝成一个新对象

image-20241111194804064

  • 上图中27行,c对象被拷贝到d,c和d指向的不是同一个对象,

  • 28 行修改了d,但是c没有被修改

  • 32行和33行中,c和d取值不同.

2.2 如果没有copy 能力,赋值就是移动对象

下面例子中 std::debug::print(&c); 会编译失败,因为 c 对象以及该移动给d了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public struct NonCopyable {
value :u64,
}

#[test]
fun test_non_copy(){
let c = NonCopyable{ value:33};
let d = c ;
std::debug::print(&c); //此时c对象已经被移动给d
std::debug::print(&d);

// sui::test_utils::destroy(c);
sui::test_utils::destroy(d);
}

2.3 销毁 (drop)

2.3.1 离开作用域自动销毁,需要具有drop能力

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
 public struct IgnoreMe has drop{
a:u32,
b:u32
}

#[test]
fun test_ignore(){
let im = IgnoreMe{a:3,b:4};
print(&im);
}

上文定义的IgnoreMe 对象im 具有自动析构的能力has drop,在离开函数的作用域时候自动析构.

2.3.2 没有drop能力的对象需要主动析构或保存到链上

{.line-numbers}
1
2
3
4
5
6
7
8
9
public struct NoDrop{ value :u64 }

#[test]
fun test_nodrop(){
let no_drop = NoDrop{ value :34};
print(&no_drop);

let NoDrop{ value: _ } = no_drop;
}

这个例子 NoDrop类型没有drop能力,对象离开作用域,需要析构,或者将对象的所有权转移.

  • 第8行是析构对象的代码

2.3.4 不能析构时,能转移所有权:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
fun useNoDrop(o : NoDrop ) : NoDrop{
std::debug::print(&o);
o
}

#[test]
fun testUseNoDrop(){
let o = NoDrop{value :4};
let d = useNoDrop(o);
NoDrop{value:_} = d;
}
  • 第1行函数UseNoDrop获得了对象o
  • 第3行,函数将N哦Drop对象o 返回出去,将所有权转移出去.
  • 第10 行,显示代码析构这个NoDrop对象

2.3.5 独立存储在链上

对象独立存储在链上,必须具有key能力 has key

image-20241114085958845

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
public struct Keyable has key{
id : UID,
}

#[test]
fun test_key(){
let mut ctx = tx_context ::dummy();
let k = Keyable{ id: object::new(&mut ctx)};
std::debug::print(&k);
transfer::transfer(k,ctx.sender());
}

这个例子中Keyable是具有key能力的,可以通过transfer传递到sui网络上存储, 指定owner地址.

2.3.6 对象作为 共享对象存储在链上,需要具有key能力

image-20241114091033160

2.3.7 对象作为一个对象的一部分存储,需要具有store能力

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use std::string::String;
public struct Grandson has store{
name : String,
}
public struct Child has store{
name : String,
child : Grandson,
}
public struct Parent has key{
id: UID,
child: Child,
}
#[test]
fun test_store_child(){
let mut ctx = tx_context::dummy();
let foo = Parent {
id : object::new(&mut ctx),
child: Child {
name : b"one child".to_string(),
child: Grandson{
name : b"a grandson".to_string(),

},
}
};
std::debug::print(&foo);
transfer::freeze_object(foo);
}
  • Parent是独立存储,具有key能力

  • Child 都是作为独立存储对象Parent的字段

  • Grandson 作为Child的字段存储在链上,

  • Child 和Grandson 都具有store能力

image-20241114095326972

2.3.8 无ablity 对象就必须在module内提供方法去析构它

  • 一个对象,没有任何ability ,就是hot potato

  • 没有key 和store 无法保存在链上

  • 没有drop 不会自动析构

  • 只能在本模块析构, 因为外部模块无法直接访问struct的字段

  • 下图中模块A, 需要使用模块B中的Foo 对象,使用完毕需要调用模块B 的析构方法

    1
    2
    3
    4
    public struct Foo {
    value :u32
    }

2.3.8.1 根据对象不能外部析构的特性,构造借贷模型

image-20241114080921239

2.3.8.2 闪电贷的逻辑

image-20241111213310276

  • 定义闪电贷的出借方,在borrow的时候获得Coin和 Loan对象,
  • 在repay的时候才能销毁Loan对象, 如果借出放不调用repay,无法销毁Loan对象,交易会回滚.
  • replay的时候需要校验传入的coin 要多于borrow的coin, 多出来的部分作为回报.
    • 下面是 贷方代码的例子
{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
module book::loan;
//hot potato pattern
public struct Loan{
feedback: u64, //还钱数
}

public struct Coin has key,store{
id:UID,
amount:u64
}

//借出钱
public fun borrow(amount:u64,ctx:&mut TxContext) :(Coin,Loan){
let feedback = amount * 103 /100;
let c = Coin{ id: object::new(ctx),amount};
(c, Loan{feedback})
}

const ErrNotEnoughFeedback:u64 = 43;
const OWNER_ADDR :address = @0xafe36044ef56d22494bfe6231e78dd128f097693f2d974761ee4d649e61f5fa2;//todo

public fun repay(coin: Coin,loan:Loan){
assert!(coin.amount >= loan.feedback,ErrNotEnoughFeedback);
transfer::public_transfer(coin,OWNER_ADDR);
Loan{ feedback:_} = loan;
}

``` {.line-numbers}

下面是借方模块的代码,使用测试代码演示

```c++
#[test_only]
module book::test_loan;
use book::loan::Coin;

////todo 贷款后赚钱的业务逻辑,这里没有实现
fun earn_money(coin : Coin ) :Coin{
coin
}


#[test]
#[expected_failure(abort_code=book::loan::ErrNotEnoughFeedback)]
fun borrow_test(){
let mut ctx = tx_context::dummy();
let (coin,loan) = book::loan::borrow(233,&mut ctx);
let new_money = earn_money(coin);
book::loan::repay(new_money,loan);
//todo 赚钱了,可以把feedback-amount 的钱,存入自己钱包
}

2.3.10 key + store ability

目前其他模块存到链上的对象,都需要key+store能力

1
2
3
4
5
6
7
8
9
10
11
public fun public_transfer<T: key + store>(obj: T, recipient: address) {
transfer_impl(obj, recipient)
}
public fun public_freeze_object<T: key + store>(obj: T) {
freeze_object_impl(obj)
}

public fun public_share_object<T: key + store>(obj: T) {
share_object_impl(obj)
}

附录:

最近在参加HOH 共学活动,

💧 HOH水分子公众号

🌊 HOH水分子X账号

📹 课程B站账号

💻 Github仓库 https://github.com/move-cn/letsmove