Repository: CosmWasm/sylvia (rust)

Issue:
Custom chain messages on sylvia contracts
We want to be able to able to use smart contacts on external chains, which requires three items:
1) Ability to return `CosmosMsg::Custom(...)` variant of `CosmosMsg` in the `Response` to send a cosmos-specific message by returning `Response<ExecC>` from the exec message.
2) Ability to query the chain with custom query by taking the `Deps<QueryC>` and `DepsMut<QueryC>` types as in context (which requires context to be generic over those).
3) Ability to handle the `sudo` entry point.

This issue covers only the 1) point of this.

## Returning the `Response<ExecC>` from the function

The clue of the issue is to allow returning a parametrized response from the messages on the contract. The example case:

```rust
#[contract]
impl MyContract {
  #[msg(instantiate)]
  fn instantiate(&self, ctx: InstantiateCtx, param: u64) -> Result<Response<OsmosisMsg>, ContractError> {
    let msg = OsmosisMsg::SomeVariant { param };
    let msg = CosmsoMsg::Custom(msg);
    let resp = Reponse::new()
      .add_message(msg);
    Ok(resp)
  }
}
```

It could be achieved by parsing the return type of every function returning `Response`, and if any of them returns parametrized variant, make an entry point returning the parametrized variant and make it the `ExecC` of the contract.

As tempting, this approach will be difficult to implement and vulnerable to edge cases. Example: what if one would like to create an alias of `type OsmoResp = Response<OsmosisMsg>`? Extracting the `ExecC` here from the macro position would be challenging.

Another problem - what if many functions are returning the `ExecC` from functions, but they differ? For now, we don't want to support any conversion between them, so this is an error, but it isn't easy to decide how to describe that clearly.

The solution here is to annotate the contract with what we want to be the contract `ExecC` on the contract implementation:

```rust
#[contract]
#[sv::custom(msg=OsmosisMsg)]
impl MyContract {
  #[msg(instantiate)]
  fn instantiate(&self, ctx: InstantiateCtx, param: u64) -> Result<Response<OsmosisMsg>, ContractError> {
    let msg = OsmosisMsg::SomeVariant { param };
    let msg = CosmsoMsg::Custom(msg);
    let resp = Reponse::new()
      .add_message(msg);
    Ok(resp)
  }
}
```

Now the entry points would be generated returning `Result<Response<OsmosisMsg>>`, and the error would be on invalid type returned from the dispatcher (we want to make sure the span for that points to the result type of the function).

When the contract has defined the `msg=...`, we require the user to always return a proper `Reponse<...>` from the contract. The reason for that is it is impossible to define the conversion which would work both as identity conversion and generic conversion from `Response<Empty>` to `Response<T>` due to conflicting implementation for `Response<Empty>` to `Response<Empty>`.

## Handling responses returned in interfaces

For now, we do not support any `ExecC` being returned for variants defined in interfaces, and we want to keep supporting this for compatibility. Messages generated for such traits would return the `Response<Empty>` type. However, we want to be able to implement them on our contracts. Let's consider such a code:

```rust
mod iface {
#[interface]
  pub trait IFace {
    type Error;
    
    #[exec]
    fn variant(&self, ctx: ExecCtx) -> Result<Response, Self::Error>;
  }
}

mod iface_impl {
  #[contract]
  #[messages(iface as IFace)]
  impl iface::IFace for MyContract {
    type Error = ContractError;
  
    #[exec]
    fn variant(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
      Ok(Response::new())
    }
  }
}


#[contract]
#[sv::custom(msg=OsmosisMsg)]
#[messages(iface as IFace: custom(msg))]
impl MyContract {
  #[msg(instantiate)]
  fn instantiate(&self, ctx: InstantiateCtx, param: u64) -> Result<Response<OsmosisMsg>, ContractError> {
    let msg = OsmosisMsg::SomeVariant { param };
    let msg = CosmsoMsg::Custom(msg);
    let resp = Reponse::new()
      .add_message(msg);
    Ok(resp)
  }
}
```

We want this to work, and to make it work properly, we need to call the conversion function of `Response` of whatever got returned by interface msg `dispatch()` in the contract-level `dispatch()`. However the problem occurs - we can call the function only if there is a conversion to be performed, as our conversion function cannot support identity (for reasons already explained).

To solve this, we will call the conversion utility if and only if `custom(msg)` "option" is added to the corresponding `#[messages(...)]` argument.

## `ExecC` in interfaces

Interfaces should be chain-agnostic in the majority of cases. However, it should be possible to implement the message from the interface that chooses to send a custom message to the chain it is implemented on. The way we will approach this is to leverage the associated type with the `ExecC`:

```rust
mod iface {
#[interface]
  pub trait IFace {
    type Error;
    type ExecC;
    
    #[exec]
    fn variant(&self, ctx: ExecCtx) -> Result<Response<Self::ExecC>, Self::Error>;
  }
}

mod iface_impl {
  #[contract]
  #[messages(iface as IFace)]
  impl iface::IFace for MyContract {
    type ExecC = OsmosisMsg;
    type Error = ContractError;
  
    #[exec]
    fn variant(&self, ctx: ExecCtx) -> Result<Response<OsmosisMsg, ContractError> {
        let msg = OsmosisMsg::SomeVariant { param };
        let msg = CosmsoMsg::Custom(msg);
        let resp = Reponse::new()
          .add_message(msg);
        Ok(resp)
     }
  }
}

#[contract]
#[sv::custom(msg=OsmosisMsg)]
#[messages(iface as IFace)]
impl MyContract {
  #[msg(instantiate)]
  fn instantiate(&self, ctx: InstantiateCtx, param: u64) -> Result<Response<OsmosisMsg>, ContractError> {
    let msg = OsmosisMsg::SomeVariant { param };
    let msg = CosmsoMsg::Custom(msg);
    let resp = Reponse::new()
      .add_message(msg);
    Ok(resp)
  }
}
```

This way the trait can implementation can use any chain-specific custom messages. To achieve that we will look for the `ExecC` associated type definition, and if it exists, we will return `Result<Response<Self::ExecC>>` from the generated message dispatch. If there is no such a message, we will fall back to return just a `Result<Response, ContractError>`.

Nothing should change here regarding implementing a trait on contract - the user will need to specify the `ExecC`, but the Rust itself would enforce it. 

Note the lack of `#[custom(...)]` on the interface impl block - our trait already returns proper `Response` and we don't want to convert it as we do not support identity conversion!

## Chain-specific interfaces

Finally, I can imagine interfaces that are considered to work only on particular chains. As it doesn't seem to be the true case (I can always try to implement the same behavior on different chains), it might be a case this simplifies some contracts not bothering about the associated `ExecC` type. To support that, we want to allow the user to specify the `ExecC` on the trait directly with the `sv::custom(...)`:

```rust
mod iface {
  #[interface]
  #[sv::custom(msg=OsmosisMsg)]
  pub trait IFace {
    type Error;
    
    #[exec]
    fn variant(&self, ctx: ExecCtx) -> Result<Response<OsmosisMsg>, Self::Error>;
  }
}

#[contract]
#[messages(iface as IFace)]
impl iface::IFace for MyContract {
  type Error = ContractError;
  
  #[exec]
  fn variant(&self, ctx: ExecCtx) -> Result<Response<OsmosisMsg, ContractError> {
    Ok(Response::new())
  }
}

mod iface_impl {
  #[contract]
  #[sv::custom(msg=OsmosisMsg)]
  #[messages(iface as IFace)]
  impl MyContract {
    #[msg(instantiate)]
    fn instantiate(&self, ctx: InstantiateCtx, param: u64) -> Result<Response<OsmosisMsg>, ContractError> {
      let msg = OsmosisMsg::SomeVariant { param };
      let msg = CosmsoMsg::Custom(msg);
      let resp = Reponse::new()
        .add_message(msg);
      Ok(resp)
    }
  }
}
```

This way user can control exactly what type can be used as the `ExecC` in the response (it can even be the trait generic or something). This way we can make it a bit easier for a "project private" interface designed for a particular chain.

## Warning about non-chain-agnostic interfaces

