Skip to main content

๐Ÿ”— Move Language Integration

Discover how Dubhe harnesses Moveโ€™s revolutionary features for secure, efficient blockchain applications

Prerequisites: Basic understanding of Move language concepts and blockchain development

๐ŸŽฏ Why Move for Blockchain Applications?

Move was designed from the ground up for blockchain development, addressing fundamental security and performance challenges that plague other smart contract languages.

Resource Safety

Linear types prevent double-spending and resource duplication

Formal Verification

Mathematical proofs ensure code correctness

Performance

Compiled bytecode with optimized execution

๐Ÿ›ก๏ธ Moveโ€™s Resource Model in Dubhe

Understanding Resources vs. Structs

// In JavaScript, objects can be accidentally duplicated
let coin1 = { value: 100 };
let coin2 = coin1; // Oops! Now we have two references to the same coin
coin2.value = 200; // This affects coin1 too!

// Or worse, we can lose references entirely
function loseCoin() {
  let tempCoin = { value: 50 };
  // tempCoin is garbage collected when function exits
  // The value is lost forever!
}

Resource Abilities in Dubhe Components

Components with key ability can be stored at addresses and have unique identities.
// World state components need key ability
struct World has key {
    id: UID,
    entities: Table<u64, EntityData>,
    next_entity_id: u64,
}

// These can be owned by addresses
public fun create_world(ctx: &mut TxContext): World {
    World {
        id: object::new(ctx),
        entities: table::new(ctx),
        next_entity_id: 1,
    }
}
Components with store ability can be stored inside other structures.
// ECS components typically have store + drop
struct HealthComponent has store, drop {
    current: u64,
    maximum: u64,
}

struct PositionComponent has store, drop {
    x: u64,
    y: u64,
}

// These can be stored in entity tables
struct EntityData has store {
    components: Bag, // Can store different component types
}
// Simple data that can be copied cheaply
struct Stats has copy, drop, store {
    strength: u8,
    agility: u8,
    intelligence: u8,
}

// Complex data that should only be moved
struct Inventory has store, drop {
    items: vector<ItemID>,
    capacity: u32,
    // Expensive to copy, but can be dropped
}

// Valuable resources that cannot be dropped
struct RareItem has key, store {
    id: UID,
    rarity: u8,
    enchantments: vector<u64>,
    // Cannot be accidentally lost!
}

๐Ÿ—๏ธ Dubheโ€™s Move Architecture Patterns

World Storage Pattern

module game::world {
    use sui::table::{Self, Table};
    use sui::bag::{Self, Bag};
    
    struct World has key {
        id: UID,
        entities: Table<u64, EntityData>,
        next_entity_id: u64,
        // Global game state
        game_config: GameConfig,
    }
    
    struct EntityData has store {
        components: Bag, // Dynamic component storage
        component_types: vector<String>, // Track component types
    }
    
    // Create world singleton
    fun init(ctx: &mut TxContext) {
        transfer::share_object(World {
            id: object::new(ctx),
            entities: table::new(ctx),
            next_entity_id: 1,
            game_config: default_config(),
        });
    }
    
    // All systems operate on shared World object
    public fun spawn_entity(world: &mut World): u64 {
        let entity_id = world.next_entity_id;
        world.next_entity_id = entity_id + 1;
        
        table::add(&mut world.entities, entity_id, EntityData {
            components: bag::new(ctx),
            component_types: vector::empty(),
        });
        
        entity_id
    }
}

System Function Patterns

