Repository: CosmWasm/sylvia
Pull request #386 (2024-05-29 11:07:02)

feat: Executors

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/CosmWasm/sylvia/pull/386?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) Report
Attention: Patch coverage is `80.88235%` with `13 lines` in your changes are missing coverage. Please review.
> Project coverage is 85.23%. Comparing base [(`0e69cc8`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/0e69cc884efbbdf1b934e5f8cbe222afdfa451a6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) to head [(`df7bee4`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/df7bee420322b4982d6272d522a731db158c33bb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).

| [Files](https://app.codecov.io/gh/CosmWasm/sylvia/pull/386?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) | Patch % | Lines |
|---|---|---|
| [sylvia-derive/src/msg\_traits.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/386?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fmsg_traits.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvbXNnX3RyYWl0cy5ycw==) | 79.62% | [11 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/386?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia/src/types.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/386?src=pr&el=tree&filepath=sylvia%2Fsrc%2Ftypes.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3NyYy90eXBlcy5ycw==) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/386?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #386      +/-   ##
==========================================
- Coverage   85.48%   85.23%   -0.26%     
==========================================
  Files          37       37              
  Lines        2639     2682      +43     
==========================================
+ Hits         2256     2286      +30     
- Misses        383      396      +13     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/CosmWasm/sylvia/pull/386?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).

kulikthebird: According to #130 _sylvia_ should provide `reply_on` method on the executor builder structure. From the _sylvia_ point of view it's better not to duplicate the interface of `SubMsg`. Instead of adding `reply_on()` method on `ExecutorBuilder`, user should use `build()` method and then use on of the `reply_*` methods of `SubMsg` to create that strucutre.

Patch:
diff --git a/sylvia-derive/src/executor.rs b/sylvia-derive/src/executor.rs
new file mode 100644
index 0000000..9e28875
--- /dev/null
+++ b/sylvia-derive/src/executor.rs
@@ -0,0 +1,195 @@
+use convert_case::Case;
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use syn::{GenericParam, Generics, Ident, Type};
+
+use crate::associated_types::{AssociatedTypes, EmitAssociated, ItemType};
+use crate::check_generics::GetPath;
+use crate::crate_module;
+use crate::message::{MsgField, MsgVariant, MsgVariants};
+use crate::parser::attributes::msg::MsgType;
+use crate::utils::{emit_bracketed_generics, SvCasing};
+
+pub struct InterfaceExecutor<'a, Generic> {
+    variants: &'a MsgVariants<'a, Generic>,
+    associated_types: &'a AssociatedTypes<'a>,
+    interface_name: &'a Ident,
+}
+
+impl<'a, Generic> InterfaceExecutor<'a, Generic>
+where
+    Generic: GetPath + PartialEq + ToTokens,
+{
+    pub fn new(
+        variants: &'a MsgVariants<'a, Generic>,
+        associated_types: &'a AssociatedTypes,
+        interface_name: &'a Ident,
+    ) -> Self {
+        Self {
+            variants,
+            associated_types,
+            interface_name,
+        }
+    }
+
+    pub fn emit_executor_trait(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            variants,
+            associated_types,
+            interface_name,
+        } = self;
+
+        let generics: Vec<_> = associated_types
+            .without_special()
+            .map(ItemType::as_name)
+            .collect();
+        let all_generics: Vec<_> = associated_types.all_names().collect();
+        let assoc_types: Vec<_> = generics
+            .iter()
+            .map(|assoc| quote! {Self:: #assoc})
+            .collect();
+        let bracketed_generics = emit_bracketed_generics(&assoc_types);
+
+        let accessor = MsgType::Exec.as_accessor_name();
+        let executor_api_path =
+            quote! { < Api #bracketed_generics as #sylvia ::types::InterfaceApi>:: #accessor };
+
+        let methods_trait_impl = variants
+            .variants()
+            .map(|variant| variant.emit_executor_impl(&executor_api_path))
+            .collect::<Vec<_>>();
+
+        let executor_methods_declaration = variants
+            .variants()
+            .map(|variant| variant.emit_executor_method_declaration());
+
+        let types_declaration = associated_types.filtered().collect::<Vec<_>>();
+        let where_clause = associated_types.as_where_clause();
+
+        quote! {
+            pub trait Executor {
+                #(#types_declaration)*
+                #(#executor_methods_declaration)*
+            }
+
+            impl <#(#all_generics,)*> Executor
+                for #sylvia ::types::ExecutorBuilder<(#sylvia ::types::EmptyExecutorBuilderState, dyn #interface_name <#( #all_generics = #all_generics,)* > ) > #where_clause {
+                #(type #generics = #generics;)*
+                #(#methods_trait_impl)*
+            }
+
+            impl <Contract: #interface_name> Executor
+                for #sylvia ::types::ExecutorBuilder<( #sylvia ::types::EmptyExecutorBuilderState, Contract )> {
+                #(type #generics = <Contract as #interface_name > :: #generics;)*
+                #(#methods_trait_impl)*
+            }
+        }
+    }
+}
+
+pub struct ContractExecutor<'a> {
+    generics: Generics,
+    self_ty: Type,
+    variants: MsgVariants<'a, GenericParam>,
+}
+
+impl<'a> ContractExecutor<'a> {
+    pub fn new(generics: Generics, self_ty: Type, variants: MsgVariants<'a, GenericParam>) -> Self {
+        Self {
+            generics,
+            self_ty,
+            variants,
+        }
+    }
+
+    pub fn emit(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            generics,
+            self_ty,
+            variants,
+            ..
+        } = self;
+
+        let where_clause = &generics.where_clause;
+        let generics: Vec<_> = generics.params.iter().collect();
+        let contract = &self_ty;
+
+        let accessor = MsgType::Exec.as_accessor_name();
+        let executor_api_path = quote! { < #contract as #sylvia ::types::ContractApi>:: #accessor };
+
+        let executor_methods_impl = variants
+            .variants()
+            .map(|variant| variant.emit_executor_impl(&executor_api_path));
+
+        let executor_methods_declaration = variants
+            .variants()
+            .map(|variant| variant.emit_executor_method_declaration());
+
+        let types_declaration = where_clause
+            .as_ref()
+            .map(EmitAssociated::emit_declaration)
+            .unwrap_or(vec![]);
+
+        let types_implementation = where_clause
+            .as_ref()
+            .map(EmitAssociated::emit_implementation)
+            .unwrap_or(vec![]);
+
+        quote! {
+            pub trait Executor<#(#generics,)*> #where_clause {
+                #(#types_declaration)*
+                #(#executor_methods_declaration)*
+            }
+
+            impl <#(#generics,)*> Executor<#(#generics,)*>
+                for #sylvia ::types::ExecutorBuilder<( #sylvia ::types::EmptyExecutorBuilderState, #contract )> #where_clause {
+                #(#types_implementation)*
+                #(#executor_methods_impl)*
+            }
+        }
+    }
+}
+
+trait EmitExecutorMethod {
+    fn emit_executor_impl(&self, api_path: &TokenStream) -> TokenStream;
+    fn emit_executor_method_declaration(&self) -> TokenStream;
+}
+
+impl EmitExecutorMethod for MsgVariant<'_> {
+    fn emit_executor_impl(&self, api_path: &TokenStream) -> TokenStream {
+        let name = self.name();
+        let fields = self.fields();
+        let sylvia = crate_module();
+
+        let parameters = fields.iter().map(MsgField::emit_method_field_folded);
+        let fields_names = fields.iter().map(MsgField::name);
+        let variant_name = name.to_case(Case::Snake);
+
+        quote! {
+            fn #variant_name(self, #(#parameters),*) -> Result<#sylvia ::types::ExecutorBuilder< #sylvia ::types::ReadyExecutorBuilderState >, #sylvia ::cw_std::StdError> {
+                Ok(#sylvia ::types::ExecutorBuilder::<#sylvia ::types::ReadyExecutorBuilderState>::new(
+                    self.contract().to_owned(),
+                    self.funds().to_owned(),
+                    #sylvia ::cw_std::to_json_binary( & #api_path :: #variant_name (#(#fields_names),*) )?,
+                ))
+            }
+        }
+    }
+
+    fn emit_executor_method_declaration(&self) -> TokenStream {
+        let name = self.name();
+        let sylvia = crate_module();
+
+        let parameters = self
+            .fields()
+            .iter()
+            .map(|field| field.emit_method_field_folded());
+        let variant_name = name.to_case(Case::Snake);
+
+        quote! {
+            fn #variant_name(self, #(#parameters),*) -> Result< #sylvia ::types::ExecutorBuilder<#sylvia ::types::ReadyExecutorBuilderState>, #sylvia ::cw_std::StdError>;
+        }
+    }
+}
diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs
index dbf7910..46bf805 100644
--- a/sylvia-derive/src/input.rs
+++ b/sylvia-derive/src/input.rs
@@ -4,6 +4,7 @@ use quote::quote;
 use syn::{GenericParam, Ident, ItemImpl, ItemTrait, TraitItem};
 
 use crate::associated_types::{AssociatedTypes, ItemType, EXEC_TYPE, QUERY_TYPE};
