cosmwasm_std/ibc/
callbacks.rs

1//! This module contains types for the IBC callbacks defined in
2//! [ADR-8](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-008-app-caller-cbs.md).
3
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::{Addr, Coin, IbcAcknowledgement, IbcPacket, Uint64};
8
9/// This is just a type representing the data that has to be sent with the IBC message to receive
10/// callbacks. It should be serialized and sent with the IBC message.
11/// The specific field and format to send it in can vary depending on the IBC message,
12/// but is usually the `memo` field by convention.
13///
14/// See [`IbcSourceCallbackMsg`] and [`IbcDestinationCallbackMsg`] for more details.
15///
16/// # Example
17///
18/// Using [`TransferMsgBuilder`](crate::TransferMsgBuilder):
19/// ```rust
20/// use cosmwasm_std::{
21///     to_json_string, Coin, IbcCallbackRequest, TransferMsgBuilder, IbcSrcCallback, IbcTimeout, Response,
22///     Timestamp,
23/// };
24/// # use cosmwasm_std::testing::mock_env;
25/// # let env = mock_env();
26///
27/// let _msg = TransferMsgBuilder::new(
28///     "channel-0".to_string(),
29///     "cosmos1example".to_string(),
30///     Coin::new(10u32, "ucoin"),
31///     Timestamp::from_seconds(12345),
32/// )
33/// .with_src_callback(IbcSrcCallback {
34///     address: env.contract.address,
35///     gas_limit: None,
36/// })
37/// .build();
38/// ```
39///
40/// Manual serialization:
41/// ```rust
42/// use cosmwasm_std::{
43///     to_json_string, Coin, IbcCallbackRequest, IbcMsg, IbcSrcCallback, IbcTimeout, Response,
44///     Timestamp,
45/// };
46/// # use cosmwasm_std::testing::mock_env;
47/// # let env = mock_env();
48///
49/// let _transfer = IbcMsg::Transfer {
50///     to_address: "cosmos1example".to_string(),
51///     channel_id: "channel-0".to_string(),
52///     amount: Coin::new(10u32, "ucoin"),
53///     timeout: Timestamp::from_seconds(12345).into(),
54///     memo: Some(to_json_string(&IbcCallbackRequest::source(IbcSrcCallback {
55///         address: env.contract.address,
56///         gas_limit: None,
57///     })).unwrap()),
58/// };
59/// ```
60#[derive(
61    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
62)]
63pub struct IbcCallbackRequest {
64    // using private fields to force use of the constructors
65    #[serde(skip_serializing_if = "Option::is_none")]
66    src_callback: Option<IbcSrcCallback>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    dest_callback: Option<IbcDstCallback>,
69}
70
71impl IbcCallbackRequest {
72    /// Use this if you want to execute callbacks on both the source and destination chain.
73    pub fn both(src_callback: IbcSrcCallback, dest_callback: IbcDstCallback) -> Self {
74        IbcCallbackRequest {
75            src_callback: Some(src_callback),
76            dest_callback: Some(dest_callback),
77        }
78    }
79
80    /// Use this if you want to execute callbacks on the source chain, but not the destination chain.
81    pub fn source(src_callback: IbcSrcCallback) -> Self {
82        IbcCallbackRequest {
83            src_callback: Some(src_callback),
84            dest_callback: None,
85        }
86    }
87
88    /// Use this if you want to execute callbacks on the destination chain, but not the source chain.
89    pub fn destination(dest_callback: IbcDstCallback) -> Self {
90        IbcCallbackRequest {
91            src_callback: None,
92            dest_callback: Some(dest_callback),
93        }
94    }
95}
96
97#[derive(
98    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
99)]
100pub struct IbcSrcCallback {
101    /// The source chain address that should receive the callback.
102    /// For CosmWasm contracts, this *must* be `env.contract.address`.
103    /// Other addresses are not allowed and will effectively be ignored.
104    pub address: Addr,
105    /// Optional gas limit for the callback (in Cosmos SDK gas units)
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub gas_limit: Option<Uint64>,
108}
109
110#[derive(
111    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
112)]
113pub struct IbcDstCallback {
114    /// The destination chain address that should receive the callback.
115    pub address: String,
116    /// Optional gas limit for the callback (in Cosmos SDK gas units)
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub gas_limit: Option<Uint64>,
119}
120
121/// The type of IBC source callback that is being called.
122///
123/// IBC source callbacks are needed for cases where your contract triggers the sending of an
124/// IBC packet through some other message (i.e. not through [`crate::IbcMsg::SendPacket`]) and needs to
125/// know whether or not the packet was successfully received on the other chain.
126/// A prominent example is the [`crate::IbcMsg::Transfer`] message. Without callbacks, you cannot know
127/// whether the transfer was successful or not.
128///
129/// Note that there are some prerequisites that need to be fulfilled to receive source callbacks:
130/// - The contract must implement the `ibc_source_callback` entrypoint.
131/// - The IBC application in the source chain must have support for the callbacks middleware.
132/// - You have to add serialized [`IbcCallbackRequest`] to a specific field of the message.
133///   For `IbcMsg::Transfer`, this is the `memo` field and it needs to be json-encoded.
134/// - The receiver of the callback must also be the sender of the message.
135#[derive(
136    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
137)]
138#[serde(rename_all = "snake_case")]
139pub enum IbcSourceCallbackMsg {
140    Acknowledgement(IbcAckCallbackMsg),
141    Timeout(IbcTimeoutCallbackMsg),
142}
143
144#[derive(
145    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
146)]
147#[non_exhaustive]
148pub struct IbcAckCallbackMsg {
149    pub acknowledgement: IbcAcknowledgement,
150    pub original_packet: IbcPacket,
151    pub relayer: Addr,
152}
153
154impl IbcAckCallbackMsg {
155    pub fn new(
156        acknowledgement: IbcAcknowledgement,
157        original_packet: IbcPacket,
158        relayer: Addr,
159    ) -> Self {
160        Self {
161            acknowledgement,
162            original_packet,
163            relayer,
164        }
165    }
166}
167
168#[derive(
169    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
170)]
171#[non_exhaustive]
172pub struct IbcTimeoutCallbackMsg {
173    pub packet: IbcPacket,
174    pub relayer: Addr,
175}
176
177impl IbcTimeoutCallbackMsg {
178    pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
179        Self { packet, relayer }
180    }
181}
182
183/// The message type of the IBC destination callback.
184///
185/// The IBC destination callback is needed for cases where someone triggers the sending of an
186/// IBC packet through some other message (i.e. not through [`crate::IbcMsg::SendPacket`]) and
187/// your contract needs to know that it received this.
188/// A prominent example is the [`crate::IbcMsg::Transfer`] message. Without callbacks, you cannot know
189/// that someone sent you IBC coins.
190///
191/// It is important to validate that the packet and acknowledgement are what you expect them to be.
192/// For example for a transfer message, the receiver is not necessarily the contract itself.
193///
194/// The callback is called when the packet is being acknowledged on the destination chain.
195/// This happens for both synchronous and asynchronous acknowledgements.
196///
197/// Note that there are some prerequisites that need to be fulfilled to receive destination callbacks:
198/// - The contract must implement the `ibc_destination_callback` entrypoint.
199/// - The IBC application in the destination chain must have support for the callbacks middleware.
200/// - You have to add serialized [`IbcCallbackRequest`] to a specific field of the message.
201///   For `IbcMsg::Transfer`, this is the `memo` field and it needs to be json-encoded.
202#[derive(
203    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
204)]
205pub struct IbcDestinationCallbackMsg {
206    pub packet: IbcPacket,
207    pub ack: IbcAcknowledgement,
208    /// When the underlying packet is a successful transfer message,
209    /// this field contains information about the transfer. Otherwise it is empty.
210    ///
211    /// This is always empty on chains using CosmWasm < 3.0
212    #[serde(default)]
213    pub transfer: Option<IbcTransferCallback>,
214}
215
216#[derive(
217    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, cw_schema::Schemaifier, JsonSchema,
218)]
219pub struct IbcTransferCallback {
220    /// Address of the sender of the transfer.
221    /// Note that this is *not* a valid address on the destination chain.
222    pub sender: String,
223    /// Address of the receiver of the transfer.
224    /// Since this is on the destination chain, this is a valid address.
225    pub receiver: Addr,
226    /// The funds that were transferred.
227    ///
228    /// When the callback is executed, the transfer is completed already and the coins are now owned
229    /// by the receiver.
230    pub funds: Vec<Coin>,
231}
232
233#[cfg(test)]
234mod tests {
235    use crate::to_json_string;
236
237    use super::*;
238
239    #[test]
240    fn ibc_callback_data_serialization() {
241        let mut data = IbcCallbackRequest::both(
242            IbcSrcCallback {
243                address: Addr::unchecked("src_address"),
244                gas_limit: Some(123u64.into()),
245            },
246            IbcDstCallback {
247                address: "dst_address".to_string(),
248                gas_limit: Some(1234u64.into()),
249            },
250        );
251
252        // both
253        let json = to_json_string(&data).unwrap();
254        assert_eq!(
255            json,
256            r#"{"src_callback":{"address":"src_address","gas_limit":"123"},"dest_callback":{"address":"dst_address","gas_limit":"1234"}}"#
257        );
258
259        // dst only, without gas limit
260        let mut src = data.src_callback.take().unwrap();
261        data.dest_callback.as_mut().unwrap().gas_limit = None;
262        let json = to_json_string(&data).unwrap();
263        assert_eq!(json, r#"{"dest_callback":{"address":"dst_address"}}"#);
264
265        // source only, without gas limit
266        src.gas_limit = None;
267        data.src_callback = Some(src);
268        data.dest_callback = None;
269        let json = to_json_string(&data).unwrap();
270        assert_eq!(json, r#"{"src_callback":{"address":"src_address"}}"#);
271    }
272}