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

Module 0x4::property_map

PropertyMap provides generic metadata support for AptosToken. It is a specialization of SimpleMap that enforces strict typing with minimal storage use by using constant u64 to represent types and storing values in bcs format.

use 0x1::bcs;
use 0x1::error;
use 0x1::from_bcs;
use 0x1::object;
use 0x1::simple_map;
use 0x1::string;
use 0x1::type_info;
use 0x1::vector;

Resource PropertyMap

A Map for typed key to value mapping, the contract using it should keep track of what keys are what types, and parse them accordingly.

#[resource_group_member(#[group = 0x1::object::ObjectGroup])]
struct PropertyMap has drop, key
Fields
inner: simple_map::SimpleMap<string::String, property_map::PropertyValue>

Struct PropertyValue

A typed value for the PropertyMap to ensure that typing is always consistent

struct PropertyValue has drop, store
Fields
type: u8
value: vector<u8>

Struct MutatorRef

A mutator ref that allows for mutation of the property map

struct MutatorRef has drop, store
Fields
self: address

Constants

Property value does not match expected type

const ETYPE_MISMATCH: u64 = 6;

const ADDRESS: u8 = 7;

const BOOL: u8 = 0;

const BYTE_VECTOR: u8 = 8;

The property key already exists

const EKEY_ALREADY_EXISTS_IN_PROPERTY_MAP: u64 = 2;

Property key and type counts do not match

const EKEY_TYPE_COUNT_MISMATCH: u64 = 5;

Property key and value counts do not match

const EKEY_VALUE_COUNT_MISMATCH: u64 = 4;

The property map does not exist

const EPROPERTY_MAP_DOES_NOT_EXIST: u64 = 1;

The key of the property is too long

const EPROPERTY_MAP_KEY_TOO_LONG: u64 = 8;

The number of properties exceeds the maximum

const ETOO_MANY_PROPERTIES: u64 = 3;

Invalid value type specified

const ETYPE_INVALID: u64 = 7;

Maximum number of items in a PropertyMap

const MAX_PROPERTY_MAP_SIZE: u64 = 1000;

Maximum number of characters in a property name

const MAX_PROPERTY_NAME_LENGTH: u64 = 128;

const STRING: u8 = 9;

const U128: u8 = 5;

const U16: u8 = 2;

const U256: u8 = 6;

const U32: u8 = 3;

const U64: u8 = 4;

const U8: u8 = 1;

Function init

public fun init(ref: &object::ConstructorRef, container: property_map::PropertyMap)
Implementation
public fun init(ref: &ConstructorRef, container: PropertyMap) {
    let signer = ref.generate_signer();
    move_to(&signer, container);
}

Function extend

public fun extend(ref: &object::ExtendRef, container: property_map::PropertyMap)
Implementation
public fun extend(ref: &ExtendRef, container: PropertyMap) {
    let signer = ref.generate_signer_for_extending();
    move_to(&signer, container);
}

Function burn

Burns the entire property map

public fun burn(ref: property_map::MutatorRef)
Implementation
public fun burn(ref: MutatorRef) acquires PropertyMap {
    move_from<PropertyMap>(ref.self);
}

Function prepare_input

Helper for external entry functions to produce a valid container for property values.

public fun prepare_input(keys: vector<string::String>, types: vector<string::String>, values: vector<vector<u8>>): property_map::PropertyMap
Implementation
public fun prepare_input(
    keys: vector<String>,
    types: vector<String>,
    values: vector<vector<u8>>,
): PropertyMap {
    let length = keys.length();
    assert!(length <= MAX_PROPERTY_MAP_SIZE, error::invalid_argument(ETOO_MANY_PROPERTIES));
    assert!(length == values.length(), error::invalid_argument(EKEY_VALUE_COUNT_MISMATCH));
    assert!(length == types.length(), error::invalid_argument(EKEY_TYPE_COUNT_MISMATCH));

    let container = simple_map::create<String, PropertyValue>();
    while (!keys.is_empty()) {
        let key = keys.pop_back();
        assert!(
            key.length() <= MAX_PROPERTY_NAME_LENGTH,
            error::invalid_argument(EPROPERTY_MAP_KEY_TOO_LONG),
        );

        let value = values.pop_back();
        let type = types.pop_back();

        let new_type = to_internal_type(type);
        validate_type(new_type, value);

        container.add(key, PropertyValue { value, type: new_type });
    };

    PropertyMap { inner: container }
}

