跳转至

存储映射器

原文:https://docs.elrond.com/developers/developer-reference/storage-mappers

Rust 框架提供了您可以使用的各种存储映射器。决定在每种情况下使用哪一种对性能至关重要。在描述每个映射器后将有一个比较部分。

注意:所有存储映射器都支持额外的键参数。

通用绘图仪

单值映射器

存储单个值。示例:

fn single_value(&self) -> SingleValueMapper<Type>;
fn single_value_with_single_key_arg(&self, key_arg: Type1) -> SingleValueMapper<Type2>;
fn single_value_with_multi_key_arg(&self, key_arg1: Type1, key_arg2: Type2) -> SingleValueMapper<Type3>; 

请记住,没有办法遍历所有的key_arg,所以如果您需要这样做,请考虑使用另一个映射器。

可用方法:

搞定

fn get() -> Type 

从存储中读取值,并将其反序列化到给定的Type。对于数值类型和向量类型,这将返回空存储的默认值,即分别为 0 和空 vec。对于自定义结构,如果试图从空存储中读取,这将发出错误信号。

设定

fn set(value: &Type) 

将存储值设置为提供的value参数。对于基锈数值类型,不需要引用。

是 _ 空

fn is_empty() -> bool 

返回true是存储条目为空。通常在存储结构类型时使用,以防止在get()上崩溃。

set _ if _ empty

fn set_if_empty(value: &Type) 

仅当该值的存储当前为空时,才设置该值。通常在#init 函数中使用,以避免在合约升级时覆盖值。

明朗

fn clear() 

清除条目。

更新

fn update<R, F: FnOnce(&mut Type) -> R>(f: F) -> R 

将闭包作为参数,将该闭包应用于当前存储的值,保存新值,并返回给定闭包可能返回的任何值。示例:

增加一个值:

fn my_value(&self) -> SingleValueMapper<BigUint>;

self.my_value().update(|val| *val += 1); 

修改结构的字段:

pub struct MyStruct {
    pub field_1: u64,
    pub field_2: u32
}

fn my_value(&self) -> SingleValueMapper<MyStruct>;

self.my_value().update(|val| val.field1 = 5); 

从闭包返回值:

fn my_value(&self) -> SingleValueMapper<BigUint>;

let new_val = self.my_value().update(|val| {
    *val += 1;
    *val
}); 

raw _ byte _ length

fn raw_byte_length() -> usize 

返回存储值的原始字节长度。这个应该很少用。

向量映射器

存储相同类型的元素,每个元素都有自己的存储键。允许通过索引访问所述项目。请记住,对于 VecMapper,索引从 1 开始。示例:

fn my_vec(&self) -> VecMapper<Type>;
fn my_vec_with_args(&self, arg: Type1) -> VecMapper<Type2>; 

可用方法:

fn push(elem: &T) 

将元素存储在索引len处,之后递增len

搞定

fn get(index: usize) -> Type 

获取特定索引处的元素。有效索引是 1 到len,包括两端。试图从无效索引中读取将发出错误信号。

设定

fn set(index: usize, value: &Type) 

设置给定索引处的元素。索引必须在 1 到len的范围内。

清除 _ 条目

fn clear_entry(index: usize) 

清除给定索引处的条目。这不会减少长度。

是 _ 空

fn is_empty() -> bool 

如果映射器没有存储元素,则返回true

len

fn len() -> usize 

返回存储在映射器中的项目数。

扩展 _ 从 _ 切片

fn extend_from_slice(slice: &[Type]) 

将给定切片中的所有元素推到映射器的末尾。比push的手动for更有效,因为内部长度只被读取和更新一次。

互换 _ 移除

fn swap_remove(index: usize) 

移除index处的元素,将最后一个元素移动到index,并将len减 1。没有办法移除元素并保持顺序。

明朗

fn clear() 

从映射器中清除所有元素。这个函数可能会因为大量收集而耗尽资源。

fn iter() -> Iter<Type> 

提供所有元素的迭代器。

设置映射器

存储一组值,不允许重复。它还提供了检查集合中是否已经存在某个值的方法。

除非你需要保持元素的顺序,否则考虑使用UnorderedSetMapperWhitelistMapper来代替,因为它们更有效。

示例:

fn my_set(&self) -> SetMapper<Type>; 

可用方法:

插入

fn insert(value: Type) -> bool 

将值插入集合中。如果项目已经存在,则返回false

