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/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/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..e933257a08 --- /dev/null +++ b/packages/std/src/stdack.rs @@ -0,0 +1,163 @@ +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. +/// +/// 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 +/// +/// 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 { + #[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::Success(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::Success(_)) + } + + #[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`: + /// + /// ``` + /// use cosmwasm_std::{StdAck, IbcReceiveResponse}; + /// + /// let ack = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20. + /// + /// 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() + } + + pub fn unwrap(self) -> Binary { + match self { + StdAck::Success(data) => data, + StdAck::Error(err) => panic!("{}", err), + } + } + + pub fn unwrap_err(self) -> String { + match self { + StdAck::Success(_) => 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::Success(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::Success(_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"}"#); + } +}