From 2b974a9f0b30ae6fafc7fa4e82e428721eeab32a Mon Sep 17 00:00:00 2001 From: Sam Blackshear Date: Sat, 10 Sep 2022 16:02:10 -0700 Subject: [PATCH] [doc] fix Move package tutorial 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 --- crates/sui/src/sui_move/new.rs | 13 +- doc/src/build/move/write-package.md | 151 +++++++----------- .../sources/{m1.move => my_module.move} | 32 ++-- 3 files changed, 85 insertions(+), 111 deletions(-) rename sui_programmability/examples/move_tutorial/sources/{m1.move => my_module.move} (90%) diff --git a/crates/sui/src/sui_move/new.rs b/crates/sui/src/sui_move/new.rs index 41e9017b16e7e..d553d3cc5f30e 100644 --- a/crates/sui/src/sui_move/new.rs +++ b/crates/sui/src/sui_move/new.rs @@ -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 { @@ -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(()) diff --git a/doc/src/build/move/write-package.md b/doc/src/build/move/write-package.md index 9e098d6ccac10..9f38b154eac34 100644 --- a/doc/src/build/move/write-package.md +++ b/doc/src/build/move/write-package.md @@ -2,108 +2,80 @@ 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 } @@ -111,25 +83,22 @@ definition): 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. diff --git a/sui_programmability/examples/move_tutorial/sources/m1.move b/sui_programmability/examples/move_tutorial/sources/my_module.move similarity index 90% rename from sui_programmability/examples/move_tutorial/sources/m1.move rename to sui_programmability/examples/move_tutorial/sources/my_module.move index 75257ceb255ce..65c6527f1245a 100644 --- a/sui_programmability/examples/move_tutorial/sources/m1.move +++ b/sui_programmability/examples/move_tutorial/sources/my_module.move @@ -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; @@ -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 } @@ -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), @@ -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; @@ -113,7 +111,7 @@ module my_first_package::m1 { // extract the sword owned by the initial owner let sword = test_scenario::take_owned(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); @@ -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 @@ -151,5 +148,4 @@ module my_first_package::m1 { let dummy_address = @0xCAFE; transfer::transfer(sword, dummy_address); } - }