Finally, most interfaces should be chain agnostic and defined with `ExecC`. Unfortunately - adding and maintaining this is another thing to remember, maintain, and document. We want Sylvia to help us with that, so whenever we see the interface with no `ExecC` or `#[custom(msg=...)]`, we assume it is an oversight or leftover after migration from previous Sylvia releases.  We want to support it (mostly to make migrations as flawless as possible), but we will warn about it something like:

```
The trait is missing the custom message definition, and returning any chain-specific custom messages on its implementation will not be possible.
Note: Consider adding the `type ExecC;` associated message in the trait definition, and then use it in the execution responses return type: `Result<Response<Self::ExecC>, _>`
Note: If it is intended and you don't want to allow executing custom messages from this trait, add the `#[sv::custom(cosmwasm_std::Empty)]` attribute on the trait.
```

## Multitest

All of the above has to have additional support in the multitest utilities. In particular - we need the contract helper to implement `Contract<ExecC>` instead of just `Contract`. Everything else should be completely transparent from MT PoV.

## Tasks split
- [x] #158 
- [x] #159 
- [x] #160
- [x] #161 
- [ ] #162 

Pull request description:
Custom msg support
close #157


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4fdf87f..81903c4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,6 +4,7 @@ on:
   push:
     branches:
       - main
+      - feat/* 
   pull_request:
 
 env:
diff --git a/.gitignore b/.gitignore
index cf42f7f..35dcf3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 target/**
 */target/**
 **/schema/**
+tarpaulin-report.html
diff --git a/README.md b/README.md
index 01fdec2..2c86331 100644
--- a/README.md
+++ b/README.md
@@ -842,5 +842,8 @@ features for you. Here is a rough roadmap for the incoming months:
 
 ## Troubleshooting
 
+For more descriptive error messages, consider using the nightly toolchain (add `+nightly`
+argument for cargo)
+
 - Missing messages from interface on your contract - You may be missing
   `messages(interface as Interface)` attribute.
diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs
index b9e0965..34b8643 100644
--- a/sylvia-derive/src/input.rs
+++ b/sylvia-derive/src/input.rs
@@ -9,15 +9,15 @@ use crate::crate_module;
 use crate::interfaces::Interfaces;
 use crate::message::{ContractEnumMessage, EnumMessage, GlueMessage, MsgVariants, StructMessage};
 use crate::multitest::{MultitestHelpers, TraitMultitestHelpers};
-use crate::parser::{ContractArgs, ContractErrorAttr, Custom, InterfaceArgs, MsgType};
+use crate::parser::{ContractArgs, ContractErrorAttr, Custom, MsgType};
 use crate::remote::Remote;
 use crate::variant_descs::AsVariantDescs;
 
 /// Preprocessed `interface` macro input
 pub struct TraitInput<'a> {
-    attributes: &'a InterfaceArgs,
     item: &'a ItemTrait,
     generics: Vec<&'a GenericParam>,
+    custom: Custom<'a>,
 }
 
 /// Preprocessed `contract` macro input for non-trait impl block
@@ -26,12 +26,13 @@ pub struct ImplInput<'a> {
     error: Type,
     item: &'a ItemImpl,
     generics: Vec<&'a GenericParam>,
+    custom: Custom<'a>,
 }
 
 impl<'a> TraitInput<'a> {
     #[cfg(not(tarpaulin_include))]
     // This requires invalid implementation which would fail at compile time and making it impossible to test
-    pub fn new(attributes: &'a InterfaceArgs, item: &'a ItemTrait) -> Self {
+    pub fn new(item: &'a ItemTrait) -> Self {
         let generics = item.generics.params.iter().collect();
 
         if !item
@@ -46,10 +47,12 @@ impl<'a> TraitInput<'a> {
             );
         }
 
+        let custom = Custom::new(&item.attrs);
+
         Self {
-            attributes,
             item,
             generics,
+            custom,
         }
     }
 
@@ -83,16 +86,8 @@ impl<'a> TraitInput<'a> {
     }
 
     fn emit_messages(&self) -> TokenStream {
-        let exec = self.emit_msg(
-            &Ident::new("ExecMsg", Span::mixed_site()),
-            MsgType::Exec,
-            self.attributes,
-        );
-        let query = self.emit_msg(
-            &Ident::new("QueryMsg", Span::mixed_site()),
-            MsgType::Query,
-            self.attributes,
-        );
+        let exec = self.emit_msg(&Ident::new("ExecMsg", Span::mixed_site()), MsgType::Exec);
+        let query = self.emit_msg(&Ident::new("QueryMsg", Span::mixed_site()), MsgType::Query);
 
         #[cfg(not(tarpaulin_include))]
         {
@@ -104,8 +99,8 @@ impl<'a> TraitInput<'a> {
         }
     }
 
-    fn emit_msg(&self, name: &Ident, msg_ty: MsgType, args: &InterfaceArgs) -> TokenStream {
-        EnumMessage::new(name, self.item, msg_ty, &self.generics, args).emit()
+    fn emit_msg(&self, name: &Ident, msg_ty: MsgType) -> TokenStream {
+        EnumMessage::new(name, self.item, msg_ty, &self.generics, &self.custom).emit()
     }
 }
 
@@ -130,25 +125,34 @@ impl<'a> ImplInput<'a> {
             )
             .unwrap_or_else(|| parse_quote! { #sylvia ::cw_std::StdError });
 
+        let custom = Custom::new(&item.attrs);
+
         Self {
             attributes,
             item,
             generics,
             error,
+            custom,
         }
     }
 
     pub fn process(&self) -> TokenStream {
         let is_trait = self.item.trait_.is_some();
         let multitest_helpers = if cfg!(feature = "mt") {
-            MultitestHelpers::new(self.item, is_trait, &self.error, &self.generics).emit()
+            MultitestHelpers::new(
+                self.item,
+                is_trait,
+                &self.error,
+                &self.generics,
+                &self.custom,
+            )
+            .emit()
         } else {
             quote! {}
         };
 
         let interfaces = Interfaces::new(self.item);
         let variants = MsgVariants::new(self.item.as_variants(), &self.generics);
-        let _ = Custom::new(self.item);
 
         match is_trait {
             true => self.process_interface(&interfaces, variants, multitest_helpers),
@@ -232,11 +236,19 @@ impl<'a> ImplInput<'a> {
     }
 
     fn emit_enum_msg(&self, name: &Ident, msg_ty: MsgType) -> TokenStream {
-        ContractEnumMessage::new(name, self.item, msg_ty, &self.generics, &self.error).emit()
+        ContractEnumMessage::new(
+            name,
+            self.item,
+            msg_ty,
+            &self.generics,
+            &self.error,
+            &self.custom,
+        )
+        .emit()
     }
 
     fn emit_glue_msg(&self, name: &Ident, msg_ty: MsgType) -> TokenStream {
-        GlueMessage::new(name, self.item, msg_ty, &self.error).emit()
+        GlueMessage::new(name, self.item, msg_ty, &self.error, &self.custom).emit()
     }
 
     fn emit_querier_for_bound_impl(
diff --git a/sylvia-derive/src/lib.rs b/sylvia-derive/src/lib.rs
index b4c5841..da68984 100644
--- a/sylvia-derive/src/lib.rs
+++ b/sylvia-derive/src/lib.rs
@@ -140,12 +140,11 @@ pub fn interface(attr: TokenStream, item: TokenStream) -> TokenStream {
     interface_impl(attr.into(), item.into()).into()
 }
 
-fn interface_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
-    fn inner(attr: TokenStream2, item: TokenStream2) -> syn::Result<TokenStream2> {
-        let attrs: parser::InterfaceArgs = parse2(attr)?;
+fn interface_impl(_attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
+    fn inner(item: TokenStream2) -> syn::Result<TokenStream2> {
         let input: ItemTrait = parse2(item)?;
 
-        let expanded = TraitInput::new(&attrs, &input).process();
+        let expanded = TraitInput::new(&input).process();
         let input = StripInput.fold_item_trait(input);
 
         Ok(quote! {
@@ -155,7 +154,7 @@ fn interface_impl(attr: TokenStream2, item: TokenStream2) -> TokenStream2 {
         })
     }
 
-    inner(attr, item).unwrap_or_else(syn::Error::into_compile_error)
+    inner(item).unwrap_or_else(syn::Error::into_compile_error)
 }
 
 /// Macro generating messages from contract impl block.
diff --git a/sylvia-derive/src/message.rs b/sylvia-derive/src/message.rs
index 38ab2b0..a1dfef0 100644
--- a/sylvia-derive/src/message.rs
+++ b/sylvia-derive/src/message.rs
@@ -1,7 +1,7 @@
 use crate::check_generics::CheckGenerics;
 use crate::crate_module;
 use crate::parser::{
-    parse_struct_message, ContractErrorAttr, ContractMessageAttr, InterfaceArgs, MsgAttr, MsgType,
+    parse_struct_message, ContractErrorAttr, ContractMessageAttr, Custom, MsgAttr, MsgType,
 };
 use crate::strip_generics::StripGenerics;
 use crate::utils::{extract_return_type, filter_wheres, process_fields};
@@ -165,7 +165,7 @@ pub struct EnumMessage<'a> {
     wheres: Vec<&'a WherePredicate>,
     full_where: Option<&'a WhereClause>,
     msg_ty: MsgType,
-    args: &'a InterfaceArgs,
+    resp_type: Type,
 }
 
 impl<'a> EnumMessage<'a> {
@@ -174,7 +174,7 @@ impl<'a> EnumMessage<'a> {
         source: &'a ItemTrait,
         ty: MsgType,
         generics: &'a [&'a GenericParam],
-        args: &'a InterfaceArgs,
+        custom: &'a Custom,
     ) -> Self {
         let trait_name = &source.ident;
 
@@ -203,6 +203,18 @@ impl<'a> EnumMessage<'a> {
             })
             .collect();
 
+        let associated_exec: Option<Type> = source.items.iter().find_map(|item| match item {
+            TraitItem::Type(ty) if ty.ident == "ExecC" => {
+                Some(parse_quote! { <C as #trait_name>::ExecC })
+            }
+            _ => None,
+        });
+
+        let resp_type = match associated_exec {
+            Some(exec) if !custom.has_msg() => exec,
+            _ => custom.msg(),
+        };
+
         let (used_generics, unused_generics) = generics_checker.used_unused();
         let wheres = filter_wheres(&source.generics.where_clause, generics, &used_generics);
 
@@ -216,7 +228,7 @@ impl<'a> EnumMessage<'a> {
             wheres,
             full_where: source.generics.where_clause.as_ref(),
             msg_ty: ty,
-            args,
+            resp_type,
         }
     }
 
@@ -233,7 +245,7 @@ impl<'a> EnumMessage<'a> {
             wheres,
             full_where,
             msg_ty,
-            args,
+            resp_type,
         } = self;
 
         let match_arms = variants
@@ -256,7 +268,7 @@ impl<'a> EnumMessage<'a> {
         };
 
         let ctx_type = msg_ty.emit_ctx_type();
-        let dispatch_type = msg_ty.emit_result_type(&args.msg_type, &parse_quote!(C::Error));
+        let dispatch_type = msg_ty.emit_result_type(resp_type, &parse_quote!(C::Error));
 
         let all_generics = if all_generics.is_empty() {
             quote! {}
@@ -330,6 +342,7 @@ pub struct ContractEnumMessage<'a> {
     msg_ty: MsgType,
     contract: &'a Type,
     error: &'a Type,
+    custom: &'a Custom<'a>,
 }
 
 impl<'a> ContractEnumMessage<'a> {
@@ -339,6 +352,7 @@ impl<'a> ContractEnumMessage<'a> {
         ty: MsgType,
         generics: &'a [&'a GenericParam],
         error: &'a Type,
+        custom: &'a Custom,
     ) -> Self {
         let mut generics_checker = CheckGenerics::new(generics);
         let variants: Vec<_> = source
@@ -371,6 +385,7 @@ impl<'a> ContractEnumMessage<'a> {
             msg_ty: ty,
             contract: &source.self_ty,
             error,
+            custom,
         }
     }
 
@@ -383,6 +398,7 @@ impl<'a> ContractEnumMessage<'a> {
             msg_ty,
             contract,
             error,
+            custom,
         } = self;
 
         let match_arms = variants
@@ -399,7 +415,7 @@ impl<'a> ContractEnumMessage<'a> {
 
         let ctx_type = msg_ty.emit_ctx_type();
         let contract = StripGenerics.fold_type((*contract).clone());
-        let ret_type = msg_ty.emit_result_type(&None, error);
+        let ret_type = msg_ty.emit_result_type(&custom.msg(), error);
 
         #[cfg(not(tarpaulin_include))]
         let enum_declaration = match name.to_string().as_str() {
@@ -824,6 +840,7 @@ pub struct GlueMessage<'a> {
     contract: &'a Type,
     msg_ty: MsgType,
     error: &'a Type,
+    custom: &'a Custom<'a>,
 }
 
 impl<'a> GlueMessage<'a> {
@@ -838,7 +855,13 @@ impl<'a> GlueMessage<'a> {
         syn::Ident::new(&format!("{}{}", module_name, name), name.span())
     }
 
-    pub fn new(name: &'a Ident, source: &'a ItemImpl, msg_ty: MsgType, error: &'a Type) -> Self {
+    pub fn new(
+        name: &'a Ident,
+        source: &'a ItemImpl,
+        msg_ty: MsgType,
+        error: &'a Type,
+        custom: &'a Custom,
+    ) -> Self {
         let interfaces: Vec<_> = source
             .attrs
             .iter()
@@ -862,6 +885,7 @@ impl<'a> GlueMessage<'a> {
             contract: &source.self_ty,
             msg_ty,
             error,
+            custom,
         }
     }
 
@@ -874,6 +898,7 @@ impl<'a> GlueMessage<'a> {
             contract,
             msg_ty,
             error,
+            custom,
         } = self;
         let contract = StripGenerics.fold_type((*contract).clone());
         let contract_name = Ident::new(&format!("Contract{}", name), name.span());
@@ -884,6 +909,7 @@ impl<'a> GlueMessage<'a> {
                 exec_generic_params,
                 query_generic_params,
                 variant,
+                ..
             } = interface;
 
             let generics = match msg_ty {
@@ -911,9 +937,18 @@ impl<'a> GlueMessage<'a> {
         let interfaces_cnt = interface_names.len();
 
         let dispatch_arms = interfaces.iter().map(|interface| {
-            let ContractMessageAttr { variant, .. } = interface;
+            let ContractMessageAttr {
+                variant,
+                has_custom_msg,
+                ..
+            } = interface;
+
+            match *has_custom_msg&& msg_ty == &MsgType::Exec
+            {
+                true => quote! { #contract_name :: #variant(msg) => Ok( #sylvia ::into_response::IntoResponse::into_response(msg.dispatch(contract, Into::into(ctx))?)?) },
+                false => quote! { #contract_name :: #variant(msg) => msg.dispatch(contract, Into::into(ctx)) }
+            }
 
-            quote! { #contract_name :: #variant(msg) => msg.dispatch(contract, Into::into(ctx)) }
         });
 
         let dispatch_arm = quote! {#contract_name :: #contract (msg) =>msg.dispatch(contract, ctx)};
@@ -948,7 +983,7 @@ impl<'a> GlueMessage<'a> {
         };
 
         let ctx_type = msg_ty.emit_ctx_type();
-        let ret_type = msg_ty.emit_result_type(&None, error);
+        let ret_type = msg_ty.emit_result_type(&custom.msg(), error);
 
         let mut response_schemas: Vec<TokenStream> = interfaces
             .iter()
@@ -1053,14 +1088,15 @@ impl<'a> GlueMessage<'a> {
     }
 }
 
-pub struct EntryPoints {
+pub struct EntryPoints<'a> {
     name: Type,
     error: Type,
     reply: Option<Ident>,
+    custom: Custom<'a>,
 }
 
-impl EntryPoints {
-    pub fn new(source: &ItemImpl) -> Self {
+impl<'a> EntryPoints<'a> {
+    pub fn new(source: &'a ItemImpl) -> Self {
         let sylvia = crate_module();
         let name = StripGenerics.fold_type(*source.self_ty.clone());
 
@@ -1085,14 +1121,29 @@ impl EntryPoints {
             .into_iter()
             .find(|variant| variant.msg_type == MsgType::Reply)
             .map(|variant| variant.function_name.clone());
+        let custom = Custom::new(&source.attrs);
 
-        Self { name, error, reply }
+        Self {
+            name,
+            error,
+            reply,
+            custom,
+        }
     }
 
     pub fn emit(&self) -> TokenStream {
-        let Self { name, error, reply } = self;
+        let Self {
+            name,
+            error,
+            reply,
+            custom,
+        } = self;
         let sylvia = crate_module();
 
+        let custom_msg = custom.msg();
+        #[cfg(not(tarpaulin_include))]
+        let resp_type = quote! { #sylvia ::cw_std::Response < #custom_msg > };
+
         #[cfg(not(tarpaulin_include))]
         {
             let reply = match reply {
@@ -1102,7 +1153,7 @@ impl EntryPoints {
                         deps: #sylvia ::cw_std::DepsMut,
                         env: #sylvia ::cw_std::Env,
                         msg: #sylvia ::cw_std::Reply,
-                    ) -> Result<#sylvia ::cw_std::Response, #error> {
+                    ) -> Result<#resp_type, #error> {
                         #name ::new(). #reply((deps, env).into(), msg).map_err(Into::into)
                     }
                 },
@@ -1119,7 +1170,7 @@ impl EntryPoints {
                         env: #sylvia ::cw_std::Env,
                         info: #sylvia ::cw_std::MessageInfo,
                         msg: InstantiateMsg,
-                    ) -> Result<#sylvia ::cw_std::Response, #error> {
+                    ) -> Result<#resp_type, #error> {
                         msg.dispatch(&#name ::new() , (deps, env, info)).map_err(Into::into)
                     }
 
@@ -1129,7 +1180,7 @@ impl EntryPoints {
                         env: #sylvia ::cw_std::Env,
                         info: #sylvia ::cw_std::MessageInfo,
                         msg: ContractExecMsg,
-                    ) -> Result<#sylvia ::cw_std::Response, #error> {
+                    ) -> Result<#resp_type, #error> {
                         msg.dispatch(&#name ::new(), (deps, env, info)).map_err(Into::into)
                     }
 
diff --git a/sylvia-derive/src/parser.rs b/sylvia-derive/src/parser.rs
index bd89430..0b145be 100644
--- a/sylvia-derive/src/parser.rs
+++ b/sylvia-derive/src/parser.rs
@@ -10,52 +10,12 @@ use syn::{
 
 use crate::crate_module;
 
-/// Parsed arguments for `interface` macro
-pub struct InterfaceArgs {
-    /// Module name wrapping generated messages, by default no additional module is created
-    pub module: Option<Ident>,
-    /// The type being a parameter of `CosmosMsg` for blockchain it is intendet to be used; can be
-    /// set to any of generic parameters to create interface being generic over blockchains; If not
-    /// provided, cosmos messages would be unparametrized (so default would be used)
-    pub msg_type: Option<Type>,
-}
-
 /// Parser arguments for `contract` macro
 pub struct ContractArgs {
     /// Module name wrapping generated messages, by default no additional module is created
     pub module: Option<Path>,
 }
 
-impl Parse for InterfaceArgs {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let mut module = None;
-        let mut msg_type = None;
-
-        while !input.is_empty() {
-            let attr: Ident = input.parse()?;
-            let _: Token![=] = input.parse()?;
-
-            if attr == "module" {
-                module = Some(input.parse()?);
-            } else if attr == "msg_type" {
-                msg_type = Some(input.parse()?);
-            } else {
-                return Err(Error::new(attr.span(), "expected `module`, or `msg_type`"));
-            }
-
-            if input.peek(Token![,]) {
-                let _: Token![,] = input.parse()?;
-            } else if !input.is_empty() {
-                return Err(input.error("Unexpected token, comma expected"));
-            }
-        }
-
-        let _: Nothing = input.parse()?;
-
-        Ok(InterfaceArgs { module, msg_type })
-    }
-}
-
 impl Parse for ContractArgs {
     fn parse(input: ParseStream) -> Result<Self> {
         let mut module = None;
@@ -122,21 +82,15 @@ impl MsgType {
     }
 
     /// Emits type which should be returned by dispatch function for this kind of message
-    pub fn emit_result_type(self, msg_type: &Option<Type>, err_type: &Type) -> TokenStream {
+    pub fn emit_result_type(self, msg_type: &Type, err_type: &Type) -> TokenStream {
         use MsgType::*;
 
         let sylvia = crate_module();
 
-        #[cfg(not(tarpaulin_include))]
-        let response_type = msg_type
-            .as_ref()
-            .map(|ty| quote! { #sylvia ::cw_std::Response< #ty > })
-            .unwrap_or_else(|| quote! { #sylvia ::cw_std::Response });
-
         match self {
             Exec | Instantiate | Migrate | Reply => {
                 quote! {
-                    std::result::Result<#response_type, #err_type>
+                    std::result::Result< #sylvia:: cw_std::Response <#msg_type>, #err_type>
                 }
             }
             Query => quote! {
@@ -227,6 +181,7 @@ pub struct ContractMessageAttr {
     pub exec_generic_params: Vec<Path>,
     pub query_generic_params: Vec<Path>,
     pub variant: Ident,
+    pub has_custom_msg: bool,
 }
 
 #[cfg(not(tarpaulin_include))]
@@ -265,6 +220,7 @@ impl Parse for ContractMessageAttr {
         let generics_open: Option<Token![:]> = content.parse()?;
         let mut exec_generic_params = vec![];
         let mut query_generic_params = vec![];
+        let mut has_custom_msg = false;
 
         if generics_open.is_some() {
             loop {
@@ -288,9 +244,19 @@ impl Parse for ContractMessageAttr {
         }
 
         let _: Token![as] = content.parse()?;
-
         let variant = content.parse()?;
 
+        if content.peek(Token![:]) {
+            let _: Token![:] = content.parse()?;
+            let attr: Ident = content.parse()?;
+            if attr == "custom" {
+                let custom_content;
+                parenthesized!(custom_content in content);
+                let custom = custom_content.parse::<Path>()?;
+                has_custom_msg = custom.is_ident("msg");
+            }
+        }
+
         if !content.is_empty() {
             return Err(Error::new(
                 content.span(),
@@ -303,6 +269,7 @@ impl Parse for ContractMessageAttr {
             exec_generic_params,
             query_generic_params,
             variant,
+            has_custom_msg,
         })
     }
 }
@@ -346,26 +313,16 @@ pub fn parse_struct_message(source: &ItemImpl, ty: MsgType) -> Option<(&ImplItem
     Some((method, msg_attr))
 }
 
-pub struct Custom {
-    _msg: Path,
-    _query: Path,
-}
-
-impl Default for Custom {
-    fn default() -> Self {
-        let sylvia = crate_module();
-
-        Self {
-            _msg: parse_quote!(#sylvia ::cw_std::Empty),
-            _query: parse_quote!(#sylvia ::cw_std::Empty),
-        }
-    }
+#[derive(Debug, Default)]
+pub struct Custom<'a> {
+    msg: Option<Type>,
+    _query: Option<Type>,
+    input_attr: Option<&'a Attribute>,
 }
 
-impl Custom {
-    pub fn new(source: &ItemImpl) -> Self {
-        let custom: Vec<_> = source
-            .attrs
+impl<'a> Custom<'a> {
+    pub fn new(attrs: &'a [Attribute]) -> Self {
+        let mut customs = attrs
             .iter()
             .filter(|attr| match sylvia_attribute(attr) {
                 Some(attr) => attr == "custom",
@@ -373,7 +330,10 @@ impl Custom {
             })
             .filter_map(|attr| {
                 let custom = match Custom::parse.parse2(attr.tokens.clone()) {
-                    Ok(custom) => custom,
+                    Ok(mut custom) => {
+                        custom.input_attr = Some(attr);
+                        custom
+                    }
                     Err(err) => {
                         emit_error!(attr.span(), err);
                         return None;
@@ -381,26 +341,37 @@ impl Custom {
                 };
 
                 Some(custom)
-            })
-            .collect();
-
-        match custom.len() {
-            0 => Self::default(),
-            1 => custom.into_iter().next().unwrap(),
-            _ => {
-                emit_error!(
-                    source.span(),
-                    "More than one `custom` attribute defined. It should be only one"
-                );
-                Self::default()
-            }
+            });
+
+        let custom = customs.next().unwrap_or_default();
+
+        for redefined in customs {
+            let redefined = redefined.input_attr.unwrap();
+            emit_error!(
+              redefined, "The attribute `custom` is redefined";
+              note = custom.input_attr.span() => "Previous definition of the attribute `custom`";
+              note = "Only one `custom` attribute can exist on a single sylvia entity"
+            );
         }
+
+        custom
+    }
+
+    pub fn msg(&self) -> Type {
+        let sylvia = crate_module();
+        self.msg
+            .clone()
+            .unwrap_or_else(|| parse_quote! { #sylvia ::cw_std::Empty })
+    }
+
+    pub fn has_msg(&self) -> bool {
+        self.msg.is_some()
     }
 }
 
 #[cfg(not(tarpaulin_include))]
 // False negative. It is being called in closure
-impl Parse for Custom {
+impl Parse for Custom<'_> {
     fn parse(input: ParseStream) -> Result<Self> {
         let content;
         parenthesized!(content in input);
@@ -410,9 +381,9 @@ impl Parse for Custom {
             let ty: Ident = content.parse()?;
             let _: Token![=] = content.parse()?;
             if ty == "msg" {
-                custom._msg = content.parse()?
+                custom.msg = Some(content.parse()?)
             } else if ty == "query" {
-                custom._query = content.parse()?
+                custom._query = Some(content.parse()?)
             } else {
                 return Err(Error::new(
                     ty.span(),
diff --git a/sylvia-derive/src/strip_input.rs b/sylvia-derive/src/strip_input.rs
index 6cfc626..4fd4b77 100644
--- a/sylvia-derive/src/strip_input.rs
+++ b/sylvia-derive/src/strip_input.rs
@@ -1,6 +1,9 @@
 use syn::fold::{self, Fold};
 use syn::punctuated::Punctuated;
-use syn::{FnArg, ImplItemMethod, ItemImpl, PatType, Receiver, Signature, Token, TraitItemMethod};
+use syn::{
+    FnArg, ImplItemMethod, ItemImpl, ItemTrait, PatType, Receiver, Signature, Token,
+    TraitItemMethod,
+};
 
 use crate::parser::sylvia_attribute;
 
@@ -55,6 +58,16 @@ impl Fold for StripInput {
         fold::fold_impl_item_method(self, ImplItemMethod { attrs, sig, ..i })
     }
 
+    fn fold_item_trait(&mut self, i: ItemTrait) -> ItemTrait {
+        let attrs = i
+            .attrs
+            .into_iter()
+            .filter(|attr| sylvia_attribute(attr).is_none())
+            .collect();
+
+        fold::fold_item_trait(self, ItemTrait { attrs, ..i })
+    }
+
     fn fold_item_impl(&mut self, i: ItemImpl) -> ItemImpl {
         let attrs = i
             .attrs


Test patch:
diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs
index 62c2a6e..3bee29d 100644
--- a/sylvia-derive/src/multitest.rs
+++ b/sylvia-derive/src/multitest.rs
@@ -9,7 +9,7 @@ use syn::{FnArg, GenericParam, ImplItem, ItemImpl, ItemTrait, Pat, PatType, Path
 use crate::check_generics::CheckGenerics;
 use crate::crate_module;
 use crate::message::MsgField;
-use crate::parser::{parse_struct_message, ContractMessageAttr, MsgAttr, MsgType};
+use crate::parser::{parse_struct_message, ContractMessageAttr, Custom, MsgAttr, MsgType};
 use crate::utils::{extract_return_type, process_fields};
 
 struct MessageSignature<'a> {
@@ -31,6 +31,7 @@ pub struct MultitestHelpers<'a> {
     generics: &'a [&'a GenericParam],
     contract_name: &'a Ident,
     proxy_name: Ident,
+    custom: &'a Custom<'a>,
 }
 
 fn interface_name(source: &ItemImpl) -> &Ident {
@@ -58,6 +59,7 @@ impl<'a> MultitestHelpers<'a> {
         is_trait: bool,
         contract_error: &'a Type,
         generics: &'a [&'a GenericParam],
+        custom: &'a Custom,
     ) -> Self {
         let mut is_migrate = false;
         let mut reply = None;
@@ -194,6 +196,7 @@ impl<'a> MultitestHelpers<'a> {
             generics,
             contract_name,
             proxy_name,
+            custom,
         }
     }
 
@@ -204,6 +207,7 @@ impl<'a> MultitestHelpers<'a> {
             proxy_name,
             source,
             is_trait,
+            custom,
             ..
         } = self;
         let sylvia = crate_module();
@@ -212,8 +216,21 @@ impl<'a> MultitestHelpers<'a> {
             return self.impl_trait_on_proxy();
         }
 
+        let custom_msg = custom.msg();
         #[cfg(not(tarpaulin_include))]
-        let mt_app = quote! {#sylvia ::cw_multi_test::App};
+        let mt_app = quote! {
+            #sylvia ::cw_multi_test::App<
+                BankT,
+                ApiT,
+                StorageT,
+                CustomT,
+                WasmT,
+                StakingT,
+                DistrT,
+                IbcT,
+                GovT,
+            >
+        };
 
         #[cfg(not(tarpaulin_include))]
         let messages = messages.iter().map(|msg| {
@@ -227,7 +244,7 @@ impl<'a> MultitestHelpers<'a> {
             if msg_ty == &MsgType::Exec {
                     quote! {
                         #[track_caller]
-                        pub fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, ExecMsg, #mt_app, #sylvia ::cw_std::Empty> {
+                        pub fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, ExecMsg, #mt_app, #custom_msg> {
                             let msg = ExecMsg:: #name ( #(#arguments),* );
 
                             #sylvia ::multitest::ExecProxy::new(&self.contract_addr, msg, &self.app)
@@ -236,13 +253,13 @@ impl<'a> MultitestHelpers<'a> {
             } else if msg_ty == &MsgType::Migrate {
                     quote! {
                         #[track_caller]
-                        pub fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::MigrateProxy::<#error_type, MigrateMsg, #mt_app, #sylvia ::cw_std::Empty> {
+                        pub fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::MigrateProxy::<#error_type, MigrateMsg, #mt_app, #custom_msg> {
                             let msg = MigrateMsg::new( #(#arguments),* );
 
                             #sylvia ::multitest::MigrateProxy::new(&self.contract_addr, msg, &self.app)
                     }
                 }
-            } else {
+            } else if msg_ty == &MsgType::Query {
                     quote! {
                         pub fn #name (&self, #(#params,)* ) -> Result<#return_type, #error_type> {
                             let msg = QueryMsg:: #name ( #(#arguments),* );
@@ -254,6 +271,8 @@ impl<'a> MultitestHelpers<'a> {
                                 .map_err(Into::into)
                         }
                     }
+            } else {
+                quote! {}
             }
         });
 
@@ -276,7 +295,7 @@ impl<'a> MultitestHelpers<'a> {
                         );
 
                         quote! {
-                            pub fn #method_name (&self) -> #module ::trait_utils:: #proxy_name <'app> {
+                            pub fn #method_name (&self) -> #module ::trait_utils:: #proxy_name <'app, #mt_app> {
                                 #module ::trait_utils:: #proxy_name ::new(self.contract_addr.clone(), self.app)
                             }
                         }
@@ -291,11 +310,6 @@ impl<'a> MultitestHelpers<'a> {
             })
             .collect();
 
-        #[cfg(not(tarpaulin_include))]
-        let mt_app = quote! {
-            #sylvia ::cw_multi_test::App
-        };
-
         #[cfg(not(tarpaulin_include))]
         {
             quote! {
@@ -306,14 +320,33 @@ impl<'a> MultitestHelpers<'a> {
 
                     #[derive(Derivative)]
                     #[derivative(Debug)]
-                    pub struct #proxy_name <'app> {
+                    pub struct #proxy_name <'app, MtApp> {
                         pub contract_addr: #sylvia ::cw_std::Addr,
                         #[derivative(Debug="ignore")]
-                        pub app: &'app #sylvia ::multitest::App< #mt_app>,
+                        pub app: &'app #sylvia ::multitest::App<MtApp>,
                     }
 
-                    impl<'app> #proxy_name <'app> {
-                        pub fn new(contract_addr: #sylvia ::cw_std::Addr, app: &'app #sylvia ::multitest::App< #mt_app>) -> Self {
+                    impl<'app, BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> #proxy_name <'app, #mt_app >
+                        where
+                            CustomT: #sylvia ::cw_multi_test::Module,
+                            CustomT::ExecT: std::fmt::Debug
+                                + PartialEq
+                                + Clone
+                                + #sylvia ::schemars::JsonSchema
+                                + #sylvia ::serde::de::DeserializeOwned
+                                + 'static,
+                            CustomT::QueryT: #sylvia ::cw_std::CustomQuery + #sylvia ::serde::de::DeserializeOwned + 'static,
+                            WasmT: #sylvia ::cw_multi_test::Wasm<CustomT::ExecT, CustomT::QueryT>,
+                            BankT: #sylvia ::cw_multi_test::Bank,
+                            ApiT: #sylvia ::cw_std::Api,
+                            StorageT: #sylvia ::cw_std::Storage,
+                            StakingT: #sylvia ::cw_multi_test::Staking,
+                            DistrT: #sylvia ::cw_multi_test::Distribution,
+                            IbcT: #sylvia ::cw_multi_test::Ibc,
+                            GovT: #sylvia ::cw_multi_test::Gov,
+                            #mt_app : Executor< #custom_msg >
+                    {
+                        pub fn new(contract_addr: #sylvia ::cw_std::Addr, app: &'app #sylvia ::multitest::App< #mt_app >) -> Self {
                             #proxy_name{ contract_addr, app }
                         }
 
@@ -322,8 +355,33 @@ impl<'a> MultitestHelpers<'a> {
                         #(#interfaces)*
                     }
 
-                    impl<'app> From<(#sylvia ::cw_std::Addr, &'app #sylvia ::multitest::App< #mt_app>)> for #proxy_name<'app> {
-                        fn from(input: (#sylvia ::cw_std::Addr, &'app #sylvia ::multitest::App< #mt_app>)) -> #proxy_name<'app> {
+                    impl<'app, BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT>
+                        From<(
+                            #sylvia ::cw_std::Addr,
+                            &'app #sylvia ::multitest::App<#mt_app>,
+                        )>
+                        for #proxy_name <'app, #mt_app >
+                    where
+                        CustomT: #sylvia ::cw_multi_test::Module,
+                        CustomT::ExecT: std::fmt::Debug
+                            + PartialEq
+                            + Clone
+                            + #sylvia ::schemars::JsonSchema
+                            + #sylvia ::serde::de::DeserializeOwned
+                            + 'static,
+                        CustomT::QueryT: #sylvia ::cw_std::CustomQuery + #sylvia ::serde::de::DeserializeOwned + 'static,
+                        WasmT: #sylvia ::cw_multi_test::Wasm<CustomT::ExecT, CustomT::QueryT>,
+                        BankT: #sylvia ::cw_multi_test::Bank,
+                        ApiT: #sylvia ::cw_std::Api,
+                        StorageT: #sylvia ::cw_std::Storage,
+                        StakingT: #sylvia ::cw_multi_test::Staking,
+                        DistrT: #sylvia ::cw_multi_test::Distribution,
+                        IbcT: #sylvia ::cw_multi_test::Ibc,
+                        GovT: #sylvia ::cw_multi_test::Gov,
+                        #mt_app : Executor< #custom_msg >,
+                    {
+                        fn from(input: (#sylvia ::cw_std::Addr, &'app #sylvia ::multitest::App< #mt_app >))
+                            -> #proxy_name<'app, #mt_app > {
                             #proxy_name::new(input.0, input.1)
                         }
                     }
@@ -339,6 +397,7 @@ impl<'a> MultitestHelpers<'a> {
             messages,
             error_type,
             source,
+            custom,
             ..
         } = self;
 
@@ -367,6 +426,7 @@ impl<'a> MultitestHelpers<'a> {
             )
             .collect();
 
+        #[cfg(not(tarpaulin_include))]
         let module = match modules.len() {
             0 => {
                 quote! {}
@@ -384,9 +444,21 @@ impl<'a> MultitestHelpers<'a> {
             }
         };
 
+        let custom_msg = custom.msg();
+
         #[cfg(not(tarpaulin_include))]
         let mt_app = quote! {
-            #sylvia ::cw_multi_test::App
+            #sylvia ::cw_multi_test::App<
+                BankT,
+                ApiT,
+                StorageT,
+                CustomT,
+                WasmT,
+                StakingT,
+                DistrT,
+                IbcT,
+                GovT,
+            >
         };
 
         #[cfg(not(tarpaulin_include))]
@@ -401,7 +473,7 @@ impl<'a> MultitestHelpers<'a> {
             if msg_ty == &MsgType::Exec {
                 quote! {
                     #[track_caller]
-                    fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, #module ExecMsg, #mt_app,  #sylvia ::cw_std::Empty> {
+                    fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, #module ExecMsg, #mt_app, #custom_msg> {
                         let msg = #module ExecMsg:: #name ( #(#arguments),* );
 
                         #sylvia ::multitest::ExecProxy::new(&self.contract_addr, msg, &self.app)
@@ -433,7 +505,7 @@ impl<'a> MultitestHelpers<'a> {
             } = msg;
             if msg_ty == &MsgType::Exec {
                 quote! {
-                    fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, #module ExecMsg, #mt_app, #sylvia ::cw_std::Empty>;
+                    fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, #module ExecMsg, MtApp, #custom_msg>;
                 }
             } else {
                 quote! {
@@ -448,11 +520,32 @@ impl<'a> MultitestHelpers<'a> {
                 pub mod test_utils {
                     use super::*;
 
-                    pub trait #trait_name {
+                    pub trait #trait_name<MtApp> {
                         #(#methods_declarations)*
                     }
 
-                    impl #trait_name for #module trait_utils:: #proxy_name<'_> {
+                    impl<BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> #trait_name< #mt_app > for #module trait_utils:: #proxy_name<'_, #mt_app >
+                    where
+                        CustomT: #sylvia ::cw_multi_test::Module,
+                        WasmT: #sylvia ::cw_multi_test::Wasm<CustomT::ExecT, CustomT::QueryT>,
+                        BankT: #sylvia ::cw_multi_test::Bank,
+                        ApiT: #sylvia ::cw_std::Api,
+                        StorageT: #sylvia ::cw_std::Storage,
+                        CustomT: #sylvia ::cw_multi_test::Module,
+                        StakingT: #sylvia ::cw_multi_test::Staking,
+                        DistrT: #sylvia ::cw_multi_test::Distribution,
+                        IbcT: #sylvia ::cw_multi_test::Ibc,
+                        GovT: #sylvia ::cw_multi_test::Gov,
+                        CustomT::ExecT: Clone
+                            + std::fmt::Debug
+                            + PartialEq
+                            + #sylvia ::schemars::JsonSchema
+                            + #sylvia ::serde::de::DeserializeOwned
+                            + 'static,
+                        CustomT::QueryT: #sylvia:: cw_std::CustomQuery + #sylvia ::serde::de::DeserializeOwned + 'static,
+                        #mt_app : #sylvia ::cw_multi_test::Executor< #custom_msg >
+                    {
+
                         #(#methods_definitions)*
                     }
                 }
@@ -490,9 +583,21 @@ impl<'a> MultitestHelpers<'a> {
 
         let impl_contract = self.generate_impl_contract();
 
+        let custom_msg = self.custom.msg();
+
         #[cfg(not(tarpaulin_include))]
         let mt_app = quote! {
-             #sylvia ::cw_multi_test::App
+            #sylvia ::cw_multi_test::App<
+                BankT,
+                ApiT,
+                StorageT,
+                CustomT,
+                #sylvia ::cw_multi_test::WasmKeeper< #custom_msg , #sylvia ::cw_std::Empty>,
+                StakingT,
+                DistrT,
+                IbcT,
+                GovT,
+            >
         };
 
         #[cfg(not(tarpaulin_include))]
@@ -500,13 +605,23 @@ impl<'a> MultitestHelpers<'a> {
             quote! {
                 #impl_contract
 
-                pub struct CodeId<'app> {
+                pub struct CodeId<'app, MtApp> {
                     code_id: u64,
-                    app: &'app #sylvia ::multitest::App< #mt_app>,
+                    app: &'app #sylvia ::multitest::App<MtApp>,
                 }
 
-                impl<'app> CodeId<'app> {
-                    pub fn store_code(app: &'app #sylvia ::multitest::App< #mt_app>) -> Self {
+                impl<'app, BankT, ApiT, StorageT, CustomT, StakingT, DistrT, IbcT, GovT> CodeId<'app, #mt_app>
+                    where
+                        BankT: #sylvia ::cw_multi_test::Bank,
+                        ApiT: #sylvia ::cw_std::Api,
+                        StorageT: #sylvia ::cw_std::Storage,
+                        CustomT: #sylvia ::cw_multi_test::Module<ExecT = #custom_msg, QueryT = #sylvia ::cw_std::Empty >,
+                        StakingT: #sylvia ::cw_multi_test::Staking,
+                        DistrT: #sylvia ::cw_multi_test::Distribution,
+                        IbcT: #sylvia ::cw_multi_test::Ibc,
+                        GovT: #sylvia ::cw_multi_test::Gov,
+                {
+                    pub fn store_code(app: &'app #sylvia ::multitest::App< #mt_app >) -> Self {
                         let code_id = app
                             .app_mut()
                             .store_code(Box::new(#contract_name ::new()));
@@ -518,8 +633,8 @@ impl<'a> MultitestHelpers<'a> {
                     }
 
                     pub fn instantiate(
-                        &self, #(#fields,)*
-                    ) -> InstantiateProxy<'_, 'app> {
+                        &self,#(#fields,)*
+                    ) -> InstantiateProxy<'_, 'app, #mt_app > {
                         let msg = InstantiateMsg {#(#fields_names,)*};
                         InstantiateProxy {
                             code_id: self,
@@ -531,15 +646,18 @@ impl<'a> MultitestHelpers<'a> {
                     }
                 }
 
-                pub struct InstantiateProxy<'a, 'app> {
-                    code_id: &'a CodeId <'app>,
+                pub struct InstantiateProxy<'a, 'app, MtApp> {
+                    code_id: &'a CodeId <'app, MtApp>,
                     funds: &'a [#sylvia ::cw_std::Coin],
                     label: &'a str,
                     admin: Option<String>,
                     msg: InstantiateMsg,
                 }
 
-                impl<'a, 'app> InstantiateProxy<'a, 'app> {
+                impl<'a, 'app, MtApp> InstantiateProxy<'a, 'app, MtApp>
+                    where
+                        MtApp: Executor< #custom_msg >,
+                {
                     pub fn with_funds(self, funds: &'a [#sylvia ::cw_std::Coin]) -> Self {
                         Self { funds, ..self }
                     }
@@ -554,7 +672,7 @@ impl<'a> MultitestHelpers<'a> {
                     }
 
                     #[track_caller]
-                    pub fn call(self, sender: &str) -> Result<#proxy_name<'app>, #error_type> {
+                    pub fn call(self, sender: &str) -> Result<#proxy_name<'app, MtApp>, #error_type> {
                         (*self.code_id.app)
                             .app_mut()
                             .instantiate_contract(
@@ -577,7 +695,9 @@ impl<'a> MultitestHelpers<'a> {
     }
 
     fn generate_impl_contract(&self) -> TokenStream {
-        let contract = &self.contract;
+        let Self {
+            contract, custom, ..
+        } = self;
         let sylvia = crate_module();
 
         #[cfg(not(tarpaulin_include))]
@@ -604,17 +724,19 @@ impl<'a> MultitestHelpers<'a> {
             }
         };
 
+        let custom_msg = custom.msg();
+
         #[cfg(not(tarpaulin_include))]
         {
             quote! {
-                impl #sylvia ::cw_multi_test::Contract<#sylvia ::cw_std::Empty> for #contract {
+                impl #sylvia ::cw_multi_test::Contract<#custom_msg> for #contract {
                     fn execute(
                         &self,
                         deps: #sylvia ::cw_std::DepsMut<#sylvia ::cw_std::Empty>,
                         env: #sylvia ::cw_std::Env,
                         info: #sylvia ::cw_std::MessageInfo,
                         msg: Vec<u8>,
-                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
+                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#custom_msg>> {
                         #sylvia ::cw_std::from_slice::<ContractExecMsg>(&msg)?
                             .dispatch(self, (deps, env, info))
                             .map_err(Into::into)
@@ -626,7 +748,7 @@ impl<'a> MultitestHelpers<'a> {
                         env: #sylvia ::cw_std::Env,
                         info: #sylvia ::cw_std::MessageInfo,
                         msg: Vec<u8>,
-                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
+                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#custom_msg>> {
                         #sylvia ::cw_std::from_slice::<InstantiateMsg>(&msg)?
                             .dispatch(self, (deps, env, info))
                             .map_err(Into::into)
@@ -648,7 +770,7 @@ impl<'a> MultitestHelpers<'a> {
                         _deps: #sylvia ::cw_std::DepsMut<#sylvia ::cw_std::Empty>,
                         _env: #sylvia ::cw_std::Env,
                         _msg: Vec<u8>,
-                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
+                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#custom_msg>> {
                         #sylvia ::anyhow::bail!("sudo not implemented for contract")
                     }
 
@@ -657,7 +779,7 @@ impl<'a> MultitestHelpers<'a> {
                         deps: #sylvia ::cw_std::DepsMut<#sylvia ::cw_std::Empty>,
                         env: #sylvia ::cw_std::Env,
                         msg: #sylvia ::cw_std::Reply,
-                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
+                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#custom_msg>> {
                         #reply
                     }
 
@@ -666,7 +788,7 @@ impl<'a> MultitestHelpers<'a> {
                         deps: #sylvia ::cw_std::DepsMut<#sylvia ::cw_std::Empty>,
                         env: #sylvia ::cw_std::Env,
                         msg: Vec<u8>,
-                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
+                    ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#custom_msg>> {
                         #migrate_body
                     }
                 }
@@ -691,26 +813,21 @@ impl<'a> TraitMultitestHelpers<'a> {
         let sylvia = crate_module();
         let proxy_name = Ident::new(&format!("{}Proxy", trait_name), trait_name.span());
 
-        #[cfg(not(tarpaulin_include))]
-        let mt_app = quote! {
-             #sylvia ::cw_multi_test::App
-        };
-
         #[cfg(not(tarpaulin_include))]
         {
             quote! {
                 pub mod trait_utils {
-                    pub struct #proxy_name <'app> {
+                    pub struct #proxy_name <'app, MtApp> {
                         pub contract_addr: #sylvia ::cw_std::Addr,
-                        pub app: &'app #sylvia ::multitest::App < #mt_app >,
+                        pub app: &'app #sylvia ::multitest::App <MtApp>,
                     }
-                    impl<'app> #proxy_name <'app> {
-                        pub fn new(contract_addr: #sylvia ::cw_std::Addr, app: &'app #sylvia ::multitest::App < #mt_app >) -> Self {
+                    impl<'app, MtApp> #proxy_name <'app, MtApp> {
+                        pub fn new(contract_addr: #sylvia ::cw_std::Addr, app: &'app #sylvia ::multitest::App < MtApp >) -> Self {
                             #proxy_name { contract_addr, app }
                         }
                     }
                     #[allow(clippy::from_over_into)]
-                    impl Into<#sylvia ::cw_std::Addr> for #proxy_name <'_> {
+                    impl<MtApp> Into<#sylvia ::cw_std::Addr> for #proxy_name <'_, MtApp> {
                         fn into(self) -> #sylvia ::cw_std::Addr {
                             self.contract_addr
                         }
diff --git a/sylvia/tests/custom_msg.rs b/sylvia/tests/custom_msg.rs
index b6f0252..85c00e2 100644
--- a/sylvia/tests/custom_msg.rs
+++ b/sylvia/tests/custom_msg.rs
@@ -2,21 +2,157 @@ use cosmwasm_std::{CustomMsg, CustomQuery, Response, StdResult};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use sylvia::contract;
-use sylvia::types::InstantiateCtx;
+use sylvia::types::{ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx};
 
 #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, JsonSchema)]
-struct MyMsg;
+pub struct MyMsg;
 
 impl CustomMsg for MyMsg {}
 
 #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, JsonSchema)]
-struct MyQuery;
+pub struct OtherMsg;
+
+impl CustomMsg for OtherMsg {}
+
+#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, JsonSchema)]
+pub struct MyQuery;
 
 impl CustomQuery for MyQuery {}
 
 pub struct MyContract;
 
+#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, JsonSchema)]
+pub struct SomeResponse;
+
+mod some_interface {
+    use cosmwasm_std::{Response, StdError, StdResult};
+    use sylvia::types::{ExecCtx, QueryCtx};
+    use sylvia::{contract, interface};
+
+    use crate::{MyMsg, SomeResponse};
+
+    #[interface]
+    #[sv::custom(msg=MyMsg, query=MyQuery)]
+    pub trait SomeInterface {
+        type Error: From<StdError>;
+
+        #[cfg(not(tarpaulin_include))]
+        #[msg(query)]
+        fn interface_query(&self, ctx: QueryCtx) -> StdResult<SomeResponse>;
+
+        #[cfg(not(tarpaulin_include))]
+        #[msg(exec)]
+        fn interface_exec(&self, ctx: ExecCtx) -> StdResult<Response<MyMsg>>;
+    }
+
+    #[contract(module=super)]
+    #[sv::custom(msg=MyMsg, query=MyQuery)]
+    impl SomeInterface for crate::MyContract {
+        type Error = StdError;
+
+        #[msg(query)]
+        fn interface_query(&self, _ctx: QueryCtx) -> StdResult<SomeResponse> {
+            Ok(SomeResponse)
+        }
+
+        #[msg(exec)]
+        fn interface_exec(&self, _ctx: ExecCtx) -> StdResult<Response<MyMsg>> {
+            Ok(Response::default())
+        }
+    }
+}
+
+// Use `#[sv::custom(..)]` if both it and associated type defined
+mod interface {
+    use crate::{MyMsg, OtherMsg};
+    use cosmwasm_std::{CustomMsg, Response, StdError, StdResult};
+    use sylvia::types::ExecCtx;
+    use sylvia::{contract, interface};
+
+    #[interface]
+    #[sv::custom(msg=MyMsg)]
+    pub trait Interface {
+        type Error: From<StdError>;
+        type ExecC: CustomMsg;
+
+        #[cfg(not(tarpaulin_include))]
+        #[msg(exec)]
+        fn exec(&self, ctx: ExecCtx) -> StdResult<Response<MyMsg>>;
+    }
+
+    #[contract(module=super)]
+    #[sv::custom(msg=MyMsg)]
+    impl Interface for crate::MyContract {
+        type Error = StdError;
+        type ExecC = OtherMsg;
+
+        #[msg(exec)]
+        fn exec(&self, _ctx: ExecCtx) -> StdResult<Response<MyMsg>> {
+            Ok(Response::default())
+        }
+    }
+}
+
+mod other_interface {
+    use cosmwasm_std::{Response, StdError, StdResult};
+    use sylvia::types::ExecCtx;
+    use sylvia::{contract, interface};
+
+    #[interface]
+    pub trait OtherInterface {
+        type Error: From<StdError>;
+
+        #[cfg(not(tarpaulin_include))]
+        #[msg(exec)]
+        fn other_interface_exec(&self, ctx: ExecCtx) -> StdResult<Response>;
+    }
+
+    #[contract(module=super)]
+    #[sv::custom(msg=crate::MyMsg)]
+    impl OtherInterface for crate::MyContract {
+        type Error = StdError;
+
+        #[msg(exec)]
+        fn other_interface_exec(&self, _ctx: ExecCtx) -> StdResult<Response> {
+            Ok(Response::default())
+        }
+    }
+}
+
+mod associated_interface {
+    use crate::MyMsg;
+    use cosmwasm_std::{CustomMsg, Response, StdError, StdResult};
+    use sylvia::types::ExecCtx;
+    use sylvia::{contract, interface};
+
+    #[interface]
+    pub trait AssociatedInterface {
+        type Error: From<StdError>;
+        type ExecC: CustomMsg;
+
+        #[cfg(not(tarpaulin_include))]
+        #[msg(exec)]
+        fn associated_exec(&self, ctx: ExecCtx) -> StdResult<Response<Self::ExecC>>;
+    }
+
+    #[contract(module=super)]
+    #[sv::custom(msg=MyMsg)]
+    impl AssociatedInterface for crate::MyContract {
+        type Error = StdError;
+        type ExecC = MyMsg;
+
+        #[msg(exec)]
+        fn associated_exec(&self, _ctx: ExecCtx) -> StdResult<Response<Self::ExecC>> {
+            Ok(Response::default())
+        }
+    }
+}
+
 #[contract]
