Skip to content

Commit

Permalink
[docs] Fleshed out Move docs using sui-move CLI a bit more (MystenLab…
Browse files Browse the repository at this point in the history
…s#563)

* Fleshed out Move docs using sui-move CLI a bit more

* Addressed review comments

* Replaced remaining occurences of FastX with Sui

* Re-added accidentally removed part of the manifest file
  • Loading branch information
awelc authored Feb 25, 2022
1 parent dbd16e3 commit 731fa7f
Showing 1 changed file with 270 additions and 59 deletions.
329 changes: 270 additions & 59 deletions doc/move.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,15 @@ to deepen your understanding of the Move language, but they are not a
strict prerequisite to following the Sui tutorial which we strived to
make self-contained.

More importantly, Sui imposes additional restrictions on the code that
can be written in Move, effectively using a subset of Move (aka Sui
Move), which makes certain parts of the original Move documentation
not applicable to smart contract development in Sui.
In Sui, Move is used to define, create and manage programmable Sui
[objects](objects.md#objects) representing user-level assets. Sui
imposes additional restrictions on the code that can be written in
Move, effectively using a subset of Move (a.k.a. Sui Move), which
makes certain parts of the original Move documentation not applicable
to smart contract development in Sui (e.g., there is no concept of a
[script](https://github.com/diem/move/blob/main/language/documentation/book/src/modules-and-scripts.md#scripts)
in Sui Move). Consequently, it's best to simply follow this tutorial
and relevant Move documentation links provided in the tutorial.

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
Expand All @@ -61,37 +66,39 @@ for more information on package layout):
my_move_package
├── Move.toml
├── sources
├── MyModule.move
├── M1.move
```

We are now ready to look at some Move code!

## First look at Move source code

The Sui platform includes _framework_ Move code that is needed to
bootstrap Sui operations, for example to create and manipulate gas
objects. In particular the gas object is defined in the GAS module
located in the
[sui_programmability/framework/sources/GAS.move](../sui_programmability/framework/sources/GAS.move)
file. As you can see the manifest file for the package containing the
GAS module is located, as expected, in the
bootstrap Sui operations. In particular, Sui supports multiple
user-defined coin types, which are custom assets define in the Move
language. Sui framework code contains the Coin module supporting
creation and management of custom coins. The Coin module is
the located in the
[sui_programmability/framework/sources/Coin.move](../sui_programmability/framework/sources/Coin.move)
file. As you can see the manifest file describing how to build the
package containing the Coin module is located, as expected, in the
[sui_programmability/framework/Move.toml](../sui_programmability/framework/Move.toml)
file.

Let's see how module definition in the GAS module file looks like
Let's see how module definition in the Coin module file looks like
(let's not worry about the module content for now, though you can read
more about them in the Move
[book](https://github.com/diem/move/blob/main/language/documentation/book/src/modules-and-scripts.md#modules)
if immediately interested):

```rust
module Sui::GAS {
module Sui::Coin {
...
}
```

As we can see, when defining a module we specify the module name
(`GAS`), preceded by the name of the package where this module resides
(`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
Expand All @@ -100,77 +107,281 @@ unique, but different packages can contain modules with the same name.

In addition to having a presence at the source code level, as we
discussed [earlier](#move-code-organization), a package in Sui is also
an object, and must have a unique numeric ID in addition to a unique
name, so that it can be identified by the Sui platform. For the
framework packages this address is assigned in the manifest file:
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"
[dev-addresses]
Sui = "0x2"
```

### Move structs

The Coin module defines the `Coin` struct type which can be used to
represent different types of user-defined coins as Sui objects:

``` rust
struct Coin<phantom T> has key, store {
id: VersionedID,
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
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/SUMMARY.md#primitive-types)
and about Move structs
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/structs-and-resources.md)).


In order for a Move struct type to define a Sui object type such
`Coin`, its first field must be `id: VersionedID` (`VersionedID` is a
struct type defined in the ID
[module](../sui_programmability/framework/sources/ID.move)), and must
also have the `key` (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 Move
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/abilities.md))

The reason that the `Coin` struct can represent different types of
coin is that the struct definition is parameterized with a type
parameter. You can read more about Move type parameters (also known as
generics)
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/generics.md)
(and also about the optional `phantom` keyword
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/generics.md#phantom-type-parameters)),
but for now it suffices to say that 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.

In particular, one type of custom coin already defined in Sui is
`Coin<GAS>`, which represents a token used to pay for gas used in Sui
computations - in this case, the concrete type used to parameterize the
`Coin` struct is the `GAS` struct in the GAS
[module](../sui_programmability/framework/sources/Coin.move):

``` rust
struct GAS has drop {}
```

We will show how to define and instantiate custom structs in the
[section](#writing-a-package) describing how to write a simple
Move package.

### Move functions

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](../sui_programmability/framework/sources/Coin.move), that is
the `value` function.

``` rust
public fun value<T>(self: &Coin<T>): u64 {
self.value
}
```

### First look at Move function definition
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
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/structs-and-resources.md#privileged-struct-operations)). The
body of the function simply retrieves the `value` field from the
`Coin` struct instance parameter and returns it. Please 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
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/references.md#references)).


We will show how to call Move functions from other functions and how
to define the new ones in the [section](#writing-a-package)
describing how to write a simple Move package.


In addition to functions callable from other functions, however, the
Sui flavor of the Move language also defines so called _entry
functions_ that can be called directly from Sui (e.g., from a Sui
wallet application that can be written in a different language) and
must satisfy a certain set of properties.

#### Entry functions

One of the basic operations in Sui is transfer of gas objects between
[addresses](overview.md) representing individual users. Here is the
transfer function definition in the GAS
[module](../sui_programmability/framework/sources/GAS.move):
[addresses](overview.md) representing individual users, and one of the
simplest entry functions is defined in the GAS
[module](../sui_programmability/framework/sources/GAS.move) to
implement gas object transfer (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):

```rust
public fun transfer(c: Coin::Coin<GAS>, recipient: vector<u8>, _ctx: &mut TxContext) {
Coin::transfer(c, Address::new(recipient))
...
}
```

It is a public function called `transfer` with 3 arguments:
In general, an entry function, must satisfy the following properties:

- must be public
- must have no return value
- its parameters are ordered as follows:
- one or more Sui objects (or vectors of objects),
- one or more primitive types (or vectors of such types)
- mutable reference to an instance of the `TxContext` struct
defined in the TxContext
[module](../sui_programmability/framework/sources/TxContext.move)

More, concretely, the `transfer` function is public, has no return
value, and has three parameters:

- `c` - it represents a gas object whose ownership is to be
transferred; a gas object _type_ (`Coin::Coin<GAS>`) is `Coin`
struct (you can read more about Move structs
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/structs-and-resources.md#structs-and-resources))
defined in the Coin
[module](../sui_programmability/framework/sources/Coin.move)
parameterized with another struct `GAS` defined in the GAS
[module](../sui_programmability/framework/sources/GAS.move) (you can
read more about generic types and how they can be used to
parameterize other types
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/generics.md#generics).
transferred
- `recipient` - it is the address of the intended recipient,
represented as a vector (built-in `vector` type) of 8-bit integers
(built-in `u8` type) - you can read more about built-in primitive
types lie these
types like these
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/SUMMARY.md#primitive-types)
- `_ctx` - it is a mutable reference to an instance of the `TxContext`
struct defined in the TxContext
[module](../sui_programmability/framework/sources/TxContext.move)
(you can read more about references
[here](https://github.com/diem/move/blob/main/language/documentation/book/src/references.md)
but for now we do not have to worry about this parameter too much as
it is unused, which is indicated by its name starting with `_`)
struct (in this particular case, this parameter is not actually used
in the function's body as indicated by its name starting with `_`)

The `transfer` function calls another function defined in the Coin
module that ultimately (through a series of other calls) implements
actual logic of transferring an instance of the `Coin` struct to a
different owner (`Address::new` function is responsible for creating
an internal representation of the recipient's address). The good thing
is that (at least for now) you don't have to worry about how this
logic is implemented - you can simply trust that framework functions
included with Sui genesis will do what they are intended to do
correctly.

Now that we have some understanding of Move code organization and of
how Move functions are defined, let us write a simple package that
will simply call into existing Sui framework code.

## Writing Simple Package

Following the Move code organization described
[earlier](#move-code-organization), let us first create the package
directory structure and create an empty manifest file:
You can see how the `transfer` function is called from a sample Sui
wallet [here](wallet.md#calling-move-code).


## Writing a package

In order to be able to build a Move package and run code defined in
this package, please make sure that you have cloned the Sui repository
to the current directory and built Sui binaries as described
[here](wallet.md#build-the-binaries).

The directory structure used in this tutorial should at the moment
look as follows (assuming Sui has been cloned to a directory called
"sui"):

```
current_directory
├── sui
```

For convenience, please also make sure the path to Sui binaries
(sui/target/release) is part of your system path.

We can now proceed to creating a package directory structure and an
empty manifest file following the Move code organization described
[earlier](#move-code-organization):

``` shell
mkdir -p my_move_package/sources
touch my_move_package/Move.toml
```

The directory structure should now look as follows:

```
current_directory
├── sui
├── my_move_package
├── Move.toml
├── sources
├── M1.move
```


Let us assume that our module is part of an implementation of a
fantasy game set in medieval times, where heroes roam the land slaying
beasts with their trusted swords to gain prizes. All of these entities
will be represented by Sui objects, in particular we want a sword to
be an upgrade-able asset that can be shared between different players. A
sword asset can be defined similarly to another asset we are already
[familiar](#first-look-at-move-source-code) with, that is a `Coin`
struct type. Let us put the following module and struct definitions in
the M1.move file:

``` rust
module MyMovePackage::M1 {
use Sui::ID::VersionedID;

struct Sword has key, store {
id: VersionedID,
magic: u64,
strength: u64,
}
}
```

Since we are developing a fantasy game, in addition to the mandatory
`id` field as well as `key` and `store` abilities (same as in the
`Coin` struct), our asset has both `magic` and `strength` fields
describing its respective attribute values. Please note that we need
to import the ID
[package](../sui_programmability/framework/sources/ID.move) from Sui
framework to gain access to the `VersionedID` struct type defined in
this package.

If we want to access sword attributes from a different package, we
need to add accessor functions to our module similar to the `value`
function in the Coin package described [earlier](#move-functions):

``` rust
public fun magic(self: &Sword): u64 {
self.magic
}

public fun strength(self: &Sword): u64 {
self.strength
}
```

In order to build a package containing this simple module, we need to
put some required metadata into the Move.toml file, including package
name, package version, local dependency path to locate Sui framework
code, and (described [earlier]((#first-look-at-move-source-code)))
package numeric ID (which must be 0x0 for user-defined modules to
facilitate package [publishing](wallet.md#package-publishing)).

```
[package]
name = "MyMovePackage"
version = "0.0.1"
[dependencies]
Sui = { local = "../fastnft/sui_programmability/framework/" }
[addresses]
MyMovePackage = "0x0"
[dev-addresses]
MyMovePackage = "0x0"
```

We can now go to the directory containing our package and build it (no
output is expected on a successful build):

``` shell
cd my_move_package
sui-move build
```

Now that we have designed our asset and its accessor functions, let us
test the code we have written.

## Testing a package

TBD

0 comments on commit 731fa7f

Please sign in to comment.