Map

A Map is a key-value store. Unlike the raw storage backend, the keys and values of a map are typed.

Keys

The key type has to implement the PrimaryKey trait. Most commonly, the key is simply a String or Addr. Other types include binary strings (Vec<u8>, [u8; N], &[u8]), numerical types, or even tuples, which can be used to create composite keys.

💡

Unlike values, keys do not need to implement anything like serde::Serialize or serde::Deserialize. Key encoding is handled by the PrimaryKey trait. Please see the Advanced section below for details.

Values

The values, as usual, are serialized as JSON and must implement the Serialize and Deserialize traits.

Operations

Similar to an Item, a Map defines methods like load, save, and may_load. The difference is that Map’s methods take a key as an argument.

Most CosmWasm-enabled chains will enable iteration. If they do, it is possible to iterate over the entries in a map using methods like keys or range.

More information can be found in the API docs.

Usage examples

Keeping users’ balances

Checking a user’s balance

use cw_storage_plus::Map;
 
let balances: Map<String, u128> = Map::new("balances");
 
let alice_balance = balances
    .may_load(&storage, "alice".to_string())
    .unwrap()
    .unwrap_or(0); // if the entry does not exist yet, assume the balance is 0
 
assert_eq!(alice_balance, 0);

Updating a user’s balance

use cw_storage_plus::Map;
 
let balances: Map<String, u128> = Map::new("balances");
 
let mut alice_balance = balances
    .may_load(&storage, "alice".to_string())
    .unwrap()
    .unwrap_or(0); // if the entry does not exist yet, assume the balance is 0
 
alice_balance += 100;
 
balances.save(&mut storage, "alice".to_string(), &alice_balance).unwrap();
assert_eq!(balances.load(&storage, "alice".to_string()).unwrap(), 100);

Keeping users’ balances with a composite key

Basic use

use cw_storage_plus::Map;
 
// The first string is the user's address, the second is the denomination
let balances: Map<(String, String), u128> = Map::new("balances");
 
let mut alice_balance = balances
    .may_load(&storage, ("alice".to_string(), "uusd".to_string()))
    .unwrap()
    .unwrap_or(0); // if the entry does not exist yet, assume the balance is 0
 
alice_balance += 100;
 
balances
    .save(&mut storage, ("alice".to_string(), "uusd".to_string()), &alice_balance)
    .unwrap();
 
assert_eq!(
    balances
        .load(&storage, ("alice".to_string(), "uusd".to_string()))
        .unwrap(),
    100
);

Iterating over composite keys

use cw_storage_plus::Map;
 
let balances: Map<(String, String), u128> = Map::new("balances");
 
balances.save(&mut storage, ("alice".to_string(), "uusd".to_string()), &100).unwrap();
balances.save(&mut storage, ("alice".to_string(), "osmo".to_string()), &200).unwrap();
balances.save(&mut storage, ("bob".to_string(), "uusd".to_string()), &300).unwrap();
 
let all_balances: Vec<_> = balances
    .range(&storage, None, None, Order::Ascending)
    .collect::<Result<_, _>>()
    .unwrap();
 
assert_eq!(
    all_balances,
    vec![
        (("bob".to_string(), "uusd".to_string()), 300),
        (("alice".to_string(), "osmo".to_string()), 200),
        (("alice".to_string(), "uusd".to_string()), 100),
    ]
);
 
let alices_balances: Vec<_> = balances
    .prefix("alice".to_string())
    .range(&storage, None, None, Order::Ascending)
    .collect::<Result<_, _>>()
    .unwrap();
 
assert_eq!(alices_balances, vec![("osmo".to_string(), 200), ("uusd".to_string(), 100)]);
⚠️

As seen here, the order of keys isn’t always lexicographic.

If you need to rely on iteration order in maps with composite keys, here’s how things work: under the hood, every component of a composite key is length-prefixed except for the last one. If you’re only iterating over the last component, you can expect things to be ordered lexicographically. For non-final components, shorter strings will always come before longer ones.

In the example, note how "bob" (a non-last component) comes before "alice". Also note how once we lock the first component to "alice", entries are ordered lexicographically by the second component.

Advanced

Using custom types as a Key in a Map storage

The current section provides an example of an implementation for PrimaryKey (and KeyDeserialize) traits.