+use crate::executor::{ContractExecutor, InterfaceExecutor};
 use crate::interfaces::Interfaces;
 use crate::message::{
     ContractApi, ContractEnumMessage, EnumMessage, GlueMessage, InterfaceApi, MsgVariants,
@@ -15,7 +16,7 @@ use crate::parser::{
     assert_new_method_defined, ContractErrorAttr, Custom, OverrideEntryPoint,
     ParsedSylviaAttributes,
 };
-use crate::querier::{ContractQuerier, TraitQuerier};
+use crate::querier::{ContractQuerier, InterfaceQuerier};
 use crate::variant_descs::AsVariantDescs;
 
 /// Preprocessed `interface` macro input
@@ -91,10 +92,14 @@ impl<'a> TraitInput<'a> {
         let messages = self.emit_messages();
         let associated_names: Vec<_> = associated_types.as_filtered_names().collect();
 
+        let executor_variants =
+            MsgVariants::new(item.as_variants(), MsgType::Exec, &associated_names, &None);
         let query_variants =
             MsgVariants::new(item.as_variants(), MsgType::Query, &associated_names, &None);
-        let querier =
-            TraitQuerier::new(&query_variants, associated_types, &item.ident).emit_trait_querier();
+        let executor = InterfaceExecutor::new(&executor_variants, associated_types, &item.ident)
+            .emit_executor_trait();
+        let querier = InterfaceQuerier::new(&query_variants, associated_types, &item.ident)
+            .emit_querier_trait();
 
         let interface_messages = InterfaceApi::new(item, associated_types, custom).emit();
 
@@ -107,6 +112,8 @@ impl<'a> TraitInput<'a> {
 
                 #querier
 
+                #executor
+
                 #interface_messages
 
                 #multitest_helpers
@@ -204,7 +211,20 @@ impl<'a> ImplInput<'a> {
         } = self;
         let multitest_helpers = self.emit_multitest_helpers();
 
-        let querier = ContractQuerier::new(item).emit();
+        let executor_variants = MsgVariants::new(item.as_variants(), MsgType::Exec, &[], &None);
+        let querier_variants = MsgVariants::new(item.as_variants(), MsgType::Query, &[], &None);
+        let executor = ContractExecutor::new(
+            item.generics.clone(),
+            *item.self_ty.clone(),
+            executor_variants,
+        )
+        .emit();
+        let querier = ContractQuerier::new(
+            item.generics.clone(),
+            *item.self_ty.clone(),
+            querier_variants,
+        )
+        .emit();
         let messages = self.emit_messages();
         let contract_api = ContractApi::new(item, generics, custom).emit();
 
@@ -218,6 +238,8 @@ impl<'a> ImplInput<'a> {
 
                 #querier
 
+                #executor
+
                 #contract_api
             }
         }
diff --git a/sylvia-derive/src/lib.rs b/sylvia-derive/src/lib.rs
index e9fc14f..08f613f 100644
--- a/sylvia-derive/src/lib.rs
+++ b/sylvia-derive/src/lib.rs
@@ -12,6 +12,7 @@ use syn::{parse2, parse_quote, ItemImpl, ItemTrait, Path};
 
 mod associated_types;
 pub(crate) mod check_generics;
+mod executor;
 mod input;
 mod interfaces;
 mod message;
diff --git a/sylvia-derive/src/querier.rs b/sylvia-derive/src/querier.rs
index 9d30121..1bdbbce 100644
--- a/sylvia-derive/src/querier.rs
+++ b/sylvia-derive/src/querier.rs
@@ -1,7 +1,7 @@
 use convert_case::Case;
 use proc_macro2::TokenStream;
 use quote::{quote, ToTokens};
-use syn::{GenericParam, Ident, ItemImpl};
+use syn::{GenericParam, Generics, Ident, Type};
 
 use crate::associated_types::{AssociatedTypes, EmitAssociated, ItemType};
 use crate::check_generics::GetPath;
@@ -9,15 +9,14 @@ use crate::crate_module;
 use crate::message::{MsgField, MsgVariant, MsgVariants};
 use crate::parser::attributes::msg::MsgType;
 use crate::utils::{emit_bracketed_generics, SvCasing};
-use crate::variant_descs::AsVariantDescs;
 
-pub struct TraitQuerier<'a, Generic> {
+pub struct InterfaceQuerier<'a, Generic> {
     variants: &'a MsgVariants<'a, Generic>,
     associated_types: &'a AssociatedTypes<'a>,
     interface_name: &'a Ident,
 }
 
-impl<'a, Generic> TraitQuerier<'a, Generic>
+impl<'a, Generic> InterfaceQuerier<'a, Generic>
 where
     Generic: GetPath + PartialEq + ToTokens,
 {
@@ -33,7 +32,7 @@ where
         }
     }
 
