Repository: CosmWasm/cosmwasm (rust)

Issue:
Allow submessage callbacks only on error
When we make a submessage, we can handle both success and errors from the released message. However, this comes at a cost of paying the instantiation gas cost of the original module for each submessage.

For some use cases, eg. mapping errors in #762, we only want to handle if an error arises, and don't care about the return values. Since we assume errors are not the normal case, we add a lot of gas overhead to all the callbacks we ignore. It would be more efficient to be able to filter these out in Go, and just get callbacks on error if that is what we want. 

In particular, I want to [extend `SubMsg`](https://github.com/CosmWasm/cosmwasm/blob/main/packages/std/src/results/subcall.rs#L13-L21) with one more field:

```rust
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct SubMsg<T = Empty>
where
    T: Clone + fmt::Debug + PartialEq + JsonSchema,
{
    pub id: u64,
    pub msg: CosmosMsg<T>,
    pub gas_limit: Option<u64>,
    pub only_error: Option<bool>,
}
```

If only_error is set to `true`, then we only make the response callback if there was an error (incl running out of gas), otherwise, just handle it like a message.

I made `only_error` as an `Option` because I feel it is usually unset and we can make the normal JSON smaller (and use less gas) by simply omitting the field. `Some(true)` may be a bit wordy, but `None` and `false` are the same, which is the typical case. (I can change it to just `bool` if there is disagreement)



Pull request:
854 submsg reply control
Closes #854 

Patch:
diff --git a/contracts/reflect/schema/execute_msg.json b/contracts/reflect/schema/execute_msg.json
index f8ac0a7c9..5fb601b79 100644
--- a/contracts/reflect/schema/execute_msg.json
+++ b/contracts/reflect/schema/execute_msg.json
@@ -396,6 +396,15 @@
         }
       }
     },
+    "ReplyOn": {
+      "description": "Use this to define when the contract gets a response callback. If you only need it for errors or success you can select just those in order to save gas.",
+      "type": "string",
+      "enum": [
+        "always",
+        "error",
+        "success"
+      ]
+    },
     "StakingMsg": {
       "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
       "anyOf": [
@@ -517,7 +526,8 @@
       "type": "object",
       "required": [
         "id",
-        "msg"
+        "msg",
+        "reply_on"
       ],
       "properties": {
         "gas_limit": {
@@ -535,6 +545,9 @@
         },
         "msg": {
           "$ref": "#/definitions/CosmosMsg_for_CustomMsg"
+        },
+        "reply_on": {
+          "$ref": "#/definitions/ReplyOn"
         }
       }
     },
diff --git a/contracts/reflect/schema/response_for__custom_msg.json b/contracts/reflect/schema/response_for__custom_msg.json
index b0160784b..50c25700d 100644
--- a/contracts/reflect/schema/response_for__custom_msg.json
+++ b/contracts/reflect/schema/response_for__custom_msg.json
@@ -384,6 +384,15 @@
         }
       }
     },
