#cosmwasm #pattern #helper #serialization #bucket #storage #singleton

deprecated cw-storage

CosmWasm library with useful helpers for Storage patterns

5 releases

0.2.2 Jul 13, 2020
0.2.1 Jul 13, 2020
0.2.0 Feb 26, 2020
0.1.1 Jan 20, 2020
0.1.0 Jan 8, 2020

#45 in #singleton

25 downloads per month
Used in 3 crates

Apache-2.0

48KB
901 lines

██╗░░░██╗███╗░░██╗███╗░░░███╗░█████╗░██╗███╗░░██╗████████╗░█████╗░██╗███╗░░██╗███████╗██████╗░
██║░░░██║████╗░██║████╗░████║██╔══██╗██║████╗░██║╚══██╔══╝██╔══██╗██║████╗░██║██╔════╝██╔══██╗
██║░░░██║██╔██╗██║██╔████╔██║███████║██║██╔██╗██║░░░██║░░░███████║██║██╔██╗██║█████╗░░██║░░██║
██║░░░██║██║╚████║██║╚██╔╝██║██╔══██║██║██║╚████║░░░██║░░░██╔══██║██║██║╚████║██╔══╝░░██║░░██║
╚██████╔╝██║░╚███║██║░╚═╝░██║██║░░██║██║██║░╚███║░░░██║░░░██║░░██║██║██║░╚███║███████╗██████╔╝
░╚═════╝░╚═╝░░╚══╝╚═╝░░░░░╚═╝╚═╝░░╚═╝╚═╝╚═╝░░╚══╝░░░╚═╝░░░╚═╝░░╚═╝╚═╝╚═╝░░╚══╝╚══════╝╚═════╝░

This repository and the cw-storage crate are unmaintained. All features were moved to the
cosmwasm-storage package in https://github.com/CosmWasm/cosmwasm.

cw-storage

CircleCI Maintenance

CosmWasm library with useful helpers for Storage patterns. This is not in the core library, so feel free to fork it and modify or extend as desired for your contracts. Pull Requests back to upstream repo with new or improved features are always welcome.

Requires Rust v1.38+ (for std::any::type_name used to generate serialization error messages)

Compatible with CosmWasm v0.7.x

Contents

Prefixed Storage

One common technique in smart contracts, especially when multiple types of data are being stored, is to create separate sub-stores with unique prefixes. Thus instead of directly dealing with storage, we wrap it and put all Foo in a Storage with key "foo" + id, and all Bar in a Storage with key "bar" + id. This lets us add multiple types of objects without too much cognitive overhead. Similar separation like Mongo collections or SQL tables.

Since we have different types for Storage and ReadonlyStorage, we use two different constructors:

use cw_storage::{prefixed, prefixed_read};

let mut store = MockStorage::new();

let mut foos = prefixed(b"foo", &mut store);
foos.set(b"one", b"foo");

let mut bars = prefixed(b"bar", &mut store);
bars.set(b"one", b"bar");

let read_foo = prefixed_read(b"foo", &store);
assert_eq!(b"foo".to_vec(), read_foo.get(b"one").unwrap());

let read_bar = prefixed_read(b"bar", &store);
assert_eq!(b"bar".to_vec(), read_bar.get(b"one").unwrap());

Please note that only one mutable reference to the underlying store may be valid at one point. The compiler sees we do not ever use foos after constructing bars, so this example is valid. However, if we did use foos again at the bottom, it would properly complain about violating unique mutable reference.

The takeaway is to create the PrefixedStorage objects when needed and not to hang around to them too long.

Typed Storage

As we divide our storage space into different subspaces or "buckets", we will quickly notice that each "bucket" works on a unique type. This leads to a lot of repeated serialization and deserialization boilerplate that can be removed. We do this by wrapping a Storage with a type-aware TypedStorage struct that provides us a higher-level access to the data.

Note that TypedStorage itself does not implement the Storage interface, so when combining with PrefixStorage, make sure to wrap the prefix first.

use cosmwasm::mock::MockStorage;
use cw_storage::{prefixed, typed};

let mut store = MockStorage::new();
let mut space = prefixed(b"data", &mut store);
let mut bucket = typed::<_, Data>(&mut space);

// save data
let data = Data {
    name: "Maria".to_string(),
    age: 42,
};
bucket.save(b"maria", &data).unwrap();

// load it properly
let loaded = bucket.load(b"maria").unwrap();
assert_eq!(data, loaded);

// loading empty can return Ok(None) or Err depending on the chosen method:
assert!(bucket.load(b"john").is_err());
assert_eq!(bucket.may_load(b"john"), Ok(None));

Beyond the basic save, load, and may_load, there is a higher-level API exposed, update. Update will load the data, apply an operation and save it again (if the operation was successful). It will also return any error that occurred, or the final state that was written if successful.

let birthday = |mut m: Option<Data>| match m {
    Some(mut d) => { 
        d.age += 1; 
        Ok(d) 
    },
    None =>  NotFound{ kind: 'Data'}.fail(),
};
let output = bucket.update(b"maria", &birthday).unwrap();
let expected = Data {
    name: "Maria".to_string(),
    age: 43,
};
assert_eq!(output, expected);

Bucket

Since the above idiom (a subspace for a class of items) is so common and useful, and there is no easy way to return this from a function (bucket holds a reference to space, and cannot live longer than the local variable), the two are often combined into a Bucket. A Bucket works just like the example above, except the creation can be in another function:

use cosmwasm::mock::MockStorage;
use cw_storage::{bucket, Bucket};

fn people<'a, S: Storage>(storage: &'a mut S) -> Bucket<'a, S, Data> {
    bucket(b"people", storage)
}

fn do_stuff() -> Result <()> {
    let mut store = MockStorage::new();
    people(&mut store).save(b"john", &Data{
        name: "John",
        age: 314,
    })?;
    OK(())
}

Singleton

Singleton is another wrapper around the TypedStorage API. There are cases when we don't need a whole subspace to hold arbitrary key-value lookup for typed data, but rather one single instance. The simplest example is some configuration information for a contract. For example, in the name service example, there is a Bucket to look up name to name data, but we also have a Singleton to store global configuration - namely the price of buying a name.

use cosmwasm::mock::MockStorage;
use cosmwasm::types::{Coin, coin};

use cw_storage::{singleton};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
    pub purchase_price: Option<Coin>,
    pub transfer_price: Option<Coin>,
}

fn initialize() -> Result<()> {
    let mut store = MockStorage::new();
    let config = singleton(&mut store, b"config");
    config.save(&Config{
        purchase_price: Some(coin("5", "FEE")),
        transfer_price: None,
    })?;
    config.update(|mut cfg| {
        cfg.transfer_price = Some(coin(2, "FEE"));
        Ok(cfg)
    })?;
    let loaded = config.load()?;
    OK(())
}

Singleton works just like Bucket, except the save, load, update methods don't take a key, and update requires the object to already exist, so the closure takes type T, rather than Option<T>. (Use save to create the object the first time). For Buckets, we often don't know which keys exist, but Singletons should be initialized when the contract is instantiated.

Since the heart of much of the smart contract code is simply transformations upon some stored state, We may be able to just code the state transitions and let the TypedStorage APIs take care of all the boilerplate.

Dependencies

~1.6–2.5MB
~53K SLoC