1use cosmwasm_schema::{
2 cw_serde,
3 serde::{Deserialize, Deserializer, Serializer},
4};
5use cosmwasm_std::{from_json, to_json_string, Binary, Coin, IbcTimeout};
6
7#[cw_serde]
8pub enum IcaSudoMsg {
9 IcaRegisterCallback(IcaRegisterCallbackData),
10 IcaTxCallback(IcaTxCallbackData),
11 TransferCallback(TransferCallbackData),
12 TransferReceipt(TransferReceiptData),
13}
14
15#[cw_serde]
16pub struct IcaRegisterCallbackData {
17 pub connection_id: String,
18 pub account_id: String,
19 pub callback: Option<Binary>,
20 pub result: IcaRegisterResult,
21}
22
23#[cw_serde]
24pub struct IcaTxCallbackData {
25 pub connection_id: String,
26 pub account_id: String,
27 pub sequence: u64,
28 pub callback: Option<Binary>,
29 pub result: IcaTxResult,
30}
31
32#[cw_serde]
33pub enum IcaRegisterResult {
34 Success { data: IcaOpenVersion },
35 Error { error: String },
36 Timeout {},
37}
38
39#[cw_serde]
40pub enum IcaTxResult {
41 Success { data: Binary },
42 Error { error: String },
43 Timeout {},
44}
45
46#[cw_serde]
47pub enum IcaMsg {
48 Register {
49 connection_id: String,
50 account_id: String,
51 version: IcaRegisterVersion,
52 callback: Option<Binary>,
53 },
54 Submit {
55 connection_id: String,
56 account_id: String,
57 msgs: Vec<ProtobufAny>,
58 memo: String,
59 timeout: u64,
60 callback: Option<Binary>,
61 },
62 Transfer {
63 channel_id: String,
64 to_address: String,
65 amount: Coin,
66 timeout: IbcTimeout,
67 callback: Binary,
68 },
69}
70
71#[cw_serde]
72#[serde(untagged)]
73pub enum IcaRegisterVersion {
74 #[serde(serialize_with = "serialize_empty_string")]
75 Default,
76 #[serde(serialize_with = "serialize_ics27")]
77 Ics27(Ics27MetadataInit),
78 #[serde(serialize_with = "serialize_ics29")]
79 Ics29(Ics29MetadataInit),
80}
81
82impl Default for IcaRegisterVersion {
83 fn default() -> Self {
84 Self::Default
85 }
86}
87
88fn serialize_empty_string<S>(serializer: S) -> Result<S::Ok, S::Error>
89where
90 S: Serializer,
91{
92 serializer.serialize_str("")
93}
94
95fn serialize_ics27<S>(metadata: &Ics27MetadataInit, serializer: S) -> Result<S::Ok, S::Error>
96where
97 S: Serializer,
98{
99 serializer.serialize_str(&to_json_string(metadata).unwrap())
100}
101
102fn serialize_ics29<S>(metadata: &Ics29MetadataInit, serializer: S) -> Result<S::Ok, S::Error>
103where
104 S: Serializer,
105{
106 serializer.serialize_str(&to_json_string(metadata).unwrap())
107}
108
109#[cw_serde]
110pub struct Ics27MetadataInit {
111 pub version: String,
112 pub controller_connection_id: String,
113 pub host_connection_id: String,
114 pub encoding: String,
115 pub tx_type: String,
116}
117
118impl Ics27MetadataInit {
119 pub fn new(controller_connection_id: String, host_connection_id: String) -> Self {
120 Self {
121 version: "ics27-1".to_string(),
122 controller_connection_id,
123 host_connection_id,
124 encoding: "proto3".to_string(),
125 tx_type: "sdk_multi_msg".to_string(),
126 }
127 }
128}
129
130#[cw_serde]
131pub struct Ics29MetadataInit {
132 pub fee_version: String,
133 #[serde(serialize_with = "serialize_ics27")]
134 pub app_version: Ics27MetadataInit,
135}
136
137impl From<Ics27MetadataInit> for Ics29MetadataInit {
138 fn from(app_version: Ics27MetadataInit) -> Self {
139 Self {
140 fee_version: "ics29-1".to_string(),
141 app_version,
142 }
143 }
144}
145
146impl Ics29MetadataInit {
147 pub fn new(controller_connection_id: String, host_connection_id: String) -> Self {
148 Ics27MetadataInit::new(controller_connection_id, host_connection_id).into()
149 }
150}
151
152#[derive(
153 ::cosmwasm_schema::serde::Serialize,
154 ::std::clone::Clone,
156 ::std::fmt::Debug,
157 ::std::cmp::PartialEq,
158 ::cosmwasm_schema::schemars::JsonSchema,
159)]
160#[allow(clippy::derive_partial_eq_without_eq)] #[serde(
162 untagged,
163 deny_unknown_fields,
164 rename_all = "snake_case",
165 crate = "::cosmwasm_schema::serde"
166)]
167#[schemars(crate = "::cosmwasm_schema::schemars")]
168pub enum IcaOpenVersion {
169 Ics27(Ics27MetadataOpen),
170 Ics29(Ics29MetadataOpen),
171}
172
173impl<'de> Deserialize<'de> for IcaOpenVersion {
174 fn deserialize<D>(deserializer: D) -> Result<IcaOpenVersion, D::Error>
175 where
176 D: Deserializer<'de>,
177 {
178 struct IcaOpenVersionVisitor;
179 impl<'de> cosmwasm_schema::serde::de::Visitor<'de> for IcaOpenVersionVisitor {
180 type Value = IcaOpenVersion;
181
182 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
183 formatter.write_str("a string containing json data")
184 }
185
186 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
187 where
188 E: cosmwasm_schema::serde::de::Error,
189 {
190 #[derive(cosmwasm_schema::serde::Deserialize)]
191 #[serde(
192 untagged,
193 rename_all = "snake_case",
194 crate = "::cosmwasm_schema::serde"
195 )]
196 enum DeserOpenVersion {
197 Ics27 {
198 #[allow(dead_code)]
199 version: String,
200 },
201 Ics29 {
202 fee_version: String,
203 app_version: String,
204 },
205 }
206 let data = Binary::from_base64(v).map_err(E::custom)?;
207 let r#type: DeserOpenVersion = from_json(data).map_err(E::custom)?;
208 match r#type {
209 DeserOpenVersion::Ics27 { .. } => {
210 let data = Binary::from_base64(v).map_err(E::custom)?;
211 Ok(IcaOpenVersion::Ics27(
212 from_json::<Ics27MetadataOpen>(data).map_err(E::custom)?,
213 ))
214 }
215 DeserOpenVersion::Ics29 {
216 fee_version,
217 app_version,
218 } => {
219 serde_json_wasm::from_str(&app_version)
221 .map_err(E::custom)
222 .map(|app_version| {
223 IcaOpenVersion::Ics29(Ics29MetadataOpen {
224 fee_version,
225 app_version,
226 })
227 })
228 }
229 }
230 }
231 }
232
233 deserializer.deserialize_any(IcaOpenVersionVisitor)
234 }
235}
236
237#[cw_serde]
238pub struct Ics27MetadataOpen {
239 pub version: String,
240 pub controller_connection_id: String,
241 pub host_connection_id: String,
242 pub address: String,
243 pub encoding: String,
244 pub tx_type: String,
245}
246
247#[cw_serde]
248pub struct Ics29MetadataOpen {
249 pub fee_version: String,
250 pub app_version: Ics27MetadataOpen,
251}
252
253#[cw_serde]
254pub struct ProtobufAny {
256 type_url: String,
258
259 value: Binary,
261}
262
263impl ProtobufAny {
264 pub fn new(type_url: impl Into<String>, value: impl Into<Binary>) -> Self {
268 ProtobufAny {
269 type_url: type_url.into(),
270 value: value.into(),
271 }
272 }
273}
274
275#[cw_serde]
276pub struct TransferCallbackData {
277 pub port: String,
278 pub channel: String,
279 pub sequence: u64,
280 pub receiver: String,
281 pub denom: String,
282 pub amount: String,
283 pub memo: String,
284 pub result: IcaTxResult,
285 pub callback: Binary,
286}
287
288#[cw_serde]
289pub struct TransferReceiptData {
290 pub port: String,
291 pub channel: String,
292 pub sequence: u64,
293 pub sender: String,
294 pub denom: String,
295 pub amount: String,
296 pub memo: String,
297}
298
299#[cfg(test)]
300mod tests {
301 use crate::IcaOpenVersion;
302
303 #[test]
304 fn test_deserialize_callback_data() {
305 let data =
306 "{\"data\":\"eyJ2ZXJzaW9uIjoiaWNzMjctMSIsImNvbnRyb2xsZXJfY29ubmVjdGlvbl9pZCI6ImNvbm5lY3Rpb24tMCIsImhvc3RfY29ubmVjdGlvbl9pZCI6ImNvbm5lY3Rpb24tMCIsImFkZHJlc3MiOiJjb3Ntb3MxbDhkY2xubmxjc2twanBuMmtwczBjYWw2ZWpuZzdyNGR5cGV0NnIyeGZkdHJjNjBjeWg2cXh2eXdjYyIsImVuY29kaW5nIjoicHJvdG8zIiwidHhfdHlwZSI6InNka19tdWx0aV9tc2cifQ==\"}";
307
308 let as_json: serde_json::Value = serde_json::from_slice(data.as_bytes()).unwrap();
309
310 let data: Result<IcaOpenVersion,_> = serde_json::from_value(as_json.get("data").unwrap().clone());
311 assert!(data.is_ok());
312 assert!(matches!(data.unwrap(), IcaOpenVersion::Ics27(_)));
313
314 let data = "{\"data\":\"eyJmZWVfdmVyc2lvbiI6ImljczI5LTEiLCAiYXBwX3ZlcnNpb24iOiJ7XCJ2ZXJzaW9uXCI6XCJpY3MyNy0xXCIsXCJjb250cm9sbGVyX2Nvbm5lY3Rpb25faWRcIjpcImNvbm5lY3Rpb24tMFwiLFwiaG9zdF9jb25uZWN0aW9uX2lkXCI6XCJjb25uZWN0aW9uLTBcIixcImFkZHJlc3NcIjpcImNvc21vczFsOGRjbG5ubGNza3BqcG4ya3BzMGNhbDZlam5nN3I0ZHlwZXQ2cjJ4ZmR0cmM2MGN5aDZxeHZ5d2NjXCIsXCJlbmNvZGluZ1wiOlwicHJvdG8zXCIsXCJ0eF90eXBlXCI6XCJzZGtfbXVsdGlfbXNnXCJ9In0=\"}";
315
316 let as_json: serde_json::Value = serde_json::from_slice(data.as_bytes()).unwrap();
317
318 let data: Result<IcaOpenVersion,_> = serde_json::from_value(as_json.get("data").unwrap().clone());
319 assert!(data.is_ok());
320 assert!(matches!(data.unwrap(), IcaOpenVersion::Ics29(_)));
321 }
322}