+    "ReplyOn": {
+      "description": "Use this to define when the contract gets a response callback. If you only need it for errors or success you can select just those in order to save gas.",
+      "type": "string",
+      "enum": [
+        "always",
+        "error",
+        "success"
+      ]
+    },
     "StakingMsg": {
       "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto",
       "anyOf": [
@@ -505,7 +514,8 @@
       "type": "object",
       "required": [
         "id",
-        "msg"
+        "msg",
+        "reply_on"
       ],
       "properties": {
         "gas_limit": {
@@ -523,6 +533,9 @@
         },
         "msg": {
           "$ref": "#/definitions/CosmosMsg_for_CustomMsg"
+        },
+        "reply_on": {
+          "$ref": "#/definitions/ReplyOn"
         }
       }
     },
diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs
index bac48b5ff..bff304510 100644
--- a/contracts/reflect/src/contract.rs
+++ b/contracts/reflect/src/contract.rs
@@ -198,7 +198,7 @@ mod tests {
     use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR};
     use cosmwasm_std::{
         coin, coins, from_binary, AllBalanceResponse, Api, BankMsg, BankQuery, Binary,
-        ContractResult, Event, StakingMsg, StdError, SubcallResponse,
+        ContractResult, Event, ReplyOn, StakingMsg, StdError, SubcallResponse,
     };
 
     #[test]
@@ -466,6 +466,7 @@ mod tests {
                 amount: coins(1, "token"),
             }
             .into(),
+            reply_on: ReplyOn::default(),
         };
 
         let msg = ExecuteMsg::ReflectSubCall {
diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs
index 9118e13ff..0bce6cf7c 100644
--- a/packages/std/src/lib.rs
+++ b/packages/std/src/lib.rs
@@ -44,8 +44,8 @@ pub use crate::query::{
 };
 pub use crate::results::{
     attr, wasm_execute, wasm_instantiate, Attribute, BankMsg, ContractResult, CosmosMsg, Empty,
-    Event, QueryResponse, Reply, Response, StakingMsg, SubMsg, SubcallResponse, SystemResult,
-    WasmMsg,
+    Event, QueryResponse, Reply, ReplyOn, Response, StakingMsg, SubMsg, SubcallResponse,
+    SystemResult, WasmMsg,
 };
 #[allow(deprecated)]
 pub use crate::results::{Context, HandleResponse, InitResponse, MigrateResponse};
diff --git a/packages/std/src/results/mod.rs b/packages/std/src/results/mod.rs
index 5b1fcb738..3075b0ca9 100644
--- a/packages/std/src/results/mod.rs
+++ b/packages/std/src/results/mod.rs
@@ -18,7 +18,7 @@ pub use cosmos_msg::{wasm_execute, wasm_instantiate, BankMsg, CosmosMsg, Staking
 pub use empty::Empty;
 pub use query::QueryResponse;
 pub use response::Response;
-pub use subcall::{Event, Reply, SubMsg, SubcallResponse};
+pub use subcall::{Event, Reply, ReplyOn, SubMsg, SubcallResponse};
 pub use system_result::SystemResult;
 
 #[deprecated(since = "0.14.0", note = "Renamed to Response.")]
diff --git a/packages/std/src/results/response.rs b/packages/std/src/results/response.rs
index 39d536373..f50d5a133 100644
--- a/packages/std/src/results/response.rs
+++ b/packages/std/src/results/response.rs
@@ -2,7 +2,7 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use std::fmt;
 
-use crate::Binary;
+use crate::{Binary, ReplyOn};
 
 use super::{Attribute, CosmosMsg, Empty};
 use crate::results::SubMsg;
@@ -125,11 +125,13 @@ where
         id: u64,
         msg: U,
         gas_limit: Option<u64>,
+        reply_on: ReplyOn,
     ) {
         let sub = SubMsg {
             id,
             msg: msg.into(),
             gas_limit,
+            reply_on,
         };
         self.submessages.push(sub);
     }
@@ -157,6 +159,7 @@ mod tests {
                 }
                 .into(),
                 gas_limit: Some(12345u64),
+                reply_on: ReplyOn::Always,
             }],
             messages: vec![BankMsg::Send {
                 to_address: HumanAddr::from("you"),
diff --git a/packages/std/src/results/subcall.rs b/packages/std/src/results/subcall.rs
index ba5641ee6..8447dc0e4 100644
--- a/packages/std/src/results/subcall.rs
+++ b/packages/std/src/results/subcall.rs
@@ -6,6 +6,26 @@ use crate::{Binary, ContractResult};
 
 use super::{Attribute, CosmosMsg, Empty};
 
+/// Use this to define when the contract gets a response callback.
+/// If you only need it for errors or success you can select just those in order
+/// to save gas.
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ReplyOn {
+    /// Always perform a callback after SubMsg is processed
+    Always,
+    /// Only callback if SubMsg returned an error, no callback on success case
+    Error,
+    /// Only callback if SubMsg was successful, no callback on error case
+    Success,
+}
+
+impl Default for ReplyOn {
+    fn default() -> Self {
+        ReplyOn::Always
+    }
+}
+
 /// A sub-message that will guarantee a subcall_response callback on success or error
 /// Note on error the subcall will revert any partial state changes due to this message,
 /// but not revert any state changes in the calling contract (that must be done in the
@@ -18,6 +38,7 @@ where
     pub id: u64,
     pub msg: CosmosMsg<T>,
     pub gas_limit: Option<u64>,
+    pub reply_on: ReplyOn,
 }
 
 /// The Result object returned to subcall_response. We always get the same id back


Test patch:
diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs
index 97070882e..ee744cadd 100644
--- a/contracts/reflect/tests/integration.rs
+++ b/contracts/reflect/tests/integration.rs
@@ -204,6 +204,7 @@ fn reflect_subcall() {
             amount: coins(1, "token"),
         }
         .into(),
+        reply_on: Default::default(),
     };
 
     let msg = ExecuteMsg::ReflectSubCall {