// Entry functions can be called externally via transactions
public entry fun attack_system(
    world: &mut World,
    attacker: u64,
    target: u64,
    weapon: u64,
    ctx: &TxContext
) {
    // Validate caller permissions
    let attacker_data = get_component<PlayerComponent>(world, attacker);
    assert!(attacker_data.owner == tx_context::sender(ctx), ENotOwner);
    
    // Execute attack logic
    let damage = calculate_damage(world, attacker, weapon);
    deal_damage(world, target, damage);
    
    // Emit events
    event::emit(AttackEvent {
        attacker,
        target,
        damage,
        timestamp: tx_context::epoch(ctx),
    });
}
// Internal functions for complex calculations
fun calculate_damage(world: &World, attacker: u64, weapon: u64): u64 {
    let stats = get_component<StatsComponent>(world, attacker);
    let weapon_data = get_component<WeaponComponent>(world, weapon);
    
    // Complex damage calculation
    let base_damage = weapon_data.damage;
    let strength_bonus = (stats.strength as u64) * 2;
    let critical_chance = stats.agility * 5; // 5% per agility point
    
    // Random critical hit (simplified)
    let random_value = pseudo_random(attacker, weapon);
    if (random_value % 100 < critical_chance) {
        (base_damage + strength_bonus) * 2 // Critical hit!
    } else {
        base_damage + strength_bonus
    }
}

fun deal_damage(world: &mut World, target: u64, damage: u64) {
    let health = get_mut_component<HealthComponent>(world, target);
    
    if (health.current > damage) {
        health.current = health.current - damage;
    } else {
        health.current = 0;
        // Add dead tag for cleanup systems
        add_component(world, target, DeadTag {});
    };
}

Event System Integration

// Events for frontend synchronization
struct EntitySpawnedEvent has copy, drop {
    entity: u64,
    entity_type: String,
    owner: address,
}

struct ComponentAddedEvent has copy, drop {
    entity: u64,
    component_type: String,
    // Note: cannot include component data directly
    // Frontend must query for updated data
}

struct AttackEvent has copy, drop {
    attacker: u64,
    target: u64,
    damage: u64,
    weapon_used: u64,
    timestamp: u64,
}

struct LevelUpEvent has copy, drop {
    player: u64,
    old_level: u8,
    new_level: u8,
    skill_points_gained: u8,
}

๐Ÿ”’ Security Patterns in Move

Capability-Based Security

// Admin capability for privileged operations
struct AdminCap has key, store {
    id: UID,
}

// Only admin can mint new game currency
public entry fun mint_currency(
    _: &AdminCap, // Capability proves admin rights
    world: &mut World,
    recipient: u64,
    amount: u64
) {
    let currency = get_mut_component<CurrencyComponent>(world, recipient);
    currency.amount = currency.amount + amount;
    
    event::emit(CurrencyMintedEvent {
        recipient,
        amount,
    });
}

// Transfer admin rights
public entry fun transfer_admin(admin_cap: AdminCap, new_admin: address) {
    transfer::public_transfer(admin_cap, new_admin);
}
struct EntityOwnership has store {
    owner: address,
    permissions: u64, // Bitfield for different permissions
}

// Permission constants
const PERMISSION_MODIFY: u64 = 1;
const PERMISSION_TRANSFER: u64 = 2;
const PERMISSION_DESTROY: u64 = 4;

// Check if caller can perform action on entity
fun check_entity_permission(
    world: &World,
    entity: u64,
    caller: address,
    required_permission: u64
): bool {
    if (has_component<EntityOwnership>(world, entity)) {
        let ownership = get_component<EntityOwnership>(world, entity);
        ownership.owner == caller && 
        (ownership.permissions & required_permission) != 0
    } else {
        false // No ownership = no permissions
    }
}

public entry fun modify_entity(
    world: &mut World,
    entity: u64,
    new_name: String,
    ctx: &TxContext
) {
    let caller = tx_context::sender(ctx);
    assert!(
        check_entity_permission(world, entity, caller, PERMISSION_MODIFY),
        ENotAuthorized
    );
    
    // Modify entity...
    let name_component = get_mut_component<NameComponent>(world, entity);
    name_component.value = new_name;
}

Input Validation Patterns

// Constants for validation
const MIN_DAMAGE: u64 = 1;
const MAX_DAMAGE: u64 = 1000;
const MIN_POSITION: u64 = 0;
const MAX_POSITION: u64 = 1000;

// Error codes
const EInvalidDamage: u64 = 1;
const EInvalidPosition: u64 = 2;
const EEntityNotFound: u64 = 3;
const EInsufficientHealth: u64 = 4;

