Repository: CosmWasm/sylvia
Pull request #248 (2023-10-26 09:49:29)

Contract api
Impl Extension trait on Contract. This change is required to generate `entry_point`s without requiring user to specify which generics are for which messages.

Unfortunately rust compiler requires fully-qualified path and in current state it's not the most user friendly Api so the primary use case is code generation.

#86 

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) Report
> Merging [#248](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) (028a1f0) into [generate_contract_types_in_sv_module](https://app.codecov.io/gh/CosmWasm/sylvia/commit/8f840c3dbdc7769219b1babd13cba616505c69de?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) (8f840c3) will **increase** coverage by `1.13%`.
> The diff coverage is `86.84%`.

```diff
@@                           Coverage Diff                            @@
##           generate_contract_types_in_sv_module     #248      +/-   ##
========================================================================
+ Coverage                                 85.87%   87.01%   +1.13%     
========================================================================
  Files                                        24       25       +1     
  Lines                                      1657     1725      +68     
========================================================================
+ Hits                                       1423     1501      +78     
+ Misses                                      234      224      -10     
```


| [Files](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) | Coverage Δ | |
|---|---|---|
| [sylvia-derive/src/input.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvaW5wdXQucnM=) | `93.44% <100.00%> (+0.33%)` | :arrow_up: |
| [sylvia-derive/src/interfaces.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvaW50ZXJmYWNlcy5ycw==) | `86.86% <100.00%> (ø)` | |
| [sylvia-derive/src/message.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvbWVzc2FnZS5ycw==) | `85.05% <100.00%> (+2.31%)` | :arrow_up: |
| [sylvia-derive/src/multitest.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvbXVsdGl0ZXN0LnJz) | `97.22% <100.00%> (+0.92%)` | :arrow_up: |
| [sylvia/src/types.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3NyYy90eXBlcy5ycw==) | `80.00% <ø> (ø)` | |
| [sylvia/tests/api.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhL3Rlc3RzL2FwaS5ycw==) | `0.00% <0.00%> (ø)` | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/CosmWasm/sylvia/pull/248/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm)

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm)


