1use crate::error::{std_error, std_error_bail};
4#[cfg(feature = "cosmwasm_2_2")]
5use cosmwasm_std::MigrateInfo;
6use cosmwasm_std::{
7 from_json, Binary, Checksum, CosmosMsg, CustomMsg, CustomQuery, Deps, DepsMut, Empty, Env,
8 MessageInfo, QuerierWrapper, Reply, Response, StdError, StdResult, SubMsg,
9};
10use serde::de::DeserializeOwned;
11use std::fmt::{Debug, Display};
12use std::ops::Deref;
13
14#[rustfmt::skip]
16pub trait Contract<C, Q = Empty>
17where
18 C: CustomMsg,
19 Q: CustomQuery,
20{
21 fn instantiate(&self, deps: DepsMut<Q>, env: Env, info: MessageInfo, msg: Vec<u8>) -> StdResult<Response<C>>;
23
24 fn execute(&self, deps: DepsMut<Q>, env: Env, info: MessageInfo, msg: Vec<u8>) -> StdResult<Response<C>>;
26
27 fn query(&self, deps: Deps<Q>, env: Env, msg: Vec<u8>) -> StdResult<Binary>;
29
30 fn reply(&self, deps: DepsMut<Q>, env: Env, msg: Reply) -> StdResult<Response<C>>;
32
33 fn sudo(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>) -> StdResult<Response<C>>;
35
36 #[cfg(not(feature = "cosmwasm_2_2"))]
38 fn migrate(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>) -> StdResult<Response<C>>;
39
40 #[cfg(feature = "cosmwasm_2_2")]
42 fn migrate(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>, info: MigrateInfo) -> StdResult<Response<C>>;
43
44 fn checksum(&self) -> Option<Checksum> {
46 None
47 }
48}
49
50#[rustfmt::skip]
51mod closures {
52 use super::*;
53
54 pub type InstantiateFn<T, C, E, Q> = fn(deps: DepsMut<Q>, env: Env, info: MessageInfo, msg: T) -> Result<Response<C>, E>;
56 pub type ExecuteFn<T, C, E, Q> = fn(deps: DepsMut<Q>, env: Env, info: MessageInfo, msg: T) -> Result<Response<C>, E>;
57 pub type QueryFn<T, E, Q> = fn(deps: Deps<Q>, env: Env, msg: T) -> Result<Binary, E>;
58 pub type ReplyFn<C, E, Q> = fn(deps: DepsMut<Q>, env: Env, msg: Reply) -> Result<Response<C>, E>;
59 pub type SudoFn<T, C, E, Q> = fn(deps: DepsMut<Q>, env: Env, msg: T) -> Result<Response<C>, E>;
60 #[cfg(not(feature = "cosmwasm_2_2"))]
61 pub type MigrateFn<T, C, E, Q> = fn(deps: DepsMut<Q>, env: Env, msg: T) -> Result<Response<C>, E>;
62 #[cfg(feature = "cosmwasm_2_2")]
63 pub type MigrateFn<T, C, E, Q> = fn(deps: DepsMut<Q>, env: Env, msg: T, info: MigrateInfo) -> Result<Response<C>, E>;
64
65 pub type InstantiateClosure<T, C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, MessageInfo, T) -> Result<Response<C>, E>>;
67 pub type ExecuteClosure<T, C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, MessageInfo, T) -> Result<Response<C>, E>>;
68 pub type QueryClosure<T, E, Q> = Box<dyn Fn(Deps<Q>, Env, T) -> Result<Binary, E>>;
69 pub type ReplyClosure<C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, Reply) -> Result<Response<C>, E>>;
70 pub type SudoClosure<T, C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, T) -> Result<Response<C>, E>>;
71 #[cfg(not(feature = "cosmwasm_2_2"))]
72 pub type MigrateClosure<T, C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, T) -> Result<Response<C>, E>>;
73 #[cfg(feature = "cosmwasm_2_2")]
74 pub type MigrateClosure<T, C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, T, MigrateInfo) -> Result<Response<C>, E>>;
75}
76
77use closures::*;
78
79pub struct ContractWrapper<
147 T1,
148 T2,
149 T3,
150 E1,
151 E2,
152 E3,
153 C = Empty,
154 Q = Empty,
155 T4 = Empty,
156 E4 = StdError,
157 E5 = StdError,
158 T6 = Empty,
159 E6 = StdError,
160> where
161 T1: DeserializeOwned, T2: DeserializeOwned, T3: DeserializeOwned, T4: DeserializeOwned, T6: DeserializeOwned, E1: Display + Debug + Send + Sync, E2: Display + Debug + Send + Sync, E3: Display + Debug + Send + Sync, E4: Display + Debug + Send + Sync, E5: Display + Debug + Send + Sync, E6: Display + Debug + Send + Sync, C: CustomMsg, Q: CustomQuery + DeserializeOwned, {
175 execute_fn: ExecuteClosure<T1, C, E1, Q>,
176 instantiate_fn: InstantiateClosure<T2, C, E2, Q>,
177 query_fn: QueryClosure<T3, E3, Q>,
178 reply_fn: Option<ReplyClosure<C, E5, Q>>,
179 sudo_fn: Option<SudoClosure<T4, C, E4, Q>>,
180 migrate_fn: Option<MigrateClosure<T6, C, E6, Q>>,
181 checksum: Option<Checksum>,
182}
183
184impl<T1, T2, T3, E1, E2, E3, C, Q> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q>
185where
186 T1: DeserializeOwned + 'static, T2: DeserializeOwned + 'static, T3: DeserializeOwned + 'static, E1: Display + Debug + Send + Sync + 'static, E2: Display + Debug + Send + Sync + 'static, E3: Display + Debug + Send + Sync + 'static, C: CustomMsg + 'static, Q: CustomQuery + DeserializeOwned + 'static, {
195 pub fn new(
197 execute_fn: ExecuteFn<T1, C, E1, Q>,
198 instantiate_fn: InstantiateFn<T2, C, E2, Q>,
199 query_fn: QueryFn<T3, E3, Q>,
200 ) -> Self {
201 Self {
202 execute_fn: Box::new(execute_fn),
203 instantiate_fn: Box::new(instantiate_fn),
204 query_fn: Box::new(query_fn),
205 reply_fn: None,
206 sudo_fn: None,
207 migrate_fn: None,
208 checksum: None,
209 }
210 }
211
212 pub fn new_with_empty(
215 execute_fn: ExecuteFn<T1, Empty, E1, Empty>,
216 instantiate_fn: InstantiateFn<T2, Empty, E2, Empty>,
217 query_fn: QueryFn<T3, E3, Empty>,
218 ) -> Self {
219 Self {
220 execute_fn: customize_execute_fn(execute_fn),
221 instantiate_fn: customize_instantiate_fn(instantiate_fn),
222 query_fn: customize_query_fn(query_fn),
223 reply_fn: None,
224 sudo_fn: None,
225 migrate_fn: None,
226 checksum: None,
227 }
228 }
229}
230
231#[allow(clippy::type_complexity)]
232impl<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
233 ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
234where
235 T1: DeserializeOwned, T2: DeserializeOwned, T3: DeserializeOwned, T4: DeserializeOwned, T6: DeserializeOwned, E1: Display + Debug + Send + Sync, E2: Display + Debug + Send + Sync, E3: Display + Debug + Send + Sync, E4: Display + Debug + Send + Sync, E5: Display + Debug + Send + Sync, E6: Display + Debug + Send + Sync, C: CustomMsg + 'static, Q: CustomQuery + DeserializeOwned + 'static, {
249 pub fn with_sudo<T4A, E4A>(
251 self,
252 sudo_fn: SudoFn<T4A, C, E4A, Q>,
253 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4A, E4A, E5, T6, E6>
254 where
255 T4A: DeserializeOwned + 'static,
256 E4A: Display + Debug + Send + Sync + 'static,
257 {
258 ContractWrapper {
259 execute_fn: self.execute_fn,
260 instantiate_fn: self.instantiate_fn,
261 query_fn: self.query_fn,
262 reply_fn: self.reply_fn,
263 sudo_fn: Some(Box::new(sudo_fn)),
264 migrate_fn: self.migrate_fn,
265 checksum: None,
266 }
267 }
268
269 pub fn with_sudo_empty<T4A, E4A>(
271 self,
272 sudo_fn: SudoFn<T4A, Empty, E4A, Empty>,
273 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4A, E4A, E5, T6, E6>
274 where
275 T4A: DeserializeOwned + 'static,
276 E4A: Display + Debug + Send + Sync + 'static,
277 {
278 ContractWrapper {
279 execute_fn: self.execute_fn,
280 instantiate_fn: self.instantiate_fn,
281 query_fn: self.query_fn,
282 reply_fn: self.reply_fn,
283 sudo_fn: Some(customize_sudo_fn(sudo_fn)),
284 migrate_fn: self.migrate_fn,
285 checksum: None,
286 }
287 }
288
289 pub fn with_reply<E5A>(
291 self,
292 reply_fn: ReplyFn<C, E5A, Q>,
293 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5A, T6, E6>
294 where
295 E5A: Display + Debug + Send + Sync + 'static,
296 {
297 ContractWrapper {
298 execute_fn: self.execute_fn,
299 instantiate_fn: self.instantiate_fn,
300 query_fn: self.query_fn,
301 reply_fn: Some(Box::new(reply_fn)),
302 sudo_fn: self.sudo_fn,
303 migrate_fn: self.migrate_fn,
304 checksum: None,
305 }
306 }
307
308 pub fn with_reply_empty<E5A>(
310 self,
311 reply_fn: ReplyFn<Empty, E5A, Empty>,
312 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5A, T6, E6>
313 where
314 E5A: Display + Debug + Send + Sync + 'static,
315 {
316 ContractWrapper {
317 execute_fn: self.execute_fn,
318 instantiate_fn: self.instantiate_fn,
319 query_fn: self.query_fn,
320 reply_fn: Some(customize_reply_fn(reply_fn)),
321 sudo_fn: self.sudo_fn,
322 migrate_fn: self.migrate_fn,
323 checksum: None,
324 }
325 }
326
327 pub fn with_migrate<T6A, E6A>(
329 self,
330 migrate_fn: MigrateFn<T6A, C, E6A, Q>,
331 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6A, E6A>
332 where
333 T6A: DeserializeOwned + 'static,
334 E6A: Display + Debug + Send + Sync + 'static,
335 {
336 ContractWrapper {
337 execute_fn: self.execute_fn,
338 instantiate_fn: self.instantiate_fn,
339 query_fn: self.query_fn,
340 reply_fn: self.reply_fn,
341 sudo_fn: self.sudo_fn,
342 migrate_fn: Some(Box::new(migrate_fn)),
343 checksum: None,
344 }
345 }
346
347 pub fn with_migrate_empty<T6A, E6A>(
349 self,
350 migrate_fn: MigrateFn<T6A, Empty, E6A, Empty>,
351 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6A, E6A>
352 where
353 T6A: DeserializeOwned + 'static,
354 E6A: Display + Debug + Send + Sync + 'static,
355 {
356 ContractWrapper {
357 execute_fn: self.execute_fn,
358 instantiate_fn: self.instantiate_fn,
359 query_fn: self.query_fn,
360 reply_fn: self.reply_fn,
361 sudo_fn: self.sudo_fn,
362 migrate_fn: Some(customize_migrate_fn(migrate_fn)),
363 checksum: None,
364 }
365 }
366
367 pub fn with_checksum(mut self, checksum: Checksum) -> Self {
369 self.checksum = Some(checksum);
370 self
371 }
372}
373
374fn customize_instantiate_fn<T, C, E, Q>(
375 raw_fn: InstantiateFn<T, Empty, E, Empty>,
376) -> InstantiateClosure<T, C, E, Q>
377where
378 T: DeserializeOwned + 'static,
379 E: Display + Debug + Send + Sync + 'static,
380 C: CustomMsg,
381 Q: CustomQuery + DeserializeOwned,
382{
383 Box::new(
384 move |mut deps: DepsMut<Q>,
385 env: Env,
386 info: MessageInfo,
387 msg: T|
388 -> Result<Response<C>, E> {
389 let deps = decustomize_deps_mut(&mut deps);
390 raw_fn(deps, env, info, msg).map(customize_response::<C>)
391 },
392 )
393}
394
395fn customize_execute_fn<T, C, E, Q>(
396 raw_fn: ExecuteFn<T, Empty, E, Empty>,
397) -> ExecuteClosure<T, C, E, Q>
398where
399 T: DeserializeOwned + 'static,
400 E: Display + Debug + Send + Sync + 'static,
401 C: CustomMsg,
402 Q: CustomQuery + DeserializeOwned,
403{
404 Box::new(
405 move |mut deps: DepsMut<Q>,
406 env: Env,
407 info: MessageInfo,
408 msg: T|
409 -> Result<Response<C>, E> {
410 let deps = decustomize_deps_mut(&mut deps);
411 raw_fn(deps, env, info, msg).map(customize_response::<C>)
412 },
413 )
414}
415
416fn customize_query_fn<T, E, Q>(raw_fn: QueryFn<T, E, Empty>) -> QueryClosure<T, E, Q>
417where
418 T: DeserializeOwned + 'static,
419 E: Display + Debug + Send + Sync + 'static,
420 Q: CustomQuery + DeserializeOwned,
421{
422 Box::new(
423 move |deps: Deps<Q>, env: Env, msg: T| -> Result<Binary, E> {
424 let deps = decustomize_deps(&deps);
425 raw_fn(deps, env, msg)
426 },
427 )
428}
429
430fn customize_reply_fn<C, E, Q>(raw_fn: ReplyFn<Empty, E, Empty>) -> ReplyClosure<C, E, Q>
431where
432 E: Display + Debug + Send + Sync + 'static,
433 C: CustomMsg,
434 Q: CustomQuery + DeserializeOwned,
435{
436 Box::new(
437 move |mut deps: DepsMut<Q>, env: Env, msg: Reply| -> Result<Response<C>, E> {
438 let deps = decustomize_deps_mut(&mut deps);
439 raw_fn(deps, env, msg).map(customize_response::<C>)
440 },
441 )
442}
443
444fn customize_sudo_fn<T, C, E, Q>(raw_fn: SudoFn<T, Empty, E, Empty>) -> SudoClosure<T, C, E, Q>
445where
446 T: DeserializeOwned + 'static,
447 E: Display + Debug + Send + Sync + 'static,
448 C: CustomMsg,
449 Q: CustomQuery + DeserializeOwned,
450{
451 Box::new(
452 move |mut deps: DepsMut<Q>, env: Env, msg: T| -> Result<Response<C>, E> {
453 let deps = decustomize_deps_mut(&mut deps);
454 raw_fn(deps, env, msg).map(customize_response::<C>)
455 },
456 )
457}
458
459fn customize_migrate_fn<T, C, E, Q>(
460 raw_fn: MigrateFn<T, Empty, E, Empty>,
461) -> MigrateClosure<T, C, E, Q>
462where
463 T: DeserializeOwned + 'static,
464 E: Display + Debug + Send + Sync + 'static,
465 C: CustomMsg,
466 Q: CustomQuery + DeserializeOwned,
467{
468 Box::new(
469 #[cfg(not(feature = "cosmwasm_2_2"))]
470 move |mut deps: DepsMut<Q>, env: Env, msg: T| -> Result<Response<C>, E> {
471 let deps = decustomize_deps_mut(&mut deps);
472 raw_fn(deps, env, msg).map(customize_response::<C>)
473 },
474 #[cfg(feature = "cosmwasm_2_2")]
475 move |mut deps: DepsMut<Q>, env: Env, msg: T, inf: MigrateInfo| -> Result<Response<C>, E> {
476 let deps = decustomize_deps_mut(&mut deps);
477 raw_fn(deps, env, msg, inf).map(customize_response::<C>)
478 },
479 )
480}
481
482fn decustomize_deps_mut<'a, Q>(deps: &'a mut DepsMut<Q>) -> DepsMut<'a, Empty>
483where
484 Q: CustomQuery + DeserializeOwned,
485{
486 DepsMut {
487 storage: deps.storage,
488 api: deps.api,
489 querier: QuerierWrapper::new(deps.querier.deref()),
490 }
491}
492
493fn decustomize_deps<'a, Q>(deps: &'a Deps<'a, Q>) -> Deps<'a, Empty>
494where
495 Q: CustomQuery + DeserializeOwned,
496{
497 Deps {
498 storage: deps.storage,
499 api: deps.api,
500 querier: QuerierWrapper::new(deps.querier.deref()),
501 }
502}
503
504fn customize_response<C>(resp: Response<Empty>) -> Response<C>
505where
506 C: CustomMsg,
507{
508 let mut customized_resp = Response::<C>::new()
509 .add_submessages(resp.messages.into_iter().map(customize_msg::<C>))
510 .add_events(resp.events)
511 .add_attributes(resp.attributes);
512 customized_resp.data = resp.data;
513 customized_resp
514}
515
516fn customize_msg<C>(msg: SubMsg<Empty>) -> SubMsg<C>
517where
518 C: CustomMsg,
519{
520 SubMsg {
521 id: msg.id,
522 payload: msg.payload,
523 msg: match msg.msg {
524 CosmosMsg::Wasm(wasm) => CosmosMsg::Wasm(wasm),
525 CosmosMsg::Bank(bank) => CosmosMsg::Bank(bank),
526 #[cfg(feature = "staking")]
527 CosmosMsg::Staking(staking) => CosmosMsg::Staking(staking),
528 #[cfg(feature = "staking")]
529 CosmosMsg::Distribution(distribution) => CosmosMsg::Distribution(distribution),
530 CosmosMsg::Custom(_) => unreachable!(),
531 #[cfg(feature = "stargate")]
532 CosmosMsg::Ibc(ibc) => CosmosMsg::Ibc(ibc),
533 #[cfg(feature = "cosmwasm_2_0")]
534 CosmosMsg::Any(any) => CosmosMsg::Any(any),
535 other => panic!("unknown message variant {other:?}"),
536 },
537 gas_limit: msg.gas_limit,
538 reply_on: msg.reply_on,
539 }
540}
541
542impl<T1, T2, T3, E1, E2, E3, C, T4, E4, E5, T6, E6, Q> Contract<C, Q>
543 for ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
544where
545 T1: DeserializeOwned, T2: DeserializeOwned, T3: DeserializeOwned, T4: DeserializeOwned, T6: DeserializeOwned, E1: Display + Debug + Send + Sync + 'static, E2: Display + Debug + Send + Sync + 'static, E3: Display + Debug + Send + Sync + 'static, E4: Display + Debug + Send + Sync + 'static, E5: Display + Debug + Send + Sync + 'static, E6: Display + Debug + Send + Sync + 'static, C: CustomMsg, Q: CustomQuery + DeserializeOwned, {
559 fn instantiate(
563 &self,
564 deps: DepsMut<Q>,
565 env: Env,
566 info: MessageInfo,
567 msg: Vec<u8>,
568 ) -> StdResult<Response<C>> {
569 let msg: T2 = from_json(msg)?;
570 (self.instantiate_fn)(deps, env, info, msg)
571 .map_err(|err: E2| StdError::msg(format!("{err}")))
572 }
573
574 fn execute(
578 &self,
579 deps: DepsMut<Q>,
580 env: Env,
581 info: MessageInfo,
582 msg: Vec<u8>,
583 ) -> StdResult<Response<C>> {
584 let msg: T1 = from_json(msg)?;
585 (self.execute_fn)(deps, env, info, msg).map_err(|err: E1| std_error!(err))
586 }
587
588 fn query(&self, deps: Deps<Q>, env: Env, msg: Vec<u8>) -> StdResult<Binary> {
592 let msg: T3 = from_json(msg)?;
593 (self.query_fn)(deps, env, msg).map_err(|err: E3| std_error!(err))
594 }
595
596 fn reply(&self, deps: DepsMut<Q>, env: Env, msg: Reply) -> StdResult<Response<C>> {
601 match &self.reply_fn {
602 Some(reply) => reply(deps, env, msg).map_err(|err: E5| std_error!(err)),
603 None => std_error_bail!("reply is not implemented for contract"),
604 }
605 }
606
607 fn sudo(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>) -> StdResult<Response<C>> {
612 let msg: T4 = from_json(msg)?;
613 match &self.sudo_fn {
614 Some(sudo) => sudo(deps, env, msg).map_err(|err: E4| std_error!(err)),
615 None => std_error_bail!("sudo is not implemented for contract"),
616 }
617 }
618
619 #[cfg(not(feature = "cosmwasm_2_2"))]
624 fn migrate(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>) -> StdResult<Response<C>> {
625 let msg: T6 = from_json(msg)?;
626 match &self.migrate_fn {
627 Some(migrate) => migrate(deps, env, msg).map_err(|err: E6| std_error!(err)),
628 None => std_error_bail!("migrate is not implemented for contract"),
629 }
630 }
631
632 #[cfg(feature = "cosmwasm_2_2")]
637 fn migrate(
638 &self,
639 deps: DepsMut<Q>,
640 env: Env,
641 msg: Vec<u8>,
642 info: MigrateInfo,
643 ) -> StdResult<Response<C>> {
644 let msg: T6 = from_json(msg)?;
645 match &self.migrate_fn {
646 Some(migrate) => migrate(deps, env, msg, info).map_err(|err: E6| std_error!(err)),
647 None => std_error_bail!("migrate is not implemented for contract"),
648 }
649 }
650
651 fn checksum(&self) -> Option<Checksum> {
653 self.checksum
654 }
655}