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區塊鏈是一種新的編程環境,咱們對應支持的習慣用法和模式的理解,仍在不斷髮展。目前,源語言還處於開發的早期階段,咱們尚未爲它準備好發佈時間表。