DubheSuiContractsSchemasReading & Writing

Using the generated Move code

After running dubhe schemagen, each resource in your config becomes a Move module under src/<name>/sources/codegen/resources/. This page shows how to use those modules in your system contracts.

Setup

All generated modules share the same two dependencies:

use dubhe::dapp_service::DappHub;  // shared object passed to every entry function
use std::ascii::String;            // used for resource_account (entity identifier)

The resource_account parameter is a String representation of the entity’s address. In practice, call address_system::ensure_origin(ctx) to obtain it from the transaction sender:

use dubhe::address_system;
 
public entry fun my_action(dh: &mut DappHub, ctx: &mut TxContext) {
    let player = address_system::ensure_origin(ctx); // returns String
    // use `player` as resource_account in resource calls
}

Checking existence

use example::level;
use example::stats;
use example::inventory;
 
public fun check_exists(dh: &DappHub, player: String, item_id: u32) {
    // Entity single-value resource
    let exists: bool = level::has(dh, player);
 
    // Entity multi-field resource
    let exists: bool = stats::has(dh, player);
 
    // Keyed resource
    let exists: bool = inventory::has(dh, player, item_id);
 
    // Assert-style helpers (abort if condition not met)
    inventory::ensure_has(dh, player, item_id);     // abort if missing
    inventory::ensure_has_not(dh, player, item_id); // abort if already exists
}

For global resources, no resource_account is needed:

use example::total_players;
 
let exists: bool = total_players::has(dh);

Reading data

use example::level;
use example::stats;
use example::inventory;
use example::total_players;
 
public fun read_data(dh: &DappHub, player: String, item_id: u32) {
    // Single value
    let lv: u32 = level::get(dh, player);
 
    // Multi-field — returns a struct
    let s = stats::get(dh, player);
    let atk: u32 = s.attack;
    let hp:  u32 = s.hp;
 
    // Per-field accessor (no need to read the whole struct)
    let atk: u32 = stats::get_attack(dh, player);
 
    // Keyed resource
    let qty: u32 = inventory::get(dh, player, item_id);
 
    // Global singleton
    let count: u32 = total_players::get(dh);
}

Writing data

All write functions are public(package) — only modules in the same package can call them, enforcing access control.

use example::level;
use example::stats;
use example::inventory;
use example::total_players;
use example::facing;
use example::direction;
 
public entry fun level_up(dh: &mut DappHub, ctx: &mut TxContext) {
    let player = address_system::ensure_origin(ctx);
 
    // Single value
    let current = level::get(dh, player);
    level::set(dh, player, current + 1, ctx);
 
    // Multi-field (all fields at once)
    stats::set(dh, player, 150, 1000, 80, ctx); // attack, hp, defense
 
    // Per-field (partial update)
    stats::set_hp(dh, player, 500, ctx);
 
    // Keyed resource
    inventory::set(dh, player, 42u32, 10u32, ctx); // item_id=42, quantity=10
 
    // Global singleton
    let n = total_players::get(dh);
    total_players::set(dh, n + 1, ctx);
 
    // Enum value
    facing::set(dh, player, direction::new_north(), ctx);
}

Deleting data

use example::level;
use example::inventory;
 
public entry fun reset_player(dh: &mut DappHub, ctx: &mut TxContext) {
    let player = address_system::ensure_origin(ctx);
 
    level::delete(dh, player);
    inventory::delete(dh, player, 42u32);
}

Offchain resources (events)

Offchain resources only have set. They emit storage events that indexers can pick up without writing to chain state.

use example::battle_log;
 
public entry fun finish_battle(
    dh: &mut DappHub,
    enemy: address,
    won: bool,
    ctx: &mut TxContext
) {
    let player = address_system::ensure_origin(ctx);
    // Emits an event — no on-chain storage written
    battle_log::set(dh, player, enemy, won, ctx);
}

Using custom errors

use example::errors::{player_not_found_error, game_over_error};
use example::level;
 
public entry fun attack(dh: &mut DappHub, ctx: &mut TxContext) {
    let player = address_system::ensure_origin(ctx);
 
    // Pass `true` to continue, `false` to abort with the error
    player_not_found_error(level::has(dh, player));
 
    let lv = level::get(dh, player);
    // ... game logic
}

Complete system example

module mygame::combat_system;
 
use dubhe::dapp_service::DappHub;
use dubhe::address_system;
use mygame::{level, stats, inventory, battle_log};
use mygame::errors::player_not_found_error;
 
public entry fun attack_monster(
    dh: &mut DappHub,
    monster: address,
    ctx: &mut TxContext
) {
    let player = address_system::ensure_origin(ctx);
 
    // Guard: player must be registered
    player_not_found_error(level::has(dh, player));
 
    // Read current state
    let lv = level::get(dh, player);
    let s  = stats::get(dh, player);
 
    // Game logic
    let damage = s.attack * lv;
    let won = damage > 500;
 
    // Update state
    if (won) {
        level::set(dh, player, lv + 1, ctx);
        stats::set_attack(dh, player, s.attack + 10, ctx);
        inventory::set(dh, player, 1u32 /* reward item */, 1u32, ctx);
    };
 
    // Emit offchain event
    battle_log::set(dh, player, monster, won, ctx);
}

Deploy hook

The deploy hook runs automatically when genesis::run is called on first publish. Use it to initialize global singletons:

// src/<name>/sources/scripts/deploy_hook.move
module mygame::deploy_hook;
 
use dubhe::dapp_service::DappHub;
use mygame::total_players;
 
public(package) fun run(dh: &mut DappHub, ctx: &mut TxContext) {
    total_players::set(dh, 0, ctx);
}

Testing

init_test.move is generated automatically and provides a helper to bootstrap the full dapp in Move unit tests:

#[test_only]
module mygame::my_test;
 
use sui::test_scenario;
use mygame::init_test;
use mygame::level;
use dubhe::address_system;
 
#[test]
public fun test_level_up() {
    let player_addr = @0xA;
    let mut scenario = test_scenario::begin(player_addr);
 
    let mut dh = init_test::deploy_dapp_for_testing(&mut scenario);
    let ctx = test_scenario::ctx(&mut scenario);
    let player = address_system::ensure_origin(ctx);
 
    // Initial state: no level yet
    assert!(!level::has(&dh, player));
 
    // Set level
    level::set(&mut dh, player, 1, ctx);
    assert!(level::get(&dh, player) == 1);
 
    dh.destroy();
    scenario.end();
}