清除

fn remove(value: &Type) 

从集合中移除值。如果集合不包含值,则返回false

包含

fn contains(value: &Type) -> bool 

如果映射器包含给定值,则返回true

是 _ 空

fn is_empty() -> bool 

如果映射器没有存储元素,则返回true

len

fn len() -> usize 

返回存储在映射器中的项目数。

明朗

fn clear() 

从映射器中清除所有元素。这个函数可能会因为大量收集而耗尽资源。

fn iter() -> Iter<Type> 

返回所有存储元素的迭代器。

unordered set mapper

与 SetMapper 相同,但不保证项目的顺序。比SetMapper更高效,除非需要维护顺序,否则应该用。在内部,UnorderedSetMapper使用一个VecMapper来存储元素,此外,它还存储每个元素的索引以提供 O(1) contains

示例:

fn my_set(&self) -> UnorderedSetMapper<Type>; 

可用方法:

UnorderedSetMapper包含与SetMapper相同的方法,唯一的区别是项目移除。没有remove,我们只有swap_remove可用。

互换 _ 移除

fn swap_remove(value: &Type) -> bool 

使用内部VecMapper的 swap_remove 方法移除元素。此外,它用移除值的索引覆盖最后一个元素的存储索引。如果元素不在集合中,则返回false

白名单映射器

存储项目的白名单。没有提供任何迭代元素的方法,所以如果您需要迭代元素,请使用UnorderedSetMapper来代替。在内部,如果每个项目被列入白名单,这个映射器只是在存储中为它们存储一个标志。

示例:

fn my_whitelist(&self) -> WhitelistMapper<Self::Api, Type> 

可用方法:

添加

fn add(value: &Type) 

将值添加到白名单中。

清除

fn remove(value: &Type) 

从白名单中删除该值。

包含

fn contains(value: &Type) -> bool 

如果映射器包含给定值,则返回true

要求 _ 白名单

fn require_whitelisted(value: &Type) 

如果项目不在白名单中,将发出错误信号。否则什么也不做。

linked list mapper

存储一个链接列表,允许快速插入/删除元素,以及迭代整个列表的可能性。

示例:

fn my_linked_list(&self) -> LinkedListMapper<Type> 

可用方法:

是 _ 空

fn is_empty() -> bool 

如果映射器没有存储元素,则返回true

len

fn len() -> usize 

返回存储在映射器中的项目数。

明朗

fn clear() 

从映射器中清除所有元素。这个函数可能会因为大量收集而耗尽资源。

fn iter() -> Iter<Type> 

返回所有存储元素的迭代器。

ITER _ from _ node _ id

fn iter_from_node_id(node_id: u32) -> Iter<Type> 

从给定的node_id开始返回一个迭代器。在多个 SC 调用上拆分迭代时很有用。

fn front() -> Option<LinkedListNode<Type>>
fn back() -> Option<LinkedListNode<Type>> 

如果列表不为空,返回第一个/最后一个元素,否则返回None。一个LinkedListNode具有以下格式:

pub struct LinkedListNode<Type> {
    value: Type,
    node_id: u32,
    next_id: u32,
    prev_id: u32,
}

impl<Type> LinkedListNode<Type> {
    pub fn get_value_cloned(&self) -> Type {
        self.value.clone()
    }

    pub fn get_value_as_ref(&self) -> &Type {
        &self.value
    }

    pub fn into_value(self) -> Type {
        self.value
    }

    pub fn get_node_id(&self) -> u32 {
        self.node_id
    }

    pub fn get_next_node_id(&self) -> u32 {
        self.next_id
    }

    pub fn get_prev_node_id(&self) -> u32 {
        self.prev_id
    }
} 

【流行 _ 前沿/流行 _ 后沿】

fn pop_front(&mut self) -> Option<LinkedListNode<Type>>
fn pop_back(&mut self) -> Option<LinkedListNode<Type>> 

从列表中移除并返回第一个/最后一个元素。

【推 _ 后/推 _ 后

pub fn push_after(node: &mut LinkedListNode<Type>, element: Type) -> Option<LinkedListNode<Type>>
pub fn push_before(node: &mut LinkedListNode<Type>, element: Type) -> Option<LinkedListNode<Type>> 

将给定的element插入到列表中给定的node之后/之前。如果插入成功,返回新插入的节点,否则返回None

推送 _ 之后 _ 节点 _id/推送 _ 之前 _ 节点 _ id

