Docs

resource_management

Resource patterns and acquires

Resource management is one of Move's most distinctive and powerful features, providing built-in safety guarantees that prevent common blockchain vulnerabilities. Move's resource model ensures that digital assets cannot be copied, accidentally lost, or double-spent through compiler-enforced linear type semantics and explicit resource handling patterns.

Overview

Resources in Move are special types marked with the key ability that represent digital assets or critical state. Unlike regular data structures, resources have strict lifecycle rules: they cannot be copied or implicitly destroyed, must be explicitly moved between storage locations, and can only exist in one place at a time. This linear type system eliminates entire classes of vulnerabilities that plague other blockchain platforms.

Resource Lifecycle

Resources follow a strict lifecycle from creation to destruction:

move
module 0x1::resource_example {
    use std::signer;

    // Resource definition - key ability required for global storage
    struct Vault has key {
        balance: u64,
        owner: address
    }

    // Creating a resource
    public entry fun create_vault(account: &signer, initial_balance: u64) {
        let vault = Vault {
            balance: initial_balance,
            owner: signer::address_of(account)
        };
        // Move resource into global storage
        move_to(account, vault);
    }

    // Accessing a resource - requires acquires annotation
    public fun get_balance(addr: address): u64 acquires Vault {
        let vault_ref = borrow_global<Vault>(addr);
        vault_ref.balance
    }

    // Modifying a resource
    public entry fun deposit(account: &signer, amount: u64) acquires Vault {
        let addr = signer::address_of(account);
        let vault_ref = borrow_global_mut<Vault>(addr);
        vault_ref.balance = vault_ref.balance + amount;
    }

    // Moving resource out of storage
    public entry fun destroy_vault(account: &signer) acquires Vault {
        let addr = signer::address_of(account);
        let Vault { balance: _, owner: _ } = move_from<Vault>(addr);
        // Resource is destructured and destroyed
    }
}

Acquires Annotation

The acquires keyword is mandatory for functions that access global resources. It serves both as documentation and as a compiler check to prevent reentrancy vulnerabilities:

move
// Function that reads from one resource and writes to another
public fun transfer_between_vaults(
    from: address,
    to: address,
    amount: u64
) acquires Vault {
    let from_vault = borrow_global_mut<Vault>(from);
    from_vault.balance = from_vault.balance - amount;

    let to_vault = borrow_global_mut<Vault>(to);
    to_vault.balance = to_vault.balance + amount;
}

// Multiple resource types require listing all
public fun complex_operation(addr: address) acquires Vault, UserProfile, Settings {
    // Can access all three resource types
}

Resource Patterns

Capability Pattern

Use capability resources to control access to privileged operations:

move
struct MintCapability has key, store {
    total_minted: u64
}

public fun mint_with_cap(
    cap: &mut MintCapability,
    recipient: address,
    amount: u64
) {
    cap.total_minted = cap.total_minted + amount;
    // Mint logic here
}

Witness Pattern

Use one-time witnesses for initialization guarantees:

move
struct COIN has drop {}

public fun initialize(witness: COIN, account: &signer) {
    // Can only be called once because witness is consumed
}

Hot Potato Pattern

Create types without drop ability to force handling:

move
struct Receipt {
    amount: u64,
    must_be_consumed: bool
}

public fun create_receipt(): Receipt {
    Receipt { amount: 100, must_be_consumed: true }
}

public fun consume_receipt(receipt: Receipt) {
    let Receipt { amount: _, must_be_consumed: _ } = receipt;
}

Real-World Use Cases

  1. Token Implementations: Manage token balances as resources to guarantee conservation of supply and prevent unauthorized minting or burning through compiler-enforced linearity.

  2. NFT Ownership: Represent unique digital assets as resources that cannot be duplicated, ensuring true scarcity and provable ownership on-chain.

  3. Vault Systems: Build secure storage mechanisms where assets can only be accessed by authorized parties, leveraging resource semantics for safety.

  4. Permission Systems: Create capability resources that grant specific permissions, ensuring that access rights cannot be forged or duplicated.

  5. Escrow Services: Hold resources in escrow with guaranteed atomic transfers, where assets must either complete the full transfer or remain in the original location.

  6. Gaming Assets: Represent in-game items as resources with unique properties that cannot be duplicated or destroyed outside intended game mechanics.

Best Practices

Always Document Acquires: Explicitly list all resource types a function accesses in the acquires clause, even when it seems obvious. This helps prevent subtle bugs.

Check Resource Existence: Use exists<T>(address) before borrowing resources to handle cases where resources might not be initialized.

Avoid Cross-Module Resource Access: Design modules to manage their own resources rather than accessing resources defined in other modules when possible.

Use Immutable Borrows: Prefer borrow_global over borrow_global_mut when only reading data to reduce potential for concurrent access issues.

Structured Destruction: When moving resources out of storage, always destructure them completely to ensure all fields are properly handled.

Resource Initialization: Provide clear initialization functions and document preconditions for resource creation to prevent misuse.

Capability Distribution: Carefully control how and when capability resources are created and distributed to maintain system security.

Common Patterns

move
// Check-then-access pattern
if (exists<Vault>(addr)) {
    let vault = borrow_global<Vault>(addr);
    // Use vault
};

// Modify with validation
public entry fun safe_withdraw(account: &signer, amount: u64) acquires Vault {
    let addr = signer::address_of(account);
    assert!(exists<Vault>(addr), EVAULT_NOT_FOUND);

    let vault = borrow_global_mut<Vault>(addr);
    assert!(vault.balance >= amount, EINSUFFICIENT_BALANCE);
    vault.balance = vault.balance - amount;
}