diff --git a/contracts/ibc-reflect/src/contract.rs b/contracts/ibc-reflect/src/contract.rs index bde021febc..7447e3f883 100644 --- a/contracts/ibc-reflect/src/contract.rs +++ b/contracts/ibc-reflect/src/contract.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{ - entry_point, from_slice, to_binary, wasm_execute, BankMsg, Binary, ContractResult, CosmosMsg, - Deps, DepsMut, Empty, Env, Event, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, + entry_point, from_slice, to_binary, wasm_execute, BankMsg, Binary, CosmosMsg, Deps, DepsMut, + Empty, Env, Event, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcOrder, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, MessageInfo, Order, QueryResponse, Reply, Response, StdError, StdResult, - SubMsg, SubMsgExecutionResponse, WasmMsg, + SubMsg, SubMsgExecutionResponse, SubMsgResult, WasmMsg, }; use crate::msg::{ @@ -35,10 +35,10 @@ pub fn instantiate( #[entry_point] pub fn reply(deps: DepsMut, _env: Env, reply: Reply) -> StdResult { match (reply.id, reply.result) { - (RECEIVE_DISPATCH_ID, ContractResult::Err(err)) => { + (RECEIVE_DISPATCH_ID, SubMsgResult::Err(err)) => { Ok(Response::new().set_data(encode_ibc_error(err))) } - (INIT_CALLBACK_ID, ContractResult::Ok(response)) => handle_init_callback(deps, response), + (INIT_CALLBACK_ID, SubMsgResult::Ok(response)) => handle_init_callback(deps, response), _ => Err(StdError::generic_err("invalid reply id or result")), } } @@ -387,7 +387,7 @@ mod tests { // fake a reply and ensure this works let response = Reply { id, - result: ContractResult::Ok(SubMsgExecutionResponse { + result: SubMsgResult::Ok(SubMsgExecutionResponse { events: fake_events(&account), data: None, }), @@ -462,7 +462,7 @@ mod tests { // fake a reply and ensure this works let response = Reply { id, - result: ContractResult::Ok(SubMsgExecutionResponse { + result: SubMsgResult::Ok(SubMsgExecutionResponse { events: fake_events(REFLECT_ADDR), data: None, }), diff --git a/contracts/ibc-reflect/tests/integration.rs b/contracts/ibc-reflect/tests/integration.rs index 62a86eb2b5..29a2a22a0a 100644 --- a/contracts/ibc-reflect/tests/integration.rs +++ b/contracts/ibc-reflect/tests/integration.rs @@ -23,7 +23,7 @@ use cosmwasm_std::testing::{ }; use cosmwasm_std::{ attr, coins, BankMsg, ContractResult, CosmosMsg, Event, IbcBasicResponse, IbcOrder, - IbcReceiveResponse, Reply, Response, SubMsgExecutionResponse, WasmMsg, + IbcReceiveResponse, Reply, Response, SubMsgExecutionResponse, SubMsgResult, WasmMsg, }; use cosmwasm_vm::testing::{ ibc_channel_connect, ibc_channel_open, ibc_packet_receive, instantiate, mock_env, mock_info, @@ -95,7 +95,7 @@ fn connect( // fake a reply and ensure this works let response = Reply { id, - result: ContractResult::Ok(SubMsgExecutionResponse { + result: SubMsgResult::Ok(SubMsgExecutionResponse { events: fake_events(&account), data: None, }), @@ -171,7 +171,7 @@ fn proper_handshake_flow() { // we get the callback from reflect let response = Reply { id, - result: ContractResult::Ok(SubMsgExecutionResponse { + result: SubMsgResult::Ok(SubMsgExecutionResponse { events: fake_events(REFLECT_ADDR), data: None, }), diff --git a/contracts/reflect/src/contract.rs b/contracts/reflect/src/contract.rs index a51413ae29..dc1e7c3e24 100644 --- a/contracts/reflect/src/contract.rs +++ b/contracts/reflect/src/contract.rs @@ -177,8 +177,8 @@ mod tests { use crate::testing::mock_dependencies_with_custom_querier; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - coin, coins, from_binary, AllBalanceResponse, BankMsg, BankQuery, Binary, ContractResult, - Event, StakingMsg, StdError, SubMsgExecutionResponse, + coin, coins, from_binary, AllBalanceResponse, BankMsg, BankQuery, Binary, Event, + StakingMsg, StdError, SubMsgExecutionResponse, SubMsgResult, }; #[test] @@ -435,7 +435,7 @@ mod tests { let id = 123u64; let data = Binary::from(b"foobar"); let events = vec![Event::new("message").add_attribute("signer", "caller-addr")]; - let result = ContractResult::Ok(SubMsgExecutionResponse { + let result = SubMsgResult::Ok(SubMsgExecutionResponse { events: events.clone(), data: Some(data.clone()), }); diff --git a/contracts/reflect/tests/integration.rs b/contracts/reflect/tests/integration.rs index 90b6d9d538..a448250542 100644 --- a/contracts/reflect/tests/integration.rs +++ b/contracts/reflect/tests/integration.rs @@ -19,7 +19,7 @@ use cosmwasm_std::{ coin, coins, from_binary, BankMsg, Binary, Coin, ContractResult, Event, Reply, Response, - StakingMsg, SubMsg, SubMsgExecutionResponse, SystemResult, + StakingMsg, SubMsg, SubMsgExecutionResponse, SubMsgResult, SystemResult, }; use cosmwasm_vm::{ testing::{ @@ -226,7 +226,7 @@ fn reply_and_query() { let id = 123u64; let data = Binary::from(b"foobar"); let events = vec![Event::new("message").add_attribute("signer", "caller-addr")]; - let result = ContractResult::Ok(SubMsgExecutionResponse { + let result = SubMsgResult::Ok(SubMsgExecutionResponse { events: events.clone(), data: Some(data.clone()), }); diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index 8fee4f3282..858effaa7c 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -60,7 +60,7 @@ pub use crate::query::{ChannelResponse, IbcQuery, ListChannelsResponse, PortIdRe pub use crate::results::{ attr, wasm_execute, wasm_instantiate, Attribute, BankMsg, ContractResult, CosmosMsg, CustomMsg, Empty, Event, QueryResponse, Reply, ReplyOn, Response, SubMsg, SubMsgExecutionResponse, - SystemResult, WasmMsg, + SubMsgResult, SystemResult, WasmMsg, }; #[cfg(feature = "staking")] pub use crate::results::{DistributionMsg, StakingMsg}; diff --git a/packages/std/src/results/mod.rs b/packages/std/src/results/mod.rs index 2bd4663fb3..a208279ac6 100644 --- a/packages/std/src/results/mod.rs +++ b/packages/std/src/results/mod.rs @@ -19,5 +19,5 @@ pub use empty::Empty; pub use events::{attr, Attribute, Event}; pub use query::QueryResponse; pub use response::Response; -pub use submessages::{Reply, ReplyOn, SubMsg, SubMsgExecutionResponse}; +pub use submessages::{Reply, ReplyOn, SubMsg, SubMsgExecutionResponse, SubMsgResult}; pub use system_result::SystemResult; diff --git a/packages/std/src/results/submessages.rs b/packages/std/src/results/submessages.rs index ef3d8d5681..bed700d102 100644 --- a/packages/std/src/results/submessages.rs +++ b/packages/std/src/results/submessages.rs @@ -1,7 +1,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::{Binary, ContractResult}; +use crate::Binary; use super::{CosmosMsg, Empty, Event}; @@ -100,12 +100,210 @@ 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, - pub result: ContractResult, + pub result: SubMsgResult, } -/// The information we get back from a successful sub-call, with full sdk events +/// This is the result type that is returned from a sub message execution. +/// +/// We use a custom type here instead of Rust's Result because we want to be able to +/// define the serialization, which is a public interface. Every language that compiles +/// to Wasm and runs in the ComsWasm VM needs to create the same JSON representation. +/// +/// Until version 1.0.0-beta5, `ContractResult` was used instead +/// of this type. Once serialized, the two types are the same. However, in the Rust type +/// system we want different types for clarity and documenation reasons. +/// +/// # Examples +/// +/// Success: +/// +/// ``` +/// # use cosmwasm_std::{to_vec, Binary, Event, SubMsgExecutionResponse, SubMsgResult}; +/// let response = SubMsgExecutionResponse { +/// data: Some(Binary::from_base64("MTIzCg==").unwrap()), +/// events: vec![Event::new("wasm").add_attribute("fo", "ba")], +/// }; +/// let result: SubMsgResult = SubMsgResult::Ok(response); +/// assert_eq!(to_vec(&result).unwrap(), br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg=="}}"#); +/// ``` +/// +/// Failure: +/// +/// ``` +/// # use cosmwasm_std::{to_vec, SubMsgResult, Response}; +/// let error_msg = String::from("Something went wrong"); +/// let result = SubMsgResult::Err(error_msg); +/// assert_eq!(to_vec(&result).unwrap(), br#"{"error":"Something went wrong"}"#); +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SubMsgResult { + Ok(SubMsgExecutionResponse), + /// An error type that every custom error created by contract developers can be converted to. + /// This could potientially have more structure, but String is the easiest. + #[serde(rename = "error")] + Err(String), +} + +// Implementations here mimic the Result API and should be implemented via a conversion to Result +// to ensure API consistency +impl SubMsgResult { + /// Converts a `SubMsgResult` to a `Result` as a convenient way + /// to access the full Result API. + pub fn into_result(self) -> Result { + Result::::from(self) + } + + pub fn unwrap(self) -> SubMsgExecutionResponse { + self.into_result().unwrap() + } + + pub fn unwrap_err(self) -> String { + self.into_result().unwrap_err() + } + + pub fn is_ok(&self) -> bool { + matches!(self, SubMsgResult::Ok(_)) + } + + pub fn is_err(&self) -> bool { + matches!(self, SubMsgResult::Err(_)) + } +} + +impl From> for SubMsgResult { + fn from(original: Result) -> SubMsgResult { + match original { + Ok(value) => SubMsgResult::Ok(value), + Err(err) => SubMsgResult::Err(err.to_string()), + } + } +} + +impl From for Result { + fn from(original: SubMsgResult) -> Result { + match original { + SubMsgResult::Ok(value) => Ok(value), + SubMsgResult::Err(err) => Err(err), + } + } +} + +/// The information we get back from a successful sub message execution, +/// with full Cosmos SDK events. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct SubMsgExecutionResponse { pub events: Vec, pub data: Option, } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{from_slice, to_vec, StdError, StdResult}; + + #[test] + fn sub_msg_result_serialization_works() { + let result = SubMsgResult::Ok(SubMsgExecutionResponse { + data: None, + events: vec![], + }); + assert_eq!( + &to_vec(&result).unwrap(), + br#"{"ok":{"events":[],"data":null}}"# + ); + + let result = SubMsgResult::Ok(SubMsgExecutionResponse { + data: Some(Binary::from_base64("MTIzCg==").unwrap()), + events: vec![Event::new("wasm").add_attribute("fo", "ba")], + }); + assert_eq!( + &to_vec(&result).unwrap(), + br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg=="}}"# + ); + + let result: SubMsgResult = SubMsgResult::Err("broken".to_string()); + assert_eq!(&to_vec(&result).unwrap(), b"{\"error\":\"broken\"}"); + } + + #[test] + fn sub_msg_result_deserialization_works() { + let result: SubMsgResult = from_slice(br#"{"ok":{"events":[],"data":null}}"#).unwrap(); + assert_eq!( + result, + SubMsgResult::Ok(SubMsgExecutionResponse { + events: vec![], + data: None, + }) + ); + + let result: SubMsgResult = from_slice( + br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"fo","value":"ba"}]}],"data":"MTIzCg=="}}"#).unwrap(); + assert_eq!( + result, + SubMsgResult::Ok(SubMsgExecutionResponse { + data: Some(Binary::from_base64("MTIzCg==").unwrap()), + events: vec![Event::new("wasm").add_attribute("fo", "ba")], + }) + ); + + let result: SubMsgResult = from_slice(br#"{"error":"broken"}"#).unwrap(); + assert_eq!(result, SubMsgResult::Err("broken".to_string())); + + // fails for additional attributes + let parse: StdResult = from_slice(br#"{"unrelated":321,"error":"broken"}"#); + match parse.unwrap_err() { + StdError::ParseErr { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + let parse: StdResult = from_slice(br#"{"error":"broken","unrelated":321}"#); + match parse.unwrap_err() { + StdError::ParseErr { .. } => {} + err => panic!("Unexpected error: {:?}", err), + } + } + + #[test] + fn sub_msg_result_can_convert_from_core_result() { + let original: Result = Ok(SubMsgExecutionResponse { + data: Some(Binary::from_base64("MTIzCg==").unwrap()), + events: vec![], + }); + let converted: SubMsgResult = original.into(); + assert_eq!( + converted, + SubMsgResult::Ok(SubMsgExecutionResponse { + data: Some(Binary::from_base64("MTIzCg==").unwrap()), + events: vec![], + }) + ); + + let original: Result = + Err(StdError::generic_err("broken")); + let converted: SubMsgResult = original.into(); + assert_eq!( + converted, + SubMsgResult::Err("Generic error: broken".to_string()) + ); + } + + #[test] + fn sub_msg_result_can_convert_to_core_result() { + let original = SubMsgResult::Ok(SubMsgExecutionResponse { + data: Some(Binary::from_base64("MTIzCg==").unwrap()), + events: vec![], + }); + let converted: Result = original.into(); + assert_eq!( + converted, + Ok(SubMsgExecutionResponse { + data: Some(Binary::from_base64("MTIzCg==").unwrap()), + events: vec![], + }) + ); + + let original = SubMsgResult::Err("went wrong".to_string()); + let converted: Result = original.into(); + assert_eq!(converted, Err("went wrong".to_string())); + } +} diff --git a/packages/vm/src/calls.rs b/packages/vm/src/calls.rs index 0d2aca7475..7a3c7a8c2f 100644 --- a/packages/vm/src/calls.rs +++ b/packages/vm/src/calls.rs @@ -682,6 +682,7 @@ mod tests { }; use cosmwasm_std::{ Empty, Event, IbcAcknowledgement, IbcOrder, Reply, ReplyOn, SubMsgExecutionResponse, + SubMsgResult, }; static CONTRACT: &[u8] = include_bytes!("../testdata/ibc_reflect.wasm"); const IBC_VERSION: &str = "ibc-reflect-v1"; @@ -726,7 +727,7 @@ mod tests { // which creates a reflect account. here we get the callback let response = Reply { id, - result: ContractResult::Ok(SubMsgExecutionResponse { + result: SubMsgResult::Ok(SubMsgExecutionResponse { events: vec![event], data: None, }),