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