Smart Tables

Summary

  • Smart Table is a data structure in Move that stores data in multiple buckets for efficient access and gas optimization.
  • It operates similarly to Smart Vector, improving speed and cost-efficiency in data management.
  • The module demonstrates how to initialize, update, and retrieve data from a SmartTable.
  • The code includes test functions to verify the correct operation of SmartTable operations.
  • SmartTable uses address as keys and u64 as values in this example, suitable for tracking user points or balances.

Overview

Similar to Smart Vector, which we explored in the previous article, Smart Table operates on the same principle. Smart Table's data is divided into multiple buckets for storage. Accessing, writing, and reading data in Smart Table occurs independently within each bucket containing that data. This organization improves speed and cost-efficiency while optimizing gas usage for users.

Example

module movement::smart_table_module {
    use aptos_std::smart_table::{Self, SmartTable};

    struct MovementTableObject has key {
        value: SmartTable<address, u64>
    }

    fun init_module(caller: &signer) {
        let val = smart_table::new<address, u64>();
        smart_table::add(&mut val, address_of(caller), 0);
        move_to(caller, MovementTableObject {
            value: val
        });
    }
}

Let's break down the code and explain each function step-by-step:

1. Module Declaration

module movement::smart_table_module {
    // Module contents
}

This declares a new module named "smart_table_module" under the "movement" address.

2. Importing Required Modules

use aptos_std::smart_table::{Self, SmartTable};

This imports the SmartTable type and its associated functions from the aptos_std library.

3. Defining a Custom Struct

struct MovementTableObject has key {
    value: SmartTable<address, u64>
}

This defines a new struct called MovementTableObject that contains a SmartTable. The SmartTable uses address as keys and u64 as values.

4. Initialization Function

fun init_module(caller: &signer) {
    // Function body
}

This function is called when the module is published. It takes a reference to the signer (the account publishing the module) as an argument.

5. Creating a New SmartTable

let val = smart_table::new<address, u64>();

This creates a new SmartTable that uses address as keys and u64 as values.

6. Adding an Initial Entry

smart_table::add(&mut val, address_of(caller), 0);

This adds an initial entry to the SmartTable. The key is the address of the caller, and the value is 0.

7. Moving the SmartTable to Storage

move_to(caller, MovementTableObject {
    value: val
});

This creates a new MovementTableObject with the SmartTable we just created and moves it to the storage of the caller's account.

This initialization sets up a SmartTable in the caller's account, ready to be used for storing and managing data efficiently.

Full Example

module movement::smart_table_module {
    use aptos_std::smart_table::{Self, SmartTable};
    use std::debug::print;
    use std::signer::address_of;

    struct MovementTableObject has key {
        value: SmartTable<address, u64>
    }

    fun init_module(caller: &signer) {
        let val = smart_table::new<address, u64>();
        smart_table::add(&mut val, address_of(caller), 0);
        move_to(caller, MovementTableObject {
            value: val
        });
    }

    #[test_only]
    fun test_init_module(caller: &signer) {
        init_module(caller);
    }

    #[view]
    fun get_amount_point(addr: address): u64 acquires MovementTableObject {
        let table = &borrow_global<MovementTableObject>(addr).value;
        *smart_table::borrow(table, addr)
    }

    fun plus_point(addr: address, value: u64) acquires MovementTableObject {
        let table = &mut borrow_global_mut<MovementTableObject>(addr).value;
        let point = *smart_table::borrow_mut(table, addr);
        point = point + value;
        smart_table::upsert(table, addr, point);
    }

    #[test(caller = @0x1)]
    fun test_get_amount_point(caller: &signer) acquires MovementTableObject {
        test_init_module(caller);
        let amount = get_amount_point(address_of(caller));
        print(&amount);
    }

    #[test(caller = @0x1)]
    fun test_plus_amount_point(caller: &signer) acquires MovementTableObject {
        test_init_module(caller);
        plus_point(address_of(caller), 10);
        let amount = get_amount_point(address_of(caller));
        print(&amount);
    }
}

1. init_module(caller: &signer)

This function initializes the module when it's published:

  • Create a new SmartTable using smart_table::new&lt;address, u64&gt;()
  • Add an initial entry to the table with the caller's address as the key and 0 as the value
  • Create a new MovementTableObject with the SmartTable and move it to the caller's storage

2. test_init_module(caller: &signer)

This is a test-only function that calls init_module:

  • It's annotated with #[test_only], meaning it's only used for testing
  • It simply calls the init_module function with the provided caller

3. get_amount_point(addr: address): u64

This function retrieves the point amount for a given address:

  • It's annotated with #[view], indicating it's a read-only function
  • Borrow the SmartTable from the MovementTableObject stored at the given address
  • Use smart_table::borrow to get the value associated with the address
  • Return the borrowed value (point amount)

4. plus_point(addr: address, value: u64)

This function adds points to a given address:

  • Borrow the SmartTable mutably from the MovementTableObject
  • Get the current point value for the address using smart_table::borrow_mut
  • Add the new value to the current point
  • Update the SmartTable with the new point value using smart_table::upsert

5. test_get_amount_point(caller: &signer)

This is a test function for get_amount_point:

  • It's annotated with #[test(caller = @0x1)], setting up a test environment
  • Call test_init_module to set up the initial state
  • Call get_amount_point with the caller's address
  • Print the retrieved amount

