Facebook Move程式語言入門

買賣虛擬貨幣

Facebook區塊鏈專案Libra的其中一個技術亮點,就是它使用了一種稱為Move的新程式語言,那麼這種語言是怎樣的呢,今天我們就從其官方的概述資料入手,近距離了解這種新的語言。

以下內容為譯文:

Move是一種新的程式語言,它為Libra區塊鏈提供了一個安全和可程式設計的基礎。Libra區塊鏈中的賬戶是任意數量Move資源及Move模組的容器。提交至Libra 區塊鏈的每個事務,都使用以 Move語言編寫的事務指令碼對其邏輯進行編碼。

這個事務指令碼可呼叫模組宣告的過程來更新區塊鏈的全域性狀態。

在本指南的第一部分內容中,我們將概括性地介紹Move語言的主要特點:

  1. Move事務指令碼啟用可程式設計事務;
  2. Move模組允許組合型智慧合約;
  3. Move語言具有第一類資源(First Class Resource);
對於求知慾強的讀者來說,Move程式語言的技術論文包含了更多關於該語言的細節資訊:

在本指南的第二部分,我們將向你展示如何在Move中間程式碼最佳化(IR)的環境下編寫自己的應用。初始的測試網並不支援自定義Move程式,但這些功能可供你在本地試用。

(圖片來自:libra.org)

一、Move語言的主要特點

1、1 Move事務指令碼啟用可程式設計事務

  1. 每個Libra事務都包含一個Move事務指令碼,該指令碼對驗證者應代表客戶端執行的邏輯進行編碼(例如,將Libra幣從Alice的賬戶移動到Bob的賬戶);
  2. 事務指令碼透過呼叫一個或多個Move模組的過程,與Libra區塊鏈全域性儲存中釋出的Move資源進行互動;
  3. 事務指令碼不會儲存在全域性狀態當中,因此其它事務指令碼無法呼叫它,這是一個一次性程式;
  4. 我們在編寫事務指令碼時,提供了幾個事務指令碼示例;

1、2 Move 模組允許組合型智慧合約

Move模組定義了更新Libra區塊鏈全域性狀態的規則。Move模組與其它區塊鏈中的智慧合約一樣都是解決相同的問題。模組宣告瞭可在使用者賬戶下發布的資源型別。Libra區塊鏈中的每個賬戶都是任意數量資源和模組的容器。
  1. 模組宣告結構型別(包括資源,這是一種特殊的結構)以及過程;
  2. Move模組的過程,定義了建立、訪問以及銷燬其宣告型別的規則。
  3. 模組是可重用的。一個模組中宣告的結構型別,可以使用另一個模組中宣告的結構型別,並且一個模組中宣告的過程可以呼叫另一個模組中宣告的公共過程。模組可以呼叫在其他Move模組中宣告的過程。事務指令碼可以呼叫已釋出模組的任何公共過程。
  4. 最終,Libra使用者將能在自己的帳戶下發布模組。

1、3 Move語言具有第一類資源

  1. Move的主要功能是定義自定義資源型別。資源型別用於編碼具有豐富可程式設計性的安全數字資產。
  2. 資源是語言中的普通值,它們可儲存為資料結構,作為引數傳遞給procedure,從procedure返回,等等;
  3. Move型別系統為資源提供了特殊的安全保障。Move資源不能複製、重複使用或丟棄。資源型別只能由定義該型別的模組建立或銷燬。這些保障是由Move虛擬機器透過bytecode驗證靜態地強制執行的。Move虛擬機器將拒絕執行尚未透過bytecode檢驗器的程式碼;
  4. Libra幣作為一種資源型別,其名稱為LibraCoin.TLibraCoin.T在語言中沒有特殊的地位,每種資源都享有相同的保護待遇;

二、 Move語言底層

2、1 Move中間程式碼最佳化(IR)

本節介紹如何使用Move IR 編寫事務指令碼以及模組。先提醒下讀者,這個Move IR 目前還處於早期的階段,因此並不穩定,它也是接下來會介紹的Move 源語言的前身(有關詳細資訊,請參閱未來開發者體驗部分內容)。Move IR是在Move bytecode之上的一個很薄的語法層,用於測試bytecode驗證者以及虛擬機器,它對開發者而言不是特別友好。Move IR足以用於編寫人類可讀的程式碼,但無法直接轉換為Move bytecode。儘管Move IR還是有些粗糙,我們還是對這個Move語言感到興奮,並希望開發者們可以嘗試一下它。

我們會介紹關於Move IR的重要演示程式碼段,並鼓勵讀者透過在本地編譯、執行和修改示例來了解它。libra/language/README.md以及libra/language/ir_to_bytecode/README.md的說明檔案解釋瞭如何執行此操作。

2、2 編寫事務指令碼

正如我們在Move事務指令碼啟用可程式設計事務部分內容中所解釋的,使用者編寫事務指令碼,以請求對Libra區塊鏈的全域性儲存進行更新。幾乎任何事務指令碼中都會出現兩個重要的構建塊:LibraAccount.TLibraCoin.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目錄下檢查LibraAccountLibraCoin模組中這兩個關鍵資源的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

2、3 編寫模組

現在,我們把注意力集中到編寫自己的Move模組上,而不僅僅是重用現有的LibraAccountLibraCoin模組。考慮這樣一個情況:Bob將來某個時候將在地址a建立一個帳戶,Alice想要“指定”Bob一筆資金,以便他可以在賬戶建立後將其存入自己的帳戶。但她也希望,如果Bob一直不建立一個賬戶,她就能收回這筆資金。

為了解決Alice的這個問題,我們將編寫一個專用的EarmarkedLibraCoin模組,它會:

  1. 宣告一個新的資源型別EarmarkedLibraCoin.T,它封裝了一筆Libra幣以及接收方地址;
  2. 允許Alice建立此型別資源,並在其賬戶下發布(create過程);
  3. 允許Bob宣告資源(claim_for_recipient過程);
  4. 允許任何擁有EarmarkedLibraCoin.T資源型別的人銷燬它,並獲取底層的Libra幣(unwrap過程);

// 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(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(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(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增加這種參量多型性。

2、4 未來開發者體驗

在不久的將來,Move IR將穩定下來,編譯和驗證程式將變得更加對使用者友好。此外,IR源的位置資訊將被跟蹤,然後傳遞給驗證者,以使錯誤訊息更容易排錯。然而,IR將繼續作為測試Move bytecode的工具。它是作為底層bytecode的一種語義透明的表示。

為了允許有效的測試, IR編譯器需生成錯誤的程式碼,這些程式碼將被bytecode驗證者拒絕,或在編譯器的執行時失敗。

而對使用者友好的源語言則是另一種選擇,它應該拒絕編譯在管道的後續步驟中將失敗的程式碼。

未來,我們將擁有更高層次的Move源語言。這種源語言將被設計成安全而容易地表達常見的Move慣用語和程式設計模式。由於Move bytecode是一種新語言,而Libra區塊鏈是一種新的程式設計環境,我們對應支援的習慣用法和模式的理解,仍在不斷髮展。目前,源語言還處於開發的早期階段,我們還沒有為它準備好釋出時間表。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;