use crate::app::no_init;
use crate::custom_handler::CachingCustomHandler;
use crate::error::{bail, AnyResult};
use crate::test_helpers::echo::EXECUTE_REPLY_BASE_ID;
use crate::test_helpers::{caller, echo, error, hackatom, payout, reflect, CustomMsg};
use crate::transactions::{transactional, StorageTransaction};
use crate::wasm::ContractData;
use crate::AppBuilder;
use crate::{
custom_app, next_block, App, AppResponse, Bank, CosmosRouter, Distribution, Executor, Module,
Router, Staking, Wasm, WasmSudo,
};
use cosmwasm_std::testing::{mock_env, MockQuerier};
use cosmwasm_std::{
coin, coins, from_json, to_json_binary, Addr, AllBalanceResponse, Api, Attribute, BankMsg,
BankQuery, Binary, BlockInfo, Coin, CosmosMsg, CustomQuery, Empty, Event, OverflowError,
OverflowOperation, Querier, Reply, StdError, StdResult, Storage, SubMsg, WasmMsg,
};
use cw_storage_plus::Item;
use cw_utils::parse_instantiate_response_data;
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
fn get_balance<BankT, ApiT, StorageT, CustomT, WasmT>(
app: &App<BankT, ApiT, StorageT, CustomT, WasmT>,
addr: &Addr,
) -> Vec<Coin>
where
CustomT::ExecT: Clone + Debug + PartialEq + JsonSchema + DeserializeOwned + 'static,
CustomT::QueryT: CustomQuery + DeserializeOwned + 'static,
WasmT: Wasm<CustomT::ExecT, CustomT::QueryT>,
BankT: Bank,
ApiT: Api,
StorageT: Storage,
CustomT: Module,
{
app.wrap().query_all_balances(addr).unwrap()
}
fn query_router<BankT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>(
router: &Router<BankT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>,
api: &dyn Api,
storage: &dyn Storage,
rcpt: &Addr,
) -> Vec<Coin>
where
CustomT::ExecT: Clone + Debug + PartialEq + JsonSchema,
CustomT::QueryT: CustomQuery + DeserializeOwned,
WasmT: Wasm<CustomT::ExecT, CustomT::QueryT>,
BankT: Bank,
CustomT: Module,
StakingT: Staking,
DistrT: Distribution,
{
let query = BankQuery::AllBalances {
address: rcpt.into(),
};
let block = mock_env().block;
let querier: MockQuerier<CustomT::QueryT> = MockQuerier::new(&[]);
let res = router
.bank
.query(api, storage, &querier, &block, query)
.unwrap();
let val: AllBalanceResponse = from_json(res).unwrap();
val.amount
}
fn query_app<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT>(
app: &App<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT>,
rcpt: &Addr,
) -> Vec<Coin>
where
CustomT::ExecT: Debug + PartialEq + Clone + JsonSchema + DeserializeOwned + 'static,
CustomT::QueryT: CustomQuery + DeserializeOwned + 'static,
WasmT: Wasm<CustomT::ExecT, CustomT::QueryT>,
BankT: Bank,
ApiT: Api,
StorageT: Storage,
CustomT: Module,
StakingT: Staking,
DistrT: Distribution,
{
let query = BankQuery::AllBalances {
address: rcpt.into(),
}
.into();
let val: AllBalanceResponse = app.wrap().query(&query).unwrap();
val.amount
}
#[test]
fn update_block() {
let mut app = App::default();
let BlockInfo { time, height, .. } = app.block_info();
app.update_block(next_block);
assert_eq!(time.plus_seconds(5), app.block_info().time);
assert_eq!(height + 1, app.block_info().height);
}
#[test]
fn multi_level_bank_cache() {
let owner = Addr::unchecked("owner");
let rcpt = Addr::unchecked("recipient");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
let mut app = App::new(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let mut cache = StorageTransaction::new(app.storage());
let msg = BankMsg::Send {
to_address: rcpt.clone().into(),
amount: coins(25, "eth"),
};
app.router()
.execute(
app.api(),
&mut cache,
&app.block_info(),
owner.clone(),
msg.into(),
)
.unwrap();
let cached_rcpt = query_router(app.router(), app.api(), &cache, &rcpt);
assert_eq!(coins(25, "eth"), cached_rcpt);
let router_rcpt = query_app(&app, &rcpt);
assert_eq!(router_rcpt, vec![]);
transactional(&mut cache, |cache2, read| {
let msg = BankMsg::Send {
to_address: rcpt.clone().into(),
amount: coins(12, "eth"),
};
app.router()
.execute(app.api(), cache2, &app.block_info(), owner, msg.into())
.unwrap();
let cached_rcpt = query_router(app.router(), app.api(), read, &rcpt);
assert_eq!(coins(25, "eth"), cached_rcpt);
let cached2_rcpt = query_router(app.router(), app.api(), cache2, &rcpt);
assert_eq!(coins(37, "eth"), cached2_rcpt);
Ok(())
})
.unwrap();
cache.prepare().commit(app.storage_mut());
let committed = query_app(&app, &rcpt);
assert_eq!(coins(37, "eth"), committed);
}
#[test]
fn duplicate_contract_code() {
let mut app = App::default();
let code_id = app.store_code(payout::contract());
let dup_code_id = app.duplicate_code(code_id).unwrap();
assert_ne!(code_id, dup_code_id);
let response = app.wrap().query_wasm_code_info(code_id).unwrap();
let dup_response = app.wrap().query_wasm_code_info(dup_code_id).unwrap();
assert_ne!(response.code_id, dup_response.code_id);
assert_eq!(response.creator, dup_response.creator);
assert_eq!(response.checksum, dup_response.checksum);
}
#[test]
fn send_tokens() {
let owner = Addr::unchecked("owner");
let rcpt = Addr::unchecked("receiver");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
let rcpt_funds = vec![coin(5, "btc")];
let mut app = App::new(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
router
.bank
.init_balance(storage, &rcpt, rcpt_funds)
.unwrap();
});
let to_send = vec![coin(30, "eth"), coin(5, "btc")];
let msg: CosmosMsg = BankMsg::Send {
to_address: rcpt.clone().into(),
amount: to_send,
}
.into();
app.execute(owner.clone(), msg.clone()).unwrap();
let rich = get_balance(&app, &owner);
assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich);
let poor = get_balance(&app, &rcpt);
assert_eq!(vec![coin(10, "btc"), coin(30, "eth")], poor);
app.execute(rcpt.clone(), msg).unwrap();
let msg = BankMsg::Send {
to_address: rcpt.into(),
amount: coins(20, "btc"),
}
.into();
app.execute(owner.clone(), msg).unwrap_err();
let rich = get_balance(&app, &owner);
assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich);
}
#[test]
fn simple_contract() {
let owner = Addr::unchecked("owner");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
let mut app = App::new(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let code_id = app.store_code(payout::contract());
let msg = payout::InstantiateMessage {
payout: coin(5, "eth"),
};
let contract_addr = app
.instantiate_contract(
code_id,
owner.clone(),
&msg,
&coins(23, "eth"),
"Payout",
None,
)
.unwrap();
let contract_data = app.contract_data(&contract_addr).unwrap();
assert_eq!(
contract_data,
ContractData {
code_id,
creator: owner.clone(),
admin: None,
label: "Payout".to_owned(),
created: app.block_info().height
}
);
let sender = get_balance(&app, &owner);
assert_eq!(sender, vec![coin(20, "btc"), coin(77, "eth")]);
let funds = get_balance(&app, &contract_addr);
assert_eq!(funds, coins(23, "eth"));
let random = Addr::unchecked("random");
let funds = get_balance(&app, &random);
assert_eq!(funds, vec![]);
let res = app
.execute_contract(random.clone(), contract_addr.clone(), &Empty {}, &[])
.unwrap();
assert_eq!(3, res.events.len());
let payout_exec = &res.events[0];
assert_eq!(payout_exec.ty.as_str(), "execute");
assert_eq!(
payout_exec.attributes,
[("_contract_address", &contract_addr)]
);
let custom_attrs = res.custom_attrs(1);
assert_eq!(custom_attrs, [("action", "payout")]);
let expected_transfer = Event::new("transfer")
.add_attribute("recipient", "random")
.add_attribute("sender", &contract_addr)
.add_attribute("amount", "5eth");
assert_eq!(&expected_transfer, &res.events[2]);
let funds = get_balance(&app, &random);
assert_eq!(funds, coins(5, "eth"));
let funds = get_balance(&app, &contract_addr);
assert_eq!(funds, coins(18, "eth"));
}
#[test]
fn reflect_success() {
let owner = Addr::unchecked("owner");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
let mut app = custom_app::<CustomMsg, Empty, _>(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let payout_id = app.store_code(payout::contract());
let msg = payout::InstantiateMessage {
payout: coin(5, "eth"),
};
let payout_addr = app
.instantiate_contract(
payout_id,
owner.clone(),
&msg,
&coins(23, "eth"),
"Payout",
None,
)
.unwrap();
let reflect_id = app.store_code(reflect::contract());
let reflect_addr = app
.instantiate_contract(reflect_id, owner, &Empty {}, &[], "Reflect", None)
.unwrap();
let funds = get_balance(&app, &reflect_addr);
assert_eq!(funds, vec![]);
let query_res: payout::CountResponse = app
.wrap()
.query_wasm_smart(&reflect_addr, &reflect::QueryMsg::Count {})
.unwrap();
assert_eq!(0, query_res.count);
let msg = SubMsg::new(WasmMsg::Execute {
contract_addr: payout_addr.clone().into(),
msg: b"{}".into(),
funds: vec![],
});
let msgs = reflect::Message {
messages: vec![msg],
};
let res = app
.execute_contract(Addr::unchecked("random"), reflect_addr.clone(), &msgs, &[])
.unwrap();
assert_eq!(4, res.events.len(), "{:?}", res.events);
let ref_exec = &res.events[0];
assert_eq!(ref_exec.ty.as_str(), "execute");
assert_eq!(ref_exec.attributes, [("_contract_address", &reflect_addr)]);
let payout_exec = &res.events[1];
assert_eq!(payout_exec.ty.as_str(), "execute");
assert_eq!(
payout_exec.attributes,
[("_contract_address", &payout_addr)]
);
let payout = &res.events[2];
assert_eq!(payout.ty.as_str(), "wasm");
assert_eq!(
payout.attributes,
[
("_contract_address", payout_addr.as_str()),
("action", "payout")
]
);
let second = &res.events[3];
assert_eq!(second.ty.as_str(), "transfer");
assert_eq!(3, second.attributes.len());
assert_eq!(second.attributes[0], ("recipient", &reflect_addr));
assert_eq!(second.attributes[1], ("sender", &payout_addr));
assert_eq!(second.attributes[2], ("amount", "5eth"));
let funds = get_balance(&app, &reflect_addr);
assert_eq!(funds, coins(5, "eth"));
let query_res: payout::CountResponse = app
.wrap()
.query_wasm_smart(&reflect_addr, &reflect::QueryMsg::Count {})
.unwrap();
assert_eq!(1, query_res.count);
}
#[test]
fn reflect_error() {
let owner = Addr::unchecked("owner");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
let mut app = custom_app::<CustomMsg, Empty, _>(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let reflect_id = app.store_code(reflect::contract());
let reflect_addr = app
.instantiate_contract(
reflect_id,
owner,
&Empty {},
&coins(40, "eth"),
"Reflect",
None,
)
.unwrap();
let funds = get_balance(&app, &reflect_addr);
assert_eq!(funds, coins(40, "eth"));
let random = Addr::unchecked("random");
let msg = SubMsg::new(BankMsg::Send {
to_address: random.clone().into(),
amount: coins(7, "eth"),
});
let msgs = reflect::Message {
messages: vec![msg],
};
let res = app
.execute_contract(random.clone(), reflect_addr.clone(), &msgs, &[])
.unwrap();
assert_eq!(2, res.events.len());
let exec = &res.events[0];
assert_eq!(exec.ty.as_str(), "execute");
assert_eq!(exec.attributes, [("_contract_address", &reflect_addr)]);
let transfer = &res.events[1];
assert_eq!(transfer.ty.as_str(), "transfer");
let funds = get_balance(&app, &random);
assert_eq!(funds, coins(7, "eth"));
let query_res: payout::CountResponse = app
.wrap()
.query_wasm_smart(&reflect_addr, &reflect::QueryMsg::Count {})
.unwrap();
assert_eq!(1, query_res.count);
let msg = SubMsg::new(BankMsg::Send {
to_address: random.clone().into(),
amount: coins(8, "eth"),
});
let msg2 = SubMsg::new(BankMsg::Send {
to_address: random.clone().into(),
amount: coins(3, "btc"),
});
let msgs = reflect::Message {
messages: vec![msg, msg2],
};
let err = app
.execute_contract(random.clone(), reflect_addr.clone(), &msgs, &[])
.unwrap_err();
assert_eq!(
StdError::overflow(OverflowError::new(OverflowOperation::Sub, 0, 3)),
err.downcast().unwrap()
);
let funds = get_balance(&app, &random);
assert_eq!(funds, coins(7, "eth"));
let query_res: payout::CountResponse = app
.wrap()
.query_wasm_smart(&reflect_addr, &reflect::QueryMsg::Count {})
.unwrap();
assert_eq!(1, query_res.count);
}
#[test]
fn sudo_works() {
let owner = Addr::unchecked("owner");
let init_funds = vec![coin(100, "eth")];
let mut app = App::new(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let payout_id = app.store_code(payout::contract());
let msg = payout::InstantiateMessage {
payout: coin(5, "eth"),
};
let payout_addr = app
.instantiate_contract(payout_id, owner, &msg, &coins(23, "eth"), "Payout", None)
.unwrap();
let payout::CountResponse { count } = app
.wrap()
.query_wasm_smart(&payout_addr, &payout::QueryMsg::Count {})
.unwrap();
assert_eq!(1, count);
let msg = payout::SudoMsg { set_count: 25 };
app.wasm_sudo(payout_addr.clone(), &msg).unwrap();
let payout::CountResponse { count } = app
.wrap()
.query_wasm_smart(&payout_addr, &payout::QueryMsg::Count {})
.unwrap();
assert_eq!(25, count);
let msg = payout::SudoMsg { set_count: 49 };
let sudo_msg = WasmSudo {
contract_addr: payout_addr.clone(),
msg: to_json_binary(&msg).unwrap(),
};
app.sudo(sudo_msg.into()).unwrap();
let payout::CountResponse { count } = app
.wrap()
.query_wasm_smart(&payout_addr, &payout::QueryMsg::Count {})
.unwrap();
assert_eq!(49, count);
}
#[test]
fn reflect_sub_message_reply_works() {
let owner = Addr::unchecked("owner");
let random = Addr::unchecked("random");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
let mut app = custom_app::<CustomMsg, Empty, _>(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let reflect_id = app.store_code(reflect::contract());
let reflect_addr = app
.instantiate_contract(
reflect_id,
owner,
&Empty {},
&coins(40, "eth"),
"Reflect",
None,
)
.unwrap();
let query = reflect::QueryMsg::Reply { id: 123 };
let res: StdResult<Reply> = app.wrap().query_wasm_smart(&reflect_addr, &query);
res.unwrap_err();
let msg = SubMsg::reply_always(
BankMsg::Send {
to_address: random.clone().into(),
amount: coins(7, "eth"),
},
123,
);
let msgs = reflect::Message {
messages: vec![msg],
};
let res = app
.execute_contract(random.clone(), reflect_addr.clone(), &msgs, &[])
.unwrap();
assert_eq!(4, res.events.len(), "{:?}", res.events);
res.assert_event(&Event::new("execute").add_attribute("_contract_address", &reflect_addr));
res.assert_event(&Event::new("transfer").add_attribute("amount", "7eth"));
res.assert_event(
&Event::new("reply")
.add_attribute("_contract_address", reflect_addr.as_str())
.add_attribute("mode", "handle_success"),
);
res.assert_event(&Event::new("wasm-custom").add_attribute("from", "reply"));
let res: Reply = app.wrap().query_wasm_smart(&reflect_addr, &query).unwrap();
assert_eq!(res.id, 123);
let reply = res.result.unwrap();
assert_eq!(1, reply.events.len());
AppResponse::from(reply).assert_event(&Event::new("transfer").add_attribute("amount", "7eth"));
let msg = SubMsg::reply_always(
BankMsg::Send {
to_address: random.clone().into(),
amount: coins(300, "btc"),
},
456,
);
let msgs = reflect::Message {
messages: vec![msg],
};
let _res = app
.execute_contract(random, reflect_addr.clone(), &msgs, &[])
.unwrap();
let query = reflect::QueryMsg::Reply { id: 456 };
let res: Reply = app.wrap().query_wasm_smart(&reflect_addr, &query).unwrap();
assert_eq!(res.id, 456);
assert!(res.result.is_err());
}
#[test]
fn send_update_admin_works() {
let owner = Addr::unchecked("owner");
let owner2 = Addr::unchecked("owner2");
let beneficiary = Addr::unchecked("beneficiary");
let mut app = App::default();
let code_id = app.store_code(hackatom::contract());
let contract = app
.instantiate_contract(
code_id,
owner.clone(),
&hackatom::InstantiateMsg {
beneficiary: beneficiary.as_str().to_owned(),
},
&[],
"Hackatom",
Some(owner.to_string()),
)
.unwrap();
let info = app.contract_data(&contract).unwrap();
assert_eq!(info.admin, Some(owner.clone()));
app.execute(
owner.clone(),
CosmosMsg::Wasm(WasmMsg::UpdateAdmin {
contract_addr: contract.to_string(),
admin: owner2.to_string(),
}),
)
.unwrap();
let info = app.contract_data(&contract).unwrap();
assert_eq!(info.admin, Some(owner2.clone()));
app.execute(
owner.clone(),
CosmosMsg::Wasm(WasmMsg::UpdateAdmin {
contract_addr: contract.to_string(),
admin: owner.to_string(),
}),
)
.unwrap_err();
let info = app.contract_data(&contract).unwrap();
assert_eq!(info.admin, Some(owner2));
}
#[test]
fn sent_wasm_migration_works() {
let owner = Addr::unchecked("owner");
let beneficiary = Addr::unchecked("beneficiary");
let init_funds = coins(30, "btc");
let mut app = App::new(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let code_id = app.store_code(hackatom::contract());
let contract = app
.instantiate_contract(
code_id,
owner.clone(),
&hackatom::InstantiateMsg {
beneficiary: beneficiary.as_str().to_owned(),
},
&coins(20, "btc"),
"Hackatom",
Some(owner.to_string()),
)
.unwrap();
let info = app.contract_data(&contract).unwrap();
assert_eq!(info.admin, Some(owner.clone()));
let state: hackatom::InstantiateMsg = app
.wrap()
.query_wasm_smart(&contract, &hackatom::QueryMsg::Beneficiary {})
.unwrap();
assert_eq!(state.beneficiary, beneficiary);
let random = Addr::unchecked("random");
let migrate_msg = hackatom::MigrateMsg {
new_guy: random.to_string(),
};
app.migrate_contract(beneficiary, contract.clone(), &migrate_msg, code_id)
.unwrap_err();
app.migrate_contract(owner.clone(), contract.clone(), &migrate_msg, code_id + 7)
.unwrap_err();
app.migrate_contract(owner, contract.clone(), &migrate_msg, code_id)
.unwrap();
let state: hackatom::InstantiateMsg = app
.wrap()
.query_wasm_smart(&contract, &hackatom::QueryMsg::Beneficiary {})
.unwrap();
assert_eq!(state.beneficiary, random);
}
#[test]
fn sent_funds_properly_visible_on_execution() {
let owner = Addr::unchecked("owner");
let beneficiary = Addr::unchecked("beneficiary");
let init_funds = coins(30, "btc");
let mut app = App::new(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let code_id = app.store_code(hackatom::contract());
let contract = app
.instantiate_contract(
code_id,
owner.clone(),
&hackatom::InstantiateMsg {
beneficiary: beneficiary.as_str().to_owned(),
},
&coins(10, "btc"),
"Hackatom",
None,
)
.unwrap();
app.execute_contract(
owner.clone(),
contract.clone(),
&Empty {},
&coins(20, "btc"),
)
.unwrap();
assert_eq!(get_balance(&app, &owner), &[]);
assert_eq!(get_balance(&app, &contract), &[]);
assert_eq!(get_balance(&app, &beneficiary), coins(30, "btc"));
}
mod custom_handler {
use super::*;
use crate::{BankSudo, BasicAppBuilder, CosmosRouter};
const LOTTERY: Item<Coin> = Item::new("lottery");
const PITY: Item<Coin> = Item::new("pity");
#[derive(Clone, Debug, PartialEq, JsonSchema, Serialize, Deserialize)]
struct CustomMsg {
lucky_winner: String,
runner_up: String,
}
struct CustomHandler {}
impl Module for CustomHandler {
type ExecT = CustomMsg;
type QueryT = Empty;
type SudoT = Empty;
fn execute<ExecC, QueryC>(
&self,
api: &dyn Api,
storage: &mut dyn Storage,
router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
block: &BlockInfo,
_sender: Addr,
msg: Self::ExecT,
) -> AnyResult<AppResponse>
where
ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
QueryC: CustomQuery + DeserializeOwned + 'static,
{
let lottery = LOTTERY.load(storage)?;
let pity = PITY.load(storage)?;
let mint = BankSudo::Mint {
to_address: msg.lucky_winner.clone(),
amount: vec![lottery],
};
router.sudo(api, storage, block, mint.into())?;
let transfer = BankMsg::Send {
to_address: msg.runner_up,
amount: vec![pity],
};
let rcpt = api.addr_validate(&msg.lucky_winner)?;
router.execute(api, storage, block, rcpt, transfer.into())?;
Ok(AppResponse::default())
}
fn sudo<ExecC, QueryC>(
&self,
_api: &dyn Api,
_storage: &mut dyn Storage,
_router: &dyn CosmosRouter<ExecC = ExecC, QueryC = QueryC>,
_block: &BlockInfo,
_msg: Self::SudoT,
) -> AnyResult<AppResponse>
where
ExecC: Debug + Clone + PartialEq + JsonSchema + DeserializeOwned + 'static,
QueryC: CustomQuery + DeserializeOwned + 'static,
{
bail!("sudo not implemented for CustomHandler")
}
fn query(
&self,
_api: &dyn Api,
_storage: &dyn Storage,
_querier: &dyn Querier,
_block: &BlockInfo,
_request: Self::QueryT,
) -> AnyResult<Binary> {
bail!("query not implemented for CustomHandler")
}
}
impl CustomHandler {
pub fn set_payout(
&self,
storage: &mut dyn Storage,
lottery: Coin,
pity: Coin,
) -> AnyResult<()> {
LOTTERY.save(storage, &lottery)?;
PITY.save(storage, &pity)?;
Ok(())
}
}
#[test]
fn dispatches_messages() {
let winner = "winner".to_string();
let second = "second".to_string();
let denom = "tix";
let lottery = coin(54321, denom);
let bonus = coin(12321, denom);
let mut app = BasicAppBuilder::<CustomMsg, Empty>::new_custom()
.with_custom(CustomHandler {})
.build(|router, _, storage| {
router
.custom
.set_payout(storage, lottery.clone(), bonus.clone())
.unwrap();
});
let start = app.wrap().query_balance(&winner, denom).unwrap();
assert_eq!(start, coin(0, denom));
let msg = CosmosMsg::Custom(CustomMsg {
lucky_winner: winner.clone(),
runner_up: second.clone(),
});
app.execute(Addr::unchecked("anyone"), msg).unwrap();
let big_win = app.wrap().query_balance(&winner, denom).unwrap();
assert_eq!(big_win, coin(42000, denom));
let little_win = app.wrap().query_balance(&second, denom).unwrap();
assert_eq!(little_win, bonus);
}
}
mod reply_data_overwrite {
use super::*;
use cosmwasm_std::to_json_binary;
use echo::EXECUTE_REPLY_BASE_ID;
fn make_echo_submsg(
contract: Addr,
data: impl Into<Option<&'static str>>,
sub_msg: Vec<SubMsg>,
id: u64,
) -> SubMsg {
let data = data.into().map(|s| s.to_owned());
SubMsg::reply_always(
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract.into(),
msg: to_json_binary(&echo::Message {
data,
sub_msg,
..echo::Message::default()
})
.unwrap(),
funds: vec![],
}),
id,
)
}
fn make_echo_submsg_no_reply(
contract: Addr,
data: impl Into<Option<&'static str>>,
sub_msg: Vec<SubMsg>,
) -> SubMsg {
let data = data.into().map(|s| s.to_owned());
SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: contract.into(),
msg: to_json_binary(&echo::Message {
data,
sub_msg,
..echo::Message::default()
})
.unwrap(),
funds: vec![],
}))
}
#[test]
fn no_submsg() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract,
&echo::Message::<Empty> {
data: Some("Data".to_owned()),
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"Data".into()));
}
#[test]
fn single_submsg() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract.clone(),
&echo::Message {
data: Some("First".to_owned()),
sub_msg: vec![make_echo_submsg(
contract,
"Second",
vec![],
EXECUTE_REPLY_BASE_ID,
)],
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"Second".into()));
}
#[test]
fn single_submsg_no_reply() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract.clone(),
&echo::Message {
data: Some("First".to_owned()),
sub_msg: vec![make_echo_submsg_no_reply(contract, "Second", vec![])],
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"First".into()));
}
#[test]
fn single_no_submsg_data() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract.clone(),
&echo::Message {
data: Some("First".to_owned()),
sub_msg: vec![make_echo_submsg(contract, None, vec![], 1)],
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"First".into()));
}
#[test]
fn single_no_top_level_data() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract.clone(),
&echo::Message {
sub_msg: vec![make_echo_submsg(
contract,
"Second",
vec![],
EXECUTE_REPLY_BASE_ID,
)],
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"Second".into()));
}
#[test]
fn single_submsg_reply_returns_none() {
let owner = Addr::unchecked("owner");
let init_funds = coins(100, "tgd");
let mut app = custom_app::<CustomMsg, Empty, _>(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let reflect_id = app.store_code(reflect::contract());
let reflect_addr = app
.instantiate_contract(reflect_id, owner.clone(), &Empty {}, &[], "Reflect", None)
.unwrap();
let echo_id = app.store_code(echo::custom_contract());
let echo_addr = app
.instantiate_contract(echo_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let echo_msg = echo::Message::<Empty> {
data: Some("my echo".into()),
events: vec![Event::new("echo").add_attribute("called", "true")],
..echo::Message::default()
};
let reflect_msg = reflect::Message {
messages: vec![SubMsg::new(WasmMsg::Execute {
contract_addr: echo_addr.to_string(),
msg: to_json_binary(&echo_msg).unwrap(),
funds: vec![],
})],
};
let res = app
.execute_contract(owner, reflect_addr.clone(), &reflect_msg, &[])
.unwrap();
assert_eq!(res.data, None);
assert_eq!(res.events.len(), 3, "{:?}", res.events);
res.assert_event(&Event::new("execute").add_attribute("_contract_address", &reflect_addr));
res.assert_event(&Event::new("execute").add_attribute("_contract_address", &echo_addr));
res.assert_event(&Event::new("wasm-echo"));
}
#[test]
fn multiple_submsg() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract.clone(),
&echo::Message {
data: Some("Orig".to_owned()),
sub_msg: vec![
make_echo_submsg(contract.clone(), None, vec![], EXECUTE_REPLY_BASE_ID + 1),
make_echo_submsg(
contract.clone(),
"First",
vec![],
EXECUTE_REPLY_BASE_ID + 2,
),
make_echo_submsg(
contract.clone(),
"Second",
vec![],
EXECUTE_REPLY_BASE_ID + 3,
),
make_echo_submsg(contract, None, vec![], EXECUTE_REPLY_BASE_ID + 4),
],
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"Second".into()));
}
#[test]
fn multiple_submsg_no_reply() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract.clone(),
&echo::Message {
data: Some("Orig".to_owned()),
sub_msg: vec![
make_echo_submsg_no_reply(contract.clone(), None, vec![]),
make_echo_submsg_no_reply(contract.clone(), "First", vec![]),
make_echo_submsg_no_reply(contract.clone(), "Second", vec![]),
make_echo_submsg_no_reply(contract, None, vec![]),
],
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"Orig".into()));
}
#[test]
fn multiple_submsg_mixed() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract.clone(),
&echo::Message {
sub_msg: vec![
make_echo_submsg(contract.clone(), None, vec![], EXECUTE_REPLY_BASE_ID + 1),
make_echo_submsg_no_reply(contract.clone(), "Hidden", vec![]),
make_echo_submsg(
contract.clone(),
"Shown",
vec![],
EXECUTE_REPLY_BASE_ID + 2,
),
make_echo_submsg(contract.clone(), None, vec![], EXECUTE_REPLY_BASE_ID + 3),
make_echo_submsg_no_reply(contract, "Lost", vec![]),
],
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"Shown".into()));
}
#[test]
fn nested_submsg() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let response = app
.execute_contract(
owner,
contract.clone(),
&echo::Message {
data: Some("Orig".to_owned()),
sub_msg: vec![make_echo_submsg(
contract.clone(),
None,
vec![make_echo_submsg(
contract.clone(),
"First",
vec![make_echo_submsg(
contract.clone(),
"Second",
vec![make_echo_submsg(
contract,
None,
vec![],
EXECUTE_REPLY_BASE_ID + 4,
)],
EXECUTE_REPLY_BASE_ID + 3,
)],
EXECUTE_REPLY_BASE_ID + 2,
)],
EXECUTE_REPLY_BASE_ID + 1,
)],
..echo::Message::default()
},
&[],
)
.unwrap();
assert_eq!(response.data, Some(b"Second".into()));
}
}
mod response_validation {
use super::*;
use crate::error::Error;
#[test]
fn empty_attribute_key() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let err = app
.execute_contract(
owner,
contract,
&echo::Message::<Empty> {
data: None,
attributes: vec![
Attribute::new(" ", "value"),
Attribute::new("proper", "proper_val"),
],
..echo::Message::default()
},
&[],
)
.unwrap_err();
assert_eq!(Error::empty_attribute_key("value"), err.downcast().unwrap(),);
}
#[test]
fn empty_attribute_value() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let err = app
.execute_contract(
owner,
contract,
&echo::Message::<Empty> {
data: None,
attributes: vec![
Attribute::new("key", " "),
Attribute::new("proper", "proper_val"),
],
..echo::Message::default()
},
&[],
)
.unwrap_err();
assert_eq!(Error::empty_attribute_value("key"), err.downcast().unwrap());
}
#[test]
fn empty_event_attribute_key() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let err = app
.execute_contract(
owner,
contract,
&echo::Message::<Empty> {
data: None,
events: vec![Event::new("event")
.add_attribute(" ", "value")
.add_attribute("proper", "proper_val")],
..echo::Message::default()
},
&[],
)
.unwrap_err();
assert_eq!(Error::empty_attribute_key("value"), err.downcast().unwrap());
}
#[test]
fn empty_event_attribute_value() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let err = app
.execute_contract(
owner,
contract,
&echo::Message::<Empty> {
data: None,
events: vec![Event::new("event")
.add_attribute("key", " ")
.add_attribute("proper", "proper_val")],
..echo::Message::default()
},
&[],
)
.unwrap_err();
assert_eq!(Error::empty_attribute_value("key"), err.downcast().unwrap());
}
#[test]
fn too_short_event_type() {
let mut app = App::default();
let owner = Addr::unchecked("owner");
let code_id = app.store_code(echo::contract());
let contract = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "Echo", None)
.unwrap();
let err = app
.execute_contract(
owner,
contract,
&echo::Message::<Empty> {
data: None,
events: vec![Event::new(" e "), Event::new("event")],
..echo::Message::default()
},
&[],
)
.unwrap_err();
assert_eq!(Error::event_type_too_short("e"), err.downcast().unwrap());
}
}
mod contract_instantiation {
#[test]
fn instantiate2_works() {
use super::*;
let mut app = App::default();
let sender = Addr::unchecked("sender");
let code_id = app.store_code_with_creator(Addr::unchecked("creator"), echo::contract());
let init_msg = to_json_binary(&Empty {}).unwrap();
let msg = WasmMsg::Instantiate2 {
admin: None,
code_id,
msg: init_msg,
funds: vec![],
label: "label".into(),
salt: [1, 2, 3, 4, 5, 6].as_slice().into(),
};
let res = app.execute(sender, msg.into()).unwrap();
let parsed = parse_instantiate_response_data(res.data.unwrap().as_slice()).unwrap();
assert!(parsed.data.is_none());
assert_eq!(parsed.contract_address, "contract010203040506");
}
}
mod wasm_queries {
#[test]
fn query_existing_code_info() {
use super::*;
let mut app = App::default();
let code_id = app.store_code_with_creator(Addr::unchecked("creator"), echo::contract());
let code_info_response = app.wrap().query_wasm_code_info(code_id).unwrap();
assert_eq!(code_id, code_info_response.code_id);
assert_eq!("creator", code_info_response.creator);
assert!(!code_info_response.checksum.is_empty());
}
#[test]
fn query_non_existing_code_info() {
use super::*;
let app = App::default();
assert_eq!(
"Generic error: Querier contract error: code id: invalid",
app.wrap().query_wasm_code_info(0).unwrap_err().to_string()
);
assert_eq!(
"Generic error: Querier contract error: code id 1: no such code",
app.wrap().query_wasm_code_info(1).unwrap_err().to_string()
);
}
}
mod custom_messages {
use super::*;
#[test]
fn triggering_custom_msg() {
let custom_handler = CachingCustomHandler::<CustomMsg, Empty>::new();
let custom_handler_state = custom_handler.state();
let mut app = AppBuilder::new_custom()
.with_custom(custom_handler)
.build(no_init);
let sender = app.api().addr_validate("sender").unwrap();
let owner = app.api().addr_validate("owner").unwrap();
let contract_id = app.store_code(echo::custom_contract());
let contract = app
.instantiate_contract(contract_id, owner, &Empty {}, &[], "Echo", None)
.unwrap();
app.execute_contract(
sender,
contract,
&echo::Message {
sub_msg: vec![SubMsg::new(CosmosMsg::Custom(CustomMsg::SetAge {
age: 20,
}))],
..Default::default()
},
&[],
)
.unwrap();
assert_eq!(
custom_handler_state.execs().to_owned(),
vec![CustomMsg::SetAge { age: 20 }]
);
assert!(custom_handler_state.queries().is_empty());
}
}
mod protobuf_wrapped_data {
use super::*;
use crate::BasicApp;
#[test]
fn instantiate_wrapped_properly() {
let owner = Addr::unchecked("owner");
let init_funds = vec![coin(20, "btc")];
let mut app = custom_app::<CustomMsg, Empty, _>(|router, _, storage| {
router
.bank
.init_balance(storage, &owner, init_funds)
.unwrap();
});
let code_id = app.store_code(reflect::contract());
let init_msg = to_json_binary(&Empty {}).unwrap();
let msg = WasmMsg::Instantiate {
admin: None,
code_id,
msg: init_msg,
funds: vec![],
label: "label".into(),
};
let res = app.execute(owner, msg.into()).unwrap();
let parsed = parse_instantiate_response_data(res.data.unwrap().as_slice()).unwrap();
assert!(parsed.data.is_none());
let count: payout::CountResponse = app
.wrap()
.query_wasm_smart(&parsed.contract_address, &reflect::QueryMsg::Count {})
.unwrap();
assert_eq!(count.count, 0);
}
#[test]
fn instantiate_with_data_works() {
let owner = Addr::unchecked("owner");
let mut app = BasicApp::new(|_, _, _| {});
let code_id = app.store_code(echo::contract());
let msg = echo::InitMessage::<Empty> {
data: Some("food".into()),
sub_msg: None,
};
let init_msg = to_json_binary(&msg).unwrap();
let msg = WasmMsg::Instantiate {
admin: None,
code_id,
msg: init_msg,
funds: vec![],
label: "label".into(),
};
let res = app.execute(owner, msg.into()).unwrap();
let parsed = parse_instantiate_response_data(res.data.unwrap().as_slice()).unwrap();
assert!(parsed.data.is_some());
assert_eq!(parsed.data.unwrap(), Binary::from(b"food"));
assert!(!parsed.contract_address.is_empty());
}
#[test]
fn instantiate_with_reply_works() {
let owner = Addr::unchecked("owner");
let mut app = BasicApp::new(|_, _, _| {});
let code_id = app.store_code(echo::contract());
let msg = echo::InitMessage::<Empty> {
data: Some("food".into()),
..Default::default()
};
let addr1 = app
.instantiate_contract(code_id, owner.clone(), &msg, &[], "first", None)
.unwrap();
let msg = echo::Message::<Empty> {
data: Some("Passed to contract instantiation, returned as reply, and then returned as response".into()),
..Default::default()
};
let sub_msg = SubMsg::reply_on_success(
WasmMsg::Execute {
contract_addr: addr1.to_string(),
msg: to_json_binary(&msg).unwrap(),
funds: vec![],
},
EXECUTE_REPLY_BASE_ID,
);
let init_msg = echo::InitMessage::<Empty> {
data: Some("Overwrite me".into()),
sub_msg: Some(vec![sub_msg]),
};
let init_msg = to_json_binary(&init_msg).unwrap();
let msg = WasmMsg::Instantiate {
admin: None,
code_id,
msg: init_msg,
funds: vec![],
label: "label".into(),
};
let res = app.execute(owner, msg.into()).unwrap();
let parsed = parse_instantiate_response_data(res.data.unwrap().as_slice()).unwrap();
assert!(parsed.data.is_some());
assert_eq!(parsed.data.unwrap(), Binary::from(b"Passed to contract instantiation, returned as reply, and then returned as response"));
assert!(!parsed.contract_address.is_empty());
assert_ne!(parsed.contract_address, addr1.to_string());
}
#[test]
fn execute_wrapped_properly() {
let owner = Addr::unchecked("owner");
let mut app = BasicApp::new(|_, _, _| {});
let code_id = app.store_code(echo::contract());
let echo_addr = app
.instantiate_contract(code_id, owner.clone(), &Empty {}, &[], "label", None)
.unwrap();
let msg = echo::Message::<Empty> {
data: Some("hello".into()),
..echo::Message::default()
};
let exec_res = app.execute_contract(owner, echo_addr, &msg, &[]).unwrap();
assert_eq!(exec_res.data, Some(Binary::from(b"hello")));
}
}
mod errors {
use super::*;
use cosmwasm_std::to_json_binary;
#[test]
fn simple_instantiation() {
let owner = Addr::unchecked("owner");
let mut app = App::default();
let code_id = app.store_code(error::contract(false));
let msg = Empty {};
let err = app
.instantiate_contract(code_id, owner, &msg, &[], "error", None)
.unwrap_err();
let source: &StdError = err.downcast_ref().unwrap();
if let StdError::GenericErr { msg } = source {
assert_eq!(msg, "Init failed");
} else {
panic!("wrong StdError variant");
}
assert_eq!(err.chain().count(), 2);
}
#[test]
fn simple_call() {
let owner = Addr::unchecked("owner");
let mut app = App::default();
let code_id = app.store_code(error::contract(true));
let msg = Empty {};
let contract_addr = app
.instantiate_contract(code_id, owner, &msg, &[], "error", None)
.unwrap();
let err = app
.execute_contract(Addr::unchecked("random"), contract_addr, &msg, &[])
.unwrap_err();
let source: &StdError = err.downcast_ref().unwrap();
if let StdError::GenericErr { msg } = source {
assert_eq!(msg, "Handle failed");
} else {
panic!("wrong StdError variant");
}
assert_eq!(err.chain().count(), 2);
}
#[test]
fn nested_call() {
let owner = Addr::unchecked("owner");
let mut app = App::default();
let error_code_id = app.store_code(error::contract(true));
let caller_code_id = app.store_code(caller::contract());
let msg = Empty {};
let caller_addr = app
.instantiate_contract(caller_code_id, owner.clone(), &msg, &[], "caller", None)
.unwrap();
let error_addr = app
.instantiate_contract(error_code_id, owner, &msg, &[], "error", None)
.unwrap();
let msg = WasmMsg::Execute {
contract_addr: error_addr.into(),
msg: to_json_binary(&Empty {}).unwrap(),
funds: vec![],
};
let err = app
.execute_contract(Addr::unchecked("random"), caller_addr, &msg, &[])
.unwrap_err();
let source: &StdError = err.downcast_ref().unwrap();
if let StdError::GenericErr { msg } = source {
assert_eq!(msg, "Handle failed");
} else {
panic!("wrong StdError variant");
}
assert_eq!(err.chain().count(), 3);
}
#[test]
fn double_nested_call() {
let owner = Addr::unchecked("owner");
let mut app = App::default();
let error_code_id = app.store_code(error::contract(true));
let caller_code_id = app.store_code(caller::contract());
let msg = Empty {};
let caller_addr1 = app
.instantiate_contract(caller_code_id, owner.clone(), &msg, &[], "caller", None)
.unwrap();
let caller_addr2 = app
.instantiate_contract(caller_code_id, owner.clone(), &msg, &[], "caller", None)
.unwrap();
let error_addr = app
.instantiate_contract(error_code_id, owner, &msg, &[], "error", None)
.unwrap();
let msg = WasmMsg::Execute {
contract_addr: caller_addr2.into(),
msg: to_json_binary(&WasmMsg::Execute {
contract_addr: error_addr.into(),
msg: to_json_binary(&Empty {}).unwrap(),
funds: vec![],
})
.unwrap(),
funds: vec![],
};
let err = app
.execute_contract(Addr::unchecked("random"), caller_addr1, &msg, &[])
.unwrap_err();
let source: &StdError = err.downcast_ref().unwrap();
if let StdError::GenericErr { msg } = source {
assert_eq!(msg, "Handle failed");
} else {
panic!("wrong StdError variant");
}
assert_eq!(err.chain().count(), 4);
}
}
mod api {
use super::*;
#[test]
fn api_addr_validate_should_work() {
let app = App::default();
let addr = app.api().addr_validate("creator").unwrap();
assert_eq!(addr.to_string(), "creator");
}
#[test]
#[cfg(not(feature = "cosmwasm_1_5"))]
fn api_addr_canonicalize_should_work() {
let app = App::default();
let canonical = app.api().addr_canonicalize("creator").unwrap();
assert_eq!(canonical.to_string(), "0000000000000000000000000000726F0000000000000000000000000000000000000000006572000000000000000000000000000000000000000000610000000000000000000000000000000000000000006374000000000000");
}
#[test]
fn api_addr_humanize_should_work() {
let app = App::default();
let canonical = app.api().addr_canonicalize("creator").unwrap();
assert_eq!(app.api().addr_humanize(&canonical).unwrap(), "creator");
}
}