区块链研究实验室 | Facebook Move语言入门教程

  • 时间:
  • 浏览:144
  • 来源:区块链技术网

Move是一种新的编程语言,旨在为Libra 区块链提供安全可编程的基础。 Libra 区块链中的帐户是任意数量的Move资源和Move模块的容器。提交给Libra区块链的每个交易都使用以move编写的交易脚本对其逻辑进行编码。交易脚本可以调用模块声明的过程来更新区块链的全局状态。

 

在本指南的第一部分中,我们将对Move语言的主要功能进行介绍:

 

  1. Move事务脚本启用可编程事务。

  2. Move模块允许组合智能合约。

  3. Move具有第一类资源。


在本指南的第二部分中,我们将“深入了解”Move并向展示如何在Move中中进行编写您自己的Move程序。初始testnet版本不支持自定义移动程序,但这些功能可供您在本地试用。

 

Move语言的主要特征:

 

  1. Move事务脚本启用可编程事务。

  2. 每个Libra事务都包含一个Move事务脚本,该脚本对应的验证者在客户端执行逻辑编码(例如将Libra从Alice的帐户移动到Bob的帐户)。

  3. 事务脚本是通过调用一个或多个Move模块的过程与与liba区块链全局存储中发布的Move资源进行交互。

  4. 事务脚本不存储在全局状态中,因此其他事务脚本无法调用它。它是一个一次性程序。

我们在编写事务脚本时提供了几个事务脚本示例。

 

  • Move模块允许组合型智能合约。

  • Move模块定义了更新libra区块链全局状态的规则。Move模块在与其他区块链系统中的智能合约相同。Move模块声明可以在用户帐户下发布的资源类型。 Libra 区块链中的每个帐户都是一个容器,可以容纳任意数量的资源和模块。

 

Move中间层表示

本节介绍如何在Move中间语言层(IR)中编写事务脚本和模块。IR是即将推出的Move源语言的早期(且不稳定)版本。IR是Move的字节码上的一个语法层,用于测试字节码验证器和虚拟机,它对于开发人员使用并不是很友好。它可以编写人类可读的代码,也可以直接编译以Move的字节码。

 

我们鼓励读者通过在本地编译,运行和修改示例来跟随这些示例学习。 libra / language / README.md和libra / language / ir_to_bytecode / README.md下的README文件解释了如何执行此操作。

 

编写事务脚本

 

正如我们在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资源是一等公民中所解释的那样,这是LibraCoin的一种类型。这种类型是语言中的“第一类公民”,就像其他Move资源一样。LibraCoin.T类型的资源可以存储在程序变量中,在程序之间传递参数等等。

 

所谓的编程语言的一等公民就是编程语言在编程的时候首要考虑的被编程对象。

感兴趣的读者可以在libra / language / stdlib / modules /目录下检查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;
}

这就结束了我们对事务脚本的“浏览”。有关更多示例(包括初始testnet中支持的事务脚本),请参阅libra / language / stdlib / transaction_scripts。

 

模块编写

我们现在将注意力转向我们自己编写的Move模块,而不是仅仅重用现有的LibraAccount和LibraCoin模块。考虑这样一个情况:Bob将来某个时候将在地址a创建一个帐户,Alice想要“指定”Bob一笔资金,以便他可以在账户创建后将其存入自己的帐户。但她也希望,如果Bob一直不创建一个账户,她就能收回这笔资金。

 

为了解决Alice的这个问题,我们将编写一个模块EarmarkedLibraCoin,其中:

 

  1. 声明一个新的资源类型EarmarkedLibraCoin.T,它包装了一个Libra coin和收款人地址。

  2. 允许Alice创建此类型并在其帐户下发布(create过程)。

  3. 允许Bob声明资源(claim_for_recipient过程)。

  4. 允许任何拥有EarmarkedLibraCoin.T的人销毁它并获取潜在的Libra代币(umwrap程序)。

 

// 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和她创建的LibraCoin.T。一旦创建了,Bob就可以通过发送一个交易来索取代币。这将调用claim_for_recipient,将结果传递给unwrap,并将返回的LibraCoin存储在他希望的任何位置。如果Bob花费太长时间在a下创建一个帐户并且Alice想要收回她的资金,她可以通过使用claim_for_creator然后解包来实现。

 

细心的读者可能已经注意到,该模块中的代码与LibraCoin.T的内部结构无关。它可以很容易地使用编程来编写(例如,资源T <AnyResource:R> {coin:AnyResource,...})。我们目前正致力于为Move添加对这种参数多态的支持。

 

在不久的将来,Move IR将稳定下来,编译和验证程序将变得更加对用户友好。此外,IR源的位置信息将被跟踪,然后传递给验证者,以使错误消息更容易排错。然而,IR将继续作为测试Move bytecode的工具。它是作为底层bytecode的一种语义透明的表示。

 

为了允许有效的测试, IR编译器需生成错误的代码,这些代码将被bytecode验证者拒绝,或在编译器的运行时失败。

 

而对用户友好的源语言则是另一种选择,它应该拒绝编译在管道的后续步骤中将失败的代码。

 

将来,我们将拥有更高级别的Move源语言。该源语言旨在安全轻松地表达常见的Move习语和编程模式。由于Move字节码是一种新语言,而Libra 全块链是一种新的编程环境,我们对应该支持的习语和模式的理解仍在不断发展,目前源语言还处于开发的早期阶段,我们还没有为它准备好发布时间表。

猜你喜欢

区块链红利吃饱后,这个巨头又想"征服"元宇宙?

据12月26日消息,百度与英伟达(NVIDIA)已达成协议,双方合作共建AI元宇宙。另外,在今日举行的百度AI开发者大会上,英伟达全球副总裁暨亚太区总裁 Raymond Teh将受邀出席,并发表主题演讲。

2021-12-27

2021年,区块链股权融资发生了怎么样的演变

过去一年,区块链行业融资井喷,在科技领域中独树一帜,A16z、红杉、老虎基金等等这些顶级机构在 2021 年的区块链行业肆意驰骋,在 DeFi、NFT、Metaverse 等领域扶持了一众创业项目。

2021-12-23

两个元宇宙的世界观,以及和区块链的关系

“元宇宙”这个名词音好听但义很难传达准确,想要更准确地理解义,取名为平行宇宙、竞争宇宙、山寨宇宙,更好。对应的,我们现在肉身所处的宇宙,我们称之为“肉身宇宙”。

2021-12-22

Alien Worlds(外星世界)区块链打金挖矿指南

Alien Worlds(外星世界)是一款4月就开始的游戏,10月27日又上线了全新的任务(Missions)游戏模式; 在WAX用户量排名中Alien Worlds(外星世界)一直排在前三,说明现在存量工作室还有很多;

2021-12-17

DeHorizon(地平线)区块链打金教程

今天我们就解析在一款社交主题游戏 — DeHorizon

2021-12-17