9 个版本 (重大更改)
0.10.0 | 2023年10月18日 |
---|---|
0.9.0 | 2023年6月4日 |
0.7.0 | 2022年12月18日 |
0.6.0 | 2022年10月25日 |
0.2.0 | 2022年1月16日 |
#1802 在 魔法豆
每月242次 下载
在 2 个 crate 中使用(通过 secret-toolkit)
205KB
4K SLoC
Secret Contract 开发工具包 - 存储工具
⚠️ 此包是 secret-toolkit
包的一个子包。请参阅其 crate 页面以了解更多信息。您需要 Rust 1.63+ 来编译此包。
此包包含许多与存储访问模式相关的工具。此 README 文件假定您对基本 cosmwasm 存储有一定了解,点击此处了解相关信息。
如何导入此子包
要导入此包,请将以下任一行添加到您的 Cargo.toml
文件中
secret-toolkit = { version = "0.6", default-features = false, features = ["utils", "storage", "serialization"] }
对于发布版本,或
secret-toolkit = { git = "https://github.com/scrtlabs/secret-toolkit", branch = "master", default-features = false, features = ["utils", "storage", "serialization"]}
对于 GitHub 版本。我们还将导入 serialization
功能,以防我们想要切换到使用 Json 而不是 Bincode2 来序列化/反序列化数据。
存储对象
项目
这是本工具包中最简单的存储对象。它基于 cosmwasm-storage-plus 中同名项目。项目允许用户指定要存储的对象的类型以及存储它所使用的序列化/反序列化方法(默认为 Bincode2)。可以将 Item 结构体视为存储键的包装器。请注意,您应该使用 Json 来 serde 枚举或存储枚举的任何结构(除了标准的 Option 枚举),因为 Bincode2 在反序列化枚举时可能会使用浮点数。这也是为什么其他 cosmwasm 链根本不使用 Bincode2,但是当您可以使用它时,您会获得一些性能提升。
初始化
此对象旨在在 state.rs
中作为静态常量初始化。然而,如果它在运行时使用可变键初始化,效果也非常好(在这种情况下,您需要提醒它存储的对象类型及其 serde)。使用以下行导入它
use secret_toolkit::storage::{Item};
使用以下行进行初始化
# use cosmwasm_std::Addr;
# use secret_toolkit_storage::Item;
pub static OWNER: Item<Addr> = Item::new(b"owner");
默认情况下,使用 Bincode2 对 Addr 进行 serde。要指定为 Json 的 Serde 算法,首先从 secret-toolkit::serialization
导入它
use secret_toolkit::serialization::{Bincode2, Json};
然后
# use cosmwasm_std::Addr;
# use secret_toolkit_storage::Item;
# use secret_toolkit_serialization::Json;
# use serde::{Serialize, Deserialize};
# #[derive(Serialize, Deserialize)]
# enum SomeEnum {};
#
pub static OWNER: Item<Addr> = Item::new(b"owner");
pub static SOME_ENUM: Item<SomeEnum, Json> = Item::new(b"some_enum");
读取/写入
读取/写入存储的方式是使用其方法。这些方法是 save
、load
、may_load
、remove
、update
。以下是在 contract.rs
执行中每个示例用法的例子
# use cosmwasm_std::{Addr, testing::mock_dependencies, StdError};
# use secret_toolkit_storage::Item;
#
# pub static OWNER: Item<Addr> = Item::new(b"owner");
#
# let mut deps = mock_dependencies();
# OWNER.save(&mut deps.storage, &Addr::unchecked("owner-addr"))?;
#
// The compiler knows that owner_addr is Addr
let owner_addr = OWNER.load(&deps.storage)?;
# Ok::<(), StdError>(())
# use cosmwasm_std::{Addr, testing::{mock_dependencies, mock_info}, StdError};
# use secret_toolkit_storage::Item;
#
# pub static OWNER: Item<Addr> = Item::new(b"owner");
#
# let mut deps = mock_dependencies();
# let info = mock_info("sender", &[]);
#
OWNER.save(&mut deps.storage, &info.sender)?;
# Ok::<(), StdError>(())
# use cosmwasm_std::{Addr, testing::mock_dependencies, StdError};
# use secret_toolkit_storage::Item;
#
# pub static OWNER: Item<Addr> = Item::new(b"owner");
#
# let mut deps = mock_dependencies();
#
// The compiler knows that may_addr is Option<Addr>
let may_addr = OWNER.may_load(&deps.storage)?;
# Ok::<(), StdError>(())
# use cosmwasm_std::{Addr, testing::mock_dependencies, StdError};
# use secret_toolkit_storage::Item;
#
# pub static OWNER: Item<Addr> = Item::new(b"owner");
#
# let mut deps = mock_dependencies();
#
// The compiler knows that may_addr is Option<Addr>
let may_addr = OWNER.remove(&mut deps.storage);
# use cosmwasm_std::{Addr, testing::{mock_dependencies, mock_info}, StdError};
# use secret_toolkit_storage::Item;
#
# pub static OWNER: Item<Addr> = Item::new(b"owner");
#
# let mut deps = mock_dependencies();
# let info = mock_info("sender", &[]);
# OWNER.save(&mut deps.storage, &Addr::unchecked("owner-addr"))?;
#
// The compiler knows that may_addr is Option<Addr>
let may_addr = OWNER.update(&mut deps.storage, |_x| Ok(info.sender))?;
# Ok::<(), StdError>(())
AppendStore
AppendStore 旨在以 cosmwasm 效率的方式复制追加列表的功能。列表的长度被存储并用于向列表中弹出/推入项。它还有一个创建只读迭代器的功能。
此存储对象还具有 remove
方法,可以从列表的任意位置删除存储的对象,但这可能非常低效。
❗ 越来越远离尾部的存储对象删除变得越来越低效。我们建议您尽可能使用
pop
和push
。
这里也遵循了 Item
的相同约定,即
- AppendStore 必须知道存储对象的类型。以及可选的 serde。
- 每个方法都需要自己的对
deps.storage
的引用。
初始化
要将此存储对象作为静态常量导入和初始化到 state.rs
中,执行以下操作
use secret_toolkit::storage::{AppendStore};
# use secret_toolkit_storage::AppendStore;
# use cosmwasm_std::StdError;
pub static COUNT_STORE: AppendStore<i32> = AppendStore::new(b"count");
# Ok::<(), StdError>(())
❗ 将对象初始化为 const 而不是 static 也可以工作,但效率较低,因为变量无法缓存长度数据。
通常,我们需要将这些存储对象与用户地址或其他可变键关联起来。在这种情况下,您不需要在 contract.rs
中初始化一个新的 AppendStore。相反,您可以通过向现有的 AppendStore 添加后缀来创建一个新的 AppendStore。这有利于防止您不得不重写 AppendStore 的签名。例如
# use secret_toolkit_storage::AppendStore;
# use cosmwasm_std::testing::mock_info;
# let info = mock_info("sender", &[]);
# pub static COUNT_STORE: AppendStore<i32> = AppendStore::new(b"count");
#
// The compiler knows that user_count_store is AppendStore<i32, Bincode2>
let user_count_store = COUNT_STORE.add_suffix(info.sender.to_string().as_bytes());
有时在迭代这些对象时,我们可能希望一次加载 n
个对象。如果正在迭代的对象存储成本低,或者我们知道多个对象需要连续访问,这可能更受欢迎。在这种情况下,我们可能想要更改内部索引大小(默认为 1)。我们在 state.rs
中这样做
# use secret_toolkit_storage::AppendStore;
pub static COUNT_STORE: AppendStore<i32> = AppendStore::new_with_page_size(b"count", 5);
读取/写入
读取/写入 AppendStore 的主要用户方法为 pop
、push
、get_len
、set_at(在长度范围内替换位置中的数据)、
clear(删除存储中的所有数据)、
remove
(在任意位置删除项,这非常低效)。在 append_store.rs
的单元测试中可以找到这些方法被广泛使用的例子。
迭代器
AppendStore 还实现了只读迭代器功能。此功能还用于创建一个名为 paging
的分页包装方法。创建迭代器的方式是
# use cosmwasm_std::{StdError, testing::mock_dependencies};
# use secret_toolkit_storage::AppendStore;
# pub static COUNT_STORE: AppendStore<i32> = AppendStore::new_with_page_size(b"count", 5);
# let deps = mock_dependencies();
#
let iter = COUNT_STORE.iter(&deps.storage)?;
# Ok::<(), StdError>(())
更多示例可以在单元测试中找到。分页包装器按以下方式使用
# use cosmwasm_std::{StdError, testing::mock_dependencies};
# use secret_toolkit_storage::AppendStore;
# pub static COUNT_STORE: AppendStore<i32> = AppendStore::new_with_page_size(b"count", 5);
# let deps = mock_dependencies();
#
let start_page: u32 = 0;
let page_size: u32 = 5;
// The compiler knows that values is Vec<i32>
let values = COUNT_STORE.paging(&deps.storage, start_page, page_size)?;
# Ok::<(), StdError>(())
DequeStore
这是一个基于AppendStore的存储包装器,它复制了一个双端列表。该存储对象允许用户高效地在列表的任一端弹出/推送项目。
初始化
要将此存储对象作为静态常量导入和初始化到 state.rs
中,执行以下操作
use secret_toolkit::storage::{DequeStore};
# use secret_toolkit_storage::DequeStore;
pub static COUNT_STORE: DequeStore<i32> = DequeStore::new(b"count");
❗ 将对象初始化为 const 而不是 static 也可以工作,但效率较低,因为变量无法缓存长度数据。
new_with_page_size的工作方式与AppendStore类似。
读取/写入
用于读写DequeStore的主要用户方法有:pop_back
、pop_front
、push_back
、push_front
、get_len
、get_off
、set_at
(在长度范围内替换位置中的数据),clear
(删除存储中的所有数据),remove
(从任意位置删除项目,这非常低效)。关于这些方法的详细示例,可以在deque_store.rs
中找到DequeStore的单元测试。
迭代器
这与AppendStore完全相同。
键映射
这种类似于哈希表存储结构允许用户使用泛型类型键来存储对象。允许通过分页遍历键和/或项目(尽管没有保证排序,但插入顺序在开始删除对象之前被保留)。这种结构的示例用例是,如果您想包含大量投票、存款或赌注并在将来某个时候遍历它们。由于一次性遍历大量数据可能很昂贵,因此该结构允许您指定每页将返回的数据量。
初始化
要将此存储对象作为静态常量导入和初始化到 state.rs
中,执行以下操作
use secret_toolkit::storage::{Keymap, KeymapBuilder};
# use secret_toolkit_storage::Keymap;
# use cosmwasm_std::{Addr};
# use serde::{Serialize, Deserialize};
# #[derive(Serialize, Deserialize)]
# struct BetInfo { bet_outcome: u32, amount: u32 };
# #[derive(Serialize, Deserialize)]
# struct Foo { vote_for: String };
#
pub static ADDR_VOTE: Keymap<Addr, Foo> = Keymap::new(b"vote");
pub static BET_STORE: Keymap<u32, BetInfo> = Keymap::new(b"bet");
❗ 将对象初始化为 const 而不是 static 也可以工作,但效率较低,因为变量无法缓存长度数据。
您可以通过将签名更改为Keymap<Addr, Uint128, Json>
来使用Json serde算法,类似于所有其他存储对象。但是,请注意,Serde算法用于序列化和反序列化存储对象(Uint128
)以及键(Addr
)。
如果您需要将键映射关联到用户地址(或任何其他变量),则也可以使用.add_suffix
方法。
例如,假设在您的合约中,用户可以下多个赌注。那么,您将希望为每个用户关联一个键映射。您可以在contract.rs
执行期间通过以下方式实现这一点。
# use secret_toolkit_storage::Keymap;
# use cosmwasm_std::{Addr, testing::mock_info};
# use serde::{Serialize, Deserialize};
# #[derive(Serialize, Deserialize)]
# struct BetInfo { bet_outcome: u32, amount: u32 };
# let info = mock_info("sender", &[]);
#
pub static BET_STORE: Keymap<u32, BetInfo> = Keymap::new(b"bet");
// The compiler knows that user_bet_store is AppendStore<u32, BetInfo>
let user_count_store = BET_STORE.add_suffix(info.sender.to_string().as_bytes());
高级初始化
还可以修改键映射结构的某些配置设置,以便更好地适应特定用例。在这种情况下,我们使用一个名为KeymapBuilder
的结构来构建具有专用配置的键映射。目前,我们可以使用KeymapBuilder来修改键映射的两个属性。
一个是可以完全禁用迭代功能,使用.without_iter()
。这基本上将键映射转换为类型化PrefixedStorage,但它也通过不存储键和键映射的长度来节省大量gas。
另一个功能是修改内部索引器的页面大小(仅当启用迭代功能时,即如果使用.without_iter()
,则此设置不相关)。键映射通过使用内部索引页面进行迭代,允许它同时加载下一个5个对象。您可以使用.with_page_size(num)
将默认的5更改为任何大于零的u32
,从而允许用户优化键映射的gas使用。
以下是在state.rs
中生成不带迭代器的键映射的代码。
# use secret_toolkit_storage::{Keymap, KeymapBuilder, WithoutIter};
# use secret_toolkit_serialization::{Json, Bincode2};
# use serde::{Serialize, Deserialize};
# #[derive(Serialize, Deserialize)]
# struct Foo { vote: u32 };
#
pub static JSON_ADDR_VOTE: Keymap<String, Foo, Json, WithoutIter> =
KeymapBuilder::new(b"json_vote").without_iter().build();
pub static BINCODE_ADDR_VOTE: Keymap<String, Foo, Bincode2, WithoutIter> =
KeymapBuilder::new(b"bincode_vote").without_iter().build();
以下是在state.rs
中生成修改了索引页面大小的键映射的代码。
# use secret_toolkit_storage::{Keymap, KeymapBuilder};
# use cosmwasm_std::{Addr};
# use secret_toolkit_serialization::{Json};
# use serde::{Serialize, Deserialize};
# #[derive(Serialize, Deserialize)]
# struct Foo { vote: u32 };
#
pub static ADDR_VOTE: Keymap<Addr, Foo> = KeymapBuilder::new(b"page_vote").with_page_size(13).build();
pub static JSON_VOTE: Keymap<Addr, Foo, Json> =
KeymapBuilder::new(b"page_vote").with_page_size(3).build();
读取/写入
您可以在Keymap的单元测试中找到更多使用键映射的示例,位于keymap.rs
。
要插入、删除或从键映射中读取,请执行以下操作
# use secret_toolkit_storage::{Keymap, KeymapBuilder};
# use cosmwasm_std::{Addr, testing::{mock_info, mock_dependencies}, StdError};
# use serde::{Serialize, Deserialize};
# #[derive(Serialize, Deserialize, PartialEq, Debug)]
# struct Foo { message: String, votes: u32 };
#
# let mut deps = mock_dependencies();
# let info = mock_info("sender", &[]);
# pub static ADDR_VOTE: Keymap<Addr, Foo> = KeymapBuilder::new(b"page_vote").with_page_size(13).build();
#
let user_addr: Addr = info.sender;
let foo = Foo {
message: "string one".to_string(),
votes: 1111,
};
ADDR_VOTE.insert(&mut deps.storage, &user_addr, &foo)?;
// Compiler knows that this is Foo
let read_foo = ADDR_VOTE.get(deps.as_ref().storage, &user_addr).unwrap();
assert_eq!(read_foo, foo);
ADDR_VOTE.remove(&mut deps.storage, &user_addr)?;
assert_eq!(ADDR_VOTE.get_len(deps.as_ref().storage)?, 0);
# Ok::<(), StdError>(())
迭代器
Keymap中有两种创建迭代器的方法。这些是.iter
和.iter_keys
。`iter_keys`仅遍历键,而`iter`遍历(键,项目)对。不言而喻,`iter_keys`效率更高,因为它不会尝试读取项目。
Keymap还有两种分页方法,这些是.paging
和.paging_keys
。`paging_keys`仅分页键,而`iter`遍历(键,项目)对。不言而喻,`iter_keys`效率更高,因为它不会尝试读取项目。
以下是一些来自单元测试的精选示例
# use cosmwasm_std::{StdResult, testing::MockStorage};
# use secret_toolkit_storage::Keymap;
# use serde::{Serialize, Deserialize};
# #[derive(Serialize, Deserialize, PartialEq, Debug)]
# struct Foo { string: String, number: u32 };
#
fn test_keymap_iter_keys() -> StdResult<()> {
let mut storage = MockStorage::new();
let keymap: Keymap<String, Foo> = Keymap::new(b"test");
let foo1 = Foo {
string: "string one".to_string(),
number: 1111,
};
let foo2 = Foo {
string: "string two".to_string(),
number: 1111,
};
let key1 = "key1".to_string();
let key2 = "key2".to_string();
keymap.insert(&mut storage, &key1, &foo1)?;
keymap.insert(&mut storage, &key2, &foo2)?;
let mut x = keymap.iter_keys(&storage)?;
let (len, _) = x.size_hint();
assert_eq!(len, 2);
assert_eq!(x.next().unwrap()?, key1);
assert_eq!(x.next().unwrap()?, key2);
Ok(())
}
# use cosmwasm_std::{StdResult, testing::MockStorage};
# use secret_toolkit_storage::Keymap;
# use serde::{Serialize, Deserialize};
# #[derive(Serialize, Deserialize, PartialEq, Debug)]
# struct Foo { string: String, number: u32 };
#
fn test_keymap_iter() -> StdResult<()> {
let mut storage = MockStorage::new();
let keymap: Keymap<Vec<u8>, Foo> = Keymap::new(b"test");
let foo1 = Foo {
string: "string one".to_string(),
number: 1111,
};
let foo2 = Foo {
string: "string two".to_string(),
number: 1111,
};
keymap.insert(&mut storage, &b"key1".to_vec(), &foo1)?;
keymap.insert(&mut storage, &b"key2".to_vec(), &foo2)?;
let mut x = keymap.iter(&storage)?;
let (len, _) = x.size_hint();
assert_eq!(len, 2);
assert_eq!(x.next().unwrap()?.1, foo1);
assert_eq!(x.next().unwrap()?.1, foo2);
Ok(())
}
Keyset
这种类似于哈希集合的存储结构允许用户存储类型化对象。允许遍历和分页值(尽管没有保证排序,尽管插入顺序被保留,直到开始删除对象)。此类结构的示例用例是如果您有一组白名单用户(您可能希望遍历)。
初始化
要将此存储对象作为静态常量导入和初始化到 state.rs
中,执行以下操作
use secret_toolkit::storage::{Keyset, KeysetBuilder};
# use secret_toolkit_storage::Keyset;
# use cosmwasm_std::Addr;
pub static WHITELIST: Keyset<Addr> = Keyset::new(b"whitelist");
❗ 将对象初始化为 const 而不是 static 也可以工作,但效率较低,因为变量无法缓存长度数据。
`add_suffix`和`KeysetBuilder`方法与Keymap的方法功能类似。
存储方法
以下是与Keyset交互使用的以下方法
.remove(storage, value)
返回StdResult<()>
.insert(storage, value)
如果迭代器被禁用,则返回StdResult<()>
,但如果迭代器启用,则返回StdResult<bool>
,这取决于值是否已存储(如果已存储则返回false)。- (仅当迭代器启用时)
.is_empty(storage)
返回StdResult<bool>
- (仅当迭代器启用时)
.get_len(storage)
返回StdResult<u32>
.contains(storage, value)
返回bool
- (仅当启用迭代器时)
.paging(storage, start_page, size)
返回StdResult<Vec<K>>
,其中K
是存储对象的类型。 - (仅当启用迭代器时)
.iter(storage)
返回StdResult<ValueIter<K, Ser>>
,其中ValueIter
是存储值的迭代器。
依赖项
~2.2–4MB
~82K SLoC