MCPcopy Index your code
hub / github.com/CosmWasm/cw-storage-plus

github.com/CosmWasm/cw-storage-plus @v3.0.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release v3.0.1 ↗ · + Follow
501 symbols 1,568 edges 29 files 122 documented · 24%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

StoragePlus

component [license]apache-url

Storage abstractions for CosmWasm smart contracts

Rust crates

The following Rust crates are maintained in this repository:

Crate Usage Download Docs Coverage
cw-storage-plus Contract development cw-storage-plus docs-storage-plus coverage-cw-storage-plus
cw-storage-macro Contract development cw-storage-macro docs-storage-macro coverage-cw-storage-macro

Introduction

This has been heavily used in many production-quality contracts. The code has demonstrated itself to be stable and powerful. It is mature enough to be the standard storage layer for your contracts.

Overview

We introduce two main classes to provide a productive abstraction on top of cosmwasm_std::Storage. They are Item, which is a typed wrapper around one database key, providing some helper functions for interacting with it without dealing with raw bytes. And Map, which allows you to store multiple unique typed objects under a prefix, indexed by a simple or compound (e.g. (&[u8], &[u8])) primary key.

Item

The usage of an Item is pretty straight-forward. You must simply provide the proper type, as well as a database key not used by any other item. Then it will provide you with a nice interface to interact with such data.

If you are coming from using Singleton, the biggest change is that we no longer store Storage inside, meaning we don't need to read and write variants of the object, just one type. Furthermore, we use const fn to create the Item, allowing it to be defined as a global compile-time constant rather than a function that must be constructed each time, which saves gas as well as typing.

Example Usage:

#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Config {
    pub owner: String,
    pub max_tokens: i32,
}

// note const constructor rather than 2 functions with Singleton
const CONFIG: Item<Config> = Item::new("config");

fn demo() -> StdResult<()> {
    let mut store = MockStorage::new();

    // may_load returns Option<T>, so None if data is missing
    // load returns T and Err(StdError::NotFound{}) if data is missing
    let empty = CONFIG.may_load(&store)?;
    assert_eq!(None, empty);
    let cfg = Config {
        owner: "admin".to_string(),
        max_tokens: 1234,
    };
    CONFIG.save(&mut store, &cfg)?;
    let loaded = CONFIG.load(&store)?;
    assert_eq!(cfg, loaded);

    // update an item with a closure (includes read and write)
    // returns the newly saved value
    let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
        c.max_tokens *= 2;
        Ok(c)
    })?;
    assert_eq!(2468, output.max_tokens);

    // you can error in an update and nothing is saved
    let failed = CONFIG.update(&mut store, |_| -> StdResult<_> {
        Err(StdError::generic_err("failure mode"))
    });
    assert!(failed.is_err());

    // loading data will show the first update was saved
    let loaded = CONFIG.load(&store)?;
    let expected = Config {
        owner: "admin".to_string(),
        max_tokens: 2468,
    };
    assert_eq!(expected, loaded);

    // we can remove data as well
    CONFIG.remove(&mut store);
    let empty = CONFIG.may_load(&store)?;
    assert_eq!(None, empty);

    Ok(())
}

Map

The usage of a Map is a little more complex, but is still pretty straight-forward. You can imagine it as a storage-backed BTreeMap, allowing key-value lookups with typed values. In addition, we support not only simple binary keys (like &[u8]), but tuples, which are combined. This allows us by example to store allowances as composite keys, i.e. (owner, spender) to look up the balance.

Beyond direct lookups, we have a super-power not found in Ethereum - iteration. That's right, you can list all items in a Map, or only part of them. We can efficiently allow pagination over these items as well, starting at the point the last query ended, with low gas costs. This requires the iterator feature to be enabled in cw-storage-plus (which automatically enables it in cosmwasm-std as well, and which is enabled by default).

If you are coming from using Bucket, the biggest change is that we no longer store Storage inside, meaning we don't need to read and write variants of the object, just one type. Furthermore, we use const fn to create the Bucket, allowing it to be defined as a global compile-time constant rather than a function that must be constructed each time, which saves gas as well as typing. In addition, the composite indexes (tuples) are more ergonomic and expressive of intention, and the range interface has been improved.

Here is an example with normal (simple) keys:

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
struct Data {
    pub name: String,
    pub age: i32,
}

const PEOPLE: Map<&str, Data> = Map::new("people");

