DEV Community

Cover image for Mastering Sui DeepBook: A Hands-On DeFi DEX Series (3)
Rajesh Royal
Rajesh Royal

Posted on

Mastering Sui DeepBook: A Hands-On DeFi DEX Series (3)

In this part of the series, we will dive into writing our first Sui Move smart contract. Before we tackle the complexity of a Decentralized Exchange (DEX), we'll start with a simpler example to understand the basics of Sui objects, ownership, and module structure. We'll build a simple "Note" contract that allows users to create and delete notes.

1. Understanding the Note Contract

Below is the code for the note contract we will be using for this tutorial. The note contract is a very simple proof of concept that allows users to create and delete notes.

We've added detailed comments to help you understand every part of the code.

1.1 The Note Module

Create a new file sources/notes.move and add the following code:

#[lint_allow(self_transfer)]
module dacade_zklogin::notes {
    use sui::tx_context::{TxContext, Self};
    use sui::transfer::Self;
    use sui::object::{Self, UID};
    use std::string::String;

    /// Struct to store the ID of the Notes (Shared Object)
    struct Notes has key {
        id: UID
    }

    /// Struct to represent a Note
    struct Note has key, store {
        id: UID,
        title: String,
        body: String
    }

    /// Module initializer to create the Notes shared object
    #[allow(unused_function)]
    fun init(ctx: &mut TxContext) {
        let notes = Notes{
            id: sui::object::new(ctx),
        };
        // Share the Notes object so it can be accessed by everyone
        transfer::share_object(notes)
    }

    /// Function to create a new note
    /// @param title: The title of the note
    /// @param body: The body of the note
    /// @param ctx: The transaction context
    public fun create_note(title: String, body: String, ctx: &mut TxContext) {
        let note = Note {
            id: object::new(ctx),
            title,
            body
        };
        // Transfer the note to the sender of the transaction
        transfer::transfer(note, tx_context::sender(ctx))
    }

    /// Function to delete a note
    /// @param note: The note object to be deleted
    /// @param _ctx: The transaction context
    public fun delete_note(note: Note, _ctx: &mut TxContext) {
        // Unpack the Note struct to access its fields
        let Note {id, title: _, body: _} = note;
        // Delete the object ID, effectively deleting the note
        sui::object::delete(id)
    }
}
Enter fullscreen mode Exit fullscreen mode

1.2 Breaking Down the Code

Let's break down the code above. The contract is pretty straightforward.

  • Structs: It has two structs: Notes and Note.

    • The Notes struct is a shared object (indicated by transfer::share_object in init). It could be used to track global state, though in this simple version it just holds a UID.
    • The Note struct represents an individual note. It has key and store abilities, meaning it can be owned by an address and transferred. It stores the id, title, and body of the note.
  • init Function: This function runs once when the package is published. It initializes the Notes struct and shares it, making it accessible to everyone.

  • create_note Function: This function allows any user to create a new note. It takes a title and body, creates a Note object, and transfers it to the sender (tx_context::sender(ctx)). This means the caller becomes the owner of the note.

  • delete_note Function: This function allows the owner of a note to delete it. It takes the Note object by value (consuming it), unpacks it, and deletes its UID.

1.3 Writing Comprehensive Tests

Testing is crucial for smart contract development. We will write a test module to verify that we can create and delete notes correctly.

Create a new file sources/notes_tests.move (or add this to your existing test file):

#[test_only]
module dacade_zklogin::notes_tests {
    use dacade_zklogin::notes::{Self, Note};
    use sui::test_scenario;
    use std::string::{Self};

