深入Libra: 使用Move编程语言编写Libra模块和脚本

这一次,我们将尝试编写简单的Libra模块和脚本,初步了解Move编程语言。

Move编程语言还在演化之中,白皮书参照Move: A Language With Programmable
Resources
, 现阶段只能通过Move Intermediate Representation (IR) 编写,要求请不要太高。Libra能活下来Move才能光大。

本文主要整理和参考官方文档: Run Move Programs Locally

注意,当前自定义的模块和脚本只能运行在Libra本地网络中,测试网络上是不支持的。

  • 模块(Module): 模块定义了更新Libra区块链的全局状态的一些规则, 类似其它区块链中的智能合约。模块声明了发布在某个账号上的资源,每个账号就是包含一组资源和模块的容器。
    • 在模块中声明了struct类型(包括资源)和过程(procedure)
    • 过程定义了创建、访问、和销毁它声明的类型
    • 模块可以重用,可以调用其它模块的过程
    • 用户在它自己的账号下发布模块
  • 脚本:准确的说是交易脚本,允许对交易进行编程。
    • 每个Libra交易中都包含一个交易脚本,比如转账操作
    • 交易和发布在Libra区块链全局存储中的资源进行交互,这是通过调用一个或者多个模块的过程实现的
    • 脚本并不存储在全局状态中,脚本不能调用脚本,它只使用一次
  • Move语言中资源是第一类的:
    • Move的主要功能是定义自定义资源类型。资源类型用于编码具有丰富可编程性的安全数字资产
    • 资源是Move语言中的普通值(ordinary value),它们可存储为数据结构,作为参数传递给procedure,从procedure返回等等
    • Move类型系统为资源提供了特殊的安全保障。Move资源不能复制、重复使用或丢弃。资源类型只能由定义该类型的模块创建或销毁。这些保障是由Move虚拟机通过bytecode验证静态地强制执行的。Move虚拟机将拒绝运行尚未通过bytecode检验器的代码
    • Libra币作为一种资源类型,其名称为LibraCoin.T。注意LibraCoin.T在语言中没有特殊的地位,每种资源都享有相同的保护

Libra本身的模块和脚本分别位于modulestransaction_scripts, 通过这些已有的模块和脚本,你可以学习Move相关的操作。

下面我们以一个代币 BirdCoin (鸟币) 为例,演示如何在本地网络进行:

  • 模块的编写、发布
  • 脚本的编写与执行
  • 相关过程的编写:查询余额、取钱、存钱、挖矿等操作

编译、发布模块

编写第一个模块

1、首先,我们定义一个代币,叫做BirdCoin, 换成人话就是鸟币。一般我们使用T代表这个模块的主要资源。
这个资源有一个u64类型的值,用来代表币值。

1
2
3
4
5
6
7
module BirdCoin {
resource T {
value: u64,
}
// ......
}

2、定义一个初始化用户币值的方法,只需调用一次。

1
2
3
4
public initialize() {
move_to_sender<T>(T{ value: 0 });
return;
}

3、接下来定义挖矿、查询余额、取款和存钱的过程。

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
public mint(value: u64): Self.T {
return T{value: move(value)};
}
public balance(): u64 acquires T{
let sender_account: &Self.T;
let token_value: u64;
sender_account = borrow_global<T>(get_txn_sender());
token_value = *(&move(sender_account).value);
return move(token_value);
}
public deposit(payee: address, to_deposit: Self.T) acquires T{
let payee_token_ref: &mut Self.T;
let payee_token_value: u64;
let to_deposit_value: u64;
payee_token_ref = borrow_global_mut<T>(move(payee));
payee_token_value = *(©(payee_token_ref).value);
T{ value: to_deposit_value } = move(to_deposit);
*(&mut move(payee_token_ref).value) = move(payee_token_value) +
move(to_deposit_value);
return;
}
public withdraw(amount: u64): Self.T acquires T{
let sender_account: &mut Self.T;
let value: u64;
sender_account = borrow_global_mut<T>(get_txn_sender());
value = *(©(sender_account).value);
assert(copy(value) >= copy(amount), 1);
*(&mut move(sender_account).value) = move(value) - copy(amount);
return T{ value: move(amount) };
}

