secret_toolkit_utils/
calls.rs

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