Repository: CosmWasm/cosmwasm (rust)

Issue:
`Reply` should contain the original payload from the triggering `SubMsg`
### Motivation

We want to send a ReplyAlways SubMsg. If and only if that message fails, we want to send a different message with the same context.

The first message has the payload `ExecuteMsg::TryThis{ important_context: Context }`.
If and only if that message fails, then we want to retry with an alternative `ExecuteMsg::TryThisInstead{ same_context: Context }`.

Currently there is no way to accomplish this as far as I can tell. If `TryThis` fails, then the context in which it was called is no longer around in the reply. We could try a hack and store the context to storage before we send either message, but that only works if we are sending one instance of `TryThis` + maybe `TryThisInstead `. If we are sending multiple instances then that isn't possible.

Pull request description:
Add payload field to SubMsg and Reply
Closes #1909

- [x] Implementation
- [x] CHANGELOG entry
- [x] MIGRATING entry

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 805fe4b91..8bfeedb76 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,15 @@ and this project adheres to
 
 ## [Unreleased]
 
+### Added
+
+- cosmwasm-std: Add new field `payload` to `SubMsg` and `Reply`. This is binary
+  data the contract can set in a contract specific format and get back then the
+  `reply` entry point is called. `SubMsg::with_payload` allows setting the
+  payload on an existing `SubMsg`. ([#2008])
+
+[#2008]: https://github.com/CosmWasm/cosmwasm/pull/2008
+
 ### Changed
 
 - cosmwasm-vm: Limit total number of function parameters in
diff --git a/MIGRATING.md b/MIGRATING.md
index 3649f4024..2b20ac4b5 100644
--- a/MIGRATING.md
+++ b/MIGRATING.md
@@ -221,6 +221,47 @@ major releases of `cosmwasm`. Note that you can also view the
   +}))?;
   ```
 
+- A new `payload` field allows you to send arbitrary data from the original
+  contract into the `reply`. If you construct `SubMsg` manually, add the
+  `payload` field:
+
+  ```diff
+   SubMsg {
+       id: 12,
+  +    payload: Binary::default(),
+       msg: my_bank_send,
+       gas_limit: Some(12345u64),
+       reply_on: ReplyOn::Always,
+   },
+  ```
+
+  or with data:
+
+  ```diff
+   SubMsg {
+       id: 12,
+  +    payload: Binary::new(vec![9, 8, 7, 6, 5]),
+       msg: my_bank_send,
+       gas_limit: Some(12345u64),
+       reply_on: ReplyOn::Always,
+   },
+  ```
+
+  If you use a constructor function, you can set the payload as follows:
+
+  ```diff
+   SubMsg::new(BankMsg::Send {
+     to_address: payout,
+     amount: coins(123456u128,"gold")
+   })
+  +.with_payload(vec![9, 8, 7, 6, 5])
+  ```
+
+  The payload data will then be available in the new field `Reply.payload` in
+  the `reply` entry point. This functionality is an optional addition introduced
+  in 2.0. To keep the CosmWasm 1.x behaviour, just set payload to
+  `Binary::default()`.
+
 ## 1.4.x -> 1.5.0
 
 - Update `cosmwasm-*` dependencies in Cargo.toml (skip the ones you don't use):
diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs
index 900ed8f24..d66dd49af 100644
--- a/contracts/ibc-reflect/src/contract.rs
+++ b/contracts/ibc-reflect/src/contract.rs
@@ -400,11 +400,13 @@ mod tests {
             res.events[0]
         );
         let id = res.messages[0].id;
+        let payload = res.messages[0].payload.clone();
 
         // fake a reply and ensure this works
         #[allow(deprecated)]
         let response = Reply {
             id,
+            payload,
             gas_used: 1234567,
             result: SubMsgResult::Ok(SubMsgResponse {
                 events: fake_events(&account),
@@ -461,6 +463,7 @@ mod tests {
         // and set up a reflect account
         assert_eq!(1, res.messages.len());
         let id = res.messages[0].id;
+        let payload = res.messages[0].payload.clone();
         if let CosmosMsg::Wasm(WasmMsg::Instantiate {
             admin,
             code_id,
@@ -486,6 +489,7 @@ mod tests {
         #[allow(deprecated)]
         let response = Reply {
             id,
+            payload,
             gas_used: 1234567,
             result: SubMsgResult::Ok(SubMsgResponse {
                 events: fake_events(reflect_addr.as_str()),
diff --git a/contracts/reflect/schema/raw/execute.json b/contracts/reflect/schema/raw/execute.json
index 65a3e108d..57d1458f0 100644
--- a/contracts/reflect/schema/raw/execute.json
+++ b/contracts/reflect/schema/raw/execute.json
@@ -698,7 +698,7 @@
       ],
       "properties": {
         "gas_limit": {
-          "description": "Gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).",
+          "description": "Gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).\n\nSetting this to `None` means unlimited. Then the submessage execution can consume all gas of the current execution context.",
           "type": [
             "integer",
             "null"
@@ -715,6 +715,15 @@
         "msg": {
           "$ref": "#/definitions/CosmosMsg_for_CustomMsg"
         },
+        "payload": {
+          "description": "Some arbirary data that the contract can set in an application specific way. This is just passed into the `reply` entry point and is not stored to state. Any encoding can be used. If `id` is used to identify a particular action, the encoding can also be different for each of those actions since you can match `id` first and then start processing the `payload`.\n\nThe environment restricts the length of this field in order to avoid abuse. The limit is environment specific and can change over time. The initial default is 128 KiB.\n\nUnset/nil/null cannot be differentiated from empty data.\n\nOn chains running CosmWasm 1.x this field will be ignored.",
+          "default": "",
+          "allOf": [
+            {
+              "$ref": "#/definitions/Binary"
+            }
+          ]
+        },
         "reply_on": {
           "$ref": "#/definitions/ReplyOn"
         }
diff --git a/contracts/reflect/schema/raw/response_to_sub_msg_result.json b/contracts/reflect/schema/raw/response_to_sub_msg_result.json
index e11c7ad73..8b9a44cd0 100644
--- a/contracts/reflect/schema/raw/response_to_sub_msg_result.json
+++ b/contracts/reflect/schema/raw/response_to_sub_msg_result.json
@@ -21,6 +21,15 @@
       "format": "uint64",
       "minimum": 0.0
     },
+    "payload": {
+      "description": "Some arbirary data that the contract set when emitting the `SubMsg`. This is just passed into the `reply` entry point and is not stored to state.\n\nUnset/nil/null cannot be differentiated from empty data.\n\nOn chains running CosmWasm 1.x this field is never filled.",
+      "default": "",
+      "allOf": [
+        {
+          "$ref": "#/definitions/Binary"
+        }
+      ]
+    },
     "result": {
       "$ref": "#/definitions/SubMsgResult"
     }
diff --git a/contracts/reflect/schema/reflect.json b/contracts/reflect/schema/reflect.json
index 0eb4351c4..c271b4a8f 100644
--- a/contracts/reflect/schema/reflect.json
+++ b/contracts/reflect/schema/reflect.json
@@ -708,7 +708,7 @@
         ],
         "properties": {
           "gas_limit": {
-            "description": "Gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).",
+            "description": "Gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).\n\nSetting this to `None` means unlimited. Then the submessage execution can consume all gas of the current execution context.",
             "type": [
               "integer",
               "null"
@@ -725,6 +725,15 @@
           "msg": {
             "$ref": "#/definitions/CosmosMsg_for_CustomMsg"
           },
+          "payload": {
+            "description": "Some arbirary data that the contract can set in an application specific way. This is just passed into the `reply` entry point and is not stored to state. Any encoding can be used. If `id` is used to identify a particular action, the encoding can also be different for each of those actions since you can match `id` first and then start processing the `payload`.\n\nThe environment restricts the length of this field in order to avoid abuse. The limit is environment specific and can change over time. The initial default is 128 KiB.\n\nUnset/nil/null cannot be differentiated from empty data.\n\nOn chains running CosmWasm 1.x this field will be ignored.",
+            "default": "",
+            "allOf": [
+              {
+                "$ref": "#/definitions/Binary"
+              }
+            ]
+          },
           "reply_on": {
             "$ref": "#/definitions/ReplyOn"
           }
@@ -1875,6 +1884,15 @@
           "format": "uint64",
           "minimum": 0.0
         },
+        "payload": {
+          "description": "Some arbirary data that the contract set when emitting the `SubMsg`. This is just passed into the `reply` entry point and is not stored to state.\n\nUnset/nil/null cannot be differentiated from empty data.\n\nOn chains running CosmWasm 1.x this field is never filled.",
+          "default": "",
+          "allOf": [
+            {
+              "$ref": "#/definitions/Binary"
+            }
+          ]
+        },
         "result": {
           "$ref": "#/definitions/SubMsgResult"
         }
diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs
index 0aa15d345..3b4149fcd 100644
--- a/contracts/reflect/src/contract.rs
+++ b/contracts/reflect/src/contract.rs
@@ -434,6 +434,7 @@ mod tests {
         let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
 
         let id = 123u64;
+        let payload = Binary::from(b"my dear");
         let data = Binary::from(b"foobar");
         let events = vec![Event::new("message").add_attribute("signer", "caller-addr")];
         let gas_used = 1234567u64;
@@ -443,12 +444,13 @@ mod tests {
             data: Some(data.clone()),
             msg_responses: vec![],
         });
-        let subcall = Reply {
+        let the_reply = Reply {
             id,
+            payload,
             gas_used,
             result,
         };
-        let res = reply(deps.as_mut(), mock_env(), subcall).unwrap();
+        let res = reply(deps.as_mut(), mock_env(), the_reply).unwrap();
         assert_eq!(0, res.messages.len());
 
         // query for a non-existant id
diff --git a/packages/std/src/results/response.rs b/packages/std/src/results/response.rs
index 921dea515..7ab54ced9 100644
--- a/packages/std/src/results/response.rs
+++ b/packages/std/src/results/response.rs
@@ -283,6 +283,7 @@ mod tests {
             messages: vec![
                 SubMsg {
                     id: 12,
+                    payload: Binary::new(vec![9, 8, 7, 6, 5]),
                     msg: BankMsg::Send {
                         to_address: String::from("checker"),
                         amount: coins(888, "moon"),
@@ -293,6 +294,7 @@ mod tests {
                 },
                 SubMsg {
                     id: UNUSED_MSG_ID,
+                    payload: Binary::default(),
                     msg: BankMsg::Send {
                         to_address: String::from("you"),
                         amount: coins(1015, "earth"),
diff --git a/packages/std/src/results/submessages.rs b/packages/std/src/results/submessages.rs
index ebc0efa17..a4c3d31da 100644
--- a/packages/std/src/results/submessages.rs
+++ b/packages/std/src/results/submessages.rs
@@ -33,8 +33,25 @@ pub struct SubMsg<T = Empty> {
     /// An arbitrary ID chosen by the contract.
     /// This is typically used to match `Reply`s in the `reply` entry point to the submessage.
     pub id: u64,
+    /// Some arbirary data that the contract can set in an application specific way.
+    /// This is just passed into the `reply` entry point and is not stored to state.
+    /// Any encoding can be used. If `id` is used to identify a particular action,
+    /// the encoding can also be different for each of those actions since you can match `id`
+    /// first and then start processing the `payload`.
+    ///
+    /// The environment restricts the length of this field in order to avoid abuse. The limit
+    /// is environment specific and can change over time. The initial default is 128 KiB.
+    ///
+    /// Unset/nil/null cannot be differentiated from empty data.
+    ///
+    /// On chains running CosmWasm 1.x this field will be ignored.
+    #[serde(default)]
+    pub payload: Binary,
     pub msg: CosmosMsg<T>,
     /// Gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
+    ///
+    /// Setting this to `None` means unlimited. Then the submessage execution can consume all gas of the
+    /// current execution context.
     pub gas_limit: Option<u64>,
     pub reply_on: ReplyOn,
 }
@@ -43,32 +60,50 @@ pub struct SubMsg<T = Empty> {
 pub const UNUSED_MSG_ID: u64 = 0;
 
 impl<T> SubMsg<T> {
-    /// new creates a "fire and forget" message with the pre-0.14 semantics
+    /// Creates a "fire and forget" message with the pre-0.14 semantics.
+    /// Since this is just an alias for [`SubMsg::reply_never`] it is somewhat recommended
+    /// to use the latter in order to make the behaviour more explicit in the caller code.
+    /// But that's up to you for now.
+    ///
+    /// By default, the submessage's gas limit will be unlimited. Use [`SubMsg::with_gas_limit`] to change it.
+    /// Setting `payload` is not advised as this will never be used.
     pub fn new(msg: impl Into<CosmosMsg<T>>) -> Self {
         Self::reply_never(msg)
     }
 
-    /// create a `SubMsg` that will provide a `reply` with the given id if the message returns `Ok`
+    /// Creates a `SubMsg` that will provide a `reply` with the given `id` if the message returns `Ok`.
+    ///
+    /// By default, the submessage's `payload` will be empty and the gas limit will be unlimited. Use
+    /// [`SubMsg::with_payload`] and [`SubMsg::with_gas_limit`] to change those.
     pub fn reply_on_success(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
         Self::reply_on(msg.into(), id, ReplyOn::Success)
     }
 
-    /// create a `SubMsg` that will provide a `reply` with the given id if the message returns `Err`
+    /// Creates a `SubMsg` that will provide a `reply` with the given `id` if the message returns `Err`.
+    ///
+    /// By default, the submessage's `payload` will be empty and the gas limit will be unlimited. Use
+    /// [`SubMsg::with_payload`] and [`SubMsg::with_gas_limit`] to change those.
     pub fn reply_on_error(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
         Self::reply_on(msg.into(), id, ReplyOn::Error)
     }
 
-    /// create a `SubMsg` that will always provide a `reply` with the given id
+    /// Create a `SubMsg` that will always provide a `reply` with the given `id`.
+    ///
+    /// By default, the submessage's `payload` will be empty and the gas limit will be unlimited. Use
+    /// [`SubMsg::with_payload`] and [`SubMsg::with_gas_limit`] to change those.
     pub fn reply_always(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
         Self::reply_on(msg.into(), id, ReplyOn::Always)
     }
 
-    /// create a `SubMsg` that will never `reply`. This is equivalent to standard message semantics.
+    /// Create a `SubMsg` that will never `reply`. This is equivalent to standard message semantics.
+    ///
+    /// By default, the submessage's gas limit will be unlimited. Use [`SubMsg::with_gas_limit`] to change it.
+    /// Setting `payload` is not advised as this will never be used.
     pub fn reply_never(msg: impl Into<CosmosMsg<T>>) -> Self {
         Self::reply_on(msg.into(), UNUSED_MSG_ID, ReplyOn::Never)
     }
 
-    /// Add a gas limit to the message.
+    /// Add a gas limit to the submessage.
     /// This gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
     ///
     /// ## Examples
@@ -86,9 +121,28 @@ impl<T> SubMsg<T> {
         self
     }
 
+    /// Add a payload to the submessage.
+    ///
+    /// ## Examples
+    ///
+    /// ```
+    /// # use cosmwasm_std::{coins, BankMsg, Binary, ReplyOn, SubMsg};
+    /// # let msg = BankMsg::Send { to_address: String::from("you"), amount: coins(1015, "earth") };
+    /// let sub_msg: SubMsg = SubMsg::reply_always(msg, 1234)
+    ///     .with_payload(vec![1, 2, 3, 4]);
+    /// assert_eq!(sub_msg.id, 1234);
+    /// assert_eq!(sub_msg.payload, Binary::new(vec![1, 2, 3, 4]));
+    /// assert_eq!(sub_msg.reply_on, ReplyOn::Always);
+    /// ```
+    pub fn with_payload(mut self, payload: impl Into<Binary>) -> Self {
+        self.payload = payload.into();
+        self
+    }
+
     fn reply_on(msg: CosmosMsg<T>, id: u64, reply_on: ReplyOn) -> Self {
         SubMsg {
             id,
+            payload: Default::default(),
             msg,
             reply_on,
             gas_limit: None,
@@ -103,6 +157,14 @@ pub struct Reply {
     /// The ID that the contract set when emitting the `SubMsg`.
     /// Use this to identify which submessage triggered the `reply`.
     pub id: u64,
+    /// Some arbirary data that the contract set when emitting the `SubMsg`.
+    /// This is just passed into the `reply` entry point and is not stored to state.
+    ///
+    /// Unset/nil/null cannot be differentiated from empty data.
+    ///
+    /// On chains running CosmWasm 1.x this field is never filled.
+    #[serde(default)]
+    pub payload: Binary,
     /// The amount of gas used by the submessage,
     /// measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
     pub gas_used: u64,
@@ -221,7 +283,71 @@ pub struct MsgResponse {
 #[allow(deprecated)]
 mod tests {
     use super::*;
-    use crate::{from_json, to_json_vec, StdError, StdResult};
+    use crate::{coins, from_json, to_json_vec, Attribute, BankMsg, StdError, StdResult};
+
+    #[test]
+    fn sub_msg_new_works() {
+        let msg = BankMsg::Send {
+            to_address: String::from("you"),
+            amount: coins(1015, "earth"),
+        };
+        let sub_msg: SubMsg = SubMsg::new(msg.clone());
+        // id and payload don't matter since there is no reply
+        assert_eq!(sub_msg.reply_on, ReplyOn::Never);
+        assert_eq!(sub_msg.gas_limit, None);
+        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
+    }
+
+    #[test]
+    fn sub_msg_reply_never_works() {
+        let msg = BankMsg::Send {
+            to_address: String::from("you"),
+            amount: coins(1015, "earth"),
+        };
+        let sub_msg: SubMsg = SubMsg::reply_never(msg.clone());
+        // id and payload don't matter since there is no reply
+        assert_eq!(sub_msg.reply_on, ReplyOn::Never);
+        assert_eq!(sub_msg.gas_limit, None);
+        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
+    }
+
+    #[test]
+    fn sub_msg_reply_always_works() {
+        let msg = BankMsg::Send {
+            to_address: String::from("you"),
+            amount: coins(1015, "earth"),
+        };
+        let sub_msg: SubMsg = SubMsg::reply_always(msg.clone(), 54);
+        assert_eq!(sub_msg.id, 54);
+        assert_eq!(sub_msg.payload, Binary::default());
+        assert_eq!(sub_msg.reply_on, ReplyOn::Always);
+        assert_eq!(sub_msg.gas_limit, None);
+        assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
+    }
+
+    #[test]
+    fn sub_msg_with_gas_limit_works() {
+        let msg = BankMsg::Send {
+            to_address: String::from("you"),
+            amount: coins(1015, "earth"),
+        };
+        let sub_msg: SubMsg = SubMsg::reply_never(msg);
+        assert_eq!(sub_msg.gas_limit, None);
+        let sub_msg = sub_msg.with_gas_limit(20);
+        assert_eq!(sub_msg.gas_limit, Some(20));
+    }
+
+    #[test]
+    fn sub_msg_with_payload_works() {
+        let msg = BankMsg::Send {
+            to_address: String::from("you"),
+            amount: coins(1015, "earth"),
+        };
+        let sub_msg: SubMsg = SubMsg::reply_never(msg);
+        assert_eq!(sub_msg.payload, Binary::default());
+        let sub_msg = sub_msg.with_payload(vec![0xAA, 3, 5, 1, 2]);
+        assert_eq!(sub_msg.payload, Binary::new(vec![0xAA, 3, 5, 1, 2]));
+    }
 
     #[test]
     fn sub_msg_result_serialization_works() {
@@ -418,4 +544,51 @@ mod tests {
         let converted: Result<SubMsgResponse, String> = original.into();
         assert_eq!(converted, Err("went wrong".to_string()));
     }
+
+    #[test]
+    fn reply_deserialization_works() {
+        // 1.x reply without payload (from https://github.com/CosmWasm/cosmwasm/issues/1909)
+        let reply: Reply = from_json(r#"{"gas_used":4312324,"id":75,"result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs="}}}"#).unwrap();
+        assert_eq!(
+            reply,
+            Reply {
+                id: 75,
+                payload: Binary::default(),
+                gas_used: 4312324,
+                result: SubMsgResult::Ok(SubMsgResponse {
+                    data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
+                    events: vec![Event {
+                        ty: "hi".to_string(),
+                        attributes: vec![Attribute {
+                            key: "si".to_string(),
+                            value: "claro".to_string(),
+                        }]
+                    }],
+                    msg_responses: vec![],
+                })
+            }
+        );
+
+        // with payload (manually added to the above test)
+        let reply: Reply = from_json(r#"{"gas_used":4312324,"id":75,"payload":"3NxjC5U=","result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs="}}}"#).unwrap();
+        assert_eq!(
+            reply,
+            Reply {
+                id: 75,
+                payload: Binary::from_base64("3NxjC5U=").unwrap(),
+                gas_used: 4312324,
+                result: SubMsgResult::Ok(SubMsgResponse {
+                    data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
+                    events: vec![Event {
+                        ty: "hi".to_string(),
+                        attributes: vec![Attribute {
+                            key: "si".to_string(),
+                            value: "claro".to_string(),
+                        }]
+                    }],
+                    msg_responses: vec![],
+                })
+            }
+        );
+    }
 }
diff --git a/packages/vm/src/calls.rs b/packages/vm/src/calls.rs
index 872cd971c..1b9fd3f1a 100644
--- a/packages/vm/src/calls.rs
+++ b/packages/vm/src/calls.rs
@@ -907,6 +907,7 @@ mod tests {
             );
             assert_eq!(ReplyOn::Success, res.messages[0].reply_on);
             let id = res.messages[0].id;
+            let payload = res.messages[0].payload.clone();
             let event = Event::new("instantiate").add_attributes(vec![
                 // We have to force this one to avoid the debug assertion against _
                 mock_wasmd_attr("_contract_address", account),
@@ -915,6 +916,7 @@ mod tests {
             #[allow(deprecated)]
             let response = Reply {
                 id,
+                payload,
                 gas_used: 1234567,
                 result: SubMsgResult::Ok(SubMsgResponse {
                     events: vec![event],


Test patch:
diff --git a/contracts/ibc-reflect/tests/integration.rs b/contracts/ibc-reflect/tests/integration.rs
index 7c6962106..ecbb60cb8 100644
--- a/contracts/ibc-reflect/tests/integration.rs
+++ b/contracts/ibc-reflect/tests/integration.rs
@@ -91,11 +91,13 @@ fn connect(
         res.events[0]
     );
     let id = res.messages[0].id;
+    let payload = res.messages[0].payload.clone();
 
     // fake a reply and ensure this works
     #[allow(deprecated)]
     let response = Reply {
         id,
+        payload,
         gas_used: 1234567,
         result: SubMsgResult::Ok(SubMsgResponse {
             events: fake_events(&account),
@@ -153,6 +155,7 @@ fn proper_handshake_flow() {
     // and set up a reflect account
     assert_eq!(1, res.messages.len());
     let id = res.messages[0].id;
+    let payload = res.messages[0].payload.clone();
     if let CosmosMsg::Wasm(WasmMsg::Instantiate {
         admin,
         code_id,
@@ -178,6 +181,7 @@ fn proper_handshake_flow() {
     #[allow(deprecated)]
     let response = Reply {
         id,
+        payload,
         gas_used: 1234567,
         result: SubMsgResult::Ok(SubMsgResponse {
             events: fake_events(reflect_addr.as_str()),
diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs
index b3cfda30c..675b4786a 100644
--- a/contracts/reflect/tests/integration.rs
+++ b/contracts/reflect/tests/integration.rs
@@ -268,6 +268,7 @@ fn reply_and_query() {
     let _res: Response = instantiate(&mut deps, mock_env(), info, msg).unwrap();
 
     let id = 123u64;
+    let payload = Binary::from(b"my dear");
     let data = Binary::from(b"foobar");
     let events = vec![Event::new("message").add_attribute("signer", "caller-addr")];
     let gas_used = 1234567u64;
@@ -277,12 +278,13 @@ fn reply_and_query() {
         data: Some(data.clone()),
         msg_responses: vec![],
     });
-    let subcall = Reply {
+    let the_reply = Reply {
         id,
+        payload,
         gas_used,
         result,
     };
-    let res: Response = reply(&mut deps, mock_env(), subcall).unwrap();
+    let res: Response = reply(&mut deps, mock_env(), the_reply).unwrap();
     assert_eq!(0, res.messages.len());
 
     // query for a non-existant id