#cosmwasm #pattern #helpers #serialization #bucket #storage #singleton

已废弃 cw-storage

用于存储模式的CosmWasm库,其中包含有用的辅助函数

5个版本

0.2.2 2020年7月13日
0.2.1 2020年7月13日
0.2.0 2020年2月26日
0.1.1 2020年1月20日
0.1.0 2020年1月8日

#40 in #singleton

Download history 14/week @ 2023-11-20 9/week @ 2023-11-27 2/week @ 2023-12-04 10/week @ 2023-12-11 15/week @ 2023-12-18 6/week @ 2023-12-25 12/week @ 2024-01-08 14/week @ 2024-01-15 14/week @ 2024-02-12 35/week @ 2024-02-19 33/week @ 2024-02-26 19/week @ 2024-03-04

每月 101 次下载
3 个 包中使用

Apache-2.0

48KB
901

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

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库,其中包含有用的辅助函数。这个库不在核心库中,因此您可以根据自己的合约需求自由地进行分叉、修改或扩展。对于添加新功能或改进功能的Pull Requests,我们总是欢迎的。

需要Rust v1.38+(用于生成序列化错误消息的 std::any::type_name

与CosmWasm v0.7.x兼容

内容

前缀存储

在智能合约中,尤其是存储多种类型的数据时,创建具有唯一前缀的单独子存储是一个常见的技巧。因此,我们不是直接处理存储,而是将其封装,并将所有 Foo 放入键为 "foo" + id 的存储中,所有 Bar 放入键为 "bar" + id 的存储中。这样,我们可以在不过度增加认知负担的情况下添加多种类型的对象。类似于Mongo集合或SQL表那样的分离。

由于我们为 StorageReadonlyStorage 使用了不同的类型,因此我们使用了两个不同的构造函数

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());

请注意,在某个时刻只能有一个对底层存储的可变引用是有效的。编译器看到我们在构建 bars 之后永远不会使用 foos,因此此示例是有效的。但是,如果我们在底部再次使用 foos,则会正确地抱怨违反唯一可变引用。

关键是要在需要时创建 PrefixedStorage 对象,而不是长时间挂在那里。

类型存储

当我们把存储空间分成不同的子空间或“桶”时,我们会很快注意到每个“桶”都使用一个独特的类型。这导致了很多重复的序列化和反序列化模板代码,可以移除。我们通过封装一个类型感知的 TypedStorage 结构体来这样做,该结构体为我们提供了对数据的更高级访问。

注意,TypedStorage 本身并没有实现 Storage 接口,所以在与 PrefixStorage 结合时,请确保先封装前缀。

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));

除了基本的 saveloadmay_load,还有一个更高层次的 API 被暴露,即 updateUpdate 将加载数据,应用操作并再次保存(如果操作成功)。它还会返回发生的任何错误,或者在操作成功时写入的最终状态。

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

由于上述惯用语(用于一类项目的子空间)非常常见且有用,而且没有简单的方法从函数中返回它(桶包含对空间的引用,不能比局部变量活得久),因此经常将这两个合并成一个 Bucket。Bucket 的工作方式与上面的例子一样,只不过创建可以在另一个函数中进行。

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

单例是 TypedStorage API 的另一个包装器。有些情况下,我们不需要整个子空间来存储任意类型的键值查找,而只需要一个单一实例。最简单的例子是某个合同的 配置 信息。例如,在 名称服务示例 中,有一个 Bucket 来查找名称到名称的数据,但我们还有一个 Singleton 来存储全局配置——即购买名称的价格。

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 的工作方式与 Bucket 一样,只不过 saveloadupdate 方法不需要键,并且 update 需要对象已经存在,因此闭包的类型为 T,而不是 Option<T>。(使用 save 在第一次创建对象)。对于 Buckets,我们通常不知道哪些键存在,但 Singletons 应在合同实例化时初始化。

由于许多智能合约代码的核心只是对某些存储状态进行转换,我们可能只需编写状态转换,让 TypedStorage API 处理所有模板代码。

依赖关系

~3.5MB
~77K SLoC