6. test_plus_amount_point(caller: &signer)

This is a test function for plus_point:

  • It's also annotated with #[test(caller = @0x1)]
  • Call test_init_module to set up the initial state
  • Call plus_point to add 10 points to the caller's address
  • Call get_amount_point to retrieve the updated point amount
  • Print the new amount

These functions demonstrate how to initialize, update, and retrieve data from a SmartTable, as well as how to set up tests for these operations.

Running Test

Running test:

movement move test -f smart_table_module

Result:

Running Move unit tests
[debug] 0
[ PASS    ] 0x696e90758094efbf0e2e9dc7fb9fbbde6c60d479bed1b1984cf62575fc864d96::smart_table_module::test_get_amount_point
[debug] 10
[ PASS    ] 0x696e90758094efbf0e2e9dc7fb9fbbde6c60d479bed1b1984cf62575fc864d96::smart_table_module::test_plus_amount_point
Test result: OK. Total tests: 2; passed: 2; failed: 0
{
  "Result": "Success"
}

Additional SimpleMap Functions

| Function | Parameters | Description | Return Value | | --- | --- | --- | --- | | new | None | Creates an empty SmartTable with default configurations | SmartTable<K, V> | | new_with_config | num_initial_buckets: u64, split_load_threshold: u8, target_bucket_size: u64 | Creates an empty SmartTable with customized configurations | SmartTable<K, V> | | destroy_empty | self: SmartTable<K, V> | Destroys an empty table | None | | destroy | self: SmartTable<K, V> | Destroys a table completely when V has drop | None | | clear | self: &mut SmartTable<K, V> | Clears a table completely when T has drop | None | | add | self: &mut SmartTable<K, V>, key: K, value: V | Adds a key-value pair to the table | None | | add_all | self: &mut SmartTable<K, V>, keys: vector<K>, values: vector<V> | Adds multiple key-value pairs to the table | None | | unzip_entries | entries: &vector<Entry<K, V>> | Unzips entries into separate key and value vectors | (vector<K>, vector<V>) | | to_simple_map | self: &SmartTable<K, V> | Converts a smart table to a simple_map | SimpleMap<K, V> | | keys | self: &SmartTable<K, V> | Gets all keys in a smart table | vector<K> | | keys_paginated | self: &SmartTable<K, V>, starting_bucket_index: u64, starting_vector_index: u64, num_keys_to_get: u64 | Gets keys from a smart table, paginated | (vector<K>, Option<u64>, Option<u64>) | | split_one_bucket | self: &mut SmartTable<K, V> | Splits one bucket into two | None | | bucket_index | level: u8, num_buckets: u64, hash: u64 | Returns the expected bucket index for a hash | u64 | | borrow | self: &SmartTable<K, V>, key: K | Borrows an immutable reference to the value associated with the key | &V | | borrow_with_default | self: &SmartTable<K, V>, key: K, default: &V | Borrows an immutable reference to the value, or returns the default if key not found | &V | | borrow_mut | self: &mut SmartTable<K, V>, key: K | Borrows a mutable reference to the value associated with the key | &mut V | | borrow_mut_with_default | self: &mut SmartTable<K, V>, key: K, default: V | Borrows a mutable reference to the value, or inserts and returns default if key not found | &mut V | | contains | self: &SmartTable<K, V>, key: K | Checks if the table contains a key | bool | | remove | self: &mut SmartTable<K, V>, key: K | Removes and returns the value associated with the key | V | | upsert | self: &mut SmartTable<K, V>, key: K, value: V | Inserts a key-value pair or updates an existing one | None | | length | self: &SmartTable<K, V> | Returns the number of entries in the table | u64 | | load_factor | self: &SmartTable<K, V> | Returns the load factor of the hashtable | u64 | | update_split_load_threshold | self: &mut SmartTable<K, V>, split_load_threshold: u8 | Updates the split load threshold | None | | update_target_bucket_size | self: &mut SmartTable<K, V>, target_bucket_size: u64 | Updates the target bucket size | None | | for_each_ref | self: &SmartTable<K, V>, f: \|&K, &V\| | Applies a function to a reference of each key-value pair | None | | for_each_mut | self: &mut SmartTable<K, V>, f: \|&K, &mut V\| | Applies a function to a mutable reference of each key-value pair | None | | map_ref | self: &SmartTable<K, V1>, f: \|&V1\|V2 | Maps a function over the values, producing a new SmartTable | SmartTable<K, V2> | | any | self: &SmartTable<K, V>, p: \|&K, &V\|bool | Checks if any key-value pair satisfies the predicate | bool | | borrow_kv | self: &Entry<K, V> | Borrows references to the key and value of an entry | (&K, &V) | | borrow_kv_mut | self: &mut Entry<K, V> | Borrows mutable references to the key and value of an entry | (&mut K, &mut V) | | num_buckets | self: &SmartTable<K, V> | Returns the number of buckets in the table | u64 | | borrow_buckets | self: &SmartTable<K, V> | Borrows a reference to the buckets of the table | &TableWithLength<u64, vector<Entry<K, V>>> | | borrow_buckets_mut | self: &mut SmartTable<K, V> | Borrows a mutable reference to the buckets of the table | &mut TableWithLength<u64, vector<Entry<K, V>>> |