cosmwasm_std/stdack.rs
1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use crate::prelude::*;
5use crate::to_json_binary;
6use crate::Binary;
7
8/// This is a standard IBC acknowledgement type. IBC application are free
9/// to use any acknowledgement format they want. However, for compatibility
10/// purposes it is recommended to use this.
11///
12/// The original proto definition can be found at <https://github.com/cosmos/cosmos-sdk/blob/v0.42.0/proto/ibc/core/channel/v1/channel.proto#L141-L147>
13/// and <https://github.com/cosmos/ibc/tree/ed849c7bac/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope>.
14///
15/// 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
16/// use JSON instead of a protobuf serialization.
17///
18/// For compatibility, we use the field name "result" for the success case in JSON.
19/// However, all Rust APIs use the term "success" for clarity and discriminability from [Result].
20///
21/// If ibc_receive_packet returns Err(), then x/wasm runtime will rollback the state and
22/// return an error message in this format.
23///
24/// ## Examples
25///
26/// For your convenience, there are success and error constructors.
27///
28/// ```
29/// use cosmwasm_std::StdAck;
30///
31/// let ack1 = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20.
32/// assert!(ack1.is_success());
33///
34/// let ack2 = StdAck::error("kaputt"); // Some free text error message
35/// assert!(ack2.is_error());
36/// ```
37#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
38#[serde(rename_all = "snake_case")]
39pub enum StdAck {
40 #[serde(rename = "result")]
41 Success(Binary),
42 Error(String),
43}
44
45impl StdAck {
46 /// Creates a success ack with the given data
47 pub fn success(data: impl Into<Binary>) -> Self {
48 StdAck::Success(data.into())
49 }
50
51 /// Creates an error ack
52 pub fn error(err: impl Into<String>) -> Self {
53 StdAck::Error(err.into())
54 }
55
56 #[must_use = "if you intended to assert that this is a success, consider `.unwrap()` instead"]
57 #[inline]
58 pub const fn is_success(&self) -> bool {
59 matches!(*self, StdAck::Success(_))
60 }
61
62 #[must_use = "if you intended to assert that this is an error, consider `.unwrap_err()` instead"]
63 #[inline]
64 pub const fn is_error(&self) -> bool {
65 !self.is_success()
66 }
67
68 /// Serialized the ack to binary using JSON. This used for setting the acknowledgement
69 /// field in IbcReceiveResponse.
70 ///
71 /// ## Examples
72 ///
73 /// Show how the acknowledgement looks on the write:
74 ///
75 /// ```
76 /// # use cosmwasm_std::StdAck;
77 /// let ack1 = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20.
78 /// assert_eq!(ack1.to_binary(), br#"{"result":"AQ=="}"#);
79 ///
80 /// let ack2 = StdAck::error("kaputt"); // Some free text error message
81 /// assert_eq!(ack2.to_binary(), br#"{"error":"kaputt"}"#);
82 /// ```
83 ///
84 /// Set acknowledgement field in `IbcReceiveResponse`:
85 ///
86 /// ```
87 /// use cosmwasm_std::{StdAck, IbcReceiveResponse};
88 ///
89 /// let ack = StdAck::success(b"\x01"); // 0x01 is a FungibleTokenPacketSuccess from ICS-20.
90 ///
91 /// let res: IbcReceiveResponse = IbcReceiveResponse::new(ack.to_binary());
92 /// let res: IbcReceiveResponse = IbcReceiveResponse::new(ack); // Does the same but consumes the instance
93 /// ```
94 pub fn to_binary(&self) -> Binary {
95 // We need a non-failing StdAck -> Binary conversion to allow using StdAck in
96 // `impl Into<Binary>` arguments.
97 // Pretty sure this cannot fail. If that changes we can create a non-failing implementation here.
98 to_json_binary(&self).unwrap()
99 }
100
101 pub fn unwrap(self) -> Binary {
102 match self {
103 StdAck::Success(data) => data,
104 StdAck::Error(err) => panic!("{}", err),
105 }
106 }
107
108 pub fn unwrap_err(self) -> String {
109 match self {
110 StdAck::Success(_) => panic!("not an error"),
111 StdAck::Error(err) => err,
112 }
113 }
114}
115
116impl From<StdAck> for Binary {
117 fn from(original: StdAck) -> Binary {
118 original.to_binary()
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn stdack_success_works() {
128 let success = StdAck::success(b"foo");
129 match success {
130 StdAck::Success(data) => assert_eq!(data, b"foo"),
131 StdAck::Error(_err) => panic!("must not be an error"),
132 }
133 }
134
135 #[test]
136 fn stdack_error_works() {
137 let err = StdAck::error("bar");
138 match err {
139 StdAck::Success(_data) => panic!("must not be a success"),
140 StdAck::Error(err) => assert_eq!(err, "bar"),
141 }
142 }
143
144 #[test]
145 fn stdack_is_success_is_error_work() {
146 let success = StdAck::success(b"foo");
147 let err = StdAck::error("bar");
148 // is_success
149 assert!(success.is_success());
150 assert!(!err.is_success());
151 // is_error
152 assert!(!success.is_error());
153 assert!(err.is_error());
154 }
155
156 #[test]
157 fn stdack_to_binary_works() {
158 let ack1 = StdAck::success(b"\x01");
159 assert_eq!(ack1.to_binary(), br#"{"result":"AQ=="}"#);
160
161 let ack2 = StdAck::error("kaputt");
162 assert_eq!(ack2.to_binary(), br#"{"error":"kaputt"}"#);
163 }
164}