pub fn push_after_node_id(node_id: usize, element: Type) -> Option<LinkedListNode<Type>>
pub fn push_before_node_id(node_id: usize, element: Type) -> Option<LinkedListNode<Type>> 

与上面的方法相同,但是使用 node_id 而不是完整的节点结构。

【前推/后推】

fn push_front(element: Type)
fn push_back(element: Type) 

将给定的element推到列表的前面/后面。可以看作是push_before_node_idpush_after_node_id的专门版本。

设置 _ 节点 _ 值

fn set_node_value(mut node: LinkedListNode<Type>, new_value: Type) 

如果列表中存在节点,则设置该节点的值。

设置 _ 节点 _ 值 _by_id

fn set_node_value_by_id(node_id: usize, new_value: Type) 

与上面的方法相同,但是使用 node_id 而不是完整的节点结构。

删除 _ 节点

fn remove_node(node: &LinkedListNode<Type>) 

从列表中移除节点(如果存在)。

删除 _ 节点 _by_id

fn remove_node(node_id: usize) 

与上面的方法相同,但是使用 node_id 而不是完整的节点结构。

fn iter() -> Iter<Type> 

返回所有存储元素的迭代器。

ITER _ from _ node _ id

fn iter_from_node_id(node_id: u32) -> Iter<Type> 

从给定的node_id开始返回一个迭代器。在多个 SC 调用上拆分迭代时很有用。

地图绘制者

存储(键,值)对,同时还允许对键进行迭代。这是使用起来最昂贵的映射器,所以确保你真的需要使用它。

示例:

fn my_map(&self) -> MapMapper<KeyType, ValueType> 

可用方法:

是 _ 空

fn is_empty() -> bool 

如果映射器没有存储元素,则返回true

len

fn len() -> usize 

返回存储在映射器中的项目数。

包含 _key

fn contains_key(k: &KeyType) -> bool 

如果映射器包含给定的键,则返回true

搞定

fn get(k: &KeyType) -> Option<ValueType> 

如果键存在,则返回Some(value)。如果映射中不存在关键字,则返回None

插入

fn insert(k: KeyType, v: ValueType) -> Option<V> 

将给定的键、值对插入到映射中,如果键已经存在,则返回Some(old_value)

清除

fn remove(k: &KeyType) -> Option<ValueType> 

从映射中移除键和相应的值,并返回值。如果键不在映射中,则返回None

键/值/iter

fn keys() -> Keys<KeyType>
fn values() -> Values<ValueType>
fn iter() -> Iter<KeyType, ValueType> 

分别在所有键、值和(键、值)对上提供迭代器。

专业制图师

FungibleTokenMapper

存储一个代币标识符(像一个SingleValueMapper<TokenIdentifier>)并提供将这个代币 ID 直接用于最常见的 API 函数的方法。请注意,如果之前没有发出代币,大多数方法调用都会失败。

示例:

fn my_token_id(&self) -> FungibleTokenMapper<Self::Api> 

可用方法:

发布/发布 _ 和 _ 设置 _ 所有 _ 角色

fn issue(issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option<CallbackClosure<SA>>) -> !

fn issue_and_set_all_roles(issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option<CallbackClosure<SA>>) -> ! 

颁发新的可替换代币。在我写这篇文章的时候是 0.05 EGLD (500000000000000),但是因为它在过去发生了变化,我们让它作为一个参数,以防它在将来再次发生变化。

此映射器只允许一个问题,因此尝试发出多个类型将会发出错误信号。

opt_callback是一个可选的自定义回拨,您可以使用它来发出呼叫。我们建议使用默认回调。为此,您需要在货物中导入 elrond-wasm-modules。toml:

[dependencies.elrond-wasm-modules]
version = "0.31.1" 

注:在撰写本文时,当前发布的 elrond-wasm 版本是 0.31.1,如有必要,请升级。

然后,您应该在合约中导入DefaultCallbacksModule:

#[elrond_wasm::contract]
pub trait MyContract: elrond_wasm_modules::default_issue_callbacks::DefaultIssueCallbacksModule {
    /* ... */
} 

另外,为opt_callback传递None

注意“从不”类型-> !作为该函数的返回类型。这意味着该函数将终止执行并启动 issue 异步调用,因此该调用之后的任何代码都不会被执行。

