Getting started with the Facebook Move programming language

One of the technical highlights of the Facebook blockchain project Libra is that it uses a new programming language called Move, so what is the language? Today we start with its official overview and get a close look at this. A new language.

The following is a translation:

Move is a new programming language that provides a secure and programmable foundation for the Libra blockchain . The account in the Libra blockchain is a container for any number of Move resources and Move modules. Each transaction submitted to the Libra blockchain encodes its logic using a transaction script written in the Move language.

This transaction script can call the procedure declared by the module to update the global state of the blockchain.

In the first part of this guide, we will give a general introduction to the main features of the Move language:

  1. Move transaction script enables programmable transactions;
  2. The Move module allows for a combined smart contract;
  3. The Move language has a first class resource (First Class Resource);

For readers with a strong desire for knowledge, the technical paper on the Move programming language contains more details about the language:

In the second part of this guide, we will show you how to write your own application in the context of Move Intermediate Code Optimization (IR). The initial test network does not support custom Move programs, but these features are available for you to try locally.

Libra

(Image courtesy of libra.org)

First, the main features of the Move language

1, 1 Move transaction script enables programmable transactions

  1. Each Libra transaction contains a Move transaction script that encodes the logic that the verifier should perform on behalf of the client (for example, moving Libra coins from Alice's account to Bob's account);
  2. The transaction script interacts with the Move resource published in the global storage of the Libra blockchain by invoking one or more Move modules;
  3. The transaction script is not stored in the global state, so other transaction scripts cannot call it, this is a one-time program;
  4. We have provided several examples of transaction scripts when writing a transaction script;

1, 2 Move module allows combined smart contract

The Move module defines rules for updating the global state of the Libra blockchain. The Move module solves the same problem as smart contracts in other blockchains. The module declares the types of resources that can be published under the user account. Each account in the Libra blockchain is a container for any number of resources and modules.

  1. The module declares the structure type (including resources, which is a special structure) and the process;
  2. The Move module's process defines the rules for creating, accessing, and destroying its declared types.
  3. Modules are reusable. The structure type declared in one module can use the structure type declared in another module, and the procedure declared in one module can call the public procedure declared in another module. Modules can call procedures declared in other Move modules. A transaction script can call any public procedure of a published module.
  4. Ultimately, Libra users will be able to release modules under their own account.

1, 3 Move language has the first class of resources

  1. The main function of Move is to define a custom resource type. Resource types are used to encode secure digital assets with rich programmability.
  2. Resources are ordinary values ​​in the language, they can be stored as data structures, passed as arguments to procedures, returned from procedures, and so on;
  3. The Move type system provides special security for resources. Move resources cannot be copied, reused, or discarded. A resource type can only be created or destroyed by a module that defines that type. These guarantees are statically enforced by the Move virtual machine via bytecode verification. The Move VM will refuse to run code that has not passed the bytecode verifier;
  4. LibraCoin.T a resource type called LibraCoin.T . LibraCoin.T has no special status in the language, and each resource enjoys the same protection;

Second, the bottom of the Move language

2, 1 Move intermediate code optimization (IR)

This section describes how to write transaction scripts and modules using Move IR. First of all, the reader is reminded that this Move IR is still in its early stages and therefore unstable. It is also the predecessor of the Move source language that will be introduced next (for more information, see the Future Developer Experience section). Move IR is a very thin syntax layer on top of Move bytecode for testing bytecode verifiers and virtual machines, which is not particularly friendly to developers. Move IR is sufficient for writing human-readable code, but it cannot be directly converted to Move bytecode. Although Move IR is still a bit rough, we are excited about this Move language and hope that developers can try it out.

We'll introduce important demo code snippets about Move IR and encourage readers to understand it by compiling, running, and modifying examples locally. libra/language/README.md and libra/language/ir_to_bytecode/README.md explains how to do this.

2, 2 write a transaction script

