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魔法豆

Download history 194/week @ 2024-04-01 73/week @ 2024-04-08 111/week @ 2024-04-15 87/week @ 2024-04-22 75/week @ 2024-04-29 71/week @ 2024-05-06 77/week @ 2024-05-13 204/week @ 2024-05-20 124/week @ 2024-05-27 89/week @ 2024-06-03 82/week @ 2024-06-10 107/week @ 2024-06-17 109/week @ 2024-06-24 13/week @ 2024-07-01 41/week @ 2024-07-08 62/week @ 2024-07-15

每月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");

读取/写入

读取/写入存储的方式是使用其方法。这些方法是 saveloadmay_loadremoveupdate。以下是在 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 方法,可以从列表的任意位置删除存储的对象,但这可能非常低效。

❗ 越来越远离尾部的存储对象删除变得越来越低效。我们建议您尽可能使用 poppush

这里也遵循了 Item 的相同约定,即

  1. AppendStore 必须知道存储对象的类型。以及可选的 serde。
  2. 每个方法都需要自己的对 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 的主要用户方法为 poppushget_lenset_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_backpop_frontpush_backpush_frontget_lenget_offset_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