或者,如果您想发布并为 SC 设置所有角色,您可以使用issue_and_set_all_roles方法。

薄荷

fn mint(amount: BigUint) -> EsdtTokenPayment<Self::Api> 

使用ESDTLocalMint内置函数为存储的代币 ID 生成amount代币。返回一个支付结构,包含代币 ID 和给定的金额。

薄荷 _ 和 _ 发送

fn mint_and_send(to: &ManagedAddress, amount: BigUint) -> EsdtTokenPayment<SA> 

与上面的方法相同,但是也将生成的代币发送到给定的地址。

fn burn(amount: &BigUint) 

使用ESDTLocalBurn内置函数刻录amount代币。

获取 _ 平衡

fn get_balance() -> BigUint 

获取 SC 对代币的当前余额。

FungibleTokenMapper类似,但用于 NFT、SFT 和元 ESDT 代币。

发布/发布 _ 和 _ 设置 _ 所有 _ 角色

fn issue(token_type: EsdtTokenType, issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option<CallbackClosure>) -> !

fn issue_and_set_all_roles(token_type: EsdtTokenType, issue_cost: BigUint, token_display_name: ManagedBuffer, token_ticker: ManagedBuffer,initial_supply: BigUint, num_decimals: usize, opt_callback: Option<CallbackClosure>) -> ! 

与前面的 issue 函数相同,但也采用一个EsdtTokenType enum 作为参数,以决定要发布哪种类型的代币。可接受的值有EsdtTokenType::NonFungibleEsdtTokenType::SemiFungibleEsdtTokenType::Meta

NFT _ create/NFT _ create _ named

fn nft_create<T: TopEncode>(amount: BigUint, attributes: &T) -> EsdtTokenPayment<Self::Api>

fn nft_create_named<T: TopEncode>(amount: BigUint, name: &ManagedBuffer, attributes: &T) -> EsdtTokenPayment<Self::Api> 

创建一个 NFT(可以选择显示name)并返回代币 ID、创建的代币的 nonce 和支付结构中的给定金额。

NFT _ create _ and _ send/NFT _ create _ and _ send _ named

fn nft_create_and_send<T: TopEncode>(to: &ManagedAddress, amount: BigUint, attributes: &T,) -> EsdtTokenPayment<Self::Api>

fn nft_create_and_send_named<T: TopEncode>(to: &ManagedAddress, amount: BigUint, name: &ManagedBuffer, attributes: &T,) -> EsdtTokenPayment<Self::Api> 

与上面的方法相同,但是也将创建的代币发送到提供的地址。

NFT _ 添加 _ 数量

fn nft_add_quantity(token_nonce: u64, amount: BigUint) -> EsdtTokenPayment<Self::Api> 

添加给定代币随机数的数量。这只能在以前使用过nft_create函数之一并且 SC 为给定的 nonce 持有至少 1 个代币的情况下使用。

NFT _ 添加 _ 数量 _ 发送

fn nft_add_quantity_and_send(to: &ManagedAddress, token_nonce: u64, amount: BigUint) -> EsdtTokenPayment<Self::Api> 

与上面的方法相同,但是也将代币发送到提供的地址。

NFT _ burn

fn nft_burn(token_nonce: u64, amount: &BigUint) 

燃烧给定随机数的amount代币。

get _ all _ token _ data

fn get_all_token_data(token_nonce: u64) -> EsdtTokenData<Self::Api> 

获取给定 nonce 的所有代币数据。SC 必须拥有给定的 nonce,该函数才能工作。

EsdtTokenData包含以下字段:

pub struct EsdtTokenData<M: ManagedTypeApi> {
    pub token_type: EsdtTokenType,
    pub amount: BigUint<M>,
    pub frozen: bool,
    pub hash: ManagedBuffer<M>,
    pub name: ManagedBuffer<M>,
    pub attributes: ManagedBuffer<M>,
    pub creator: ManagedAddress<M>,
    pub royalties: BigUint<M>,
    pub uris: ManagedVec<M, ManagedBuffer<M>>,
} 

获取 _ 平衡

fn get_balance(token_nonce: u64) -> BigUint 

获取给定代币现时的 SC 余额。

获取 _ 代币 _ 属性

fn get_token_attributes<T: TopDecode>(token_nonce: u64) -> T 

获取给定标记 nonce 的属性。SC 必须拥有给定的 nonce,该函数才能工作。

FungibleTokenMapper 和 NonFungibleTokenMapper 的常用函数

