这一次,我们将尝试编写简单的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本身的模块和脚本分别位于modules和transaction_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
|
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
|
参考文档
- https://developers.libra.org/docs/move-overview
- https://developers.libra.org/docs/run-move-locally
- https://deqode.com/blog/move-language-tutorial/
- https://learnblockchain.cn/2019/06/28/deep-move/