The Rust framework provides various storage mappers you can use. Deciding which one to use for every situation is critical for performance. There will be a comparison section after each mapper is described.
Note: All the storage mappers support additional key arguments.
General purpose mappers
SingleValueMapper
Stores a single value. Examples:
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>;
Keep in mind there is no way of iterating over all key_arg
s, so if you need to do that, consider using another mapper.
Available methods:
get
fn get() -> Type
Reads the value from storage and deserializes it to the given Type
. For numerical types and vector types, this will return the default value for empty storage, i.e. 0 and empty vec respectively. For custom structs, this will signal an error if trying to read from empty storage.
set
fn set(value: &Type)
Sets the stored value to the provided value
argument. For base Rust numerical types, the reference is not needed.
is_empty
fn is_empty() -> bool
Returns true
is the storage entry is empty. Usually used when storing struct types to prevent crashes on get()
.
set_if_empty
fn set_if_empty(value: &Type)
Sets the value only if the storage for that value is currently empty. Usually used in #init functions to not overwrite values on contract upgrade.
clear
fn clear()
Clears the entry.
update
fn update<R, F: FnOnce(&mut Type) -> R>(f: F) -> R
Takes a closure as argument, applies that closure to the currently stored value, saves the new value, and returns any value the given closure might return. Examples:
Incrementing a value:
fn my_value(&self) -> SingleValueMapper<BigUint>;
self.my_value().update(|val| *val += 1);
Modifying a struct's field:
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);
Returning a value from the closure:
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
Returns the raw byte length of the stored value. This should be rarely used.
VecMapper
Stores elements of the same type, each under their own storage key. Allows access by index for said items. Keep in mind indexes start at 1 for VecMapper. Examples:
fn my_vec(&self) -> VecMapper<Type>;
fn my_vec_with_args(&self, arg: Type1) -> VecMapper<Type2>;
Available methods:
push
fn push(elem: &T)
Stores the element at index len
and increments len
afterwards.
get
fn get(index: usize) -> Type
Gets the element at the specific index. Valid indexes are 1 to len
, both ends included. Attempting to read from an invalid index will signal an error.
set
fn set(index: usize, value: &Type)
Sets the element at the given index. Index must be in inclusive range 1 to len
.
clear_entry
fn clear_entry(index: usize)
Clears the entry at the given index. This does not decrease the length.
is_empty
fn is_empty() -> bool
Returns true
if the mapper has no elements stored.
len
fn len() -> usize
Returns the number of items stored in the mapper.
extend_from_slice
fn extend_from_slice(slice: &[Type])
Pushes all elements from the given slice at the end of the mapper. More efficient than manual for
of push
, as the internal length is only read and updated once.
swap_remove
fn swap_remove(index: usize)
Removes the element at index
, moves the last element to index
and decreases the len
by 1. There is no way of removing an element and preserving the order.
clear
fn clear()
Clears all the elements from the mapper. This function can run out of gas for big collections.
iter
fn iter() -> Iter<Type>
Provides an iterator over all the elements.
SetMapper
Stores a set of values, with no duplicates being allowed. It also provides methods for checking if a value already exists in the set. Values order is given by their order of insertion.
Unless you need to maintain the order of the elements, consider using UnorderedSetMapper
or WhitelistMapper
instead, as they're more efficient.
Examples:
fn my_set(&self) -> SetMapper<Type>;
Available methods:
insert
fn insert(value: Type) -> bool
Insers the value into the set. Returns false
if the item was already present.
remove
fn remove(value: &Type)
Removes the value from the set. Returns false
if the set did not contain the value.
contains
fn contains(value: &Type) -> bool
Returns true
if the mapper contains the given value.
is_empty
fn is_empty() -> bool
Returns true
if the mapper has no elements stored.
len
fn len() -> usize
Returns the number of items stored in the mapper.
clear
fn clear()
Clears all the elements from the mapper. This function can run out of gas for big collections.
iter
fn iter() -> Iter<Type>
Returns an iterator over all the stored elements.
UnorderedSetMapper
Same as SetMapper, but does not guarantee the order of the items. More efficient than SetMapper
, and should be used instead unless you need to maintain the order. Internally, UnorderedSetMapper
uses a VecMapper
to store the elements, and additionally, it stores each element's index to provide O(1) contains
.
Examples:
fn my_set(&self) -> UnorderedSetMapper<Type>;
Available methods:
UnorderedSetMapper
contains the same methods as SetMapper
, the only difference being item removal. Instead of remove
, we only have swap_remove
available.
swap_remove
fn swap_remove(value: &Type) -> bool
Uses the internal VecMapper
's swap_remove method to remove the element. Additionally, it overwrites the last element's stored index with the removed value's index. Returns false
if the element was not present in the set.
WhitelistMapper
Stores a whitelist of items. Does not provide any means of iterating over the elements, so if you need to iterate over the elements, use UnorderedSetMapper
instead. Internally, this mapper simply stores a flag in storage for each item if they're whitelisted.
Examples:
fn my_whitelist(&self) -> WhitelistMapper<Type>
Available methods:
add
fn add(value: &Type)
Adds the value to the whitelist.
remove
fn remove(value: &Type)
Removes the value from the whitelist.
contains
fn contains(value: &Type) -> bool
Returns true
if the mapper contains the given value.
require_whitelisted
fn require_whitelisted(value: &Type)
Will signal an error if the item is not whitelisted. Does nothing otherwise.
LinkedListMapper
Stores a linked list, which allows fast insertion/removal of elements, as well as possibility to iterate over the whole list.
Examples:
fn my_linked_list(&self) -> LinkedListMapper<Type>
Available methods:
is_empty
fn is_empty() -> bool
Returns true
if the mapper has no elements stored.
len
fn len() -> usize
Returns the number of items stored in the mapper.
clear
fn clear()
Clears all the elements from the mapper. This function can run out of gas for big collections.
iter
fn iter() -> Iter<Type>
Returns an iterator over all the stored elements.
iter_from_node_id
fn iter_from_node_id(node_id: u32) -> Iter<Type>
Returns an iterator starting from the given node_id
. Useful when splitting iteration over multiple SC calls.
front
fn front() -> Option<LinkedListNode<Type>>
fn back() -> Option<LinkedListNode<Type>>
Returns the first/last element if the list is not empty, None
otherwise. A LinkedListNode
has the following format:
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
}
}
pop_front/pop_back
fn pop_front(&mut self) -> Option<LinkedListNode<Type>>
fn pop_back(&mut self) -> Option<LinkedListNode<Type>>
Removes and returns the first/last element from the list.
push_after/push_before
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>>
Inserts the given element
into the list after/before the given node
. Returns the newly inserted node if the insertion was successful, None
otherwise.
push_after_node_id/push_before_node_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>>
Same as the methods above, but uses node_id instead of a full node struct.
push_front/push_back
fn push_front(element: Type)
fn push_back(element: Type)
Pushes the given element
at the front/back of the list. Can be seen as specialized versions of push_before_node_id
and push_after_node_id
.
set_node_value
fn set_node_value(mut node: LinkedListNode<Type>, new_value: Type)
Sets a node's value, if the node exists in the list.
set_node_value_by_id
fn set_node_value_by_id(node_id: usize, new_value: Type)
Same as the method above, but uses node_id instead of a full node struct.
remove_node
fn remove_node(node: &LinkedListNode<Type>)
Removes the node from the list, if it exists.
remove_node_by_id
fn remove_node(node_id: usize)
Same as the method above, but uses node_id instead of a full node struct.
iter
fn iter() -> Iter<Type>
Returns an iterator over all the stored elements.
iter_from_node_id
fn iter_from_node_id(node_id: u32) -> Iter<Type>
Returns an iterator starting from the given node_id
. Useful when splitting iteration over multiple SC calls.
MapMapper
Stores (key, value) pairs, while also allowing iteration over keys. This is the most expensive mapper to use, so make sure you really need to use it. Keys order is given by their order of insertion (same as SetMapper
).
Examples:
fn my_map(&self) -> MapMapper<KeyType, ValueType>
Available methods:
is_empty
fn is_empty() -> bool
Returns true
if the mapper has no elements stored.
len
fn len() -> usize
Returns the number of items stored in the mapper.
contains_key
fn contains_key(k: &KeyType) -> bool
Returns true
if the mapper contains the given key.
get
fn get(k: &KeyType) -> Option<ValueType>
Returns Some(value)
if the key exists. Returns None
if the key does not exist in the map.
insert
fn insert(k: KeyType, v: ValueType) -> Option<V>
Inserts the given key, value pair into the map, and returns Some(old_value)
if the key was already present.
remove
fn remove(k: &KeyType) -> Option<ValueType>
Removes the key and the corresponding value from the map, and returns the value. If the key was not present in the map, None
is returned.
keys/values/iter
fn keys() -> Keys<KeyType>
fn values() -> Values<ValueType>
fn iter() -> Iter<KeyType, ValueType>
Provides an iterator over all keys, values, and (key, value) pairs respectively.
Specialized mappers
FungibleTokenMapper
Stores a token identifier (like a SingleValueMapper<TokenIdentifier>
) and provides methods for using this token ID directly with the most common API functions. Note that most method calls will fail if the token was not issued previously.
Examples:
fn my_token_id(&self) -> FungibleTokenMapper
Available methods:
issue/issue_and_set_all_roles
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>>) -> !
Issues a new fungible token. issue_cost
is 0.05 EGLD (5000000000000000) at the time of writing this, but since this changed in the past, we've let it as an argument it case it changes again in the future.
This mapper allows only one issue, so trying to issue multiple types will signal an error.
opt_callback
is an optional custom callback you can use for your issue call. We recommend using the default callback. To do so, you need to import multiversx-sc-modules in your Cargo.toml:
[dependencies.multiversx-sc-modules]
version = "0.39.0"
Note: current released multiversx-sc version at the time of writing this was 0.39.0, upgrade if necessary.
Then you should import the DefaultCallbacksModule
in your contract:
#[multiversx_sc::contract]
pub trait MyContract: multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule {
/* ... */
}
Additionally, pass None
for opt_callback
.
Note the "never" type -> !
as return type for this function. This means this function will terminate the execution and launch the issue async call, so any code after this call will not be executed.
Alternatively, if you want to issue and also have all roles set for the SC, you can use the issue_and_set_all_roles
method instead.
mint
fn mint(amount: BigUint) -> EsdtTokenPayment
Mints amount
tokens for the stored token ID, using the ESDTLocalMint
built-in function. Returns a payment struct, containing the token ID and the given amount.
mint_and_send
fn mint_and_send(to: &ManagedAddress, amount: BigUint) -> EsdtTokenPayment<SA>
Same as the method above, but also sends the minted tokens to the given address.
burn
fn burn(amount: &BigUint)
Burns amount
tokens, using the ESDTLocalBurn
built-in function.
get_balance
fn get_balance() -> BigUint
Gets the current balance the SC has for the token.
NonFungibleTokenMapper
Similar to the FungibleTokenMapper
, but is used for NFT, SFT and META-ESDT tokens.
issue/issue_and_set_all_roles
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>) -> !
Same as the previous issue function, but also takes an EsdtTokenType
enum as argument, to decide which type of token to issue. Accepted values are EsdtTokenType::NonFungible
, EsdtTokenType::SemiFungible
and EsdtTokenType::Meta
.
nft_create/nft_create_named
fn nft_create<T: TopEncode>(amount: BigUint, attributes: &T) -> EsdtTokenPayment
fn nft_create_named<T: TopEncode>(amount: BigUint, name: &ManagedBuffer, attributes: &T) -> EsdtTokenPayment
Creates an NFT (optionally with a display name
) and returns the token ID, the created token's nonce, and the given amount in a payment struct.
nft_create_and_send/nft_create_and_send_named
fn nft_create_and_send<T: TopEncode>(to: &ManagedAddress, amount: BigUint, attributes: &T,) -> EsdtTokenPayment
fn nft_create_and_send_named<T: TopEncode>(to: &ManagedAddress, amount: BigUint, name: &ManagedBuffer, attributes: &T,) -> EsdtTokenPayment
Same as the methods above, but also sends the created token to the provided address.
nft_add_quantity
fn nft_add_quantity(token_nonce: u64, amount: BigUint) -> EsdtTokenPayment
Adds quantity for the given token nonce. This can only be used if one of the nft_create
functions was used before AND the SC holds at least 1 token for the given nonce.
nft_add_quantity_and_send
fn nft_add_quantity_and_send(to: &ManagedAddress, token_nonce: u64, amount: BigUint) -> EsdtTokenPayment
Same as the method above, but also sends the tokens to the provided address.
nft_burn
fn nft_burn(token_nonce: u64, amount: &BigUint)
Burns amount
tokens for the given nonce.
get_all_token_data
fn get_all_token_data(token_nonce: u64) -> EsdtTokenData<Self::Api>
Gets all the token data for the given nonce. The SC must own the given nonce for this function to work.
EsdtTokenData
contains the following fields:
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>>,
}
get_balance
fn get_balance(token_nonce: u64) -> BigUint
Gets the SC's balance for the given token nonce.
get_token_attributes
fn get_token_attributes<T: TopDecode>(token_nonce: u64) -> T
Gets the attributes for the given token nonce. The SC must own the given nonce for this function to work.
Common functions for FungibleTokenMapper and NonFungibleTokenMapper
Both mappers work similarly, so some functions have the same implementation for both.
is_empty
fn is_empty() -> bool
Returns true
if the token ID is not set yet.
get_token_id
fn get_token_id() -> TokenIdentifier<SA>
Gets the stored token ID.
set_token_id
fn set_token_id(token_id: &TokenIdentifier)
Manually sets the token ID for this mapper. This can only be used once, and can not be overwritten afterwards. This will fail if the token was issue previously, as the token ID was automatically set.
require_same_token/require_all_same_token
fn require_same_token(expected_token_id: &TokenIdentifier)
fn require_all_same_token(payments: &ManagedVec<EsdtTokenPayment>)
Will signal an error if the provided token ID argument(s) differs from the stored token. Useful in #[payable]
methods when you only want to this token as payment.
set_local_roles
fn set_local_roles(roles: &[EsdtLocalRole], opt_callback: Option<CallbackClosure>) -> !
Sets the provided local roles for the token. By default, no callback is used for this call, but you may provide a custom callback if you want to.
You don't need to call this function if you use issue_and_set_all_roles
for issuing.
Same as the issue function, this will terminate execution when called.
set_local_roles_for_address
fn set_local_roles_for_address(address: &ManagedAddress, roles: &[EsdtLocalRole], opt_callback: Option<CallbackClosure>) -> !
Similar to the previous function, but sets the roles for a specific address instead of the SC address.
UniqueIdMapper
A special mapper that holds the values from 1 to N, with the following property: if mapper[i] == i
, then nothing is actually stored.
This makes it so the mapper initialization is O(1) instead of O(N). Very useful when you want to have a list of available IDs, as its name suggests.
Both the IDs and the indexes are usize
.
Note: If you want an in-memory version of this, you can use the SparseArray
type provided by the framework.
Examples:
fn my_id_mapper(&self) -> UniqueIdMapper<Self::Api>
Available methods:
set_initial_len
fn set_initial_len(&mut self, len: usize)
Sets the initial mapper length, i.e. the N
. The length may only be set once.
is_empty
fn is_empty() -> bool
Returns true
if the mapper has no elements stored.
len
fn len() -> usize
Returns the number of items stored in the mapper.
get
fn get(index: usize) -> usize
Gets the value for the given index. If the entry is empty, then index
is returned, as per the mapper's property.
set
fn set(&mut self, index: usize, id: usize)
Sets the value at the given index. The mapper's internal property of mapper[i] == i
if empty entry is maintained.
swap_remove
fn swap_remove(index: usize) -> usize
Removes the ID at the given index
and returns it. Also, the value at index
is now set the value of the last entry in the map. Length is decreased by 1.
iter
fn iter() -> Iter<usize>
Provides an iterator over all the IDs.
Comparisons between the different mappers
SingleValueMapper vs old storage_set/storage_get pairs
There is no difference between SingleValueMapper
and the old-school setters/getters. In fact, SingleValueMapper
is basically a combination between storage_set
, storage_get
, storage_is_empty
and storage_clear
. Use of SingleValueMapper
is encouraged, as it's a lot more compact, and has no performance penalty (if, for example, you never use is_empty()
, that code will be removed by the compiler).
SingleValueMapper vs VecMapper
Storing a ManagedVec<T>
can be done in two ways:
#[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>;
Both of those approaches have their merits. The SingleValueMapper
concatenates all elements and stores them under a single key, while the VecMapper
stores each element under a different key. This also means that SingleValueMapper
uses nested-encoding for each element, while VecMapper
uses top-encoding.
Use SingleValueMapper
when:
- you need to read the whole array on every use
- the array is expected to be of small length
Use VecMapper
when:
- you only require reading a part of the array
T
's top-encoding is vastly more efficient thanT
's nested-encoding (for example:u64
)
VecMapper vs SetMapper
The primary use for SetMapper
is storing a whitelist of addresses, token ids, etc. A token ID whitelist can be stored in these two ways:
#[storage_mapper("my_vec_whitelist")]
fn my_vec_whitelist(&self) -> VecMapper<TokenIdentifier>
#[storage_mapper("my_set_mapper")]
fn my_set_mapper(&self) -> SetMapper<TokenIdentifier>;
This might look very similar, but the implications of using VecMapper
for this are very damaging to the potential gas costs. Checking for an item's existence in VecMapper
is done in O(n), with each iteration requiring a new storage read! Worst case scenario is the Token ID is not in the whitelist and the whole Vec is read.
SetMapper
is vastly more efficient than this, as it provides checking for a value in O(1). However, this does not come without a cost. This is how the storage looks for a SetMapper
with two elements (this snippet is taken from a scenario test):
"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
uses 3 * N + 1 storage entries, where N is the number of elements. Checking for an element is very easy, as the only thing the mapper has to do is check the node_id
entry for the provided token ID.
Even so, for this particular case, SetMapper
is way better than VecMapper
.
VecMapper vs LinkedListMapper
LinkedListMapper
can be seen as a specialization for the VecMapper
. It allows insertion/removal only at either end of the list, known as pushing/popping. It's also storage-efficient, as it only requires 2 * N + 1 storage entries. The storage for such a mapper looks like this:
"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"
This is one of the lesser used mappers, as its purpose is very specific, but it's very useful if you ever need to store a queue.
SingleValueMapper vs MapMapper
Believe it or not, most of the time, MapMapper
is not even needed, and can simply be replaced by a SingleValueMapper
. For example, let's say you want to store an ID for every Address. It might be tempting to use MapMapper
, which would look like this:
#[storage_mapper("address_id_mapper")]
fn address_id_mapper(&self) -> MapMapper<ManagedAddress, u64>;
This can be replaced with the following SingleValueMapper
:
#[storage_mapper("address_id_mapper")]
fn address_id_mapper(&self, address: &ManagedAddress) -> SingleValueMapper<u64>;
Both of them provide (almost) the same functionality. The difference is that the SingleValueMapper
does not provide a way to iterate over all the keys, i.e. Addresses in this case, but it's also 4-5 times more efficient.
Unless you need to iterate over all the entries, MapMapper
should be avoided, as this is the most expensive mapper. It uses 4 * N + 1 storage entries. The storage for a MapMapper
looks like this:
"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"
Keep in mind that all the mappers can have as many additional arguments for the main key. For example, you can have a VecMapper
for every user pair, like this:
#[storage_mapper("list_per_user_pair")]
fn list_per_user_pair(&self, first_addr: &ManagedAddress, second_addr: &ManagedAddress) -> VecMapper<T>;
Using the correct mapper for your situation can greatly decrease gas costs and complexity, so always remember to carefully evaluate your use-case.
Accessing a value at an address
Because of the way the storage mappers are structured, it is very easy to access a "remote" value, meaning a value stored under a key at a different address than the current one.
#[storage_mapper("key_example")]
fn content(&self) -> MapMapper<u32, u32>;
#[storage_mapper_from_address("key_example")]
fn content_from_address(&self, address: ManagedAddress, ...) -> SetMapper<u32, ManagedAddress>;
The content_from_address
function is used to create a new map for accessing the storage of another contract, identified by its address.
The function can have any name, but it is necessary to be tagged with #[storage_mapper_from_address("key_example")]
, where "key_example" is the exact key used by the storage they wish to access.
Parameters
&self
: reference to the current instance of the contract.address: ManagedAddress
: required parameter; it specifies the address of the contract whose storage mapper you want to access.- optional extra keys of any type.
Return type
The function will return the desired mapper that will store the data. In addition, ManagedAddress
will always be added to the end of the list of generics in the storage mapper.
This feature only works intra-shard
.
Also note that a remote value found under a key at an address can only be read
, not modified.
Example
If a developer wanted, for example, to iterate over another contract's SetMapper
, instead of retrieving the values through a call to an endpoint and then iterating, one could simply create a new SetMapper
with a specific address
parameter. Afterwards, the iter
function can be called easily to accomplish the task.
#[storage_mapper("my_remote_mapper")]
fn my_set_mapper(&self) -> SetMapper<u32>
This is a simple SetMapper
registered under the address of contract_to_be_called
, and the value stored will be registered under the key provided, my_remote_mapper
. If we wanted to iterate over the values of my_set_mapper
intra-shard, we could write:
// the address of the contract containing the storage (contract_to_be_called)
#[storage_mapper("contract_address")]
fn contract_address(&self) -> SingleValueMapper<ManagedAddress>;
// by creating the mapper with the address of the sc and exact storage key
// we get access to the value stored under that key
#[storage_mapper_from_address("contract_address")]
fn contract_from_address(&self, address: ManagedAddress) -> SetMapper<u32, ManagedAddress>;
#[endpoint]
fn my_endpoint(&self) -> u32 {
let mut sum = 0u32;
let address = self.contract_address().get();
for number in self.contract_from_address(address).iter() {
sum += number
}
sum
}
Calling my_endpoint
will return the sum of the values stored in contract_to_be_called
's SetMapper.
By specifying the expected type, storage key and address, the value can be read and used inside our logic.