这两种映射器的工作方式相似,所以有些函数对两者有相同的实现。

是 _ 空

fn is_empty() -> bool 

如果代币 ID 尚未设置,则返回true

get _ token _ id

fn get_token_id() -> TokenIdentifier<SA> 

获取存储的代币 ID。

set _ token _ id

fn set_token_id(token_id: &TokenIdentifier) 

手动设置此映射器的代币 ID。这个只能用一次,以后不能覆盖。如果代币是以前颁发的,这将失败,因为代币 ID 是自动设置的。

要求 _ 相同 _ 代币/要求 _ 全部 _ 相同 _ 代币

fn require_same_token(expected_token_id: &TokenIdentifier<SA>)
fn require_all_same_token(payments: &ManagedVec<EsdtTokenPayment<Self::Api>>) 

如果提供的代币 ID 参数与存储的代币不同,将发出错误信号。在#[payable]方法中很有用,当你只想要这个代币作为支付时。

设置 _ 本地 _ 角色

fn set_local_roles(roles: &[EsdtLocalRole], opt_callback: Option<CallbackClosure>) -> ! 

为代币设置提供的本地角色。默认情况下,此调用不使用回调,但是如果您愿意,可以提供自定义回调。

如果使用issue_and_set_all_roles发布,则不需要调用该函数。

与 issue 函数相同,这将在被调用时终止执行。

设置 _ 本地 _ 角色 _ 地址

fn set_local_roles_for_address(address: &ManagedAddress, roles: &[EsdtLocalRole], opt_callback: Option<CallbackClosure>) -> ! 

类似于前面的功能,但是为特定地址而不是 SC 地址设置角色。

uniqueid mapper

一个特殊的映射器,保存从 1 到 N 的值,具有以下属性:如果mapper[i] == i,则实际上不存储任何内容。

这使得映射器初始化为 O(1)而不是 O(N)。顾名思义,当您想要一个可用 id 的列表时,这非常有用。

id 和索引都是usize

注意:如果你想要一个内存版本,你可以使用框架提供的SparseArray类型。

示例:

fn my_id_mapper(&self) -> UniqueIdMapper<Self::Api> 

可用方法:

fn set_initial_len(&mut self, len: usize) 

设置初始映射器长度,即N。长度只能设置一次。

是 _ 空

fn is_empty() -> bool 

如果映射器没有存储元素,则返回true

len

fn len() -> usize 

返回存储在映射器中的项目数。

搞定

fn get(index: usize) -> usize 

获取给定索引的值。如果条目为空,那么根据映射器的属性,返回index

设定

fn set(&mut self, index: usize, id: usize) 

设置给定索引处的值。如果维护空条目,映射器的内部属性为mapper[i] == i

互换 _ 移除

fn swap_remove(index: usize) -> usize 

移除给定index处的 ID 并返回它。另外,index处的值现在被设置为映射中最后一个条目的值。长度减少 1。

fn iter() -> Iter<usize> 

为所有的 id 提供一个迭代器。

不同制图者之间的比较

单值映射器 vs 旧存储 _ 设置/存储 _ 获取对

SingleValueMapper和老派的 setter/getter 没什么区别。其实,SingleValueMapper基本上就是storage_setstorage_getstorage_is_emptystorage_clear的组合。鼓励使用SingleValueMapper,因为它更紧凑,并且没有性能损失(例如,如果您从不使用is_empty(),编译器将会删除该代码)。

单值映射器 vs 向量映射器

存储ManagedVec<T>有两种方式:

#[storage_mapper("my_vec_single)]
fn my_vec_single(&self) -> SingleValueMapper<ManagedVec<T>>

#[storage_mapper("my_vec_mapper)]
fn my_vec_mapper(&self) -> VecMapper<T>; 

这两种方法各有千秋。SingleValueMapper将所有元素连接起来并存储在一个键下,而VecMapper将每个元素存储在不同的键下。这也意味着SingleValueMapper对每个元素使用嵌套编码,而VecMapper使用顶层编码。

在以下情况下使用SingleValueMapper:

  • 每次使用时都需要读取整个数组
  • 该阵列预计长度较小

在以下情况下使用VecMapper:

  • 你只需要读取数组的一部分
  • T的顶层编码比T的嵌套编码效率高得多(例如:u64)

vecmappeal vs setmapper

