hermit_toolkit_utils/
calls.rs

1use serde::{de::DeserializeOwned, Serialize};
2
3use cosmwasm_std::{
4    to_binary, Coin, CosmosMsg, HumanAddr, Querier, QueryRequest, StdResult, Uint128, WasmMsg,
5    WasmQuery,
6};
7
8use super::space_pad;
9
10/// A trait marking types that define the instantiation message of a contract
11///
12/// This trait requires specifying a padding block size and provides a method to create the
13/// CosmosMsg used to instantiate a contract
14pub trait InitCallback: Serialize {
15    /// pad the message to blocks of this size
16    const BLOCK_SIZE: usize;
17
18    /// Returns StdResult<CosmosMsg>
19    ///
20    /// Tries to convert the instance of the implementing type to a CosmosMsg that will trigger the
21    /// instantiation of a contract.  The BLOCK_SIZE specified in the implementation is used when
22    /// padding the message
23    ///
24    /// # Arguments
25    ///
26    /// * `label` - String holding the label for the new contract instance
27    /// * `code_id` - code ID of the contract to be instantiated
28    /// * `callback_code_hash` - String holding the code hash of the contract to be instantiated
29    /// * `send_amount` - Optional Uint128 amount of native coin to send with instantiation message
30    fn to_cosmos_msg(
31        &self,
32        label: String,
33        code_id: u64,
34        callback_code_hash: String,
35        send_amount: Option<Uint128>,
36    ) -> StdResult<CosmosMsg> {
37        let mut msg = to_binary(self)?;
38        // can not have 0 block size
39        let padding = if Self::BLOCK_SIZE == 0 {
40            1
41        } else {
42            Self::BLOCK_SIZE
43        };
44        space_pad(&mut msg.0, padding);
45        let mut send = Vec::new();
46        if let Some(amount) = send_amount {
47            send.push(Coin {
48                amount,
49                denom: String::from("uscrt"),
50            });
51        }
52        let init = WasmMsg::Instantiate {
53            code_id,
54            msg,
55            callback_code_hash,
56            send,
57            label,
58        };
59        Ok(init.into())
60    }
61}
62
63/// A trait marking types that define the handle message(s) of a contract
64///
65/// This trait requires specifying a padding block size and provides a method to create the
66/// CosmosMsg used to execute a handle method of a contract
67pub trait HandleCallback: Serialize {
68    /// pad the message to blocks of this size
69    const BLOCK_SIZE: usize;
70
71    /// Returns StdResult<CosmosMsg>
72    ///
73    /// Tries to convert the instance of the implementing type to a CosmosMsg that will trigger a
74    /// handle function of a contract.  The BLOCK_SIZE specified in the implementation is used when
75    /// padding the message
76    ///
77    /// # Arguments
78    ///
79    /// * `callback_code_hash` - String holding the code hash of the contract to be executed
80    /// * `contract_addr` - address of the contract being called
81    /// * `send_amount` - Optional Uint128 amount of native coin to send with the handle message
82    fn to_cosmos_msg(
83        &self,
84        callback_code_hash: String,
85        contract_addr: HumanAddr,
86        send_amount: Option<Uint128>,
87    ) -> StdResult<CosmosMsg> {
88        let mut msg = to_binary(self)?;
89        // can not have 0 block size
90        let padding = if Self::BLOCK_SIZE == 0 {
91            1
92        } else {
93            Self::BLOCK_SIZE
94        };
95        space_pad(&mut msg.0, padding);
96        let mut send = Vec::new();
97        if let Some(amount) = send_amount {
98            send.push(Coin {
99                amount,
100                denom: String::from("uscrt"),
101            });
102        }
103        let execute = WasmMsg::Execute {
104            msg,
105            contract_addr,
106            callback_code_hash,
107            send,
108        };
109        Ok(execute.into())
110    }
111}
112
113/// A trait marking types that define the query message(s) of a contract
114///
115/// This trait requires specifying a padding block size and provides a method to query a contract
116pub trait Query: Serialize {
117    /// pad the message to blocks of this size
118    const BLOCK_SIZE: usize;
119
120    /// Returns StdResult<T>, where T is the type defining the query response
121    ///
122    /// Tries to query a contract and deserialize the query response.  The BLOCK_SIZE specified in the
123    /// implementation is used when padding the message
124    ///
125    /// # Arguments
126    ///
127    /// * `querier` - a reference to the Querier dependency of the querying contract
128    /// * `callback_code_hash` - String holding the code hash of the contract to be queried
129    /// * `contract_addr` - address of the contract being queried
130    fn query<Q: Querier, T: DeserializeOwned>(
131        &self,
132        querier: &Q,
133        callback_code_hash: String,
134        contract_addr: HumanAddr,
135    ) -> StdResult<T> {
136        let mut msg = to_binary(self)?;
137        // can not have 0 block size
138        let padding = if Self::BLOCK_SIZE == 0 {
139            1
140        } else {
141            Self::BLOCK_SIZE
142        };
143        space_pad(&mut msg.0, padding);
144        querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
145            contract_addr,
146            callback_code_hash,
147            msg,
148        }))
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use cosmwasm_std::{to_vec, Binary, Querier, QuerierResult};
156    use serde::Deserialize;
157
158    #[derive(Serialize)]
159    struct FooInit {
160        pub f1: i8,
161        pub f2: i8,
162    }
163
164    impl InitCallback for FooInit {
165        const BLOCK_SIZE: usize = 256;
166    }
167
168    #[derive(Serialize)]
169    enum FooHandle {
170        Var1 { f1: i8, f2: i8 },
171    }
172
173    // All you really need to do is make people give you the padding block size.
174    impl HandleCallback for FooHandle {
175        const BLOCK_SIZE: usize = 256;
176    }
177
178    #[derive(Serialize)]
179    enum FooQuery {
180        Query1 { f1: i8, f2: i8 },
181    }
182
183    impl Query for FooQuery {
184        const BLOCK_SIZE: usize = 256;
185    }
186
187    #[test]
188    fn test_handle_callback_implementation_works() -> StdResult<()> {
189        let address = HumanAddr("secret1xyzasdf".to_string());
190        let hash = "asdf".to_string();
191        let amount = Uint128(1234);
192
193        let cosmos_message: CosmosMsg = FooHandle::Var1 { f1: 1, f2: 2 }.to_cosmos_msg(
194            hash.clone(),
195            address.clone(),
196            Some(amount),
197        )?;
198
199        match cosmos_message {
200            CosmosMsg::Wasm(WasmMsg::Execute {
201                contract_addr,
202                callback_code_hash,
203                msg,
204                send,
205            }) => {
206                assert_eq!(contract_addr, address);
207                assert_eq!(callback_code_hash, hash);
208                let mut expected_msg = r#"{"Var1":{"f1":1,"f2":2}}"#.as_bytes().to_vec();
209                space_pad(&mut expected_msg, 256);
210                assert_eq!(msg.0, expected_msg);
211                assert_eq!(send, vec![Coin::new(amount.0, "uscrt")])
212            }
213            other => panic!("unexpected CosmosMsg variant: {:?}", other),
214        };
215
216        Ok(())
217    }
218
219    #[test]
220    fn test_init_callback_implementation_works() -> StdResult<()> {
221        let lbl = "testlabel".to_string();
222        let id = 17u64;
223        let hash = "asdf".to_string();
224        let amount = Uint128(1234);
225
226        let cosmos_message: CosmosMsg =
227            FooInit { f1: 1, f2: 2 }.to_cosmos_msg(lbl.clone(), id, hash.clone(), Some(amount))?;
228
229        match cosmos_message {
230            CosmosMsg::Wasm(WasmMsg::Instantiate {
231                code_id,
232                msg,
233                callback_code_hash,
234                send,
235                label,
236            }) => {
237                assert_eq!(code_id, id);
238                let mut expected_msg = r#"{"f1":1,"f2":2}"#.as_bytes().to_vec();
239                space_pad(&mut expected_msg, 256);
240                assert_eq!(msg.0, expected_msg);
241                assert_eq!(callback_code_hash, hash);
242                assert_eq!(send, vec![Coin::new(amount.0, "uscrt")]);
243                assert_eq!(label, lbl)
244            }
245            other => panic!("unexpected CosmosMsg variant: {:?}", other),
246        };
247
248        Ok(())
249    }
250
251    #[test]
252    fn test_query_works() -> StdResult<()> {
253        #[derive(Serialize, Deserialize, PartialEq, Debug)]
254        struct QueryResponse {
255            bar1: i8,
256            bar2: i8,
257        }
258
259        struct MyMockQuerier {}
260
261        impl Querier for MyMockQuerier {
262            fn raw_query(&self, request: &[u8]) -> QuerierResult {
263                let mut expected_msg = r#"{"Query1":{"f1":1,"f2":2}}"#.as_bytes().to_vec();
264                space_pad(&mut expected_msg, 256);
265                let expected_request: QueryRequest<FooQuery> =
266                    QueryRequest::Wasm(WasmQuery::Smart {
267                        contract_addr: HumanAddr("secret1xyzasdf".to_string()),
268                        callback_code_hash: "asdf".to_string(),
269                        msg: Binary(expected_msg),
270                    });
271                let test_req: &[u8] = &to_vec(&expected_request).unwrap();
272                assert_eq!(request, test_req);
273                Ok(to_binary(&QueryResponse { bar1: 1, bar2: 2 }))
274            }
275        }
276
277        let querier = MyMockQuerier {};
278        let address = HumanAddr("secret1xyzasdf".to_string());
279        let hash = "asdf".to_string();
280
281        let response: QueryResponse =
282            FooQuery::Query1 { f1: 1, f2: 2 }.query(&querier, hash, address)?;
283        assert_eq!(response, QueryResponse { bar1: 1, bar2: 2 });
284
285        Ok(())
286    }
287}