title |
---|
Debug and Publish the Sui Move Package |
At the moment there isn't a yet debugger for Move. To help with debugging, however, you could use Std::Debug
module to print out arbitrary value. To do so, first import the Debug
module:
use Std::Debug;
Then in places where you want to print out a value v
, regardless of its type, simply do:
Debug::print(&v);
or the following if v is already a reference:
Debug::print(v);
Debug
module also provides a function to print out the current stacktrace:
Debug::print_stack_trace();
Alternatively, any call to abort
or assertion failure will also print the stacktrace at the point of failure.
For functions in a Move package to actually be callable from Sui (rather than for Sui execution scenario to be emulated), the package has to be published to Sui's distributed ledger where it is represented as a Sui object.
At this point, however, the
sui move
command does not support package publishing. In fact, it is
not clear if it even makes sense to accommodate package publishing,
which happens once per package creation, in the context of a unit
testing framework. Instead, one can use a Sui CLI client to
publish Move code and to
call it. See the
Sui CLI client documentation for a description of how
to publish the package we have written as as
part of this tutorial.
There is, however, an important aspect of publishing packages that affects Move code development in Sui - each module in a package can include a special initializer function that will be run at the publication time. The goal of an initializer function is to pre-initialize module-specific data (e.g., to create singleton objects). The initializer function must have the following properties in order to be executed at publication:
- name
init
- single parameter of
&mut TxContext
type - no return values
- private visibility
While the sui move
command does not support publishing explicitly,
we can still test module initializers using our testing framework -
one can simply dedicate the first transaction to executing the
initializer function. Let us use a concrete example to illustrate
this.
Continuing our fantasy game example, let's introduce a
concept of a forge that will be involved in the process of creating
swords - for starters let it keep track of how many swords have been
created. Let us define the Forge
struct and a function returning the
number of created swords as follows and put into the m1.move
file:
struct Forge has key, store {
info: Info,
swords_created: u64,
}
public fun swords_created(self: &Forge): u64 {
self.swords_created
}
In order to keep track of the number of created swords we must
initialize the forge object and set its sword_create
counts to 0.
And module initializer is the perfect place to do it:
// module initializer to be executed when this module is published
fun init(ctx: &mut TxContext) {
use sui::transfer;
use sui::tx_context;
let admin = Forge {
info: object::new(ctx),
swords_created: 0,
};
// transfer the forge object to the module/package publisher
// (presumably the game admin)
transfer::transfer(admin, tx_context::sender(ctx));
}
In order to use the forge, we need to modify the sword_create
function to take the forge as a parameter and to update the number of
created swords at the end of the function:
public entry fun sword_create(forge: &mut Forge, magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) {
...
forge.swords_created = forge.swords_created + 1;
}
We can now create a function to test the module initialization:
#[test]
public fun test_module_init() {
use sui::test_scenario;
// create test address representing game admin
let admin = @0xABBA;
// first transaction to emulate module initialization
let scenario = &mut test_scenario::begin(&admin);
{
init(test_scenario::ctx(scenario));
};
// second transaction to check if the forge has been created
// and has initial value of zero swords created
test_scenario::next_tx(scenario, &admin);
{
// extract the Forge object
let forge = test_scenario::take_owned<Forge>(scenario);
// verify number of created swords
assert!(swords_created(&forge) == 0, 1);
// return the Forge object to the object pool
test_scenario::return_owned(scenario, forge)
}
}
As we can see in the test function defined above, in the first transaction we (explicitly) call the initializer, and in the next transaction we check if the forge object has been created and properly initialized.
If we try to run tests on the whole package at this point, we will
encounter compilation errors in the existing tests due to the
sword_create
function signature change. We will leave the changes
required for the tests to run again as an exercise for the reader. The
entire source code for the package we have developed (with all the
tests properly adjusted) can be found in
m1.move.