use crate::addresses::{AddressGenerator, SimpleAddressGenerator};
use crate::app::{CosmosRouter, RouterQuerier};
use crate::checksums::{ChecksumGenerator, SimpleChecksumGenerator};
use crate::contracts::Contract;
use crate::error::{bail, AnyContext, AnyError, AnyResult, Error};
use crate::executor::AppResponse;
use crate::prefixed_storage::{prefixed, prefixed_read, PrefixedStorage, ReadonlyPrefixedStorage};
use crate::transactions::transactional;
use cosmwasm_std::testing::mock_wasmd_attr;
use cosmwasm_std::{
to_json_binary, Addr, Api, Attribute, BankMsg, Binary, BlockInfo, Checksum, Coin, ContractInfo,
ContractInfoResponse, CustomMsg, CustomQuery, Deps, DepsMut, Env, Event, MessageInfo, Order,
Querier, QuerierWrapper, Record, Reply, ReplyOn, Response, StdResult, Storage, SubMsg,
SubMsgResponse, SubMsgResult, TransactionInfo, WasmMsg, WasmQuery,
};
use cw_storage_plus::Map;
use prost::Message;
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::borrow::Borrow;
use std::collections::BTreeMap;
use std::fmt::Debug;
const CONTRACTS: Map<&Addr, ContractData> = Map::new("contracts");
const NAMESPACE_WASM: &[u8] = b"wasm";
const CONTRACT_ATTR: &str = "_contract_address";
#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct WasmSudo {
pub contract_addr: Addr,
pub message: Binary,
}
impl WasmSudo {
pub fn new<T: Serialize>(contract_addr: &Addr, msg: &T) -> StdResult<WasmSudo> {
Ok(WasmSudo {
contract_addr: contract_addr.clone(),
message: to_json_binary(msg)?,
})
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ContractData {
pub code_id: u64,
pub creator: Addr,
pub admin: Option<Addr>,
pub label: String,
pub created: u64,
}
struct CodeData {
creator: Addr,
checksum: Checksum,
source_id: usize,
}
pub trait Wasm<ExecC, QueryC> {
fn execute(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
sender: Addr,
msg: WasmMsg,
) -> AnyResult<AppResponse>;
fn query(
&self,
api: &dyn Api,
storage: &dyn Storage,
querier: &dyn Querier,
block: &BlockInfo,
request: WasmQuery,
) -> AnyResult<Binary>;
fn sudo(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
msg: WasmSudo,
) -> AnyResult<AppResponse>;
fn store_code(&mut self, creator: Addr, code: Box<dyn Contract<ExecC, QueryC>>) -> u64;
fn store_code_with_id(
&mut self,
creator: Addr,
code_id: u64,
code: Box<dyn Contract<ExecC, QueryC>>,
) -> AnyResult<u64>;
fn duplicate_code(&mut self, code_id: u64) -> AnyResult<u64>;
fn contract_data(&self, storage: &dyn Storage, address: &Addr) -> AnyResult<ContractData>;
fn dump_wasm_raw(&self, storage: &dyn Storage, address: &Addr) -> Vec<Record>;
}
pub struct WasmKeeper<ExecC, QueryC> {
code_base: Vec<Box<dyn Contract<ExecC, QueryC>>>,
code_data: BTreeMap<u64, CodeData>,
address_generator: Box<dyn AddressGenerator>,
checksum_generator: Box<dyn ChecksumGenerator>,
_p: std::marker::PhantomData<QueryC>,
}
impl<ExecC, QueryC> Default for WasmKeeper<ExecC, QueryC> {
fn default() -> Self {
Self {
code_base: Vec::default(),
code_data: BTreeMap::default(),
address_generator: Box::new(SimpleAddressGenerator),
checksum_generator: Box::new(SimpleChecksumGenerator),
_p: std::marker::PhantomData,
}
}
}
impl<ExecC, QueryC> Wasm<ExecC, QueryC> for WasmKeeper<ExecC, QueryC>
where
ExecC: CustomMsg + DeserializeOwned + 'static,
QueryC: CustomQuery + DeserializeOwned + 'static,
{
fn execute(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
sender: Addr,
msg: WasmMsg,
) -> AnyResult<AppResponse> {
self.execute_wasm(api, storage, router, block, sender.clone(), msg.clone())
.context(format!(
"Error executing WasmMsg:\n sender: {}\n {:?}",
sender, msg
))
}
fn query(
&self,
api: &dyn Api,
storage: &dyn Storage,
querier: &dyn Querier,
block: &BlockInfo,
request: WasmQuery,
) -> AnyResult<Binary> {
match request {
WasmQuery::Smart { contract_addr, msg } => {
let addr = api.addr_validate(&contract_addr)?;
self.query_smart(addr, api, storage, querier, block, msg.into())
}
WasmQuery::Raw { contract_addr, key } => {
let addr = api.addr_validate(&contract_addr)?;
Ok(self.query_raw(addr, storage, &key))
}
WasmQuery::ContractInfo { contract_addr } => {
let addr = api.addr_validate(&contract_addr)?;
let contract = self.contract_data(storage, &addr)?;
let res = ContractInfoResponse::new(
contract.code_id,
contract.creator,
contract.admin,
false,
None,
);
to_json_binary(&res).map_err(Into::into)
}
WasmQuery::CodeInfo { code_id } => {
let code_data = self.code_data(code_id)?;
let res = cosmwasm_std::CodeInfoResponse::new(
code_id,
code_data.creator.clone(),
code_data.checksum,
);
to_json_binary(&res).map_err(Into::into)
}
_ => unimplemented!("{}", Error::unsupported_wasm_query(request)),
}
}
fn sudo(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
msg: WasmSudo,
) -> AnyResult<AppResponse> {
let custom_event = Event::new("sudo").add_attribute(CONTRACT_ATTR, &msg.contract_addr);
let res = self.call_sudo(
msg.contract_addr.clone(),
api,
storage,
router,
block,
msg.message.to_vec(),
)?;
let (res, msgs) = self.build_app_response(&msg.contract_addr, custom_event, res);
self.process_response(api, router, storage, block, msg.contract_addr, res, msgs)
}
fn store_code(&mut self, creator: Addr, code: Box<dyn Contract<ExecC, QueryC>>) -> u64 {
let code_id = self
.next_code_id()
.unwrap_or_else(|| panic!("{}", Error::NoMoreCodeIdAvailable));
self.save_code(code_id, creator, code)
}
fn store_code_with_id(
&mut self,
creator: Addr,
code_id: u64,
code: Box<dyn Contract<ExecC, QueryC>>,
) -> AnyResult<u64> {
if self.code_data.contains_key(&code_id) {
bail!(Error::duplicated_code_id(code_id));
} else if code_id == 0 {
bail!(Error::invalid_code_id());
}
Ok(self.save_code(code_id, creator, code))
}
fn duplicate_code(&mut self, code_id: u64) -> AnyResult<u64> {
let code_data = self.code_data(code_id)?;
let new_code_id = self
.next_code_id()
.ok_or_else(Error::no_more_code_id_available)?;
self.code_data.insert(
new_code_id,
CodeData {
creator: code_data.creator.clone(),
checksum: code_data.checksum,
source_id: code_data.source_id,
},
);
Ok(new_code_id)
}
fn contract_data(&self, storage: &dyn Storage, address: &Addr) -> AnyResult<ContractData> {
CONTRACTS
.load(&prefixed_read(storage, NAMESPACE_WASM), address)
.map_err(Into::into)
}
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()
}
}
impl<ExecC, QueryC> WasmKeeper<ExecC, QueryC> {
pub fn contract_code(&self, code_id: u64) -> AnyResult<&dyn Contract<ExecC, QueryC>> {
let code_data = self.code_data(code_id)?;
Ok(self.code_base[code_data.source_id].borrow())
}
fn code_data(&self, code_id: u64) -> AnyResult<&CodeData> {
if code_id < 1 {
bail!(Error::invalid_code_id());
}
Ok(self
.code_data
.get(&code_id)
.ok_or_else(|| Error::unregistered_code_id(code_id))?)
}
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> {
let namespace = self.contract_namespace(address);
let storage = PrefixedStorage::multilevel(storage, &[NAMESPACE_WASM, &namespace]);
Box::new(storage)
}
fn contract_storage_readonly<'a>(
&self,
storage: &'a dyn Storage,
address: &Addr,
) -> Box<dyn Storage + 'a> {
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: CustomMsg,
{
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)
}
fn save_code(
&mut self,
code_id: u64,
creator: Addr,
code: Box<dyn Contract<ExecC, QueryC>>,
) -> u64 {
let source_id = self.code_base.len();
let checksum = self.checksum_generator.checksum(&creator, code_id);
self.code_base.push(code);
self.code_data.insert(
code_id,
CodeData {
creator,
checksum,
source_id,
},
);
code_id
}
fn next_code_id(&self) -> Option<u64> {
self.code_data.keys().last().unwrap_or(&0u64).checked_add(1)
}
}
impl<ExecC, QueryC> WasmKeeper<ExecC, QueryC>
where
ExecC: CustomMsg + DeserializeOwned + 'static,
QueryC: CustomQuery + DeserializeOwned + 'static,
{
pub fn new() -> Self {
Self::default()
}
pub fn with_address_generator(
mut self,
address_generator: impl AddressGenerator + 'static,
) -> Self {
self.address_generator = Box::new(address_generator);
self
}
pub fn with_checksum_generator(
mut self,
checksum_generator: impl ChecksumGenerator + 'static,
) -> Self {
self.checksum_generator = Box::new(checksum_generator);
self
}
pub fn query_smart(
&self,
address: Addr,
api: &dyn Api,
storage: &dyn Storage,
querier: &dyn Querier,
block: &BlockInfo,
msg: Vec<u8>,
) -> AnyResult<Binary> {
self.with_storage_readonly(
api,
storage,
querier,
block,
address,
|handler, deps, env| handler.query(deps, env, msg),
)
}
pub fn query_raw(&self, address: Addr, storage: &dyn Storage, key: &[u8]) -> Binary {
let storage = self.contract_storage_readonly(storage, &address);
let data = storage.get(key).unwrap_or_default();
data.into()
}
fn send<T>(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
sender: T,
recipient: String,
amount: &[Coin],
) -> AnyResult<AppResponse>
where
T: Into<Addr>,
{
if !amount.is_empty() {
let msg: cosmwasm_std::CosmosMsg<ExecC> = BankMsg::Send {
to_address: recipient,
amount: amount.to_vec(),
}
.into();
let res = router.execute(api, storage, block, sender.into(), msg)?;
Ok(res)
} else {
Ok(AppResponse::default())
}
}
fn update_admin(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
sender: Addr,
contract_addr: &str,
new_admin: Option<String>,
) -> AnyResult<AppResponse> {
let contract_addr = api.addr_validate(contract_addr)?;
let admin = new_admin.map(|a| api.addr_validate(&a)).transpose()?;
let mut data = self.contract_data(storage, &contract_addr)?;
if data.admin != Some(sender) {
bail!("Only admin can update the contract admin: {:?}", data.admin);
}
data.admin = admin;
self.save_contract(storage, &contract_addr, &data)?;
Ok(AppResponse {
data: None,
events: vec![],
})
}
fn execute_wasm(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
sender: Addr,
msg: WasmMsg,
) -> AnyResult<AppResponse> {
match msg {
WasmMsg::Execute {
contract_addr,
msg,
funds,
} => {
let contract_addr = api.addr_validate(&contract_addr)?;
self.send(
api,
storage,
router,
block,
sender.clone(),
contract_addr.clone().into(),
&funds,
)?;
let info = MessageInfo { sender, funds };
let res = self.call_execute(
api,
storage,
contract_addr.clone(),
router,
block,
info,
msg.to_vec(),
)?;
let custom_event =
Event::new("execute").add_attribute(CONTRACT_ATTR, &contract_addr);
let (res, msgs) = self.build_app_response(&contract_addr, custom_event, res);
let mut res =
self.process_response(api, router, storage, block, contract_addr, res, msgs)?;
res.data = execute_response(res.data);
Ok(res)
}
WasmMsg::Instantiate {
admin,
code_id,
msg,
funds,
label,
} => self.process_wasm_msg_instantiate(
api, storage, router, block, sender, admin, code_id, msg, funds, label, None,
),
WasmMsg::Instantiate2 {
admin,
code_id,
msg,
funds,
label,
salt,
} => self.process_wasm_msg_instantiate(
api,
storage,
router,
block,
sender,
admin,
code_id,
msg,
funds,
label,
Some(salt),
),
WasmMsg::Migrate {
contract_addr,
new_code_id,
msg,
} => {
let contract_addr = api.addr_validate(&contract_addr)?;
if new_code_id as usize > self.code_data.len() {
bail!("Cannot migrate contract to unregistered code id");
}
let mut data = self.contract_data(storage, &contract_addr)?;
if data.admin != Some(sender) {
bail!("Only admin can migrate contract: {:?}", data.admin);
}
data.code_id = new_code_id;
self.save_contract(storage, &contract_addr, &data)?;
let res = self.call_migrate(
contract_addr.clone(),
api,
storage,
router,
block,
msg.to_vec(),
)?;
let custom_event = Event::new("migrate")
.add_attribute(CONTRACT_ATTR, &contract_addr)
.add_attribute("code_id", new_code_id.to_string());
let (res, msgs) = self.build_app_response(&contract_addr, custom_event, res);
let mut res =
self.process_response(api, router, storage, block, contract_addr, res, msgs)?;
res.data = execute_response(res.data);
Ok(res)
}
WasmMsg::UpdateAdmin {
contract_addr,
admin,
} => self.update_admin(api, storage, sender, &contract_addr, Some(admin)),
WasmMsg::ClearAdmin { contract_addr } => {
self.update_admin(api, storage, sender, &contract_addr, None)
}
_ => unimplemented!("{}", Error::unsupported_wasm_message(msg)),
}
}
fn process_wasm_msg_instantiate(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
sender: Addr,
admin: Option<String>,
code_id: u64,
msg: Binary,
funds: Vec<Coin>,
label: String,
salt: Option<Binary>,
) -> AnyResult<AppResponse> {
if label.is_empty() {
bail!("Label is required on all contracts");
}
let contract_addr = self.register_contract(
api,
storage,
code_id,
sender.clone(),
admin.map(Addr::unchecked),
label,
block.height,
salt,
)?;
self.send(
api,
storage,
router,
block,
sender.clone(),
contract_addr.clone().into(),
&funds,
)?;
let info = MessageInfo { sender, funds };
let res = self.call_instantiate(
contract_addr.clone(),
api,
storage,
router,
block,
info,
msg.to_vec(),
)?;
let custom_event = Event::new("instantiate")
.add_attribute(CONTRACT_ATTR, &contract_addr)
.add_attribute("code_id", code_id.to_string());
let (res, msgs) = self.build_app_response(&contract_addr, custom_event, res);
let mut res = self.process_response(
api,
router,
storage,
block,
contract_addr.clone(),
res,
msgs,
)?;
res.data = Some(instantiate_response(res.data, &contract_addr));
Ok(res)
}
fn execute_submsg(
&self,
api: &dyn Api,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
storage: &mut dyn Storage,
block: &BlockInfo,
contract: Addr,
msg: SubMsg<ExecC>,
) -> AnyResult<AppResponse> {
let SubMsg {
msg, id, reply_on, ..
} = msg;
let res = transactional(storage, |write_cache, _| {
router.execute(api, write_cache, block, contract.clone(), msg)
});
if let Ok(mut r) = res {
if matches!(reply_on, ReplyOn::Always | ReplyOn::Success) {
let reply = Reply {
id,
payload: Default::default(),
gas_used: 0,
result: SubMsgResult::Ok(
#[allow(deprecated)]
SubMsgResponse {
events: r.events.clone(),
data: r.data,
msg_responses: vec![],
},
),
};
let reply_res = self.reply(api, router, storage, block, contract, reply)?;
r.data = reply_res.data;
r.events.extend_from_slice(&reply_res.events);
} else {
r.data = None;
}
Ok(r)
} else if let Err(e) = res {
if matches!(reply_on, ReplyOn::Always | ReplyOn::Error) {
let reply = Reply {
id,
payload: Default::default(),
gas_used: 0,
result: SubMsgResult::Err(format!("{:?}", e)),
};
self.reply(api, router, storage, block, contract, reply)
} else {
Err(e)
}
} else {
res
}
}
fn reply(
&self,
api: &dyn Api,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
storage: &mut dyn Storage,
block: &BlockInfo,
contract: Addr,
reply: Reply,
) -> AnyResult<AppResponse> {
let ok_attr = if reply.result.is_ok() {
"handle_success"
} else {
"handle_failure"
};
let custom_event = Event::new("reply")
.add_attribute(CONTRACT_ATTR, &contract)
.add_attribute("mode", ok_attr);
let res = self.call_reply(contract.clone(), api, storage, router, block, reply)?;
let (res, msgs) = self.build_app_response(&contract, custom_event, res);
self.process_response(api, router, storage, block, contract, res, msgs)
}
fn build_app_response(
&self,
contract: &Addr,
custom_event: Event, response: Response<ExecC>,
) -> (AppResponse, Vec<SubMsg<ExecC>>) {
let Response {
messages,
attributes,
events,
data,
..
} = response;
let mut app_events = Vec::with_capacity(2 + events.len());
app_events.push(custom_event);
if !attributes.is_empty() {
let wasm_event = Event::new("wasm")
.add_attribute(CONTRACT_ATTR, contract)
.add_attributes(attributes);
app_events.push(wasm_event);
}
let wasm_events = events.into_iter().map(|mut ev| {
ev.ty = format!("wasm-{}", ev.ty);
ev.attributes
.insert(0, mock_wasmd_attr(CONTRACT_ATTR, contract));
ev
});
app_events.extend(wasm_events);
let app = AppResponse {
events: app_events,
data,
};
(app, messages)
}
fn process_response(
&self,
api: &dyn Api,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
storage: &mut dyn Storage,
block: &BlockInfo,
contract: Addr,
response: AppResponse,
messages: Vec<SubMsg<ExecC>>,
) -> AnyResult<AppResponse> {
let AppResponse { mut events, data } = response;
let data = messages.into_iter().try_fold(data, |data, resend| {
let sub_res =
self.execute_submsg(api, router, storage, block, contract.clone(), resend)?;
events.extend_from_slice(&sub_res.events);
Ok::<_, AnyError>(sub_res.data.or(data))
})?;
Ok(AppResponse { events, data })
}
pub fn register_contract(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
code_id: u64,
creator: Addr,
admin: impl Into<Option<Addr>>,
label: String,
created: u64,
salt: impl Into<Option<Binary>>,
) -> AnyResult<Addr> {
if code_id as usize > self.code_data.len() {
bail!("Cannot init contract with unregistered code id");
}
let instance_id = self.instance_count(storage) as u64;
let addr = if let Some(salt_binary) = salt.into() {
let code_data = self.code_data(code_id)?;
let canonical_addr = &api.addr_canonicalize(creator.as_ref())?;
self.address_generator.predictable_contract_address(
api,
storage,
code_id,
instance_id,
code_data.checksum.as_slice(),
canonical_addr,
salt_binary.as_slice(),
)?
} else {
self.address_generator
.contract_address(api, storage, code_id, instance_id)?
};
if self.contract_data(storage, &addr).is_ok() {
bail!(Error::duplicated_contract_address(addr));
}
let info = ContractData {
code_id,
creator,
admin: admin.into(),
label,
created,
};
self.save_contract(storage, &addr, &info)?;
Ok(addr)
}
pub fn call_execute(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
address: Addr,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
info: MessageInfo,
msg: Vec<u8>,
) -> AnyResult<Response<ExecC>> {
Self::verify_response(self.with_storage(
api,
storage,
router,
block,
address,
|contract, deps, env| contract.execute(deps, env, info, msg),
)?)
}
pub fn call_instantiate(
&self,
address: Addr,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
info: MessageInfo,
msg: Vec<u8>,
) -> AnyResult<Response<ExecC>> {
Self::verify_response(self.with_storage(
api,
storage,
router,
block,
address,
|contract, deps, env| contract.instantiate(deps, env, info, msg),
)?)
}
pub fn call_reply(
&self,
address: Addr,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
reply: Reply,
) -> AnyResult<Response<ExecC>> {
Self::verify_response(self.with_storage(
api,
storage,
router,
block,
address,
|contract, deps, env| contract.reply(deps, env, reply),
)?)
}
pub fn call_sudo(
&self,
address: Addr,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
msg: Vec<u8>,
) -> AnyResult<Response<ExecC>> {
Self::verify_response(self.with_storage(
api,
storage,
router,
block,
address,
|contract, deps, env| contract.sudo(deps, env, msg),
)?)
}
pub fn call_migrate(
&self,
address: Addr,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
msg: Vec<u8>,
) -> AnyResult<Response<ExecC>> {
Self::verify_response(self.with_storage(
api,
storage,
router,
block,
address,
|contract, deps, env| contract.migrate(deps, env, msg),
)?)
}
fn get_env<T: Into<Addr>>(&self, address: T, block: &BlockInfo) -> Env {
Env {
block: block.clone(),
contract: ContractInfo {
address: address.into(),
},
transaction: Some(TransactionInfo { index: 0 }),
}
}
fn with_storage_readonly<F, T>(
&self,
api: &dyn Api,
storage: &dyn Storage,
querier: &dyn Querier,
block: &BlockInfo,
address: Addr,
action: F,
) -> AnyResult<T>
where
F: FnOnce(&dyn Contract<ExecC, QueryC>, Deps<QueryC>, Env) -> AnyResult<T>,
{
let contract = self.contract_data(storage, &address)?;
let handler = self.contract_code(contract.code_id)?;
let storage = self.contract_storage_readonly(storage, &address);
let env = self.get_env(address, block);
let deps = Deps {
storage: storage.as_ref(),
api,
querier: QuerierWrapper::new(querier),
};
action(handler, deps, env)
}
fn with_storage<F, T>(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
address: Addr,
action: F,
) -> AnyResult<T>
where
F: FnOnce(&dyn Contract<ExecC, QueryC>, DepsMut<QueryC>, Env) -> AnyResult<T>,
ExecC: DeserializeOwned,
{
let contract = self.contract_data(storage, &address)?;
let handler = self.contract_code(contract.code_id)?;
transactional(storage, |write_cache, read_store| {
let mut contract_storage = self.contract_storage(write_cache, &address);
let querier = RouterQuerier::new(router, api, read_store, block);
let env = self.get_env(address, block);
let deps = DepsMut {
storage: contract_storage.as_mut(),
api,
querier: QuerierWrapper::new(&querier),
};
action(handler, deps, env)
})
}
pub fn save_contract(
&self,
storage: &mut dyn Storage,
address: &Addr,
contract: &ContractData,
) -> AnyResult<()> {
CONTRACTS
.save(&mut prefixed(storage, NAMESPACE_WASM), address, contract)
.map_err(Into::into)
}
fn instance_count(&self, storage: &dyn Storage) -> usize {
CONTRACTS
.range_raw(
&prefixed_read(storage, NAMESPACE_WASM),
None,
None,
Order::Ascending,
)
.count()
}
}
#[derive(Clone, PartialEq, Message)]
struct InstantiateResponse {
#[prost(string, tag = "1")]
pub address: String,
#[prost(bytes, tag = "2")]
pub data: Vec<u8>,
}
fn instantiate_response(data: Option<Binary>, contact_address: &Addr) -> Binary {
let data = data.unwrap_or_default().to_vec();
let init_data = InstantiateResponse {
address: contact_address.into(),
data,
};
let mut new_data = Vec::<u8>::with_capacity(init_data.encoded_len());
init_data.encode(&mut new_data).unwrap();
new_data.into()
}
#[derive(Clone, PartialEq, Message)]
struct ExecuteResponse {
#[prost(bytes, tag = "1")]
pub data: Vec<u8>,
}
fn execute_response(data: Option<Binary>) -> Option<Binary> {
data.map(|d| {
let exec_data = ExecuteResponse { data: d.to_vec() };
let mut new_data = Vec::<u8>::with_capacity(exec_data.encoded_len());
exec_data.encode(&mut new_data).unwrap();
new_data.into()
})
}
#[cfg(test)]
mod test {
use super::*;
use crate::app::Router;
use crate::bank::BankKeeper;
use crate::module::FailingModule;
use crate::staking::{DistributionKeeper, StakeKeeper};
use crate::stargate::StargateFailingModule;
use crate::test_helpers::{caller, error, payout};
use crate::transactions::StorageTransaction;
use crate::{GovFailingModule, IbcFailingModule};
use cosmwasm_std::testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage};
use cosmwasm_std::{
coin, from_json, to_json_vec, CanonicalAddr, CodeInfoResponse, CosmosMsg, Empty, HexBinary,
StdError,
};
type BasicRouter<ExecC = Empty, QueryC = Empty> = Router<
BankKeeper,
FailingModule<ExecC, QueryC, Empty>,
WasmKeeper<ExecC, QueryC>,
StakeKeeper,
DistributionKeeper,
IbcFailingModule,
GovFailingModule,
StargateFailingModule,
>;
fn wasm_keeper() -> WasmKeeper<Empty, Empty> {
WasmKeeper::new()
}
fn mock_router() -> BasicRouter {
Router {
wasm: WasmKeeper::new(),
bank: BankKeeper::new(),
custom: FailingModule::new(),
staking: StakeKeeper::new(),
distribution: DistributionKeeper::new(),
ibc: IbcFailingModule::new(),
gov: GovFailingModule::new(),
stargate: StargateFailingModule::new(),
}
}
#[test]
fn register_contract() {
let api = MockApi::default();
let creator_addr = api.addr_make("creator");
let user_addr = api.addr_make("foobar");
let admin_addr = api.addr_make("admin");
let unregistered_addr = api.addr_make("unregistered");
let mut wasm_storage = MockStorage::new();
let mut wasm_keeper = wasm_keeper();
let block = mock_env().block;
let code_id = wasm_keeper.store_code(creator_addr, error::contract(false));
transactional(&mut wasm_storage, |cache, _| {
wasm_keeper.register_contract(
&api,
cache,
code_id + 1,
user_addr.clone(),
admin_addr.clone(),
"label".to_owned(),
1000,
None,
)
})
.unwrap_err();
let contract_addr = transactional(&mut wasm_storage, |cache, _| {
wasm_keeper.register_contract(
&api,
cache,
code_id,
user_addr.clone(),
admin_addr.clone(),
"label".to_owned(),
1000,
None,
)
})
.unwrap();
let contract_data = wasm_keeper
.contract_data(&wasm_storage, &contract_addr)
.unwrap();
assert_eq!(
contract_data,
ContractData {
code_id,
creator: user_addr.clone(),
admin: admin_addr.into(),
label: "label".to_owned(),
created: 1000,
}
);
let err = transactional(&mut wasm_storage, |cache, _| {
let info = mock_info(user_addr.as_str(), &[]);
wasm_keeper.call_instantiate(
contract_addr.clone(),
&api,
cache,
&mock_router(),
&block,
info,
b"{}".to_vec(),
)
})
.unwrap_err();
assert_eq!(
StdError::generic_err("Init failed"),
err.downcast().unwrap()
);
let err = transactional(&mut wasm_storage, |cache, _| {
let info = mock_info(user_addr.as_str(), &[]);
wasm_keeper.call_instantiate(
unregistered_addr,
&api,
cache,
&mock_router(),
&block,
info,
b"{}".to_vec(),
)
})
.unwrap_err();
assert!(matches!(err.downcast().unwrap(), StdError::NotFound { .. }));
}
#[test]
fn query_contract_info() {
let api = MockApi::default();
let creator_addr = api.addr_make("creator");
let admin_addr = api.addr_make("admin");
let mut wasm_storage = MockStorage::new();
let mut wasm_keeper = wasm_keeper();
let block = mock_env().block;
let code_id = wasm_keeper.store_code(creator_addr.clone(), payout::contract());
assert_eq!(1, code_id);
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut wasm_storage,
code_id,
creator_addr.clone(),
admin_addr.clone(),
"label".to_owned(),
1000,
None,
)
.unwrap();
let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
let query = WasmQuery::ContractInfo {
contract_addr: contract_addr.into(),
};
let contract_info = wasm_keeper
.query(&api, &wasm_storage, &querier, &block, query)
.unwrap();
let actual: ContractInfoResponse = from_json(contract_info).unwrap();
let expected =
ContractInfoResponse::new(code_id, creator_addr, admin_addr.into(), false, None);
assert_eq!(expected, actual);
}
#[test]
fn query_code_info() {
let api = MockApi::default();
let wasm_storage = MockStorage::new();
let mut wasm_keeper = wasm_keeper();
let block = mock_env().block;
let creator_addr = api.addr_make("creator");
let code_id = wasm_keeper.store_code(creator_addr.clone(), payout::contract());
let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
let query = WasmQuery::CodeInfo { code_id };
let code_info = wasm_keeper
.query(&api, &wasm_storage, &querier, &block, query)
.unwrap();
let actual: CodeInfoResponse = from_json(code_info).unwrap();
assert_eq!(code_id, actual.code_id);
assert_eq!(creator_addr.as_str(), actual.creator.as_str());
assert_eq!(32, actual.checksum.as_slice().len());
}
#[test]
fn different_contracts_must_have_different_checksum() {
let api = MockApi::default();
let creator_addr = api.addr_make("creator");
let wasm_storage = MockStorage::new();
let mut wasm_keeper = wasm_keeper();
let block = mock_env().block;
let code_id_payout = wasm_keeper.store_code(creator_addr.clone(), payout::contract());
let code_id_caller = wasm_keeper.store_code(creator_addr, caller::contract());
let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
let query_payout = WasmQuery::CodeInfo {
code_id: code_id_payout,
};
let query_caller = WasmQuery::CodeInfo {
code_id: code_id_caller,
};
let code_info_payout = wasm_keeper
.query(&api, &wasm_storage, &querier, &block, query_payout)
.unwrap();
let code_info_caller = wasm_keeper
.query(&api, &wasm_storage, &querier, &block, query_caller)
.unwrap();
let info_payout: CodeInfoResponse = from_json(code_info_payout).unwrap();
let info_caller: CodeInfoResponse = from_json(code_info_caller).unwrap();
assert_eq!(code_id_payout, info_payout.code_id);
assert_eq!(code_id_caller, info_caller.code_id);
assert_ne!(info_caller.code_id, info_payout.code_id);
assert_eq!(info_caller.creator, info_payout.creator);
assert_ne!(info_caller.checksum, info_payout.checksum);
}
#[test]
fn querying_invalid_code_info_must_fail() {
let api = MockApi::default();
let wasm_storage = MockStorage::new();
let wasm_keeper = wasm_keeper();
let block = mock_env().block;
let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
let query = WasmQuery::CodeInfo { code_id: 100 };
wasm_keeper
.query(&api, &wasm_storage, &querier, &block, query)
.unwrap_err();
}
#[test]
fn can_dump_raw_wasm_state() {
let api = MockApi::default();
let creator_addr = api.addr_make("creator");
let admin_addr = api.addr_make("admin");
let user_addr = api.addr_make("foobar");
let mut wasm_keeper = wasm_keeper();
let block = mock_env().block;
let code_id = wasm_keeper.store_code(creator_addr, payout::contract());
let mut wasm_storage = MockStorage::new();
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut wasm_storage,
code_id,
user_addr,
admin_addr,
"label".to_owned(),
1000,
None,
)
.unwrap();
let payout = coin(1500, "mlg");
let msg = payout::InstantiateMessage {
payout: payout.clone(),
};
wasm_keeper
.call_instantiate(
contract_addr.clone(),
&api,
&mut wasm_storage,
&mock_router(),
&block,
mock_info("foobar", &[]),
to_json_vec(&msg).unwrap(),
)
.unwrap();
let state = wasm_keeper.dump_wasm_raw(&wasm_storage, &contract_addr);
assert_eq!(state.len(), 2);
let (k, v) = &state[0];
assert_eq!(k.as_slice(), b"count");
let count: u32 = from_json(v).unwrap();
assert_eq!(count, 1);
let (k, v) = &state[1];
assert_eq!(k.as_slice(), b"payout");
let stored_pay: payout::InstantiateMessage = from_json(v).unwrap();
assert_eq!(stored_pay.payout, payout);
}
#[test]
fn contract_send_coins() {
let api = MockApi::default();
let creator_addr = api.addr_make("creator");
let user_addr = api.addr_make("foobar");
let mut wasm_keeper = wasm_keeper();
let block = mock_env().block;
let code_id = wasm_keeper.store_code(creator_addr, payout::contract());
let mut wasm_storage = MockStorage::new();
let mut cache = StorageTransaction::new(&wasm_storage);
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut cache,
code_id,
user_addr.clone(),
None,
"label".to_owned(),
1000,
None,
)
.unwrap();
let payout = coin(100, "TGD");
let info = mock_info(user_addr.as_str(), &[]);
let init_msg = to_json_vec(&payout::InstantiateMessage {
payout: payout.clone(),
})
.unwrap();
let res = wasm_keeper
.call_instantiate(
contract_addr.clone(),
&api,
&mut cache,
&mock_router(),
&block,
info,
init_msg,
)
.unwrap();
assert_eq!(0, res.messages.len());
let info = mock_info(user_addr.as_str(), &[]);
let res = wasm_keeper
.call_execute(
&api,
&mut cache,
contract_addr.clone(),
&mock_router(),
&block,
info,
b"{}".to_vec(),
)
.unwrap();
assert_eq!(1, res.messages.len());
match &res.messages[0].msg {
CosmosMsg::Bank(BankMsg::Send { to_address, amount }) => {
assert_eq!(to_address.as_str(), user_addr.as_str());
assert_eq!(amount.as_slice(), &[payout.clone()]);
}
m => panic!("Unexpected message {:?}", m),
}
cache.prepare().commit(&mut wasm_storage);
let query = to_json_vec(&payout::QueryMsg::Payout {}).unwrap();
let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
let data = wasm_keeper
.query_smart(contract_addr, &api, &wasm_storage, &querier, &block, query)
.unwrap();
let res: payout::InstantiateMessage = from_json(data).unwrap();
assert_eq!(res.payout, payout);
}
fn assert_payout(
router: &WasmKeeper<Empty, Empty>,
storage: &mut dyn Storage,
contract_addr: &Addr,
payout: &Coin,
) {
let api = MockApi::default();
let info = mock_info("silly", &[]);
let res = router
.call_execute(
&api,
storage,
contract_addr.clone(),
&mock_router(),
&mock_env().block,
info,
b"{}".to_vec(),
)
.unwrap();
assert_eq!(1, res.messages.len());
match &res.messages[0].msg {
CosmosMsg::Bank(BankMsg::Send { to_address, amount }) => {
assert_eq!(to_address.as_str(), "silly");
assert_eq!(amount.as_slice(), &[payout.clone()]);
}
m => panic!("Unexpected message {:?}", m),
}
}
fn assert_no_contract(storage: &dyn Storage, contract_addr: &Addr) {
let contract = CONTRACTS.may_load(storage, contract_addr).unwrap();
assert!(contract.is_none(), "{:?}", contract_addr);
}
#[test]
fn multi_level_wasm_cache() {
let api = MockApi::default();
let creator_addr = api.addr_make("creator");
let user_addr = api.addr_make("foobar");
let mut wasm_keeper = wasm_keeper();
let block = mock_env().block;
let code_id = wasm_keeper.store_code(creator_addr, payout::contract());
let mut wasm_storage = MockStorage::new();
let payout1 = coin(100, "TGD");
let contract1 = transactional(&mut wasm_storage, |cache, _| {
let contract = wasm_keeper
.register_contract(
&api,
cache,
code_id,
user_addr.clone(),
None,
"".to_string(),
1000,
None,
)
.unwrap();
let info = mock_info(user_addr.as_str(), &[]);
let init_msg = to_json_vec(&payout::InstantiateMessage {
payout: payout1.clone(),
})
.unwrap();
wasm_keeper
.call_instantiate(
contract.clone(),
&api,
cache,
&mock_router(),
&block,
info,
init_msg,
)
.unwrap();
Ok(contract)
})
.unwrap();
let payout2 = coin(50, "BTC");
let payout3 = coin(1234, "ATOM");
let (contract2, contract3) = transactional(&mut wasm_storage, |cache, wasm_reader| {
assert_payout(&wasm_keeper, cache, &contract1, &payout1);
let contract2 = wasm_keeper
.register_contract(
&api,
cache,
code_id,
user_addr.clone(),
None,
"".to_owned(),
1000,
None,
)
.unwrap();
let info = mock_info(user_addr.as_str(), &[]);
let init_msg = to_json_vec(&payout::InstantiateMessage {
payout: payout2.clone(),
})
.unwrap();
let _res = wasm_keeper
.call_instantiate(
contract2.clone(),
&api,
cache,
&mock_router(),
&block,
info,
init_msg,
)
.unwrap();
assert_payout(&wasm_keeper, cache, &contract2, &payout2);
let contract3 = transactional(cache, |cache2, read| {
assert_payout(&wasm_keeper, cache2, &contract1, &payout1);
assert_payout(&wasm_keeper, cache2, &contract2, &payout2);
let contract3 = wasm_keeper
.register_contract(
&api,
cache2,
code_id,
user_addr,
None,
"".to_owned(),
1000,
None,
)
.unwrap();
let info = mock_info("johnny", &[]);
let init_msg = to_json_vec(&payout::InstantiateMessage {
payout: payout3.clone(),
})
.unwrap();
let _res = wasm_keeper
.call_instantiate(
contract3.clone(),
&api,
cache2,
&mock_router(),
&block,
info,
init_msg,
)
.unwrap();
assert_payout(&wasm_keeper, cache2, &contract3, &payout3);
assert_no_contract(read, &contract3);
Ok(contract3)
})
.unwrap();
assert_payout(&wasm_keeper, cache, &contract1, &payout1);
assert_payout(&wasm_keeper, cache, &contract2, &payout2);
assert_payout(&wasm_keeper, cache, &contract3, &payout3);
assert_no_contract(wasm_reader, &contract1);
assert_no_contract(wasm_reader, &contract2);
assert_no_contract(wasm_reader, &contract3);
Ok((contract2, contract3))
})
.unwrap();
assert_payout(&wasm_keeper, &mut wasm_storage, &contract1, &payout1);
assert_payout(&wasm_keeper, &mut wasm_storage, &contract2, &payout2);
assert_payout(&wasm_keeper, &mut wasm_storage, &contract3, &payout3);
}
fn assert_admin(
storage: &dyn Storage,
wasm_keeper: &WasmKeeper<Empty, Empty>,
contract_addr: &impl ToString,
admin: Option<Addr>,
) {
let api = MockApi::default();
let querier: MockQuerier<Empty> = MockQuerier::new(&[]);
let data = wasm_keeper
.query(
&api,
storage,
&querier,
&mock_env().block,
WasmQuery::ContractInfo {
contract_addr: contract_addr.to_string(),
},
)
.unwrap();
let res: ContractInfoResponse = from_json(data).unwrap();
assert_eq!(res.admin, admin);
}
#[test]
fn update_clear_admin_works() {
let api = MockApi::default();
let mut wasm_keeper = wasm_keeper();
let block = mock_env().block;
let creator = api.addr_make("creator");
let code_id = wasm_keeper.store_code(creator.clone(), caller::contract());
let mut wasm_storage = MockStorage::new();
let admin = api.addr_make("admin");
let new_admin = api.addr_make("new_admin");
let normal_user = api.addr_make("normal_user");
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut wasm_storage,
code_id,
creator,
admin.clone(),
"label".to_owned(),
1000,
None,
)
.unwrap();
let info = mock_info("admin", &[]);
let init_msg = to_json_vec(&Empty {}).unwrap();
let res = wasm_keeper
.call_instantiate(
contract_addr.clone(),
&api,
&mut wasm_storage,
&mock_router(),
&block,
info,
init_msg,
)
.unwrap();
assert_eq!(0, res.messages.len());
assert_admin(
&wasm_storage,
&wasm_keeper,
&contract_addr,
Some(admin.clone()),
);
wasm_keeper
.execute_wasm(
&api,
&mut wasm_storage,
&mock_router(),
&block,
normal_user.clone(),
WasmMsg::UpdateAdmin {
contract_addr: contract_addr.to_string(),
admin: normal_user.to_string(),
},
)
.unwrap_err();
assert_admin(
&wasm_storage,
&wasm_keeper,
&contract_addr,
Some(admin.clone()),
);
let res = wasm_keeper
.execute_wasm(
&api,
&mut wasm_storage,
&mock_router(),
&block,
admin,
WasmMsg::UpdateAdmin {
contract_addr: contract_addr.to_string(),
admin: new_admin.to_string(),
},
)
.unwrap();
assert_eq!(res.events.len(), 0);
assert_admin(
&wasm_storage,
&wasm_keeper,
&contract_addr,
Some(new_admin.clone()),
);
let res = wasm_keeper
.execute_wasm(
&api,
&mut wasm_storage,
&mock_router(),
&block,
new_admin,
WasmMsg::ClearAdmin {
contract_addr: contract_addr.to_string(),
},
)
.unwrap();
assert_eq!(res.events.len(), 0);
assert_admin(&wasm_storage, &wasm_keeper, &contract_addr, None);
}
#[test]
fn uses_simple_address_generator_by_default() {
let api = MockApi::default();
let mut wasm_keeper = wasm_keeper();
let creator_addr = api.addr_make("creator");
let code_id = wasm_keeper.store_code(creator_addr.clone(), payout::contract());
assert_eq!(1, code_id);
let mut wasm_storage = MockStorage::new();
let admin = api.addr_make("admin");
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut wasm_storage,
code_id,
creator_addr.clone(),
admin.clone(),
"label".to_owned(),
1000,
None,
)
.unwrap();
assert_eq!(
contract_addr.as_str(),
"cosmwasm1mzdhwvvh22wrt07w59wxyd58822qavwkx5lcej7aqfkpqqlhaqfsgn6fq2",
"default address generator returned incorrect address"
);
let salt = HexBinary::from_hex("c0ffee").unwrap();
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut wasm_storage,
code_id,
creator_addr.clone(),
admin.clone(),
"label".to_owned(),
1000,
Binary::from(salt.clone()),
)
.unwrap();
assert_eq!(
contract_addr.as_str(),
"cosmwasm1drhu6t78wacgm5qjzs4hvkv9fd9awa9henw7fh6vmzrhf7k2nkjsg3flns",
"default address generator returned incorrect address"
);
let code_id = wasm_keeper.store_code(creator_addr, payout::contract());
assert_eq!(2, code_id);
let user_addr = api.addr_make("boobaz");
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut wasm_storage,
code_id,
user_addr,
admin,
"label".to_owned(),
1000,
Binary::from(salt),
)
.unwrap();
assert_eq!(
contract_addr.as_str(),
"cosmwasm13cfeertf2gny0rzp5jwqzst8crmfgvcd2lq5su0c9z66yxa45qdsdd0uxc",
"default address generator returned incorrect address"
);
}
struct TestAddressGenerator {
address: Addr,
predictable_address: Addr,
}
impl AddressGenerator for TestAddressGenerator {
fn contract_address(
&self,
_api: &dyn Api,
_storage: &mut dyn Storage,
_code_id: u64,
_instance_id: u64,
) -> AnyResult<Addr> {
Ok(self.address.clone())
}
fn predictable_contract_address(
&self,
_api: &dyn Api,
_storage: &mut dyn Storage,
_code_id: u64,
_instance_id: u64,
_checksum: &[u8],
_creator: &CanonicalAddr,
_salt: &[u8],
) -> AnyResult<Addr> {
Ok(self.predictable_address.clone())
}
}
#[test]
fn can_use_custom_address_generator() {
let api = MockApi::default();
let expected_addr = api.addr_make("address");
let expected_predictable_addr = api.addr_make("predictable_address");
let mut wasm_keeper: WasmKeeper<Empty, Empty> =
WasmKeeper::new().with_address_generator(TestAddressGenerator {
address: expected_addr.clone(),
predictable_address: expected_predictable_addr.clone(),
});
let creator = api.addr_make("creator");
let code_id = wasm_keeper.store_code(creator.clone(), payout::contract());
let mut wasm_storage = MockStorage::new();
let admin = api.addr_make("admin");
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut wasm_storage,
code_id,
creator.clone(),
admin.clone(),
"label".to_owned(),
1000,
None,
)
.unwrap();
assert_eq!(
contract_addr, expected_addr,
"custom address generator returned incorrect address"
);
let contract_addr = wasm_keeper
.register_contract(
&api,
&mut wasm_storage,
code_id,
creator,
admin,
"label".to_owned(),
1000,
Binary::from(HexBinary::from_hex("23A74B8C").unwrap()),
)
.unwrap();
assert_eq!(
contract_addr, expected_predictable_addr,
"custom address generator returned incorrect address"
);
}
}