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