-    pub fn emit_trait_querier(&self) -> TokenStream {
+    pub fn emit_querier_trait(&self) -> TokenStream {
         let sylvia = crate_module();
         let Self {
             variants,
@@ -60,29 +59,26 @@ where
             .map(|variant| variant.emit_querier_impl(&api_path))
             .collect::<Vec<_>>();
 
-        let methods_declaration = variants
+        let querier_methods_declaration = variants
             .variants()
-            .map(|variant| variant.emit_querier_declaration());
+            .map(|variant| variant.emit_querier_method_declaration());
 
-        let types_declaration = associated_types.filtered();
+        let types_declaration = associated_types.filtered().collect::<Vec<_>>();
         let where_clause = associated_types.as_where_clause();
 
         quote! {
             pub trait Querier {
                 #(#types_declaration)*
-
-                #(#methods_declaration)*
+                #(#querier_methods_declaration)*
             }
 
             impl <'a, C: #sylvia ::cw_std::CustomQuery, #(#all_generics,)*> Querier for #sylvia ::types::BoundQuerier<'a, C, dyn #interface_name <#( #all_generics = #all_generics,)*> > #where_clause {
                 #(type #generics = #generics;)*
-
                 #(#methods_trait_impl)*
             }
 
             impl <'a, C: #sylvia ::cw_std::CustomQuery, Contract: #interface_name> Querier for #sylvia ::types::BoundQuerier<'a, C, Contract> {
                 #(type #generics = <Contract as #interface_name > :: #generics;)*
-
                 #(#methods_trait_impl)*
             }
         }
@@ -90,35 +86,43 @@ where
 }
 
 pub struct ContractQuerier<'a> {
-    source: &'a ItemImpl,
+    generics: Generics,
+    self_ty: Type,
     variants: MsgVariants<'a, GenericParam>,
 }
 
 impl<'a> ContractQuerier<'a> {
-    pub fn new(source: &'a ItemImpl) -> Self {
-        let variants = MsgVariants::new(source.as_variants(), MsgType::Query, &[], &None);
-        Self { source, variants }
+    pub fn new(generics: Generics, self_ty: Type, variants: MsgVariants<'a, GenericParam>) -> Self {
+        Self {
+            generics,
+            self_ty,
+            variants,
+        }
     }
 
     pub fn emit(&self) -> TokenStream {
         let sylvia = crate_module();
         let Self {
-            source, variants, ..
+            generics,
+            self_ty,
+            variants,
+            ..
         } = self;
 
-        let where_clause = &source.generics.where_clause;
-        let generics: Vec<_> = source.generics.params.iter().collect();
-        let contract = &source.self_ty;
+        let where_clause = &generics.where_clause;
+        let generics: Vec<_> = generics.params.iter().collect();
+        let contract = &self_ty;
 
         let accessor = MsgType::Query.as_accessor_name();
         let api_path = quote! { < #contract as #sylvia ::types::ContractApi>:: #accessor };
-        let methods_impl = variants
+
+        let querier_methods_impl = variants
             .variants()
             .map(|variant| variant.emit_querier_impl(&api_path));
 
-        let methods_declaration = variants
+        let querier_methods_declaration = variants
             .variants()
-            .map(|variant| variant.emit_querier_declaration());
+            .map(|variant| variant.emit_querier_method_declaration());
 
         let types_declaration = where_clause
             .as_ref()
@@ -130,8 +134,6 @@ impl<'a> ContractQuerier<'a> {
             .map(EmitAssociated::emit_implementation)
             .unwrap_or(vec![]);
 
-        let contract_name = *source.self_ty.clone();
-
         let bracketed_generics = if !generics.is_empty() {
             quote! { < #(#generics,)* > }
         } else {
@@ -141,25 +143,23 @@ impl<'a> ContractQuerier<'a> {
         quote! {
             pub trait Querier #bracketed_generics {
                 #(#types_declaration)*
-
-                #(#methods_declaration)*
+                #(#querier_methods_declaration)*
             }
 
-            impl <'sv_querier_lifetime, #(#generics,)* C: #sylvia ::cw_std::CustomQuery> Querier #bracketed_generics for #sylvia ::types::BoundQuerier<'sv_querier_lifetime, C, #contract_name > #where_clause {
+            impl <'sv_querier_lifetime, #(#generics,)* C: #sylvia ::cw_std::CustomQuery> Querier #bracketed_generics for #sylvia ::types::BoundQuerier<'sv_querier_lifetime, C, #contract > #where_clause {
                 #(#types_implementation)*
-
-                #(#methods_impl)*
+                #(#querier_methods_impl)*
             }
         }
     }
 }
 
-trait EmitMethod {
+trait EmitQuerierMethod {
     fn emit_querier_impl(&self, api_path: &TokenStream) -> TokenStream;
-    fn emit_querier_declaration(&self) -> TokenStream;
+    fn emit_querier_method_declaration(&self) -> TokenStream;
 }
 
-impl EmitMethod for MsgVariant<'_> {
+impl EmitQuerierMethod for MsgVariant<'_> {
     fn emit_querier_impl(&self, api_path: &TokenStream) -> TokenStream {
         let sylvia = crate_module();
         let name = self.name();
@@ -178,7 +178,7 @@ impl EmitMethod for MsgVariant<'_> {
         }
     }
 
-    fn emit_querier_declaration(&self) -> TokenStream {
+    fn emit_querier_method_declaration(&self) -> TokenStream {
         let sylvia = crate_module();
         let name = self.name();
         let return_type = self.return_type();
diff --git a/sylvia/Cargo.toml b/sylvia/Cargo.toml
index 030e090..4e23976 100644
--- a/sylvia/Cargo.toml
+++ b/sylvia/Cargo.toml
@@ -12,7 +12,7 @@ categories = ["api-bindings", "wasm"]
 readme = "../README.md"
 
 [features]
-mt = ["sylvia-derive/mt", "dep:cw-multi-test", "dep:anyhow", "dep:derivative"]
+mt = ["sylvia-derive/mt", "dep:cw-multi-test", "dep:anyhow"]
 stargate = ["cosmwasm-std/stargate"]
 iterator = ["cosmwasm-std/iterator"]
 cosmwasm_1_1 = ["cosmwasm-std/cosmwasm_1_1"]
@@ -37,7 +37,7 @@ serde-json-wasm = "1.0.1"
 konst = "0.3.8"
 cw-multi-test = { workspace = true, optional = true }
 anyhow = { workspace = true, optional = true }
-derivative = { version = "2.2.0", optional = true }
+derivative = { version = "2.2.0" }
 cw-utils = { workspace = true, optional = true }
 
 [dev-dependencies]
diff --git a/sylvia/src/types.rs b/sylvia/src/types.rs
index 969eac2..4b2d686 100644
--- a/sylvia/src/types.rs
+++ b/sylvia/src/types.rs
@@ -1,5 +1,5 @@
 //! Module providing utilities to build and use sylvia contracts.
-use cosmwasm_std::{Deps, DepsMut, Empty, Env, MessageInfo};
+use cosmwasm_std::{Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo, WasmMsg};
 use derivative::Derivative;
 use schemars::JsonSchema;
 use serde::de::DeserializeOwned;
@@ -160,6 +160,160 @@ impl<'a, C: cosmwasm_std::CustomQuery, Contract> From<&'a BoundQuerier<'a, C, Co
     }
 }
 
+pub struct EmptyExecutorBuilderState;
+pub struct ReadyExecutorBuilderState;
+
+/// This structure represents a collection of execution methods for
+/// a Sylvia contract or interface. An instance of this structure
+/// should be created using a [Remote] object, obtained through the
+/// [Remote::executor] method.
+///
+/// [ExecutorBuilder] implements the
+/// Executor traits generated by the `contract` and `interface` macros,
+/// encompassing all `sv::msg(exec)` methods of the specified contracts
+/// and interfaces.
+///
+/// ```rust
+/// pub mod another_contract {
+///     # use cosmwasm_std::{Response, StdResult};
+///     # use sylvia::contract;
+///     # use sylvia::types::{ExecCtx, InstantiateCtx};
+///     pub struct AnotherContract {}
+///     
+///     #[contract]
+///     impl AnotherContract {
+///         pub fn new() -> Self { Self {} }
+///
+///         #[sv::msg(instantiate)]
+///         fn instantiate(&self, ctx: InstantiateCtx) -> StdResult<Response> {
+///             Ok(Response::new())
+///         }
+///
+///         #[sv::msg(exec)]
+///         fn exec_method(&self, ctx: ExecCtx) -> StdResult<Response> {
+///             Ok(Response::new())
+///         }
+///     }
+/// }
+/// # use cosmwasm_std::{coin, Addr, Response, StdResult};
+/// # use cw_storage_plus::Item;
+/// # use sylvia::contract;
+/// # use sylvia::types::{ExecCtx, InstantiateCtx, Remote};
+/// # use another_contract::AnotherContract;
+/// # use another_contract::sv::Executor;
+///
+/// pub struct Contract<'a> {
+///    pub remote_contract: Item<Remote<'a, AnotherContract>>,
+/// }
+///
+/// #[contract]
+/// impl<'a> Contract<'a> {
+///     pub fn new() -> Self {
+///         Self {
+///             remote_contract: Item::new("remote_contract"),
+///         }
+///     }
+///
+///     #[sv::msg(instantiate)]
+///     fn instantiate(&self, ctx: InstantiateCtx, remote_addr: Addr) -> StdResult<Response> {
+///         self.remote_contract.save(
+///             ctx.deps.storage,
+///             &Remote::new(remote_addr.clone()),
+///         )?;
+///         Ok(Response::new())
+///     }
+///
+///     #[sv::msg(exec)]
+///     fn call_another_contract(&self, ctx: ExecCtx) -> StdResult<Response> {
+///         let remote = self.remote_contract.load(ctx.deps.storage)?;
+///         let exec_method = remote.executor().with_funds(vec![coin(2345, "atom")]).exec_method()?.build();
+///         Ok(Response::new().add_message(exec_method))
+///     }
+/// }
+///
+/// # fn main() {}
+/// ```
+///
+/// It is also possible to call execution methods on other contract's interface:
+///
+/// ```rust
+/// pub mod interface {
+///     # use cosmwasm_std::{Response, StdResult, StdError};
+///     # use sylvia::interface;
+///     # use sylvia::types::ExecCtx;
+///     #[interface]
+///     pub trait Interface {
+///         type Error: From<StdError>;
+///
+///         #[sv::msg(exec)]
+///         fn exec_method(&self, ctx: ExecCtx) -> StdResult<Response>;
+///     }
+/// }
+/// # use interface::Interface;
+/// # use interface::sv::Executor;
+/// # use cosmwasm_std::{Addr, Response, StdError, StdResult};
+/// # use sylvia::types::Remote;
+///
+/// fn execute_method(remote_addr: Addr) -> StdResult<Response> {
+///     let remote = Remote::<'_, dyn Interface<Error=StdError>>::new(remote_addr);
+///     let msg = remote.executor().exec_method()?.build();
+///     Ok(Response::new().add_message(msg))
+/// }
+/// ```
+pub struct ExecutorBuilder<State: ?Sized> {
+    contract: String,
+    funds: Vec<Coin>,
+    msg: Binary,
+    _state: std::marker::PhantomData<State>,
+}
+
+impl<Contract: ?Sized> ExecutorBuilder<(EmptyExecutorBuilderState, Contract)> {
+    /// Creates an instance of the structure based
+    /// on the destination contract's address.
+    pub fn new(contract: &cosmwasm_std::Addr) -> Self {
+        Self {
+            contract: contract.to_string(),
+            funds: vec![],
+            msg: Binary::default(),
+            _state: std::marker::PhantomData,
+        }
+    }
+
+    /// Adds the funds to the execution message.
+    pub fn with_funds(self, funds: Vec<Coin>) -> Self {
+        Self { funds, ..self }
+    }
+
+    /// Returns funds set in this builder.
+    pub fn funds(&self) -> &Vec<Coin> {
+        &self.funds
+    }
+
+    /// Returns contract's address represented as [str].
+    pub fn contract(&self) -> &str {
+        &self.contract
+    }
+}
+
+impl ExecutorBuilder<ReadyExecutorBuilderState> {
+    pub fn new(contract: String, funds: Vec<Coin>, msg: Binary) -> Self {
+        Self {
+            contract,
+            funds,
+            msg,
+            _state: std::marker::PhantomData,
+        }
+    }
+
+    pub fn build(self) -> WasmMsg {
+        WasmMsg::Execute {
+            contract_addr: self.contract,
+            msg: self.msg,
+            funds: self.funds,
+        }
+    }
+}
+
 /// Represents a contract on the chain and acts as a gateway to communicate with it.
 ///
 /// # Example
@@ -272,6 +426,10 @@ impl<'a, Contract: ?Sized> Remote<'a, Contract> {
             _phantom: std::marker::PhantomData,
         }
     }
+
+    pub fn executor(&self) -> ExecutorBuilder<(EmptyExecutorBuilderState, Contract)> {
+        ExecutorBuilder::<(EmptyExecutorBuilderState, Contract)>::new(&self.addr)
+    }
 }
 
 impl<'a, Contract: ?Sized> AsRef<cosmwasm_std::Addr> for Remote<'a, Contract> {


Test patch:
diff --git a/sylvia/tests/executor.rs b/sylvia/tests/executor.rs
new file mode 100644
index 0000000..fa7687a
--- /dev/null
+++ b/sylvia/tests/executor.rs
@@ -0,0 +1,162 @@
+#![cfg(feature = "mt")]
+
+use cosmwasm_std::{Addr, Response, StdResult};
+use cw_storage_plus::Item;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use sylvia::contract;
+use sylvia::types::InstantiateCtx;
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]
+pub struct CountResponse {
+    pub count: u64,
+}
+
+pub mod counter {
+    use cosmwasm_std::{Response, StdError, StdResult};
+    use sylvia::interface;
+    use sylvia::types::{ExecCtx, QueryCtx};
+
+    use crate::CountResponse;
+
+    #[interface]
+    #[sv::custom(msg=cosmwasm_std::Empty, query=cosmwasm_std::Empty)]
+    pub trait Counter {
+        type Error: From<StdError>;
+
+        #[sv::msg(query)]
+        fn count(&self, ctx: QueryCtx) -> StdResult<CountResponse>;
+
+        #[sv::msg(exec)]
+        fn decrease(&self, ctx: ExecCtx) -> StdResult<Response>;
+    }
+}
+
+use cosmwasm_std::StdError;
+use counter::sv::Executor as InterfaceExecutor;
+use counter::Counter;
+use sv::Executor as ContractExecutor;
+use sylvia::types::{ExecCtx, QueryCtx};
+
+impl Counter for CounterContract<'_> {
+    type Error = StdError;
+
+    fn count(&self, ctx: QueryCtx) -> StdResult<CountResponse> {
+        let count = self.count.load(ctx.deps.storage)?;
+        Ok(CountResponse { count })
+    }
+
+    fn decrease(&self, ctx: ExecCtx) -> StdResult<Response> {
+        self.count.update(ctx.deps.storage, |count| {
+            let count = count.saturating_sub(1);
+            Ok::<_, StdError>(count)
+        })?;
+
+        Ok(Response::new())
+    }
+}
+
+pub struct CounterContract<'a> {
+    pub count: Item<u64>,
+    pub remote_contract: Item<sylvia::types::Remote<'a, CounterContract<'a>>>,
+    pub remote_interface: Item<sylvia::types::Remote<'a, dyn Counter<Error = StdError>>>,
+}
+
+#[contract]
+#[sv::messages(counter)]
+impl<'a> CounterContract<'a> {
+    #[allow(clippy::new_without_default)]
+    pub fn new() -> Self {
+        Self {
+            count: Item::new("count"),
+            remote_contract: Item::new("remote_contract"),
+            remote_interface: Item::new("remote_interface"),
+        }
+    }
+
+    #[sv::msg(instantiate)]
+    fn instantiate(&self, ctx: InstantiateCtx, remote_addr: Addr) -> StdResult<Response> {
+        self.count.save(ctx.deps.storage, &100)?;
+        self.remote_contract.save(
+            ctx.deps.storage,
+            &sylvia::types::Remote::new(remote_addr.clone()),
+        )?;
+        self.remote_interface
+            .save(ctx.deps.storage, &sylvia::types::Remote::new(remote_addr))?;
+        Ok(Response::new())
+    }
+
+    #[sv::msg(exec)]
+    fn increase(&self, ctx: ExecCtx) -> StdResult<Response> {
+        self.count.update(ctx.deps.storage, |count| {
+            let count = count.saturating_add(1);
+            Ok::<_, StdError>(count)
+        })?;
+
+        Ok(Response::new())
+    }
+
+    #[sv::msg(exec)]
+    fn increase_in_other_contract(&self, ctx: ExecCtx) -> StdResult<Response> {
+        let remote = self.remote_contract.load(ctx.deps.storage)?;
+        let increase_msg = remote.executor().increase()?.build();
+        Ok(Response::new().add_message(increase_msg))
+    }
+
+    #[sv::msg(exec)]
+    fn decrease_in_other_contract(&self, ctx: ExecCtx) -> StdResult<Response> {
+        let remote = self.remote_interface.load(ctx.deps.storage)?;
+        let decrease_msg = remote.executor().decrease()?.build();
+        Ok(Response::new().add_message(decrease_msg))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use cosmwasm_std::Addr;
+    use cw_multi_test::IntoBech32;
+    use sylvia::multitest::App;
+
+    use crate::counter::sv::mt::CounterProxy;
+    use crate::sv::mt::{CodeId, CounterContractProxy};
+
+    #[test]
+    fn call_querier() {
+        let app = App::default();
+        let code_id = CodeId::store_code(&app);
+
+        let owner = "owner".into_bech32();
+
+        let first_contract = code_id
+            .instantiate(Addr::unchecked("remote"))
+            .with_label("First Counter")
+            .call(&owner)
+            .unwrap();
+
+        let second_contract = code_id
+            .instantiate(first_contract.contract_addr.clone())
+            .with_label("Second Counter")
+            .call(&owner)
+            .unwrap();
+
+        second_contract
+            .increase_in_other_contract()
+            .call(&owner)
+            .unwrap();
+
+        let resp = first_contract.count().unwrap();
+        assert_eq!(resp.count, 101);
+
+        second_contract
+            .decrease_in_other_contract()
+            .call(&owner)
+            .unwrap();
+        second_contract
+            .decrease_in_other_contract()
+            .call(&owner)
+            .unwrap();
+
+        let resp = first_contract.count().unwrap();
+        assert_eq!(resp.count, 99);
+    }
+}
diff --git a/sylvia/tests/remote.rs b/sylvia/tests/remote.rs
index a4c194d..96b94d4 100644
--- a/sylvia/tests/remote.rs
+++ b/sylvia/tests/remote.rs
@@ -188,15 +188,15 @@ where
 }
 
 pub mod manager {
-    use cosmwasm_std::{to_json_binary, Addr, Response, StdError, StdResult, WasmMsg};
+    use cosmwasm_std::{Addr, Response, StdError, StdResult};
     use cw_storage_plus::Item;
     use schemars::JsonSchema;
     use serde::de::DeserializeOwned;
     use serde::Serialize;
     use sylvia::contract;
-    use sylvia::types::{ExecCtx, InstantiateCtx, InterfaceApi, QueryCtx};
+    use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};
 
