From 1c6cb247c90f4c9f3bdf145f68d375e40275d53a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wo=C5=BAniak?= Date: Wed, 18 Oct 2023 12:29:45 +0200 Subject: [PATCH 1/3] feat: Support generics on every message type --- sylvia-derive/src/input.rs | 54 ++++-------- sylvia-derive/src/message.rs | 155 +++++++++++++++++++-------------- sylvia-derive/src/multitest.rs | 69 +++++++++------ sylvia-derive/src/parser.rs | 24 ++--- sylvia/tests/generics.rs | 72 ++++++++++++--- 5 files changed, 216 insertions(+), 158 deletions(-) diff --git a/sylvia-derive/src/input.rs b/sylvia-derive/src/input.rs index 8f9f2400..74061b64 100644 --- a/sylvia-derive/src/input.rs +++ b/sylvia-derive/src/input.rs @@ -185,16 +185,16 @@ impl<'a> ImplInput<'a> { let Self { item, generics, .. } = self; let multitest_helpers = self.emit_multitest_helpers(generics); let where_clause = &item.generics.where_clause; - let variants = MsgVariants::new( + + let querier = MsgVariants::new( self.item.as_variants(), MsgType::Query, generics, where_clause, - ); - - let messages = self.emit_messages(&variants); + ) + .emit_querier(); + let messages = self.emit_messages(); let remote = Remote::new(&self.interfaces).emit(); - let querier = variants.emit_querier(); let querier_from_impl = self.interfaces.emit_querier_from_impl(); #[cfg(not(tarpaulin_include))] @@ -213,23 +213,13 @@ impl<'a> ImplInput<'a> { } } - fn emit_messages(&self, variants: &MsgVariants) -> TokenStream { + fn emit_messages(&self) -> TokenStream { let instantiate = self.emit_struct_msg(MsgType::Instantiate); let migrate = self.emit_struct_msg(MsgType::Migrate); - let exec_impl = - self.emit_enum_msg(&Ident::new("ExecMsg", Span::mixed_site()), MsgType::Exec); - let query_impl = - self.emit_enum_msg(&Ident::new("QueryMsg", Span::mixed_site()), MsgType::Query); - let exec = self.emit_glue_msg( - &Ident::new("ExecMsg", Span::mixed_site()), - MsgType::Exec, - variants, - ); - let query = self.emit_glue_msg( - &Ident::new("QueryMsg", Span::mixed_site()), - MsgType::Query, - variants, - ); + let exec_impl = self.emit_enum_msg(MsgType::Exec); + let query_impl = self.emit_enum_msg(MsgType::Query); + let exec = self.emit_glue_msg(MsgType::Exec); + let query = self.emit_glue_msg(MsgType::Query); #[cfg(not(tarpaulin_include))] { @@ -254,26 +244,16 @@ impl<'a> ImplInput<'a> { .map_or(quote! {}, |msg| msg.emit()) } - fn emit_enum_msg(&self, name: &Ident, msg_ty: MsgType) -> TokenStream { - ContractEnumMessage::new( - name, - self.item, - msg_ty, - &self.generics, - &self.error, - &self.custom, - ) - .emit() + fn emit_enum_msg(&self, msg_ty: MsgType) -> TokenStream { + ContractEnumMessage::new(self.item, msg_ty, &self.generics, &self.error, &self.custom) + .emit() } - fn emit_glue_msg( - &self, - name: &Ident, - msg_ty: MsgType, - variants: &MsgVariants, - ) -> TokenStream { + fn emit_glue_msg(&self, msg_ty: MsgType) -> TokenStream { + let Self { generics, item, .. } = self; + let where_clause = &item.generics.where_clause; + let variants = MsgVariants::new(item.as_variants(), msg_ty, generics, where_clause); GlueMessage::new( - name, self.item, msg_ty, &self.error, diff --git a/sylvia-derive/src/message.rs b/sylvia-derive/src/message.rs index 443b11c2..a067a7c3 100644 --- a/sylvia-derive/src/message.rs +++ b/sylvia-derive/src/message.rs @@ -348,37 +348,32 @@ impl<'a> EnumMessage<'a> { /// Representation of single enum message pub struct ContractEnumMessage<'a> { - name: &'a Ident, variants: MsgVariants<'a, GenericParam>, msg_ty: MsgType, contract: &'a Type, error: &'a Type, custom: &'a Custom<'a>, + where_clause: &'a Option, } impl<'a> ContractEnumMessage<'a> { pub fn new( - name: &'a Ident, source: &'a ItemImpl, msg_ty: MsgType, generics: &'a [&'a GenericParam], error: &'a Type, custom: &'a Custom, ) -> Self { - let variants = MsgVariants::new( - source.as_variants(), - msg_ty, - generics, - &source.generics.where_clause, - ); + let where_clause = &source.generics.where_clause; + let variants = MsgVariants::new(source.as_variants(), msg_ty, generics, where_clause); Self { - name, variants, msg_ty, contract: &source.self_ty, error, custom, + where_clause, } } @@ -386,18 +381,21 @@ impl<'a> ContractEnumMessage<'a> { let sylvia = crate_module(); let Self { - name, variants, msg_ty, contract, error, custom, + where_clause, + .. } = self; + let enum_name = msg_ty.emit_msg_name(false); let match_arms = variants.emit_dispatch_legs(); - let generic_name = variants.emit_generic_name(name); let unused_generics = variants.unused_generics(); let unused_generics = emit_bracketed_generics(unused_generics); + let used_generics = variants.used_generics(); + let used_generics = emit_bracketed_generics(used_generics); let mut variant_names = variants.as_names_snake_cased(); variant_names.sort(); @@ -419,13 +417,13 @@ impl<'a> ContractEnumMessage<'a> { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(#sylvia ::serde::Serialize, #sylvia ::serde::Deserialize, Clone, Debug, PartialEq, #sylvia ::schemars::JsonSchema, #derive_query )] #[serde(rename_all="snake_case")] - pub enum #generic_name { + pub enum #enum_name #used_generics { #(#variants,)* } - impl #generic_name { - pub fn dispatch #unused_generics (self, contract: &#contract, ctx: #ctx_type) -> #ret_type { - use #name::*; + impl #used_generics #enum_name #used_generics { + pub fn dispatch #unused_generics (self, contract: &#contract, ctx: #ctx_type) -> #ret_type #where_clause { + use #enum_name::*; match self { #(#match_arms,)* @@ -650,13 +648,17 @@ impl<'a> MsgVariant<'a> { } } - pub fn emit_multitest_proxy_methods( + pub fn emit_multitest_proxy_methods( &self, msg_ty: &MsgType, custom_msg: &Type, mt_app: &Type, error_type: &Type, - ) -> TokenStream { + generics: &[&Generic], + ) -> TokenStream + where + Generic: ToTokens, + { let sylvia = crate_module(); let Self { name, @@ -668,27 +670,33 @@ impl<'a> MsgVariant<'a> { let params = fields.iter().map(|field| field.emit_method_field()); let arguments = fields.iter().map(MsgField::name); let name = Ident::new(&name.to_string().to_case(Case::Snake), name.span()); + let enum_name = msg_ty.emit_msg_name(false); + let enum_name: Type = if !generics.is_empty() { + parse_quote! { #enum_name ::< #(#generics,)* > } + } else { + parse_quote! { #enum_name } + }; match msg_ty { MsgType::Exec => quote! { #[track_caller] - pub fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, ExecMsg, #mt_app, #custom_msg> { - let msg = ExecMsg:: #name ( #(#arguments),* ); + pub fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::ExecProxy::<#error_type, #enum_name, #mt_app, #custom_msg> { + let msg = #enum_name :: #name ( #(#arguments),* ); #sylvia ::multitest::ExecProxy::new(&self.contract_addr, msg, &self.app) } }, MsgType::Migrate => quote! { #[track_caller] - pub fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::MigrateProxy::<#error_type, MigrateMsg, #mt_app, #custom_msg> { - let msg = MigrateMsg::new( #(#arguments),* ); + pub fn #name (&self, #(#params,)* ) -> #sylvia ::multitest::MigrateProxy::<#error_type, #enum_name, #mt_app, #custom_msg> { + let msg = #enum_name ::new( #(#arguments),* ); #sylvia ::multitest::MigrateProxy::new(&self.contract_addr, msg, &self.app) } }, MsgType::Query => quote! { pub fn #name (&self, #(#params,)* ) -> Result<#return_type, #error_type> { - let msg = QueryMsg:: #name ( #(#arguments),* ); + let msg = #enum_name :: #name ( #(#arguments),* ); (*self.app) .app() @@ -871,8 +879,13 @@ where &self.unused_generics } - pub fn where_predicates(&'a self) -> &'a [&'a WherePredicate] { - &self.where_predicates + pub fn as_where_clause(&'a self) -> Option { + let where_predicates = &self.where_predicates; + if !where_predicates.is_empty() { + Some(parse_quote!( where #(#where_predicates,)* )) + } else { + None + } } pub fn emit_querier(&self) -> TokenStream { @@ -980,10 +993,11 @@ where } = self; let values = msg_ty.emit_ctx_values(); - let msg_name = msg_ty.emit_msg_name(used_generics.as_slice()); + let msg_name = msg_ty.emit_msg_name(true); + let bracketed_generics = emit_bracketed_generics(used_generics); quote! { - #sylvia ::cw_std::from_slice::< #msg_name >(&msg)? + #sylvia ::cw_std::from_slice::< #msg_name #bracketed_generics >(&msg)? .dispatch(self, ( #values )) .map_err(Into::into) } @@ -1010,13 +1024,14 @@ where let params = msg_ty.emit_ctx_params(custom_query); let values = msg_ty.emit_ctx_values(); let ep_name = msg_ty.emit_ep_name(); - let msg_name = msg_ty.emit_msg_name(used_generics); + let msg_name = msg_ty.emit_msg_name(true); + let bracketed_generics = emit_bracketed_generics(used_generics); quote! { #[#sylvia ::cw_std::entry_point] pub fn #ep_name ( #params , - msg: #msg_name, + msg: #msg_name #bracketed_generics, ) -> Result<#resp_type, #error> { msg.dispatch(&#name ::new() , ( #values )).map_err(Into::into) } @@ -1031,7 +1046,13 @@ where self.variants .iter() .map(|variant| { - variant.emit_multitest_proxy_methods(&self.msg_ty, custom_msg, mt_app, error_type) + variant.emit_multitest_proxy_methods( + &self.msg_ty, + custom_msg, + mt_app, + error_type, + &self.used_generics, + ) }) .collect() } @@ -1100,17 +1121,6 @@ where .map(MsgVariant::emit_variants_constructors) } - pub fn emit_generic_name(&self, name: &Ident) -> TokenStream { - let generics = emit_bracketed_generics(&self.used_generics); - - #[cfg(not(tarpaulin_include))] - { - quote! { - #name #generics - } - } - } - pub fn emit(&self) -> impl Iterator + '_ { self.variants.iter().map(MsgVariant::emit) } @@ -1201,27 +1211,26 @@ impl<'a> MsgField<'a> { /// Glue message is the message composing Exec/Query messages from several traits #[derive(Debug)] pub struct GlueMessage<'a> { - name: &'a Ident, + source: &'a ItemImpl, contract: &'a Type, msg_ty: MsgType, error: &'a Type, custom: &'a Custom<'a>, interfaces: &'a Interfaces, - variants: &'a MsgVariants<'a, GenericParam>, + variants: MsgVariants<'a, GenericParam>, } impl<'a> GlueMessage<'a> { pub fn new( - name: &'a Ident, source: &'a ItemImpl, msg_ty: MsgType, error: &'a Type, custom: &'a Custom, interfaces: &'a Interfaces, - variants: &'a MsgVariants<'a, GenericParam>, + variants: MsgVariants<'a, GenericParam>, ) -> Self { GlueMessage { - name, + source, contract: &source.self_ty, msg_ty, error, @@ -1234,7 +1243,7 @@ impl<'a> GlueMessage<'a> { pub fn emit(&self) -> TokenStream { let sylvia = crate_module(); let Self { - name, + source, contract, msg_ty, error, @@ -1242,19 +1251,28 @@ impl<'a> GlueMessage<'a> { interfaces, variants, } = self; - let contract_name = StripGenerics.fold_type((*contract).clone()); - let enum_name = Ident::new(&format!("Contract{}", name), name.span()); + let used_generics = variants.used_generics(); - let used_generics = emit_bracketed_generics(used_generics); let unused_generics = variants.unused_generics(); + let where_clause = variants.as_where_clause(); + let full_where_clause = &source.generics.where_clause; + + let contract_enum_name = msg_ty.emit_msg_name(true); + let enum_name = msg_ty.emit_msg_name(false); + let contract_name = StripGenerics.fold_type((*contract).clone()); let unused_generics = emit_bracketed_generics(unused_generics); - let where_clause = variants.where_clause(); + let bracketed_used_generics = emit_bracketed_generics(used_generics); let variants = interfaces.emit_glue_message_variants(msg_ty); - let contract_variant = quote! { #contract_name ( #name ) }; + let contract_variant = quote! { #contract_name ( #enum_name #bracketed_used_generics ) }; let mut messages_call = interfaces.emit_messages_call(msg_ty); - messages_call.push(quote! { &#name :: messages() }); + let prefixed_used_generics = if !used_generics.is_empty() { + quote! { :: #bracketed_used_generics } + } else { + quote! {} + }; + messages_call.push(quote! { &#enum_name #prefixed_used_generics :: messages() }); let variants_cnt = messages_call.len(); @@ -1277,22 +1295,22 @@ impl<'a> GlueMessage<'a> { match (msg_ty, customs.has_msg) { (MsgType::Exec, true) => quote! { - #enum_name:: #variant(msg) => #sylvia ::into_response::IntoResponse::into_response(msg.dispatch(contract, Into::into( #ctx ))?) + #contract_enum_name:: #variant(msg) => #sylvia ::into_response::IntoResponse::into_response(msg.dispatch(contract, Into::into( #ctx ))?) }, _ => quote! { - #enum_name :: #variant(msg) => msg.dispatch(contract, Into::into( #ctx )) + #contract_enum_name :: #variant(msg) => msg.dispatch(contract, Into::into( #ctx )) }, } }); let dispatch_arm = - quote! {#enum_name :: #contract_name (msg) => msg.dispatch(contract, ctx)}; + quote! {#contract_enum_name :: #contract_name (msg) => msg.dispatch(contract, ctx)}; let interfaces_deserialization_attempts = interfaces.emit_deserialization_attempts(msg_ty); #[cfg(not(tarpaulin_include))] let contract_deserialization_attempt = quote! { - let msgs = &#name :: messages(); + let msgs = &#enum_name #prefixed_used_generics :: messages(); if msgs.into_iter().any(|msg| msg == &recv_msg_name) { match val.deserialize_into() { Ok(msg) => return Ok(Self:: #contract_name (msg)), @@ -1305,15 +1323,16 @@ impl<'a> GlueMessage<'a> { let ret_type = msg_ty.emit_result_type(&custom.msg_or_default(), error); let mut response_schemas_calls = interfaces.emit_response_schemas_calls(msg_ty); - response_schemas_calls.push(quote! {#name :: response_schemas_impl()}); + response_schemas_calls + .push(quote! {#enum_name #prefixed_used_generics :: response_schemas_impl()}); - let response_schemas = match name.to_string().as_str() { - "QueryMsg" => { + let response_schemas = match msg_ty { + MsgType::Query => { #[cfg(not(tarpaulin_include))] { quote! { #[cfg(not(target_arch = "wasm32"))] - impl #sylvia ::cw_schema::QueryResponses for #enum_name { + impl #bracketed_used_generics #sylvia ::cw_schema::QueryResponses for #contract_enum_name #bracketed_used_generics #where_clause { fn response_schemas_impl() -> std::collections::BTreeMap { let responses = [#(#response_schemas_calls),*]; responses.into_iter().flatten().collect() @@ -1333,21 +1352,23 @@ impl<'a> GlueMessage<'a> { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(#sylvia ::serde::Serialize, Clone, Debug, PartialEq, #sylvia ::schemars::JsonSchema)] #[serde(rename_all="snake_case", untagged)] - pub enum #enum_name #used_generics { + pub enum #contract_enum_name #bracketed_used_generics { #(#variants,)* #contract_variant } - impl #used_generics #enum_name #used_generics { - pub fn dispatch #unused_generics #where_clause ( + impl #bracketed_used_generics #contract_enum_name #bracketed_used_generics { + pub fn dispatch #unused_generics ( self, contract: &#contract, ctx: #ctx_type, - ) -> #ret_type { - const _: () = { + ) -> #ret_type #full_where_clause { + const fn assert_no_intersection #bracketed_used_generics () #where_clause { let msgs: [&[&str]; #variants_cnt] = [#(#messages_call),*]; #sylvia ::utils::assert_no_intersection(msgs); - }; + } + + assert_no_intersection #prefixed_used_generics (); match self { #(#dispatch_arms,)* @@ -1358,7 +1379,7 @@ impl<'a> GlueMessage<'a> { #response_schemas - impl<'de> serde::Deserialize<'de> for #enum_name { + impl<'de, #(#used_generics,)* > serde::Deserialize<'de> for #contract_enum_name #bracketed_used_generics #where_clause { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs index dbec15a1..f958cd5c 100644 --- a/sylvia-derive/src/multitest.rs +++ b/sylvia-derive/src/multitest.rs @@ -38,6 +38,7 @@ pub struct MultitestHelpers<'a, Generics> { is_trait: bool, source: &'a ItemImpl, generics: &'a [&'a Generics], + where_clause: &'a Option, contract_name: &'a Ident, proxy_name: Ident, custom: &'a Custom<'a>, @@ -127,6 +128,7 @@ where is_trait, source, generics, + where_clause, contract_name, proxy_name, custom, @@ -150,6 +152,8 @@ where exec_variants, query_variants, migrate_variants, + generics, + where_clause, .. } = self; let sylvia = crate_module(); @@ -180,6 +184,9 @@ where query_variants.emit_multitest_proxy_methods(&custom_msg, &mt_app, error_type); let migrate_methods = migrate_variants.emit_multitest_proxy_methods(&custom_msg, &mt_app, error_type); + let where_predicates = where_clause + .as_ref() + .map(|where_clause| &where_clause.predicates); let contract_block = self.generate_contract_helpers(); @@ -195,13 +202,14 @@ where #[derive(Derivative)] #[derivative(Debug)] - pub struct #proxy_name <'app, MtApp> { + pub struct #proxy_name <'app, MtApp, #(#generics,)* > { pub contract_addr: #sylvia ::cw_std::Addr, #[derivative(Debug="ignore")] pub app: &'app #sylvia ::multitest::App, + _phantom: std::marker::PhantomData<( #(#generics,)* )>, } - impl<'app, BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> #proxy_name <'app, #mt_app > + impl<'app, BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT, #(#generics,)* > #proxy_name <'app, #mt_app, #(#generics,)* > where CustomT: #sylvia ::cw_multi_test::Module, CustomT::ExecT: std::fmt::Debug @@ -219,10 +227,11 @@ where DistrT: #sylvia ::cw_multi_test::Distribution, IbcT: #sylvia ::cw_multi_test::Ibc, GovT: #sylvia ::cw_multi_test::Gov, - #mt_app : Executor< #custom_msg > + #mt_app : Executor< #custom_msg >, + #where_predicates { pub fn new(contract_addr: #sylvia ::cw_std::Addr, app: &'app #sylvia ::multitest::App< #mt_app >) -> Self { - #proxy_name{ contract_addr, app } + #proxy_name { contract_addr, app, _phantom: std::marker::PhantomData::default() } } #( #exec_methods )* @@ -231,12 +240,12 @@ where #( #proxy_accessors )* } - impl<'app, BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT> + impl<'app, BankT, ApiT, StorageT, CustomT, WasmT, StakingT, DistrT, IbcT, GovT, #(#generics,)* > From<( #sylvia ::cw_std::Addr, &'app #sylvia ::multitest::App<#mt_app>, )> - for #proxy_name <'app, #mt_app > + for #proxy_name <'app, #mt_app, #(#generics,)* > where CustomT: #sylvia ::cw_multi_test::Module, CustomT::ExecT: std::fmt::Debug @@ -255,9 +264,10 @@ where IbcT: #sylvia ::cw_multi_test::Ibc, GovT: #sylvia ::cw_multi_test::Gov, #mt_app : Executor< #custom_msg >, + #where_predicates { fn from(input: (#sylvia ::cw_std::Addr, &'app #sylvia ::multitest::App< #mt_app >)) - -> #proxy_name<'app, #mt_app > { + -> #proxy_name<'app, #mt_app, #(#generics,)* > { #proxy_name::new(input.0, input.1) } } @@ -378,10 +388,10 @@ where fn generate_contract_helpers(&self) -> TokenStream { let sylvia = crate_module(); let Self { - source, error_type, is_trait, generics, + where_clause, contract_name, proxy_name, instantiate_variants, @@ -404,11 +414,10 @@ where let used_generics = instantiate_variants.used_generics(); let bracketed_used_generics = emit_bracketed_generics(used_generics); - let bracketed_generics = emit_bracketed_generics(generics); - let full_where_clause = &source.generics.where_clause; - let where_predicates = instantiate_variants.where_predicates(); - let where_clause = instantiate_variants.where_clause(); + let where_predicates = where_clause + .as_ref() + .map(|where_clause| &where_clause.predicates); let contract = if !generics.is_empty() { quote! { #contract_name ::< #(#generics,)* > } } else { @@ -446,12 +455,14 @@ where quote! { #impl_contract - pub struct CodeId<'app, MtApp> { + pub struct CodeId<'app, MtApp, #(#generics,)* > { code_id: u64, app: &'app #sylvia ::multitest::App, + _phantom: std::marker::PhantomData<( #(#generics,)* )>, + } - impl<'app, BankT, ApiT, StorageT, CustomT, StakingT, DistrT, IbcT, GovT> CodeId<'app, #mt_app> + impl<'app, BankT, ApiT, StorageT, CustomT, StakingT, DistrT, IbcT, GovT, #(#generics,)* > CodeId<'app, #mt_app, #(#generics,)* > where BankT: #sylvia ::cw_multi_test::Bank, ApiT: #sylvia ::cw_std::Api, @@ -461,23 +472,24 @@ where DistrT: #sylvia ::cw_multi_test::Distribution, IbcT: #sylvia ::cw_multi_test::Ibc, GovT: #sylvia ::cw_multi_test::Gov, + #where_predicates { - pub fn store_code #bracketed_generics (app: &'app #sylvia ::multitest::App< #mt_app >) -> Self #full_where_clause { + pub fn store_code(app: &'app #sylvia ::multitest::App< #mt_app >) -> Self { let code_id = app .app_mut() .store_code(Box::new(#contract ::new())); - Self { code_id, app } + Self { code_id, app, _phantom: std::marker::PhantomData::default() } } pub fn code_id(&self) -> u64 { self.code_id } - pub fn instantiate #bracketed_used_generics ( + pub fn instantiate( &self,#(#fields,)* - ) -> InstantiateProxy<'_, 'app, #mt_app, #(#used_generics,)* > #where_clause { + ) -> InstantiateProxy<'_, 'app, #mt_app, #(#generics,)* > { let msg = #instantiate_msg {#(#fields_names,)*}; - InstantiateProxy::<_, #(#used_generics,)* > { + InstantiateProxy::<_, #(#generics,)* > { code_id: self, funds: &[], label: "Contract", @@ -487,24 +499,24 @@ where } } - pub struct InstantiateProxy<'a, 'app, MtApp, #(#used_generics,)* > { - code_id: &'a CodeId <'app, MtApp>, - funds: &'a [#sylvia ::cw_std::Coin], - label: &'a str, + pub struct InstantiateProxy<'proxy, 'app, MtApp, #(#generics,)* > { + code_id: &'proxy CodeId <'app, MtApp, #(#generics,)* >, + funds: &'proxy [#sylvia ::cw_std::Coin], + label: &'proxy str, admin: Option, msg: InstantiateMsg #bracketed_used_generics, } - impl<'a, 'app, MtApp, #(#used_generics,)* > InstantiateProxy<'a, 'app, MtApp, #(#used_generics,)* > + impl<'proxy, 'app, MtApp, #(#generics,)* > InstantiateProxy<'proxy, 'app, MtApp, #(#generics,)* > where MtApp: Executor< #custom_msg >, - #(#where_predicates,)* + #where_predicates { - pub fn with_funds(self, funds: &'a [#sylvia ::cw_std::Coin]) -> Self { + pub fn with_funds(self, funds: &'proxy [#sylvia ::cw_std::Coin]) -> Self { Self { funds, ..self } } - pub fn with_label(self, label: &'a str) -> Self { + pub fn with_label(self, label: &'proxy str) -> Self { Self { label, ..self } } @@ -514,7 +526,7 @@ where } #[track_caller] - pub fn call(self, sender: &str) -> Result<#proxy_name<'app, MtApp>, #error_type> { + pub fn call(self, sender: &str) -> Result<#proxy_name<'app, MtApp, #(#generics,)* >, #error_type> { (*self.code_id.app) .app_mut() .instantiate_contract( @@ -529,6 +541,7 @@ where .map(|addr| #proxy_name { contract_addr: addr, app: self.code_id.app, + _phantom: std::marker::PhantomData::default(), }) } } diff --git a/sylvia-derive/src/parser.rs b/sylvia-derive/src/parser.rs index 51ca393c..864eb183 100644 --- a/sylvia-derive/src/parser.rs +++ b/sylvia-derive/src/parser.rs @@ -1,6 +1,6 @@ use proc_macro2::{Punct, TokenStream}; use proc_macro_error::emit_error; -use quote::{quote, ToTokens}; +use quote::quote; use syn::fold::Fold; use syn::parse::{Error, Nothing, Parse, ParseBuffer, ParseStream, Parser}; use syn::punctuated::Punctuated; @@ -145,21 +145,15 @@ impl MsgType { } } - pub fn emit_msg_name(&self, generics: &[&Generic]) -> Type - where - Generic: ToTokens, - { - let generics = if !generics.is_empty() { - quote! { ::< #(#generics,)* > } - } else { - quote! {} - }; + pub fn emit_msg_name(&self, is_wrapper: bool) -> Type { match self { - MsgType::Exec => parse_quote! { ContractExecMsg #generics }, - MsgType::Query => parse_quote! { ContractQueryMsg #generics }, - MsgType::Instantiate => parse_quote! { InstantiateMsg #generics }, - MsgType::Migrate => parse_quote! { MigrateMsg #generics }, - MsgType::Reply => parse_quote! { ReplyMsg #generics }, + MsgType::Exec if is_wrapper => parse_quote! { ContractExecMsg }, + MsgType::Query if is_wrapper => parse_quote! { ContractQueryMsg }, + MsgType::Exec => parse_quote! { ExecMsg }, + MsgType::Query => parse_quote! { QueryMsg }, + MsgType::Instantiate => parse_quote! { InstantiateMsg }, + MsgType::Migrate => parse_quote! { MigrateMsg }, + MsgType::Reply => parse_quote! { ReplyMsg }, MsgType::Sudo => todo!(), } } diff --git a/sylvia/tests/generics.rs b/sylvia/tests/generics.rs index a386124e..5b153111 100644 --- a/sylvia/tests/generics.rs +++ b/sylvia/tests/generics.rs @@ -80,26 +80,63 @@ pub mod non_generic { } pub mod generic_contract { - use cosmwasm_std::{CustomQuery, Response, StdResult}; + use cosmwasm_std::{Reply, Response, StdResult}; use serde::de::DeserializeOwned; use serde::Deserialize; - use sylvia::types::{CustomMsg, InstantiateCtx}; + use sylvia::types::{CustomMsg, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx}; use sylvia_derive::contract; - pub struct GenericContract(std::marker::PhantomData<(Msg, QueryRet)>); + pub struct GenericContract( + std::marker::PhantomData<( + InstantiateParam, + ExecParam, + QueryParam, + MigrateParam, + RetType, + )>, + ); #[contract] - impl GenericContract + impl + GenericContract where - for<'msg_de> Msg: CustomMsg + Deserialize<'msg_de> + 'msg_de, - for<'a> QueryRet: CustomQuery + DeserializeOwned + 'a, + for<'msg_de> InstantiateParam: CustomMsg + Deserialize<'msg_de> + 'msg_de, + for<'exec> ExecParam: CustomMsg + DeserializeOwned + 'exec, + for<'exec> QueryParam: CustomMsg + DeserializeOwned + 'exec, + for<'exec> MigrateParam: CustomMsg + DeserializeOwned + 'exec, + for<'ret> RetType: CustomMsg + DeserializeOwned + 'ret, { pub const fn new() -> Self { Self(std::marker::PhantomData) } #[msg(instantiate)] - pub fn instantiate(&self, _ctx: InstantiateCtx, _msg: Msg) -> StdResult { + pub fn instantiate( + &self, + _ctx: InstantiateCtx, + _msg: InstantiateParam, + ) -> StdResult { + Ok(Response::new()) + } + + #[msg(exec)] + pub fn execute(&self, _ctx: ExecCtx, _msg: ExecParam) -> StdResult { + Ok(Response::new()) + } + + #[msg(query)] + pub fn query(&self, _ctx: QueryCtx, _msg: QueryParam) -> StdResult { + Ok(Response::new()) + } + + #[msg(migrate)] + pub fn migrate(&self, _ctx: MigrateCtx, _msg: MigrateParam) -> StdResult { + Ok(Response::new()) + } + + #[allow(dead_code)] + #[msg(reply)] + fn reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { Ok(Response::new()) } } @@ -327,18 +364,31 @@ mod tests { #[test] fn generic_contract() { + use crate::generic_contract::multitest_utils::CodeId; let app = App::default(); - let code_id = crate::generic_contract::multitest_utils::CodeId::store_code::< + let code_id: CodeId< + cw_multi_test::BasicApp, + ExternalMsg, ExternalMsg, - ExternalQuery, - >(&app); + ExternalMsg, + crate::ExternalMsg, + crate::ExternalMsg, + > = CodeId::store_code(&app); let owner = "owner"; - code_id + let contract = code_id .instantiate(ExternalMsg {}) .with_label("GenericContract") + .with_admin(owner) .call(owner) .unwrap(); + + contract.execute(ExternalMsg).call(owner).unwrap(); + contract.query(ExternalMsg).unwrap(); + contract + .migrate(ExternalMsg) + .call(owner, code_id.code_id()) + .unwrap(); } } From dfa4f54531f321b2dda2128f6948d7416d823b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wo=C5=BAniak?= Date: Mon, 23 Oct 2023 13:38:54 +0200 Subject: [PATCH 2/3] feat: Move `messages` method out of EnumMsg --- examples/interfaces/cw4/src/lib.rs | 4 ++-- sylvia-derive/src/interfaces.rs | 31 +++++++++-------------------- sylvia-derive/src/message.rs | 32 +++++++++++++++++++----------- sylvia-derive/src/parser.rs | 14 ++++++------- 4 files changed, 38 insertions(+), 43 deletions(-) diff --git a/examples/interfaces/cw4/src/lib.rs b/examples/interfaces/cw4/src/lib.rs index cc730746..4b27dcde 100644 --- a/examples/interfaces/cw4/src/lib.rs +++ b/examples/interfaces/cw4/src/lib.rs @@ -84,7 +84,7 @@ mod tests { #[test] fn exec_msgs() { assert_eq!( - Cw4ExecMsg::messages(), + execute_messages(), ["add_hook", "remove_hook", "update_admin", "update_members"] ); } @@ -92,7 +92,7 @@ mod tests { #[test] fn query_msgs() { assert_eq!( - Cw4QueryMsg::messages(), + query_messages(), ["admin", "hooks", "list_members", "member", "total_weight"] ); } diff --git a/sylvia-derive/src/interfaces.rs b/sylvia-derive/src/interfaces.rs index 09559c70..877edebf 100644 --- a/sylvia-derive/src/interfaces.rs +++ b/sylvia-derive/src/interfaces.rs @@ -106,45 +106,32 @@ impl Interfaces { } pub fn emit_messages_call(&self, msg_ty: &MsgType) -> Vec { - let sylvia = crate_module(); - self.interfaces .iter() .map(|interface| { - let ContractMessageAttr { - module, generics, .. - } = interface; - let generics = if !generics.is_empty() { - quote! { < #generics > } - } else { - quote! {} - }; - let type_name = msg_ty.as_accessor_name(); + let ContractMessageAttr { module, .. } = interface; + + let ep_name = msg_ty.emit_ep_name(); + let messages_fn_name = Ident::new(&format!("{}_messages", ep_name), module.span()); quote! { - &<#module :: InterfaceTypes #generics as #sylvia ::types::InterfaceMessages> :: #type_name :: messages() + &#module :: #messages_fn_name() } }) .collect() } pub fn emit_deserialization_attempts(&self, msg_ty: &MsgType) -> Vec { - let sylvia = crate_module(); - self.interfaces .iter() .map(|interface| { let ContractMessageAttr { - module, variant, generics, .. + module, variant, .. } = interface; - let generics = if !generics.is_empty() { - quote! { < #generics > } - } else { - quote! {} - }; + let ep_name = msg_ty.emit_ep_name(); + let messages_fn_name = Ident::new(&format!("{}_messages", ep_name), module.span()); - let type_name = msg_ty.as_accessor_name(); quote! { - let msgs = &<#module :: InterfaceTypes #generics as #sylvia ::types::InterfaceMessages> :: #type_name :: messages(); + let msgs = &#module :: #messages_fn_name(); if msgs.into_iter().any(|msg| msg == &recv_msg_name) { match val.deserialize_into() { Ok(msg) => return Ok(Self:: #variant (msg)), diff --git a/sylvia-derive/src/message.rs b/sylvia-derive/src/message.rs index a067a7c3..4af19680 100644 --- a/sylvia-derive/src/message.rs +++ b/sylvia-derive/src/message.rs @@ -293,6 +293,9 @@ impl<'a> EnumMessage<'a> { let unique_enum_name = Ident::new(&format!("{}{}", trait_name, name), name.span()); + let ep_name = msg_ty.emit_ep_name(); + let messages_fn_name = Ident::new(&format!("{}_messages", ep_name), name.span()); + #[cfg(not(tarpaulin_include))] let enum_declaration = match name.to_string().as_str() { "QueryMsg" => { @@ -336,11 +339,12 @@ impl<'a> EnumMessage<'a> { #match_arms } } - pub const fn messages() -> [&'static str; #msgs_cnt] { - [#(#msgs,)*] - } #(#variants_constructors)* } + + pub const fn #messages_fn_name () -> [&'static str; #msgs_cnt] { + [#(#msgs,)*] + } } } } @@ -411,6 +415,9 @@ impl<'a> ContractEnumMessage<'a> { _ => quote! {}, }; + let ep_name = msg_ty.emit_ep_name(); + let messages_fn_name = Ident::new(&format!("{}_messages", ep_name), contract.span()); + #[cfg(not(tarpaulin_include))] { quote! { @@ -429,12 +436,13 @@ impl<'a> ContractEnumMessage<'a> { #(#match_arms,)* } } - pub const fn messages() -> [&'static str; #variants_cnt] { - [#(#variant_names,)*] - } #(#variants_constructors)* } + + pub const fn #messages_fn_name () -> [&'static str; #variants_cnt] { + [#(#variant_names,)*] + } } } } @@ -1265,6 +1273,8 @@ impl<'a> GlueMessage<'a> { let variants = interfaces.emit_glue_message_variants(msg_ty); + let ep_name = msg_ty.emit_ep_name(); + let messages_fn_name = Ident::new(&format!("{}_messages", ep_name), contract.span()); let contract_variant = quote! { #contract_name ( #enum_name #bracketed_used_generics ) }; let mut messages_call = interfaces.emit_messages_call(msg_ty); let prefixed_used_generics = if !used_generics.is_empty() { @@ -1272,7 +1282,7 @@ impl<'a> GlueMessage<'a> { } else { quote! {} }; - messages_call.push(quote! { &#enum_name #prefixed_used_generics :: messages() }); + messages_call.push(quote! { &#messages_fn_name() }); let variants_cnt = messages_call.len(); @@ -1310,7 +1320,7 @@ impl<'a> GlueMessage<'a> { #[cfg(not(tarpaulin_include))] let contract_deserialization_attempt = quote! { - let msgs = &#enum_name #prefixed_used_generics :: messages(); + let msgs = &#messages_fn_name(); if msgs.into_iter().any(|msg| msg == &recv_msg_name) { match val.deserialize_into() { Ok(msg) => return Ok(Self:: #contract_name (msg)), @@ -1363,12 +1373,10 @@ impl<'a> GlueMessage<'a> { contract: &#contract, ctx: #ctx_type, ) -> #ret_type #full_where_clause { - const fn assert_no_intersection #bracketed_used_generics () #where_clause { + const _: () = { let msgs: [&[&str]; #variants_cnt] = [#(#messages_call),*]; #sylvia ::utils::assert_no_intersection(msgs); - } - - assert_no_intersection #prefixed_used_generics (); + }; match self { #(#dispatch_arms,)* diff --git a/sylvia-derive/src/parser.rs b/sylvia-derive/src/parser.rs index 864eb183..836ed6df 100644 --- a/sylvia-derive/src/parser.rs +++ b/sylvia-derive/src/parser.rs @@ -105,16 +105,16 @@ impl MsgType { } #[cfg(not(tarpaulin_include))] - pub fn emit_ep_name(self) -> TokenStream { + pub fn emit_ep_name(self) -> Ident { use MsgType::*; match self { - Exec => quote! { execute }, - Instantiate => quote! { instantiate }, - Migrate => quote! { migrate }, - Sudo => quote! { sudo }, - Reply => quote! { reply }, - Query => quote! { query }, + Exec => parse_quote! { execute }, + Instantiate => parse_quote! { instantiate }, + Migrate => parse_quote! { migrate }, + Sudo => parse_quote! { sudo }, + Reply => parse_quote! { reply }, + Query => parse_quote! { query }, } } From 47423850515607fdee7a2585e1110aa0f362dfdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wo=C5=BAniak?= Date: Mon, 23 Oct 2023 15:21:54 +0200 Subject: [PATCH 3/3] test: Move generic contracts and interfaces to examples --- .github/workflows/ci.yml | 26 ++ examples/Cargo.lock | 55 +++ examples/Cargo.toml | 28 +- .../contracts/generic_contract/.cargo/config | 6 + .../contracts/generic_contract/Cargo.toml | 31 ++ .../generic_contract/src/bin/schema.rs | 14 + .../generic_contract/src/contract.rs | 98 +++++ .../contracts/generic_contract/src/lib.rs | 1 + .../generic_iface_on_contract/.cargo/config | 6 + .../generic_iface_on_contract/Cargo.toml | 34 ++ .../src/bin/schema.rs | 12 + .../generic_iface_on_contract/src/contract.rs | 81 ++++ .../src/custom_and_generic.rs | 31 ++ .../generic_iface_on_contract/src/cw1.rs | 26 ++ .../generic_iface_on_contract/src/generic.rs | 35 ++ .../generic_iface_on_contract/src/lib.rs | 4 + .../interfaces/custom-and-generic/Cargo.toml | 22 + .../interfaces/custom-and-generic/src/lib.rs | 69 +++ examples/interfaces/generic/Cargo.toml | 22 + examples/interfaces/generic/src/lib.rs | 61 +++ sylvia-derive/src/multitest.rs | 14 +- sylvia/src/types.rs | 6 + sylvia/tests/generics.rs | 394 ------------------ 23 files changed, 663 insertions(+), 413 deletions(-) create mode 100644 examples/contracts/generic_contract/.cargo/config create mode 100644 examples/contracts/generic_contract/Cargo.toml create mode 100644 examples/contracts/generic_contract/src/bin/schema.rs create mode 100644 examples/contracts/generic_contract/src/contract.rs create mode 100644 examples/contracts/generic_contract/src/lib.rs create mode 100644 examples/contracts/generic_iface_on_contract/.cargo/config create mode 100644 examples/contracts/generic_iface_on_contract/Cargo.toml create mode 100644 examples/contracts/generic_iface_on_contract/src/bin/schema.rs create mode 100644 examples/contracts/generic_iface_on_contract/src/contract.rs create mode 100644 examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs create mode 100644 examples/contracts/generic_iface_on_contract/src/cw1.rs create mode 100644 examples/contracts/generic_iface_on_contract/src/generic.rs create mode 100644 examples/contracts/generic_iface_on_contract/src/lib.rs create mode 100644 examples/interfaces/custom-and-generic/Cargo.toml create mode 100644 examples/interfaces/custom-and-generic/src/lib.rs create mode 100644 examples/interfaces/generic/Cargo.toml create mode 100644 examples/interfaces/generic/src/lib.rs delete mode 100644 sylvia/tests/generics.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31b3ba7e..8eb303fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,6 +66,7 @@ jobs: run: cargo clippy --all-targets -- -D warnings - name: Fmt check project run: cargo fmt --check + - name: Test examples working-directory: examples run: cargo test --locked @@ -75,6 +76,7 @@ jobs: - name: Fmt check examples working-directory: examples run: cargo fmt --check + - name: Build cw20-base example working-directory: examples/contracts/cw20-base run: cargo build --release --target wasm32-unknown-unknown --locked --lib @@ -90,10 +92,18 @@ jobs: - name: Build custom working-directory: examples/contracts/custom run: cargo build --release --target wasm32-unknown-unknown --locked --lib + - name: Build generic_contract + working-directory: examples/contracts/generic_contract + run: cargo build --release --target wasm32-unknown-unknown --locked --lib + - name: Build generic_iface_on_contract + working-directory: examples/contracts/generic_iface_on_contract + run: cargo build --release --target wasm32-unknown-unknown --locked --lib + - name: Install cosmwasm-check run: cargo install cosmwasm-check --force - name: Check contracts run: find examples/target/wasm32-unknown-unknown/release/ -type f -name "*.wasm" -exec cosmwasm-check {} \; + - name: Cw1-whitelist schema working-directory: examples/contracts/cw1-whitelist/ run: cargo schema @@ -109,6 +119,13 @@ jobs: - name: Custom schema working-directory: examples/contracts/custom run: cargo schema + - name: Generic_contract schema + working-directory: examples/contracts/generic_contract + run: cargo schema + - name: generic_iface_on_contract schema + working-directory: examples/contracts/generic_iface_on_contract + run: cargo schema + - name: Cw1-whitelist ts-codegen working-directory: examples/contracts/cw1-whitelist/ run: cosmwasm-ts-codegen generate --plugin client --schema ./schema --out ./ts --name cw1-whitelist --no-bundle @@ -124,6 +141,13 @@ jobs: - name: Custom ts-codegen working-directory: examples/contracts/custom/ run: cosmwasm-ts-codegen generate --plugin client --schema ./schema --out ./ts --name custom --no-bundle + - name: Generic_contract ts-codegen + working-directory: examples/contracts/generic_contract/ + run: cosmwasm-ts-codegen generate --plugin client --schema ./schema --out ./ts --name custom --no-bundle + - name: Generic_iface_on_contract ts-codegen + working-directory: examples/contracts/generic_iface_on_contract + run: cosmwasm-ts-codegen generate --plugin client --schema ./schema --out ./ts --name custom --no-bundle + - name: Archive schema artifats uses: actions/upload-artifact@v3 with: @@ -134,6 +158,8 @@ jobs: examples/contracts/cw20-base/schema/cw20-base.json examples/contracts/entry-points-overriding/schema/entry-points-overriding.json examples/contracts/custom/schema/custom.json + examples/contracts/generic_contract/schema/generic_contract.json + examples/contracts/generic_iface_on_contract/schema/generic_iface_on_contract.json coverage: name: Code coverage diff --git a/examples/Cargo.lock b/examples/Cargo.lock index fd957db0..74bcaebe 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -241,6 +241,18 @@ dependencies = [ "sylvia", ] +[[package]] +name = "custom-and-generic" +version = "0.5.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "serde", + "sylvia", +] + [[package]] name = "cw-multi-test" version = "0.16.5" @@ -549,6 +561,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" +[[package]] +name = "generic" +version = "0.5.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "serde", + "sylvia", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -559,6 +583,37 @@ dependencies = [ "version_check", ] +[[package]] +name = "generic_contract" +version = "0.5.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus", + "cw-utils", + "serde", + "sylvia", +] + +[[package]] +name = "generic_iface_on_contract" +version = "0.5.0" +dependencies = [ + "anyhow", + "cosmwasm-schema", + "cosmwasm-std", + "custom-and-generic", + "cw-multi-test", + "cw-storage-plus", + "cw-utils", + "cw1", + "generic", + "serde", + "sylvia", +] + [[package]] name = "getrandom" version = "0.2.10" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index a8a0e3ad..f5b26b33 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,18 +1,22 @@ [workspace] members = [ - # Contract intefaces - "interfaces/cw1", - "interfaces/cw4", - "interfaces/cw20-allowances", - "interfaces/cw20-minting", - "interfaces/cw20-marketing", + # Contract intefaces + "interfaces/cw1", + "interfaces/cw4", + "interfaces/cw20-allowances", + "interfaces/cw20-minting", + "interfaces/cw20-marketing", + "interfaces/custom-and-generic", + "interfaces/generic", - # Contracts - "contracts/cw1-whitelist", - "contracts/cw1-subkeys", - "contracts/cw20-base", - "contracts/entry-points-overriding", - "contracts/custom", + # Contracts + "contracts/cw1-whitelist", + "contracts/cw1-subkeys", + "contracts/cw20-base", + "contracts/entry-points-overriding", + "contracts/custom", + "contracts/generic_contract", + "contracts/generic_iface_on_contract", ] resolver = "2" diff --git a/examples/contracts/generic_contract/.cargo/config b/examples/contracts/generic_contract/.cargo/config new file mode 100644 index 00000000..d8ab80fe --- /dev/null +++ b/examples/contracts/generic_contract/.cargo/config @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown --lib" +wasm-debug = "build --target wasm32-unknown-unknown --lib" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --bin schema" diff --git a/examples/contracts/generic_contract/Cargo.toml b/examples/contracts/generic_contract/Cargo.toml new file mode 100644 index 00000000..a34ded2b --- /dev/null +++ b/examples/contracts/generic_contract/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "generic_contract" +version = { workspace = true } +authors = ["Jan Woźniak "] +edition = { workspace = true } +description = "Example of generic contract" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/sylvia" +homepage = "https://cosmwasm.com" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +library = [] +tests = ["library", "cw-multi-test", "anyhow"] + +[dependencies] +anyhow = { version = "1.0", optional = true } +cosmwasm-schema = "1.2" +cosmwasm-std = { version = "1.3", features = ["staking"] } +cw-multi-test = { version = "0.16", optional = true } +cw-storage-plus = "1.0" +cw-utils = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sylvia = { path = "../../../sylvia" } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.16" +sylvia = { path = "../../../sylvia", features = ["mt"] } diff --git a/examples/contracts/generic_contract/src/bin/schema.rs b/examples/contracts/generic_contract/src/bin/schema.rs new file mode 100644 index 00000000..a8828949 --- /dev/null +++ b/examples/contracts/generic_contract/src/bin/schema.rs @@ -0,0 +1,14 @@ +use cosmwasm_schema::write_api; + +#[cfg(not(tarpaulin_include))] +fn main() { + use generic_contract::contract::{ + ContractExecMsg, ContractQueryMsg, ExternalMsg, InstantiateMsg, + }; + + write_api! { + instantiate: InstantiateMsg, + execute: ContractExecMsg, + query: ContractQueryMsg, + } +} diff --git a/examples/contracts/generic_contract/src/contract.rs b/examples/contracts/generic_contract/src/contract.rs new file mode 100644 index 00000000..9ef1ee4b --- /dev/null +++ b/examples/contracts/generic_contract/src/contract.rs @@ -0,0 +1,98 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Reply, Response, StdResult}; +use serde::de::DeserializeOwned; +use serde::Deserialize; +use sylvia::types::{CustomMsg, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx}; +use sylvia::{contract, schemars}; + +#[cw_serde] +pub struct ExternalMsg; +impl cosmwasm_std::CustomMsg for ExternalMsg {} + +pub struct GenericContract( + std::marker::PhantomData<( + InstantiateParam, + ExecParam, + QueryParam, + MigrateParam, + RetType, + )>, +); + +#[contract] +impl + GenericContract +where + for<'msg_de> InstantiateParam: CustomMsg + Deserialize<'msg_de> + 'msg_de, + ExecParam: CustomMsg + DeserializeOwned + 'static, + QueryParam: CustomMsg + DeserializeOwned + 'static, + MigrateParam: CustomMsg + DeserializeOwned + 'static, + RetType: CustomMsg + DeserializeOwned + 'static, +{ + pub const fn new() -> Self { + Self(std::marker::PhantomData) + } + + #[msg(instantiate)] + pub fn instantiate(&self, _ctx: InstantiateCtx, _msg: InstantiateParam) -> StdResult { + Ok(Response::new()) + } + + #[msg(exec)] + pub fn execute(&self, _ctx: ExecCtx, _msg: ExecParam) -> StdResult { + Ok(Response::new()) + } + + #[msg(query)] + pub fn query(&self, _ctx: QueryCtx, _msg: QueryParam) -> StdResult { + Ok(Response::new()) + } + + #[msg(migrate)] + pub fn migrate(&self, _ctx: MigrateCtx, _msg: MigrateParam) -> StdResult { + Ok(Response::new()) + } + + #[allow(dead_code)] + #[msg(reply)] + fn reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { + Ok(Response::new()) + } +} + +#[cfg(test)] +mod tests { + use sylvia::multitest::App; + + use crate::contract::ExternalMsg; + + #[test] + fn generic_contract() { + use super::multitest_utils::CodeId; + let app = App::default(); + let code_id: CodeId< + ExternalMsg, + ExternalMsg, + ExternalMsg, + super::ExternalMsg, + super::ExternalMsg, + _, + > = CodeId::store_code(&app); + + let owner = "owner"; + + let contract = code_id + .instantiate(ExternalMsg {}) + .with_label("GenericContract") + .with_admin(owner) + .call(owner) + .unwrap(); + + contract.execute(ExternalMsg).call(owner).unwrap(); + contract.query(ExternalMsg).unwrap(); + contract + .migrate(ExternalMsg) + .call(owner, code_id.code_id()) + .unwrap(); + } +} diff --git a/examples/contracts/generic_contract/src/lib.rs b/examples/contracts/generic_contract/src/lib.rs new file mode 100644 index 00000000..2943dbb5 --- /dev/null +++ b/examples/contracts/generic_contract/src/lib.rs @@ -0,0 +1 @@ +pub mod contract; diff --git a/examples/contracts/generic_iface_on_contract/.cargo/config b/examples/contracts/generic_iface_on_contract/.cargo/config new file mode 100644 index 00000000..d8ab80fe --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/.cargo/config @@ -0,0 +1,6 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown --lib" +wasm-debug = "build --target wasm32-unknown-unknown --lib" +unit-test = "test --lib" +integration-test = "test --test integration" +schema = "run --bin schema" diff --git a/examples/contracts/generic_iface_on_contract/Cargo.toml b/examples/contracts/generic_iface_on_contract/Cargo.toml new file mode 100644 index 00000000..99ad5aa1 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "generic_iface_on_contract" +version = { workspace = true } +authors = ["Jan Woźniak "] +edition = { workspace = true } +description = "Generic interfaces implemented on non generic contract" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/sylvia" +homepage = "https://cosmwasm.com" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +library = [] +tests = ["library", "cw-multi-test", "anyhow"] + +[dependencies] +anyhow = { version = "1.0", optional = true } +cosmwasm-schema = "1.2" +cosmwasm-std = { version = "1.3", features = ["staking"] } +cw-multi-test = { version = "0.16", optional = true } +cw-storage-plus = "1.0" +cw-utils = "1.0" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sylvia = { path = "../../../sylvia" } +cw1 = { path = "../../interfaces/cw1" } +generic = { path = "../../interfaces/generic" } +custom-and-generic = { path = "../../interfaces/custom-and-generic" } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.16" +sylvia = { path = "../../../sylvia", features = ["mt"] } diff --git a/examples/contracts/generic_iface_on_contract/src/bin/schema.rs b/examples/contracts/generic_iface_on_contract/src/bin/schema.rs new file mode 100644 index 00000000..8a7c48e5 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/bin/schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +#[cfg(not(tarpaulin_include))] +fn main() { + use generic_iface_on_contract::contract::{ContractExecMsg, ContractQueryMsg, InstantiateMsg}; + + write_api! { + instantiate: InstantiateMsg, + execute: ContractExecMsg, + query: ContractQueryMsg, + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/contract.rs b/examples/contracts/generic_iface_on_contract/src/contract.rs new file mode 100644 index 00000000..f35d0d69 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/contract.rs @@ -0,0 +1,81 @@ +use cosmwasm_std::{Response, StdResult}; +use sylvia::types::{InstantiateCtx, SvCustomMsg}; +use sylvia::{contract, schemars}; + +pub struct NonGenericContract; + +#[contract] +#[messages(generic as Generic: custom(msg))] +#[messages(custom_and_generic as CustomAndGeneric)] +#[messages(cw1 as Cw1: custom(msg))] +/// Required if interface returns generic `Response` +#[sv::custom(msg=SvCustomMsg)] +impl NonGenericContract { + pub const fn new() -> Self { + Self + } + + #[msg(instantiate)] + pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult> { + Ok(Response::new()) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{CosmosMsg, Empty}; + use sylvia::{multitest::App, types::SvCustomMsg}; + + use super::NonGenericContract; + use crate::custom_and_generic::test_utils::CustomAndGeneric; + use crate::cw1::test_utils::Cw1; + use crate::generic::test_utils::Generic; + + #[test] + fn mt_helpers() { + let _ = NonGenericContract::new(); + let app = App::>::custom(|_, _, _| {}); + let code_id = super::multitest_utils::CodeId::store_code(&app); + + let owner = "owner"; + + let contract = code_id + .instantiate() + .with_label("Cw1Contract") + .call(owner) + .unwrap(); + + // Non custom non generic interface + contract + .cw1_proxy() + .can_execute("sender".to_owned(), CosmosMsg::Custom(Empty {})) + .unwrap(); + contract + .cw1_proxy() + .execute(vec![CosmosMsg::Custom(Empty {})]) + .call(owner) + .unwrap(); + + // Non-Custom generic Interface + contract + .generic_proxy() + .generic_query(SvCustomMsg {}) + .unwrap(); + contract + .generic_proxy() + .generic_exec(vec![CosmosMsg::Custom(SvCustomMsg {})]) + .call(owner) + .unwrap(); + + // Custom generic Interface + contract + .custom_and_generic_proxy() + .custom_generic_query(SvCustomMsg {}) + .unwrap(); + contract + .custom_and_generic_proxy() + .custom_generic_execute(vec![CosmosMsg::Custom(SvCustomMsg {})]) + .call(owner) + .unwrap(); + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs b/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs new file mode 100644 index 00000000..90052b86 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/custom_and_generic.rs @@ -0,0 +1,31 @@ +use cosmwasm_std::{CosmosMsg, Response, StdError, StdResult}; +use custom_and_generic::CustomAndGeneric; +use sylvia::contract; +use sylvia::types::{ExecCtx, QueryCtx, SvCustomMsg}; + +#[contract(module = crate::contract)] +#[messages(custom_and_generic as CustomAndGeneric)] +#[sv::custom(msg=sylvia::types::SvCustomMsg)] +impl CustomAndGeneric + for crate::contract::NonGenericContract +{ + type Error = StdError; + + #[msg(exec)] + fn custom_generic_execute( + &self, + _ctx: ExecCtx, + _msgs: Vec>, + ) -> StdResult> { + Ok(Response::new()) + } + + #[msg(query)] + fn custom_generic_query( + &self, + _ctx: QueryCtx, + _msg: sylvia::types::SvCustomMsg, + ) -> StdResult { + Ok(SvCustomMsg {}) + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/cw1.rs b/examples/contracts/generic_iface_on_contract/src/cw1.rs new file mode 100644 index 00000000..8d45bc0d --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/cw1.rs @@ -0,0 +1,26 @@ +use cosmwasm_std::{CosmosMsg, Response, StdError, StdResult}; +use cw1::{CanExecuteResp, Cw1}; +use sylvia::contract; +use sylvia::types::{ExecCtx, QueryCtx}; + +#[contract(module = crate::contract)] +#[messages(cw1 as Cw1)] +#[sv::custom(msg=sylvia::types::SvCustomMsg)] +impl Cw1 for crate::contract::NonGenericContract { + type Error = StdError; + + #[msg(exec)] + fn execute(&self, _ctx: ExecCtx, _msgs: Vec) -> StdResult { + Ok(Response::new()) + } + + #[msg(query)] + fn can_execute( + &self, + _ctx: QueryCtx, + _sender: String, + _msg: CosmosMsg, + ) -> StdResult { + Ok(CanExecuteResp::default()) + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/generic.rs b/examples/contracts/generic_iface_on_contract/src/generic.rs new file mode 100644 index 00000000..d13827a0 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/generic.rs @@ -0,0 +1,35 @@ +use cosmwasm_std::{CosmosMsg, Response, StdError, StdResult}; +use generic::Generic; +use sylvia::contract; +use sylvia::types::{ExecCtx, QueryCtx, SvCustomMsg}; + +#[contract(module = crate::contract)] +#[messages(generic as Generic)] +#[sv::custom(msg = SvCustomMsg)] +impl Generic + for crate::contract::NonGenericContract +{ + type Error = StdError; + + #[msg(exec)] + fn generic_exec( + &self, + _ctx: ExecCtx, + _msgs: Vec>, + ) -> StdResult { + Ok(Response::new()) + } + + // Sylvia will fail if single type is used to match against two different generics + // It's because we have to map unique generics used as they can be used multiple times. + // If for some reason like here one type would be used in place of two generics either full + // path or some alias has to be used. + #[msg(query)] + fn generic_query( + &self, + _ctx: QueryCtx, + _msg: sylvia::types::SvCustomMsg, + ) -> StdResult { + Ok(SvCustomMsg {}) + } +} diff --git a/examples/contracts/generic_iface_on_contract/src/lib.rs b/examples/contracts/generic_iface_on_contract/src/lib.rs new file mode 100644 index 00000000..ea8b3738 --- /dev/null +++ b/examples/contracts/generic_iface_on_contract/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod custom_and_generic; +pub mod cw1; +pub mod generic; diff --git a/examples/interfaces/custom-and-generic/Cargo.toml b/examples/interfaces/custom-and-generic/Cargo.toml new file mode 100644 index 00000000..c1f38f99 --- /dev/null +++ b/examples/interfaces/custom-and-generic/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "custom-and-generic" +version = { workspace = true } +authors = ["Jan Woźniak "] +edition = { workspace = true } +description = "Interface with custom msg and generic support." +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/sylvia" +homepage = "https://cosmwasm.com" + +[features] +mt = ["sylvia/mt"] + +[dependencies] +cosmwasm-std = { version = "1.3", features = ["staking"] } +cosmwasm-schema = "1.2" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sylvia = { path = "../../../sylvia" } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.16" diff --git a/examples/interfaces/custom-and-generic/src/lib.rs b/examples/interfaces/custom-and-generic/src/lib.rs new file mode 100644 index 00000000..134eb77e --- /dev/null +++ b/examples/interfaces/custom-and-generic/src/lib.rs @@ -0,0 +1,69 @@ +use cosmwasm_std::{CosmosMsg, CustomMsg, Response, StdError}; + +use serde::de::DeserializeOwned; +use serde::Deserialize; +use sylvia::types::{ExecCtx, QueryCtx}; +use sylvia::{interface, schemars}; + +#[interface] +#[sv::custom(msg=RetType)] +pub trait CustomAndGeneric +where + for<'msg_de> ExecParam: CustomMsg + Deserialize<'msg_de>, + QueryParam: sylvia::types::CustomMsg, + RetType: CustomMsg + DeserializeOwned, +{ + type Error: From; + + #[msg(exec)] + fn custom_generic_execute( + &self, + ctx: ExecCtx, + msgs: Vec>, + ) -> Result, Self::Error>; + + #[msg(query)] + fn custom_generic_query( + &self, + ctx: QueryCtx, + param: QueryParam, + ) -> Result; +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::{Addr, CosmosMsg, Empty, QuerierWrapper}; + use sylvia::types::{InterfaceMessages, SvCustomMsg}; + + use crate::Querier; + + #[test] + fn construct_messages() { + let contract = Addr::unchecked("contract"); + + // Direct message construction + let _ = super::QueryMsg::<_, Empty>::custom_generic_query(SvCustomMsg {}); + let _ = super::ExecMsg::custom_generic_execute(vec![CosmosMsg::Custom(SvCustomMsg {})]); + let _ = super::ExecMsg::custom_generic_execute(vec![CosmosMsg::Custom(SvCustomMsg {})]); + + // Querier + let deps = mock_dependencies(); + let querier_wrapper: QuerierWrapper = QuerierWrapper::new(&deps.querier); + + let querier = super::BoundQuerier::borrowed(&contract, &querier_wrapper); + let _: Result = + super::Querier::custom_generic_query(&querier, SvCustomMsg {}); + let _: Result = querier.custom_generic_query(SvCustomMsg {}); + + // Construct messages with Interface extension + let _ = + as InterfaceMessages>::Query::custom_generic_query( + SvCustomMsg {}, + ); + let _= + as InterfaceMessages>::Exec::custom_generic_execute( + vec![ CosmosMsg::Custom(SvCustomMsg{}), + ]); + } +} diff --git a/examples/interfaces/generic/Cargo.toml b/examples/interfaces/generic/Cargo.toml new file mode 100644 index 00000000..c5832b18 --- /dev/null +++ b/examples/interfaces/generic/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "generic" +version = { workspace = true } +authors = ["Jan Woźniak "] +edition = { workspace = true } +description = "Interface with generic support." +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/sylvia" +homepage = "https://cosmwasm.com" + +[features] +mt = ["sylvia/mt"] + +[dependencies] +cosmwasm-std = { version = "1.3", features = ["staking"] } +cosmwasm-schema = "1.2" +serde = { version = "1.0", default-features = false, features = ["derive"] } +sylvia = { path = "../../../sylvia" } + +[dev-dependencies] +anyhow = "1.0" +cw-multi-test = "0.16" diff --git a/examples/interfaces/generic/src/lib.rs b/examples/interfaces/generic/src/lib.rs new file mode 100644 index 00000000..042db389 --- /dev/null +++ b/examples/interfaces/generic/src/lib.rs @@ -0,0 +1,61 @@ +use cosmwasm_std::{CosmosMsg, CustomMsg, Response, StdError}; + +use serde::{de::DeserializeOwned, Deserialize}; +use sylvia::types::{ExecCtx, QueryCtx}; +use sylvia::{interface, schemars}; + +#[interface] +pub trait Generic +where + for<'msg_de> ExecParam: CustomMsg + Deserialize<'msg_de>, + QueryParam: sylvia::types::CustomMsg, + RetType: CustomMsg + DeserializeOwned, +{ + type Error: From; + + #[msg(exec)] + fn generic_exec( + &self, + ctx: ExecCtx, + msgs: Vec>, + ) -> Result; + + #[msg(query)] + fn generic_query(&self, ctx: QueryCtx, param: QueryParam) -> Result; +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{testing::mock_dependencies, Addr, CosmosMsg, Empty, QuerierWrapper}; + use sylvia::types::{InterfaceMessages, SvCustomMsg}; + + use crate::Querier; + + #[test] + fn construct_messages() { + let contract = Addr::unchecked("contract"); + + // Direct message construction + let _ = super::QueryMsg::<_, Empty>::generic_query(SvCustomMsg {}); + let _ = super::ExecMsg::generic_exec(vec![CosmosMsg::Custom(SvCustomMsg {})]); + let _ = super::ExecMsg::generic_exec(vec![CosmosMsg::Custom(SvCustomMsg {})]); + + // Querier + let deps = mock_dependencies(); + let querier_wrapper: QuerierWrapper = QuerierWrapper::new(&deps.querier); + + let querier = super::BoundQuerier::borrowed(&contract, &querier_wrapper); + let _: Result = super::Querier::generic_query(&querier, SvCustomMsg {}); + let _: Result = querier.generic_query(SvCustomMsg {}); + + // Construct messages with Interface extension + let _ = + as InterfaceMessages>::Query::generic_query( + SvCustomMsg {}, + ); + let _= + as InterfaceMessages>::Exec::generic_exec(vec![ + CosmosMsg::Custom(SvCustomMsg{}), + ]); + } +} diff --git a/sylvia-derive/src/multitest.rs b/sylvia-derive/src/multitest.rs index f958cd5c..b92cac65 100644 --- a/sylvia-derive/src/multitest.rs +++ b/sylvia-derive/src/multitest.rs @@ -455,14 +455,14 @@ where quote! { #impl_contract - pub struct CodeId<'app, MtApp, #(#generics,)* > { + pub struct CodeId<'app, #(#generics,)* MtApp> { code_id: u64, app: &'app #sylvia ::multitest::App, _phantom: std::marker::PhantomData<( #(#generics,)* )>, } - impl<'app, BankT, ApiT, StorageT, CustomT, StakingT, DistrT, IbcT, GovT, #(#generics,)* > CodeId<'app, #mt_app, #(#generics,)* > + impl<'app, BankT, ApiT, StorageT, CustomT, StakingT, DistrT, IbcT, GovT, #(#generics,)* > CodeId<'app, #(#generics,)* #mt_app > where BankT: #sylvia ::cw_multi_test::Bank, ApiT: #sylvia ::cw_std::Api, @@ -487,9 +487,9 @@ where pub fn instantiate( &self,#(#fields,)* - ) -> InstantiateProxy<'_, 'app, #mt_app, #(#generics,)* > { + ) -> InstantiateProxy<'_, 'app, #(#generics,)* #mt_app > { let msg = #instantiate_msg {#(#fields_names,)*}; - InstantiateProxy::<_, #(#generics,)* > { + InstantiateProxy::< #(#generics,)* _> { code_id: self, funds: &[], label: "Contract", @@ -499,15 +499,15 @@ where } } - pub struct InstantiateProxy<'proxy, 'app, MtApp, #(#generics,)* > { - code_id: &'proxy CodeId <'app, MtApp, #(#generics,)* >, + pub struct InstantiateProxy<'proxy, 'app, #(#generics,)* MtApp> { + code_id: &'proxy CodeId <'app, #(#generics,)* MtApp>, funds: &'proxy [#sylvia ::cw_std::Coin], label: &'proxy str, admin: Option, msg: InstantiateMsg #bracketed_used_generics, } - impl<'proxy, 'app, MtApp, #(#generics,)* > InstantiateProxy<'proxy, 'app, MtApp, #(#generics,)* > + impl<'proxy, 'app, #(#generics,)* MtApp> InstantiateProxy<'proxy, 'app, #(#generics,)* MtApp> where MtApp: Executor< #custom_msg >, #where_predicates diff --git a/sylvia/src/types.rs b/sylvia/src/types.rs index c70ed7b3..059ede46 100644 --- a/sylvia/src/types.rs +++ b/sylvia/src/types.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::{CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo}; use serde::de::DeserializeOwned; @@ -99,6 +100,11 @@ pub trait CustomMsg: cosmwasm_std::CustomMsg + DeserializeOwned {} impl CustomMsg for T where T: cosmwasm_std::CustomMsg + DeserializeOwned {} +#[cw_serde] +pub struct SvCustomMsg; + +impl cosmwasm_std::CustomMsg for SvCustomMsg {} + pub trait InterfaceMessages { type Exec; type Query; diff --git a/sylvia/tests/generics.rs b/sylvia/tests/generics.rs deleted file mode 100644 index 5b153111..00000000 --- a/sylvia/tests/generics.rs +++ /dev/null @@ -1,394 +0,0 @@ -use cosmwasm_schema::cw_serde; - -pub mod cw1 { - use cosmwasm_std::{CosmosMsg, CustomMsg, CustomQuery, Response, StdError}; - - use serde::{de::DeserializeOwned, Deserialize}; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::interface; - - #[interface(module=msg)] - #[sv::custom(msg=Msg)] - pub trait Cw1 - where - for<'msg_de> Msg: CustomMsg + Deserialize<'msg_de>, - Param: sylvia::types::CustomMsg, - QueryRet: CustomQuery + DeserializeOwned, - { - type Error: From; - - #[msg(exec)] - fn execute( - &self, - ctx: ExecCtx, - msgs: Vec>, - ) -> Result, Self::Error>; - - #[msg(query)] - fn some_query(&self, ctx: QueryCtx, param: Param) -> Result; - } -} - -pub mod whitelist { - use cosmwasm_std::{CosmosMsg, CustomMsg, CustomQuery, Response, StdError}; - - use serde::de::DeserializeOwned; - use serde::Deserialize; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::interface; - - #[interface(module=msg)] - pub trait Whitelist - where - for<'msg_de> Msg: CustomMsg + Deserialize<'msg_de>, - QueryRet: CustomQuery + DeserializeOwned, - { - type Error: From; - - #[msg(exec)] - fn update_admins( - &self, - ctx: ExecCtx, - msgs: Vec>, - ) -> Result; - - #[msg(query)] - fn admins_list(&self, ctx: QueryCtx) -> Result; - } -} - -pub mod non_generic { - use cosmwasm_std::{CosmosMsg, Empty, Response, StdError}; - - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::interface; - - #[interface(module=msg)] - pub trait NonGeneric { - type Error: From; - - #[msg(exec)] - fn non_generic_exec( - &self, - ctx: ExecCtx, - msgs: Vec>, - ) -> Result; - - #[msg(query)] - fn non_generic_query(&self, ctx: QueryCtx) -> Result; - } -} - -pub mod generic_contract { - use cosmwasm_std::{Reply, Response, StdResult}; - use serde::de::DeserializeOwned; - use serde::Deserialize; - use sylvia::types::{CustomMsg, ExecCtx, InstantiateCtx, MigrateCtx, QueryCtx, ReplyCtx}; - use sylvia_derive::contract; - - pub struct GenericContract( - std::marker::PhantomData<( - InstantiateParam, - ExecParam, - QueryParam, - MigrateParam, - RetType, - )>, - ); - - #[contract] - impl - GenericContract - where - for<'msg_de> InstantiateParam: CustomMsg + Deserialize<'msg_de> + 'msg_de, - for<'exec> ExecParam: CustomMsg + DeserializeOwned + 'exec, - for<'exec> QueryParam: CustomMsg + DeserializeOwned + 'exec, - for<'exec> MigrateParam: CustomMsg + DeserializeOwned + 'exec, - for<'ret> RetType: CustomMsg + DeserializeOwned + 'ret, - { - pub const fn new() -> Self { - Self(std::marker::PhantomData) - } - - #[msg(instantiate)] - pub fn instantiate( - &self, - _ctx: InstantiateCtx, - _msg: InstantiateParam, - ) -> StdResult { - Ok(Response::new()) - } - - #[msg(exec)] - pub fn execute(&self, _ctx: ExecCtx, _msg: ExecParam) -> StdResult { - Ok(Response::new()) - } - - #[msg(query)] - pub fn query(&self, _ctx: QueryCtx, _msg: QueryParam) -> StdResult { - Ok(Response::new()) - } - - #[msg(migrate)] - pub fn migrate(&self, _ctx: MigrateCtx, _msg: MigrateParam) -> StdResult { - Ok(Response::new()) - } - - #[allow(dead_code)] - #[msg(reply)] - fn reply(&self, _ctx: ReplyCtx, _reply: Reply) -> StdResult { - Ok(Response::new()) - } - } -} - -pub mod cw1_contract { - use cosmwasm_std::{Response, StdResult}; - use sylvia::types::InstantiateCtx; - use sylvia_derive::contract; - - use crate::{ExternalMsg, ExternalQuery}; - - pub struct Cw1Contract; - - #[contract] - #[messages(crate::cw1 as Cw1)] - #[messages(crate::whitelist as Whitelist: custom(msg))] - #[messages(crate::non_generic as NonGeneric: custom(msg))] - /// Required if interface returns generic `Response` - #[sv::custom(msg=ExternalMsg)] - impl Cw1Contract { - pub const fn new() -> Self { - Self - } - - #[msg(instantiate)] - pub fn instantiate(&self, _ctx: InstantiateCtx) -> StdResult> { - Ok(Response::new()) - } - } -} - -pub mod impl_non_generic { - use crate::cw1_contract::Cw1Contract; - use crate::non_generic::NonGeneric; - use cosmwasm_std::{CosmosMsg, Empty, Response, StdError}; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::contract; - - #[contract(module = crate::cw1_contract)] - #[messages(crate::non_generic as NonGeneric)] - #[sv::custom(msg=crate::ExternalMsg)] - impl NonGeneric for Cw1Contract { - type Error = StdError; - - #[msg(exec)] - fn non_generic_exec( - &self, - _ctx: ExecCtx, - _msgs: Vec>, - ) -> Result { - Ok(Response::new()) - } - - #[msg(query)] - fn non_generic_query(&self, _ctx: QueryCtx) -> Result { - Ok(Response::default()) - } - } -} - -pub mod impl_whitelist { - use cosmwasm_std::{CosmosMsg, Response, StdError}; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::contract; - - use crate::cw1_contract::Cw1Contract; - use crate::whitelist::Whitelist; - use crate::{ExternalMsg, ExternalQuery}; - - #[contract(module = crate::cw1_contract)] - #[messages(crate::whitelist as Whitelist)] - #[sv::custom(msg=ExternalMsg)] - impl Whitelist for Cw1Contract { - type Error = StdError; - - #[msg(exec)] - fn update_admins( - &self, - _ctx: ExecCtx, - _msgs: Vec>, - ) -> Result { - Ok(Response::new()) - } - - #[msg(query)] - fn admins_list(&self, _ctx: QueryCtx) -> Result { - Ok(ExternalQuery {}) - } - } -} - -pub mod impl_cw1 { - use cosmwasm_std::{CosmosMsg, Response, StdError}; - use sylvia::types::{ExecCtx, QueryCtx}; - use sylvia_derive::contract; - - use crate::{cw1::Cw1, cw1_contract::Cw1Contract, ExternalMsg}; - - #[contract(module = crate::cw1_contract)] - #[messages(crate::cw1 as Cw1)] - #[sv::custom(msg=ExternalMsg)] - impl Cw1 for Cw1Contract { - type Error = StdError; - - #[msg(exec)] - fn execute( - &self, - _ctx: ExecCtx, - _msgs: Vec>, - ) -> Result, Self::Error> { - Ok(Response::new()) - } - - #[msg(query)] - fn some_query( - &self, - _ctx: QueryCtx, - _param: crate::ExternalMsg, - ) -> Result { - Ok(crate::ExternalQuery {}) - } - } -} - -#[cw_serde] -pub struct ExternalMsg; -impl cosmwasm_std::CustomMsg for ExternalMsg {} - -#[cw_serde] -pub struct ExternalQuery; -impl cosmwasm_std::CustomQuery for ExternalQuery {} - -#[cfg(all(test, feature = "mt"))] -mod tests { - use crate::cw1::{InterfaceTypes, Querier as Cw1Querier}; - use crate::cw1_contract::Cw1Contract; - use crate::impl_cw1::test_utils::Cw1; - use crate::impl_non_generic::test_utils::NonGeneric; - use crate::impl_whitelist::test_utils::Whitelist; - use crate::non_generic::Querier as NonGenericQuerier; - use crate::whitelist::Querier as WhitelistQuerier; - use crate::{ExternalMsg, ExternalQuery}; - use cosmwasm_std::{testing::mock_dependencies, Addr, CosmosMsg, Empty, QuerierWrapper}; - use sylvia::multitest::App; - use sylvia::types::InterfaceMessages; - - #[test] - fn construct_messages() { - let contract = Addr::unchecked("contract"); - - // Direct message construction - // cw1 - let _ = crate::cw1::QueryMsg::<_, Empty>::some_query(ExternalMsg {}); - let _ = crate::cw1::ExecMsg::execute(vec![CosmosMsg::Custom(ExternalMsg {})]); - let _ = crate::cw1::ExecMsg::execute(vec![CosmosMsg::Custom(Empty {})]); - - // whitelist - let _ = crate::whitelist::QueryMsg::::admins_list(); - let _ = crate::whitelist::ExecMsg::update_admins(vec![CosmosMsg::Custom(ExternalMsg {})]); - - // non_generic - let _ = crate::non_generic::QueryMsg::non_generic_query(); - let _ = crate::non_generic::ExecMsg::non_generic_exec(vec![]); - - // Generic Querier - let deps = mock_dependencies(); - let querier: QuerierWrapper = QuerierWrapper::new(&deps.querier); - - let cw1_querier = crate::cw1::BoundQuerier::borrowed(&contract, &querier); - let _: Result = - crate::cw1::Querier::some_query(&cw1_querier, ExternalMsg {}); - let _: Result = cw1_querier.some_query(ExternalMsg {}); - - let contract_querier = crate::cw1_contract::BoundQuerier::borrowed(&contract, &querier); - let _: Result = contract_querier.some_query(ExternalMsg {}); - let _: Result = contract_querier.admins_list(); - let _ = contract_querier.non_generic_query(); - - // Construct messages with Interface extension - let _ = - as InterfaceMessages>::Query::some_query( - ExternalMsg {}, - ); - let _= - as InterfaceMessages>::Exec::execute(vec![ - CosmosMsg::Custom(ExternalMsg {}), - ]); - } - - #[test] - fn mt_helpers() { - let _ = Cw1Contract::new(); - let app = App::>::custom(|_, _, _| {}); - let code_id = crate::cw1_contract::multitest_utils::CodeId::store_code(&app); - - let owner = "owner"; - - let contract = code_id - .instantiate() - .with_label("Cw1Contract") - .call(owner) - .unwrap(); - - // CustomMsg generic Interface - contract.cw1_proxy().some_query(ExternalMsg {}).unwrap(); - contract.cw1_proxy().execute(vec![]).call(owner).unwrap(); - - // Non-CustomMsg generic Interface - contract.whitelist_proxy().admins_list().unwrap(); - contract - .whitelist_proxy() - .update_admins(vec![]) - .call(owner) - .unwrap(); - - // Non-CustomMsg non-generic Interface - contract.non_generic_proxy().non_generic_query().unwrap(); - contract - .non_generic_proxy() - .non_generic_exec(vec![]) - .call(owner) - .unwrap(); - } - - #[test] - fn generic_contract() { - use crate::generic_contract::multitest_utils::CodeId; - let app = App::default(); - let code_id: CodeId< - cw_multi_test::BasicApp, - ExternalMsg, - ExternalMsg, - ExternalMsg, - crate::ExternalMsg, - crate::ExternalMsg, - > = CodeId::store_code(&app); - - let owner = "owner"; - - let contract = code_id - .instantiate(ExternalMsg {}) - .with_label("GenericContract") - .with_admin(owner) - .call(owner) - .unwrap(); - - contract.execute(ExternalMsg).call(owner).unwrap(); - contract.query(ExternalMsg).unwrap(); - contract - .migrate(ExternalMsg) - .call(owner, code_id.code_id()) - .unwrap(); - } -}