Repository: CosmWasm/sylvia
Pull request #88 (2023-03-22 10:49:05)

Better proxy generation


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57f1aba..9948593 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to
 [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+- Changed the way multitest helpers are generated to avoid weird `use` statements in code.
+
 ## \[0.3.1\] - 2023-03-03
 
 - Slight improvement the invalid message received error
diff --git a/contracts/cw1-subkeys/Cargo.toml b/contracts/cw1-subkeys/Cargo.toml
index fc066cc..0c22258 100644
--- a/contracts/cw1-subkeys/Cargo.toml
+++ b/contracts/cw1-subkeys/Cargo.toml
@@ -26,3 +26,5 @@ thiserror = "1.0.38"
 anyhow = "1.0.68"
 cw-multi-test = "0.16"
 sylvia = { path = "../../sylvia", features = ["mt"] }
+cw1-whitelist = { path = "../cw1-whitelist", features = ["library", "mt"] }
+cw1 = { path = "../../interfaces/cw1", features = ["mt"] }
diff --git a/contracts/cw1-subkeys/src/contract.rs b/contracts/cw1-subkeys/src/contract.rs
index 82f0d84..a29ef3d 100644
--- a/contracts/cw1-subkeys/src/contract.rs
+++ b/contracts/cw1-subkeys/src/contract.rs
@@ -9,15 +9,11 @@ use cw_storage_plus::{Bound, Map};
 use cw_utils::Expiration;
 use sylvia::{contract, schemars};
 
-#[cfg(test)]
-use crate::cw1::multitest_utils::Cw1Proxy;
 use crate::error::ContractError;
 use crate::responses::{
     AllAllowancesResponse, AllPermissionsResponse, AllowanceInfo, PermissionsInfo,
 };
 use crate::state::{Allowance, Permissions};
-#[cfg(test)]
-use crate::whitelist::multitest_utils::WhitelistProxy;
 
 pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
 pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
diff --git a/contracts/cw1-subkeys/src/cw1.rs b/contracts/cw1-subkeys/src/cw1.rs
index c9202ae..bfa1536 100644
--- a/contracts/cw1-subkeys/src/cw1.rs
+++ b/contracts/cw1-subkeys/src/cw1.rs
@@ -1,13 +1,12 @@
 use cosmwasm_std::{ensure, Addr, DepsMut, Env, MessageInfo, Response, StdResult};
 use cw1::{CanExecuteResp, Cw1};
-#[cfg(test)]
-use cw1::{ExecMsg, QueryMsg};
 use sylvia::contract;
 
 use crate::contract::Cw1SubkeysContract;
 use crate::error::ContractError;
 
 #[contract]
+#[messages(cw1 as Cw1)]
 impl Cw1 for Cw1SubkeysContract<'_> {
     type Error = ContractError;
 
diff --git a/contracts/cw1-subkeys/src/whitelist.rs b/contracts/cw1-subkeys/src/whitelist.rs
index f0891d5..799b3b1 100644
--- a/contracts/cw1-subkeys/src/whitelist.rs
+++ b/contracts/cw1-subkeys/src/whitelist.rs
@@ -1,8 +1,8 @@
 use cosmwasm_std::{Deps, DepsMut, Env, MessageInfo, Response, StdResult};
 use cw1_whitelist::responses::AdminListResponse;
-use cw1_whitelist::whitelist::Whitelist;
 #[cfg(test)]
-use cw1_whitelist::whitelist::{ExecMsg, QueryMsg};
+use cw1_whitelist::whitelist;
+use cw1_whitelist::whitelist::Whitelist;
 use sylvia::contract;
 
 use crate::contract::Cw1SubkeysContract;
@@ -11,6 +11,7 @@ use crate::error::ContractError;
 // This can be skipped by tarpaulin as it's covered in cw1-whitelist
 #[cfg(not(tarpaulin_include))]
 #[contract]
+#[messages(whitelist as Whitelist)]
 impl Whitelist for Cw1SubkeysContract<'_> {
     type Error = ContractError;
 
diff --git a/contracts/cw1-whitelist/Cargo.toml b/contracts/cw1-whitelist/Cargo.toml
index ab14b2b..7e4e406 100644
--- a/contracts/cw1-whitelist/Cargo.toml
+++ b/contracts/cw1-whitelist/Cargo.toml
@@ -14,6 +14,7 @@ crate-type = ["cdylib", "rlib"]
 [features]
 library = []
 tests = ["library", "cw-multi-test", "anyhow"]
+mt = ["sylvia/mt"]
 
 [dependencies]
 cosmwasm-std = { version = "1.2", features = ["staking"] }
diff --git a/contracts/cw1-whitelist/src/contract.rs b/contracts/cw1-whitelist/src/contract.rs
index f42828e..3e7aa0c 100644
--- a/contracts/cw1-whitelist/src/contract.rs
+++ b/contracts/cw1-whitelist/src/contract.rs
@@ -1,9 +1,5 @@
-#[cfg(test)]
-use crate::cw1::multitest_utils::Cw1Proxy;
 use crate::error::ContractError;
 use crate::whitelist;
-#[cfg(test)]
-use crate::whitelist::multitest_utils::WhitelistProxy;
 use cosmwasm_std::{Addr, Deps, DepsMut, Empty, Env, MessageInfo, Response};
 
 use cw2::set_contract_version;
diff --git a/contracts/cw1-whitelist/src/cw1.rs b/contracts/cw1-whitelist/src/cw1.rs
index bde9a02..f44a7d8 100644
--- a/contracts/cw1-whitelist/src/cw1.rs
+++ b/contracts/cw1-whitelist/src/cw1.rs
@@ -1,13 +1,12 @@
 use cosmwasm_std::{Addr, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
 use cw1::{CanExecuteResp, Cw1};
-#[cfg(test)]
-use cw1::{ExecMsg, QueryMsg};
 use sylvia::contract;
 
 use crate::contract::Cw1WhitelistContract;
 use crate::error::ContractError;
 
 #[contract]
+#[messages(cw1 as Cw1)]
 impl Cw1 for Cw1WhitelistContract<'_> {
     type Error = ContractError;
 
diff --git a/contracts/cw20-base/Cargo.toml b/contracts/cw20-base/Cargo.toml
index aed14cb..657f8cd 100644
--- a/contracts/cw20-base/Cargo.toml
+++ b/contracts/cw20-base/Cargo.toml
@@ -38,3 +38,8 @@ assert_matches = "1.5.0"
 cw-multi-test = "0.16"
 cw-utils = "1.0"
 sylvia = { path = "../../sylvia", features = ["mt"] }
+cw20-allowances = { path = "../../interfaces/cw20-allowances", features = [
+    "mt",
+] }
+cw20-marketing = { path = "../../interfaces/cw20-marketing", features = ["mt"] }
+cw20-minting = { path = "../../interfaces/cw20-minting", features = ["mt"] }
diff --git a/contracts/cw20-base/src/allowances.rs b/contracts/cw20-base/src/allowances.rs
index 41637a6..7e41621 100644
--- a/contracts/cw20-base/src/allowances.rs
+++ b/contracts/cw20-base/src/allowances.rs
@@ -19,11 +19,13 @@ const MAX_LIMIT: u32 = 30;
 const DEFAULT_LIMIT: u32 = 10;
 
 #[contract]
+#[messages(cw20_allowances as Cw20Allowances)]
 impl Cw20Allowances for Cw20Base<'_> {
     type Error = ContractError;
 
     /// Allows spender to access an additional amount tokens from the owner's (env.sender) account.
     /// If expires is Some(), overwrites current allowance expiration with this one.
+    #[msg(exec)]
     fn increase_allowance(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -64,6 +66,7 @@ impl Cw20Allowances for Cw20Base<'_> {
 
     /// Lowers the spender's access of tokens from the owner's (env.sender) account by amount.
     /// If expires is Some(), overwrites current allowance expiration with this one.
+    #[msg(exec)]
     fn decrease_allowance(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -114,6 +117,7 @@ impl Cw20Allowances for Cw20Base<'_> {
 
     /// Transfers amount tokens from owner -> recipient
     /// if `env.sender` has sufficient pre-approval.
+    #[msg(exec)]
     fn transfer_from(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -166,6 +170,7 @@ impl Cw20Allowances for Cw20Base<'_> {
 
     /// Sends amount tokens from owner -> contract
     /// if `env.sender` has sufficient pre-approval.
+    #[msg(exec)]
     fn send_from(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -216,6 +221,7 @@ impl Cw20Allowances for Cw20Base<'_> {
     }
 
     /// Destroys amount of tokens forever
+    #[msg(exec)]
     fn burn_from(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -253,6 +259,7 @@ impl Cw20Allowances for Cw20Base<'_> {
     }
 
     /// Returns how much spender can use from owner account, 0 if unset.
+    #[msg(query)]
     fn allowance(
         &self,
         ctx: (Deps, Env),
@@ -301,6 +308,7 @@ impl Cw20Allowances for Cw20Base<'_> {
     }
 
     /// Returns all allowances this spender has been granted. Supports pagination.
+    #[msg(query)]
     fn all_spender_allowances(
         &self,
         ctx: (Deps, Env),
@@ -332,6 +340,7 @@ impl Cw20Allowances for Cw20Base<'_> {
     }
 
     /// Returns all allowances this spender has been granted. Supports pagination.
+    #[msg(query)]
     fn all_accounts(
         &self,
         ctx: (Deps, Env),
diff --git a/contracts/cw20-base/src/contract.rs b/contracts/cw20-base/src/contract.rs
index 46eb710..2aced33 100644
--- a/contracts/cw20-base/src/contract.rs
+++ b/contracts/cw20-base/src/contract.rs
@@ -1,10 +1,4 @@
-#[cfg(test)]
-use crate::allowances::multitest_utils::Cw20AllowancesProxy;
 use crate::error::ContractError;
-#[cfg(test)]
-use crate::marketing::multitest_utils::Cw20MarketingProxy;
-#[cfg(test)]
-use crate::minting::multitest_utils::Cw20MintingProxy;
 use crate::responses::{BalanceResponse, Cw20Coin, Cw20ReceiveMsg, TokenInfoResponse};
 use crate::validation::{validate_accounts, validate_msg, verify_logo};
 use cosmwasm_schema::cw_serde;
diff --git a/contracts/cw20-base/src/marketing.rs b/contracts/cw20-base/src/marketing.rs
index db19b28..d5e848a 100644
--- a/contracts/cw20-base/src/marketing.rs
+++ b/contracts/cw20-base/src/marketing.rs
@@ -7,9 +7,11 @@ use cw20_marketing::{Cw20Marketing, EmbeddedLogo, Logo};
 use sylvia::contract;
 
 #[contract]
+#[messages(cw20_marketing as Cw20Marketing)]
 impl Cw20Marketing for Cw20Base<'_> {
     type Error = ContractError;
 
+    #[msg(exec)]
     fn update_marketing(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -65,6 +67,7 @@ impl Cw20Marketing for Cw20Base<'_> {
         Ok(res)
     }
 
+    #[msg(exec)]
     fn upload_logo(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -102,6 +105,7 @@ impl Cw20Marketing for Cw20Base<'_> {
         Ok(res)
     }
 
+    #[msg(query)]
     fn marketing_info(&self, ctx: (Deps, Env)) -> StdResult<MarketingInfoResponse> {
         let (deps, _) = ctx;
 
@@ -111,6 +115,7 @@ impl Cw20Marketing for Cw20Base<'_> {
             .unwrap_or_default())
     }
 
+    #[msg(query)]
     fn download_logo(&self, ctx: (Deps, Env)) -> StdResult<DownloadLogoResponse> {
         let (deps, _) = ctx;
 
diff --git a/contracts/cw20-base/src/minting.rs b/contracts/cw20-base/src/minting.rs
index a232330..9e4f65d 100644
--- a/contracts/cw20-base/src/minting.rs
+++ b/contracts/cw20-base/src/minting.rs
@@ -6,9 +6,11 @@ use cw20_minting::Cw20Minting;
 use sylvia::contract;
 
 #[contract]
+#[messages(cw20_minting as Cw20Minting)]
 impl Cw20Minting for Cw20Base<'_> {
     type Error = ContractError;
 
+    #[msg(exec)]
     fn mint(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -60,6 +62,7 @@ impl Cw20Minting for Cw20Base<'_> {
         Ok(res)
     }
 
+    #[msg(exec)]
     fn update_minter(
         &self,
         ctx: (DepsMut, Env, MessageInfo),
@@ -101,6 +104,7 @@ impl Cw20Minting for Cw20Base<'_> {
         Ok(resp)
     }
 
+    #[msg(query)]
     fn minter(&self, ctx: (Deps, Env)) -> StdResult<Option<MinterResponse>> {
         let (deps, _) = ctx;
 
diff --git a/interfaces/cw1/Cargo.toml b/interfaces/cw1/Cargo.toml
index 8a3dea0..e14b3ae 100644
--- a/interfaces/cw1/Cargo.toml
+++ b/interfaces/cw1/Cargo.toml
@@ -8,6 +8,9 @@ license = "Apache-2.0"
 repository = "https://github.com/CosmWasm/sylvia"
 homepage = "https://cosmwasm.com"
 
+[features]
+mt = ["sylvia/mt"]
+
 [dependencies]
 cosmwasm-std = { version = "1.2", features = ["staking"] }
 cosmwasm-schema = "1.2"
diff --git a/interfaces/cw20-allowances/Cargo.toml b/interfaces/cw20-allowances/Cargo.toml
index 137f729..3ba6315 100644
--- a/interfaces/cw20-allowances/Cargo.toml
+++ b/interfaces/cw20-allowances/Cargo.toml
@@ -8,6 +8,9 @@ license = "Apache-2.0"
 repository = "https://github.com/CosmWasm/sylvia"
 homepage = "https://cosmwasm.com"
 
+[features]
+mt = ["sylvia/mt"]
+
 [dependencies]
 cosmwasm-std = { version = "1.2", features = ["staking"] }
 cosmwasm-schema = "1.2"
diff --git a/interfaces/cw20-marketing/Cargo.toml b/interfaces/cw20-marketing/Cargo.toml
index dcffc48..c270433 100644
--- a/interfaces/cw20-marketing/Cargo.toml
+++ b/interfaces/cw20-marketing/Cargo.toml
@@ -8,6 +8,9 @@ license = "Apache-2.0"
 repository = "https://github.com/CosmWasm/sylvia"
 homepage = "https://cosmwasm.com"
 
+[features]
+mt = ["sylvia/mt"]
+
 [dependencies]
 cosmwasm-std = { version = "1.2", features = ["staking"] }
 cosmwasm-schema = "1.2"
diff --git a/interfaces/cw20-minting/Cargo.toml b/interfaces/cw20-minting/Cargo.toml
index 5617495..67cf155 100644
--- a/interfaces/cw20-minting/Cargo.toml
+++ b/interfaces/cw20-minting/Cargo.toml
@@ -8,6 +8,9 @@ license = "Apache-2.0"
 repository = "https://github.com/CosmWasm/sylvia"
 homepage = "https://cosmwasm.com"
 
+[features]
+mt = ["sylvia/mt"]
+
 [dependencies]
 cosmwasm-std = { version = "1.2", features = ["staking"] }
 cosmwasm-schema = "1.2"
diff --git a/interfaces/cw4/Cargo.toml b/interfaces/cw4/Cargo.toml
index de446f5..b363bce 100644
--- a/interfaces/cw4/Cargo.toml
+++ b/interfaces/cw4/Cargo.toml
@@ -8,6 +8,9 @@ license = "Apache-2.0"
 repository = "https://github.com/CosmWasm/sylvia"
 homepage = "https://cosmwasm.com"
 
+[features]
+mt = ["sylvia/mt"]
+
 [dependencies]
 cosmwasm-std = { version = "1.2.1", features = ["staking"] }
 cosmwasm-schema = "1.2"
diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs
index 8fdbe95..180ce4f 100644
--- a/sylvia-derive/src/input.rs
+++ b/sylvia-derive/src/input.rs
@@ -4,7 +4,7 @@ use quote::quote;
 use syn::{GenericParam, Ident, ItemImpl, ItemTrait, TraitItem};
 
 use crate::message::{ContractEnumMessage, EnumMessage, GlueMessage, StructMessage};
-use crate::multitest::MultitestHelpers;
+use crate::multitest::{MultitestHelpers, TraitMultitestHelpers};
 use crate::parser::{ContractArgs, InterfaceArgs, MsgType};
 
 /// Preprocessed `interface` macro input
@@ -48,6 +48,7 @@ impl<'a> TraitInput<'a> {
 
     pub fn process(&self) -> TokenStream {
         let messages = self.emit_messages();
+        let multitest_helpers = self.emit_helpers();
 
         if let Some(module) = &self.attributes.module {
             quote! {
@@ -55,10 +56,24 @@ impl<'a> TraitInput<'a> {
                     use super::*;
 
                     #messages
+
+                    #multitest_helpers
                 }
             }
         } else {
-            messages
+            quote! {
+                #messages
+                #multitest_helpers
+            }
+        }
+    }
+
+    fn emit_helpers(&self) -> TokenStream {
+        if cfg!(feature = "mt") {
+            let multitest_helpers = TraitMultitestHelpers::new(self.item);
+            multitest_helpers.emit()
+        } else {
+            quote! {}
         }
     }
 
diff --git a/sylvia/Cargo.toml b/sylvia/Cargo.toml
index 841734d..7eedc4f 100644
--- a/sylvia/Cargo.toml
+++ b/sylvia/Cargo.toml
@@ -11,7 +11,7 @@ keywords = ["cosmwasm", "blockchain", "confio"]
 categories = ["api-bindings", "wasm"]
 
 [features]
-mt = ["sylvia-derive/mt", "dep:cw-multi-test"]
+mt = ["sylvia-derive/mt", "dep:cw-multi-test", "dep:anyhow"]
 
 [dependencies]
 sylvia-derive = { version = "0.3.1", path = "../sylvia-derive" }
@@ -23,6 +23,7 @@ serde-cw-value = "0.7.0"
 serde-json-wasm = "0.5.0"
 konst = "0.3"
 cw-multi-test = { version = "0.16.2", optional = true }
+anyhow = { version = "1.0", optional = true }
 
 [dev-dependencies]
 anyhow = "1.0"
diff --git a/sylvia/examples/basic.rs b/sylvia/examples/basic.rs
index 0ff738f..8c501d6 100644
--- a/sylvia/examples/basic.rs
+++ b/sylvia/examples/basic.rs
@@ -64,9 +64,11 @@ impl Default for GroupContract {
     }
 }
 
+#[contract]
 impl Group for GroupContract {
     type Error = Error;
 
+    #[msg(exec)]
     fn update_admin(
         &self,
         _ctx: (DepsMut, Env, MessageInfo),
@@ -75,6 +77,7 @@ impl Group for GroupContract {
         todo!()
     }
 
+    #[msg(exec)]
     fn update_members(
         &self,
         _ctx: (DepsMut, Env, MessageInfo),
@@ -84,6 +87,7 @@ impl Group for GroupContract {
         todo!()
     }
 
+    #[msg(query)]
     fn member(&self, _ctx: (Deps, Env), _addr: String) -> Result<MemberResp, Self::Error> {
         todo!()
     }
diff --git a/sylvia/src/lib.rs b/sylvia/src/lib.rs
index dfc69a8..a3335d9 100644
--- a/sylvia/src/lib.rs
+++ b/sylvia/src/lib.rs
@@ -6,7 +6,11 @@
 pub mod multitest;
 pub mod utils;
 
+#[cfg(feature = "mt")]
+pub use anyhow;
 pub use cosmwasm_std as cw_std;
+#[cfg(feature = "mt")]
+pub use cw_multi_test;
 pub use schemars;
 pub use serde;
 pub use serde_cw_value as serde_value;


Test patch:
diff --git a/contracts/cw1-whitelist/src/multitest.rs b/contracts/cw1-whitelist/src/multitest.rs
index 01d8d55..8e82891 100644
--- a/contracts/cw1-whitelist/src/multitest.rs
+++ b/contracts/cw1-whitelist/src/multitest.rs
@@ -3,9 +3,11 @@ mod test {
     use cosmwasm_std::{to_binary, WasmMsg};
 
     use crate::contract::multitest_utils::Cw1WhitelistContractCodeId;
+    use crate::cw1::test_utils::Cw1Methods;
     use crate::error::ContractError;
     use crate::responses::AdminListResponse;
     use crate::whitelist;
+    use crate::whitelist::test_utils::WhitelistMethods;
     use assert_matches::assert_matches;
     use sylvia::multitest::App;
 
diff --git a/contracts/cw20-base/src/multitest/receiver_contract.rs b/contracts/cw20-base/src/multitest/receiver_contract.rs
index 3b7ff2c..5f43515 100644
--- a/contracts/cw20-base/src/multitest/receiver_contract.rs
+++ b/contracts/cw20-base/src/multitest/receiver_contract.rs
@@ -3,8 +3,6 @@ use cw_multi_test::{App, Executor};
 use sylvia::{contract, schemars};
 
 use super::receiver;
-#[cfg(test)]
-use super::receiver::multitest_utils::ReceiverProxy;
 pub struct ReceiverContract {}
 
 #[contract]
diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs
index 172d7cb..b78000a 100644
--- a/sylvia-derive/src/multitest.rs
+++ b/sylvia-derive/src/multitest.rs
@@ -4,7 +4,7 @@ use proc_macro_error::emit_error;
 use quote::quote;
 use syn::parse::{Parse, Parser};
 use syn::spanned::Spanned;
-use syn::{FnArg, GenericParam, ImplItem, ItemImpl, Pat, PatType, Path, Type};
+use syn::{FnArg, GenericParam, ImplItem, ItemImpl, ItemTrait, Pat, PatType, Path, Type};
 
 use crate::check_generics::CheckGenerics;
 use crate::crate_module;
@@ -59,6 +59,7 @@ impl<'a> MultitestHelpers<'a> {
         generics: &'a [&'a GenericParam],
     ) -> Self {
         let mut is_migrate = false;
+        let sylvia = crate_module();
 
         let messages: Vec<_> = source
             .items
@@ -91,7 +92,7 @@ impl<'a> MultitestHelpers<'a> {
                             }
                         }
                     } else {
-                        quote! { cw_multi_test::AppResponse }
+                        quote! { #sylvia ::cw_multi_test::AppResponse }
                     };
 
                     let name = &sig.ident;
@@ -196,10 +197,15 @@ impl<'a> MultitestHelpers<'a> {
             error_type,
             proxy_name,
             source,
+            is_trait,
             ..
         } = self;
         let sylvia = crate_module();
 
+        if *is_trait {
+            return self.impl_trait_on_proxy();
+        }
+
         let messages = messages.iter().map(|msg| {
             let MessageSignature {
                 name,
@@ -244,16 +250,16 @@ impl<'a> MultitestHelpers<'a> {
                     Ok(interface) => {
                         let ContractMessageAttr { module, .. } = interface;
                         assert!(!module.segments.is_empty());
-                        let module = &module.segments[0].ident;
-                        let method_name = Ident::new(&format!("{}_proxy", module), module.span());
+                        let module_name = &module.segments[0].ident;
+                        let method_name = Ident::new(&format!("{}_proxy", module_name), module_name.span());
                         let proxy_name = Ident::new(
-                            &format!("{}Proxy", module.to_string().to_case(Case::UpperCamel)),
-                            module.span(),
+                            &format!("{}Proxy", module_name.to_string().to_case(Case::UpperCamel)),
+                            module_name.span(),
                         );
 
                         quote! {
-                            pub fn #method_name (&self) -> #proxy_name <'app> {
-                                #proxy_name ::new(self.contract_addr.clone(), self.app)
+                            pub fn #method_name (&self) -> #module ::trait_utils:: #proxy_name <'app> {
+                                #module ::trait_utils:: #proxy_name ::new(self.contract_addr.clone(), self.app)
                             }
                         }
                     }
@@ -268,18 +274,17 @@ impl<'a> MultitestHelpers<'a> {
             .collect();
 
         quote! {
-            #[cfg(test)]
             pub mod multitest_utils {
                 use super::*;
-                use cw_multi_test::Executor;
+                use #sylvia ::cw_multi_test::Executor;
 
                 pub struct #proxy_name <'app> {
-                    pub contract_addr: cosmwasm_std::Addr,
+                    pub contract_addr: #sylvia ::cw_std::Addr,
                     pub app: &'app #sylvia ::multitest::App,
                 }
 
                 impl<'app> #proxy_name <'app> {
-                    pub fn new(contract_addr: cosmwasm_std::Addr, app: &'app #sylvia ::multitest::App) -> Self {
+                    pub fn new(contract_addr: #sylvia ::cw_std::Addr, app: &'app #sylvia ::multitest::App) -> Self {
                         #proxy_name{ contract_addr, app }
                     }
 
@@ -288,8 +293,8 @@ impl<'a> MultitestHelpers<'a> {
                     #(#interfaces)*
                 }
 
-                impl<'app> From<(cosmwasm_std::Addr, &'app #sylvia ::multitest::App)> for #proxy_name<'app> {
-                    fn from(input: (cosmwasm_std::Addr, &'app #sylvia ::multitest::App)) -> #proxy_name<'app> {
+                impl<'app> From<(#sylvia ::cw_std::Addr, &'app #sylvia ::multitest::App)> for #proxy_name<'app> {
+                    fn from(input: (#sylvia ::cw_std::Addr, &'app #sylvia ::multitest::App)) -> #proxy_name<'app> {
                         #proxy_name::new(input.0, input.1)
                     }
                 }
@@ -299,6 +304,123 @@ impl<'a> MultitestHelpers<'a> {
         }
     }
 
+    fn impl_trait_on_proxy(&self) -> TokenStream {
+        let Self {
+            messages,
+            error_type,
+            source,
+            ..
+        } = self;
+
+        let sylvia = crate_module();
+
+        let interface_name = interface_name(self.source);
+        let proxy_name = &self.proxy_name;
+        let trait_name = Ident::new(&format!("{}Methods", interface_name), interface_name.span());
+
+        let modules: Vec<_> = source
+            .attrs
+            .iter()
+            .filter(|attr| attr.path.is_ident("messages"))
+            .filter_map(
+                |attr| match ContractMessageAttr::parse.parse2(attr.tokens.clone()) {
+                    Ok(interface) => {
+                        let ContractMessageAttr { module, .. } = &interface;
+                        assert!(!module.segments.is_empty());
+                        Some(module.segments[0].ident.clone())
+                    }
+                    Err(err) => {
+                        emit_error!(attr.span(), err);
+                        None
+                    }
+                },
+            )
+            .collect();
+
+        let module = match modules.len() {
+            0 => {
+                quote! {}
+            }
+            1 => {
+                let module = &modules[0];
+                quote! {#module ::}
+            }
+            _ => {
+                emit_error!(
+                    source.span(),
+                    "Only one #[messages] attribute is allowed per contract"
+                );
+                return quote! {};
+            }
+        };
+        let methods_definitions = messages.iter().map(|msg| {
+            let MessageSignature {
+                name,
+                params,
+                arguments,
+                msg_ty,
+                return_type,
+            } = msg;
+            if msg_ty == &MsgType::Exec {
+                quote! {
+                    #[track_caller]
+                    fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, #module ExecMsg> {
+                        let msg = #module ExecMsg:: #name ( #(#arguments),* );
+
+                        #sylvia ::multitest::ExecProxy::new(&self.contract_addr, msg, &self.app)
+                    }
+                }
+            } else {
+                quote! {
+                    fn #name (&self, #(#params,)* ) -> Result<#return_type, #error_type> {
+                        let msg = #module QueryMsg:: #name ( #(#arguments),* );
+
+                        self.app
+                            .app
+                            .borrow()
+                            .wrap()
+                            .query_wasm_smart(self.contract_addr.clone(), &msg)
+                            .map_err(Into::into)
+                    }
+                }
+            }
+        });
+
+        let methods_declarations = messages.iter().map(|msg| {
+            let MessageSignature {
+                name,
+                params,
+                msg_ty,
+                return_type,
+                ..
+            } = msg;
+            if msg_ty == &MsgType::Exec {
+                quote! {
+                    fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, #module ExecMsg>;
+                }
+            } else {
+                quote! {
+                    fn #name (&self, #(#params,)* ) -> Result<#return_type, #error_type>;
+                }
+            }
+        });
+
+        quote! {
+            #[cfg(test)]
+            pub mod test_utils {
+                use super::*;
+
+                pub trait #trait_name {
+                    #(#methods_declarations)*
+                }
+
+                impl #trait_name for #module trait_utils:: #proxy_name<'_> {
+                    #(#methods_definitions)*
+                }
+            }
+        }
+    }
+
     fn generate_contract_helpers(&self) -> TokenStream {
         let Self {
             error_type,
@@ -368,14 +490,14 @@ impl<'a> MultitestHelpers<'a> {
 
             pub struct InstantiateProxy<'a, 'app> {
                 code_id: &'a #code_id <'app>,
-                funds: &'a [cosmwasm_std::Coin],
+                funds: &'a [#sylvia ::cw_std::Coin],
                 label: &'a str,
                 admin: Option<String>,
                 msg: InstantiateMsg,
             }
 
             impl<'a, 'app> InstantiateProxy<'a, 'app> {
-                pub fn with_funds(self, funds: &'a [cosmwasm_std::Coin]) -> Self {
+                pub fn with_funds(self, funds: &'a [#sylvia ::cw_std::Coin]) -> Self {
                     Self { funds, ..self }
                 }
 
@@ -396,7 +518,7 @@ impl<'a> MultitestHelpers<'a> {
                         .borrow_mut()
                         .instantiate_contract(
                             self.code_id.code_id,
-                            cosmwasm_std::Addr::unchecked(sender),
+                            #sylvia ::cw_std::Addr::unchecked(sender),
                             &self.msg,
                             self.funds,
                             self.label,
@@ -414,83 +536,121 @@ impl<'a> MultitestHelpers<'a> {
 
     fn generate_impl_contract(&self) -> TokenStream {
         let contract = &self.contract;
+        let sylvia = crate_module();
 
         // MigrateMsg is not generated all the time in contrary to Exec, Query and Instantiate.
         let migrate_body = if self.is_migrate {
             quote! {
-                cosmwasm_std::from_slice::<MigrateMsg>(&msg)?
+                #sylvia ::cw_std::from_slice::<MigrateMsg>(&msg)?
                     .dispatch(self, (deps, env))
                     .map_err(Into::into)
             }
         } else {
             quote! {
-                anyhow::bail!("migrate not implemented for contract")
+                #sylvia ::anyhow::bail!("migrate not implemented for contract")
             }
         };
         quote! {
-            impl cw_multi_test::Contract<cosmwasm_std::Empty> for #contract {
+            impl #sylvia ::cw_multi_test::Contract<#sylvia ::cw_std::Empty> for #contract {
                 fn execute(
                     &self,
-                    deps: cosmwasm_std::DepsMut<cosmwasm_std::Empty>,
-                    env: cosmwasm_std::Env,
-                    info: cosmwasm_std::MessageInfo,
+                    deps: #sylvia ::cw_std::DepsMut<#sylvia ::cw_std::Empty>,
+                    env: #sylvia ::cw_std::Env,
+                    info: #sylvia ::cw_std::MessageInfo,
                     msg: Vec<u8>,
-                ) -> anyhow::Result<cosmwasm_std::Response<cosmwasm_std::Empty>> {
-                    cosmwasm_std::from_slice::<ContractExecMsg>(&msg)?
+                ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
+                    #sylvia ::cw_std::from_slice::<ContractExecMsg>(&msg)?
                         .dispatch(self, (deps, env, info))
                         .map_err(Into::into)
                 }
 
                 fn instantiate(
                     &self,
-                    deps: cosmwasm_std::DepsMut<cosmwasm_std::Empty>,
-                    env: cosmwasm_std::Env,
-                    info: cosmwasm_std::MessageInfo,
+                    deps: #sylvia ::cw_std::DepsMut<#sylvia ::cw_std::Empty>,
+                    env: #sylvia ::cw_std::Env,
+                    info: #sylvia ::cw_std::MessageInfo,
                     msg: Vec<u8>,
-                ) -> anyhow::Result<cosmwasm_std::Response<cosmwasm_std::Empty>> {
-                    cosmwasm_std::from_slice::<InstantiateMsg>(&msg)?
+                ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
+                    #sylvia ::cw_std::from_slice::<InstantiateMsg>(&msg)?
                         .dispatch(self, (deps, env, info))
                         .map_err(Into::into)
                 }
 
                 fn query(
                     &self,
-                    deps: cosmwasm_std::Deps<cosmwasm_std::Empty>,
-                    env: cosmwasm_std::Env,
+                    deps: #sylvia ::cw_std::Deps<#sylvia ::cw_std::Empty>,
+                    env: #sylvia ::cw_std::Env,
                     msg: Vec<u8>,
-                ) -> anyhow::Result<cosmwasm_std::Binary> {
-                    cosmwasm_std::from_slice::<ContractQueryMsg>(&msg)?
+                ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Binary> {
+                    #sylvia ::cw_std::from_slice::<ContractQueryMsg>(&msg)?
                         .dispatch(self, (deps, env))
                         .map_err(Into::into)
                 }
 
                 fn sudo(
                     &self,
-                    _deps: cosmwasm_std::DepsMut<cosmwasm_std::Empty>,
-                    _env: cosmwasm_std::Env,
+                    _deps: #sylvia ::cw_std::DepsMut<#sylvia ::cw_std::Empty>,
+                    _env: #sylvia ::cw_std::Env,
                     _msg: Vec<u8>,
-                ) -> anyhow::Result<cosmwasm_std::Response<cosmwasm_std::Empty>> {
-                    anyhow::bail!("sudo not implemented for contract")
+                ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
+                    #sylvia ::anyhow::bail!("sudo not implemented for contract")
                 }
 
                 fn reply(
                     &self,
-                    _deps: cosmwasm_std::DepsMut<cosmwasm_std::Empty>,
-                    _env: cosmwasm_std::Env,
-                    _msg: cosmwasm_std::Reply,
-                ) -> anyhow::Result<cosmwasm_std::Response<cosmwasm_std::Empty>> {
-                    anyhow::bail!("reply not implemented for contract")
+                    _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::bail!("reply not implemented for contract")
                 }
 
                 fn migrate(
                     &self,
-                    deps: cosmwasm_std::DepsMut<cosmwasm_std::Empty>,
-                    env: cosmwasm_std::Env,
+                    deps: #sylvia ::cw_std::DepsMut<#sylvia ::cw_std::Empty>,
+                    env: #sylvia ::cw_std::Env,
                     msg: Vec<u8>,
-                ) -> anyhow::Result<cosmwasm_std::Response<cosmwasm_std::Empty>> {
+                ) -> #sylvia ::anyhow::Result<#sylvia ::cw_std::Response<#sylvia ::cw_std::Empty>> {
                     #migrate_body
                 }
             }
         }
     }
 }
+
+pub struct TraitMultitestHelpers<'a> {
+    trait_name: &'a Ident,
+}
+
+impl<'a> TraitMultitestHelpers<'a> {
+    pub fn new(source: &'a ItemTrait) -> Self {
+        Self {
+            trait_name: &source.ident,
+        }
+    }
+
+    pub fn emit(&self) -> TokenStream {
+        let trait_name = self.trait_name;
+        let sylvia = crate_module();
+        let proxy_name = Ident::new(&format!("{}Proxy", trait_name), trait_name.span());
+
+        quote! {
+            pub mod trait_utils {
+                pub struct #proxy_name <'app> {
+                    pub contract_addr: #sylvia ::cw_std::Addr,
+                    pub app: &'app #sylvia ::multitest::App,
+                }
+                impl<'app> #proxy_name <'app> {
+                    pub fn new(contract_addr: #sylvia ::cw_std::Addr, app: &'app #sylvia ::multitest::App) -> Self {
+                        #proxy_name { contract_addr, app }
+                    }
+                }
+                impl Into<#sylvia ::cw_std::Addr> for #proxy_name <'_> {
+                    fn into(self) -> #sylvia ::cw_std::Addr {
+                        self.contract_addr
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/sylvia/tests/dispatching.rs b/sylvia/tests/dispatching.rs
index 479870a..64b07ee 100644
--- a/sylvia/tests/dispatching.rs
+++ b/sylvia/tests/dispatching.rs
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
 use std::cell::RefCell;
 use std::collections::HashMap;
 
-use sylvia::interface;
+use sylvia::{contract, interface};
 
 #[interface]
 pub trait Interface {
@@ -51,14 +51,17 @@ pub struct QueryResponse {
     desc: String,
 }
 
+#[contract]
 impl Interface for Contract {
     type Error = StdError;
 
+    #[msg(exec)]
     fn no_args_execution(&self, _: (DepsMut, Env, MessageInfo)) -> Result<Response, StdError> {
         *self.execs.borrow_mut() += 1;
         Ok(Response::new())
     }
 
+    #[msg(exec)]
     fn argumented_execution(
         &self,
         _: (DepsMut, Env, MessageInfo),
@@ -74,11 +77,13 @@ impl Interface for Contract {
         Ok(Response::new())
     }
 
+    #[msg(query)]
     fn no_args_query(&self, _: (Deps, Env)) -> Result<EmptyQueryResponse, StdError> {
         *self.queries.borrow_mut() += 1;
         Ok(dbg!(EmptyQueryResponse {}))
     }
 
+    #[msg(query)]
     fn argumented_query(&self, _: (Deps, Env), user: Addr) -> Result<QueryResponse, Self::Error> {
         *self.queries.borrow_mut() += 1;
         Ok(self.data.borrow().get(&user).unwrap().clone())