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};
23pub 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 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)]
97pub 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 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 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 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 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 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}