`

发布

1、模块必须和一个账号绑定,我们首先启动一个本地网络:

1
2
3
cargo run -p libra_swarm -- -s
libra%

2、然后创建一个账号:

1
2
3
libra% a c
>> Creating/retrieving next account from wallet
Created/retrieved account #0 address 4b6db7161fb394e287b1b9a45629740fe23b71566c8e976416f8d47bbe80438a

3、接着编译上面的模块

1
2
3
4
5
6
7
libra% dev c 0 /Users/xxxxx/libra_local_network/move/birdcoin.mvir module
>> Compiling program
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/compiler -l /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/786d6a4502e5910b3a0706fa68e91963.mvir -m`
Finished dev [unoptimized + debuginfo] target(s) in 0.40s
Running `target/debug/compiler /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/786d6a4502e5910b3a0706fa68e91963.mvir -a 4b6db7161fb394e287b1b9a45629740fe23b71566c8e976416f8d47bbe80438a -m`
Successfully compiled a program at /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/786d6a4502e5910b3a0706fa68e91963.mv

4、发布模块

上面的模块编译到/var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/786d6a4502e5910b3a0706fa68e91963.mv, 你将它发布在第0个账号下。

1
2
3
4
libra% dev publish 0 /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/786d6a4502e5910b3a0706fa68e91963.mv
waiting ........transaction is stored!
no events emitted
Successfully published module

测试脚本

下面我们需要编写一个脚本,用来初始化账号的鸟币,以及尝试挖矿、存钱以及查询操作。

因为CLI没有直接的命令操作鸟币, 我们在脚本中使用assert进行验证。

编写交易脚本

下面是一个交易脚本,这里我们直接使用\{\{sender\}\}.BirdCoin导入模块,因为我们以下的测试都是以这个账号进行测试的。你也可以直接使用这个账号导入模块,因为发布的模块是公共的,可以被其它模块或者脚本使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import \{\{sender\}\}.BirdCoin;
main() {
let sender: address;
let minted_tokens: BirdCoin.T;
let balance: u64;
sender = get_txn_sender();
BirdCoin.initialize();
minted_tokens = BirdCoin.mint(100);
BirdCoin.deposit(move(sender), move(minted_tokens));
balance = BirdCoin.balance();
assert(move(balance) == 100, 3);
return;
}

在这个脚本中,我们首先为这个账号初始化值为0的鸟币,然后挖出100个鸟币, 存入到这个账号上。

经过这一系列的操作,账号的账上应该有100个鸟币了。

编译脚本

1
2
3
4
5
6
7
8
libra% dev compile 0 /Users/xxxxx/libra_local_network/move/birdcoin_script.mvir script
>> Compiling program
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/compiler -l /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/f3e8e68584dc01dc2953ff887203324f.mvir`
Finished dev [unoptimized + debuginfo] target(s) in 0.40s
Running `target/debug/compiler /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/f3e8e68584dc01dc2953ff887203324f.mvir -a 4b6db7161fb394e287b1b9a45629740fe23b71566c8e976416f8d47bbe80438a --deps=/var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/5070552218b6e5bbea6a2029931d4664`
Successfully compiled a program at /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/f3e8e68584dc01dc2953ff887203324f.mv
libra%

运行脚本

1
2
3
4
libra% dev execute 0 /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/f3e8e68584dc01dc2953ff887203324f.mv
waiting ........transaction is stored!
no events emitted
Successfully finished execution

参考文档

  1. https://developers.libra.org/docs/move-overview
  2. https://developers.libra.org/docs/run-move-locally
  3. https://deqode.com/blog/move-language-tutorial/
  4. https://learnblockchain.cn/2019/06/28/deep-move/