+#[messages(some_interface as SomeInterface)]
+#[messages(other_interface as OtherInterface: custom(msg))]
+#[messages(associated_interface as AssociatedInterface)]
+#[messages(interface as Interface)]
 #[sv::custom(msg=MyMsg, query=MyQuery)]
 impl MyContract {
     #[allow(clippy::new_without_default)]
@@ -25,17 +161,78 @@ impl MyContract {
     }
 
     #[msg(instantiate)]
-    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response> {
+    pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult<Response<MyMsg>> {
+        Ok(Response::default())
+    }
+
+    #[msg(exec)]
+    pub fn some_exec(&self, _ctx: ExecCtx) -> StdResult<Response<MyMsg>> {
+        Ok(Response::default())
+    }
+
+    #[msg(query)]
+    pub fn some_query(&self, _ctx: QueryCtx) -> StdResult<SomeResponse> {
+        Ok(SomeResponse)
+    }
+
+    #[cfg(not(tarpaulin_include))]
+    #[msg(migrate)]
+    pub fn some_migrate(&self, _ctx: MigrateCtx) -> StdResult<Response<MyMsg>> {
         Ok(Response::default())
     }
 }
 
-#[cfg(test)]
+#[cfg(all(test, feature = "mt"))]
 mod tests {
+    use crate::interface::test_utils::Interface;
     use crate::MyContract;
+    use sylvia::multitest::App;
+
+    use crate::associated_interface::test_utils::AssociatedInterface;
+    use crate::other_interface::test_utils::OtherInterface;
+    use crate::some_interface::test_utils::SomeInterface;
+    use crate::MyMsg;
 
     #[test]
     fn test_custom() {
         let _ = MyContract::new();
+        let app = App::<cw_multi_test::BasicApp<MyMsg>>::custom(|_, _, _| {});
+        let code_id = crate::multitest_utils::CodeId::store_code(&app);
+
+        let owner = "owner";
+
+        let contract = code_id
+            .instantiate()
+            .with_label("MyContract")
+            .call(owner)
+            .unwrap();
+
+        contract.some_exec().call(owner).unwrap();
+        contract.some_query().unwrap();
+
+        // Interface messsages
+        contract.some_interface_proxy().interface_query().unwrap();
+        contract
+            .some_interface_proxy()
+            .interface_exec()
+            .call(owner)
+            .unwrap();
+
+        // Other interface messages
+        contract
+            .other_interface_proxy()
+            .other_interface_exec()
+            .call(owner)
+            .unwrap();
+
+        // Associated interface messages
+        contract
+            .associated_interface_proxy()
+            .associated_exec()
+            .call(owner)
+            .unwrap();
+
+        // Both associated type and custom attr used
+        contract.interface_proxy().exec().call(owner).unwrap();
     }
 }