cw_ica_controller/ibc/
relay.rs

1//! This module contains the entry points for:
2//! - The IBC packet acknowledgement.
3//! - The IBC packet timeout.
4//! - The IBC packet receive.
5
6use cosmwasm_std::entry_point;
7use cosmwasm_std::{
8    from_json, DepsMut, Env, IbcBasicResponse, IbcPacketAckMsg, IbcPacketReceiveMsg,
9    IbcPacketTimeoutMsg, IbcReceiveResponse, Never,
10};
11
12use crate::types::{query_msg, state, ContractError};
13
14use super::types::{events, packet::acknowledgement::Data as AcknowledgementData};
15
16/// Implements the IBC module's `OnAcknowledgementPacket` handler.
17///
18/// # Errors
19///
20/// This function returns an error if:
21///
22/// - The acknowledgement data is invalid.
23/// - [`ibc_packet_ack::success`] or [`ibc_packet_ack::error`] returns an error.
24#[entry_point]
25#[allow(clippy::needless_pass_by_value)]
26pub fn ibc_packet_ack(
27    deps: DepsMut,
28    _env: Env,
29    ack: IbcPacketAckMsg,
30) -> Result<IbcBasicResponse, ContractError> {
31    // This lets the ICA controller know whether or not the sent transactions succeeded.
32    match from_json(&ack.acknowledgement.data)? {
33        AcknowledgementData::Result(res) => {
34            ibc_packet_ack::success(deps, ack.original_packet, ack.relayer, res)
35        }
36        AcknowledgementData::Error(err) => {
37            ibc_packet_ack::error(deps, ack.original_packet, ack.relayer, err)
38        }
39    }
40}
41
42/// Implements the IBC module's `OnTimeoutPacket` handler.
43///
44/// # Errors
45///
46/// This function returns an error if:
47///
48/// - [`ibc_packet_timeout::callback`] returns an error.
49/// - The channel state cannot be loaded or saved.
50#[entry_point]
51#[allow(clippy::needless_pass_by_value)]
52pub fn ibc_packet_timeout(
53    deps: DepsMut,
54    _env: Env,
55    msg: IbcPacketTimeoutMsg,
56) -> Result<IbcBasicResponse, ContractError> {
57    // If the channel is ordered, close it.
58    let mut channel_state = state::CHANNEL_STATE.load(deps.storage)?;
59    if channel_state.is_ordered() {
60        channel_state.close();
61        state::CHANNEL_STATE.save(deps.storage, &channel_state)?;
62    }
63
64    ibc_packet_timeout::callback(deps, msg.packet, msg.relayer)
65}
66
67/// Implements the IBC module's `OnRecvPacket` handler.
68///
69/// # Panics
70/// Always panics because the ICA controller cannot receive packets.
71#[entry_point]
72#[allow(clippy::needless_pass_by_value, clippy::missing_errors_doc)]
73pub fn ibc_packet_receive(
74    _deps: DepsMut,
75    _env: Env,
76    _msg: IbcPacketReceiveMsg,
77) -> Result<IbcReceiveResponse, Never> {
78    // An ICA controller cannot receive packets, so this is a panic.
79    // It must be implemented to satisfy the wasmd interface.
80    unreachable!("ICA controller cannot receive packets")
81}
82
83mod ibc_packet_ack {
84    use cosmwasm_std::{Addr, Binary, IbcPacket};
85
86    use crate::types::callbacks::IcaControllerCallbackMsg;
87
88    use super::{
89        events, query_msg, state, AcknowledgementData, ContractError, DepsMut, IbcBasicResponse,
90    };
91
92    /// Handles the successful acknowledgement of an ica packet. This means that the
93    /// transaction was successfully executed on the host chain.
94    #[allow(clippy::needless_pass_by_value)]
95    pub fn success(
96        deps: DepsMut,
97        packet: IbcPacket,
98        relayer: Addr,
99        res: Binary,
100    ) -> Result<IbcBasicResponse, ContractError> {
101        let success_event = events::packet_ack::success(&packet, &res);
102        let ica_acknowledgement = AcknowledgementData::Result(res);
103        let query_result = state::PENDING_QUERIES
104            .may_load(deps.storage, (&packet.src.channel_id, packet.sequence))?
105            .map(
106                |paths| -> Result<query_msg::IcaQueryResult, ContractError> {
107                    let resp_msg =
108                        ica_acknowledgement.decode_module_query_safe_resp_last_index()?;
109                    Ok(query_msg::result_from_response(paths, &resp_msg))
110                },
111            )
112            .transpose()?;
113
114        state::PENDING_QUERIES.remove(deps.storage, (&packet.src.channel_id, packet.sequence));
115
116        if let Some(contract_addr) = state::STATE.load(deps.storage)?.callback_address {
117            let callback_msg = IcaControllerCallbackMsg::OnAcknowledgementPacketCallback {
118                ica_acknowledgement,
119                original_packet: packet,
120                relayer,
121                query_result,
122            }
123            .into_cosmos_msg(contract_addr)?;
124
125            Ok(IbcBasicResponse::default()
126                .add_message(callback_msg)
127                .add_event(success_event))
128        } else {
129            Ok(IbcBasicResponse::default().add_event(success_event))
130        }
131    }
132
133    /// Handles the unsuccessful acknowledgement of an ica packet. This means that the
134    /// transaction failed to execute on the host chain.
135    #[allow(clippy::needless_pass_by_value)]
136    pub fn error(
137        deps: DepsMut,
138        packet: IbcPacket,
139        relayer: Addr,
140        err: String,
141    ) -> Result<IbcBasicResponse, ContractError> {
142        let state = state::STATE.load(deps.storage)?;
143        let error_event = events::packet_ack::error(&packet, &err);
144
145        if let Some(contract_addr) = state.callback_address {
146            let callback_msg = IcaControllerCallbackMsg::OnAcknowledgementPacketCallback {
147                ica_acknowledgement: AcknowledgementData::Error(err.clone()),
148                original_packet: packet,
149                relayer,
150                query_result: Some(query_msg::IcaQueryResult::Error(err)),
151            }
152            .into_cosmos_msg(contract_addr)?;
153
154            Ok(IbcBasicResponse::default()
155                .add_message(callback_msg)
156                .add_event(error_event))
157        } else {
158            Ok(IbcBasicResponse::default().add_event(error_event))
159        }
160    }
161}
162
163mod ibc_packet_timeout {
164    use cosmwasm_std::{Addr, IbcPacket};
165
166    use crate::types::{callbacks::IcaControllerCallbackMsg, state};
167
168    use super::{ContractError, DepsMut, IbcBasicResponse};
169
170    /// Handles the timeout callbacks.
171    #[allow(clippy::needless_pass_by_value)]
172    pub fn callback(
173        deps: DepsMut,
174        packet: IbcPacket,
175        relayer: Addr,
176    ) -> Result<IbcBasicResponse, ContractError> {
177        let state = state::STATE.load(deps.storage)?;
178
179        state::PENDING_QUERIES.remove(deps.storage, (&packet.src.channel_id, packet.sequence));
180
181        if let Some(contract_addr) = state.callback_address {
182            let callback_msg = IcaControllerCallbackMsg::OnTimeoutPacketCallback {
183                original_packet: packet,
184                relayer,
185            }
186            .into_cosmos_msg(contract_addr)?;
187
188            Ok(IbcBasicResponse::default().add_message(callback_msg))
189        } else {
190            Ok(IbcBasicResponse::default())
191        }
192    }
193}