Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have cw_storey use a mock CosmWasm contract for integration tests #92

Merged
merged 2 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions packages/cw-storey/tests/contract/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
pub mod msg;

use cosmwasm_std::{
to_json_binary, Deps, DepsMut, Empty, Env, MessageInfo, QueryResponse, Response, StdError,
StdResult,
};

use cw_storey::containers::{Item, Map};

const ITEM: Item<u32> = Item::new(0);
const MAP: Map<String, Item<u32>> = Map::new(1);

//#[entry_point]
pub fn instantiate(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: Empty,
) -> Result<Response, StdError> {
Ok(Response::default())
}

//#[entry_point]
pub fn execute(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: msg::ExecuteMsg,
) -> Result<Response, StdError> {
use msg::ExecuteMsg::*;

match msg {
SetItem { val } => execute::set_item(deps, val),
SetMapEntry { key, val } => execute::set_map_entry(deps, key, val),
}
}

//#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: msg::QueryMsg) -> StdResult<QueryResponse> {
use msg::QueryMsg::*;

match msg {
Item {} => to_json_binary(&query::get_item(deps)?),
MapEntry { key } => to_json_binary(&query::get_map_entry(deps, key)?),
MapEntries {} => to_json_binary(&query::get_map_entries(deps)?),
}
}

mod execute {
use super::*;

pub(crate) fn set_item(deps: DepsMut, val: u32) -> Result<Response, StdError> {
ITEM.access(deps.storage).set(&val)?;

Ok(Response::default())
}

pub(crate) fn set_map_entry(
deps: DepsMut,
key: String,
val: u32,
) -> Result<Response, StdError> {
MAP.access(deps.storage).entry_mut(&key).set(&val)?;

Ok(Response::default())
}
}

mod query {
use storey::containers::IterableAccessor as _;

use super::*;

pub(crate) fn get_item(deps: Deps) -> StdResult<Option<u32>> {
ITEM.access(deps.storage).get()
}

pub(crate) fn get_map_entry(deps: Deps, key: String) -> StdResult<Option<u32>> {
MAP.access(deps.storage).entry(&key).get()
}

pub(crate) fn get_map_entries(deps: Deps) -> StdResult<Vec<(String, u32)>> {
MAP.access(deps.storage)
.pairs()
.map(|res| res.map_err(|e| StdError::generic_err(e.to_string())))
.map(|res| res.map(|((k, ()), v)| (k, v)))
.collect()
}
}
10 changes: 10 additions & 0 deletions packages/cw-storey/tests/contract/msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub enum ExecuteMsg {
SetItem { val: u32 },
SetMapEntry { key: String, val: u32 },
}

pub enum QueryMsg {
Item {},
MapEntry { key: String },
MapEntries {},
}
121 changes: 83 additions & 38 deletions packages/cw-storey/tests/smoke_test.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,107 @@
use cw_storey::containers::Item;
mod contract;

use storey::containers::{IterableAccessor as _, Map};
use storey::storage::IntoStorage as _;
use cosmwasm_std::testing::{
message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage,
};
use cosmwasm_std::{from_json, Empty, OwnedDeps};

// The tests in this module are meant to briefly test the integration of `storey`
// with `cosmwasm_std::Storage` and MessagePack serialization.
//
// They're not meant to comprehensively test the storage abstractions provided by `storey`.
// That's already done in the `storey` crate itself.

// this mimicks how storage is accessed from CosmWasm smart contracts,
// where developers have access to `&dyn cosmwasm_std::Storage` or
// `&mut dyn cosmwasm_std::Storage`, but not the concrete type.
fn give_me_storage() -> Box<dyn cosmwasm_std::Storage> {
Box::new(cosmwasm_std::testing::MockStorage::new())
}
// That's already done in the `storey` crate itself. These are "smoke tests".

