cw_orch_interchain_core/
packet.rs

1use cosmwasm_std::{Binary, StdError};
2use cw_orch_core::environment::CwEnv;
3use cw_orch_core::environment::IndexResponse;
4use ibc_relayer_types::core::{
5    ics04_channel::packet::Sequence,
6    ics24_host::identifier::{ChannelId, PortId},
7};
8
9use crate::{results::NetworkId, tx::TxId};
10
11/// Structure to hold simple information about a sent packet
12#[derive(Debug, Clone)]
13pub struct IbcPacketInfo {
14    /// Port on which is packet was sent
15    pub src_port: PortId,
16    /// Channel on which is packet was sent
17    pub src_channel: ChannelId,
18    /// Packet identification (sequence is `u64` number)
19    pub sequence: Sequence,
20    /// Chain identification to which the packet was sent
21    pub dst_chain_id: NetworkId,
22}
23
24/// Raw packet outcome
25/// The T generic is used to allow for raw transactions or analyzed transactions to be used
26#[derive(Debug, PartialEq, Clone)]
27#[must_use = "We recommend using `PacketAnalysis::assert()` to assert ibc success"]
28pub enum IbcPacketOutcome<T> {
29    /// Packet timeout
30    Timeout {
31        /// Only a timeout transaction gets broadcasted
32        timeout_tx: T,
33    },
34    /// Packet successfully transferred
35    Success {
36        /// The packets gets transmitted to the dst chain
37        receive_tx: T,
38        /// The ack is broadcasted back on the src chain
39        ack_tx: T,
40        /// The raw binary acknowledgement retrieved from `ack_tx`
41        ack: Binary,
42    },
43}
44
45/// The result of awaiting the Lifecycle of Single packet across IBC
46///
47/// This identifies:
48/// - `send_tx`: The transaction in which the packet was sent (if available)
49/// - `outcome`: The outcome of the Lifecycle and the corresponding transactions (Receive/Acknowledgement or Timeout)
50#[derive(Clone)]
51#[must_use = "We recommend using `PacketAnalysis::assert()` to assert IBC success"]
52pub struct SinglePacketFlow<Chain: CwEnv> {
53    /// The transaction during which the packet was sent
54    ///
55    /// Can optionally be specified, depending on the environment on which the implementation is done
56    /// This is not available for the [`Mock`] implementation for instance
57    pub send_tx: Option<TxId<Chain>>,
58    /// Outcome transactions of the packet (+ eventual acknowledgment)
59    pub outcome: IbcPacketOutcome<TxId<Chain>>,
60}
61
62/// The result of awaiting all packets sent across IBC during a single transaction.
63///
64/// This structure is nested and allows identifying all packets emitted during the subsequent (receive/acknowledgement/timeout) transactions
65///
66/// This identifies:
67/// - `tx_id`: The original transaction that was used as a starting point of the awaiting procedure
68/// - `packets`: For each packet sent inside this transaction, this contains the outcome of the lifecycle of the packet.
69///     This also contains the result of awaiting all packets sent across IBC during each outcome transactions (receive/acknowledgement/timeout)
70#[derive(Clone)]
71#[must_use = "We recommend using `PacketAnalysis::assert()` to assert IBC success"]
72pub struct NestedPacketsFlow<Chain: CwEnv> {
73    /// Identification of the transaction
74    pub tx_id: TxId<Chain>,
75    /// Result of following a packet + Recursive Analysis of the resulting transactions for additional IBC packets
76    pub packets: Vec<IbcPacketOutcome<NestedPacketsFlow<Chain>>>,
77}
78
79impl<Chain: CwEnv> IndexResponse for SinglePacketFlow<Chain> {
80    fn events(&self) -> Vec<cosmwasm_std::Event> {
81        let mut events: Vec<_> = self
82            .send_tx
83            .as_ref()
84            .map(|tx| tx.response.events())
85            .unwrap_or_default();
86        let other_events = self.outcome.events();
87        events.extend(other_events);
88
89        events
90    }
91
92    fn event_attr_value(
93        &self,
94        event_type: &str,
95        attr_key: &str,
96    ) -> cosmwasm_std::StdResult<String> {
97        self.send_tx
98            .as_ref()
99            .map(|r| r.event_attr_value(event_type, attr_key))
100            .and_then(|res| res.ok())
101            .or_else(|| self.outcome.event_attr_value(event_type, attr_key).ok())
102            .ok_or(StdError::generic_err(format!(
103                "event of type {event_type} does not have a value at key {attr_key}"
104            )))
105    }
106
107    fn event_attr_values(&self, event_type: &str, attr_key: &str) -> Vec<String> {
108        let mut all_results: Vec<_> = self
109            .send_tx
110            .as_ref()
111            .map(|tx| tx.response.event_attr_values(event_type, attr_key))
112            .unwrap_or_default();
113        let other_results = self.outcome.event_attr_values(event_type, attr_key);
114        all_results.extend(other_results);
115
116        all_results
117    }
118
119    fn data(&self) -> Option<Binary> {
120        unimplemented!("No data fields on Ibc Packet Flow, this is not well defined")
121    }
122}
123
124impl<Chain: CwEnv> IndexResponse for NestedPacketsFlow<Chain> {
125    fn events(&self) -> Vec<cosmwasm_std::Event> {
126        let mut self_events = self.tx_id.response.events();
127        let other_events = self
128            .packets
129            .iter()
130            .flat_map(|packet_result| packet_result.events());
131        self_events.extend(other_events);
132        self_events
133    }
134
135    fn event_attr_value(
136        &self,
137        event_type: &str,
138        attr_key: &str,
139    ) -> cosmwasm_std::StdResult<String> {
140        self.tx_id
141            .response
142            .event_attr_value(event_type, attr_key)
143            .or_else(|_| {
144                self.packets
145                    .iter()
146                    .find_map(|packet_result| {
147                        packet_result.event_attr_value(event_type, attr_key).ok()
148                    })
149                    .ok_or(StdError::generic_err(format!(
150                        "event of type {event_type} does not have a value at key {attr_key}"
151                    )))
152            })
153    }
154
155    fn event_attr_values(&self, event_type: &str, attr_key: &str) -> Vec<String> {
156        let mut all_results = self.tx_id.response.event_attr_values(event_type, attr_key);
157
158        all_results.extend(
159            self.packets
160                .iter()
161                .flat_map(|packet_result| packet_result.event_attr_values(event_type, attr_key)),
162        );
163
164        all_results
165    }
166
167    fn data(&self) -> Option<Binary> {
168        unimplemented!("No data fields on Ibc Tx Analysis")
169    }
170}
171
172pub mod success {
173    use crate::ack_parser::IbcHooksAck;
174    use crate::{ack_parser::polytone_callback::Callback, tx::TxId};
175    use cosmwasm_std::{Binary, Empty, StdError};
176    use cw_orch_core::environment::CwEnv;
177    use cw_orch_core::environment::IndexResponse;
178
179    /// Contains the result (ack success) associated with various Ibc applications
180    #[derive(Debug, PartialEq, Clone)]
181    #[non_exhaustive]
182    pub enum IbcAppResult<CustomResult = Empty> {
183        /// Contains a successful result for Polytone
184        Polytone(Callback),
185        /// Signals a successful result for ICS20 (token transfer)
186        Ics20,
187        /// Contains a successful result according to the ICS004 standard
188        Ics004(Vec<u8>),
189        /// Contains a successful result according to the ibc hooks standard
190        /// https://github.com/cosmos/ibc-apps/blob/8cb681e31589bc90b47e0ab58173a579825fd56d/modules/ibc-hooks/wasm_hook.go#L119C1-L119C86
191        IbcHooks(IbcHooksAck),
192        /// Contains a custom result. This is only used if a custom parsing function is specified
193        Custom(CustomResult),
194    }
195
196    impl IbcAppResult<Empty> {
197        /// Casts the Result into a Result with a specified CustomResult type
198        pub fn into_custom<CustomResult>(self) -> IbcAppResult<CustomResult> {
199            match self {
200                IbcAppResult::Polytone(callback) => IbcAppResult::Polytone(callback),
201                IbcAppResult::Ics20 => IbcAppResult::Ics20,
202                IbcAppResult::Ics004(vec) => IbcAppResult::Ics004(vec),
203                IbcAppResult::IbcHooks(ibc_hooks_ack) => IbcAppResult::IbcHooks(ibc_hooks_ack),
204                IbcAppResult::Custom(_) => unreachable!(),
205            }
206        }
207    }
208
209    /// Success packet outcome. This is the result of a packet analysis.
210    /// The T generic is used to allow for raw transactions or analyzed transactions to be used
211    #[derive(Debug, PartialEq, Clone)]
212    pub struct IbcPacketResult<T: IndexResponse, CustomResult = Empty> {
213        /// The packets gets transmitted to the dst chain
214        pub receive_tx: T,
215        /// The ack is broadcasted back on the src chain
216        pub ack_tx: T,
217        /// The parsed and raw binary acknowledgement retrieved from `ack_tx`
218        pub ibc_app_result: IbcAppResult<CustomResult>,
219    }
220
221    /// Success Packet Flow. This is the result of a packet analysis.
222    ///
223    /// This allows identifying the different transactions involved.
224    #[derive(Clone)]
225    pub struct SuccessSinglePacketFlow<Chain: CwEnv, CustomResult = Empty> {
226        /// The transaction during which the packet was sent
227        ///
228        /// Can optionally be specified, depending on the environment on which the implementation is done
229        /// This is not available for the [`Mock`] implementation for instance
230        pub send_tx: Option<TxId<Chain>>,
231        /// Result of the successful packet flow
232        pub result: IbcPacketResult<TxId<Chain, CustomResult>, CustomResult>,
233    }
234
235    /// The result of following all packets sent across IBC during a single transaction.
236    ///
237    /// This structure is nested and will also await all packets emitted during the subsequent (receive/acknowledgement) transactions
238    #[derive(Clone)]
239    pub struct SuccessNestedPacketsFlow<Chain: CwEnv, CustomResult = Empty> {
240        /// Identification of the transaction
241        pub tx_id: TxId<Chain>,
242        /// Result of following a packet + Recursive Analysis of the resulting transactions for additional IBC packets
243        pub packets:
244            Vec<IbcPacketResult<SuccessNestedPacketsFlow<Chain, CustomResult>, CustomResult>>,
245    }
246
247    impl<T: IndexResponse, CustomResult> IndexResponse for IbcPacketResult<T, CustomResult> {
248        fn events(&self) -> Vec<cosmwasm_std::Event> {
249            [self.receive_tx.events(), self.ack_tx.events()].concat()
250        }
251
252        fn event_attr_value(
253            &self,
254            event_type: &str,
255            attr_key: &str,
256        ) -> cosmwasm_std::StdResult<String> {
257            self.receive_tx
258                .event_attr_value(event_type, attr_key)
259                .or_else(|_| self.ack_tx.event_attr_value(event_type, attr_key))
260        }
261
262        fn event_attr_values(&self, event_type: &str, attr_key: &str) -> Vec<String> {
263            [
264                self.receive_tx.event_attr_values(event_type, attr_key),
265                self.ack_tx.event_attr_values(event_type, attr_key),
266            ]
267            .concat()
268        }
269
270        fn data(&self) -> Option<Binary> {
271            unimplemented!("No data fields on Ibc Packet Flow, this is not well defined")
272        }
273    }
274
275    impl<Chain: CwEnv, CustomResult> IndexResponse for SuccessSinglePacketFlow<Chain, CustomResult> {
276        fn events(&self) -> Vec<cosmwasm_std::Event> {
277            let mut events: Vec<_> = self
278                .send_tx
279                .as_ref()
280                .map(|tx| tx.response.events())
281                .unwrap_or_default();
282            let other_events = self.result.events();
283            events.extend(other_events);
284
285            events
286        }
287
288        fn event_attr_value(
289            &self,
290            event_type: &str,
291            attr_key: &str,
292        ) -> cosmwasm_std::StdResult<String> {
293            self.send_tx
294                .as_ref()
295                .map(|r| r.event_attr_value(event_type, attr_key))
296                .and_then(|res| res.ok())
297                .or_else(|| self.result.event_attr_value(event_type, attr_key).ok())
298                .ok_or(StdError::generic_err(format!(
299                    "event of type {event_type} does not have a value at key {attr_key}"
300                )))
301        }
302
303        fn event_attr_values(&self, event_type: &str, attr_key: &str) -> Vec<String> {
304            let mut all_results: Vec<_> = self
305                .send_tx
306                .as_ref()
307                .map(|tx| tx.response.event_attr_values(event_type, attr_key))
308                .unwrap_or_default();
309            let other_results = self.result.event_attr_values(event_type, attr_key);
310            all_results.extend(other_results);
311
312            all_results
313        }
314
315        fn data(&self) -> Option<Binary> {
316            unimplemented!("No data fields on SuccessSinglePacketFlow, this is not well defined")
317        }
318    }
319
320    impl<Chain: CwEnv, CustomResult> IndexResponse for SuccessNestedPacketsFlow<Chain, CustomResult> {
321        fn events(&self) -> Vec<cosmwasm_std::Event> {
322            let mut self_events = self.tx_id.response.events();
323            let other_events = self
324                .packets
325                .iter()
326                .flat_map(|packet_result| packet_result.events());
327            self_events.extend(other_events);
328            self_events
329        }
330
331        fn event_attr_value(
332            &self,
333            event_type: &str,
334            attr_key: &str,
335        ) -> cosmwasm_std::StdResult<String> {
336            self.tx_id
337                .response
338                .event_attr_value(event_type, attr_key)
339                .or_else(|_| {
340                    self.packets
341                        .iter()
342                        .find_map(|packet_result| {
343                            packet_result.event_attr_value(event_type, attr_key).ok()
344                        })
345                        .ok_or(StdError::generic_err(format!(
346                            "event of type {event_type} does not have a value at key {attr_key}"
347                        )))
348                })
349        }
350
351        fn event_attr_values(&self, event_type: &str, attr_key: &str) -> Vec<String> {
352            let mut all_results = self.tx_id.response.event_attr_values(event_type, attr_key);
353
354            all_results.extend(
355                self.packets.iter().flat_map(|packet_result| {
356                    packet_result.event_attr_values(event_type, attr_key)
357                }),
358            );
359
360            all_results
361        }
362
363        fn data(&self) -> Option<Binary> {
364            unimplemented!("No data fields on SuccessNestedPacketsFlow")
365        }
366    }
367}
368
369mod debug {
370    use cw_orch_core::environment::CwEnv;
371
372    use super::{
373        success::{SuccessNestedPacketsFlow, SuccessSinglePacketFlow},
374        NestedPacketsFlow, SinglePacketFlow,
375    };
376
377    impl<C: CwEnv> std::fmt::Debug for SinglePacketFlow<C> {
378        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379            f.debug_struct("SinglePacketFlow")
380                .field("send_tx", &self.send_tx)
381                .field("outcome", &self.outcome)
382                .finish()
383        }
384    }
385
386    impl<C: CwEnv> std::fmt::Debug for NestedPacketsFlow<C> {
387        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388            f.debug_struct("NestedPacketsFlow")
389                .field("tx_id", &self.tx_id)
390                .field("packets", &self.packets)
391                .finish()
392        }
393    }
394
395    impl<C: CwEnv, CustomResult: std::fmt::Debug> std::fmt::Debug
396        for SuccessSinglePacketFlow<C, CustomResult>
397    {
398        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399            f.debug_struct("SuccessSinglePacketFlow")
400                .field("sent_tx", &self.send_tx)
401                .field("result", &self.result)
402                .finish()
403        }
404    }
405
406    impl<C: CwEnv, CustomResult: std::fmt::Debug> std::fmt::Debug
407        for SuccessNestedPacketsFlow<C, CustomResult>
408    {
409        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410            f.debug_struct("SuccessNestedPacketsFlow")
411                .field("tx_id", &self.tx_id)
412                .field("packets", &self.packets)
413                .finish()
414        }
415    }
416}
417
418mod index_response {
419    use cosmwasm_std::Binary;
420    use cw_orch_core::environment::IndexResponse;
421
422    use super::IbcPacketOutcome;
423
424    impl<T: IndexResponse> IndexResponse for IbcPacketOutcome<T> {
425        fn events(&self) -> Vec<cosmwasm_std::Event> {
426            match &self {
427                IbcPacketOutcome::Timeout { timeout_tx } => timeout_tx.events(),
428                IbcPacketOutcome::Success {
429                    receive_tx,
430                    ack_tx,
431                    ack: _,
432                } => [receive_tx.events(), ack_tx.events()].concat(),
433            }
434        }
435
436        fn event_attr_value(
437            &self,
438            event_type: &str,
439            attr_key: &str,
440        ) -> cosmwasm_std::StdResult<String> {
441            match &self {
442                IbcPacketOutcome::Timeout { timeout_tx } => {
443                    timeout_tx.event_attr_value(event_type, attr_key)
444                }
445                IbcPacketOutcome::Success {
446                    receive_tx,
447                    ack_tx,
448                    ack: _,
449                } => receive_tx
450                    .event_attr_value(event_type, attr_key)
451                    .or_else(|_| ack_tx.event_attr_value(event_type, attr_key)),
452            }
453        }
454
455        fn event_attr_values(&self, event_type: &str, attr_key: &str) -> Vec<String> {
456            match &self {
457                IbcPacketOutcome::Timeout { timeout_tx } => {
458                    timeout_tx.event_attr_values(event_type, attr_key)
459                }
460                IbcPacketOutcome::Success {
461                    receive_tx,
462                    ack_tx,
463                    ack: _,
464                } => [
465                    receive_tx.event_attr_values(event_type, attr_key),
466                    ack_tx.event_attr_values(event_type, attr_key),
467                ]
468                .concat(),
469            }
470        }
471
472        fn data(&self) -> Option<Binary> {
473            unimplemented!("No data fields on Ibc Packet Flow, this is not well defined")
474        }
475    }
476}