Skip to content

Commit

Permalink
[doc] fix Move package tutorial
Browse files Browse the repository at this point in the history
The package tutorial is currently broken, which is confusing some folks on Discord and in builder's groups. Let's take this opportunity to fix and simplify the tutorial:

* Use `sui move new` to create the package skeleton
* Fix `sui move new` to create a Move.toml that depends on the devnet branch + has a named alias for `sui` by default
* Fix unused imports in the example code
* Expand the example code and explain the different parts of the module
  • Loading branch information
sblackshear committed Sep 12, 2022
1 parent 994f962 commit 2b974a9
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 111 deletions.
13 changes: 11 additions & 2 deletions crates/sui/src/sui_move/new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
use clap::Parser;
use move_cli::base::new;
use std::path::PathBuf;
use sui_types::SUI_FRAMEWORK_ADDRESS;

const SUI_PKG_NAME: &str = "Sui";
const SUI_PKG_PATH: &str = "{ git = \"https://github.com/MystenLabs/sui.git\", subdir = \"crates/sui-framework\", rev = \"main\" }";

// Use devnet by default. Probably want to add options to make this configurable later
const SUI_PKG_PATH: &str = "{ git = \"https://github.com/MystenLabs/sui.git\", subdir = \"crates/sui-framework\", rev = \"devnet\" }";

