From aef3f74080951bd042adda954ac1671fb82db281 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 23 Aug 2023 09:29:26 +0200 Subject: [PATCH 1/3] Add StdAck --- CHANGELOG.md | 2 + packages/std/src/lib.rs | 2 + packages/std/src/stdack.rs | 156 +++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 packages/std/src/stdack.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e116a990d1..6eff430a0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,9 @@ and this project adheres to - cosmwasm-std: Make `abs_diff` const for `Uint{256,512}` and `Int{64,128,256,512}`. It is now const for all integer types. - cosmwasm-std: Implement `TryFrom` for `Decimal` ([#1832]) +- cosmwasm-std: Add `StdAck`. ([#1512]) +[#1512]: https://github.com/CosmWasm/cosmwasm/issues/1512 [#1799]: https://github.com/CosmWasm/cosmwasm/pull/1799 [#1806]: https://github.com/CosmWasm/cosmwasm/pull/1806 [#1832]: https://github.com/CosmWasm/cosmwasm/pull/1832 diff --git a/packages/std/src/lib.rs b/packages/std/src/lib.rs index d44d686da6..6bccc14595 100644 --- a/packages/std/src/lib.rs +++ b/packages/std/src/lib.rs @@ -28,6 +28,7 @@ mod query; mod results; mod sections; mod serde; +mod stdack; mod storage; mod timestamp; mod traits; @@ -87,6 +88,7 @@ pub use crate::results::{DistributionMsg, StakingMsg}; #[cfg(feature = "stargate")] pub use crate::results::{GovMsg, VoteOption}; pub use crate::serde::{from_binary, from_slice, to_binary, to_vec}; +pub use crate::stdack::StdAck; pub use crate::storage::MemoryStorage; pub use crate::timestamp::Timestamp; pub use crate::traits::{Api, Querier, QuerierResult, QuerierWrapper, Storage}; diff --git a/packages/std/src/stdack.rs b/packages/std/src/stdack.rs new file mode 100644 index 0000000000..860e82c6a7 --- /dev/null +++ b/packages/std/src/stdack.rs @@ -0,0 +1,156 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::binary::Binary; +use crate::to_binary; + +/// This is a standard IBC acknowledgement type. IBC application are free +/// to use any acknowledgement format they want. However, for compatibility +/// purposes it is recommended to use this. +/// +/// The original proto definition can be found at +/// and . +/// +/// In contrast to the original idea, [ICS-20](https://github.com/cosmos/ibc/tree/ed849c7bacf16204e9509f0f0df325391f3ce25c/spec/app/ics-020-fungible-token-transfer#technical-specification) and CosmWasm IBC protocols +/// use JSON instead of a protobuf serialization. +/// +/// If ibc_receive_packet returns Err(), then x/wasm runtime will rollback the state and return an error message in this format. +/// +/// ## Examples +/// +/// For your convenience, there are success and error constructors. +/// +/// ``` +/// use cosmwasm_std::StdAck; +/// +/// let ack1 = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20. +/// assert!(ack1.is_success()); +/// +/// let ack2 = StdAck::error("kaputt"); // Some free text error message +/// assert!(ack2.is_error()); +/// ``` +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum StdAck { + Result(Binary), + Error(String), +} + +impl StdAck { + /// Creates a success ack with the given data + pub fn success(data: impl Into) -> Self { + StdAck::Result(data.into()) + } + + /// Creates an error ack + pub fn error(err: impl Into) -> Self { + StdAck::Error(err.into()) + } + + #[must_use = "if you intended to assert that this is a success, consider `.unwrap()` instead"] + #[inline] + pub const fn is_success(&self) -> bool { + matches!(*self, StdAck::Result(_)) + } + + #[must_use = "if you intended to assert that this is an error, consider `.unwrap_err()` instead"] + #[inline] + pub const fn is_error(&self) -> bool { + !self.is_success() + } + + /// Serialized the ack to binary using JSON. This used for setting the acknowledgement + /// field in IbcReceiveResponse. + /// + /// ## Examples + /// + /// Show how the acknowledgement looks on the write: + /// + /// ``` + /// # use cosmwasm_std::StdAck; + /// let ack1 = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20. + /// assert_eq!(ack1.to_binary(), br#"{"result":"AQ=="}"#); + /// + /// let ack2 = StdAck::error("kaputt"); // Some free text error message + /// assert_eq!(ack2.to_binary(), br#"{"error":"kaputt"}"#); + /// ``` + /// + /// Set acknowledgement field in `IbcReceiveResponse`: + /// + /// ```ignore + /// use cosmwasm_std::{StdAck, IbcReceiveResponse}; + /// + /// let ack = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20. + /// + /// let res = IbcReceiveResponse::new().set_ack(ack.to_binary()); + /// let res = IbcReceiveResponse::new().set_ack(ack); // Does the same but consumes the instance + /// ``` + pub fn to_binary(&self) -> Binary { + // Pretty sure this cannot fail. If that changes we can create a non-failing implementation here. + to_binary(&self).unwrap() + } + + pub fn unwrap(self) -> Binary { + match self { + StdAck::Result(data) => data, + StdAck::Error(err) => panic!("{}", err), + } + } + + pub fn unwrap_err(self) -> String { + match self { + StdAck::Result(_) => panic!("not an error"), + StdAck::Error(err) => err, + } + } +} + +impl From for Binary { + fn from(original: StdAck) -> Binary { + original.to_binary() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stdack_success_works() { + let success = StdAck::success(b"foo"); + match success { + StdAck::Result(data) => assert_eq!(data, b"foo"), + StdAck::Error(_err) => panic!("must not be an error"), + } + } + + #[test] + fn stdack_error_works() { + let err = StdAck::error("bar"); + match err { + StdAck::Result(_data) => panic!("must not be a success"), + StdAck::Error(err) => assert_eq!(err, "bar"), + } + } + + #[test] + fn stdack_is_success_is_error_work() { + let success = StdAck::success(b"foo"); + let err = StdAck::error("bar"); + // is_success + assert!(success.is_success()); + assert!(!err.is_success()); + // is_eror + assert!(!success.is_error()); + assert!(err.is_error()); + } + + #[test] + fn stdack_to_binary_works() { + let ack1 = StdAck::success(b"\x01"); + assert_eq!(ack1.to_binary(), br#"{"result":"AQ=="}"#); + + let ack2 = StdAck::error("kaputt"); + assert_eq!(ack2.to_binary(), br#"{"error":"kaputt"}"#); + } +} From 4df0e340eba37db9e8e18b107f292d402ad93c23 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 23 Aug 2023 10:39:59 +0200 Subject: [PATCH 2/3] Rename to StdAck::Success --- packages/std/src/stdack.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/std/src/stdack.rs b/packages/std/src/stdack.rs index 860e82c6a7..46acad3a2b 100644 --- a/packages/std/src/stdack.rs +++ b/packages/std/src/stdack.rs @@ -14,7 +14,11 @@ use crate::to_binary; /// In contrast to the original idea, [ICS-20](https://github.com/cosmos/ibc/tree/ed849c7bacf16204e9509f0f0df325391f3ce25c/spec/app/ics-020-fungible-token-transfer#technical-specification) and CosmWasm IBC protocols /// use JSON instead of a protobuf serialization. /// -/// If ibc_receive_packet returns Err(), then x/wasm runtime will rollback the state and return an error message in this format. +/// For compatibility, we use the field name "result" for the success case in JSON. +/// However, all Rust APIs use the term "success" for clarity and discriminability from [Result]. +/// +/// If ibc_receive_packet returns Err(), then x/wasm runtime will rollback the state and +/// return an error message in this format. /// /// ## Examples /// @@ -32,14 +36,15 @@ use crate::to_binary; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum StdAck { - Result(Binary), + #[serde(rename = "result")] + Success(Binary), Error(String), } impl StdAck { /// Creates a success ack with the given data pub fn success(data: impl Into) -> Self { - StdAck::Result(data.into()) + StdAck::Success(data.into()) } /// Creates an error ack @@ -50,7 +55,7 @@ impl StdAck { #[must_use = "if you intended to assert that this is a success, consider `.unwrap()` instead"] #[inline] pub const fn is_success(&self) -> bool { - matches!(*self, StdAck::Result(_)) + matches!(*self, StdAck::Success(_)) } #[must_use = "if you intended to assert that this is an error, consider `.unwrap_err()` instead"] @@ -92,14 +97,14 @@ impl StdAck { pub fn unwrap(self) -> Binary { match self { - StdAck::Result(data) => data, + StdAck::Success(data) => data, StdAck::Error(err) => panic!("{}", err), } } pub fn unwrap_err(self) -> String { match self { - StdAck::Result(_) => panic!("not an error"), + StdAck::Success(_) => panic!("not an error"), StdAck::Error(err) => err, } } @@ -119,7 +124,7 @@ mod tests { fn stdack_success_works() { let success = StdAck::success(b"foo"); match success { - StdAck::Result(data) => assert_eq!(data, b"foo"), + StdAck::Success(data) => assert_eq!(data, b"foo"), StdAck::Error(_err) => panic!("must not be an error"), } } @@ -128,7 +133,7 @@ mod tests { fn stdack_error_works() { let err = StdAck::error("bar"); match err { - StdAck::Result(_data) => panic!("must not be a success"), + StdAck::Success(_data) => panic!("must not be a success"), StdAck::Error(err) => assert_eq!(err, "bar"), } } From 94d0eea61d66b0d98b264d1ae7c7f84feefa1776 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 23 Aug 2023 11:12:10 +0200 Subject: [PATCH 3/3] Improve some docs around StdAck -> Binary conversion --- packages/std/src/ibc.rs | 11 +++++++++++ packages/std/src/stdack.rs | 8 +++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/std/src/ibc.rs b/packages/std/src/ibc.rs index f56944691c..d3afd68887 100644 --- a/packages/std/src/ibc.rs +++ b/packages/std/src/ibc.rs @@ -666,6 +666,17 @@ impl IbcReceiveResponse { } /// Set the acknowledgement for this response. + /// + /// ## Examples + /// + /// ``` + /// use cosmwasm_std::{StdAck, IbcReceiveResponse}; + /// + /// fn make_response_with_ack() -> IbcReceiveResponse { + /// let ack = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20. + /// IbcReceiveResponse::new().set_ack(ack) + /// } + /// ``` pub fn set_ack(mut self, ack: impl Into) -> Self { self.acknowledgement = ack.into(); self diff --git a/packages/std/src/stdack.rs b/packages/std/src/stdack.rs index 46acad3a2b..e933257a08 100644 --- a/packages/std/src/stdack.rs +++ b/packages/std/src/stdack.rs @@ -82,15 +82,17 @@ impl StdAck { /// /// Set acknowledgement field in `IbcReceiveResponse`: /// - /// ```ignore + /// ``` /// use cosmwasm_std::{StdAck, IbcReceiveResponse}; /// /// let ack = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20. /// - /// let res = IbcReceiveResponse::new().set_ack(ack.to_binary()); - /// let res = IbcReceiveResponse::new().set_ack(ack); // Does the same but consumes the instance + /// let res: IbcReceiveResponse = IbcReceiveResponse::new().set_ack(ack.to_binary()); + /// let res: IbcReceiveResponse = IbcReceiveResponse::new().set_ack(ack); // Does the same but consumes the instance /// ``` pub fn to_binary(&self) -> Binary { + // We need a non-failing StdAck -> Binary conversion to allow using StdAck in + // `impl Into` arguments. // Pretty sure this cannot fail. If that changes we can create a non-failing implementation here. to_binary(&self).unwrap() }