-    use crate::counter::sv::{Api as CounterApi, Querier};
+    use crate::counter::sv::{Executor, Querier};
     use crate::{ExampleMsg, ExampleQuery, InterfaceStorage};
 
     pub struct ManagerContract<'a, CounterT> {
@@ -236,27 +236,14 @@ pub mod manager {
             ctx: ExecCtx<ExampleQuery>,
             value: CounterT,
         ) -> Result<Response<ExampleMsg>, StdError> {
-            // This should be simplified to something like
-            // ```rust
-            // let msg = self
-            //      .remote_counter
-            //      .load(ctx.deps.storage)?
-            //      .add(value)
-            //      .with_funds(&[])
-            //      .build();
-            // ```
-            // after https://github.com/CosmWasm/sylvia/issues/130
-            let msg = <CounterApi<_> as InterfaceApi>::Exec::add(value);
-            let wasm = WasmMsg::Execute {
-                contract_addr: self
-                    .remote_counter
-                    .load(ctx.deps.storage)?
-                    .interface_remote
-                    .as_ref()
-                    .to_string(),
-                msg: to_json_binary(&msg)?,
-                funds: vec![],
-            };
+            let wasm = self
+                .remote_counter
+                .load(ctx.deps.storage)?
+                .interface_remote
+                .executor()
+                .with_funds(vec![])
+                .add(value)?
+                .build();
             let resp = Response::new().add_message(wasm);
             Ok(resp)
         }