[EN] My First Impression of Sui Move — (2) Building a Sword Example
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]
- Introduction & Minting a simple NFT
- Building a Simple Sword Example
- 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 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