public entry fun cast_spell(
    world: &mut World,
    caster: u64,
    target_x: u64,
    target_y: u64,
    spell_power: u64,
    ctx: &TxContext
) {
    // Validate inputs
    assert!(spell_power >= MIN_DAMAGE && spell_power <= MAX_DAMAGE, EInvalidDamage);
    assert!(target_x <= MAX_POSITION && target_y <= MAX_POSITION, EInvalidPosition);
    assert!(table::contains(&world.entities, caster), EEntityNotFound);
    
    // Check caster has enough mana
    let mana = get_component<ManaComponent>(world, caster);
    let spell_cost = spell_power / 10;
    assert!(mana.current >= spell_cost, EInsufficientMana);
    
    // Execute spell logic...
}

โšก Performance Optimization in Move

Gas-Efficient Data Structures

use sui::table::{Self, Table};
use sui::vec_map::{Self, VecMap};
use sui::vec_set::{Self, VecSet};

struct OptimizedWorld has key {
    id: UID,
    
    // Use Table for large, sparse collections
    entities: Table<u64, EntityData>,
    
    // Use VecMap for small, frequently accessed collections
    active_players: VecMap<address, u64>, // Usually < 100 players
    
    // Use VecSet for unique collections
    online_entities: VecSet<u64>, // Entities currently online
    
    // Use vector for sequential data
    recent_events: vector<GameEvent>, // Last 10 events
}

Batch Operations

// Process multiple entities in single transaction
public entry fun batch_heal(
    world: &mut World,
    entities: vector<u64>,
    heal_amounts: vector<u64>
) {
    let len = vector::length(&entities);
    assert!(len == vector::length(&heal_amounts), ESizeMismatch);
    assert!(len <= MAX_BATCH_SIZE, EBatchTooLarge);
    
    let i = 0;
    while (i < len) {
        let entity = *vector::borrow(&entities, i);
        let heal_amount = *vector::borrow(&heal_amounts, i);
        
        if (has_component<HealthComponent>(world, entity)) {
            let health = get_mut_component<HealthComponent>(world, entity);
            health.current = math::min(
                health.current + heal_amount,
                health.maximum
            );
        };
        
        i = i + 1;
    };
    
    // Emit single batch event instead of many individual events
    event::emit(BatchHealEvent {
        entities,
        heal_amounts,
        timestamp: tx_context::epoch(ctx),
    });
}

Lazy Evaluation Patterns

// Instead of computing expensive values immediately
struct ExpensiveStatsComponent has store, drop {
    base_stats: Stats,
    equipment_bonuses: vector<StatModifier>,
    buff_effects: vector<BuffEffect>,
    
    // Cache expensive calculations
    cached_total_stats: Option<Stats>,
    cache_dirty: bool,
}

public fun get_total_stats(stats: &mut ExpensiveStatsComponent): &Stats {
    if (stats.cache_dirty || option::is_none(&stats.cached_total_stats)) {
        // Only recalculate when needed
        let total = calculate_total_stats(stats);
        stats.cached_total_stats = option::some(total);
        stats.cache_dirty = false;
    };
    
    option::borrow(&stats.cached_total_stats)
}

public fun add_equipment_bonus(
    stats: &mut ExpensiveStatsComponent,
    modifier: StatModifier
) {
    vector::push_back(&mut stats.equipment_bonuses, modifier);
    stats.cache_dirty = true; // Mark cache as needing refresh
}

๐Ÿงช Testing Move Code in Dubhe

Unit Testing Components

#[test_only]
module game::test_components {
    use game::components::{HealthComponent, PositionComponent};
    
    #[test]
    fun test_health_component_creation() {
        let health = HealthComponent {
            current: 100,
            maximum: 100,
        };
        
        assert!(health.current == 100, 0);
        assert!(health.maximum == 100, 0);
    }
    
    #[test]
    fun test_position_component_updates() {
        let mut pos = PositionComponent {
            x: 10,
            y: 20,
        };
        
        pos.x = pos.x + 5;
        pos.y = pos.y - 3;
        
        assert!(pos.x == 15, 0);
        assert!(pos.y == 17, 0);
    }
}

Integration Testing

#[test_only]
module game::integration_tests {
    use sui::test_scenario::{Self, Scenario};
    use game::world::{Self, World};
    
