Getting Started With Move
Move is a new programming language developed to provide a safe and programmable foundation for the Libra Blockchain. An account in the Libra Blockchain is a container for an arbitrary number of Move resources and Move modules. Every transaction submitted to the Libra Blockchain uses a transaction script written in Move to encode its logic. The transaction script can call procedures declared by a module to update the global state of the blockchain.
In the first part of this guide, we will provide a high-level introduction to the key features of the Move language:
- Move Transaction Scripts Enable Programmable Transactions
- Move Modules Allow Composable Smart Contracts
- Move Has First Class Resources
For the curious reader, the Move technical paper contains much more detail about the language.
In the second part of this guide, we will “look under the hood” and show you how to write your own Move programs in the Move source language. Custom Move programs are not supported in the initial testnet release, but these features are available for you to try out locally.
Key Features of Move
Move Transaction Scripts Enable Programmable Transactions
- Each Libra transaction includes a Move transaction script that encodes the logic a validator should perform on the client's behalf (for example, to transfer Libra from Alice's account to Bob's account).
- The transaction script interacts with Move resources published in the global storage of the Libra Blockchain by calling the procedures of one or more Move modules.
- A transaction script is not stored in the global state, and it cannot be invoked by other transaction scripts. It is a single-use program.
- We present several examples of transaction scripts in Writing Transaction Scripts.
Move Modules Allow Composable Smart Contracts
Move modules define the rules for updating the global state of the Libra Blockchain. Modules fill the same niche as smart contracts in other blockchain systems. Modules declare resource types that can be published under user accounts. Each account in the Libra Blockchain is a container for an arbitrary number of Move resources and Move modules.
- A module declares both struct types (including resources, which are a special kind of struct) and procedures.
- The procedures of a Move module define the rules for creating, accessing, and destroying the types it declares.
- Modules are reusable. A struct type declared in one module can use struct types declared in another module, and a procedure declared in one module can invoke public procedures declared in another module. A module can invoke procedures declared in other Move modules. Transaction scripts can invoke any public procedure of a published module.
- Eventually, we expect that Libra users will be able to publish modules under their own accounts.
Move Has First Class Resources
- The key feature of Move is the ability to define custom resource types. Resource types are used to encode safe digital assets with rich programmability.
- 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.
- The Move type system provides special safety guarantees for resources. Move resources can never be duplicated, reused, or discarded. A resource type can only be created or destroyed by the module that defines the type. These guarantees are enforced statically by the Move virtual machine via bytecode verification. The Move virtual machine will refuse to run code that has not passed through the bytecode verifier.
- All Libra currencies are implemented using the generic
Libra::T type
. For example: the LBR currency is represented asLibra::T<LBR::T>
and a hypothetical USD currency would be represented asLibra::T<USD::T>
.Libra::T
has no special status in the language; every Move resource enjoys the same protections.
Move: Under the Hood
Move Source Language
This section describes how to write transaction scripts and modules in the Move source language. We will proceed by presenting snippets of heavily-commented Move code. We encourage readers to follow along with the examples by compiling, running, and modifying them locally. The README files libra/language/README.md
and libra/language/move-lang/README.md
explain how to do this.
Writing Transaction Scripts
As we explained in Move Transaction Scripts Enable Programmable Transactions, users write transaction scripts to request updates to the global storage of the Libra Blockchain. There are two important building blocks that will appear in almost any transaction script: the LibraAccount::T
and Libra::T
resource types. LibraAccount
is the name of the module, and T
is the name of a resource declared by that module. This is a common naming convention in Move; the “main” type declared by a module is typically named T
.
When we say that a user "has an account at address 0xff
on the Libra Blockchain", what we mean is that the address 0xff
holds an instance of the LibraAccount::T
resource. Every nonempty address has a LibraAccount::T
resource. This resource stores account data, such as the sequence number, authentication key, and balance. Any part of the Libra system that wants to interact with an account must do so by reading data from the LibraAccount::T
resource or invoking procedures of the LibraAccount
module.
The account balance is a generic resource of type LibraAccount::Balance
. A single Libra account may hold balances in multiple currencies. For example, an account that holds both LBR and USD would have a LibraAccount::Balance<LBR::T>
resource and a LibraAccount::Balance<USD::T>
resource. However, every account must have at least one balance.
A Balance<Token>
resource holds a resource of type Libra::T<Token>
. As we explained in Move Has First Class Resources, this is the generic type of a Libra coin. This type is a "first-class citizen" in the language just like any other Move resource. Resources of type Libra::T
can be stored in program variables, passed between procedures, and so on.
We encourage the interested reader to examine the Move definitions of these two key resources in the LibraAccount
and Libra
modules under the libra/language/stdlib/modules/
directory.
Now let us see how a programmer can interact with these modules and resources in a transaction script.
// Simple peer-peer payment example.
script {
// Use LibraAccount module published on the blockchain at account address
// 0x0...0. 0x0 is shorthand that the language pads out to
// 16 bytes by adding leading zeroes.
use 0x0::LBR;
use 0x0::Libra;
use 0x0::LibraAccount;
fun main(payee: address, amount: u64) {
// Acquire a Libra::T<LBR::T> resource with value `amount` from the sender's
// account. This will fail if the sender's balance is less than `amount`.
let coin = LibraAccount::withdraw_from_sender<LBR::T>(amount);
// Move the `coin` resource into the account of `payee`. If there is no
// account at the address `payee`, this step will fail
// Note that we could have also written `deposit<LBR::T>` here, but the
// compiler is able to infer this for us based on the type of `coin`
LibraAccount::deposit(payee, coin)
}
}
Let us look at a more complex example. In this example, we will use a transaction script to pay multiple recipients instead of just one.
// Multiple payee example. This is written in a slightly verbose way to
// emphasize the ability to split a `Libra::T<LBR::T>` resource. The more concise
// way would be to use multiple calls to `LibraAccount::withdraw_from_sender`.
script {
use 0x0::LBR;
use 0x0::Libra;
use 0x0::LibraAccount;
fun main(payee1: address, amount1: u64, payee2: address, amount2: u64) {
let total = amount1 + amount2;
let coin1 = LibraAccount::withdraw_from_sender<LBR::T>(total);
// This mutates `coin1`, which now has value `amount1`.
// `coin2` has value `amount2`.
let coin2 = LibraCoin::withdraw(&mut coin1, amount2);
// Perform the payments
LibraAccount::deposit(payee1, coin1);
LibraAccount::deposit(payee2, coin2)
}
}
This concludes our "tour" of transaction scripts. For more examples, including the transaction scripts supported in the initial testnet, refer to libra/language/stdlib/transaction_scripts
.
Writing Modules
We will now turn our attention to writing our own Move modules instead of just reusing the existing LibraAccount
and Libra
modules. Consider this situation:
Bob is going to create an account at address a at some point in the future. Alice wants to "earmark" some of her coins for Bob so that he can pull them into his account once it is created. But she also wants to be able to remove the earmark from her coins if, for example, Bob never creates the account.
To solve this problem for Alice, we will write a module EarmarkedLibra
which:
- Declares a new resource type
EarmarkedLibra::T
that wraps a Libra::T and recipient address. - Allows Alice to create such a type and publish it under her account (the
create
procedure). - Allows Bob to claim the resource (the
claim_for_recipient
procedure). - Allows anyone with an
EarmarkedLibra::T
to destroy it and acquire the underlying coin (theunwrap
procedure).
// The address where the module will be published:
address 0xcc2219df031a68115fad9aee98e051e9 {
// A module for earmarking a coin for a specific recipient
module EarmarkedLibraCoin {
use 0x0::Libra;
// A wrapper containing a generic Libra and the address of the recipient the
// coin is earmarked for.
resource struct T<Token> {
coin: Libra::T<Token>,
recipient: address
}
// Create a new earmarked coin with the given `recipient`.
// Publish the coin under the transaction sender's account address.
public fun create<Token>(coin: Libra::T<Token>, recipient: address) {
// Construct or "pack" a new resource of type T. Only procedures of the
// `EarmarkedLibraCoin` module can create an `EarmarkedLibraCoin.T`.
let t = T { coin, 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);
}
// Allow the transaction sender to claim a coin that was earmarked for her.
public fun claim_for_recipient<Token>(
earmarked_coin_address: address
): Libra::T<Token> acquires T {
// Remove the earmarked coin resource published under `earmarked_coin_address`
// and "unpack" it to remove `coin and `recipient`.
// If there is no resource of type T<Token> published under the address, this will fail.
let T { coin, recipient } = move_from<T<Token>>(earmarked_coin_address);
// 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(recipient == Transaction::sender(), 99);
// Return the coin
coin
}
// Allow the creator of the earmarked coin to reclaim it.
public fun claim_for_creator<Token>(): Libra::T<Token> acquires T {
let T { coin, recipient:_ } = move_from<T<Token>>(Transaction::sender());
coin
}
}
}
Alice can create an earmarked coin for Bob by creating a transaction script that invokes create
on Bob's address a and a Libra::T
that she owns. Once a has been created, Bob can claim the coin by sending a transaction from a. This invokes claim_for_recipient
to claim a Libra::T
for Bob that he can store in whichever address he wishes. If Bob takes too long to create an account under a and Alice wants to remove the earmark from her coins, she can do so by using claim_for_creator
.