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, 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(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
61pub struct IbcCallbackRequest {
62 // using private fields to force use of the constructors
63 #[serde(skip_serializing_if = "Option::is_none")]
64 src_callback: Option<IbcSrcCallback>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 dest_callback: Option<IbcDstCallback>,
67}
68
69impl IbcCallbackRequest {
70 /// Use this if you want to execute callbacks on both the source and destination chain.
71 pub fn both(src_callback: IbcSrcCallback, dest_callback: IbcDstCallback) -> Self {
72 IbcCallbackRequest {
73 src_callback: Some(src_callback),
74 dest_callback: Some(dest_callback),
75 }
76 }
77
78 /// Use this if you want to execute callbacks on the source chain, but not the destination chain.
79 pub fn source(src_callback: IbcSrcCallback) -> Self {
80 IbcCallbackRequest {
81 src_callback: Some(src_callback),
82 dest_callback: None,
83 }
84 }
85
86 /// Use this if you want to execute callbacks on the destination chain, but not the source chain.
87 pub fn destination(dest_callback: IbcDstCallback) -> Self {
88 IbcCallbackRequest {
89 src_callback: None,
90 dest_callback: Some(dest_callback),
91 }
92 }
93}
94
95#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
96pub struct IbcSrcCallback {
97 /// The source chain address that should receive the callback.
98 /// For CosmWasm contracts, this *must* be `env.contract.address`.
99 /// Other addresses are not allowed and will effectively be ignored.
100 pub address: Addr,
101 /// Optional gas limit for the callback (in Cosmos SDK gas units)
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub gas_limit: Option<Uint64>,
104}
105
106#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
107pub struct IbcDstCallback {
108 /// The destination chain address that should receive the callback.
109 pub address: String,
110 /// Optional gas limit for the callback (in Cosmos SDK gas units)
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub gas_limit: Option<Uint64>,
113}
114
115/// The type of IBC source callback that is being called.
116///
117/// IBC source callbacks are needed for cases where your contract triggers the sending of an
118/// IBC packet through some other message (i.e. not through [`crate::IbcMsg::SendPacket`]) and needs to
119/// know whether or not the packet was successfully received on the other chain.
120/// A prominent example is the [`crate::IbcMsg::Transfer`] message. Without callbacks, you cannot know
121/// whether the transfer was successful or not.
122///
123/// Note that there are some prerequisites that need to be fulfilled to receive source callbacks:
124/// - The contract must implement the `ibc_source_callback` entrypoint.
125/// - The IBC application in the source chain must have support for the callbacks middleware.
126/// - You have to add serialized [`IbcCallbackRequest`] to a specific field of the message.
127/// For `IbcMsg::Transfer`, this is the `memo` field and it needs to be json-encoded.
128/// - The receiver of the callback must also be the sender of the message.
129#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
130#[serde(rename_all = "snake_case")]
131pub enum IbcSourceCallbackMsg {
132 Acknowledgement(IbcAckCallbackMsg),
133 Timeout(IbcTimeoutCallbackMsg),
134}
135
136#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
137#[non_exhaustive]
138pub struct IbcAckCallbackMsg {
139 pub acknowledgement: IbcAcknowledgement,
140 pub original_packet: IbcPacket,
141 pub relayer: Addr,
142}
143
144impl IbcAckCallbackMsg {
145 pub fn new(
146 acknowledgement: IbcAcknowledgement,
147 original_packet: IbcPacket,
148 relayer: Addr,
149 ) -> Self {
150 Self {
151 acknowledgement,
152 original_packet,
153 relayer,
154 }
155 }
156}
157
158#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
159#[non_exhaustive]
160pub struct IbcTimeoutCallbackMsg {
161 pub packet: IbcPacket,
162 pub relayer: Addr,
163}
164
165impl IbcTimeoutCallbackMsg {
166 pub fn new(packet: IbcPacket, relayer: Addr) -> Self {
167 Self { packet, relayer }
168 }
169}
170
171/// The message type of the IBC destination callback.
172///
173/// The IBC destination callback is needed for cases where someone triggers the sending of an
174/// IBC packet through some other message (i.e. not through [`crate::IbcMsg::SendPacket`]) and
175/// your contract needs to know that it received this.
176/// A prominent example is the [`crate::IbcMsg::Transfer`] message. Without callbacks, you cannot know
177/// that someone sent you IBC coins.
178///
179/// It is important to validate that the packet and acknowledgement are what you expect them to be.
180/// For example for a transfer message, the receiver is not necessarily the contract itself.
181///
182/// The callback is called when the packet is being acknowledged on the destination chain.
183/// This happens for both synchronous and asynchronous acknowledgements.
184///
185/// Note that there are some prerequisites that need to be fulfilled to receive destination callbacks:
186/// - The contract must implement the `ibc_destination_callback` entrypoint.
187/// - The IBC application in the destination chain must have support for the callbacks middleware.
188/// - You have to add serialized [`IbcCallbackRequest`] to a specific field of the message.
189/// For `IbcMsg::Transfer`, this is the `memo` field and it needs to be json-encoded.
190#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
191pub struct IbcDestinationCallbackMsg {
192 pub packet: IbcPacket,
193 pub ack: IbcAcknowledgement,
194}
195
196#[cfg(test)]
197mod tests {
198 use crate::to_json_string;
199
200 use super::*;
201
202 #[test]
203 fn ibc_callback_data_serialization() {
204 let mut data = IbcCallbackRequest::both(
205 IbcSrcCallback {
206 address: Addr::unchecked("src_address"),
207 gas_limit: Some(123u64.into()),
208 },
209 IbcDstCallback {
210 address: "dst_address".to_string(),
211 gas_limit: Some(1234u64.into()),
212 },
213 );
214
215 // both
216 let json = to_json_string(&data).unwrap();
217 assert_eq!(
218 json,
219 r#"{"src_callback":{"address":"src_address","gas_limit":"123"},"dest_callback":{"address":"dst_address","gas_limit":"1234"}}"#
220 );
221
222 // dst only, without gas limit
223 let mut src = data.src_callback.take().unwrap();
224 data.dest_callback.as_mut().unwrap().gas_limit = None;
225 let json = to_json_string(&data).unwrap();
226 assert_eq!(json, r#"{"dest_callback":{"address":"dst_address"}}"#);
227
228 // source only, without gas limit
229 src.gas_limit = None;
230 data.src_callback = Some(src);
231 data.dest_callback = None;
232 let json = to_json_string(&data).unwrap();
233 assert_eq!(json, r#"{"src_callback":{"address":"src_address"}}"#);
234 }
235}