Let’s imagine that we would like to use Denom as a key in a map like Map<Denom, Uint64>. Denom is a widely used enum that represents either a CW20 token or a Native token.

Here is a complete implementation.

use cosmwasm_std::{Addr, StdError, StdResult};
use cw_storage_plus::{split_first_key, Key, KeyDeserialize, Prefixer, PrimaryKey};
use std::u8;
 
#[derive(Clone, Debug, PartialEq, Eq)]
enum Denom {
    Native(String),
    Cw20(Addr),
}
 
const NATIVE_PREFIX: u8 = 1;
const CW20_PREFIX: u8 = 2;
 
impl PrimaryKey<'_> for Denom {
    type Prefix = u8;
    type SubPrefix = ();
    type Suffix = String;
    type SuperSuffix = Self;
 
    fn key(&self) -> Vec<Key> {
        let (prefix, value) = match self {
            Denom::Native(name) => (NATIVE_PREFIX, name.as_bytes()),
            Denom::Cw20(addr) => (CW20_PREFIX, addr.as_bytes()),
        };
        vec![Key::Val8([prefix]), Key::Ref(value)]
    }
}
 
impl Prefixer<'_> for Denom {
    fn prefix(&self) -> Vec<Key> {
        let (prefix, value) = match self {
            Denom::Native(name) => (NATIVE_PREFIX.prefix(), name.prefix()),
            Denom::Cw20(addr) => (CW20_PREFIX.prefix(), addr.prefix()),
        };
 
        let mut result: Vec<Key> = vec![];
        result.extend(prefix);
        result.extend(value);
        result
    }
}
 
impl KeyDeserialize for Denom {
    type Output = Self;
 
    const KEY_ELEMS: u16 = 2;
 
    #[inline(always)]
    fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
        let (prefix, value) = split_first_key(Self::KEY_ELEMS, value.as_ref())?;
        let value = value.to_vec();
 
        match u8::from_vec(prefix)? {
            NATIVE_PREFIX => Ok(Denom::Native(String::from_vec(value)?)),
            CW20_PREFIX => Ok(Denom::Cw20(Addr::from_vec(value)?)),
            _ => Err(StdError::generic_err("Invalid prefix")),
        }
    }
}
 
#[cfg(test)]
mod test {
    use crate::Denom;
    use cosmwasm_std::testing::MockStorage;
    use cosmwasm_std::{Addr, Uint64};
    use cw_storage_plus::Map;
 
    #[test]
    fn round_trip_tests() {
        let test_data = vec![
            Denom::Native("cosmos".to_string()),
            Denom::Native("some_long_native_value_with_high_precise".to_string()),
            Denom::Cw20(Addr::unchecked("contract1")),
            Denom::Cw20(Addr::unchecked(
                "cosmos1p7d8mnjttcszv34pk2a5yyug3474mhffasa7tg",
            )),
        ];
 
        for denom in test_data {
            verify_map_serde(denom);
        }
    }
 
    fn verify_map_serde(denom: Denom) {
        let mut storage = MockStorage::new();
        let map: Map<Denom, Uint64> = Map::new("denom_map");
        let mock_value = Uint64::from(123u64);
 
        map.save(&mut storage, denom.clone(), &mock_value).unwrap();
 
        assert!(map.has(&storage, denom.clone()), "key should exist");
 
        let value = map.load(&storage, denom).unwrap();
        assert_eq!(value, mock_value, "value should match");
    }
}

The idea is to store the Denom in the storage as a composite key with 2 elements:

  1. The prefix which is either NATIVE_PREFIX or CW20_PREFIX to differentiate between the two types on a raw bytes level.
  2. The value which is either the native token name or the cw20 token address
💡

The example implementation is based on a so-called composite key i.e. with KEY_ELEMS=2.

Choosing between single element key and composite key

The composite key approach in our example was chosen for demonstration purpose rather than a common logic. Because such a prefix with only 2 unique values doesn’t give much profit in terms of index lookup performance vs full-collection lookup. Also, Denom’s type and its value are logically coupled with each other so there is not much need to split them into prefixes and suffixes.

Another example is a User entity with first_name and last_name fields where such a separation for prefixes and suffixes would be natural. In this case, if you define first_name as a prefix and last_name as a suffix you will be able to have indexed search to query all John’s; and have to fetch all keys to find all Doe’s.