fn demo() -> StdResult<()> {
    let mut store = MockStorage::new();
    let data = Data {
        name: "John".to_string(),
        age: 32,
    };

    // load and save with extra key argument
    let empty = PEOPLE.may_load(&store, "john")?;
    assert_eq!(None, empty);
    PEOPLE.save(&mut store, "john", &data)?;
    let loaded = PEOPLE.load(&store, "john")?;
    assert_eq!(data, loaded);

    // nothing on another key
    let missing = PEOPLE.may_load(&store, "jack")?;
    assert_eq!(None, missing);

    // update function for new or existing keys
    let birthday = |d: Option<Data>| -> StdResult<Data> {
        match d {
            Some(one) => Ok(Data {
                name: one.name,
                age: one.age + 1,
            }),
            None => Ok(Data {
                name: "Newborn".to_string(),
                age: 0,
            }),
        }
    };

    let old_john = PEOPLE.update(&mut store, "john", birthday)?;
    assert_eq!(33, old_john.age);
    assert_eq!("John", old_john.name.as_str());

    let new_jack = PEOPLE.update(&mut store, "jack", birthday)?;
    assert_eq!(0, new_jack.age);
    assert_eq!("Newborn", new_jack.name.as_str());

    // update also changes the store
    assert_eq!(old_john, PEOPLE.load(&store, "john")?);
    assert_eq!(new_jack, PEOPLE.load(&store, "jack")?);

    // removing leaves us empty
    PEOPLE.remove(&mut store, "john");
    let empty = PEOPLE.may_load(&store, "john")?;
    assert_eq!(None, empty);

    Ok(())
}

Key types

A Map key can be anything that implements the PrimaryKey trait. There are a series of implementations of PrimaryKey already provided (see keys.rs):

  • impl<'a> PrimaryKey<'a> for &'a [u8]
  • impl<'a> PrimaryKey<'a> for &'a str
  • impl<'a> PrimaryKey<'a> for Vec<u8>
  • impl<'a> PrimaryKey<'a> for String
  • impl<'a> PrimaryKey<'a> for Addr
  • impl<'a, const N: usize> PrimaryKey<'a> for [u8; N]
  • impl<'a, T: Prefixer<'a>> Prefixer<'a> for &'a T
  • impl<'a, T: PrimaryKey<'a> + Prefixer<'a>, U: PrimaryKey<'a>> PrimaryKey<'a> for (T, U)
  • impl<'a, T: PrimaryKey<'a> + Prefixer<'a>, U: PrimaryKey<'a> + Prefixer<'a>, V: PrimaryKey<'a>> PrimaryKey<'a> for (T, U, V)
  • PrimaryKey implemented for unsigned integers up to u128
  • PrimaryKey implemented for signed integers up to i128

That means that byte and string slices, byte vectors, and strings, can be conveniently used as keys. Moreover, some other types can be used as well, like addresses and address references, pairs, triples, and integer types.

If the key represents an address, we suggest using &Addr for keys in storage, instead of String or string slices. This implies doing address validation through addr_validate on any address passed in via a message, to ensure it's a legitimate address, and not random text which will fail later. pub fn addr_validate(&self, &str) -> Addr in deps.api can be used for address validation, and the returned Addr can then be conveniently used as key in a Map or similar structure.

It's also convenient to use references (i.e. borrowed values) instead of values for keys (i.e. &Addr instead of Addr), as that will typically save some cloning during key reading / writing.

Composite Keys

There are times when we want to use multiple items as a key. For example, when storing allowances based on account owner and spender. We could try to manually concatenate them before calling, but that can lead to overlap, and is a bit low-level for us. Also, by explicitly separating the keys, we can easily provide helpers to do range queries over a prefix, such as "show me all allowances for one owner" (first part of the composite key). Just like you'd expect from your favorite database.

Here's how we use it with composite keys. Just define a tuple as a key and use that everywhere you used a single key above.

// Note the tuple for primary key. We support one slice, or a 2 or 3-tuple.
// Adding longer tuples is possible, but unlikely to be needed.
const ALLOWANCE: Map<(&str, &str), u64> = Map::new("allow");

fn demo() -> StdResult<()> {
    let mut store = MockStorage::new();

    // save and load on a composite key
    let empty = ALLOWANCE.may_load(&store, ("owner", "spender"))?;
    assert_eq!(None, empty);
    ALLOWANCE.save(&mut store, ("owner", "spender"), &777)?;
    let loaded = ALLOWANCE.load(&store, ("owner", "spender"))?;
    assert_eq!(777, loaded);

    // doesn't appear under other key (even if a concat would be the same)
    let different = ALLOWANCE.may_load(&store, ("owners", "pender")).unwrap();
    assert_eq!(None, different);

    // simple update
    ALLOWANCE.update(&mut store, ("owner", "spender"), |v| {
        Ok(v.unwrap_or_default() + 222)
    })?;
    let loaded = ALLOWANCE.load(&store, ("owner", "spender"))?;
    assert_eq!(999, loaded);

    Ok(())
}