    #[test]
    fun test_create_and_delete_note() {
        let user = @0xA;
        // Start a test scenario with 'user' as the sender
        let scenario = test_scenario::begin(user);

        // 1. Create a note
        {
            let ctx = test_scenario::ctx(&mut scenario);
            let title = string::utf8(b"My First Note");
            let body = string::utf8(b"This is the body of my note.");
            notes::create_note(title, body, ctx);
        };

        // 2. Verify note creation and ownership
        test_scenario::next_tx(&mut scenario, user);
        {
            // Try to take the Note object from the sender's inventory
            let note = test_scenario::take_from_sender<Note>(&scenario);

            // If we successfully took it, it means the note was created and transferred to the user.
            // Now, let's delete it.

            // 3. Delete the note
            let ctx = test_scenario::ctx(&mut scenario);
            notes::delete_note(note, ctx);
        };

        // 4. Verify note deletion
        test_scenario::next_tx(&mut scenario, user);
        {
            // To verify deletion, we check that the user no longer has the note.
            // has_most_recent_for_sender returns false if the object is no longer accessible/owned.
            assert!(!test_scenario::has_most_recent_for_sender<Note>(&scenario), 0);
        };

        test_scenario::end(scenario);
    }
}
Enter fullscreen mode Exit fullscreen mode

1.4 Running the Tests

To run the tests, open your terminal in the contract directory and run:

sui move test
Enter fullscreen mode Exit fullscreen mode

You should see output indicating that the test passed:

Running Move unit tests
[ PASS    ] dacade_zklogin::notes_tests::test_create_and_delete_note
Test result: OK. Total tests: 1; passed: 1; failed: 0
Enter fullscreen mode Exit fullscreen mode

3.5 Publishing the Contract

Now that we have tested our contract, it's time to publish it to the Sui Devnet.

  1. Switch to Devnet: Ensure your Sui client is connected to the devnet environment.

    sui client switch --env devnet
    
  2. Publish: Run the publish command. We set a gas budget to ensure the transaction has enough gas to execute.

    sui client publish --gas-budget 100000000
    

    Note: If you haven't funded your address yet, run sui client faucet to get some test SUI.

3.6 Saving Important Information

When you publish a package, the Sui CLI outputs a transaction summary. This output contains critical information that you must save for connecting your frontend application to the smart contract later.

Look for the Object Changes section in the output. You need to identify and save the following IDs:

  1. Package ID: This is the ID of the published code. It is listed under Published Objects.
  2. Shared Object ID: In our init function, we created a Notes object and shared it. You need to find the ID of this object. It will be listed under Created Objects with Owner: Shared.

Example Output Analysis:

╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes                                                                                   │
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects:                                                                                 │
│  ┌──                                                                                             │
│  │ ObjectID: 0x81833f94d4019d03fdc0d63abdf179950740f513bf434ae182e1031e0568a2b7                  │
│  │ Owner: Shared( 5 )                                                                            │
│  │ ObjectType: 0xfea5f1839d675fa03d5b02208adf2e6167336b4646399cb2248b9a7347a48998::notes::Notes  │
│  └──                                                                                             │
...
│ Published Objects:                                                                               │
│  ┌──                                                                                             │
│  │ PackageID: 0xfea5f1839d675fa03d5b02208adf2e6167336b4646399cb2248b9a7347a48998                 │
│  │ Version: 1                                                                                    │
│  │ Digest: 93isZkd3bCKDqeMUL1wUhWnxYvJT4KLc72U6buFAPS4F                                          │
│  │ Modules: notes                                                                                │
│  └──                                                                                             │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Enter fullscreen mode Exit fullscreen mode

What to save:

  • Package ID: 0xfea5...8998 (from the Published Objects section)
  • Notes Shared Object ID: 0x8183...a2b7 (from the Created Objects section where Owner is Shared)

Tip: Create a .env file or a constants.ts file in your frontend project now and save these values. You will need them to interact with the contract from the UI.

Congratulations! If you’ve made it this far, give yourself a well-deserved pat on the back, you’re officially wearing the blockchain pro badge now. 😄

You’ve successfully written, tested, and published your first Sui Move smart contract. Not bad at all.

In the next part, we’ll put these concepts to work by building the core components of the DeepBook DEX frontend, where theory meets practice and things start getting seriously interesting.

Top comments (0)