Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Build and Test

Before building anything, we must implement our contract logic. Copy the following code into the sources/counter.move file created in the previous step:

module counter::counter;

use stylus::{
    tx_context::TxContext,
    object::{Self, UID},
    transfer::{Self}
};

#[test_only]
use stylus::test_scenario;

/// Initial value for new counters.
const INITIAL_VALUE: u64 = 1;

/// A simple counter object.
public struct Counter has key {
    id: UID,
    owner: address,
    value: u64
}

/// Create a new counter with initial value.
entry fun create(ctx: &mut TxContext) {
  transfer::share_object(Counter {
    id: object::new(ctx),
    owner: ctx.sender(),
    value: INITIAL_VALUE,
  });
}

/// Increment a counter by 1.
entry fun increment(counter: &mut Counter) {
    counter.value = counter.value + 1;
}


/// Read counter.
#[ext(abi(view))]
entry fun read(counter: &Counter): u64 {
    counter.value
}

/// Set value (only runnable by the Counter owner)
entry fun set_value(counter: &mut Counter, value: u64, ctx: &TxContext) {
    assert!(counter.owner == ctx.sender(), 0);
    counter.value = value;
}

//
// Unit tests
//
#[test]
fun test_increment() {
    let mut ctx = test_scenario::new_tx_context();
    let uid = object::new(&mut ctx);
    let mut c = Counter { id: uid, owner: @0x1, value: 0 };

    c.increment();
    assert!(c.value == 1);

    test_scenario::drop_storage_object(c);
}

#[test]
fun test_read() {
    let mut ctx = test_scenario::new_tx_context();
    let uid = object::new(&mut ctx);
    let c = Counter { id: uid, owner: @0x2, value: 42 };

    let v = c.read();
    assert!(v == 42);

    test_scenario::drop_storage_object(c);
}

#[test]
fun test_set_value_by_owner() {
    let mut ctx = test_scenario::new_tx_context();
    let uid = object::new(&mut ctx);

    let mut c = Counter {
        id: uid,
        owner: test_scenario::default_sender(),
        value: 5
    };

    c.set_value(99, &ctx);

    assert!(c.value == 99);

    test_scenario::drop_storage_object(c);
}

#[test, expected_failure]
fun test_set_value_wrong_owner_should_fail() {
    test_scenario::set_sender_address(@0x5);
    let mut ctx = test_scenario::new_tx_context();
    let uid = object::new(&mut ctx);
    let mut c = Counter { id: uid, owner: @0x4, value: 5 };


    c.set_value(99, &ctx);

    assert!(c.value == 99);

    test_scenario::drop_storage_object(c);
}

There are a lot of new concepts in this code that we will cover in future sections. For now, just note that we have defined a simple counter contract with the ability to create, increment, read, and set the value of a counter. We have also included some unit tests to verify the functionality of our contract.

Building the Project

Now that we have our contract code in place, we can build the project using the move-stylus CLI. Open a terminal, navigate to the root directory of your project (counter), and run the following command:

move-stylus build

You should see output indicating that the build was successful:

INCLUDING DEPENDENCY StylusFramework
INCLUDING DEPENDENCY MoveStdlib
BUILDING counter
COMPILING counter

After building, the code is ready to be deployed to a blockchain or tested locally. We will cover deployment in a later section.

Running Tests

To ensure that our contract works as expected, we can run the unit tests we defined earlier. Use the following command to run the tests:

move-stylus test

You should see output indicating that all tests have passed:

COMPILING counter

Running 0x0::counter tests (./sources/counter.move)

  0x0::counter::test_increment ... PASSED
  0x0::counter::test_read ... PASSED
  0x0::counter::test_set_value_by_owner ... PASSED
  0x0::counter::test_set_value_wrong_owner_should_fail [expected failure] ... PASSED

Total Tests : 4, Passed: 4, Failed: 0.