apollo_cw_multi_test/
executor.rs

1use std::fmt;
2
3use cosmwasm_std::{
4    to_binary, Addr, Attribute, BankMsg, Binary, Coin, CosmosMsg, Event, SubMsgResponse, WasmMsg,
5};
6use cw_utils::{parse_execute_response_data, parse_instantiate_response_data};
7use schemars::JsonSchema;
8use serde::Serialize;
9
10use anyhow::Result as AnyResult;
11
12#[derive(Default, Clone, Debug)]
13pub struct AppResponse {
14    pub events: Vec<Event>,
15    pub data: Option<Binary>,
16}
17
18impl AppResponse {
19    // Return all custom attributes returned by the contract in the `idx` event.
20    // We assert the type is wasm, and skip the contract_address attribute.
21    #[track_caller]
22    pub fn custom_attrs(&self, idx: usize) -> &[Attribute] {
23        assert_eq!(self.events[idx].ty.as_str(), "wasm");
24        &self.events[idx].attributes[1..]
25    }
26
27    /// Check if there is an Event that is a super-set of this.
28    /// It has the same type, and all compare.attributes are included in it as well.
29    /// You don't need to specify them all.
30    pub fn has_event(&self, expected: &Event) -> bool {
31        self.events.iter().any(|ev| {
32            expected.ty == ev.ty
33                && expected
34                    .attributes
35                    .iter()
36                    .all(|at| ev.attributes.contains(at))
37        })
38    }
39
40    /// Like has_event but panics if no match
41    #[track_caller]
42    pub fn assert_event(&self, expected: &Event) {
43        assert!(
44            self.has_event(expected),
45            "Expected to find an event {:?}, but received: {:?}",
46            expected,
47            self.events
48        );
49    }
50}
51
52/// They have the same shape, SubMsgExecutionResponse is what is returned in reply.
53/// This is just to make some test cases easier.
54impl From<SubMsgResponse> for AppResponse {
55    fn from(reply: SubMsgResponse) -> Self {
56        AppResponse {
57            data: reply.data,
58            events: reply.events,
59        }
60    }
61}
62
63pub trait Executor<C>
64where
65    C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static,
66{
67    /// Runs arbitrary CosmosMsg.
68    /// This will create a cache before the execution, so no state changes are persisted if this
69    /// returns an error, but all are persisted on success.
70    fn execute(&self, sender: Addr, msg: CosmosMsg<C>) -> AnyResult<AppResponse>;
71
72    /// Create a contract and get the new address.
73    /// This is just a helper around execute()
74    fn instantiate_contract<T: Serialize, U: Into<String>>(
75        &self,
76        code_id: u64,
77        sender: Addr,
78        init_msg: &T,
79        send_funds: &[Coin],
80        label: U,
81        admin: Option<String>,
82    ) -> AnyResult<Addr> {
83        // instantiate contract
84        let init_msg = to_binary(init_msg)?;
85        let msg = WasmMsg::Instantiate {
86            admin,
87            code_id,
88            msg: init_msg,
89            funds: send_funds.to_vec(),
90            label: label.into(),
91        };
92        let res = self.execute(sender, msg.into())?;
93        let data = parse_instantiate_response_data(res.data.unwrap_or_default().as_slice())?;
94        Ok(Addr::unchecked(data.contract_address))
95    }
96
97    /// Execute a contract and process all returned messages.
98    /// This is just a helper around execute(),
99    /// but we parse out the data field to that what is returned by the contract (not the protobuf wrapper)
100    fn execute_contract<T: Serialize + std::fmt::Debug>(
101        &self,
102        sender: Addr,
103        contract_addr: Addr,
104        msg: &T,
105        send_funds: &[Coin],
106    ) -> AnyResult<AppResponse> {
107        let binary_msg = to_binary(msg)?;
108        let wrapped_msg = WasmMsg::Execute {
109            contract_addr: contract_addr.into_string(),
110            msg: binary_msg,
111            funds: send_funds.to_vec(),
112        };
113        let mut res = self.execute(sender, wrapped_msg.into())?;
114        res.data = res
115            .data
116            .and_then(|d| parse_execute_response_data(d.as_slice()).unwrap().data);
117        Ok(res)
118    }
119
120    /// Migrate a contract. Sender must be registered admin.
121    /// This is just a helper around execute()
122    fn migrate_contract<T: Serialize>(
123        &mut self,
124        sender: Addr,
125        contract_addr: Addr,
126        msg: &T,
127        new_code_id: u64,
128    ) -> AnyResult<AppResponse> {
129        let msg = to_binary(msg)?;
130        let msg = WasmMsg::Migrate {
131            contract_addr: contract_addr.into(),
132            msg,
133            new_code_id,
134        };
135        self.execute(sender, msg.into())
136    }
137
138    fn send_tokens(
139        &mut self,
140        sender: Addr,
141        recipient: Addr,
142        amount: &[Coin],
143    ) -> AnyResult<AppResponse> {
144        let msg = BankMsg::Send {
145            to_address: recipient.to_string(),
146            amount: amount.to_vec(),
147        };
148        self.execute(sender, msg.into())
149    }
150}