1. 程式人生 > 程式設計 >Libra教程之:來了,你最愛的Move語言

Libra教程之:來了,你最愛的Move語言

Move語言

Move是一種新的程式語言,旨在為Libra區塊鏈提供安全且可程式設計的基礎。 Libra區塊鏈中的帳戶就是由任意數量的Move resources和Move modules組成的。 提交給Libra區塊鏈的每個交易都使用Move編寫的交易指令碼來對其邏輯進行編碼。

交易指令碼通過呼叫module宣告的procedures來更新區塊鏈的全域性狀態。

Move的核心概念

Move交易指令碼

每個Libra交易都包含一個Move交易指令碼,該指令碼對驗證程式代表客戶執行的邏輯進行編碼(例如,將Libra從A的帳戶轉移到B的帳戶)。

通過呼叫一個或多個Move模組的procedures,事務指令碼與釋出在Libra區塊鏈的全域性儲存中的Move resources進行互動。

事務指令碼並不會在全域性狀態中儲存,並且其他事務指令碼也無法呼叫它。 它是一個一次性程式。

Move modules

Move modules定義了用來更新Libra區塊鏈的全域性狀態的規則。 modules相當於其他區塊鏈中的智慧合約。 它宣告瞭可以在使用者帳戶下發布的resources型別。 Libra區塊鏈中的每個帳戶都是一個容器,用於容納任意數量的resources和modules。

module主要用來宣告結構型別(包括資源,這是一種特殊的結構)和procedures。

Move module的procedures定義了用於建立,訪問和銷燬其宣告的型別的規則。

modules是可重用的。 在一個module中宣告的結構型別可以使用在另一個module中宣告的結構型別,並且在一個module中宣告的可以procedure呼叫在另一個module中宣告的public procedures。 一個module可以呼叫在其他Move module中宣告的procedures。 事務指令碼可以呼叫已釋出module的任何public procedures。

最後,Libra使用者將能夠使用自己的帳戶釋出modules。

Move resources

Move的主要功能是能夠定義自定義資源型別。 資源型別主要對數字資產進行編碼。

資源在Libra中隨處可見。 它們可以儲存為資料結構,可以作為引數傳遞給過程,可以從過程中返回,等等。

Move type system為資源提供了特殊的安全保證。 Move resources永遠不能被複制,重用或丟棄。 資源型別只能由定義該型別的模組建立或銷燬。這是由Move虛擬機器器通過位元組碼驗證來強制進行保證的。 Move虛擬機器器將拒絕執行尚未通過位元組碼驗證程式的程式碼。

Libra貨幣是通過LibraCoin.T的資源型別來實現的。 和其他的資源一樣,LibraCoin.T也是一種資源。

寫一個Move程式

本節我會介紹怎麼使用Move IR來編寫事務指令碼和模組。IR是即將推出的Move源語言的預覽版本(不穩定)。 Move IR是Move位元組碼上的一個薄語法層,用於測試位元組碼驗證程式和虛擬機器器,它對開發人員並不特別友好。 它足夠高,可以編寫人類可讀的程式碼,但是也足夠低,可以直接編譯為Move位元組碼。

編寫交易指令碼

使用者通過交易指令碼來請求對Libra區塊鏈的全域性儲存進行更新。幾乎所有事務指令碼中都會出現兩個重要資源:LibraAccount.T和LibraCoin.T資源型別。 LibraAccount是module的名稱,而T是該module宣告的資源的名稱。這是Move中的通用命名約定。module宣告的“主要”型別通常稱為T。

當我們說使用者“在Libra區塊鏈上的地址0xff擁有一個帳戶”時,我們的意思是地址0xff擁有LibraAccount.T資源的例項。每個非空地址都有一個LibraAccount.T資源。此資源儲存帳戶資料,例如序列號,身份驗證金鑰和餘額。要與帳戶進行互動的Libra系統的任何部分都必須通過從LibraAccount.T資源中讀取資料或呼叫LibraAccount module的procedures來進行此操作。

帳戶餘額是LibraCoin.T型別的資源。這是Libra貨幣的型別。與任何其他Move資源一樣,此型別在語言上是一等公民。

LibraCoin.T型別的資源可以儲存在程式變數中,在過程之間傳遞,等等。

現在讓我們看看程式設計師如何在事務指令碼中與這些模組和資源進行互動。

// 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: LibraCoin.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: 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: LibraCoin.T;
  let coin2: 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;
}
複製程式碼

好了,這就是簡單的交易指令碼,雖然我們不瞭解Move IR的語法,但是直接看內容應該就很容易明白這個指令碼到底在做什麼了。

編寫自己的Modules

上面的交易指令碼使用了現有的LibraAccount和LibraCoin modules,那麼我們怎麼編寫自己的Move modules呢?

考慮這種情況:B將來會在地址a建立一個帳戶。 A想為B“專款”一些資金,以便他一旦建立就可以將其存入他的帳戶。 但是,如果B從未建立該帳戶,她還希望能夠自己收回資金。

為瞭解決A的這個問題,我們將編寫一個模組EarmarkedLibraCoin:

  • 宣告一個新的資源型別EarmarkedLibraCoin.T,該資源型別包裝了Libra coin和收件人地址。
  • 允許A建立此型別別並將其釋出到她的帳戶下(建立過程)。
  • 允許B宣告資源(claim_for_recipient過程)。
  • 允許擁有EarmarkedLibraCoin.T的任何人銷燬它並獲得相應的coin(拆包程式)。
// 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: 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: LibraCoin.T,recipient: address) {
    let t: Self.T;

    // Construct or "pack" a new resource of type T. Only procedures of the
    // `EarmarkedLibraCoin` module can create an `EarmarkedLibraCoin.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): Self.T acquires T {
    let t: Self.T;
    let t_ref: &Self.T;
    let sender: address;

    // Remove the earmarked coin resource published under `earmarked_coin_address`.
    // If there is no 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(): Self.T acquires T {
    let t: Self.T;
    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: Self.T): LibraCoin.T {
    let coin: 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);
  }

}
複製程式碼

A可以通過建立交易指令碼來為B建立專用coin,該交易指令碼呼叫B地址a上的create和她擁有的LibraCoin.T。 建立a之後,B可以通過傳送來自a的交易來claim coin。 這將呼叫claim_for_recipient,將結果傳遞給unwrap,並在他希望的任何地方儲存返回的LibraCoin。

如果B花費太長時間在a帳戶下建立帳戶,而A想要收回其資金,則可以通過使用Claim_for_creator然後取消unwrap來做到這一點。

好了,我們的程式就寫完了。目前IR還是不穩定版本,關於IR語法的更詳細內容,我會在後面的文章中講到。

更多教程請參考 flydean的部落格