#[derive(Parser)]
pub struct New {
Expand All @@ -21,7 +24,13 @@ impl New {
path,
"0.0.1",
[(SUI_PKG_NAME, SUI_PKG_PATH)],
[(name, "0x0")],
[
(name, "0x0"),
(
&SUI_PKG_NAME.to_lowercase(),
&SUI_FRAMEWORK_ADDRESS.to_string(),
),
],
"",
)?;
Ok(())
Expand Down
151 changes: 60 additions & 91 deletions doc/src/build/move/write-package.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,134 +2,103 @@
title: Write a Sui Move Package
---

##
##

In order to build a Move package and run code defined in
this package, first [install Sui binaries](../install.md#binaries) and
[clone the repository](../install.md#source-code) as this tutorial assumes
you have the Sui repository source code in your current directory.
In order to build a Move package and run code defined in this package, first [install Sui binaries](../install.md#binaries).

Refer to the code example developed for this tutorial in the
[m1.move](https://github.com/MystenLabs/sui/tree/main/sui_programmability/examples/move_tutorial/sources/m1.move) file.
### Creating the package

The directory structure used in this tutorial should at the moment
look as follows (assuming Sui has been cloned to a directory called
"sui"):
First, create an empty Move package:

```
current_directory
├── sui
```

For convenience, make sure the path to Sui binaries
(`~/.cargo/bin`), including the `sui` command used throughout
this tutorial, is part of your system path:

```
$ which sui
``` shell
$ sui move new my_first_package
```

### Creating the directory structure
This creates a skeleton Move project in the `my_first_package` directory. Let's take a look at the package manifest created by this command:

Now proceed to creating a package directory structure in the current
directory, parallel to the `sui` repository. It will contain an
empty manifest file and an empty module source file following the
[Move code organization](../move/index.md#move-code-organization)
described earlier.
```shell
$ cat my_first_package/Move.toml
[package]
name = "my_first_package"
version = "0.0.1"

So from the same directory containing the `sui` repository create a
parallel directory to it by running:
[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" }

``` shell
$ mkdir -p my_move_package/sources
touch my_move_package/sources/m1.move
touch my_move_package/Move.toml
[addresses]
my_first_package = "0x0"
sui = "0x2"
```
The directory structure should now be (please note that directories at the same indentation level in the figure below should also be at the same level in the file system):
This file contains:
* Package metadata such as name and version (`[package]` section)
* Other packages that this package depends on (`[dependencies]` section). This package only depends on the Sui Framework, but other third-party dependencies should be added here.
* A list of *named addresses* (`[addresses]` section). These names can be used as convenient aliases for the given addresses in the source code.
```
current_directory
├── sui
├── my_move_package
├── Move.toml
├── sources
├── m1.move
```
### Defining the package
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 upgradable asset that can be shared between different players. A
sword asset can be defined similarly to another asset we are already
familiar with from our
[First look at Move source code](../move/index.md#first-look-at-move-source-code). That
is a `Coin` struct type.
Let's start by creating a source file in the package:
``` shell
$ touch my_first_package/sources/my_module.move
```
Let us put the following module and struct
definitions in the `m1.move` file:
and adding the following code to the `my_module.move` file:
``` rust
module my_first_package::m1 {
```move
module my_first_package::my_module {
// Part 1: imports
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::TxContext;
// Part 2: struct definitions
struct Sword has key, store {
id: UID,
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
[Object package](https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/sources/object.move) from
Sui framework to gain access to the `ID` 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 in [Move
functions](#move-functions) (please make sure you add these functions,
and all the following code in this tutorial, in the scope of our
package - between curly braces starting and ending the package
definition):

``` rust
struct Forge has key, store {
id: UID,
swords_created: u64,
}
// Part 3: module initializer to be executed when this module is published
fun init(ctx: &mut TxContext) {
let admin = Forge {
id: object::new(ctx),
swords_created: 0,
};
// transfer the forge object to the module/package publisher
transfer::transfer(admin, tx_context::sender(ctx));
}
// Part 4: accessors required to read the struct attributes
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 package numeric ID, which must be `0x0` for user-defined modules
to facilitate [package publishing](../cli-client.md#publish-packages).
public fun swords_created(self: &Forge): u64 {
self.swords_created
}
// part 5: public/ entry functions (introduced later in the tutorial)
// part 6: private functions (if any)
}
```
[package]
name = "MyFirstPackage"
version = "0.0.1"
[dependencies]
Sui = { local = "../sui/crates/sui-framework" }
Let's break down the four different parts of this code:
[addresses]
my_first_package = "0x0"
```
1. Imports: these allow our module to use types and functions declared in other modules. In this case, we pull in imports from three different modules.
2. Struct declarations: these define types that can be created/destroyed by this module. Here the `key` *abilities* indicate that these structs are Sui objects that can be transferred between addresses. The `store` ability on the sword allows it to appear in fields of other structs and to be transferred freely.
3. Module initializer: this is a special function that is invoked exactly once when the module is published.
See the [Move.toml](https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/move_tutorial/Move.toml)
file used in our [end-to-end tutorial](../../explore/tutorials.md) for an example.
4. Accessor functions--these allow the fields of the fields of module's struct to be read from other modules.
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

module my_first_package::m1 {
module my_first_package::my_module {
// Part 1: imports
use sui::object::{Self, UID};
use sui::transfer;
use sui::tx_context::TxContext;

// Part 2: struct definitions
struct Sword has key, store {
id: UID,
magic: u64,
strength: u64,
}

struct Forge has key, store {
struct Forge has key {
id: UID,
swords_created: u64,
}

// module initializer to be executed when this module is published
// Part 3: module initializer to be executed when this module is published
fun init(ctx: &mut TxContext) {
use sui::transfer;
use sui::tx_context;
Expand All @@ -25,14 +28,10 @@ module my_first_package::m1 {
swords_created: 0,
};
// transfer the forge object to the module/package publisher
// (presumably the game admin)
transfer::transfer(admin, tx_context::sender(ctx));
}

public fun swords_created(self: &Forge): u64 {
self.swords_created
}

// Part 4: accessors required to read the struct attributes
public fun magic(self: &Sword): u64 {
self.magic
}
Expand All @@ -41,8 +40,12 @@ module my_first_package::m1 {
self.strength
}

public fun swords_created(self: &Forge): u64 {
self.swords_created
}

// Part 5: entry functions to create and transfer swords
public entry fun sword_create(forge: &mut Forge, magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) {
use sui::transfer;
// create a sword
let sword = Sword {
id: object::new(ctx),
Expand All @@ -54,12 +57,7 @@ module my_first_package::m1 {
forge.swords_created = forge.swords_created + 1;
}

public entry fun sword_transfer(sword: Sword, recipient: address) {
use sui::transfer;
// transfer the sword
transfer::transfer(sword, recipient);
}

// Part 6: tests
#[test]
public fun test_module_init() {
use sui::test_scenario;
Expand Down Expand Up @@ -113,7 +111,7 @@ module my_first_package::m1 {
// extract the sword owned by the initial owner
let sword = test_scenario::take_owned<Sword>(scenario);
// transfer the sword to the final owner
sword_transfer(sword, final_owner);
transfer::transfer(sword, final_owner);
};
// fourth transaction executed by the final sword owner
test_scenario::next_tx(scenario, &final_owner);
Expand All @@ -131,7 +129,6 @@ module my_first_package::m1 {

#[test]
public fun test_sword_create() {
use sui::transfer;
use sui::tx_context;

// create a dummy TxContext for testing
Expand All @@ -151,5 +148,4 @@ module my_first_package::m1 {
let dummy_address = @0xCAFE;
transfer::transfer(sword, dummy_address);
}

}

0 comments on commit 2b974a9

Please sign in to comment.