Function to_external_type

Maps String representation of types from their u8 representation

fun to_external_type(type: u8): string::String
Implementation
inline fun to_external_type(type: u8): String {
    if (type == BOOL) {
        string::utf8(b"bool")
    } else if (type == U8) {
        string::utf8(b"u8")
    } else if (type == U16) {
        string::utf8(b"u16")
    } else if (type == U32) {
        string::utf8(b"u32")
    } else if (type == U64) {
        string::utf8(b"u64")
    } else if (type == U128) {
        string::utf8(b"u128")
    } else if (type == U256) {
        string::utf8(b"u256")
    } else if (type == ADDRESS) {
        string::utf8(b"address")
    } else if (type == BYTE_VECTOR) {
        string::utf8(b"vector<u8>")
    } else if (type == STRING) {
        string::utf8(b"0x1::string::String")
    } else {
        abort (error::invalid_argument(ETYPE_INVALID))
    }
}

Function to_internal_type

Maps the String representation of types to u8

fun to_internal_type(type: string::String): u8
Implementation
inline fun to_internal_type(type: String): u8 {
    if (type == string::utf8(b"bool")) {
        BOOL
    } else if (type == string::utf8(b"u8")) {
        U8
    } else if (type == string::utf8(b"u16")) {
        U16
    } else if (type == string::utf8(b"u32")) {
        U32
    } else if (type == string::utf8(b"u64")) {
        U64
    } else if (type == string::utf8(b"u128")) {
        U128
    } else if (type == string::utf8(b"u256")) {
        U256
    } else if (type == string::utf8(b"address")) {
        ADDRESS
    } else if (type == string::utf8(b"vector<u8>")) {
        BYTE_VECTOR
    } else if (type == string::utf8(b"0x1::string::String")) {
        STRING
    } else {
        abort (error::invalid_argument(ETYPE_INVALID))
    }
}

Function type_info_to_internal_type

Maps Move type to u8 representation

fun type_info_to_internal_type<T>(): u8
Implementation
inline fun type_info_to_internal_type<T>(): u8 {
    let type = type_info::type_name<T>();
    to_internal_type(type)
}

Function validate_type

Validates property value type against its expected type

fun validate_type(type: u8, value: vector<u8>)
Implementation
inline fun validate_type(type: u8, value: vector<u8>) {
    if (type == BOOL) {
        from_bcs::to_bool(value);
    } else if (type == U8) {
        from_bcs::to_u8(value);
    } else if (type == U16) {
        from_bcs::to_u16(value);
    } else if (type == U32) {
        from_bcs::to_u32(value);
    } else if (type == U64) {
        from_bcs::to_u64(value);
    } else if (type == U128) {
        from_bcs::to_u128(value);
    } else if (type == U256) {
        from_bcs::to_u256(value);
    } else if (type == ADDRESS) {
        from_bcs::to_address(value);
    } else if (type == BYTE_VECTOR) {
        // nothing to validate...
    } else if (type == STRING) {
        from_bcs::to_string(value);
    } else {
        abort (error::invalid_argument(ETYPE_MISMATCH))
    };
}

Function generate_mutator_ref

public fun generate_mutator_ref(ref: &object::ConstructorRef): property_map::MutatorRef
Implementation
public fun generate_mutator_ref(ref: &ConstructorRef): MutatorRef {
    MutatorRef { self: ref.address_from_constructor_ref() }
}

Function contains_key

public fun contains_key<T: key>(object: &object::Object<T>, key: &string::String): bool
Implementation
public fun contains_key<T: key>(object: &Object<T>, key: &String): bool acquires PropertyMap {
    assert_exists(object.object_address());
    let property_map = &PropertyMap[object.object_address()];
    property_map.inner.contains_key(key)
}

Function length

public fun length<T: key>(object: &object::Object<T>): u64
Implementation
public fun length<T: key>(object: &Object<T>): u64 acquires PropertyMap {
    assert_exists(object.object_address());
    let property_map = &PropertyMap[object.object_address()];
    property_map.inner.length()
}

