Skip to main content

cw_orch_daemon/queriers/
ibc.rs

1use crate::{cosmos_modules, error::DaemonError, Daemon};
2use cosmos_modules::ibc_channel;
3use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest;
4use cosmrs::proto::ibc::{
5    applications::transfer::v1::{DenomTrace, QueryDenomHashResponse, QueryDenomTraceResponse},
6    core::{
7        channel::v1::QueryPacketCommitmentResponse,
8        client::v1::{IdentifiedClientState, QueryClientStatesResponse},
9        connection::v1::{ConnectionEnd, IdentifiedConnection, State},
10    },
11    lightclients::tendermint::v1::ClientState,
12};
13use cw_orch_core::environment::{Querier, QuerierGetter};
14use prost::Message;
15use tokio::runtime::Handle;
16use tonic::transport::Channel;
17
18/// Querier for the Cosmos IBC module
19/// All the async function are prefixed with `_`
20pub struct Ibc {
21    pub channel: Channel,
22    pub rt_handle: Option<Handle>,
23}
24
25impl Ibc {
26    pub fn new(daemon: &Daemon) -> Self {
27        Self {
28            channel: daemon.channel(),
29            rt_handle: Some(daemon.rt_handle.clone()),
30        }
31    }
32
33    pub fn new_async(channel: Channel) -> Self {
34        Self {
35            channel,
36            rt_handle: None,
37        }
38    }
39}
40
41impl Querier for Ibc {
42    type Error = DaemonError;
43}
44
45impl QuerierGetter<Ibc> for Daemon {
46    fn querier(&self) -> Ibc {
47        Ibc::new(self)
48    }
49}
50
51impl Ibc {
52    // ### Transfer queries ### //
53
54    /// Get the trace of a specific denom
55    pub async fn _denom_trace(&self, hash: String) -> Result<DenomTrace, DaemonError> {
56        let denom_trace: QueryDenomTraceResponse = cosmos_query!(
57            self,
58            ibc_transfer,
59            denom_trace,
60            QueryDenomTraceRequest { hash: hash }
61        );
62        Ok(denom_trace.denom_trace.unwrap())
63    }
64
65    /// Get the hash of a specific denom from its trace
66    pub async fn _denom_hash(&self, trace: String) -> Result<String, DaemonError> {
67        let denom_hash: QueryDenomHashResponse = cosmos_query!(
68            self,
69            ibc_transfer,
70            denom_hash,
71            QueryDenomHashRequest { trace: trace }
72        );
73        Ok(denom_hash.hash)
74    }
75
76    // ### Client queries ###
77
78    /// Get all the IBC clients for this daemon
79    pub async fn _clients(&self) -> Result<Vec<IdentifiedClientState>, DaemonError> {
80        let ibc_clients: QueryClientStatesResponse = cosmos_query!(
81            self,
82            ibc_client,
83            client_states,
84            QueryClientStatesRequest { pagination: None }
85        );
86        Ok(ibc_clients.client_states)
87    }
88
89    /// Get the state of a specific IBC client
90    pub async fn _client_state(
91        &self,
92        client_id: impl ToString,
93        // Add the necessary parameters here
94    ) -> Result<cosmos_modules::ibc_client::QueryClientStateResponse, DaemonError> {
95        let response: cosmos_modules::ibc_client::QueryClientStateResponse = cosmos_query!(
96            self,
97            ibc_client,
98            client_state,
99            QueryClientStateRequest {
100                client_id: client_id.to_string(),
101            }
102        );
103        Ok(response)
104    }
105
106    /// Get the consensus state of a specific IBC client
107    pub async fn _consensus_states(
108        &self,
109        client_id: impl ToString,
110    ) -> Result<cosmos_modules::ibc_client::QueryConsensusStatesResponse, DaemonError> {
111        let client_id = client_id.to_string();
112        let response: cosmos_modules::ibc_client::QueryConsensusStatesResponse = cosmos_query!(
113            self,
114            ibc_client,
115            consensus_states,
116            QueryConsensusStatesRequest {
117                client_id: client_id,
118                pagination: None,
119            }
120        );
121        Ok(response)
122    }
123
124    /// Get the consensus status of a specific IBC client
125    pub async fn _client_status(
126        &self,
127        client_id: impl ToString,
128        // Add the necessary parameters here
129    ) -> Result<cosmos_modules::ibc_client::QueryClientStatusResponse, DaemonError> {
130        let response: cosmos_modules::ibc_client::QueryClientStatusResponse = cosmos_query!(
131            self,
132            ibc_client,
133            client_status,
134            QueryClientStatusRequest {
135                client_id: client_id.to_string(),
136            }
137        );
138        Ok(response)
139    }
140
141    /// Get the ibc client parameters
142    pub async fn _client_params(
143        &self,
144    ) -> Result<cosmos_modules::ibc_client::QueryClientParamsResponse, DaemonError> {
145        let response: cosmos_modules::ibc_client::QueryClientParamsResponse =
146            cosmos_query!(self, ibc_client, client_params, QueryClientParamsRequest {});
147        Ok(response)
148    }
149
150    // ### Connection queries ###
151
152    /// Query the IBC connections for a specific chain
153    pub async fn _connections(&self) -> Result<Vec<IdentifiedConnection>, DaemonError> {
154        use cosmos_modules::ibc_connection::QueryConnectionsResponse;
155
156        let ibc_connections: QueryConnectionsResponse = cosmos_query!(
157            self,
158            ibc_connection,
159            connections,
160            QueryConnectionsRequest { pagination: None }
161        );
162        Ok(ibc_connections.connections)
163    }
164
165    /// Search for open connections with a specific chain.
166    pub async fn _open_connections(
167        &self,
168        client_chain_id: impl ToString,
169    ) -> Result<Vec<IdentifiedConnection>, DaemonError> {
170        let connections = self._connections().await?;
171        let mut open_connections = Vec::new();
172        for connection in connections {
173            if connection.state() == State::Open {
174                open_connections.push(connection);
175            }
176        }
177
178        // now search for the connections that use a client with the correct chain ids
179        let mut filtered_connections = Vec::new();
180        for connection in open_connections {
181            let client_state = self._connection_client(&connection.id).await?;
182            if client_state.chain_id == client_chain_id.to_string() {
183                filtered_connections.push(connection);
184            }
185        }
186
187        Ok(filtered_connections)
188    }
189
190    // Get the information about a specific connection
191    pub async fn _connection_end(
192        &self,
193        connection_id: impl Into<String>,
194    ) -> Result<Option<ConnectionEnd>, DaemonError> {
195        use cosmos_modules::ibc_connection::QueryConnectionResponse;
196
197        let connection_id = connection_id.into();
198        let ibc_client_connections: QueryConnectionResponse = cosmos_query!(
199            self,
200            ibc_connection,
201            connection,
202            QueryConnectionRequest {
203                connection_id: connection_id.clone()
204            }
205        );
206
207        Ok(ibc_client_connections.connection)
208    }
209
210    /// Get all the connections for this client
211    pub async fn _client_connections(
212        &self,
213        client_id: impl Into<String>,
214    ) -> Result<Vec<String>, DaemonError> {
215        use cosmos_modules::ibc_connection::QueryClientConnectionsResponse;
216
217        let client_id = client_id.into();
218        let ibc_client_connections: QueryClientConnectionsResponse = cosmos_query!(
219            self,
220            ibc_connection,
221            client_connections,
222            QueryClientConnectionsRequest {
223                client_id: client_id.clone()
224            }
225        );
226
227        Ok(ibc_client_connections.connection_paths)
228    }
229
230    /// Get the (tendermint) client state for a specific connection
231    pub async fn _connection_client(
232        &self,
233        connection_id: impl Into<String>,
234    ) -> Result<ClientState, DaemonError> {
235        use cosmos_modules::ibc_connection::QueryConnectionClientStateResponse;
236        let connection_id = connection_id.into();
237
238        let ibc_connection_client: QueryConnectionClientStateResponse = cosmos_query!(
239            self,
240            ibc_connection,
241            connection_client_state,
242            QueryConnectionClientStateRequest {
243                connection_id: connection_id.clone()
244            }
245        );
246
247        let client_state =
248            ibc_connection_client
249                .identified_client_state
250                .ok_or(DaemonError::ibc_err(format!(
251                    "error identifying client for connection {}",
252                    connection_id
253                )))?;
254
255        let client_state = ClientState::decode(client_state.client_state.unwrap().value.as_slice())
256            .map_err(|e| DaemonError::ibc_err(format!("error decoding client state: {}", e)))?;
257
258        Ok(client_state)
259    }
260
261    // ### Channel queries ###
262
263    /// Get the channel for a specific port and channel id
264    pub async fn _channel(
265        &self,
266        port_id: impl Into<String>,
267        channel_id: impl Into<String>,
268    ) -> Result<ibc_channel::Channel, DaemonError> {
269        use cosmos_modules::ibc_channel::QueryChannelResponse;
270
271        let port_id = port_id.into();
272        let channel_id = channel_id.into();
273        let ibc_channel: QueryChannelResponse = cosmos_query!(
274            self,
275            ibc_channel,
276            channel,
277            QueryChannelRequest {
278                port_id: port_id.clone(),
279                channel_id: channel_id.clone(),
280            }
281        );
282
283        ibc_channel.channel.ok_or(DaemonError::ibc_err(format!(
284            "error fetching channel {} on port {}",
285            channel_id, port_id
286        )))
287    }
288
289    /// List all the channels
290    pub async fn _channels(
291        &self,
292        pagination: Option<PageRequest>,
293    ) -> Result<Vec<ibc_channel::IdentifiedChannel>, DaemonError> {
294        use cosmos_modules::ibc_channel::QueryChannelsResponse;
295
296        let ibc_channels: QueryChannelsResponse = cosmos_query!(
297            self,
298            ibc_channel,
299            channels,
300            QueryChannelsRequest {
301                pagination: pagination
302            }
303        );
304
305        Ok(ibc_channels.channels)
306    }
307
308    /// Get all the channels for a specific connection
309    pub async fn _connection_channels(
310        &self,
311        connection_id: impl Into<String>,
312    ) -> Result<Vec<ibc_channel::IdentifiedChannel>, DaemonError> {
313        use cosmos_modules::ibc_channel::QueryConnectionChannelsResponse;
314
315        let connection_id = connection_id.into();
316        let ibc_connection_channels: QueryConnectionChannelsResponse = cosmos_query!(
317            self,
318            ibc_channel,
319            connection_channels,
320            QueryConnectionChannelsRequest {
321                connection: connection_id.clone(),
322                pagination: None,
323            }
324        );
325
326        Ok(ibc_connection_channels.channels)
327    }
328
329    /// Get the client state for a specific channel and port
330    pub async fn _channel_client_state(
331        &self,
332        port_id: impl Into<String>,
333        channel_id: impl Into<String>,
334    ) -> Result<IdentifiedClientState, DaemonError> {
335        use cosmos_modules::ibc_channel::QueryChannelClientStateResponse;
336
337        let port_id = port_id.into();
338        let channel_id = channel_id.into();
339        let ibc_channel_client_state: QueryChannelClientStateResponse = cosmos_query!(
340            self,
341            ibc_channel,
342            channel_client_state,
343            QueryChannelClientStateRequest {
344                port_id: port_id.clone(),
345                channel_id: channel_id.clone(),
346            }
347        );
348
349        ibc_channel_client_state
350            .identified_client_state
351            .ok_or(DaemonError::ibc_err(format!(
352                "error identifying client for channel {} on port {}",
353                channel_id, port_id
354            )))
355    }
356
357    // ### Packet queries ###
358
359    // Commitment
360
361    /// Get all the packet commitments for a specific channel and port
362    pub async fn _packet_commitments(
363        &self,
364        port_id: impl Into<String>,
365        channel_id: impl Into<String>,
366    ) -> Result<Vec<ibc_channel::PacketState>, DaemonError> {
367        use cosmos_modules::ibc_channel::QueryPacketCommitmentsResponse;
368
369        let port_id = port_id.into();
370        let channel_id = channel_id.into();
371        let ibc_packet_commitments: QueryPacketCommitmentsResponse = cosmos_query!(
372            self,
373            ibc_channel,
374            packet_commitments,
375            QueryPacketCommitmentsRequest {
376                port_id: port_id.clone(),
377                channel_id: channel_id.clone(),
378                pagination: None,
379            }
380        );
381
382        Ok(ibc_packet_commitments.commitments)
383    }
384
385    /// Get the packet commitment for a specific channel, port and sequence
386    pub async fn _packet_commitment(
387        &self,
388        port_id: impl Into<String>,
389        channel_id: impl Into<String>,
390        sequence: u64,
391    ) -> Result<QueryPacketCommitmentResponse, DaemonError> {
392        let port_id = port_id.into();
393        let channel_id = channel_id.into();
394        let ibc_packet_commitment: QueryPacketCommitmentResponse = cosmos_query!(
395            self,
396            ibc_channel,
397            packet_commitment,
398            QueryPacketCommitmentRequest {
399                port_id: port_id.clone(),
400                channel_id: channel_id.clone(),
401                sequence: sequence,
402            }
403        );
404
405        Ok(ibc_packet_commitment)
406    }
407
408    // Receipt
409
410    /// Returns if the packet is received on the connected chain.
411    pub async fn _packet_receipt(
412        &self,
413        port_id: impl Into<String>,
414        channel_id: impl Into<String>,
415        sequence: u64,
416    ) -> Result<bool, DaemonError> {
417        let port_id = port_id.into();
418        let channel_id = channel_id.into();
419        let ibc_packet_receipt: ibc_channel::QueryPacketReceiptResponse = cosmos_query!(
420            self,
421            ibc_channel,
422            packet_receipt,
423            QueryPacketReceiptRequest {
424                port_id: port_id.clone(),
425                channel_id: channel_id.clone(),
426                sequence: sequence,
427            }
428        );
429
430        Ok(ibc_packet_receipt.received)
431    }
432
433    // Acknowledgement
434
435    /// Get all the packet acknowledgements for a specific channel, port and commitment sequences
436    pub async fn _packet_acknowledgements(
437        &self,
438        port_id: impl Into<String>,
439        channel_id: impl Into<String>,
440        packet_commitment_sequences: Vec<u64>,
441    ) -> Result<Vec<ibc_channel::PacketState>, DaemonError> {
442        use cosmos_modules::ibc_channel::QueryPacketAcknowledgementsResponse;
443
444        let port_id = port_id.into();
445        let channel_id = channel_id.into();
446        let ibc_packet_acknowledgements: QueryPacketAcknowledgementsResponse = cosmos_query!(
447            self,
448            ibc_channel,
449            packet_acknowledgements,
450            QueryPacketAcknowledgementsRequest {
451                port_id: port_id.clone(),
452                channel_id: channel_id.clone(),
453                packet_commitment_sequences: packet_commitment_sequences,
454                pagination: None,
455            }
456        );
457
458        Ok(ibc_packet_acknowledgements.acknowledgements)
459    }
460
461    /// Get the packet acknowledgement for a specific channel, port and sequence
462    pub async fn _packet_acknowledgement(
463        &self,
464        port_id: impl Into<String>,
465        channel_id: impl Into<String>,
466        sequence: u64,
467    ) -> Result<Vec<u8>, DaemonError> {
468        let port_id = port_id.into();
469        let channel_id = channel_id.into();
470        let ibc_packet_acknowledgement: ibc_channel::QueryPacketAcknowledgementResponse = cosmos_query!(
471            self,
472            ibc_channel,
473            packet_acknowledgement,
474            QueryPacketAcknowledgementRequest {
475                port_id: port_id.clone(),
476                channel_id: channel_id.clone(),
477                sequence: sequence,
478            }
479        );
480
481        Ok(ibc_packet_acknowledgement.acknowledgement)
482    }
483
484    /// No acknowledgement exists on receiving chain for the given packet commitment sequence on sending chain.
485    /// Returns the packet sequences that have not yet been received.
486    pub async fn _unreceived_packets(
487        &self,
488        port_id: impl Into<String>,
489        channel_id: impl Into<String>,
490        packet_commitment_sequences: Vec<u64>,
491    ) -> Result<Vec<u64>, DaemonError> {
492        use cosmos_modules::ibc_channel::QueryUnreceivedPacketsResponse;
493
494        let port_id = port_id.into();
495        let channel_id = channel_id.into();
496        let ibc_packet_unreceived: QueryUnreceivedPacketsResponse = cosmos_query!(
497            self,
498            ibc_channel,
499            unreceived_packets,
500            QueryUnreceivedPacketsRequest {
501                port_id: port_id.clone(),
502                channel_id: channel_id.clone(),
503                packet_commitment_sequences: packet_commitment_sequences,
504            }
505        );
506
507        Ok(ibc_packet_unreceived.sequences)
508    }
509
510    /// Returns the acknowledgement sequences that have not yet been received.
511    /// Given a list of acknowledgement sequences from counterparty, determine if an ack on the counterparty chain has been received on the executing chain.
512    /// Returns the list of acknowledgement sequences that have not yet been received.
513    pub async fn _unreceived_acks(
514        &self,
515        port_id: impl Into<String>,
516        channel_id: impl Into<String>,
517        packet_ack_sequences: Vec<u64>,
518    ) -> Result<Vec<u64>, DaemonError> {
519        let port_id = port_id.into();
520        let channel_id = channel_id.into();
521        let ibc_packet_unreceived: ibc_channel::QueryUnreceivedAcksResponse = cosmos_query!(
522            self,
523            ibc_channel,
524            unreceived_acks,
525            QueryUnreceivedAcksRequest {
526                port_id: port_id.clone(),
527                channel_id: channel_id.clone(),
528                packet_ack_sequences: packet_ack_sequences,
529            }
530        );
531
532        Ok(ibc_packet_unreceived.sequences)
533    }
534
535    /// Returns the acknowledgement sequences that have not yet been received.
536    /// Given a list of acknowledgement sequences from counterparty, determine if an ack on the counterparty chain has been received on the executing chain.
537    /// Returns the list of acknowledgement sequences that have not yet been received.
538    pub async fn _next_sequence_receive(
539        &self,
540        port_id: impl Into<String>,
541        channel_id: impl Into<String>,
542    ) -> Result<u64, DaemonError> {
543        let port_id = port_id.into();
544        let channel_id = channel_id.into();
545        let next_receive: ibc_channel::QueryNextSequenceReceiveResponse = cosmos_query!(
546            self,
547            ibc_channel,
548            next_sequence_receive,
549            QueryNextSequenceReceiveRequest {
550                port_id: port_id.clone(),
551                channel_id: channel_id.clone(),
552            }
553        );
554
555        Ok(next_receive.next_sequence_receive)
556    }
557}