As explained in the section on enabling transactional transactions in the Move Transaction Script, the user writes a transaction script to request an update to the global storage of the Libra blockchain. There are two important building blocks in almost any transaction script: LibraAccount.T and LibraCoin.T resource types, LibraAccount is the name of the module, and T is the name of the resource declared by the module. This is a common naming convention in Move. The "main" type of the module declaration is usually named T.

When we say that a user "has an account with address 0xff on the Libra LibraAccount.T ", we mean that this 0xff address holds an instance of the LibraAccount.T resource. Each non-empty address has a LibraAccount.T resource. This resource stores account data such as serial number, verification key, and balance. Any part of the Libra system that is to interact with the account must pass the process of reading data from the LibraAccount.T resource or calling the LibraAccount module.

The account balance is a type of resource for LibraCoin.T . As we explained in Move's first resource category, this is a type of Libra coin. This type is the "first class citizen" in the language, just like other Move resources. LibraCoin.T type resources can be stored in process variables, passed between procedures, and so on.

We encourage interested readers to check the Move IR definitions for these two key resources in the LibraAccount and LibraCoin modules in the libra/language/stdlib/modules/ directory directory.

Now let's see how programmers interact with these modules and resources in a transaction script.


// 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 streaming, 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 (eg, 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; There is an unfortunate problem with this transaction script: if the address recipient does not have an account, it will fail. We will fix this by modifying the script to create an account for the recipient (if the recipient does not already have an account).

 // 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; } Let's look at a more complicated example. In this example, we will use a transaction script to pay for multiple recipients (instead of a single recipient).


// Multiple payee example. This is written in a slightly verbose way to //marcinski 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; } Ok, here we end the presentation of the Transaction Script section, for more examples, including the transaction scripts supported in the initial test network, see

Libra/language/stdlib/transaction_scripts

.

2, 3 writing modules

Now, we focus on writing our own Move module, not just the existing LibraAccount and LibraCoin modules. Consider a situation where Bob will create an account at address a sometime in the future, and Alice wants to "designate" Bob a fund so that he can deposit it into his account after the account is created. But she also hopes that if Bob doesn't create an account, she can take back the money.

To solve this problem with Alice, we will write a dedicated EarmarkedLibraCoin module that will:

  1. Declare a new resource type, EarmarkedLibraCoin.T , which encapsulates a Libra currency and the recipient's address;
  2. Allow Alice to create this type of resource and publish it under its account ( create process);
  3. Allow Bob to declare resources ( claim_for_recipient procedure);
  4. Allow anyone with the EarmarkedLibraCoin.T resource type to destroy it and get the underlying Libra currency ( unwrap process);
 // 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 99s 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 can create a pre-arranged currency for Bob by creating a transaction script, calling the creation of Bob's address a, and the LibraCoin.T she owns. Once address a is created, Bob can pick up the token by sending a transaction from a, which calls claim_for_recipient , passes the result to unwrap , and stores the returned LibraCoin he wants. If Bob spends too much time creating a, and Alice wants to withdraw her funds, Alice can use claim_for_creator and then unwrap .

Observers may have noticed that the code in this module is not known to the internal structure of LibraCoin.T . It can be easily written using generic programming (for example, resource T { coin: AnyResource, ... } ). We are currently working on adding this parametric polymorphism to Move.

2, 4 future developer experience

In the near future, Move IR will stabilize, and the compilation and verification process will become more user friendly. In addition, the location information of the IR source will be tracked and passed to the verifier to make the error message easier to troubleshoot. However, IR will continue to be a tool for testing Move bytecode. It is a semantically transparent representation of the underlying bytecode.

In order to allow for efficient testing, the IR compiler needs to generate the wrong code, which will be rejected by the bytecode verifier or fail at runtime of the compiler.

A user-friendly source language is another option, and it should refuse to compile code that will fail in subsequent steps in the pipeline.

In the future, we will have a higher level of Move source language. This source language will be designed to safely and easily express common Move idioms and programming patterns. Since Move bytecode is a new language and the Libra blockchain is a new programming environment, our understanding of the supported idioms and patterns is still evolving. Currently, the source language is still in the early stages of development, and we have not yet prepared a release schedule for it.