[EN] My First Impression of Sui Move — (2) Building a Sword Example

Sigrid Jin
DSRV
Published in
7 min readJul 25, 2022

--

DSRV Research publishes in-depth blockchain-related content with the aim of safely onboarding more people to Web3.

Disclaimer: This article is for informational purposes only and should not be taken as financial advice. No information contained within this article is a recommendation to invest in any of the assets mentioned. All investors are advised to thoroughly conduct their own research before making any financial decisions.

[Series on My First Impression of Sui Move]

  1. Introduction & Minting a simple NFT
  2. Building a Simple Sword Example
  3. Building a Simple Tic-Tac-Toe Example with Frontend

Building an example — Forging a Sword

Let’s look into another example with a simple fantasy game. Be sure that your working computer has Sui binaries. Clone the repository, as this tutorial assumes that you are working on the Sui repository.

The directory structure should now look like the following. You can either refer to the code example here or in the official documentation. Create some files in the current directory, which is parallel to the Sui repository.

For starters, the module should be declared first. We want a sword to be an upgradeable object that may be used by several users.

The struct must include public accessor functions in its module if the developer wishes to access sword characteristics from a different package. The asset also features magic and strength fields that describe the values of its various attribute fields, in addition to the required id field and key and store abilities.

Let’s write some testing methods first. To construct a unique identifier for our sword, we must first create a mockup of the TxContext struct. Next, we create the actual sword. Finally, we use its accessor methods to make sure they provide the expected results.

Note that the sword itself is supplied to the function’s accessor methods as a read-only reference argument, but the dummy context is passed as a mutable reference argument &mut to the tx_context::new_id function.

Let’s now go into the specifics of the new testing function. The initial step is to generate several addresses that represent users in the testing environment. We have an admin user with two regular users participating in the game. The first scenario is to create by initiating the first transaction on behalf of the admin, which also generates a sword and transfers ownership to the owner.

The second transaction is carried out by the first owner. Note that the initial owner is supplied as a parameter to the test_scenario::next_tx function. They transfer ownership of the sword to the final owner.

The test_scenario module comes in at this point since its take_owned method makes an object of Sword owned by an address that performs within the current transaction. In this scenario, the object retrieved from the storage is moved to a different address.

The last transaction is carried out by the final owner, who obtains the Sword object from storage and verifies that it has the appropriate characteristics. It is worth noting that once an object is made available by creating or fetching it from simulated storage, it cannot simply disappear.

The test_scenario package provides a simple method, similar to what occurs when Move is performed in the context of Sui– just returning the sword to the object pool using the test_scenario::return_owned function.

The Move.toml file must contain certain metadata in order to create a package that contains this straightforward module, including the package name, version, local dependency path (to find the Sui framework code), and package numeric ID, which must be 0x0 for user-defined modules so as to facilitate package publishing.

The Move.toml file’s MoveStdlib dependent local file path must be changed so that it points to the right Move standard library. To do this, I used git to clone the repository, which allowed me to install the right MoveStdlib locally. I then made the following changes to Move.toml.

Ensure that you are in the right directory containing the package, then build with the following command.

The result should look like the following.

A public function with the #[test] annotation and no arguments or return values has a single Move unit test. The following command, which should be performed in the directory titled ‘my move package’ in our running example, causes the testing framework to carry out the aforementioned functions.

The result should resemble the following. And, unsurprisingly, your unit test failed because we have not written any implementation logic yet.

Tests drive the development, which sounds simple huh? To pass the test, you need to write the method sword_create on the struct above. The new method is self-explanatory and makes use of the same internal Sui modules, TxContext and Transfer as in the earlier parts.

Continuing with our fantasy game example. I am going to introduce a Forge object that will be engaged in the sword-making process. Let’s say the Forge singleton object needs to keep track of how many swords have been manufactured. Defining the Forge struct as the following. Then, the module has a getter that returns the number of forged swords.

To make the TxContext struct available for function declarations, we must add an extra import line at the module level in order for this code to construct.

We can now run the test command again and see that we now have two successful tests for our module.

Move claims to be an object-oriented programming language, which means that we are falling short of creating a constructor method to date. Each module in a package can have a custom initializer function that will be called when the package is published. Now you can write a method to test module initialization.

As you can see in the test method, we explicitly call the initializer in the first transaction and then verify if the Forge object has been created and correctly initialized in the next transaction.

When you run the test, definitely, it throws the error saying no init module is implemented.

To be performed during publishing (or deploying) the module, write the module initializer as the following.

Running your designated unit tests, and they should be passed.

Let’s begin the game. Before publishing the module, let us take a look at the account addresses we own in our CLI client with the command. Because these addresses are produced at random, they will differ from what you see.

Because we’ll be utilizing the address and gas objects again and again throughout this publishing process, let’s declare them environment variables so we don’t have to put them in every time.

For the address above, let’s discover the gas object. If you do not have anything for a gas object, go to the first section of this article to request test SUI tokens at the faucet.

Only one gas item is required. So, for each account, choose the first gas item. I have already published a plethora of modules beforehand so that the value of the initial gas object is diminished to 39063. Again, various IDs will be shown on your machine. Let’s include these in our environment variable as well.

Let’s publish your module! Run the command below. Make sure you enter the right path of your module after --path flag. The root directory of your module is the directory where Move.toml is located. From my side, I ran the command directly on the root directory of the module.

I have my transaction hash which is AdSdjYdaCgfnljF0bk50ZWVdk+/cgafSYB1eA/d+Ca8=. The transaction hash on Sui Explorer may include the bytecode distributed on Sui's blockchain state. The package was successfully published. Some gas was charged, resulting in a change in the initial gas value. During the publishing process, the init function was called so that module initializers had run. The sword_created value is initialized to zero.

The package above is not able to find on the explorer now, unfortunately, the devnet is regularly wiped out.

The newly published package has the ID 0x5f35e44748ddc37ab57f7d97435b3b984d13d8e6 which would be different from your side. We add the package to another environment variable.

You would get the PACKAGE id and newly created Forge object id. Use the sui client call feature to call the designated function after --function flag. You can also pass arguments after the --args flag. In this example, the first argument was the Forge object so that passing the published Forge Id would be accurate.

The second and third argument is to pass an unsigned integer to specify the magic and strength. The third argument is the address of the recipient where we are going to inject the $RECIPIENT environment argument declared above. The last argument which is TxContext is injected under the hood. Thanks, Sui CLI!

Your Forge object has an incremented sword_created value, as we declared in the sword_create function at the m1.move file to increment the swords_created value by 1 after creating a new sword. Looking at the example transaction on the Sui Explorer, you could find the newly created objectId. It would definitely be the new Sword object.

This is it! You have successfully written the contract module for Sui, published it, and called the function. In chapter 3, we will look into another simple Tic-tac-toe example to further appreciate the Move language and its ecosystem.

Author
Sigrid Jin of DSRV, Technical Writer & Developer Evangelist (Twitter @sigridjin_eth)

Reviewed By
Tilly of DSRV, Communications Manager

--

--