Skip to content

Commit 74959fb

Browse files
committed
Added IBC chapters.
1 parent b785b8b commit 74959fb

File tree

11 files changed

+1103
-0
lines changed

11 files changed

+1103
-0
lines changed

docs/ibc/basic-concepts.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
sidebar_position: 2
3+
---
4+
5+
# Basic concepts
6+
7+
In order to understand how IBC works, it is important to understand some basic concepts:
8+
9+
- **Port**: Every instantiation of an ibc-enabled contract creates a unique port, similarly to how
10+
it creates a unique address. Native Cosmos SDK modules can also have one or multiple unique ports.
11+
- **Channel**: A connection between two ports on different blockchains that allows them to send
12+
packets to each other. Each port can have multiple channels.
13+
- **Packet**: A piece of binary data that is sent through a channel. It can time out if it is not
14+
delivered within a certain time frame.
15+
- **Relayer**: An off-chain service that is responsible for passing packets between blockchains. The
16+
relayer watches for new packet commitments on one chain and submits them to the other chain. This
17+
is the way in which your packets actually reach the other chain. Anyone can run a relayer.
18+
19+
Here's an example diagram of how these concepts are related:
20+
21+
```mermaid
22+
graph LR;
23+
subgraph AA [Chain A]
24+
A[contract];
25+
B[port 1];
26+
H[channel 1];
27+
I[channel 2];
28+
end
29+
subgraph BB [Chain B]
30+
J[channel 1];
31+
K[channel 2];
32+
E[port 1];
33+
F[port 2];
34+
G[port 3];
35+
C[contract];
36+
D[module];
37+
end
38+
A --- B;
39+
B --- H;
40+
B --- I;
41+
42+
H <-->|relayer| J;
43+
I <-->|relayer| K;
44+
45+
J --- E;
46+
K --- F;
47+
E --- C;
48+
F --- D;
49+
G --- D;
50+
```
51+
52+
We will go into more detail on how these concepts are related to CosmWasm in the later sections, but
53+
this should give you some basic terminology to start with. If you want to learn more about IBC, you
54+
can read the [IBC specification] or check out the [IBC documentation].
55+
56+
[IBC specification]: https://github.com/cosmos/ibc
57+
[IBC documentation]: https://ibc.cosmos.network/main
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"label": "Extensions",
3+
"position": 5,
4+
"link": {
5+
"type": "doc",
6+
"id": "extensions"
7+
}
8+
}

