title |
---|
Write Smart Contracts with Sui Move |
Welcome to the Sui tutorial for building smart contracts with the Move language. This tutorial provides a brief explanation of the Move language and includes concrete examples to demonstrate how Move can be used in Sui.
Move is an open source language for writing safe smart contracts. It was originally developed at Facebook to power the Diem blockchain. However, Move was designed as a platform-agnostic language to enable common libraries, tooling, and developer communities across blockchains with vastly different data and execution models. Sui, 0L, and Starcoin are using Move, and there are also plans to integrate the language in several upcoming and existing platforms (e.g., Celo).
The Move language documentation is available in the Move GitHub repository and includes a tutorial and a book describing language features in detail. These are invaluable resources to deepen your understanding of the Move language but not strict prerequisites to following the Sui tutorial, which we strived to make self-contained. Further, Sui does differ in some ways from Move, which we explore here.
In Sui, Move is used to define, create, and manage programmable Sui objects representing user-level assets. Sui's object system is implemented by adding new functionality to Move while also imposing additional restrictions, creating a dialect of Move (a.k.a. Sui Move) that makes certain parts of the original Move documentation not applicable to smart contract development in Sui. Consequently, it's best to simply follow this tutorial and the relevant Move documentation links within.
Before we look at the Move code included with Sui, let's talk briefly about Move code organization, which applies both to code included with Sui and the custom code written by the developers.
The main unit of Move code organization (and distribution) is a
package. A package consists of a set of modules defined in separate
files with the .move
extension. These files include Move functions and
type definitions. A package must include the Move.toml
manifest file
describing package configuration, for example package metadata or
package dependencies. See
Move.toml
for more information about package manifest files.
The minimal package source directory structure looks as follows and
contains the manifest file and the sources
subdirectory where one or
more module files are located:
my_move_package
├── Move.toml
├── sources
├── my_module.move
See Package Layout and Manifest Syntax for more information on package layout.
We are now ready to look at some Move code! You can either keep reading for an introductory description of the main Move language constructs or you can jump straight into the code by writing a simple Move package, and checking out additional code examples.
The Sui platform includes framework Move code that is needed to
bootstrap Sui operations. In particular, Sui supports multiple
user-defined coin types, which are custom assets defined in the Move
language. Sui framework code contains the Coin
module supporting
creation and management of custom coins. The Coin
module is
located in the
coin.move
file. As you would expect, the manifest file describing how to build the
package containing the Coin
module is located in the corresponding
Move.toml
file.
Let's see how module definition appears in the Coin
module file:
module sui::coin {
...
}
(Let's not worry about the rest of the module contents for now; you can read more about modules in the Move book later.)
Important: In Sui Move, package names are always in CamelCase, while the address alias is lowercase, for examples
sui = 0x2
andstd = 0x1
. So:Sui
= name of the imported package (Sui = sui framework),sui
= address alias of 0x2,sui::sui
= module sui under the address 0x2, andsui::sui::SUI
= type in the module above.
As we can see, when defining a module we specify the module name
(Coin
), preceded by the name of the package where this module resides
(Sui
). The combination of the package name and the module name
is used to uniquely identify a module in Move source code (e.g., to be
able to use if from other modules). The package name is globally
unique, but different packages can contain modules with the same name.
Module names are not unique, but combined with unique package name renders
a unique combination.
For example, if you have package "P" that has been published, you cannot publish another package named "P". At the same time you can have module "P1::M1", "P2::M1", and "P1::M2" but not another, say, "P1::M1" in the system at the same time.
In addition to having a presence at the source code level, as we discussed in Move code organization, a package in Sui is also a Sui object and must have a unique numeric ID in addition to a unique name, which is assigned in the manifest file:
[addresses]
sui = "0x2"
The Coin
module defines the Coin
struct type that can be used to
represent different types of user-defined coins as Sui objects:
struct Coin<phantom T> has key, store {
id: UID,
value: u64
}
Move's struct type is similar to struct types defined in other programming languages, such as C or C++, and contains a name and a set of typed fields. In particular, struct fields can be of a primitive type, such as an integer type, or of a struct type.
You can read more about Move primitive types and structs in the Move book.
In order for a Move struct type to define a Sui object type such as
Coin
, its first field must be id: UID
, which is a
struct type defined in the
object module. The
Move struct type must
also have the key
ability, which allows the object to be persisted
in Sui's global storage. Abilities of a Move struct are listed after
the has
keyword in the struct definition, and their existence (or
lack thereof) helps enforcing various properties on a definition or on
instances of a given struct.
You can read more about struct abilities in the Move book.
The reason that the Coin
struct can represent different types of
coin is that the struct definition is parameterized with a type
parameter. When an instance of the Coin
struct is created, it can
be passed an arbitrary concrete Move type (e.g. another struct type)
to distinguish different types of coins from one another.
Learn about Move type parameters known as generics and the optional phantom keyword at your leisure.
In particular, one type of custom coin already defined in Sui is
Coin<SUI>
, which represents a token used to pay for Sui
computations (more generally known as gas) - in this case, the concrete type used to parameterize the
Coin
struct is the SUI
struct in the
SUI module:
struct SUI has drop {}
We will show how to define and instantiate custom structs in the section describing how to write a simple Move package.
Similarly to other popular programming languages, the main unit of
computation in Move is a function. Let us look at one of the simplest
functions defined in the
Coin module, that is
the value
function.
public fun value<T>(self: &Coin<T>): u64 {
self.value
}
This public function can be called by functions in other modules to
return the unsigned integer value currently stored in a given
instance of the Coin
struct. Direct access to fields of a struct is
allowed only within the module defining a given struct as described in
Privileged Struct Operations.
The body of the function simply retrieves the value
field from the
Coin
struct instance parameter and returns it. Note that the
coin parameter is a read-only reference to the Coin
struct instance,
indicated by the &
preceding the parameter type. Move's type system
enforces an invariant that struct instance arguments passed by
read-only references (as opposed to mutable references) cannot be
modified in the body of a function.
You can read more about Move references in the Move book.
We will show how to call Move functions from other functions and how to define the new ones in the section describing how to write a simple Move package.
The Sui flavor of the Move language also defines entry functions. These must satisfy a certain set of properties and can be called directly from Sui (e.g., from a Sui application written in a different language).
One of the basic operations in Sui is a gas object transfer between addresses representing individual users. The gas object transfer implementation in the SUI module is also a simple example of the use of an entry function:
public entry fun transfer(c: coin::Coin<SUI>, recipient: address, _ctx: &mut TxContext) {
...
}
(Let's not worry about the function body for now - since the function is part of Sui framework, you can trust that it will do what it is intended to do.)
In general, an entry function, must satisfy the following properties:
- have the
entry
modifier- Note: The visibility does not matter. The function can be
public
,public(friend)
, or internal.
- Note: The visibility does not matter. The function can be
- have no return value
- (optional) have a mutable reference to an instance of the
TxContext
struct defined in the TxContext module as the last parameter
More concretely, the transfer
function is public, has no return
value, and has three parameters:
c
- represents a gas object whose ownership is to be transferredrecipient
- the address of the intended recipient_ctx
- a mutable reference to an instance of theTxContext
struct (in this particular case, this parameter is not actually used in the function's body as indicated by its name starting with_
)- Note that since it is unused, the parameter could be removed. The mutable reference to the
TxContext
is optional for entry functions.
- Note that since it is unused, the parameter could be removed. The mutable reference to the
Calling Move code describes how to call the transfer
function from the Sui CLI client.