Facebook:Libra之Move入門

Move是一種新的編程語言,它爲Libra區塊鏈提供了一個安全和可編程的基礎。Libra區塊鏈中的帳戶是任意數量Move資源及Move模塊的容器。提交至Libra 區塊鏈的每筆交易,都是用以Move語言編寫的交易腳本對其邏輯進行編碼。這個交易腳本可調用模塊聲明的過程來更新區塊鏈的全局狀態。git

在本指南的第一部份內容中,咱們將歸納性地介紹Move語言的主要特色:程序員

  1. Move交易腳本啓用可編程交易
  2. Move模塊容許組合型智能合約
  3. Move語言具備第一類資源

對於求知慾強的讀者來講,Move編程語言的技術論文包含了更多關於該語言的細節信息:編程

在本指南的第二部分,咱們將向你展現如何在Move中間代碼優化(Move intermediate representation)的環境下編寫本身的應用。初始的測試網並不支持自定義Move程序,但這些功能可供你在本地試用。安全

Move語言的主要特色

Move交易腳本啓用可編程交易

  • 每一個Libra交易都包含一個Move交易腳本,該腳本對驗證者應表明客戶端執行的邏輯進行編碼(例如,將Libra幣從A的帳戶移動到B的帳戶);
  • 交易腳本經過調用一個或多個Move模塊的過程,與Libra區塊鏈全局存儲中發佈的Move資源進行交互;
  • 交易腳本不會存儲在全局狀態當中,所以其它交易腳本沒法調用它,這是一個一次性程序;
  • 咱們在編寫交易腳本時,提供了幾個交易腳本示例;

Move 模塊容許組合型智能合約

Move模塊定義了更新Libra區塊鏈全局狀態的規則。Move模塊與其它區塊鏈中的智能合約同樣都是解決相同的問題。模塊聲明瞭可在用戶帳戶下發布的資源類型。Libra區塊鏈中的每一個帳戶都是任意數量資源和模塊的容器。數據結構

  • 模塊聲明結構類型(包括資源,這是一種特殊的結構)以及過程;
  • Move模塊的過程,定義了建立、訪問以及銷燬其聲明類型的規則。
  • 模塊是可重用的。一個模塊中聲明的結構類型,可使用另外一個模塊中聲明的結構類型,而且一個模塊中聲明的過程能夠調用另外一個模塊中聲明的公共過程。模塊能夠調用在其餘Move模塊中聲明的過程。交易腳本能夠調用已發佈模塊的任何公共過程。
  • 最終,Libra用戶將能在本身的賬戶下發布模塊。

Move語言具備第一類資源

  • Move的主要功能是定義自定義資源類型。資源類型用於編碼具備豐富可編程性的安全數字資產。
  • 資源是語言中的普通值,它們可存儲爲數據結構,做爲參數傳遞給procedure,從procedure返回,等等;
  • Move類型系統爲資源提供了特殊的安全保障。Move資源不能複製、重複使用或丟棄。資源類型只能由定義該類型的模塊建立或銷燬。這些保障是由Move虛擬機經過bytecode驗證靜態地強制執行的。Move虛擬機將拒絕運行還沒有經過bytecode檢驗器的代碼;
  • ibra幣做爲一種資源類型,其名稱爲LibraCoin.T。LibraCoin.T在語言中沒有特殊的地位,每種資源都享有相同的保護待遇;

Move語言底層

Move中間代碼優化(Move Intermediate Representation)

本節介紹如何使用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幣以及接收方地址;
  • 容許Alice建立此類型資源,並在其帳戶下發布(create過程);
  • 容許Bob聲明資源(claim_for_recipient過程);
  • 容許任何擁有EarmarkedLibraCoin.T資源類型的人銷燬它,並獲取底層的Libra幣(unwrap過程);
  • Allows anyone with an 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區塊鏈是一種新的編程環境,咱們對應支持的習慣用法和模式的理解,仍在不斷髮展。目前,源語言還處於開發的早期階段,咱們尚未爲它準備好發佈時間表。

相關文章
相關標籤/搜索