Repository: CosmWasm/sylvia
Pull request #440 (2024-10-08 12:49:53)

feat: Auto deserialize payload

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/CosmWasm/sylvia/pull/440?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) Report
Attention: Patch coverage is `92.68293%` with `9 lines` in your changes missing coverage. Please review.
> Project coverage is 72.36%. Comparing base [(`3aa9968`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/3aa9968f6c967f411d8b31713cb43dee16fabdba?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) to head [(`7cfccf7`)](https://app.codecov.io/gh/CosmWasm/sylvia/commit/7cfccf7e4da8abaf109a1c2e57adbb22cd3a807d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm).

| [Files with missing lines](https://app.codecov.io/gh/CosmWasm/sylvia/pull/440?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) | Patch % | Lines |
|---|---|---|
| [sylvia-derive/src/contract/communication/reply.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/440?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fcontract%2Fcommunication%2Freply.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvY29udHJhY3QvY29tbXVuaWNhdGlvbi9yZXBseS5ycw==) | 95.49% | [5 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/440?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |
| [sylvia-derive/src/parser/attributes/mod.rs](https://app.codecov.io/gh/CosmWasm/sylvia/pull/440?src=pr&el=tree&filepath=sylvia-derive%2Fsrc%2Fparser%2Fattributes%2Fmod.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm#diff-c3lsdmlhLWRlcml2ZS9zcmMvcGFyc2VyL2F0dHJpYnV0ZXMvbW9kLnJz) | 20.00% | [4 Missing :warning: ](https://app.codecov.io/gh/CosmWasm/sylvia/pull/440?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=CosmWasm) |

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


```diff
@@                                Coverage Diff                                @@
##           jawoznia/feat/admin_management_through_remote     #440      +/-   ##
=================================================================================
+ Coverage                                          72.12%   72.36%   +0.23%     
=================================================================================
  Files                                                 56       56              
  Lines                                               3484     3542      +58     
=================================================================================
+ Hits                                                2513     2563      +50     
- Misses                                               971      979       +8     
```

</details>

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


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2c27b17..f0cd58e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,9 +22,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
 
 [[package]]
 name = "anyhow"
-version = "1.0.87"
+version = "1.0.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
+checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
 
 [[package]]
 name = "ark-bls12-381"
@@ -227,15 +227,15 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-core"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d905990ef3afb5753bb709dc7de88e9e370aa32bcc2f31731d4b533b63e82490"
+checksum = "5f6ceb8624260d0d3a67c4e1a1d43fc7e9406720afbcb124521501dd138f90aa"
 
 [[package]]
 name = "cosmwasm-crypto"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b2a7bd9c1dd9a377a4dc0f4ad97d24b03c33798cd5a6d7ceb8869b41c5d2f2d"
+checksum = "4125381e5fd7fefe9f614640049648088015eca2b60d861465329a5d87dfa538"
 dependencies = [
  "ark-bls12-381",
  "ark-ec",
@@ -256,9 +256,9 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-derive"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "029910b409398fdf81955d7301b906caf81f2c42b013ea074fbd89720229c424"
+checksum = "1b5658b1dc64e10b56ae7a449f678f96932a96f6cfad1769d608d1d1d656480a"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -291,9 +291,9 @@ dependencies = [
 
 [[package]]
 name = "cosmwasm-std"
-version = "2.1.3"
+version = "2.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51dec99a2e478715c0a4277f0dbeadbb8466500eb7dec873d0924edd086e77f1"
+checksum = "70eb7ab0c1e99dd6207496963ba2a457c4128ac9ad9c72a83f8d9808542b849b"
 dependencies = [
  "base64",
  "bech32",
@@ -854,9 +854,9 @@ dependencies = [
 
 [[package]]
 name = "prost"
-version = "0.13.2"
+version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995"
+checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
 dependencies = [
  "bytes",
  "prost-derive",
@@ -864,9 +864,9 @@ dependencies = [
 
 [[package]]
 name = "prost-derive"
-version = "0.13.2"
+version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac"
+checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
 dependencies = [
  "anyhow",
  "itertools 0.13.0",
@@ -1192,18 +1192,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.63"
+version = "1.0.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.63"
+version = "1.0.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/Cargo.toml b/Cargo.toml
index 0b76ff1..c7f2ff5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@ sylvia-derive = { version = "1.2.1", path = "sylvia-derive" }
 anyhow = "1.0.86"
 cosmwasm-schema = "2.1.1"
 cosmwasm-std = "2.1.1"
-cw-multi-test = "2.1.0"
+cw-multi-test = "2.1.1"
 cw-storage-plus = "2.0.0"
 schemars = "0.8.21"
 cw-utils = "2.0.0"
diff --git a/sylvia-derive/src/contract/communication/reply.rs b/sylvia-derive/src/contract/communication/reply.rs
index 28c6764..4ba0ed1 100644
--- a/sylvia-derive/src/contract/communication/reply.rs
+++ b/sylvia-derive/src/contract/communication/reply.rs
@@ -6,7 +6,8 @@ use syn::{parse_quote, GenericParam, Ident, ItemImpl, Type};
 
 use crate::crate_module;
 use crate::parser::attributes::msg::ReplyOn;
-use crate::parser::MsgType;
+use crate::parser::{MsgType, SylviaAttribute};
+use crate::types::msg_field::MsgField;
 use crate::types::msg_variant::{MsgVariant, MsgVariants};
 use crate::utils::emit_turbofish;
 
@@ -118,40 +119,11 @@ impl<'a> Reply<'a> {
             }
         });
 
-        let submsg_methods_implementation = reply_data.iter().map(|data| {
-            let method_name = &data.handler_name;
-            let reply_on = data.emit_cw_reply_on();
-            let reply_id = &data.reply_id;
-
-            quote! {
-                fn #method_name (self) -> #sylvia ::cw_std::SubMsg<CustomMsgT> {
-                    #sylvia ::cw_std::SubMsg {
-                        reply_on: #reply_on ,
-                        id: #reply_id ,
-                        ..self
-                    }
-                }
-            }
-        });
-
-        let wasmmsg_methods_implementation = reply_data.iter().map(|data| {
-            let method_name = &data.handler_name;
-            let reply_on = data.emit_cw_reply_on();
-            let reply_id = &data.reply_id;
-
-            quote! {
-                fn #method_name (self) -> #sylvia ::cw_std::SubMsg<CustomMsgT> {
-                    #sylvia ::cw_std::SubMsg {
-                        reply_on: #reply_on ,
-                        id: #reply_id ,
-                        msg: self.into(),
-                        payload: Default::default(),
-                        gas_limit: None,
-                    }
-                }
-            }
-        });
-        let cosmosmsg_methods_implementation = wasmmsg_methods_implementation.clone();
+        let submsg_reply_setters = reply_data.iter().map(ReplyData::emit_submsg_setter);
+        let submsg_converters: Vec<_> = reply_data
+            .iter()
+            .map(ReplyData::emit_submsg_converter)
+            .collect();
 
         quote! {
             pub trait SubMsgMethods<CustomMsgT> {
@@ -159,15 +131,15 @@ impl<'a> Reply<'a> {
             }
 
             impl<CustomMsgT> SubMsgMethods<CustomMsgT> for #sylvia ::cw_std::SubMsg<CustomMsgT> {
-                #(#submsg_methods_implementation)*
+                #(#submsg_reply_setters)*
             }
 
             impl<CustomMsgT> SubMsgMethods<CustomMsgT> for #sylvia ::cw_std::WasmMsg {
-                #(#wasmmsg_methods_implementation)*
+                #(#submsg_converters)*
             }
 
             impl<CustomMsgT> SubMsgMethods<CustomMsgT> for #sylvia ::cw_std::CosmosMsg<CustomMsgT> {
-                #(#cosmosmsg_methods_implementation)*
+                #(#submsg_converters)*
             }
         }
     }
@@ -185,7 +157,8 @@ impl<'a> ReplyVariants<'a> for MsgVariants<'a, GenericParam> {
 
         self.variants()
             .flat_map(ReplyVariant::as_reply_data)
-            .for_each(|(reply_id,  handler_name,function_name, reply_on)| {
+            .for_each(|(reply_id,  handler_name, handler)| {
+                let reply_on = handler.msg_attr().reply_on();
                 match reply_data
                     .iter_mut()
                     .find(|existing_data| existing_data.reply_id == reply_id)
@@ -194,18 +167,22 @@ impl<'a> ReplyVariants<'a> for MsgVariants<'a, GenericParam> {
                         if existing_data
                             .handlers
                             .iter()
-                            .any(|(_, prev_reply_on)| prev_reply_on.excludes(&reply_on)) =>
+                            .any(|existing_handler| existing_handler.msg_attr().reply_on().excludes(&reply_on)) =>
                     {
                         existing_data.handlers.iter().for_each(
-                            |(prev_function_name, prev_reply_on)| {
+                            |existing_handler| {
+                                let existing_reply_id = &existing_data.reply_id;
+                                let existing_reply_on = existing_handler.msg_attr().reply_on();
+                                let existing_function_name= existing_handler.function_name();
+
                                 emit_error!(reply_id.span(), "Duplicated reply handler.";
-                                    note = existing_data.reply_id.span() => format!("Previous definition of handler={} for reply_on={} defined on `fn {}()`", existing_data.reply_id.to_string(), prev_reply_on, prev_function_name);
+                                    note = existing_data.reply_id.span() => format!("Previous definition of handler={} for reply_on={} defined on `fn {}()`", existing_reply_id, existing_reply_on, existing_function_name);
                                 )
                             },
                         )
                     }
-                    Some(existing_data) => existing_data.handlers.push((function_name, reply_on)),
-                    None => reply_data.push(ReplyData::new(reply_id, function_name, reply_on, handler_name)),
+                    Some(existing_data) => existing_data.handlers.push(handler),
+                    None => reply_data.push(ReplyData::new(reply_id, handler,  handler_name)),
                 }
             });
 
@@ -215,22 +192,20 @@ impl<'a> ReplyVariants<'a> for MsgVariants<'a, GenericParam> {
 
 /// Maps single reply id with its handlers.
 struct ReplyData<'a> {
+    /// Unique identifier for the reply.
     pub reply_id: Ident,
+    /// Unique name of the handler from which the [reply_id](ReplyData::reply_id) was constructed.
     pub handler_name: &'a Ident,
-    pub handlers: Vec<(&'a Ident, ReplyOn)>,
+    /// Handler methods for the reply id.
+    pub handlers: Vec<&'a MsgVariant<'a>>,
 }
 
 impl<'a> ReplyData<'a> {
-    pub fn new(
-        reply_id: Ident,
-        function_name: &'a Ident,
-        reply_on: ReplyOn,
-        handler_name: &'a Ident,
-    ) -> Self {
+    pub fn new(reply_id: Ident, variant: &'a MsgVariant<'a>, handler_name: &'a Ident) -> Self {
         Self {
             reply_id,
             handler_name,
-            handlers: vec![(function_name, reply_on)],
+            handlers: vec![variant],
         }
     }
 
@@ -259,15 +234,15 @@ impl<'a> ReplyData<'a> {
         let is_always = self
             .handlers
             .iter()
-            .any(|(_, reply_on)| reply_on == &ReplyOn::Always);
+            .any(|handler| handler.msg_attr().reply_on() == ReplyOn::Always);
         let is_success = self
             .handlers
             .iter()
-            .any(|(_, reply_on)| reply_on == &ReplyOn::Success);
+            .any(|handler| handler.msg_attr().reply_on() == ReplyOn::Success);
         let is_failure = self
             .handlers
             .iter()
-            .any(|(_, reply_on)| reply_on == &ReplyOn::Failure);
+            .any(|handler| handler.msg_attr().reply_on() == ReplyOn::Failure);
 
         if is_always || (is_success && is_failure) {
             quote! { #sylvia ::cw_std::ReplyOn::Always }
@@ -282,33 +257,92 @@ impl<'a> ReplyData<'a> {
             quote! { #sylvia ::cw_std::ReplyOn::Never }
         }
     }
+
+    fn emit_submsg_setter(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            reply_id,
+            handler_name,
+            ..
+        } = self;
+
+        let method_name = handler_name;
+        let reply_on = self.emit_cw_reply_on();
+
+        quote! {
+            fn #method_name (self) -> #sylvia ::cw_std::SubMsg<CustomMsgT> {
+                #sylvia ::cw_std::SubMsg {
+                    reply_on: #reply_on ,
+                    id: #reply_id ,
+                    ..self
+                }
+            }
+        }
+    }
+
+    fn emit_submsg_converter(&self) -> TokenStream {
+        let sylvia = crate_module();
+        let Self {
+            reply_id,
+            handler_name,
+            ..
+        } = self;
+
+        let method_name = handler_name;
+        let reply_on = self.emit_cw_reply_on();
+
+        quote! {
+            fn #method_name (self) -> #sylvia ::cw_std::SubMsg<CustomMsgT> {
+                #sylvia ::cw_std::SubMsg {
+                    reply_on: #reply_on ,
+                    id: #reply_id ,
+                    msg: self.into(),
+                    payload: Default::default(),
+                    gas_limit: None,
+                }
+            }
+        }
+    }
 }
 
 /// Emits match arm for [ReplyOn::Success].
 /// In case neither [ReplyOn::Success] nor [ReplyOn::Always] is present, `Response::events`
 /// and `Response::data` are forwarded in the `Response`
-fn emit_success_match_arm(
-    handlers: &[(&Ident, ReplyOn)],
-    contract_turbofish: &Type,
-) -> TokenStream {
+fn emit_success_match_arm(handlers: &[&MsgVariant], contract_turbofish: &Type) -> TokenStream {
     let sylvia = crate_module();
 
-    match handlers
-        .iter()
-        .find(|(_, reply_on)| reply_on == &ReplyOn::Success || reply_on == &ReplyOn::Always)
-    {
-        Some((function_name, reply_on)) if reply_on == &ReplyOn::Success => quote! {
-            #sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => {
-                #[allow(deprecated)]
-                let #sylvia ::cw_std::SubMsgResponse { events, data, msg_responses} = sub_msg_resp;
-                #contract_turbofish ::new(). #function_name ((deps, env, gas_used, events, msg_responses).into(), data, payload)
+    match handlers.iter().find(|handler| {
+        handler.msg_attr().reply_on() == ReplyOn::Success
+            || handler.msg_attr().reply_on() == ReplyOn::Always
+    }) {
+        Some(handler) if handler.msg_attr().reply_on() == ReplyOn::Success => {
+            let function_name = handler.function_name();
+            let payload = handler.emit_payload_parameters();
+            let payload_deserialization = handler.emit_payload_deserialization();
+
+            quote! {
+                #sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => {
+                    #[allow(deprecated)]
+                    let #sylvia ::cw_std::SubMsgResponse { events, data, msg_responses} = sub_msg_resp;
+                    #payload_deserialization
+
+                    #contract_turbofish ::new(). #function_name ((deps, env, gas_used, events, msg_responses).into(), data, #payload )
+                }
             }
-        },
-        Some((function_name, reply_on)) if reply_on == &ReplyOn::Always => quote! {
-            #sylvia ::cw_std::SubMsgResult::Ok(_) => {
-                #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, payload)
+        }
+        Some(handler) if handler.msg_attr().reply_on() == ReplyOn::Always => {
+            let function_name = handler.function_name();
+            let payload = handler.emit_payload_parameters();
+            let payload_deserialization = handler.emit_payload_deserialization();
+
+            quote! {
+                #sylvia ::cw_std::SubMsgResult::Ok(_) => {
+                    #payload_deserialization
+
+                    #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, #payload )
+                }
             }
-        },
+        }
         _ => quote! {
             #sylvia ::cw_std::SubMsgResult::Ok(sub_msg_resp) => {
                 let mut resp = sylvia::cw_std::Response::new().add_events(sub_msg_resp.events);
@@ -327,26 +361,39 @@ fn emit_success_match_arm(
 /// Emits match arm for [ReplyOn::Failure].
 /// In case neither [ReplyOn::Failure] nor [ReplyOn::Always] is present,
 /// the error is forwarded.
-fn emit_failure_match_arm(
-    handlers: &[(&Ident, ReplyOn)],
-    contract_turbofish: &Type,
-) -> TokenStream {
+fn emit_failure_match_arm(handlers: &[&MsgVariant], contract_turbofish: &Type) -> TokenStream {
     let sylvia = crate_module();
 
-    match handlers
-        .iter()
-        .find(|(_, reply_on)| reply_on == &ReplyOn::Failure || reply_on == &ReplyOn::Always)
-    {
-        Some((function_name, reply_on)) if reply_on == &ReplyOn::Failure => quote! {
-            #sylvia ::cw_std::SubMsgResult::Err(error) => {
-                #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), error, payload)
+    match handlers.iter().find(|handler| {
+        handler.msg_attr().reply_on() == ReplyOn::Failure
+            || handler.msg_attr().reply_on() == ReplyOn::Always
+    }) {
+        Some(handler) if handler.msg_attr().reply_on() == ReplyOn::Failure => {
+            let function_name = handler.function_name();
+            let payload = handler.emit_payload_parameters();
+            let payload_deserialization = handler.emit_payload_deserialization();
+
+            quote! {
+                #sylvia ::cw_std::SubMsgResult::Err(error) => {
+                    #payload_deserialization
+
+                    #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), error, #payload )
+                }
             }
-        },
-        Some((function_name, reply_on)) if reply_on == &ReplyOn::Always => quote! {
-            #sylvia ::cw_std::SubMsgResult::Err(_) => {
-                #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, payload)
+        }
+        Some(handler) if handler.msg_attr().reply_on() == ReplyOn::Always => {
+            let function_name = handler.function_name();
+            let payload = handler.emit_payload_parameters();
+            let payload_deserialization = handler.emit_payload_deserialization();
+
+            quote! {
+                #sylvia ::cw_std::SubMsgResult::Err(_) => {
+                    #payload_deserialization
+
+                    #contract_turbofish ::new(). #function_name ((deps, env, gas_used, vec![], vec![]).into(), result, #payload )
+                }
             }
-        },
+        }
         _ => quote! {
             #sylvia ::cw_std::SubMsgResult::Err(error) => {
                 Err(sylvia::cw_std::StdError::generic_err(error)).map_err(Into::into)
@@ -357,7 +404,9 @@ fn emit_failure_match_arm(
 
 trait ReplyVariant<'a> {
     fn as_handlers(&'a self) -> Vec<&'a Ident>;
-    fn as_reply_data(&self) -> Vec<(Ident, &Ident, &Ident, ReplyOn)>;
+    fn as_reply_data(&self) -> Vec<(Ident, &Ident, &MsgVariant)>;
+    fn emit_payload_parameters(&self) -> TokenStream;
+    fn emit_payload_deserialization(&self) -> TokenStream;
 }
 
 impl<'a> ReplyVariant<'a> for MsgVariant<'a> {
@@ -368,19 +417,42 @@ impl<'a> ReplyVariant<'a> for MsgVariant<'a> {
         self.msg_attr().handlers().iter().collect()
     }
 
-    fn as_reply_data(&self) -> Vec<(Ident, &Ident, &Ident, ReplyOn)> {
+    fn as_reply_data(&self) -> Vec<(Ident, &Ident, &MsgVariant)> {
         self.as_handlers()
             .iter()
-            .map(|&handler| {
-                (
-                    handler.as_reply_id(),
-                    handler,
-                    self.function_name(),
-                    self.msg_attr().reply_on(),
-                )
-            })
+            .map(|&handler| (handler.as_reply_id(), handler, self))
             .collect()
     }
+
+    fn emit_payload_parameters(&self) -> TokenStream {
+        if self
+            .fields()
+            .iter()
+            .any(|field| field.contains_attribute(SylviaAttribute::Payload))
+        {
+            quote! { payload }
+        } else {
+            let deserialized_payload = self.fields().iter().skip(1).map(MsgField::name);
+            quote! { #(#deserialized_payload),* }
+        }
+    }
+
+    fn emit_payload_deserialization(&self) -> TokenStream {
+        let sylvia = crate_module();
+
+        if self
+            .fields()
+            .iter()
+            .any(|field| field.contains_attribute(SylviaAttribute::Payload))
+        {
+            return quote! {};
+        }
+
+        let deserialized_names = self.fields().iter().skip(1).map(MsgField::name);
+        quote! {
+            let ( #(#deserialized_names),* ) = #sylvia ::cw_std::from_json(&payload)?;
+        }
+    }
 }
 
 /// Maps self to an [Ident] reply id.
diff --git a/sylvia-derive/src/parser/attributes/mod.rs b/sylvia-derive/src/parser/attributes/mod.rs
index c3b3cb5..9390dac 100644
--- a/sylvia-derive/src/parser/attributes/mod.rs
+++ b/sylvia-derive/src/parser/attributes/mod.rs
@@ -21,6 +21,7 @@ pub use override_entry_point::{FilteredOverrideEntryPoints, OverrideEntryPoint};
 
 /// This struct represents all possible attributes that
 /// are parsed and utilized by sylvia.
+#[derive(Clone, Copy, Debug, PartialEq)]
 pub enum SylviaAttribute {
     Custom,
     Error,
@@ -29,6 +30,7 @@ pub enum SylviaAttribute {
     OverrideEntryPoint,
     VariantAttrs,
     MsgAttrs,
+    Payload,
 }
 
 impl SylviaAttribute {
@@ -50,6 +52,7 @@ impl SylviaAttribute {
             "override_entry_point" => Some(Self::OverrideEntryPoint),
             "attr" => Some(Self::VariantAttrs),
             "msg_attr" => Some(Self::MsgAttrs),
+            "payload" => Some(Self::Payload),
             _ => None,
         }
     }
@@ -158,6 +161,12 @@ impl ParsedSylviaAttributes {
                     self.msg_attrs_forward.push(message_attrs);
                 }
             }
+            SylviaAttribute::Payload => {
+                emit_error!(
+                    attr, "The attribute `sv::payload` used in wrong context";
+                    note = attr.span() => "The `sv::payload` should be used as a prefix for `Binary` payload.";
+                );
+            }
         }
     }
 }
diff --git a/sylvia-derive/src/types/msg_field.rs b/sylvia-derive/src/types/msg_field.rs
index 14f26de..227732d 100644
--- a/sylvia-derive/src/types/msg_field.rs
+++ b/sylvia-derive/src/types/msg_field.rs
@@ -1,5 +1,6 @@
 use crate::fold::StripSelfPath;
 use crate::parser::check_generics::{CheckGenerics, GetPath};
+use crate::parser::SylviaAttribute;
 use proc_macro2::TokenStream;
 use proc_macro_error::emit_error;
 use quote::quote;
@@ -115,4 +116,10 @@ impl<'a> MsgField<'a> {
     pub fn name(&self) -> &'a Ident {
         self.name
     }
+
+    pub fn contains_attribute(&self, sv_attr: SylviaAttribute) -> bool {
+        self.attrs
+            .iter()
+            .any(|attr| SylviaAttribute::new(attr) == Some(sv_attr))
+    }
 }


Test patch:
diff --git a/sylvia/tests/reply.rs b/sylvia/tests/reply.rs
index 9cc0e13..befae36 100644
--- a/sylvia/tests/reply.rs
+++ b/sylvia/tests/reply.rs
@@ -1,6 +1,7 @@
 #![cfg(feature = "sv_replies")]
 
-use cosmwasm_std::{BankMsg, CosmosMsg, Empty, SubMsgResult};
+use cosmwasm_schema::cw_serde;
+use cosmwasm_std::{to_json_binary, BankMsg, CosmosMsg, Empty, SubMsgResult};
 use cw_storage_plus::Item;
 use cw_utils::{parse_instantiate_response_data, ParseReplyError};
 use noop_contract::sv::{Executor, NoopContractInstantiateBuilder};
@@ -56,6 +57,11 @@ mod noop_contract {
     }
 }
 
+#[cw_serde]
+pub struct InstantiatePayload {
+    pub sender: Addr,
+}
+
 #[derive(Error, Debug, PartialEq)]
 pub enum ContractError {
     #[error("{0}")]
@@ -91,13 +97,19 @@ where
     #[sv::msg(instantiate)]
     pub fn instantiate(
         &self,
-        _ctx: InstantiateCtx<Q>,
+        ctx: InstantiateCtx<Q>,
         remote_code_id: u64,
     ) -> Result<Response<M>, ContractError> {
+        // Custom type can be used as a payload.
+        let payload = InstantiatePayload {
+            sender: ctx.info.sender,
+        };
         let sub_msg = InstantiateBuilder::noop_contract(remote_code_id)?
             .with_label("noop")
             .build()
-            .remote_instantiated();
+            .remote_instantiated()
+            .with_payload(to_json_binary(&payload)?);
+
         Ok(Response::new().add_submessage(sub_msg))
     }
 
@@ -158,13 +170,17 @@ where
         ctx: ExecCtx<Q>,
         should_fail: bool,
     ) -> Result<Response<M>, ContractError> {
+        // Tuple can be used as a payload.
+        let payload = to_json_binary(&(42_u32, "Hello, world!".to_string()))?;
+
         let msg = self
             .remote
             .load(ctx.deps.storage)?
             .executor()
             .noop(should_fail)?
             .build()
-            .always();
+            .always()
+            .with_payload(payload);
 
         Ok(Response::new().add_submessage(msg))
     }
@@ -198,7 +214,10 @@ where
         &self,
         ctx: ReplyCtx<Q>,
         data: Option<Binary>,
-        _payload: Binary,
+        // Blocked by https://github.com/CosmWasm/cw-multi-test/pull/216.
+        // Payload is not currently forwarded in the MultiTest.
+        // _instantiate_payload: InstantiatePayload,
+        #[sv::payload] _payload: Binary,
     ) -> Result<Response<M>, ContractError> {
         self.last_reply
             .save(ctx.deps.storage, &REMOTE_INSTANTIATED_REPLY_ID)?;
@@ -216,7 +235,7 @@ where
         &self,
         ctx: ReplyCtx<Q>,
         _data: Option<Binary>,
-        _payload: Binary,
+        #[sv::payload] _payload: Binary,
     ) -> Result<Response<M>, ContractError> {
         self.last_reply.save(ctx.deps.storage, &SUCCESS_REPLY_ID)?;
 
@@ -228,7 +247,7 @@ where
         &self,
         ctx: ReplyCtx<Q>,
         _error: String,
-        _payload: Binary,
+        #[sv::payload] _payload: Binary,
     ) -> Result<Response<M>, ContractError> {
         self.last_reply.save(ctx.deps.storage, &FAILURE_REPLY_ID)?;
 
@@ -240,7 +259,9 @@ where
         &self,
         ctx: ReplyCtx<Q>,
         _result: SubMsgResult,
-        _payload: Binary,
+        #[sv::payload] _payload: Binary,
+        // _first_part_payload: u32,
+        // _second_part_payload: String,
     ) -> Result<Response<M>, ContractError> {
         self.last_reply.save(ctx.deps.storage, &ALWAYS_REPLY_ID)?;
 
diff --git a/sylvia/tests/reply_generation.rs b/sylvia/tests/reply_generation.rs
index 171fa6b..bf32020 100644
--- a/sylvia/tests/reply_generation.rs
+++ b/sylvia/tests/reply_generation.rs
@@ -25,7 +25,7 @@ impl Contract {
         &self,
         _ctx: ReplyCtx,
         _result: SubMsgResult,
-        _payload: Binary,
+        #[sv::payload] _payload: Binary,
     ) -> StdResult<Response> {
         Ok(Response::new())
     }
@@ -36,7 +36,7 @@ impl Contract {
         &self,
         _ctx: ReplyCtx,
         _result: SubMsgResult,
-        _payload: Binary,
+        #[sv::payload] _payload: Binary,
     ) -> StdResult<Response> {
         Ok(Response::new())
     }
@@ -47,7 +47,7 @@ impl Contract {
         &self,
         _ctx: ReplyCtx,
         _data: Option<Binary>,
-        _payload: Binary,
+        #[sv::payload] _payload: Binary,
     ) -> StdResult<Response> {
         Ok(Response::new())
     }
@@ -58,7 +58,7 @@ impl Contract {
         &self,
         _ctx: ReplyCtx,
         _result: SubMsgResult,
-        _payload: Binary,
+        #[sv::payload] _payload: Binary,
     ) -> StdResult<Response> {
         Ok(Response::new())
     }
@@ -69,7 +69,7 @@ impl Contract {
         &self,
         _ctx: ReplyCtx,
         _error: String,
-        _payload: Binary,
+        #[sv::payload] _payload: Binary,
     ) -> StdResult<Response> {
         Ok(Response::new())
     }