Skip to content

Commit ec91dcb

Browse files
authored
Merge pull request #2484 from CosmWasm/co/ibc-callbacks-funds
Add special handling for IBC Transfer to destination callback
2 parents 1ce8364 + e91b17e commit ec91dcb

File tree

6 files changed

+166
-3
lines changed

6 files changed

+166
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ and this project adheres to
3535
([#2480])
3636
- cosmwasm-std: Add `WasmQuery::RawRange` query to allow querying raw storage
3737
ranges of different contracts. ([#2471])
38+
- cosmwasm-std: Add `transfer` field to `IbcDestinationCallbackMsg`, providing
39+
an easier way to handle an IBC transfer in a destination callback. ([#2484])
3840

3941
## Changed
4042

@@ -138,6 +140,7 @@ and this project adheres to
138140
[#2477]: https://github.com/CosmWasm/cosmwasm/pull/2477
139141
[#2479]: https://github.com/CosmWasm/cosmwasm/pull/2479
140142
[#2480]: https://github.com/CosmWasm/cosmwasm/pull/2480
143+
[#2484]: https://github.com/CosmWasm/cosmwasm/pull/2484
141144

142145
## [2.2.0] - 2024-12-17
143146

contracts/ibc-callbacks/schema/ibc-callbacks.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,22 @@
148148
"description": "Binary is a wrapper around Vec<u8> to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec<u8>. See also <https://github.com/CosmWasm/cosmwasm/blob/main/docs/MESSAGE_TYPES.md>.",
149149
"type": "string"
150150
},
151+
"Coin": {
152+
"type": "object",
153+
"required": [
154+
"amount",
155+
"denom"
156+
],
157+
"properties": {
158+
"amount": {
159+
"$ref": "#/definitions/Uint256"
160+
},
161+
"denom": {
162+
"type": "string"
163+
}
164+
},
165+
"additionalProperties": false
166+
},
151167
"IbcAckCallbackMsg": {
152168
"type": "object",
153169
"required": [
@@ -193,6 +209,17 @@
193209
},
194210
"packet": {
195211
"$ref": "#/definitions/IbcPacket"
212+
},
213+
"transfer": {
214+
"description": "When the underlying packet is a successful transfer message, this field contains information about the transfer. Otherwise it is empty.\n\nThis is always empty on chains using CosmWasm < 3.0",
215+
"anyOf": [
216+
{
217+
"$ref": "#/definitions/IbcTransferCallback"
218+
},
219+
{
220+
"type": "null"
221+
}
222+
]
196223
}
197224
},
198225
"additionalProperties": false
@@ -325,6 +352,36 @@
325352
},
326353
"additionalProperties": false
327354
},
355+
"IbcTransferCallback": {
356+
"type": "object",
357+
"required": [
358+
"funds",
359+
"receiver",
360+
"sender"
361+
],
362+
"properties": {
363+
"funds": {
364+
"description": "The funds that were transferred.\n\nWhen the callback is executed, the transfer is completed already and the coins are now owned by the receiver.",
365+
"type": "array",
366+
"items": {
367+
"$ref": "#/definitions/Coin"
368+
}
369+
},
370+
"receiver": {
371+
"description": "Address of the receiver of the transfer. Since this is on the destination chain, this is a valid address.",
372+
"allOf": [
373+
{
374+
"$ref": "#/definitions/Addr"
375+
}
376+
]
377+
},
378+
"sender": {
379+
"description": "Address of the sender of the transfer. Note that this is *not* a valid address on the destination chain.",
380+
"type": "string"
381+
}
382+
},
383+
"additionalProperties": false
384+
},
328385
"Timestamp": {
329386
"description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
330387
"allOf": [
@@ -333,6 +390,10 @@
333390
}
334391
]
335392
},
393+
"Uint256": {
394+
"description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `new` to create instances out of u128, `from` for other primitive uint types or `from_be_bytes` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::new(258u128); let b = Uint256::from(258u16); let c = Uint256::from_be_bytes([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); assert_eq!(a, c); ```",
395+
"type": "string"
396+
},
336397
"Uint64": {
337398
"description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
338399
"type": "string"

contracts/ibc-callbacks/schema/raw/response_to_callback_stats.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@
3838
"description": "Binary is a wrapper around Vec<u8> to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec<u8>. See also <https://github.com/CosmWasm/cosmwasm/blob/main/docs/MESSAGE_TYPES.md>.",
3939
"type": "string"
4040
},
41+
"Coin": {
42+
"type": "object",
43+
"required": [
44+
"amount",
45+
"denom"
46+
],
47+
"properties": {
48+
"amount": {
49+
"$ref": "#/definitions/Uint256"
50+
},
51+
"denom": {
52+
"type": "string"
53+
}
54+
},
55+
"additionalProperties": false
56+
},
4157
"IbcAckCallbackMsg": {
4258
"type": "object",
4359
"required": [
@@ -83,6 +99,17 @@
8399
},
84100
"packet": {
85101
"$ref": "#/definitions/IbcPacket"
102+
},
103+
"transfer": {
104+
"description": "When the underlying packet is a successful transfer message, this field contains information about the transfer. Otherwise it is empty.\n\nThis is always empty on chains using CosmWasm < 3.0",
105+
"anyOf": [
106+
{
107+
"$ref": "#/definitions/IbcTransferCallback"
108+
},
109+
{
110+
"type": "null"
111+
}
112+
]
86113
}
87114
},
88115
"additionalProperties": false
@@ -215,6 +242,36 @@
215242
},
216243
"additionalProperties": false
217244
},
245+
"IbcTransferCallback": {
246+
"type": "object",
247+
"required": [
248+
"funds",
249+
"receiver",
250+
"sender"
251+
],
252+
"properties": {
253+
"funds": {
254+
"description": "The funds that were transferred.\n\nWhen the callback is executed, the transfer is completed already and the coins are now owned by the receiver.",
255+
"type": "array",
256+
"items": {
257+
"$ref": "#/definitions/Coin"
258+
}
259+
},
260+
"receiver": {
261+
"description": "Address of the receiver of the transfer. Since this is on the destination chain, this is a valid address.",
262+
"allOf": [
263+
{
264+
"$ref": "#/definitions/Addr"
265+
}
266+
]
267+
},
268+
"sender": {
269+
"description": "Address of the sender of the transfer. Note that this is *not* a valid address on the destination chain.",
270+
"type": "string"
271+
}
272+
},
273+
"additionalProperties": false
274+
},
218275
"Timestamp": {
219276
"description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
220277
"allOf": [
@@ -223,6 +280,10 @@
223280
}
224281
]
225282
},
283+
"Uint256": {
284+
"description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `new` to create instances out of u128, `from` for other primitive uint types or `from_be_bytes` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::new(258u128); let b = Uint256::from(258u16); let c = Uint256::from_be_bytes([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); assert_eq!(a, c); ```",
285+
"type": "string"
286+
},
226287
"Uint64": {
227288
"description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
228289
"type": "string"

contracts/ibc-callbacks/src/contract.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use cosmwasm_std::{
2-
entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, IbcBasicResponse,
2+
ensure, entry_point, to_json_binary, Binary, Deps, DepsMut, Empty, Env, IbcBasicResponse,
33
IbcDestinationCallbackMsg, IbcDstCallback, IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout,
44
MessageInfo, Response, StdError, StdResult, TransferMsgBuilder,
55
};
@@ -132,6 +132,23 @@ pub fn ibc_destination_callback(
132132
) -> StdResult<IbcBasicResponse> {
133133
let mut counts = load_stats(deps.storage)?;
134134

135+
// Assert that the receiver has the funds in the message.
136+
// This is just for testing purposes and to show that the funds are already available during
137+
// the callback.
138+
if let Some(transfer) = &msg.transfer {
139+
for coin in &transfer.funds {
140+
let balance = deps
141+
.querier
142+
.query_balance(&transfer.receiver, &coin.denom)?;
143+
ensure!(
144+
balance.amount >= coin.amount,
145+
StdError::generic_err(format!(
146+
"Didn't receive expected funds. expected: {coin}, have: {balance}"
147+
))
148+
);
149+
}
150+
}
151+
135152
// save the receive
136153
counts.ibc_destination_callbacks.push(msg);
137154

packages/std/src/ibc/callbacks.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use schemars::JsonSchema;
55
use serde::{Deserialize, Serialize};
66

7-
use crate::{Addr, IbcAcknowledgement, IbcPacket, Uint64};
7+
use crate::{Addr, Coin, IbcAcknowledgement, IbcPacket, Uint64};
88

99
/// This is just a type representing the data that has to be sent with the IBC message to receive
1010
/// callbacks. It should be serialized and sent with the IBC message.
@@ -191,6 +191,27 @@ impl IbcTimeoutCallbackMsg {
191191
pub struct IbcDestinationCallbackMsg {
192192
pub packet: IbcPacket,
193193
pub ack: IbcAcknowledgement,
194+
/// When the underlying packet is a successful transfer message,
195+
/// this field contains information about the transfer. Otherwise it is empty.
196+
///
197+
/// This is always empty on chains using CosmWasm < 3.0
198+
#[serde(default)]
199+
pub transfer: Option<IbcTransferCallback>,
200+
}
201+
202+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
203+
pub struct IbcTransferCallback {
204+
/// Address of the sender of the transfer.
205+
/// Note that this is *not* a valid address on the destination chain.
206+
pub sender: String,
207+
/// Address of the receiver of the transfer.
208+
/// Since this is on the destination chain, this is a valid address.
209+
pub receiver: Addr,
210+
/// The funds that were transferred.
211+
///
212+
/// When the callback is executed, the transfer is completed already and the coins are now owned
213+
/// by the receiver.
214+
pub funds: Vec<Coin>,
194215
}
195216

196217
#[cfg(test)]

packages/std/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ pub use crate::ibc::{
7575
IbcDestinationCallbackMsg, IbcDstCallback, IbcEndpoint, IbcMsg, IbcOrder, IbcPacket,
7676
IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse,
7777
IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, IbcTimeoutBlock, IbcTimeoutCallbackMsg,
78-
TransferMsgBuilder,
78+
IbcTransferCallback, TransferMsgBuilder,
7979
};
8080
pub use crate::ibc2::{
8181
Ibc2Msg, Ibc2PacketAckMsg, Ibc2PacketReceiveMsg, Ibc2PacketSendMsg, Ibc2PacketTimeoutMsg,

0 commit comments

Comments
 (0)