Move是一种新的编程语言,它为Libra区块链提供了一个安全和可编程的基础。Libra区块链中的帐户是任意数量Move资源及Move模块的容器。提交至Libra 区块链的每笔交易,都是用以Move语言编写的交易脚本对其逻辑进行编码。这个交易脚本可调用模块声明的过程来更新区块链的全局状态。git
在本指南的第一部份内容中,咱们将归纳性地介绍Move语言的主要特色:程序员
对于求知欲强的读者来讲,Move编程语言的技术论文包含了更多关于该语言的细节信息:编程
在本指南的第二部分,咱们将向你展现如何在Move中间代码优化(Move intermediate representation)的环境下编写本身的应用。初始的测试网并不支持自定义Move程序,但这些功能可供你在本地试用。安全
Move模块定义了更新Libra区块链全局状态的规则。Move模块与其它区块链中的智能合约同样都是解决相同的问题。模块声明了可在用户帐户下发布的资源类型。Libra区块链中的每一个帐户都是任意数量资源和模块的容器。数据结构
本节介绍如何使用Move IR 编写交易脚本以及模块。先提醒下读者,这个Move IR 目前还处于早期的阶段,所以并不稳定,它也是接下来会介绍的Move 源语言的前身(有关详细信息,请参阅将来开发者体验部份内容)。Move IR是在Move bytecode之上的一个很薄的语法层,用于测试bytecode验证者以及虚拟机,它对开发者而言不是特别友好。Move IR足以用于编写人类可读的代码,但没法直接转换为Move bytecode。尽管Move IR仍是有些粗糙,咱们仍是对这个Move语言感到兴奋,并但愿开发者们能够尝试一下它。app
咱们会介绍关于Move IR的重要演示代码段,并鼓励读者经过在本地编译、运行和修改示例来了解它。libra/language/README.md
以及 libra/language/ir_to_bytecode/README.md
的说明文件解释了如何执行此操做。less
正如咱们在Move交易脚本启用可编程交易部份内容中所解释的,用户编写交易脚本,以请求对Libra区块链的全局存储进行更新。几乎任何交易脚本中都会出现两个重要的构建块:LibraAccount.T和LibraCoin.T资源类型,LibraAccount是模块的名称,T是该模块声明的资源的名称。这是在Move中常见的命名规则。模块声明的“main”类型一般命名为T.编程语言
当咱们说一个用户“在Libra区块链上拥有一个地址为0xff的账户”时,咱们的意思是,这个0xff地址持有LibraAccount.T资源的实例。每一个非空地址都有一个LibraAccount.T资源。此资源存储帐户数据,如序列号、验证密钥和余额。要与账户交互的Libra系统的任何部分,都必须经过从LibraAccount.T资源中读取数据或调用LibraAccount模块的过程。工具
帐户余额是LibraCoin.T
的一种类型资源。正如咱们在Move具备第一类资源部份内容中解释的,这是Libra币的一种类型。这种类型是语言中的“第一类公民”,就像其余Move资源同样。LibraCoin.T的类型的资源能够存储在过程变量中,在过程之间传递,等等。区块链
咱们鼓励感兴趣的读者在 libra/language/stdlib/modules/directory
目录下检查LibraAccount和LibraCoin模块中这两个关键资源的Move IR定义,
如今,让咱们看看程序员如何在一个交易脚本中与这些模块和资源交互。
// Simple peer-peer payment example. // Use LibraAccount module published on the blockchain at account address // 0x0...0 (with 64 zeroes). 0x0 is shorthand that the IR pads out to // 256 bits (64 digits) by adding leading zeroes. import 0x0.LibraAccount; import 0x0.LibraCoin; main(payee: address, amount: u64) { // The bytecode (and consequently, the IR) has typed locals. The scope of // each local is the entire procedure. All local variable declarations must // be at the beginning of the procedure. Declaration and initialization of // variables are separate operations, but the bytecode verifier will prevent // any attempt to use an uninitialized variable. let coin: R#LibraCoin.T; // The R# part of the type above is one of two *kind annotation* R# and V# // (shorthand for "Resource" and "unrestricted Value"). These annotations // must match the kind of the type declaration (e.g., does the LibraCoin // module declare `resource T` or `struct T`?). // Acquire a LibraCoin.T resource with value `amount` from the sender's // account. This will fail if the sender's balance is less than `amount`. coin = LibraAccount.withdraw_from_sender(move(amount)); // Move the LibraCoin.T resource into the account of `payee`. If there is no // account at the address `payee`, this step will fail LibraAccount.deposit(move(payee), move(coin)); // Every procedure must end in a `return`. The IR compiler is very literal: // it directly translates the source it is given. It will not do fancy // things like inserting missing `return`s. return; }
此交易脚本存在着一个问题:若是地址接收方没有帐户,它将执行失败。咱们将经过修改脚原本解决这个问题,为接收方建立一个帐户(若是接收方还不具有帐户的话)。
// A small variant of the peer-peer payment example that creates a fresh // account if one does not already exist. import 0x0.LibraAccount; import 0x0.LibraCoin; main(payee: address, amount: u64) { let coin: R#LibraCoin.T; let account_exists: bool; // Acquire a LibraCoin.T resource with value `amount` from the sender's // account. This will fail if the sender's balance is less than `amount`. coin = LibraAccount.withdraw_from_sender(move(amount)); account_exists = LibraAccount.exists(copy(payee)); if (!move(account_exists)) { // Creates a fresh account at the address `payee` by publishing a // LibraAccount.T resource under this address. If theres is already a // LibraAccount.T resource under the address, this will fail. create_account(copy(payee)); } LibraAccount.deposit(move(payee), move(coin)); return; }
让咱们看一个更复杂的例子。在这个例子中,咱们将使用交易脚本为多个接收方进行支付(而不是单个接收方)。
// Multiple payee example. This is written in a slightly verbose way to // emphasize the ability to split a `LibraCoin.T` resource. The more concise // way would be to use multiple calls to `LibraAccount.withdraw_from_sender`. import 0x0.LibraAccount; import 0x0.LibraCoin; main(payee1: address, amount1: u64, payee2: address, amount2: u64) { let coin1: R#LibraCoin.T; let coin2: R#LibraCoin.T; let total: u64; total = move(amount1) + copy(amount2); coin1 = LibraAccount.withdraw_from_sender(move(total)); // This mutates `coin1`, which now has value `amount1`. // `coin2` has value `amount2`. coin2 = LibraCoin.withdraw(&mut coin1, move(amount2)); // Perform the payments LibraAccount.deposit(move(payee1), move(coin1)); LibraAccount.deposit(move(payee2), move(coin2)); return; }
好了,到这里,咱们就结束了交易脚本部分的展现,有关更多例子,包括初始测试网中支持的交易脚本,请参阅libra/language/stdlib/transaction_scripts
。
如今,咱们把注意力集中到编写本身的Move模块上,而不只仅是重用现有的LibraAccount和LibraCoin模块。考虑这样一个状况:Bob未来某个时候将在地址a建立一个账户,Alice想要“指定”Bob一笔资金,以便他能够在帐户建立后将其存入本身的账户。但她也但愿,若是Bob一直不建立一个帐户,她就能收回这笔资金。
为了解决Alice的这个问题,咱们将编写一个专用的EarmarkedLibraCoin
模块,它会:
EarmarkedLibraCoin.T
,它封装了一笔Libra币以及接收方地址;create
过程);claim_for_recipient
过程);EarmarkedLibraCoin.T
资源类型的人销毁它,并获取底层的Libra币(unwrap
过程);EarmarkedLibraCoin.T
to destroy it and acquire the underlying coin (the unwrap
procedure).// A module for earmarking a coin for a specific recipient module EarmarkedLibraCoin { import 0x0.LibraCoin; // A wrapper containing a Libra coin and the address of the recipient the // coin is earmarked for. resource T { coin: R#LibraCoin.T, recipient: address } // Create a new earmarked coin with the given `recipient`. // Publish the coin under the transaction sender's account address. public create(coin: R#LibraCoin.T, recipient: address) { let t: R#Self.T; // Construct or "pack" a new resource of type T. Only procedures of the // `EarmarkedCoin` module can create an `EarmarkedCoin.T`. t = T { coin: move(coin), recipient: move(recipient), }; // Publish the earmarked coin under the transaction sender's account // address. Each account can contain at most one resource of a given type; // this call will fail if the sender already has a resource of this type. move_to_sender<T>(move(t)); return; } // Allow the transaction sender to claim a coin that was earmarked for her. public claim_for_recipient(earmarked_coin_address: address): R#Self.T { let t: R#Self.T; let t_ref: &R#Self.T; let sender: address; // Remove the earmarked coin resource published under `earmarked_coin_address`. // If there is resource of type T published under the address, this will fail. t = move_from<T>(move(earmarked_coin_address)); t_ref = &t; // This is a builtin that returns the address of the transaction sender. sender = get_txn_sender(); // Ensure that the transaction sender is the recipient. If this assertion // fails, the transaction will fail and none of its effects (e.g., // removing the earmarked coin) will be committed. 99 is an error code // that will be emitted in the transaction output if the assertion fails. assert(*(&move(t_ref).recipient) == move(sender), 99); return move(t); } // Allow the creator of the earmarked coin to reclaim it. public claim_for_creator(): R#Self.T { let t: R#Self.T; let coin: R#LibraCoin.T; let recipient: address; let sender: address; sender = get_txn_sender(); // This will fail if no resource of type T under the sender's address. t = move_from<T>(move(sender)); return move(t); } // Extract the Libra coin from its wrapper and return it to the caller. public unwrap(t: R#Self.T): R#LibraCoin.T { let coin: R#LibraCoin.T; let recipient: address; // This "unpacks" a resource type by destroying the outer resource, but // returning its contents. Only the module that declares a resource type // can unpack it. T { coin, recipient } = move(t); return move(coin); } }
Alice能够为Bob建立一种预先安排的币,方法是建立一个交易脚本,调用Bob的地址a的create
,以及她所拥有的LibraCoin.T
。一旦地址 a 被建立,Bob就能够经过从 a 发送一个交易来领取这笔币,这会调用claim_for_recipient
,将结果传递给unwrap
,并将返回的LibraCoin
存储在他但愿的任何地方。若是Bob在建立 a 的过程当中花费的时间太长,而Alice想要收回她的资金,那么Alice可使用 claim_for_creator
,而后unwrap
。
观察型读者可能已经注意到,本模块中的代码对LibraCoin.T
的内部结构不可知。它能够很容易地使用泛型编程(例如,resource T { coin: AnyResource, ... }
)编写。咱们目前正致力于为Move增长这种参量多态性。
在不久的未来,Move IR将稳定下来,编译和验证程序将变得更加对用户友好。此外,IR源的位置信息将被跟踪,而后传递给验证者,以使错误消息更容易排错。然而,IR将继续做为测试Move bytecode的工具。它是做为底层bytecode的一种语义透明的表示。
为了容许有效的测试, IR编译器需生成错误的代码,这些代码将被bytecode验证者拒绝,或在编译器的运行时失败。而对用户友好的源语言则是另外一种选择,它应该拒绝编译在管道的后续步骤中将失败的代码。
将来,咱们将拥有更高层次的Move源语言。这种源语言将被设计成安全而容易地表达常见的Move惯用语和编程模式。因为Move bytecode是一种新语言,而Libra区块链是一种新的编程环境,咱们对应支持的习惯用法和模式的理解,仍在不断发展。目前,源语言还处于开发的早期阶段,咱们尚未为它准备好发布时间表。