Function read

Read the property and get it’s external type in it’s bcs encoded format

The preferred method is to use read_<type> where the type is already known.

public fun read<T: key>(object: &object::Object<T>, key: &string::String): (string::String, vector<u8>)
Implementation
public fun read<T: key>(object: &Object<T>, key: &String): (String, vector<u8>) acquires PropertyMap {
    assert_exists(object.object_address());
    let property_map = &PropertyMap[object.object_address()];
    let property_value = property_map.inner.borrow(key);
    let new_type = to_external_type(property_value.type);
    (new_type, property_value.value)
}

Function assert_exists

fun assert_exists(object: address)
Implementation
inline fun assert_exists(object: address) {
    assert!(
        exists<PropertyMap>(object),
        error::not_found(EPROPERTY_MAP_DOES_NOT_EXIST),
    );
}

Function read_typed

Read a type and verify that the type is correct

fun read_typed<T: key, V>(object: &object::Object<T>, key: &string::String): vector<u8>
Implementation
inline fun read_typed<T: key, V>(object: &Object<T>, key: &String): vector<u8> {
    let (type, value) = read(object, key);
    assert!(
        type == type_info::type_name<V>(),
        error::invalid_argument(ETYPE_MISMATCH),
    );
    value
}

Function read_bool

public fun read_bool<T: key>(object: &object::Object<T>, key: &string::String): bool
Implementation
public fun read_bool<T: key>(object: &Object<T>, key: &String): bool acquires PropertyMap {
    let value = read_typed<T, bool>(object, key);
    from_bcs::to_bool(value)
}

Function read_u8

public fun read_u8<T: key>(object: &object::Object<T>, key: &string::String): u8
Implementation
public fun read_u8<T: key>(object: &Object<T>, key: &String): u8 acquires PropertyMap {
    let value = read_typed<T, u8>(object, key);
    from_bcs::to_u8(value)
}

Function read_u16

public fun read_u16<T: key>(object: &object::Object<T>, key: &string::String): u16
Implementation
public fun read_u16<T: key>(object: &Object<T>, key: &String): u16 acquires PropertyMap {
    let value = read_typed<T, u16>(object, key);
    from_bcs::to_u16(value)
}

Function read_u32

public fun read_u32<T: key>(object: &object::Object<T>, key: &string::String): u32
Implementation
public fun read_u32<T: key>(object: &Object<T>, key: &String): u32 acquires PropertyMap {
    let value = read_typed<T, u32>(object, key);
    from_bcs::to_u32(value)
}

Function read_u64

public fun read_u64<T: key>(object: &object::Object<T>, key: &string::String): u64
Implementation
public fun read_u64<T: key>(object: &Object<T>, key: &String): u64 acquires PropertyMap {
    let value = read_typed<T, u64>(object, key);
    from_bcs::to_u64(value)
}

Function read_u128

public fun read_u128<T: key>(object: &object::Object<T>, key: &string::String): u128
Implementation
public fun read_u128<T: key>(object: &Object<T>, key: &String): u128 acquires PropertyMap {
    let value = read_typed<T, u128>(object, key);
    from_bcs::to_u128(value)
}

Function read_u256

public fun read_u256<T: key>(object: &object::Object<T>, key: &string::String): u256
Implementation
public fun read_u256<T: key>(object: &Object<T>, key: &String): u256 acquires PropertyMap {
    let value = read_typed<T, u256>(object, key);
    from_bcs::to_u256(value)
}

Function read_address

public fun read_address<T: key>(object: &object::Object<T>, key: &string::String): address
Implementation
public fun read_address<T: key>(object: &Object<T>, key: &String): address acquires PropertyMap {
    let value = read_typed<T, address>(object, key);
    from_bcs::to_address(value)
}

Function read_bytes

public fun read_bytes<T: key>(object: &object::Object<T>, key: &string::String): vector<u8>
Implementation
public fun read_bytes<T: key>(object: &Object<T>, key: &String): vector<u8> acquires PropertyMap {
    let value = read_typed<T, vector<u8>>(object, key);
    from_bcs::to_bytes(value)
}

Function read_string