SetMapper的主要用途是存储地址、代币 id 等的白名单。代币 ID 白名单可以通过以下两种方式存储:

#[storage_mapper("my_vec_whitelist)]
fn my_vec_whitelist(&self) -> VecMapper<TokenIdentifier>

#[storage_mapper("my_set_mapper)]
fn my_set_mapper(&self) -> SetMapper<TokenIdentifier>; 

这可能看起来非常相似,但是使用VecMapper的含义对潜在的天然气成本非常有害。在 O(n)中检查一个条目在VecMapper中是否存在,每次迭代都需要一次新的存储读取!最坏的情况是代币 ID 不在白名单中,整个 Vec 被读取。

SetMapper比这有效得多,因为它提供了对 O(1)中的值的检查。然而,这并不是没有代价的。这是存储如何寻找具有两个元素的SetMapper(这个片段取自 mandos 测试):

"str:tokenWhitelist.info": "u32:2|u32:1|u32:2|u32:2",
"str:tokenWhitelist.node_idEGLD-123456": "2",
"str:tokenWhitelist.node_idETH-123456": "1",
"str:tokenWhitelist.node_links|u32:1": "u32:0|u32:2",
"str:tokenWhitelist.node_links|u32:2": "u32:1|u32:0",
"str:tokenWhitelist.value|u32:2": "str:EGLD-123456",
"str:tokenWhitelist.value|u32:1": "str:ETH-123456" 

A SetMapper使用 3 * N + 1 个存储条目,其中 N 是元素的数量。检查一个元素非常容易,因为映射器唯一要做的事情就是检查提供的代币 ID 的node_id条目。

即便如此,对于这种特殊情况,SetMapperVecMapper好得多。

vecmapter vs linked list mapper

LinkedListMapper可以看作是对VecMapper的特殊化。它只允许在列表的任意一端插入/删除,称为推入/弹出。它还具有存储效率,因为它只需要 2 * N + 1 个存储条目。这种映射器的存储如下所示:

"str:list_mapper.node_links|u32:1": "u32:0|u32:2",
"str:list_mapper.node_links|u32:2": "u32:1|u32:0",
"str:list_mapper.value|u32:1": "123",
"str:list_mapper.value|u32:2": "111",
"str:list_mapper.info": "u32:2|u32:1|u32:2|u32:2" 

这是很少使用的映射器之一,因为它的用途非常具体,但是如果您需要存储队列,它非常有用。

SingleValueMapper vs MapMapper

信不信由你,很多时候,MapMapper甚至都不需要,可以简单的用一个SingleValueMapper代替。例如,假设您想为每个地址存储一个 ID。使用MapMapper可能很有诱惑力,它看起来像这样:

#[storage_mapper("address_id_mapper")]
fn address_id_mapper(&self) -> MapMapper<ManagedAddress, u64>; 

这可以替换为以下SingleValueMapper:

#[storage_mapper("address_id_mapper")]
fn address_id_mapper(&self, address: &ManagedAddress) -> SingleValueMapper<u64>; 

它们都提供(几乎)相同的功能。不同之处在于,SingleValueMapper没有提供遍历所有键的方法,也就是说,本例中的地址,但是它的效率也提高了 4-5 倍。

除非您需要迭代所有条目,否则应该避免使用MapMapper,因为这是最昂贵的映射器。它使用 4 * N + 1 个存储条目。MapMapper的存储看起来像这样:

"str:map_mapper.node_links|u32:1": "u32:0|u32:2",
"str:map_mapper.node_links|u32:2": "u32:1|u32:0",
"str:map_mapper.value|u32:1": "123",
"str:map_mapper.value|u32:2": "111",
"str:map_mapper.node_id|u32:123": "1",
"str:map_mapper.node_id|u32:111": "2",
"str:map_mapper.mapped|u32:123": "456",
"str:map_mapper.mapped|u32:111": "222",
"str:map_mapper.info": "u32:2|u32:1|u32:2|u32:2" 

请记住,所有的映射器都可以有尽可能多的主关键字的附加参数。例如,您可以为每个用户对设置一个VecMapper,如下所示:

#[storage_mapper("list_per_user_pair")]
fn list_per_user_pair(&self, first_addr: &ManagedAddress, second_addr: &ManagedAddress) -> VecMapper<T>; 

根据您的情况使用正确的映射器可以大大降低气体成本和复杂性,因此请始终记得仔细评估您的使用案例。



回到顶部