Path

Under the scenes, we create a Path from the Map when accessing a key. PEOPLE.load(&store, "jack") == PEOPLE.key("jack").load(). Map.key() returns a Path, which has the same interface as Item, re-using the calculated path to this key.

For simple keys, this is just a bit less typing and a bit less gas if you use the same key for many calls. However, for composite keys, like ("owner", "spender") it is much less typing. And highly recommended anywhere you will use a composite key even twice:

#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
struct Data {
    pub name: String,
    pub age: i32,
}

const PEOPLE: Map<&str, Data> = Map::new("people");
const ALLOWANCE: Map<(&str, &str), u64> = Map::new("allow");

fn demo() -> StdResult<()> {
    let mut store = MockStorage::new();
    let data = Data {
        name: "John".to_string(),
        age: 32,
    };

    // create a Path one time to use below
    let john = PEOPLE.key("john");

    // Use this just like an Item above
    let empty = john.may_load(&store)?;
    assert_eq!(None, empty);
    john.save(&mut store, &data)?;
    let loaded = john.load(&store)?;
    assert_eq!(data, loaded);
    john.remove(&mut store);
    let empty = john.may_load(&store)?;
    assert_eq!(None, empty);

    // Same for composite keys, just use both parts in `key()`.
    // Notice how much less verbose than the above example.
    let allow = ALLOWANCE.key(("owner", "spender"));
    allow.save(&mut store, &1234)?;
    let loaded = allow.load(&store)?;
    assert_eq!(1234, loaded);
    allow.update(&mut store, |x| Ok(x.unwrap_or_default() * 2))?;
    let loaded = allow.load(&store)?;
    assert_eq!(2468, loaded);

    Ok(())
}

Prefix

In addition to getting one particular item out of a map, we can iterate over the map (or a subset of the map). This let us answer questions like "show me all tokens", and we provide some nice Bound helpers to easily allow pagination or custom ranges.

The general format is to get a Prefix by calling map.prefix(k), where k is exactly one less item than the normal key (If map.key() took (&[u8], &[u8]), then map.prefix() takes &[u8]. If map.key() took &[u8], map.prefix() takes ()). Onc

Extension points exported contracts — how you extend this code

PrimaryKey (Interface)
`PrimaryKey` needs to be implemented for types that want to be a `Map` (or `Map`-like) key, or part of a key. In partic [10 …
src/keys.rs
IndexList (Interface)
(no doc) [7 implementers]
src/indexed_map.rs
KeyDeserialize (Interface)
(no doc) [13 implementers]
src/de.rs
Bounder (Interface)
(no doc) [9 implementers]
src/bound.rs
Index (Interface)
Note: we cannot store traits with generic functions inside `Box `, so I pull S: Storage to a top-level [2 implementers]
src/indexes/mod.rs
IntKey (Interface)
Our int keys are simply the big-endian representation bytes for unsigned ints, but "sign-flipped" (xored msb) big-endian
src/int_key.rs
Endian (Interface)
(no doc)
src/endian.rs
Prefixer (Interface)
(no doc) [9 implementers]
src/keys.rs

Core symbols most depended-on inside this repo

save
called by 70
src/map.rs
save
called by 56
src/indexed_map.rs
as_slice
called by 37
src/item.rs
push_back
called by 34
src/deque.rs
len
called by 32
src/deque.rs
save
called by 31
src/indexed_snapshot.rs
range
called by 31
src/map.rs
iter
called by 27
src/deque.rs

Shape

Method 231
Function 219
Class 37
Interface 8
Enum 6

Languages

Rust100%

Modules by API surface

src/indexed_map.rs55 symbols
src/map.rs53 symbols
src/indexed_snapshot.rs46 symbols
src/deque.rs43 symbols
src/snapshot/map.rs39 symbols
src/keys.rs30 symbols
src/prefix.rs27 symbols
src/item.rs24 symbols
src/snapshot/item.rs23 symbols
src/indexes/multi.rs20 symbols
src/indexes/unique.rs18 symbols
src/de.rs18 symbols

For agents

$ claude mcp add cw-storage-plus \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact