Skip to content

Commit

Permalink
Merge pull request #732 from CosmWasm/dump-state-multitest
Browse files Browse the repository at this point in the history
Dump state multitest
  • Loading branch information
maurolacy authored Jun 2, 2022
2 parents 6d20854 + 7f86a66 commit 10661dd
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 71 deletions.
8 changes: 7 additions & 1 deletion packages/multi-test/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use anyhow::Result as AnyResult;
use cosmwasm_std::testing::{mock_env, MockApi, MockStorage};
use cosmwasm_std::{
from_slice, to_binary, Addr, Api, Binary, BlockInfo, ContractResult, CustomQuery, Empty,
Querier, QuerierResult, QuerierWrapper, QueryRequest, Storage, SystemError, SystemResult,
Querier, QuerierResult, QuerierWrapper, QueryRequest, Record, Storage, SystemError,
SystemResult,
};
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
Expand Down Expand Up @@ -565,6 +566,11 @@ where
pub fn contract_data(&self, address: &Addr) -> AnyResult<ContractData> {
self.read_module(|router, _, storage| router.wasm.load_contract(storage, address))
}

/// This gets a raw state dump of all key-values held by a given contract
pub fn dump_wasm_raw(&self, address: &Addr) -> Vec<Record> {
self.read_module(|router, _, storage| router.wasm.dump_wasm_raw(storage, address))
}
}

impl<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT>
Expand Down
196 changes: 126 additions & 70 deletions packages/multi-test/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::ops::Deref;
use cosmwasm_std::{
to_binary, Addr, Api, Attribute, BankMsg, Binary, BlockInfo, Coin, ContractInfo,
ContractInfoResponse, CustomQuery, Deps, DepsMut, Env, Event, MessageInfo, Order, Querier,
QuerierWrapper, Reply, ReplyOn, Response, StdResult, Storage, SubMsg, SubMsgResponse,
QuerierWrapper, Record, Reply, ReplyOn, Response, StdResult, Storage, SubMsg, SubMsgResponse,
SubMsgResult, TransactionInfo, WasmMsg, WasmQuery,
};
use cosmwasm_storage::{prefixed, prefixed_read, PrefixedStorage, ReadonlyPrefixedStorage};
Expand Down Expand Up @@ -191,6 +191,80 @@ impl<ExecC, QueryC> WasmKeeper<ExecC, QueryC> {
.load(&prefixed_read(storage, NAMESPACE_WASM), address)
.map_err(Into::into)
}

pub fn dump_wasm_raw(&self, storage: &dyn Storage, address: &Addr) -> Vec<Record> {
let storage = self.contract_storage_readonly(storage, address);
storage.range(None, None, Order::Ascending).collect()
}

fn contract_namespace(&self, contract: &Addr) -> Vec<u8> {
let mut name = b"contract_data/".to_vec();
name.extend_from_slice(contract.as_bytes());
name
}

fn contract_storage<'a>(
&self,
storage: &'a mut dyn Storage,
address: &Addr,
) -> Box<dyn Storage + 'a> {
// We double-namespace this, once from global storage -> wasm_storage
// then from wasm_storage -> the contracts subspace
let namespace = self.contract_namespace(address);
let storage = PrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]);
Box::new(storage)
}

// fails RUNTIME if you try to write. please don't
fn contract_storage_readonly<'a>(
&self,
storage: &'a dyn Storage,
address: &Addr,
) -> Box<dyn Storage + 'a> {
// We double-namespace this, once from global storage -> wasm_storage
// then from wasm_storage -> the contracts subspace
let namespace = self.contract_namespace(address);
let storage = ReadonlyPrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]);
Box::new(storage)
}

fn verify_attributes(attributes: &[Attribute]) -> AnyResult<()> {
for attr in attributes {
let key = attr.key.trim();
let val = attr.value.trim();

if key.is_empty() {
bail!(Error::empty_attribute_key(val));
}

if val.is_empty() {
bail!(Error::empty_attribute_value(key));
}

if key.starts_with('_') {
bail!(Error::reserved_attribute_key(key));
}
}

Ok(())
}

fn verify_response<T>(response: Response<T>) -> AnyResult<Response<T>>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
Self::verify_attributes(&response.attributes)?;

for event in &response.events {
Self::verify_attributes(&event.attributes)?;
let ty = event.ty.trim();
if ty.len() < 2 {
bail!(Error::event_type_too_short(ty));
}
}

Ok(response)
}
}

impl<ExecC, QueryC> WasmKeeper<ExecC, QueryC>
Expand Down Expand Up @@ -782,75 +856,6 @@ where
// it is lowercase to be compatible with the MockApi implementation of cosmwasm-std >= 1.0.0-beta8
Addr::unchecked(format!("contract{}", count))
}

fn contract_namespace(&self, contract: &Addr) -> Vec<u8> {
let mut name = b"contract_data/".to_vec();
name.extend_from_slice(contract.as_bytes());
name
}

fn contract_storage<'a>(
&self,
storage: &'a mut dyn Storage,
address: &Addr,
) -> Box<dyn Storage + 'a> {
// We double-namespace this, once from global storage -> wasm_storage
// then from wasm_storage -> the contracts subspace
let namespace = self.contract_namespace(address);
let storage = PrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]);
Box::new(storage)
}

// fails RUNTIME if you try to write. please don't
fn contract_storage_readonly<'a>(
&self,
storage: &'a dyn Storage,
address: &Addr,
) -> Box<dyn Storage + 'a> {
// We double-namespace this, once from global storage -> wasm_storage
// then from wasm_storage -> the contracts subspace
let namespace = self.contract_namespace(address);
let storage = ReadonlyPrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]);
Box::new(storage)
}

fn verify_attributes(attributes: &[Attribute]) -> AnyResult<()> {
for attr in attributes {
let key = attr.key.trim();
let val = attr.value.trim();

if key.is_empty() {
bail!(Error::empty_attribute_key(val));
}

if val.is_empty() {
bail!(Error::empty_attribute_value(key));
}

if key.starts_with('_') {
bail!(Error::reserved_attribute_key(key));
}
}

Ok(())
}

fn verify_response<T>(response: Response<T>) -> AnyResult<Response<T>>
where
T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
Self::verify_attributes(&response.attributes)?;

for event in &response.events {
Self::verify_attributes(&event.attributes)?;
let ty = event.ty.trim();
if ty.len() < 2 {
bail!(Error::event_type_too_short(ty));
}
}

Ok(response)
}
}

// TODO: replace with code in utils
Expand Down Expand Up @@ -1050,6 +1055,57 @@ mod test {
assert_eq!(expected, from_slice(&info).unwrap());
}

#[test]
fn can_dump_raw_wasm_state() {
let api = MockApi::default();
let mut keeper = WasmKeeper::<Empty, Empty>::new();
let block = mock_env().block;
let code_id = keeper.store_code(payout::contract());

let mut wasm_storage = MockStorage::new();

let contract_addr = keeper
.register_contract(
&mut wasm_storage,
code_id,
Addr::unchecked("foobar"),
Addr::unchecked("admin"),
"label".to_owned(),
1000,
)
.unwrap();

// make a contract with state
let payout = coin(1500, "mlg");
let msg = payout::InstantiateMessage {
payout: payout.clone(),
};
keeper
.call_instantiate(
contract_addr.clone(),
&api,
&mut wasm_storage,
&mock_router(),
&block,
mock_info("foobar", &[]),
to_vec(&msg).unwrap(),
)
.unwrap();

// dump state
let state = keeper.dump_wasm_raw(&wasm_storage, &contract_addr);
assert_eq!(state.len(), 2);
// check contents
let (k, v) = &state[0];
assert_eq!(k.as_slice(), b"count");
let count: u32 = from_slice(v).unwrap();
assert_eq!(count, 1);
let (k, v) = &state[1];
assert_eq!(k.as_slice(), b"payout");
let stored_pay: payout::InstantiateMessage = from_slice(v).unwrap();
assert_eq!(stored_pay.payout, payout);
}

#[test]
fn contract_send_coins() {
let api = MockApi::default();
Expand Down

0 comments on commit 10661dd

Please sign in to comment.