kujira_std/
ica.rs

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    // ::cosmwasm_schema::serde::Deserialize,
155    ::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)] // Allow users of `#[cw_serde]` to not implement Eq without clippy complaining
161#[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                        // app version is json-escaped Ics27MetadataOpen
220                        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]
254/// Type for wrapping any protobuf message
255pub struct ProtobufAny {
256    /// **type_url** describes the type of the serialized message
257    type_url: String,
258
259    ///  **value** must be a valid serialized protocol buffer of the above specified type
260    value: Binary,
261}
262
263impl ProtobufAny {
264    /// Helper to create new ProtobufAny type:
265    /// * **type_url** describes the type of the serialized message
266    /// * **value** must be a valid serialized protocol buffer of the above specified type
267    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}