Patch:
diff --git a/examples/interfaces/custom-and-generic/src/lib.rs b/examples/interfaces/custom-and-generic/src/lib.rs
index 80eb0cf..f56d450 100644
--- a/examples/interfaces/custom-and-generic/src/lib.rs
+++ b/examples/interfaces/custom-and-generic/src/lib.rs
@@ -34,7 +34,7 @@ where
 mod tests {
     use cosmwasm_std::testing::mock_dependencies;
     use cosmwasm_std::{Addr, CosmosMsg, Empty, QuerierWrapper};
-    use sylvia::types::{InterfaceMessages, SvCustomMsg};
+    use sylvia::types::{InterfaceApi, SvCustomMsg};
 
     use crate::sv::Querier;
 
@@ -58,11 +58,11 @@ mod tests {
 
         // Construct messages with Interface extension
         let _ =
-            <super::sv::InterfaceTypes<SvCustomMsg, _, SvCustomMsg> as InterfaceMessages>::Query::custom_generic_query(
+            <super::sv::Api<SvCustomMsg, _, SvCustomMsg> as InterfaceApi>::Query::custom_generic_query(
                 SvCustomMsg {},
             );
         let _=
-            <super::sv::InterfaceTypes<_, SvCustomMsg, cosmwasm_std::Empty> as InterfaceMessages>::Exec::custom_generic_execute(
+            <super::sv::Api<_, SvCustomMsg, cosmwasm_std::Empty> as InterfaceApi>::Exec::custom_generic_execute(
             vec![ CosmosMsg::Custom(SvCustomMsg{}),
             ]);
     }
diff --git a/examples/interfaces/generic/src/lib.rs b/examples/interfaces/generic/src/lib.rs
index 827067e..f680153 100644
--- a/examples/interfaces/generic/src/lib.rs
+++ b/examples/interfaces/generic/src/lib.rs
@@ -27,7 +27,7 @@ where
 #[cfg(test)]
 mod tests {
     use cosmwasm_std::{testing::mock_dependencies, Addr, CosmosMsg, Empty, QuerierWrapper};
-    use sylvia::types::{InterfaceMessages, SvCustomMsg};
+    use sylvia::types::{InterfaceApi, SvCustomMsg};
 
     use crate::sv::Querier;
 
@@ -49,12 +49,11 @@ mod tests {
         let _: Result<SvCustomMsg, _> = querier.generic_query(SvCustomMsg {});
 
         // Construct messages with Interface extension
-        let _ =
-            <super::sv::InterfaceTypes<SvCustomMsg, _, SvCustomMsg> as InterfaceMessages>::Query::generic_query(
-                SvCustomMsg {},
-            );
+        let _ = <super::sv::Api<SvCustomMsg, _, SvCustomMsg> as InterfaceApi>::Query::generic_query(
+            SvCustomMsg {},
+        );
         let _=
-            <super::sv::InterfaceTypes<_, SvCustomMsg, cosmwasm_std::Empty> as InterfaceMessages>::Exec::generic_exec(vec![
+            <super::sv::Api<_, SvCustomMsg, cosmwasm_std::Empty> as InterfaceApi>::Exec::generic_exec(vec![
                 CosmosMsg::Custom(SvCustomMsg{}),
             ]);
     }
diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs
index edd55cb..839702c 100644
--- a/sylvia-derive/src/input.rs
+++ b/sylvia-derive/src/input.rs
@@ -12,7 +12,8 @@ use crate::check_generics::GetPath;
 use crate::crate_module;
 use crate::interfaces::Interfaces;
 use crate::message::{
-    ContractEnumMessage, EnumMessage, GlueMessage, InterfaceMessages, MsgVariants, StructMessage,
+    ContractApi, ContractEnumMessage, EnumMessage, GlueMessage, InterfaceApi, MsgVariants,
+    StructMessage,
 };
 use crate::multitest::{MultitestHelpers, TraitMultitestHelpers};
 use crate::parser::{ContractArgs, ContractErrorAttr, Custom, MsgType, OverrideEntryPoints};
@@ -77,7 +78,7 @@ impl<'a> TraitInput<'a> {
         )
         .emit_querier();
 
-        let interface_messages = InterfaceMessages::new(self.item, &self.generics).emit();
+        let interface_messages = InterfaceApi::new(self.item, &self.generics).emit();
 
         #[cfg(not(tarpaulin_include))]
         {
@@ -189,7 +190,12 @@ impl<'a> ImplInput<'a> {
     }
 
     fn process_contract(&self) -> TokenStream {
-        let Self { item, generics, .. } = self;
+        let Self {
+            item,
+            generics,
+            custom,
+            ..
+        } = self;
         let multitest_helpers = self.emit_multitest_helpers(generics);
         let where_clause = &item.generics.where_clause;
 
@@ -203,6 +209,7 @@ impl<'a> ImplInput<'a> {
         let messages = self.emit_messages();
         let remote = Remote::new(&self.interfaces).emit();
         let querier_from_impl = self.interfaces.emit_querier_from_impl();
+        let contract_api = ContractApi::new(item, generics, custom).emit();
 
         #[cfg(not(tarpaulin_include))]
         {
@@ -219,6 +226,8 @@ impl<'a> ImplInput<'a> {
                     #querier
 
                     #(#querier_from_impl)*
+
+                    #contract_api
                 }
             }
         }
diff --git a/sylvia-derive/src/interfaces.rs b/sylvia-derive/src/interfaces.rs
index 61455b1..0e75edd 100644
--- a/sylvia-derive/src/interfaces.rs
+++ b/sylvia-derive/src/interfaces.rs
@@ -95,7 +95,7 @@ impl Interfaces {
                 };
 
                 let interface_enum =
-                    quote! { <#module ::sv::InterfaceTypes #generics as #sylvia ::types::InterfaceMessages> };
+                    quote! { <#module ::sv::Api #generics as #sylvia ::types::InterfaceApi> };
                 if msg_ty == &MsgType::Query {
                     quote! { #variant ( #interface_enum :: Query) }
                 } else {
@@ -161,7 +161,7 @@ impl Interfaces {
 
                 let type_name = msg_ty.as_accessor_name();
                 quote! {
-                    <#module ::sv::InterfaceTypes #generics as #sylvia ::types::InterfaceMessages> :: #type_name :: response_schemas_impl()
+                    <#module ::sv::Api #generics as #sylvia ::types::InterfaceApi> :: #type_name :: response_schemas_impl()
                 }
             })
             .collect()
diff --git a/sylvia-derive/src/message.rs b/sylvia-derive/src/message.rs
index 141d114..fceb8c5 100644
--- a/sylvia-derive/src/message.rs
+++ b/sylvia-derive/src/message.rs
@@ -745,7 +745,8 @@ impl<'a> MsgVariant<'a> {
         let params = fields.iter().map(|field| field.emit_method_field());
         let arguments = fields.iter().map(MsgField::name);
         let bracketed_generics = emit_bracketed_generics(generics);
-        let interface_enum = quote! { < #module sv::InterfaceTypes #bracketed_generics as #sylvia ::types::InterfaceMessages> };
+        let interface_enum =
+            quote! { < #module sv::Api #bracketed_generics as #sylvia ::types::InterfaceApi> };
         let type_name = msg_ty.as_accessor_name();
         let name = Ident::new(&name.to_string().to_case(Case::Snake), name.span());
 
@@ -1434,13 +1435,113 @@ impl<'a> GlueMessage<'a> {
     }
 }
 
-pub struct InterfaceMessages<'a> {
+pub struct ContractApi<'a> {
+    source: &'a ItemImpl,
+    exec_variants: MsgVariants<'a, GenericParam>,
+    query_variants: MsgVariants<'a, GenericParam>,
+    instantiate_variants: MsgVariants<'a, GenericParam>,
+    migrate_variants: MsgVariants<'a, GenericParam>,
+    generics: &'a [&'a GenericParam],
+    custom: &'a Custom<'a>,
+}
+
+impl<'a> ContractApi<'a> {
+    pub fn new(
+        source: &'a ItemImpl,
+        generics: &'a [&'a GenericParam],
+        custom: &'a Custom<'a>,
+    ) -> Self {
+        let exec_variants = MsgVariants::new(
+            source.as_variants(),
+            MsgType::Exec,
+            generics,
+            &source.generics.where_clause,
+        );
+
+        let query_variants = MsgVariants::new(
+            source.as_variants(),
+            MsgType::Query,
+            generics,
+            &source.generics.where_clause,
+        );
+
+        let instantiate_variants = MsgVariants::new(
+            source.as_variants(),
+            MsgType::Instantiate,
+            generics,
+            &source.generics.where_clause,
+        );
+
+        let migrate_variants = MsgVariants::new(
+            source.as_variants(),
+            MsgType::Migrate,
+            generics,
+            &source.generics.where_clause,
+        );
+
+        Self {
+            source,
+            exec_variants,
+            query_variants,
+            instantiate_variants,
+            migrate_variants,
+            generics,
+            custom,
+        }
+    }
+
+    pub fn emit(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            source,
+            exec_variants,
+            query_variants,
+            instantiate_variants,
+            migrate_variants,
+            generics,
+            custom,
+        } = self;
+
+        let contract_name = &source.self_ty;
+        let exec_generics = &exec_variants.used_generics;
+        let query_generics = &query_variants.used_generics;
+        let instantiate_generics = &instantiate_variants.used_generics;
+        let migrate_generics = &migrate_variants.used_generics;
+
+        let bracket_generics = emit_bracketed_generics(generics);
+        let exec_bracketed_generics = emit_bracketed_generics(exec_generics);
+        let query_bracketed_generics = emit_bracketed_generics(query_generics);
+        let instantiate_bracketed_generics = emit_bracketed_generics(instantiate_generics);
+        let migrate_bracketed_generics = emit_bracketed_generics(migrate_generics);
+
+        let migrate_type = match !migrate_variants.variants().is_empty() {
+            true => quote! { type Migrate = MigrateMsg #migrate_bracketed_generics; },
+            false => quote! { type Migrate = #sylvia ::cw_std::Empty; },
+        };
+        let custom_query = custom.query_or_default();
+
+        quote! {
+            impl #bracket_generics #sylvia ::types::ContractApi for #contract_name {
+                type ContractExec = ContractExecMsg #exec_bracketed_generics;
+                type ContractQuery = ContractQueryMsg #query_bracketed_generics;
+                type Exec = ExecMsg #exec_bracketed_generics;
+                type Query = QueryMsg #query_bracketed_generics;
+                type Instantiate = InstantiateMsg #instantiate_bracketed_generics;
+                #migrate_type
+                type Remote<'remote> = Remote<'remote>;
+                type Querier<'querier> = BoundQuerier<'querier, #custom_query >;
+            }
+        }
+    }
+}
+
+pub struct InterfaceApi<'a> {
     exec_variants: MsgVariants<'a, GenericParam>,
     query_variants: MsgVariants<'a, GenericParam>,
     generics: &'a [&'a GenericParam],
 }
 
-impl<'a> InterfaceMessages<'a> {
+impl<'a> InterfaceApi<'a> {
     pub fn new(source: &'a ItemTrait, generics: &'a [&'a GenericParam]) -> Self {
         let exec_variants = MsgVariants::new(
             source.as_variants(),
@@ -1487,11 +1588,11 @@ impl<'a> InterfaceMessages<'a> {
         };
 
         quote! {
-            pub struct InterfaceTypes #bracket_generics {
+            pub struct Api #bracket_generics {
                 #phantom
             }
 
-            impl #bracket_generics #sylvia ::types::InterfaceMessages for InterfaceTypes #bracket_generics {
+            impl #bracket_generics #sylvia ::types::InterfaceApi for Api #bracket_generics {
                 type Exec = ExecMsg #exec_bracketed_generics;
                 type Query = QueryMsg #query_bracketed_generics;
             }
diff --git a/sylvia-derive/src/remote.rs b/sylvia-derive/src/remote.rs
index bfe6342..76fcdf1 100644
--- a/sylvia-derive/src/remote.rs
+++ b/sylvia-derive/src/remote.rs
@@ -33,13 +33,11 @@ impl<'a> Remote<'a> {
             #[derive(#sylvia ::serde::Serialize, #sylvia ::serde::Deserialize, Clone, Debug, PartialEq, #sylvia ::schemars::JsonSchema)]
             pub struct Remote<'a>(std::borrow::Cow<'a, #sylvia ::cw_std::Addr>);
 
-            impl Remote<'static> {
+            impl<'a> Remote<'a> {
                 pub fn new(addr: #sylvia ::cw_std::Addr) -> Self {
                     Self(std::borrow::Cow::Owned(addr))
                 }
-            }
 
-            impl<'a> Remote<'a> {
                 pub fn borrowed(addr: &'a #sylvia ::cw_std::Addr) -> Self {
                     Self(std::borrow::Cow::Borrowed(addr))
                 }
diff --git a/sylvia/src/types.rs b/sylvia/src/types.rs
index 059ede4..d5c3d33 100644
--- a/sylvia/src/types.rs
+++ b/sylvia/src/types.rs
@@ -105,7 +105,18 @@ pub struct SvCustomMsg;
 
 impl cosmwasm_std::CustomMsg for SvCustomMsg {}
 
-pub trait InterfaceMessages {
+pub trait InterfaceApi {
     type Exec;
     type Query;
 }
+
+pub trait ContractApi {
+    type Instantiate;
+    type Query;
+    type Exec;
+    type ContractQuery;
+    type ContractExec;
+    type Migrate;
+    type Querier<'querier>;
+    type Remote<'remote>;
+}


Test patch:
diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs
index 3ea3ad8..15e788f 100644
--- a/sylvia-derive/src/multitest.rs
+++ b/sylvia-derive/src/multitest.rs
@@ -323,7 +323,8 @@ where
         };
 
         let bracketed_generics = emit_bracketed_generics(generics);
-        let interface_enum = quote! { < #module sv::InterfaceTypes #bracketed_generics as #sylvia ::types::InterfaceMessages> };
+        let interface_enum =
+            quote! { < #module sv::Api #bracketed_generics as #sylvia ::types::InterfaceApi> };
 
         let exec_methods = exec_variants.emit_interface_multitest_proxy_methods(
             &custom_msg,
diff --git a/sylvia/tests/api.rs b/sylvia/tests/api.rs
new file mode 100644
index 0000000..e9c39a6
--- /dev/null
+++ b/sylvia/tests/api.rs
@@ -0,0 +1,127 @@
+use cosmwasm_std::{Response, StdResult};
+use std::marker::PhantomData;
+
+use sylvia::types::{CustomMsg, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx};
+use sylvia_derive::contract;
+
+pub struct SomeContract<Instantiate, Query, Exec, Migrate, Ret> {
+    _phantom: PhantomData<(Instantiate, Query, Exec, Migrate, Ret)>,
+}
+
+#[contract]
+impl<Instantiate, Query, Exec, Migrate, Ret> SomeContract<Instantiate, Query, Exec, Migrate, Ret>
+where
+    Instantiate: CustomMsg + 'static,
+    Query: CustomMsg + 'static,
+    Exec: CustomMsg + 'static,
+    Migrate: CustomMsg + 'static,
+    Ret: CustomMsg + 'static,
+{
+    pub const fn new() -> Self {
+        Self {
+            _phantom: PhantomData,
+        }
+    }
+
+    #[msg(instantiate)]
+    pub fn instantiate(&self, _ctx: InstantiateCtx, _param: Instantiate) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[msg(exec)]
+    pub fn exec(&self, _ctx: ExecCtx, _param: Exec) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[msg(query)]
+    pub fn query(&self, _ctx: QueryCtx, _param: Query) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+
+    #[msg(migrate)]
+    pub fn migrate(&self, _ctx: MigrateCtx, _param: Migrate) -> StdResult<Response> {
+        Ok(Response::new())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::SomeContract;
+    use cosmwasm_std::{testing::mock_dependencies, Addr, QuerierWrapper};
+    use sylvia::types::{ContractApi, SvCustomMsg};
+
+    #[test]
+    fn api() {
+        let owner = Addr::unchecked("owner");
+
+        let _: crate::sv::InstantiateMsg<SvCustomMsg> = <SomeContract<
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+        > as ContractApi>::Instantiate::new(
+            SvCustomMsg
+        );
+
+        let exec: crate::sv::ExecMsg<SvCustomMsg> = <SomeContract<
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+        > as ContractApi>::Exec::exec(
+            SvCustomMsg
+        );
+
+        let query: crate::sv::QueryMsg<SvCustomMsg> = <SomeContract<
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+        > as ContractApi>::Query::query(
+            SvCustomMsg
+        );
+
+        let _: crate::sv::ContractExecMsg<SvCustomMsg> = <SomeContract<
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+        > as ContractApi>::ContractExec::SomeContract(
+            exec
+        );
+
+        let _: crate::sv::ContractQueryMsg<SvCustomMsg> = <SomeContract<
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+        > as ContractApi>::ContractQuery::SomeContract(
+            query
+        );
+
+        let _: crate::sv::Remote<'_> = <SomeContract<
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+        > as ContractApi>::Remote::new(owner.clone());
+
+        let deps = mock_dependencies();
+        let querier_wrapper: QuerierWrapper = QuerierWrapper::new(&deps.querier);
+        let _: crate::sv::BoundQuerier<'_, cosmwasm_std::Empty> = <SomeContract<
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+            SvCustomMsg,
+        > as ContractApi>::Querier::borrowed(
+            &owner, &querier_wrapper
+        );
+    }
+}