docs/ibc/extensions/callbacks.mdx

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
---
2+
title: Callbacks
3+
sidebar_position: 1
4+
---
5+
6+
import Tabs from '@theme/Tabs';
7+
import TabItem from '@theme/TabItem';
8+
9+
# ADR-8: IBC Callbacks
10+
11+
If you send an IBC packet from a contract, you'll get an acknowledgement of receipt or timeout in
12+
the respective handler (see [Packet lifecycle](../own-protocol/packet-lifecycle) for more details).
13+
This is great, but what if you want to get notified on completion of IBC packets you did not send
14+
directly from the contract, but that were caused by a message you sent? One real-world use-case
15+
of this is [ICS20](../ics20) transfers. When you send an ICS20 transfer message, the transfer module
16+
on the chain sends an IBC packet, but you do not get any feedback on whether the transfer was successful
17+
or not and the destination does not get informed of its newfound wealth.
18+
19+
To solve this problem, the [ADR-8 specification][adr-8] was created. On the source chain, it
20+
provides callbacks when an IBC packet was acknowledged or timed out. On the destination chain, it
21+
triggers callbacks when a packet is received.
22+
23+
IBC Callbacks are a generalized successor of [IBC Hooks][ibc-hooks] that works not just for ICS20
24+
transfers, but any message that supports the required interface.
25+
26+
[adr-8]: https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-008-app-caller-cbs.md
27+
[ibc-hooks]: https://github.com/cosmos/ibc-apps/blob/main/modules/ibc-hooks/README.md
28+
29+
:::tip
30+
To receive callbacks, the chain needs to support IBC Callbacks for the message type.
31+
:::
32+
33+
## Enabling IBC Callbacks for a message
34+
35+
You need to explicitly opt-in to IBC Callbacks for each message. In order to do this, you need to
36+
add some metadata to the message, including who should receive the callbacks.
37+
38+
:::tip
39+
The exact data format and how to add it to the message can vary, but for the `IbcMsg::Transfer`
40+
message, this data is in JSON format and needs to be added to the `memo` field.
41+
:::
42+
43+
44+
To make this as easy as possible, we provide two ways to generate the correct JSON. One is a builder
45+
type for the `IbcMsg::Transfer` type which provides a type-safe way to generate the complete
46+
`IbcMsg::Transfer`, the other is a helper type [`IbcCallbackRequest`] that just generates the JSON
47+
for the `memo` field:
48+
49+
[`IbcCallbackRequest`]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.IbcCallbackRequest.html
50+
51+
<Tabs>
52+
<TabItem value="1" label="TransferMsgBuilder (recommended)">
53+
54+
```rust
55+
let msg = TransferMsgBuilder::new(
56+
"channel-0".to_string(),
57+
"cosmos1exampleaddress".to_string(),
58+
Coin::new(10u32, "ucoin"),
59+
Timestamp::from_seconds(12345),
60+
)
61+
.with_src_callback(IbcSrcCallback {
62+
address: env.contract.address,
63+
gas_limit: None,
64+
})
65+
.with_dst_callback(IbcDstCallback {
66+
address: "cosmos1exampleaddress".to_string(),
67+
gas_limit: None,
68+
})
69+
.build();
70+
71+
Ok(Response::new().add_message(msg))
72+
```
73+
74+
</TabItem>
75+
76+
<TabItem value="2" label="IbcCallbackRequest">
77+
78+
```rust
79+
let msg = IbcMsg::Transfer {
80+
channel_id: "channel-0".to_string(),
81+
to_address: "cosmos1exampleaddress".to_string(),
82+
amount: Coin::new(10u32, "ucoin"),
83+
timeout: Timestamp::from_seconds(12345).into(),
84+
memo: Some(to_json_string(&IbcCallbackRequest::both(IbcSrcCallback {
85+
address: env.contract.address,
86+
gas_limit: None,
87+
}, IbcDstCallback {
88+
address: "cosmos1exampleaddress".to_string(),
89+
gas_limit: None,
90+
})).unwrap()),
91+
};
92+
93+
Ok(Response::new().add_message(msg))
94+
```
95+
96+
</TabItem>
97+
98+
</Tabs>
99+
100+
As you can see, you can request callbacks for both the source and destination chain. However, you
101+
can also request callbacks for only one of them. For this, you need to provide the address that
102+
should receive the callback. Optionally you can set a gas limit for the callback execution. Please
103+
take a look at the `IbcCallbackRequest` docs for more information.
104+
105+
:::tip
106+
The `address` of the source callback always needs to be the contract address that sends the
107+
message (`env.contract.address`). Otherwise, the callback will error and the contract will not be
108+
called.
109+
:::
110+
111+
## Receiving IBC Callbacks
112+
113+
To receive callbacks, you need to implement two new entrypoints in your contract:
114+
115+
- `ibc_source_callback`, receiving an [`IbcSourceCallbackMsg`] enum which can be one of two types:
116+
- [`IbcAckCallbackMsg`] if the packet was acknowledged
117+
- [`IbcTimeoutCallbackMsg`] if the packet timed out
118+
- `ibc_destination_callback`, receiving an [`IbcDestinationCallbackMsg`]
119+
120+
[`IbcSourceCallbackMsg`]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/enum.IbcSourceCallbackMsg.html
121+
[`IbcAckCallbackMsg`]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.IbcAckCallbackMsg.html
122+
[`IbcTimeoutCallbackMsg`]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.IbcTimeoutCallbackMsg.html
123+
[`IbcDestinationCallbackMsg`]: https://docs.rs/cosmwasm-std/latest/cosmwasm_std/struct.IbcDestinationCallbackMsg.html
124+
125+
### Source Callback
126+
127+
The `ibc_source_callback` entrypoint is called when the packet was either acknowledged or timed out.
128+
You can use this to update your contract state, release locked funds or trigger other actions.
129+
130+
As mentioned above, the receiver of this callback is always the contract that sent the message. This
131+
means you don't need to assume that an attacker might be sending you fake callbacks, reducing the
132+
need for validation.
133+
134+
This is how you can implement the `ibc_source_callback` entrypoint:
135+
136+
```rust
137+
#[cfg_attr(not(feature = "library"), entry_point)]
138+
pub fn ibc_source_callback(
139+
deps: DepsMut,
140+
_env: Env,
141+
msg: IbcSourceCallbackMsg,
142+
) -> StdResult<IbcBasicResponse> {
143+
match msg {
144+
IbcSourceCallbackMsg::Acknowledgement(IbcAckCallbackMsg {
145+
acknowledgement,
146+
original_packet,
147+
relayer,
148+
..
149+
}) => {
150+
// handle the acknowledgement
151+
}
152+
IbcSourceCallbackMsg::Timeout(IbcTimeoutCallbackMsg {
153+
packet, relayer, ..
154+
}) => {
155+
// handle the timeout
156+
}
157+
}
158+
159+
Ok(IbcBasicResponse::new().add_attribute("action", "ibc_source_callback"))
160+
}
161+
```
162+
163+
#### Acknowledgement
164+
165+
When the packet was acknowledged, you will receive the `Acknowledgement(IbcAckCallbackMsg)` variant
166+
of `IbcSourceCallbackMsg`. This means that the packet was successfully received and processed by the
167+
application on the destination chain. The message contains the original packet data, the
168+
acknowledgement and the address of the relayer.
169+
170+
#### Timeout
171+
172+
When the packet timed out, you will receive the `Timeout(IbcTimeoutCallbackMsg)` variant of
173+
`IbcSourceCallbackMsg`. This means that the packet was not delivered to the destination chain in
174+
time (e.g. because no relayer picked it up or the chain is stopped). The message contains the
175+
original packet data and the address of the relayer who told you about the timeout.
176+
177+
### Destination Callback
178+
179+
The `ibc_destination_callback` entrypoint is called when a packet was acknowledged on the
180+
destination chain. The shape of an acknowledgement is protocol specific and usually contains both
181+
success and error cases.
182+
183+
For the `IbcMsg::Transfer` message, a success acknowledgement means that the tokens were
184+
successfully transferred to the destination chain. It allows you to use the received tokens
185+
immediately, update the contract state to reflect the new tokens or trigger other actions.
186+
187+
:::warning
188+
It is important to validate that the packet and acknowledgement are what you expect them to be.
189+
For example for a transfer message, the receiver of the funds is not necessarily the contract that
190+
receives the callbacks.
191+
:::
192+
193+
This is how you can implement the `ibc_destination_callback` entrypoint:
194+
195+
:::tip
196+
This example uses the `ibc` crate with the `serde` feature, which provides a data type for the
197+
transfer packet format to avoid defining that ourselves. You can add it to your `Cargo.toml` by
198+
running `cargo add ibc --features serde`.
199+
:::
200+
201+
```rust
202+
use ibc::apps::transfer::types::proto::transfer::v2::FungibleTokenPacketData;
203+
use std::str::FromStr;
204+
205+
#[cfg_attr(not(feature = "library"), entry_point)]
206+
pub fn ibc_destination_callback(
207+
deps: DepsMut,
208+
env: Env,
209+
msg: IbcDestinationCallbackMsg,
210+
) -> StdResult<IbcBasicResponse> {
211+
ensure_eq!(
212+
msg.packet.dest.port_id,
213+
"transfer", // transfer module uses this port by default
214+
StdError::generic_err("only want to handle transfer packets")
215+
);
216+
ensure_eq!(
217+
msg.ack.data,
218+
StdAck::success(b"\x01").to_binary(), // this is how a successful transfer ack looks
219+
StdError::generic_err("only want to handle successful transfers")
220+
);
221+
// At this point we know that this is a callback for a successful transfer,
222+
// but not to whom it is going, how much and what denom.
223+
224+
// Parse the packet data to get that information:
225+
let packet_data: FungibleTokenPacketData = from_json(&msg.packet.data)?;
226+
227+
// The receiver should be a valid address on this chain.
228+
// Remember, we are on the destination chain.
229+
let receiver = deps.api.addr_validate(packet_data.receiver.as_ref())?;
230+
ensure_eq!(
231+
receiver,
232+
env.contract.address,
233+
StdError::generic_err("only want to handle transfers to this contract")
234+
);
235+
236+
// We only care about this chain's native token in this example.
237+
// The `packet_data.denom` is formatted as `{port id}/{channel id}/{denom}`,
238+
// where the port id and channel id are the source chain's identifiers.
239+
// Please note that the denom is not formatted like that when you transfer untrn
240+
// from Neutron to some other chain. In that case, the denom is just the native
241+
// token of the source chain (untrn).
242+
// That being said, assuming we are running this on Neutron and only want to
243+
// handle NTRN tokens, the denom should look like this:
244+
let ntrn_denom_on_source_chain = format!(
245+
"{}/{}/untrn",
246+
msg.packet.src.port_id, msg.packet.src.channel_id
247+
);
248+
ensure_eq!(
249+
packet_data.denom,
250+
ntrn_denom_on_source_chain,
251+
StdError::generic_err("only want to handle NTRN tokens")
252+
);
253+
254+
// Now, we can do something with our tokens.
255+
// For example, we could send them to some address:
256+
let msg = BankMsg::Send {
257+
to_address: "neutron155exr8rqjrknusllpzxdfvezxr8ddpqehj9g9d".to_string(),
258+
// this panics if the amount is too large
259+
amount: coins(u128::from_str(&packet_data.amount).unwrap(), "untrn"),
260+
};
261+
262+
Ok(IbcBasicResponse::new()
263+
.add_message(msg)
264+
.add_attribute("action", "ibc_destination_callback"))
265+
}
266+
```
267+
268+
:::warning
269+
Please note that this example assumes an ICS20 v1 channel. At the time of writing, the
270+
specification and implementation have just been extended with a v2 which changes the [packet
271+
format]. If you want to use this in production code, you should make sure to support both formats,
272+
such that a channel upgrade does not break your contract.
273+
:::
274+
275+
As mentioned above, anyone can send you a destination callback for a packet. This means you need to
276+
make sure that the packet and acknowledgement are what you expect them to be. For example, for a
277+
transfer message, you need to make sure that the transfer was successful, that the receiver of the
278+
funds is your contract and the denomination is what you want to receive. This requires some
279+
knowledge about the [packet format].
280+
281+
[packet format]: https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md#data-structures
282+
283+
### Error handling
284+
285+
Returning an error or panicking from a callback will **not** influence the IBC packet lifecycle. The
286+
packet will still be acknowledged or timed out. This means that you can safely return errors from
287+
your callbacks if you want to ignore the packet.
288+
289+
It will, however, undo any state changes that you made in the callback, just like most other entrypoints.

docs/ibc/extensions/extensions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Extensions
2+
3+
In this chapter we will cover various extensions to IBC that are not part of the core IBC protocol,
4+
but can be used in combination with either your own protocol or an existing one (like [ICS20](./ics20)).

docs/ibc/getting-started.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
sidebar_position: 1
3+
---
4+
5+
# Getting started
6+
7+
To get started, you need to enable the `stargate` feature of the `cosmwasm-std` crate.
8+
This will enable additional functionality that is not available on all chains, including IBC support.
9+
10+
```toml
11+
cosmwasm-std = { version = "2", features = ["stargate"] }
12+
```
13+
14+
:::info
15+
16+
The naming "stargate" is somewhat confusing. It is a reference to the Cosmos SDK 0.40 upgrade with the same name.
17+
This upgrade introduced (among other things) IBC.
18+
19+
:::

0 commit comments

Comments
 (0)