use serde::Serialize;
#[cfg(test)]
use cosmwasm_std::testing::{mock_env, MockApi};
use cosmwasm_std::{
from_slice, to_binary, Api, Attribute, BankMsg, Binary, BlockInfo, Coin, ContractResult,
CosmosMsg, Empty, HumanAddr, MessageInfo, Querier, QuerierResult, QuerierWrapper, QueryRequest,
Response, SystemError, SystemResult, WasmMsg,
};
use crate::bank::{Bank, BankCache, BankOps, BankRouter};
use crate::wasm::{Contract, StorageFactory, WasmCache, WasmOps, WasmRouter};
#[derive(Default, Clone, Debug)]
pub struct AppResponse {
pub attributes: Vec<Attribute>,
pub data: Option<Binary>,
}
#[derive(Default, Clone)]
pub struct ActionResponse {
pub messages: Vec<CosmosMsg<Empty>>,
pub attributes: Vec<Attribute>,
pub data: Option<Binary>,
}
impl From<Response<Empty>> for ActionResponse {
fn from(input: Response<Empty>) -> Self {
ActionResponse {
messages: input.messages,
attributes: input.attributes,
data: input.data,
}
}
}
impl ActionResponse {
fn init(input: Response<Empty>, address: HumanAddr) -> Self {
ActionResponse {
messages: input.messages,
attributes: input.attributes,
data: Some(address.as_bytes().into()),
}
}
}
pub struct App {
wasm: WasmRouter,
bank: BankRouter,
}
impl Querier for App {
fn raw_query(&self, bin_request: &[u8]) -> QuerierResult {
let request: QueryRequest<Empty> = match from_slice(bin_request) {
Ok(v) => v,
Err(e) => {
return SystemResult::Err(SystemError::InvalidRequest {
error: format!("Parsing query request: {}", e.to_string()),
request: bin_request.into(),
})
}
};
let contract_result: ContractResult<Binary> = self.query(request).into();
SystemResult::Ok(contract_result)
}
}
impl App {
pub fn new<B: Bank + 'static>(
api: Box<dyn Api>,
block: BlockInfo,
bank: B,
storage_factory: StorageFactory,
) -> Self {
App {
wasm: WasmRouter::new(api, block, storage_factory),
bank: BankRouter::new(bank, storage_factory()),
}
}
pub fn cache(&'_ self) -> AppCache<'_> {
AppCache::new(self)
}
pub fn set_block(&mut self, block: BlockInfo) {
self.wasm.set_block(block);
}
pub fn update_block<F: Fn(&mut BlockInfo)>(&mut self, action: F) {
self.wasm.update_block(action);
}
pub fn block_info(&self) -> BlockInfo {
self.wasm.block_info()
}
pub fn set_bank_balance(
&mut self,
account: HumanAddr,
amount: Vec<Coin>,
) -> Result<(), String> {
self.bank.set_balance(account, amount)
}
pub fn store_code(&mut self, code: Box<dyn Contract>) -> u64 {
self.wasm.store_code(code) as u64
}
pub fn wrap(&self) -> QuerierWrapper {
QuerierWrapper::new(self)
}
pub fn query(&self, request: QueryRequest<Empty>) -> Result<Binary, String> {
match request {
QueryRequest::Wasm(req) => self.wasm.query(self, req),
QueryRequest::Bank(req) => self.bank.query(req),
_ => unimplemented!(),
}
}
pub fn instantiate_contract<T: Serialize, U: Into<String>, V: Into<HumanAddr>>(
&mut self,
code_id: u64,
sender: V,
init_msg: &T,
send_funds: &[Coin],
label: U,
) -> Result<HumanAddr, String> {
let init_msg = to_binary(init_msg).map_err(|e| e.to_string())?;
let msg: CosmosMsg = WasmMsg::Instantiate {
code_id,
msg: init_msg,
send: send_funds.to_vec(),
label: label.into(),
}
.into();
let res = self.execute(sender.into(), msg)?;
parse_contract_addr(&res.data)
}
pub fn execute_contract<T: Serialize, U: Into<HumanAddr>>(
&mut self,
sender: U,
contract_addr: U,
msg: &T,
send_funds: &[Coin],
) -> Result<AppResponse, String> {
let msg = to_binary(msg).map_err(|e| e.to_string())?;
let msg = WasmMsg::Execute {
contract_addr: contract_addr.into(),
msg,
send: send_funds.to_vec(),
}
.into();
self.execute(sender.into(), msg)
}
pub fn execute(
&mut self,
sender: HumanAddr,
msg: CosmosMsg<Empty>,
) -> Result<AppResponse, String> {
let mut all = self.execute_multi(sender, vec![msg])?;
let res = all.pop().unwrap();
Ok(res)
}
pub fn execute_multi(
&mut self,
sender: HumanAddr,
msgs: Vec<CosmosMsg<Empty>>,
) -> Result<Vec<AppResponse>, String> {
let mut cache = self.cache();
let res: Result<Vec<AppResponse>, String> = msgs
.into_iter()
.map(|msg| cache.execute(sender.clone(), msg))
.collect();
if res.is_ok() {
let ops = cache.prepare();
ops.commit(self);
}
res
}
}
pub struct AppCache<'a> {
router: &'a App,
wasm: WasmCache<'a>,
bank: BankCache<'a>,
}
pub struct AppOps {
wasm: WasmOps,
bank: BankOps,
}
impl AppOps {
pub fn commit(self, router: &mut App) {
self.bank.commit(&mut router.bank);
self.wasm.commit(&mut router.wasm);
}
}
impl<'a> AppCache<'a> {
fn new(router: &'a App) -> Self {
AppCache {
router,
wasm: router.wasm.cache(),
bank: router.bank.cache(),
}
}
pub fn prepare(self) -> AppOps {
AppOps {
wasm: self.wasm.prepare(),
bank: self.bank.prepare(),
}
}
fn execute(&mut self, sender: HumanAddr, msg: CosmosMsg<Empty>) -> Result<AppResponse, String> {
match msg {
CosmosMsg::Wasm(msg) => {
let (resender, res) = self.handle_wasm(sender, msg)?;
let mut attributes = res.attributes;
for resend in res.messages {
let subres = self.execute(resender.clone(), resend)?;
attributes.extend_from_slice(&subres.attributes);
}
Ok(AppResponse {
attributes,
data: res.data,
})
}
CosmosMsg::Bank(msg) => {
self.bank.execute(sender, msg)?;
Ok(AppResponse::default())
}
_ => unimplemented!(),
}
}
fn handle_wasm(
&mut self,
sender: HumanAddr,
msg: WasmMsg,
) -> Result<(HumanAddr, ActionResponse), String> {
match msg {
WasmMsg::Execute {
contract_addr,
msg,
send,
} => {
self.send(&sender, &contract_addr, &send)?;
let info = MessageInfo {
sender,
funds: send,
};
let res =
self.wasm
.handle(contract_addr.clone(), self.router, info, msg.to_vec())?;
Ok((contract_addr, res.into()))
}
WasmMsg::Instantiate {
code_id,
msg,
send,
label: _,
} => {
let contract_addr = self.wasm.register_contract(code_id as usize)?;
self.send(&sender, &contract_addr, &send)?;
let info = MessageInfo {
sender,
funds: send,
};
let res = self
.wasm
.init(contract_addr.clone(), self.router, info, msg.to_vec())?;
Ok((
contract_addr.clone(),
ActionResponse::init(res, contract_addr),
))
}
WasmMsg::Migrate { .. } => unimplemented!(),
m => panic!("Unsupported wasm message: {:?}", m),
}
}
fn send<T: Into<HumanAddr>, U: Into<HumanAddr>>(
&mut self,
sender: T,
recipient: U,
amount: &[Coin],
) -> Result<AppResponse, String> {
if !amount.is_empty() {
let sender: HumanAddr = sender.into();
let msg = BankMsg::Send {
to_address: recipient.into(),
amount: amount.to_vec(),
};
self.bank.execute(sender, msg)?;
}
Ok(AppResponse::default())
}
}
pub fn parse_contract_addr(data: &Option<Binary>) -> Result<HumanAddr, String> {
let bin = data
.as_ref()
.ok_or_else(|| "No data response".to_string())?
.to_vec();
let str = String::from_utf8(bin).map_err(|e| e.to_string())?;
Ok(HumanAddr::from(str))
}
#[cfg(test)]
mod test {
use super::*;
use crate::test_helpers::{
contract_payout, contract_reflect, EmptyMsg, PayoutMessage, ReflectMessage, ReflectResponse,
};
use crate::SimpleBank;
use cosmwasm_std::testing::MockStorage;
use cosmwasm_std::{attr, coin, coins};
fn mock_router() -> App {
let env = mock_env();
let api = Box::new(MockApi::default());
let bank = SimpleBank {};
App::new(api, env.block, bank, || Box::new(MockStorage::new()))
}
fn get_balance(router: &App, addr: &HumanAddr) -> Vec<Coin> {
router.wrap().query_all_balances(addr).unwrap()
}
#[test]
fn send_tokens() {
let mut router = mock_router();
let owner = HumanAddr::from("owner");
let rcpt = HumanAddr::from("receiver");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
let rcpt_funds = vec![coin(5, "btc")];
router
.set_bank_balance(owner.clone(), init_funds.clone())
.unwrap();
router
.set_bank_balance(rcpt.clone(), rcpt_funds.clone())
.unwrap();
let to_send = vec![coin(30, "eth"), coin(5, "btc")];
let msg: CosmosMsg = BankMsg::Send {
to_address: rcpt.clone(),
amount: to_send.clone(),
}
.into();
router.execute(owner.clone(), msg.clone()).unwrap();
let rich = get_balance(&router, &owner);
assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich);
let poor = get_balance(&router, &rcpt);
assert_eq!(vec![coin(10, "btc"), coin(30, "eth")], poor);
router.execute(rcpt.clone(), msg).unwrap();
let msg = BankMsg::Send {
to_address: rcpt.clone(),
amount: coins(20, "btc"),
}
.into();
router.execute(owner.clone(), msg).unwrap_err();
let rich = get_balance(&router, &owner);
assert_eq!(vec![coin(15, "btc"), coin(70, "eth")], rich);
}
#[test]
fn simple_contract() {
let mut router = mock_router();
let owner = HumanAddr::from("owner");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
router
.set_bank_balance(owner.clone(), init_funds.clone())
.unwrap();
let code_id = router.store_code(contract_payout());
let msg = PayoutMessage {
payout: coin(5, "eth"),
};
let contract_addr = router
.instantiate_contract(code_id, &owner, &msg, &coins(23, "eth"), "Payout")
.unwrap();
let sender = get_balance(&router, &owner);
assert_eq!(sender, vec![coin(20, "btc"), coin(77, "eth")]);
let funds = get_balance(&router, &contract_addr);
assert_eq!(funds, coins(23, "eth"));
let random = HumanAddr::from("random");
let funds = get_balance(&router, &random);
assert_eq!(funds, vec![]);
let res = router
.execute_contract(&random, &contract_addr, &EmptyMsg {}, &[])
.unwrap();
assert_eq!(1, res.attributes.len());
assert_eq!(&attr("action", "payout"), &res.attributes[0]);
let funds = get_balance(&router, &random);
assert_eq!(funds, coins(5, "eth"));
let funds = get_balance(&router, &contract_addr);
assert_eq!(funds, coins(18, "eth"));
}
#[test]
fn reflect_success() {
let mut router = mock_router();
let owner = HumanAddr::from("owner");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
router
.set_bank_balance(owner.clone(), init_funds.clone())
.unwrap();
let payout_id = router.store_code(contract_payout());
let msg = PayoutMessage {
payout: coin(5, "eth"),
};
let payout_addr = router
.instantiate_contract(payout_id, &owner, &msg, &coins(23, "eth"), "Payout")
.unwrap();
let reflect_id = router.store_code(contract_reflect());
let reflect_addr = router
.instantiate_contract(reflect_id, &owner, &EmptyMsg {}, &[], "Reflect")
.unwrap();
let funds = get_balance(&router, &reflect_addr);
assert_eq!(funds, vec![]);
let qres: ReflectResponse = router
.wrap()
.query_wasm_smart(&reflect_addr, &EmptyMsg {})
.unwrap();
assert_eq!(1, qres.count);
let msg = WasmMsg::Execute {
contract_addr: payout_addr.clone(),
msg: b"{}".into(),
send: vec![],
}
.into();
let msgs = ReflectMessage {
messages: vec![msg],
};
let res = router
.execute_contract(&HumanAddr::from("random"), &reflect_addr, &msgs, &[])
.unwrap();
assert_eq!(1, res.attributes.len());
assert_eq!(&attr("action", "payout"), &res.attributes[0]);
let funds = get_balance(&router, &reflect_addr);
assert_eq!(funds, coins(5, "eth"));
let qres: ReflectResponse = router
.wrap()
.query_wasm_smart(&reflect_addr, &EmptyMsg {})
.unwrap();
assert_eq!(2, qres.count);
}
#[test]
fn reflect_error() {
let mut router = mock_router();
let owner = HumanAddr::from("owner");
let init_funds = vec![coin(20, "btc"), coin(100, "eth")];
router
.set_bank_balance(owner.clone(), init_funds.clone())
.unwrap();
let reflect_id = router.store_code(contract_reflect());
let reflect_addr = router
.instantiate_contract(
reflect_id,
&owner,
&EmptyMsg {},
&coins(40, "eth"),
"Reflect",
)
.unwrap();
let funds = get_balance(&router, &reflect_addr);
assert_eq!(funds, coins(40, "eth"));
let random = HumanAddr::from("random");
let msg = BankMsg::Send {
to_address: random.clone(),
amount: coins(7, "eth"),
}
.into();
let msgs = ReflectMessage {
messages: vec![msg],
};
let res = router
.execute_contract(&random, &reflect_addr, &msgs, &[])
.unwrap();
assert_eq!(0, res.attributes.len());
let funds = get_balance(&router, &random);
assert_eq!(funds, coins(7, "eth"));
let qres: ReflectResponse = router
.wrap()
.query_wasm_smart(&reflect_addr, &EmptyMsg {})
.unwrap();
assert_eq!(2, qres.count);
let msg = BankMsg::Send {
to_address: random.clone(),
amount: coins(8, "eth"),
}
.into();
let msg2 = BankMsg::Send {
to_address: random.clone(),
amount: coins(3, "btc"),
}
.into();
let msgs = ReflectMessage {
messages: vec![msg, msg2],
};
let err = router
.execute_contract(&random, &reflect_addr, &msgs, &[])
.unwrap_err();
assert_eq!("Cannot subtract 3 from 0", err.as_str());
let funds = get_balance(&router, &random);
assert_eq!(funds, coins(7, "eth"));
let qres: ReflectResponse = router
.wrap()
.query_wasm_smart(&reflect_addr, &EmptyMsg {})
.unwrap();
assert_eq!(2, qres.count);
}
}