public fun read_string<T: key>(object: &object::Object<T>, key: &string::String): string::String
Implementation
public fun read_string<T: key>(object: &Object<T>, key: &String): String acquires PropertyMap {
    let value = read_typed<T, String>(object, key);
    from_bcs::to_string(value)
}

Function add

Add a property, already bcs encoded as a vector<u8>

public fun add(ref: &property_map::MutatorRef, key: string::String, type: string::String, value: vector<u8>)
Implementation
public fun add(ref: &MutatorRef, key: String, type: String, value: vector<u8>) acquires PropertyMap {
    let new_type = to_internal_type(type);
    validate_type(new_type, value);
    add_internal(ref, key, new_type, value);
}

Function add_typed

Add a property that isn’t already encoded as a vector<u8>

public fun add_typed<T: drop>(ref: &property_map::MutatorRef, key: string::String, value: T)
Implementation
public fun add_typed<T: drop>(ref: &MutatorRef, key: String, value: T) acquires PropertyMap {
    let type = type_info_to_internal_type<T>();
    add_internal(ref, key, type, bcs::to_bytes(&value));
}

Function add_internal

fun add_internal(ref: &property_map::MutatorRef, key: string::String, type: u8, value: vector<u8>)
Implementation
inline fun add_internal(ref: &MutatorRef, key: String, type: u8, value: vector<u8>) {
    assert_exists(ref.self);
    let property_map = &mut PropertyMap[ref.self];
    property_map.inner.add(key, PropertyValue { type, value });
}

Function update

Updates a property in place already bcs encoded

public fun update(ref: &property_map::MutatorRef, key: &string::String, type: string::String, value: vector<u8>)
Implementation
public fun update(ref: &MutatorRef, key: &String, type: String, value: vector<u8>) acquires PropertyMap {
    let new_type = to_internal_type(type);
    validate_type(new_type, value);
    update_internal(ref, key, new_type, value);
}

Function update_typed

Updates a property in place that is not already bcs encoded

public fun update_typed<T: drop>(ref: &property_map::MutatorRef, key: &string::String, value: T)
Implementation
public fun update_typed<T: drop>(ref: &MutatorRef, key: &String, value: T) acquires PropertyMap {
    let type = type_info_to_internal_type<T>();
    update_internal(ref, key, type, bcs::to_bytes(&value));
}

Function update_internal

fun update_internal(ref: &property_map::MutatorRef, key: &string::String, type: u8, value: vector<u8>)
Implementation
inline fun update_internal(ref: &MutatorRef, key: &String, type: u8, value: vector<u8>) {
    assert_exists(ref.self);
    let property_map = &mut PropertyMap[ref.self];
    let old_value = property_map.inner.borrow_mut(key);
    *old_value = PropertyValue { type, value };
}

Function remove

Removes a property from the map, ensuring that it does in fact exist

public fun remove(ref: &property_map::MutatorRef, key: &string::String)
Implementation
public fun remove(ref: &MutatorRef, key: &String) acquires PropertyMap {
    assert_exists(ref.self);
    let property_map = &mut PropertyMap[ref.self];
    property_map.inner.remove(key);
}

Function assert_end_to_end_input

fun assert_end_to_end_input(object: object::Object<object::ObjectCore>)
Implementation
fun assert_end_to_end_input(object: Object<ObjectCore>) acquires PropertyMap {
    assert!(read_bool(&object, &string::utf8(b"bool")), 0);
    assert!(read_u8(&object, &string::utf8(b"u8")) == 0x12, 1);
    assert!(read_u16(&object, &string::utf8(b"u16")) == 0x1234, 2);
    assert!(read_u32(&object, &string::utf8(b"u32")) == 0x12345678, 3);
    assert!(read_u64(&object, &string::utf8(b"u64")) == 0x1234567812345678, 4);
    assert!(read_u128(&object, &string::utf8(b"u128")) == 0x12345678123456781234567812345678, 5);
    assert!(
        read_u256(
            &object,
            &string::utf8(b"u256")
        ) == 0x1234567812345678123456781234567812345678123456781234567812345678,
        6
    );
    assert!(read_bytes(&object, &string::utf8(b"vector<u8>")) == vector[0x01], 7);
    assert!(read_string(&object, &string::utf8(b"0x1::string::String")) == string::utf8(b"a"), 8);

    assert!(length(&object) == 9, 9);
}