1use crate::error::{anyhow, bail, AnyError, AnyResult};
4use cosmwasm_std::{
5 from_json, Binary, Checksum, CosmosMsg, CustomMsg, CustomQuery, Deps, DepsMut, Empty, Env,
6 MessageInfo, QuerierWrapper, Reply, Response, SubMsg,
7};
8use serde::de::DeserializeOwned;
9use std::fmt::{Debug, Display};
10use std::ops::Deref;
11
12#[rustfmt::skip]
14pub trait Contract<C, Q = Empty>
15where
16 C: CustomMsg,
17 Q: CustomQuery,
18{
19 fn execute(&self, deps: DepsMut<Q>, env: Env, info: MessageInfo, msg: Vec<u8>) -> AnyResult<Response<C>>;
21
22 fn instantiate(&self, deps: DepsMut<Q>, env: Env, info: MessageInfo, msg: Vec<u8>) -> AnyResult<Response<C>>;
24
25 fn query(&self, deps: Deps<Q>, env: Env, msg: Vec<u8>) -> AnyResult<Binary>;
27
28 fn sudo(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>) -> AnyResult<Response<C>>;
30
31 fn reply(&self, deps: DepsMut<Q>, env: Env, msg: Reply) -> AnyResult<Response<C>>;
33
34 fn migrate(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>) -> AnyResult<Response<C>>;
36
37 fn checksum(&self) -> Option<Checksum> {
39 None
40 }
41}
42
43#[rustfmt::skip]
44mod closures {
45 use super::*;
46
47 pub type ContractFn<T, C, E, Q> = fn(deps: DepsMut<Q>, env: Env, info: MessageInfo, msg: T) -> Result<Response<C>, E>;
49 pub type PermissionedFn<T, C, E, Q> = fn(deps: DepsMut<Q>, env: Env, msg: T) -> Result<Response<C>, E>;
50 pub type ReplyFn<C, E, Q> = fn(deps: DepsMut<Q>, env: Env, msg: Reply) -> Result<Response<C>, E>;
51 pub type QueryFn<T, E, Q> = fn(deps: Deps<Q>, env: Env, msg: T) -> Result<Binary, E>;
52
53 pub type ContractClosure<T, C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, MessageInfo, T) -> Result<Response<C>, E>>;
55 pub type PermissionedClosure<T, C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, T) -> Result<Response<C>, E>>;
56 pub type ReplyClosure<C, E, Q> = Box<dyn Fn(DepsMut<Q>, Env, Reply) -> Result<Response<C>, E>>;
57 pub type QueryClosure<T, E, Q> = Box<dyn Fn(Deps<Q>, Env, T) -> Result<Binary, E>>;
58}
59
60use closures::*;
61
62pub struct ContractWrapper<
130 T1,
131 T2,
132 T3,
133 E1,
134 E2,
135 E3,
136 C = Empty,
137 Q = Empty,
138 T4 = Empty,
139 E4 = AnyError,
140 E5 = AnyError,
141 T6 = Empty,
142 E6 = AnyError,
143> where
144 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, {
158 execute_fn: ContractClosure<T1, C, E1, Q>,
159 instantiate_fn: ContractClosure<T2, C, E2, Q>,
160 query_fn: QueryClosure<T3, E3, Q>,
161 sudo_fn: Option<PermissionedClosure<T4, C, E4, Q>>,
162 reply_fn: Option<ReplyClosure<C, E5, Q>>,
163 migrate_fn: Option<PermissionedClosure<T6, C, E6, Q>>,
164 checksum: Option<Checksum>,
165}
166
167impl<T1, T2, T3, E1, E2, E3, C, Q> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q>
168where
169 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, {
178 pub fn new(
180 execute_fn: ContractFn<T1, C, E1, Q>,
181 instantiate_fn: ContractFn<T2, C, E2, Q>,
182 query_fn: QueryFn<T3, E3, Q>,
183 ) -> Self {
184 Self {
185 execute_fn: Box::new(execute_fn),
186 instantiate_fn: Box::new(instantiate_fn),
187 query_fn: Box::new(query_fn),
188 sudo_fn: None,
189 reply_fn: None,
190 migrate_fn: None,
191 checksum: None,
192 }
193 }
194
195 pub fn new_with_empty(
198 execute_fn: ContractFn<T1, Empty, E1, Empty>,
199 instantiate_fn: ContractFn<T2, Empty, E2, Empty>,
200 query_fn: QueryFn<T3, E3, Empty>,
201 ) -> Self {
202 Self {
203 execute_fn: customize_contract_fn(execute_fn),
204 instantiate_fn: customize_contract_fn(instantiate_fn),
205 query_fn: customize_query_fn(query_fn),
206 sudo_fn: None,
207 reply_fn: None,
208 migrate_fn: None,
209 checksum: None,
210 }
211 }
212}
213
214#[allow(clippy::type_complexity)]
215impl<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
216 ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
217where
218 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, {
232 pub fn with_sudo<T4A, E4A>(
234 self,
235 sudo_fn: PermissionedFn<T4A, C, E4A, Q>,
236 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4A, E4A, E5, T6, E6>
237 where
238 T4A: DeserializeOwned + 'static,
239 E4A: Display + Debug + Send + Sync + 'static,
240 {
241 ContractWrapper {
242 execute_fn: self.execute_fn,
243 instantiate_fn: self.instantiate_fn,
244 query_fn: self.query_fn,
245 sudo_fn: Some(Box::new(sudo_fn)),
246 reply_fn: self.reply_fn,
247 migrate_fn: self.migrate_fn,
248 checksum: None,
249 }
250 }
251
252 pub fn with_sudo_empty<T4A, E4A>(
254 self,
255 sudo_fn: PermissionedFn<T4A, Empty, E4A, Empty>,
256 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4A, E4A, E5, T6, E6>
257 where
258 T4A: DeserializeOwned + 'static,
259 E4A: Display + Debug + Send + Sync + 'static,
260 {
261 ContractWrapper {
262 execute_fn: self.execute_fn,
263 instantiate_fn: self.instantiate_fn,
264 query_fn: self.query_fn,
265 sudo_fn: Some(customize_permissioned_fn(sudo_fn)),
266 reply_fn: self.reply_fn,
267 migrate_fn: self.migrate_fn,
268 checksum: None,
269 }
270 }
271
272 pub fn with_reply<E5A>(
274 self,
275 reply_fn: ReplyFn<C, E5A, Q>,
276 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5A, T6, E6>
277 where
278 E5A: Display + Debug + Send + Sync + 'static,
279 {
280 ContractWrapper {
281 execute_fn: self.execute_fn,
282 instantiate_fn: self.instantiate_fn,
283 query_fn: self.query_fn,
284 sudo_fn: self.sudo_fn,
285 reply_fn: Some(Box::new(reply_fn)),
286 migrate_fn: self.migrate_fn,
287 checksum: None,
288 }
289 }
290
291 pub fn with_reply_empty<E5A>(
293 self,
294 reply_fn: ReplyFn<Empty, E5A, Empty>,
295 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5A, T6, E6>
296 where
297 E5A: Display + Debug + Send + Sync + 'static,
298 {
299 ContractWrapper {
300 execute_fn: self.execute_fn,
301 instantiate_fn: self.instantiate_fn,
302 query_fn: self.query_fn,
303 sudo_fn: self.sudo_fn,
304 reply_fn: Some(customize_permissioned_fn(reply_fn)),
305 migrate_fn: self.migrate_fn,
306 checksum: None,
307 }
308 }
309
310 pub fn with_migrate<T6A, E6A>(
312 self,
313 migrate_fn: PermissionedFn<T6A, C, E6A, Q>,
314 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6A, E6A>
315 where
316 T6A: DeserializeOwned + 'static,
317 E6A: Display + Debug + Send + Sync + 'static,
318 {
319 ContractWrapper {
320 execute_fn: self.execute_fn,
321 instantiate_fn: self.instantiate_fn,
322 query_fn: self.query_fn,
323 sudo_fn: self.sudo_fn,
324 reply_fn: self.reply_fn,
325 migrate_fn: Some(Box::new(migrate_fn)),
326 checksum: None,
327 }
328 }
329
330 pub fn with_migrate_empty<T6A, E6A>(
332 self,
333 migrate_fn: PermissionedFn<T6A, Empty, E6A, Empty>,
334 ) -> ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6A, E6A>
335 where
336 T6A: DeserializeOwned + 'static,
337 E6A: Display + Debug + Send + Sync + 'static,
338 {
339 ContractWrapper {
340 execute_fn: self.execute_fn,
341 instantiate_fn: self.instantiate_fn,
342 query_fn: self.query_fn,
343 sudo_fn: self.sudo_fn,
344 reply_fn: self.reply_fn,
345 migrate_fn: Some(customize_permissioned_fn(migrate_fn)),
346 checksum: None,
347 }
348 }
349
350 pub fn with_checksum(mut self, checksum: Checksum) -> Self {
352 self.checksum = Some(checksum);
353 self
354 }
355}
356
357fn customize_contract_fn<T, C, E, Q>(
358 raw_fn: ContractFn<T, Empty, E, Empty>,
359) -> ContractClosure<T, C, E, Q>
360where
361 T: DeserializeOwned + 'static,
362 E: Display + Debug + Send + Sync + 'static,
363 C: CustomMsg,
364 Q: CustomQuery + DeserializeOwned,
365{
366 Box::new(
367 move |mut deps: DepsMut<Q>,
368 env: Env,
369 info: MessageInfo,
370 msg: T|
371 -> Result<Response<C>, E> {
372 let deps = decustomize_deps_mut(&mut deps);
373 raw_fn(deps, env, info, msg).map(customize_response::<C>)
374 },
375 )
376}
377
378fn customize_query_fn<T, E, Q>(raw_fn: QueryFn<T, E, Empty>) -> QueryClosure<T, E, Q>
379where
380 T: DeserializeOwned + 'static,
381 E: Display + Debug + Send + Sync + 'static,
382 Q: CustomQuery + DeserializeOwned,
383{
384 Box::new(
385 move |deps: Deps<Q>, env: Env, msg: T| -> Result<Binary, E> {
386 let deps = decustomize_deps(&deps);
387 raw_fn(deps, env, msg)
388 },
389 )
390}
391
392fn customize_permissioned_fn<T, C, E, Q>(
393 raw_fn: PermissionedFn<T, Empty, E, Empty>,
394) -> PermissionedClosure<T, C, E, Q>
395where
396 T: DeserializeOwned + 'static,
397 E: Display + Debug + Send + Sync + 'static,
398 C: CustomMsg,
399 Q: CustomQuery + DeserializeOwned,
400{
401 Box::new(
402 move |mut deps: DepsMut<Q>, env: Env, msg: T| -> Result<Response<C>, E> {
403 let deps = decustomize_deps_mut(&mut deps);
404 raw_fn(deps, env, msg).map(customize_response::<C>)
405 },
406 )
407}
408
409fn decustomize_deps_mut<'a, Q>(deps: &'a mut DepsMut<Q>) -> DepsMut<'a, Empty>
410where
411 Q: CustomQuery + DeserializeOwned,
412{
413 DepsMut {
414 storage: deps.storage,
415 api: deps.api,
416 querier: QuerierWrapper::new(deps.querier.deref()),
417 }
418}
419
420fn decustomize_deps<'a, Q>(deps: &'a Deps<'a, Q>) -> Deps<'a, Empty>
421where
422 Q: CustomQuery + DeserializeOwned,
423{
424 Deps {
425 storage: deps.storage,
426 api: deps.api,
427 querier: QuerierWrapper::new(deps.querier.deref()),
428 }
429}
430
431fn customize_response<C>(resp: Response<Empty>) -> Response<C>
432where
433 C: CustomMsg,
434{
435 let mut customized_resp = Response::<C>::new()
436 .add_submessages(resp.messages.into_iter().map(customize_msg::<C>))
437 .add_events(resp.events)
438 .add_attributes(resp.attributes);
439 customized_resp.data = resp.data;
440 customized_resp
441}
442
443fn customize_msg<C>(msg: SubMsg<Empty>) -> SubMsg<C>
444where
445 C: CustomMsg,
446{
447 SubMsg {
448 id: msg.id,
449 payload: msg.payload,
450 msg: match msg.msg {
451 CosmosMsg::Wasm(wasm) => CosmosMsg::Wasm(wasm),
452 CosmosMsg::Bank(bank) => CosmosMsg::Bank(bank),
453 #[cfg(feature = "staking")]
454 CosmosMsg::Staking(staking) => CosmosMsg::Staking(staking),
455 #[cfg(feature = "staking")]
456 CosmosMsg::Distribution(distribution) => CosmosMsg::Distribution(distribution),
457 CosmosMsg::Custom(_) => unreachable!(),
458 #[cfg(feature = "stargate")]
459 CosmosMsg::Ibc(ibc) => CosmosMsg::Ibc(ibc),
460 #[cfg(feature = "cosmwasm_2_0")]
461 CosmosMsg::Any(any) => CosmosMsg::Any(any),
462 other => panic!("unknown message variant {:?}", other),
463 },
464 gas_limit: msg.gas_limit,
465 reply_on: msg.reply_on,
466 }
467}
468
469impl<T1, T2, T3, E1, E2, E3, C, T4, E4, E5, T6, E6, Q> Contract<C, Q>
470 for ContractWrapper<T1, T2, T3, E1, E2, E3, C, Q, T4, E4, E5, T6, E6>
471where
472 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, {
486 fn execute(
490 &self,
491 deps: DepsMut<Q>,
492 env: Env,
493 info: MessageInfo,
494 msg: Vec<u8>,
495 ) -> AnyResult<Response<C>> {
496 let msg: T1 = from_json(msg)?;
497 (self.execute_fn)(deps, env, info, msg).map_err(|err: E1| anyhow!(err))
498 }
499
500 fn instantiate(
504 &self,
505 deps: DepsMut<Q>,
506 env: Env,
507 info: MessageInfo,
508 msg: Vec<u8>,
509 ) -> AnyResult<Response<C>> {
510 let msg: T2 = from_json(msg)?;
511 (self.instantiate_fn)(deps, env, info, msg).map_err(|err: E2| anyhow!(err))
512 }
513
514 fn query(&self, deps: Deps<Q>, env: Env, msg: Vec<u8>) -> AnyResult<Binary> {
518 let msg: T3 = from_json(msg)?;
519 (self.query_fn)(deps, env, msg).map_err(|err: E3| anyhow!(err))
520 }
521
522 fn sudo(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>) -> AnyResult<Response<C>> {
527 let msg: T4 = from_json(msg)?;
528 match &self.sudo_fn {
529 Some(sudo) => sudo(deps, env, msg).map_err(|err: E4| anyhow!(err)),
530 None => bail!("sudo is not implemented for contract"),
531 }
532 }
533
534 fn reply(&self, deps: DepsMut<Q>, env: Env, reply_data: Reply) -> AnyResult<Response<C>> {
539 let msg: Reply = reply_data;
540 match &self.reply_fn {
541 Some(reply) => reply(deps, env, msg).map_err(|err: E5| anyhow!(err)),
542 None => bail!("reply is not implemented for contract"),
543 }
544 }
545
546 fn migrate(&self, deps: DepsMut<Q>, env: Env, msg: Vec<u8>) -> AnyResult<Response<C>> {
551 let msg: T6 = from_json(msg)?;
552 match &self.migrate_fn {
553 Some(migrate) => migrate(deps, env, msg).map_err(|err: E6| anyhow!(err)),
554 None => bail!("migrate is not implemented for contract"),
555 }
556 }
557
558 fn checksum(&self) -> Option<Checksum> {
560 self.checksum
561 }
562}