    #[test]
    fun test_full_game_flow() {
        let admin = @0xADMIN;
        let player1 = @0xPLAYER1;
        let player2 = @0xPLAYER2;
        
        let mut scenario = test_scenario::begin(admin);
        
        // Initialize world
        {
            world::init(test_scenario::ctx(&mut scenario));
        };
        
        test_scenario::next_tx(&mut scenario, player1);
        {
            let mut world = test_scenario::take_shared<World>(&scenario);
            
            // Player 1 creates character
            let entity1 = world::spawn_entity(&mut world);
            world::add_component(&mut world, entity1, PlayerComponent {
                owner: player1,
                name: b"Alice",
            });
            
            test_scenario::return_shared(world);
        };
        
        test_scenario::next_tx(&mut scenario, player2);
        {
            let mut world = test_scenario::take_shared<World>(&scenario);
            
            // Player 2 creates character
            let entity2 = world::spawn_entity(&mut world);
            world::add_component(&mut world, entity2, PlayerComponent {
                owner: player2,
                name: b"Bob",
            });
            
            // Players interact...
            
            test_scenario::return_shared(world);
        };
        
        test_scenario::end(scenario);
    }
}

๐ŸŽฏ Advanced Move Patterns

Generic Programming

// Generic stat component that can hold different types
struct StatComponent<T: store + drop> has store, drop {
    base_value: T,
    modifiers: vector<T>,
}

// Specialized for different stat types
type HealthStat = StatComponent<u64>;
type SpeedStat = StatComponent<u64>;
type CriticalChanceStat = StatComponent<u16>; // Percentage (0-10000)

// Generic functions for all stat types
public fun add_modifier<T: store + drop + copy>(
    stat: &mut StatComponent<T>,
    modifier: T
) {
    vector::push_back(&mut stat.modifiers, modifier);
}

// Type-specific implementations
public fun calculate_total_u64(stat: &StatComponent<u64>): u64 {
    let total = stat.base_value;
    let i = 0;
    while (i < vector::length(&stat.modifiers)) {
        total = total + *vector::borrow(&stat.modifiers, i);
        i = i + 1;
    };
    total
}

Witness Pattern for Type Safety

// Witness types for different component categories
struct CombatComponent has drop {}
struct MovementComponent has drop {}
struct SocialComponent has drop {}

// Generic component registration with type safety
public fun register_component_type<ComponentType: store + drop, Witness: drop>(
    world: &mut World,
    _witness: Witness,
    default_value: ComponentType
) {
    let component_name = type_name::get<ComponentType>();
    
    // Store default values for component types
    bag::add(&mut world.component_defaults, component_name, default_value);
    
    // Different behavior based on witness type
    if (type_name::get<Witness>() == type_name::get<CombatComponent>()) {
        // Register with combat system
    } else if (type_name::get<Witness>() == type_name::get<MovementComponent>()) {
        // Register with movement system
    };
}

// Usage with compile-time type safety
public fun setup_game(world: &mut World) {
    register_component_type(
        world,
        CombatComponent {}, // Witness proves this is combat-related
        HealthComponent { current: 100, maximum: 100 }
    );
    
    register_component_type(
        world,
        MovementComponent {}, // Witness proves this is movement-related
        PositionComponent { x: 0, y: 0 }
    );
}

๐Ÿ“š Best Practices Summary

Security Best Practices

Use Capabilities

Implement capability-based access control for privileged operations

Validate Inputs

Always validate function parameters and state preconditions

Resource Safety

Leverage Moveโ€™s resource types to prevent duplication and loss

Event Emission

Emit events for important state changes for frontend synchronization

Performance Best Practices

Batch Operations

Process multiple entities in single transactions when possible

Optimize Data Layout

Use compact data structures and pack related data together

Lazy Evaluation

Defer expensive computations until actually needed

Cache Results

Cache expensive calculations and invalidate when data changes

๐Ÿš€ Next Steps

1

Practice with Examples

Build the Monster Hunter tutorial to see these patterns in action
2

Explore Schema Generation

Learn how schema-driven development automates Move code generation
3

Optimize Performance

Deep dive into performance optimization techniques
4

Master Testing

Follow the testing guide for comprehensive testing strategies