clone_cw_multi_test/
contracts.rs

1use std::{
2    error::Error,
3    fmt::{self, Debug, Display},
4};
5
6use schemars::JsonSchema;
7
8use cosmwasm_std::{
9    from_json, Binary, Checksum, CustomMsg, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, QuerierWrapper, Reply, Response, StdError
10};
11
12use anyhow::Result as AnyResult;
13use serde::de::DeserializeOwned;
14
15use crate::wasm_emulation::{
16    query::{mock_querier::ForkState, MockQuerier},
17    storage::{
18        dual_std_storage::DualStorage,
19        storage_wrappers::{ReadonlyStorageWrapper, StorageWrapper},
20    },
21};
22use anyhow::{anyhow, bail};
23/// Interface to call into a [Contract].
24pub trait Contract<T, Q = Empty>
25where
26    T: CustomMsg + DeserializeOwned + Clone + std::fmt::Debug + PartialEq + JsonSchema,
27    Q: CustomQuery + DeserializeOwned,
28{
29    fn execute(
30        &self,
31        deps: DepsMut<Q>,
32        env: Env,
33        info: MessageInfo,
34        msg: Vec<u8>,
35        fork_state: ForkState<T, Q>,
36    ) -> AnyResult<Response<T>>;
37
38    fn instantiate(
39        &self,
40        deps: DepsMut<Q>,
41        env: Env,
42        info: MessageInfo,
43        msg: Vec<u8>,
44        fork_state: ForkState<T, Q>,
45    ) -> AnyResult<Response<T>>;
46
47    fn query(
48        &self,
49        deps: Deps<Q>,
50        env: Env,
51        msg: Vec<u8>,
52        fork_state: ForkState<T, Q>,
53    ) -> AnyResult<Binary>;
54
55    fn sudo(
56        &self,
57        deps: DepsMut<Q>,
58        env: Env,
59        msg: Vec<u8>,
60        fork_state: ForkState<T, Q>,
61    ) -> AnyResult<Response<T>>;
62
63    fn reply(
64        &self,
65        deps: DepsMut<Q>,
66        env: Env,
67        msg: Reply,
68        fork_state: ForkState<T, Q>,
69    ) -> AnyResult<Response<T>>;
70
71    fn migrate(
72        &self,
73        deps: DepsMut<Q>,
74        env: Env,
75        msg: Vec<u8>,
76        fork_state: ForkState<T, Q>,
77    ) -> AnyResult<Response<T>>;
78
79    /// Returns the provided checksum of the contract's Wasm blob.
80    fn checksum(&self) -> Option<Checksum> {
81        None
82    }
83}
84
85type ContractFn<T, C, E, Q> =
86    fn(deps: DepsMut<Q>, env: Env, info: MessageInfo, msg: T) -> Result<Response<C>, E>;
87type PermissionedFn<T, C, E, Q> = fn(deps: DepsMut<Q>, env: Env, msg: T) -> Result<Response<C>, E>;
88type ReplyFn<C, E, Q> = fn(deps: DepsMut<Q>, env: Env, msg: Reply) -> Result<Response<C>, E>;
89type QueryFn<T, E, Q> = fn(deps: Deps<Q>, env: Env, msg: T) -> Result<Binary, E>;
90
91type ContractClosure<T, C, E, Q> = fn(DepsMut<Q>, Env, MessageInfo, T) -> Result<Response<C>, E>;
92type PermissionedClosure<T, C, E, Q> = fn(DepsMut<Q>, Env, T) -> Result<Response<C>, E>;
93type ReplyClosure<C, E, Q> = fn(DepsMut<Q>, Env, Reply) -> Result<Response<C>, E>;
94type QueryClosure<T, E, Q> = fn(Deps<Q>, Env, T) -> Result<Binary, E>;
95
96#[derive(Clone, Copy)]
97/// Wraps the exported functions from a contract and provides the normalized format
98/// Place T4 and E4 at the end, as we just want default placeholders for most contracts that don't have sudo
99pub struct ContractWrapper<
100    T1,
101    T2,
102    T3,
103    E1,
104    E2,
105    E3,
106    C = Empty,
107    Q = Empty,
108    T4 = Empty,
109    E4 = StdError,
110    E5 = StdError,
111    T6 = Empty,
112    E6 = StdError,
113> where
114    T1: DeserializeOwned + Debug,
115    T2: DeserializeOwned,
116    T3: DeserializeOwned,
117    T4: DeserializeOwned,
118    T6: DeserializeOwned,
119    E1: Display + Debug + Send + Sync + 'static,
120    E2: Display + Debug + Send + Sync + 'static,
121    E3: Display + Debug + Send + Sync + 'static,
122    E4: Display + Debug + Send + Sync + 'static,
123    E5: Display + Debug + Send + Sync + 'static,
124    E6: Display + Debug + Send + Sync + 'static,
125    C: Clone + fmt::Debug + PartialEq + JsonSchema,
126    Q: CustomQuery + DeserializeOwned + 'static,
127{
128    execute_fn: ContractClosure<T1, C, E1, Q>,
129    instantiate_fn: ContractClosure<T2, C, E2, Q>,
130    pub query_fn: QueryClosure<T3, E3, Q>,
131    sudo_fn: Option<PermissionedClosure<T4, C, E4, Q>>,
132    reply_fn: Option<ReplyClosure<C, E5, Q>>,
133    migrate_fn: Option<PermissionedClosure<T6, C, E6, Q>>,
134    checksum: Option<Checksum>,
135}
136
137impl<T1, T2, T3, E1, E2, E3, C, Q> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q>
138where
139    T1: DeserializeOwned + Debug + 'static,
140    T2: DeserializeOwned + 'static,
141    T3: DeserializeOwned + 'static,
142    E1: Display + Debug + Send + Sync + 'static,
143    E2: Display + Debug + Send + Sync + 'static,
144    E3: Display + Debug + Send + Sync + 'static,
145    C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static,
146    Q: CustomQuery + DeserializeOwned + 'static,
147{
148    pub fn new(
149        execute_fn: ContractFn<T1, C, E1, Q>,
150        instantiate_fn: ContractFn<T2, C, E2, Q>,
151        query_fn: QueryFn<T3, E3, Q>,
152    ) -> Self {
153        Self {
154            execute_fn,
155            instantiate_fn,
156            query_fn,
157            sudo_fn: None,
158            reply_fn: None,
159            migrate_fn: None,
160            checksum: None,
161        }
162    }
163}
164
165impl<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
166    ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
167where
168    T1: DeserializeOwned + Debug + 'static,
169    T2: DeserializeOwned + 'static,
170    T3: DeserializeOwned + 'static,
171    T4: DeserializeOwned + 'static,
172    T6: DeserializeOwned + 'static,
173    E1: Display + Debug + Send + Sync + 'static,
174    E2: Display + Debug + Send + Sync + 'static,
175    E3: Display + Debug + Send + Sync + 'static,
176    E4: Display + Debug + Send + Sync + 'static,
177    E5: Display + Debug + Send + Sync + 'static,
178    E6: Display + Debug + Send + Sync + 'static,
179    C: Clone + fmt::Debug + PartialEq + JsonSchema + 'static,
180    Q: CustomQuery + DeserializeOwned + 'static,
181{
182    pub fn with_sudo<T4A, E4A>(
183        self,
184        sudo_fn: PermissionedFn<T4A, C, E4A, Q>,
185    ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4A, E4A, E5, T6, E6>
186    where
187        T4A: DeserializeOwned + 'static,
188        E4A: Display + Debug + Send + Sync + 'static,
189    {
190        ContractWrapper {
191            execute_fn: self.execute_fn,
192            instantiate_fn: self.instantiate_fn,
193            query_fn: self.query_fn,
194            sudo_fn: Some(sudo_fn),
195            reply_fn: self.reply_fn,
196            migrate_fn: self.migrate_fn,
197            checksum: None,
198        }
199    }
200
201    pub fn with_reply<E5A>(
202        self,
203        reply_fn: ReplyFn<C, E5A, Q>,
204    ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5A, T6, E6>
205    where
206        E5A: Display + Debug + Send + Sync + 'static,
207    {
208        ContractWrapper {
209            execute_fn: self.execute_fn,
210            instantiate_fn: self.instantiate_fn,
211            query_fn: self.query_fn,
212            sudo_fn: self.sudo_fn,
213            reply_fn: Some(reply_fn),
214            migrate_fn: self.migrate_fn,
215            checksum: None,
216        }
217    }
218
219    pub fn with_migrate<T6A, E6A>(
220        self,
221        migrate_fn: PermissionedFn<T6A, C, E6A, Q>,
222    ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6A, E6A>
223    where
224        T6A: DeserializeOwned + 'static,
225        E6A: Display + Debug + Send + Sync + 'static,
226    {
227        ContractWrapper {
228            execute_fn: self.execute_fn,
229            instantiate_fn: self.instantiate_fn,
230            query_fn: self.query_fn,
231            sudo_fn: self.sudo_fn,
232            reply_fn: self.reply_fn,
233            migrate_fn: Some(migrate_fn),
234            checksum: None,
235        }
236    }
237    /// Populates [ContractWrapper] with the provided checksum of the contract's Wasm blob.
238    pub fn with_checksum(mut self, checksum: Checksum) -> Self {
239        self.checksum = Some(checksum);
240        self
241    }
242}
243
244impl<T1, T2, T3, E1, E2, E3, C, T4, E4, E5, T6, E6, Q> Contract<C, Q>
245    for ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
246where
247    T1: DeserializeOwned + Debug + Clone,
248    T2: DeserializeOwned + Debug + Clone,
249    T3: DeserializeOwned + Debug + Clone,
250    T4: DeserializeOwned,
251    T6: DeserializeOwned,
252    E1: Display + Debug + Send + Sync + Error + 'static,
253    E2: Display + Debug + Send + Sync + Error + 'static,
254    E3: Display + Debug + Send + Sync + Error + 'static,
255    E4: Display + Debug + Send + Sync + 'static,
256    E5: Display + Debug + Send + Sync + 'static,
257    E6: Display + Debug + Send + Sync + 'static,
258    C: CustomMsg + DeserializeOwned + Clone + fmt::Debug + PartialEq + JsonSchema,
259    Q: CustomQuery + DeserializeOwned,
260{
261    fn execute(
262        &self,
263        deps: DepsMut<Q>,
264        env: Env,
265        info: MessageInfo,
266        msg: Vec<u8>,
267        fork_state: ForkState<C, Q>,
268    ) -> AnyResult<Response<C>> {
269        let querier = MockQuerier::new(fork_state.clone());
270        let mut storage = DualStorage::new(
271            fork_state.remote,
272            env.contract.address.to_string(),
273            Box::new(StorageWrapper::new(deps.storage)),
274        )?;
275        let deps = DepsMut {
276            storage: &mut storage,
277            api: deps.api,
278            querier: QuerierWrapper::new(&querier),
279        };
280
281        let msg: T1 = from_json(msg)?;
282        (self.execute_fn)(deps, env, info, msg).map_err(|err| anyhow!(err))
283    }
284
285    fn instantiate(
286        &self,
287        deps: DepsMut<Q>,
288        env: Env,
289        info: MessageInfo,
290        msg: Vec<u8>,
291        fork_state: ForkState<C, Q>,
292    ) -> AnyResult<Response<C>> {
293        let querier = MockQuerier::new(fork_state.clone());
294        let mut storage = DualStorage::new(
295            fork_state.remote,
296            env.contract.address.to_string(),
297            Box::new(StorageWrapper::new(deps.storage)),
298        )?;
299        let deps = DepsMut {
300            storage: &mut storage,
301            api: deps.api,
302            querier: QuerierWrapper::new(&querier),
303        };
304        let msg: T2 = from_json(msg)?;
305        (self.instantiate_fn)(deps, env, info, msg).map_err(|err| anyhow!(err))
306    }
307
308    fn query(
309        &self,
310        deps: Deps<Q>,
311        env: Env,
312        msg: Vec<u8>,
313        fork_state: ForkState<C, Q>,
314    ) -> AnyResult<Binary> {
315        let querier = MockQuerier::new(fork_state.clone());
316        let mut storage = DualStorage::new(
317            fork_state.remote,
318            env.contract.address.to_string(),
319            Box::new(ReadonlyStorageWrapper::new(deps.storage)),
320        )?;
321        let deps = Deps {
322            storage: &mut storage,
323            api: deps.api,
324            querier: QuerierWrapper::new(&querier),
325        };
326        let msg: T3 = from_json(msg)?;
327        (self.query_fn)(deps, env, msg).map_err(|err| anyhow!(err))
328    }
329
330    // this returns an error if the contract doesn't implement sudo
331    fn sudo(
332        &self,
333        deps: DepsMut<Q>,
334        env: Env,
335        msg: Vec<u8>,
336        fork_state: ForkState<C, Q>,
337    ) -> AnyResult<Response<C>> {
338        let querier = MockQuerier::new(fork_state.clone());
339        let mut storage = DualStorage::new(
340            fork_state.remote,
341            env.contract.address.to_string(),
342            Box::new(StorageWrapper::new(deps.storage)),
343        )?;
344        let deps = DepsMut {
345            storage: &mut storage,
346            api: deps.api,
347            querier: QuerierWrapper::new(&querier),
348        };
349        let msg = from_json(msg)?;
350        match &self.sudo_fn {
351            Some(sudo) => sudo(deps, env, msg).map_err(|err| anyhow!(err)),
352            None => bail!("sudo not implemented for contract"),
353        }
354    }
355
356    // this returns an error if the contract doesn't implement reply
357    fn reply(
358        &self,
359        deps: DepsMut<Q>,
360        env: Env,
361        reply_data: Reply,
362        fork_state: ForkState<C, Q>,
363    ) -> AnyResult<Response<C>> {
364        let querier = MockQuerier::new(fork_state.clone());
365        let mut storage = DualStorage::new(
366            fork_state.remote,
367            env.contract.address.to_string(),
368            Box::new(StorageWrapper::new(deps.storage)),
369        )?;
370        let deps = DepsMut {
371            storage: &mut storage,
372            api: deps.api,
373            querier: QuerierWrapper::new(&querier),
374        };
375        match &self.reply_fn {
376            Some(reply) => reply(deps, env, reply_data).map_err(|err| anyhow!(err)),
377            None => bail!("reply not implemented for contract"),
378        }
379    }
380
381    // this returns an error if the contract doesn't implement migrate
382    fn migrate(
383        &self,
384        deps: DepsMut<Q>,
385        env: Env,
386        msg: Vec<u8>,
387        fork_state: ForkState<C, Q>,
388    ) -> AnyResult<Response<C>> {
389        let querier = MockQuerier::new(fork_state.clone());
390        let mut storage = DualStorage::new(
391            fork_state.remote,
392            env.contract.address.to_string(),
393            Box::new(StorageWrapper::new(deps.storage)),
394        )?;
395        let deps = DepsMut {
396            storage: &mut storage,
397            api: deps.api,
398            querier: QuerierWrapper::new(&querier),
399        };
400        let msg = from_json(msg)?;
401        match &self.migrate_fn {
402            Some(migrate) => migrate(deps, env, msg).map_err(|err| anyhow!(err)),
403            None => bail!("migrate not implemented for contract"),
404        }
405    }
406
407    /// Returns the provided checksum of the contract's Wasm blob.
408    fn checksum(&self) -> Option<Checksum> {
409        self.checksum
410    }
411}
412
413#[cfg(test)]
414pub mod test {
415
416    use cosmwasm_std::{
417        testing::{message_info, mock_dependencies, mock_env},
418        to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult,
419    };
420
421    use super::ContractWrapper;
422
423    fn execute(_deps: DepsMut, _env: Env, _info: MessageInfo, _msg: Empty) -> StdResult<Response> {
424        Ok(Response::new())
425    }
426
427    fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult<Binary> {
428        to_json_binary("resp")
429    }
430
431    fn instantiate(
432        _deps: DepsMut,
433        _env: Env,
434        _info: MessageInfo,
435        _msg: Empty,
436    ) -> StdResult<Response> {
437        Ok(Response::new())
438    }
439
440    #[test]
441    fn mock_contract() -> anyhow::Result<()> {
442        let contract = ContractWrapper::new(execute, instantiate, query);
443
444        let clone = contract.execute_fn;
445        let second_clone = clone;
446
447        clone(
448            mock_dependencies().as_mut(),
449            mock_env(),
450            message_info(&Addr::unchecked("sender"), &[]),
451            Empty {},
452        )?;
453
454        second_clone(
455            mock_dependencies().as_mut(),
456            mock_env(),
457            message_info(&Addr::unchecked("sender"), &[]),
458            Empty {},
459        )?;
460
461        Ok(())
462    }
463}