clone_cw_multi_test/
executor.rs

1use crate::error::AnyResult;
2use cosmwasm_std::{
3    to_json_binary, Addr, Attribute, BankMsg, Binary, Coin, CosmosMsg, Event, SubMsgResponse,
4    WasmMsg,
5};
6use cw_utils::{parse_execute_response_data, parse_instantiate_response_data};
7use schemars::JsonSchema;
8use serde::Serialize;
9use std::fmt::Debug;
10
11#[derive(Default, Clone, Debug)]
12pub struct AppResponse {
13    pub events: Vec<Event>,
14    pub data: Option<Binary>,
15}
16
17impl AppResponse {
18    // Return all custom attributes returned by the contract in the `idx` event.
19    // We assert the type is wasm, and skip the contract_address attribute.
20    #[track_caller]
21    pub fn custom_attrs(&self, idx: usize) -> &[Attribute] {
22        assert_eq!(self.events[idx].ty.as_str(), "wasm");
23        &self.events[idx].attributes[1..]
24    }
25
26    /// Check if there is an Event that is a super-set of this.
27    /// It has the same type, and all compare.attributes are included in it as well.
28    /// You don't need to specify them all.
29    pub fn has_event(&self, expected: &Event) -> bool {
30        self.events.iter().any(|ev| {
31            expected.ty == ev.ty
32                && expected
33                    .attributes
34                    .iter()
35                    .all(|at| ev.attributes.contains(at))
36        })
37    }
38
39    /// Like has_event but panics if no match
40    #[track_caller]
41    pub fn assert_event(&self, expected: &Event) {
42        assert!(
43            self.has_event(expected),
44            "Expected to find an event {:?}, but received: {:?}",
45            expected,
46            self.events
47        );
48    }
49}
50
51/// They have the same shape, SubMsgExecutionResponse is what is returned in reply.
52/// This is just to make some test cases easier.
53impl From<SubMsgResponse> for AppResponse {
54    fn from(reply: SubMsgResponse) -> Self {
55        AppResponse {
56            #[allow(deprecated)]
57            data: reply.data,
58            events: reply.events,
59        }
60    }
61}
62
63pub trait Executor<C>
64where
65    C: Clone + 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(&mut 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        &mut 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_json_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    /// Instantiates a new contract and returns its predictable address.
98    /// This is a helper function around [execute][Self::execute] function
99    /// with `WasmMsg::Instantiate2` message.
100    fn instantiate2_contract<M, L, A, S>(
101        &mut self,
102        code_id: u64,
103        sender: Addr,
104        init_msg: &M,
105        funds: &[Coin],
106        label: L,
107        admin: A,
108        salt: S,
109    ) -> AnyResult<Addr>
110    where
111        M: Serialize,
112        L: Into<String>,
113        A: Into<Option<String>>,
114        S: Into<Binary>,
115    {
116        let msg = WasmMsg::Instantiate2 {
117            admin: admin.into(),
118            code_id,
119            msg: to_json_binary(init_msg)?,
120            funds: funds.to_vec(),
121            label: label.into(),
122            salt: salt.into(),
123        };
124        let execute_response = self.execute(sender, msg.into())?;
125        let instantiate_response =
126            parse_instantiate_response_data(execute_response.data.unwrap_or_default().as_slice())?;
127        Ok(Addr::unchecked(instantiate_response.contract_address))
128    }
129
130    /// Execute a contract and process all returned messages.
131    /// This is just a helper around execute(),
132    /// but we parse out the data field to that what is returned by the contract (not the protobuf wrapper)
133    fn execute_contract<T: Serialize + Debug>(
134        &mut self,
135        sender: Addr,
136        contract_addr: Addr,
137        msg: &T,
138        send_funds: &[Coin],
139    ) -> AnyResult<AppResponse> {
140        let binary_msg = to_json_binary(msg)?;
141        let wrapped_msg = WasmMsg::Execute {
142            contract_addr: contract_addr.into_string(),
143            msg: binary_msg,
144            funds: send_funds.to_vec(),
145        };
146        let mut res = self.execute(sender, wrapped_msg.into())?;
147        res.data = res
148            .data
149            .and_then(|d| parse_execute_response_data(d.as_slice()).unwrap().data);
150        Ok(res)
151    }
152
153    /// Migrate a contract. Sender must be registered admin.
154    /// This is just a helper around execute()
155    fn migrate_contract<T: Serialize>(
156        &mut self,
157        sender: Addr,
158        contract_addr: Addr,
159        msg: &T,
160        new_code_id: u64,
161    ) -> AnyResult<AppResponse> {
162        let msg = to_json_binary(msg)?;
163        let msg = WasmMsg::Migrate {
164            contract_addr: contract_addr.into(),
165            msg,
166            new_code_id,
167        };
168        self.execute(sender, msg.into())
169    }
170
171    fn send_tokens(
172        &mut self,
173        sender: Addr,
174        recipient: Addr,
175        amount: &[Coin],
176    ) -> AnyResult<AppResponse> {
177        let msg = BankMsg::Send {
178            to_address: recipient.to_string(),
179            amount: amount.to_vec(),
180        };
181        self.execute(sender, msg.into())
182    }
183}