116 个版本 (33 个稳定版)
1.5.2 | 2024年1月15日 |
---|---|
1.5.0 | 2023年10月31日 |
1.4.3 | 2024年1月18日 |
1.4.0 | 2023年9月4日 |
0.10.0 | 2020年7月30日 |
#1307 in 魔法豆
18,239 每月下载量
用于 141 个包 (112 个直接使用)
1.5MB
32K SLoC
警告:未维护
此包不再维护,不应再使用。作为替代方案,请查看 cw-storage-plus
cosmwasm-storage
CosmWasm 库,提供存储模式的有用辅助工具。您可以使用 Storage
实现,或者依赖于这些工具以减少一些常见的模板代码。
内容
前缀存储
在智能合约中,尤其是在存储多种类型数据时,创建具有唯一前缀的单独子存储是一个常见的技巧。因此,我们不是直接处理存储,而是将其封装,并将所有 Foo
放入一个以 "foo" + id
为键的存储中,将所有 Bar
放入一个以 "bar" + id
为键的存储中。这样我们就可以在不增加太多认知负担的情况下添加多种类型的对象。类似于 Mongo 集合或 SQL 表的类似分离。
由于我们为 Storage
和 ReadonlyStorage
使用了不同的类型,所以我们使用了两个不同的构造函数
use cosmwasm_std::testing::MockStorage;
use cosmwasm_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
对象,而不是长时间保留它们。
类型存储
随着我们将存储空间划分为不同的子空间或“桶”,我们会很快注意到每个“桶”都工作在独特的类型上。这导致了很多重复的序列化和反序列化模板代码可以被移除。我们通过包装一个 Storage
到一个类型感知的 TypedStorage
结构体来实现这一点,它为我们提供了对数据的更高级访问。
请注意,TypedStorage
本身并不实现 Storage
接口,所以当与 PrefixStorage
结合使用时,请确保首先包装前缀。
use cosmwasm_std::testing::MockStorage;
use cosmwasm_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));
除了基本的 save
、load
和 may_load
,还有一个更高级的 API 被公开,即 update
。 Update
将加载数据,应用操作,然后再次保存(如果操作成功)。它还会返回发生的任何错误,或者在成功的情况下写入的最终状态。
let on_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", &on_birthday).unwrap();
let expected = Data {
name: "Maria".to_string(),
age: 43,
};
assert_eq!(output, expected);
桶
由于上述惯例(一个子空间用于一个项目类)非常常见且有用,而且没有简单的方法可以从函数中返回它(桶包含对空间的引用,其生命周期不能超过局部变量),这两个常常结合成一个 Bucket
。Bucket 的工作方式就像上面的例子一样,只不过创建可以在另一个函数中进行。
use cosmwasm_std::StdResult;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_storage::{bucket, Bucket};
fn people<'a, S: Storage>(storage: &'a mut S) -> Bucket<'a, S, Data> {
bucket(b"people", storage)
}
fn do_stuff() -> StdResult<()> {
let mut store = MockStorage::new();
people(&mut store).save(b"john", &Data{
name: "John",
age: 314,
})?;
OK(())
}
单例
Singleton 是对 TypedStorage
API 的另一种包装。有些情况下,我们不需要整个子空间来持有任意键值查找的定型数据,而是需要一个单一的存储键。最简单的例子是某个合约的 配置 信息。例如,在 name service 示例 中,有一个 Bucket
来查找名称到名称数据,但我们还有一个 Singleton
来存储全局配置 - 也就是购买名称的价格。
请注意,在此上下文中,“singleton”一词并不指代 单例模式,而是一个单元素容器。
use cosmwasm_std::{Coin, coin, StdResult};
use cosmwasm_std::testing::MockStorage;
use cosmwasm_storage::{singleton};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
pub purchase_price: Option<Coin>,
pub transfer_price: Option<Coin>,
}
fn initialize() -> StdResult<()> {
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
一样,只不过 save
、load
和 update
方法不接收键,并且 update
需要 object 已经存在,所以闭包的类型是 T
,而不是 Option<T>
。(使用 save
在第一次创建对象)。对于 Buckets
,我们通常不知道哪些键存在,但 Singleton
应在合约实例化时初始化。
由于许多智能合约代码的核心是简单地转换一些存储状态,我们可能只需要编写状态转换,让 TypedStorage
API 处理所有模板代码。
许可
此包是 cosmwasm 存储库的一部分,根据 Apache License 2.0 许可(请参阅 NOTICE 和 LICENSE)。
依赖项
~2–4MB
~83K SLoC