1个版本 (0个不稳定)
1.0.0-provbeta2 | 2022年2月23日 |
---|
#31 在 #singleton
58KB
1K SLoC
cosmwasm-storage
CosmWasm库,包含用于存储模式的实用辅助函数。您可以在 cosmwasm-std
中使用 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 包含对空间的引用,且不能比局部变量存活时间更长),因此通常将它们合并成一个 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
Singleton 是围绕 TypedStorage
API 的另一个包装器。有些情况下,我们不需要整个子空间来存储任意类型的键值查找,而只需要一个存储键。最简单的例子是某个合约的 配置 信息。例如,在 名称服务示例 中,有一个 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
要求对象已经存在,因此闭包的类型为 T
,而不是 Option<T>
。 (使用 save
首次创建对象)。对于 Buckets
,我们通常不知道哪些键存在,但 Singleton
应在合约实例化时初始化。
由于许多智能合约代码的核心仅仅是某些存储状态的转换,我们可能只需编写状态转换,让 TypedStorage
API 处理所有样板代码。
许可协议
此包是 cosmwasm 存储库的一部分,根据 Apache License 2.0 许可(请参阅 NOTICE 和 LICENSE)。
依赖关系
~4–5.5MB
~125K SLoC