#[test]
fn smoke_test() {
let mut storage = give_me_storage();

let item1 = Item::<u64>::new(0);

item1.access(&mut *storage).set(&42).unwrap();
assert_eq!(item1.access(&mut *storage).get().unwrap(), Some(42));

let item2 = Item::<u64>::new(1);
assert_eq!(item2.access(&mut *storage).get().unwrap(), None);
fn item() {
let mut deps = setup();

assert_eq!((&mut *storage,).into_storage().0.get(&[0]), Some(vec![42]));
assert_eq!(None, get_item(&deps));
set_item(&mut deps, 42);
assert_eq!(Some(42), get_item(&deps));
}

#[test]
fn map() {
let mut storage = give_me_storage();
let mut deps = setup();

let map = Map::<String, Item<u32>>::new(0);
assert_eq!(None, get_map_entry(&deps, "foo".to_string()));
set_map_entry(&mut deps, "foo".to_string(), 42);
assert_eq!(Some(42), get_map_entry(&deps, "foo".to_string()));
assert_eq!(None, get_map_entry(&deps, "foobar".to_string()));
}

map.access(&mut *storage).entry_mut("foo").set(&42).unwrap();
#[test]
fn iteration() {
let mut deps = setup();

set_map_entry(&mut deps, "foo".to_string(), 42);
set_map_entry(&mut deps, "bar".to_string(), 43);
set_map_entry(&mut deps, "baz".to_string(), 44);

assert_eq!(
map.access(&mut *storage).entry("foo").get().unwrap(),
Some(42)
vec![
("bar".to_string(), 43),
("baz".to_string(), 44),
("foo".to_string(), 42),
],
get_map_entries(&deps)
);
}

#[test]
fn iteration() {
let mut storage = give_me_storage();
// The following code provides helper functions to test a mock CosmWasm contract.
// The mock contract itself can be found in the `contract` module.
//
// This kind of setup is common in CosmWasm repos. For example, see the core CosmWasm
// repo: https://github.com/CosmWasm/cosmwasm/tree/main/contracts

fn setup() -> OwnedDeps<MockStorage, MockApi, MockQuerier> {
let mut deps = mock_dependencies();
let creator = deps.api.addr_make("creator");
let msg = Empty {};
let info = message_info(&creator, &[]);
let res = contract::instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
assert_eq!(0, res.messages.len());
deps
}

let map = Map::<String, Item<u32>>::new(0);
#[track_caller]
fn set_item(deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>, val: u32) {
let caller = deps.api.addr_make("caller");
let msg = contract::msg::ExecuteMsg::SetItem { val };
contract::execute(deps.as_mut(), mock_env(), message_info(&caller, &[]), msg).unwrap();
}

map.access(&mut *storage).entry_mut("foo").set(&42).unwrap();
map.access(&mut *storage).entry_mut("bar").set(&43).unwrap();
#[track_caller]
fn set_map_entry(deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>, key: String, val: u32) {
let caller = deps.api.addr_make("caller");
let msg = contract::msg::ExecuteMsg::SetMapEntry { key, val };
contract::execute(deps.as_mut(), mock_env(), message_info(&caller, &[]), msg).unwrap();
}

#[track_caller]
fn get_item(deps: &OwnedDeps<MockStorage, MockApi, MockQuerier>) -> Option<u32> {
let res = contract::query(deps.as_ref(), mock_env(), contract::msg::QueryMsg::Item {}).unwrap();
from_json(&res).unwrap()
}

#[track_caller]
fn get_map_entry(deps: &OwnedDeps<MockStorage, MockApi, MockQuerier>, key: String) -> Option<u32> {
let res = contract::query(
deps.as_ref(),
mock_env(),
contract::msg::QueryMsg::MapEntry { key },
)
.unwrap();
from_json(&res).unwrap()
}

let access = map.access(&mut *storage);
let mut iter = access.keys();
assert_eq!(iter.next().unwrap().unwrap().0, "bar");
assert_eq!(iter.next().unwrap().unwrap().0, "foo");
assert!(iter.next().is_none());
#[track_caller]
fn get_map_entries(deps: &OwnedDeps<MockStorage, MockApi, MockQuerier>) -> Vec<(String, u32)> {
let res = contract::query(
deps.as_ref(),
mock_env(),
contract::msg::QueryMsg::MapEntries {},
)
.unwrap();
from_json(&res).unwrap()
}
8 changes: 0 additions & 8 deletions packages/storey-storage/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ pub trait Storage {
}
}

/// A trait for converting a type into one that implements [`Storage`].
///
/// This trait is meant to be implemented for a tuple of the intended type, as in
/// `impl IntoStorage<T> for (T,)`. This is to allow blanket implementations on foreign
/// types without stumbling into [E0210](https://stackoverflow.com/questions/63119000/why-am-i-required-to-cover-t-in-impl-foreigntraitlocaltype-for-t-e0210).
///
/// Implementing this trait for foreign types allows to use those foreign types directly
/// with functions like [`Item::access`](crate::Item::access).
pub trait IntoStorage<O>: Sized {
fn into_storage(self) -> O;
}
Expand Down
63 changes: 61 additions & 2 deletions packages/storey/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,65 @@ mod branch;

pub use branch::StorageBranch;
pub use storey_storage::{
IntoStorage, IterableStorage, RevIterableStorage, Storage, StorageBackend, StorageBackendMut,
StorageMut,
IterableStorage, RevIterableStorage, Storage, StorageBackend, StorageBackendMut, StorageMut,
};

/// A trait for converting a type into one that implements [`Storage`].
///
/// This trait is meant to be implemented for a tuple of the intended type, as in
/// `impl IntoStorage<T> for (T,)`. This is to allow blanket implementations on foreign
/// types without stumbling into [E0210](https://stackoverflow.com/questions/63119000/why-am-i-required-to-cover-t-in-impl-foreigntraitlocaltype-for-t-e0210).
///
/// Implementing this trait for foreign types allows to use those foreign types directly
/// with functions like [`Item::access`](crate::containers::Item::access).
///
/// # Example
///
/// This example should give you an idea of how to allow a foreign type to be used directly with functions
/// like [`Item::access`](crate::containers::Item::access).
///
/// Blanket implementations should also be possible!
///
/// ```
/// use storey::storage::{IntoStorage, StorageBackend};
///
/// mod foreign_crate {
/// // This is a foreign type that we want to use with `storey`. Note that due to orphan rules,
/// // we can't implement `StorageBackend` for this type.
/// pub struct ExtStorage;
///
/// impl ExtStorage {
/// pub fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
/// todo!()
/// }
///
/// pub fn has(&self, key: &[u8]) -> bool {
/// todo!()
/// }
/// }
/// }
///
/// use foreign_crate::ExtStorage;
///
/// // Our wrapper can be used as a storage backend. It delegates all calls to the foreign type.
/// struct MyStorage(ExtStorage);
///
/// impl StorageBackend for MyStorage {
/// fn get(&self, key: &[u8]) -> Option<Vec<u8>> {
/// self.0.get(key)
/// }
///
/// fn has(&self, key: &[u8]) -> bool {
/// self.0.has(key)
/// }
/// }
///
/// // Implementing `IntoStorage` like this makes it possible to use `ExtStorage` directly with
/// // functions like `Item::access`, without users having to wrap it in `MyStorage`.
/// impl IntoStorage<MyStorage> for (ExtStorage,) {
/// fn into_storage(self) -> MyStorage {
/// MyStorage(self.0)
/// }
/// }
/// ```
pub use storey_storage::IntoStorage;
Loading