From e7c23df2bdbf95f71afa6ca43be48b4e508f119e Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Tue, 18 Jun 2024 14:06:04 +0200 Subject: [PATCH] Add storage to channel lifecycle --- .../ibc/diy-protocol/channel-lifecycle.mdx | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/src/pages/ibc/diy-protocol/channel-lifecycle.mdx b/src/pages/ibc/diy-protocol/channel-lifecycle.mdx index de34c2bb..4ea31e39 100644 --- a/src/pages/ibc/diy-protocol/channel-lifecycle.mdx +++ b/src/pages/ibc/diy-protocol/channel-lifecycle.mdx @@ -60,6 +60,8 @@ entrypoint is called on chain B with the `IbcChannelOpenMsg::OpenTry` variant. See the following example and the [`IbcChannelOpenMsg`] documentation. ```rust filename="ibc.rs" template="core" +use cw_storage_plus::Item; + /// enforces ordering and versioning constraints #[entry_point] pub fn ibc_channel_open( @@ -69,10 +71,15 @@ pub fn ibc_channel_open( ) -> StdResult { let channel = msg.channel(); - // here we should check if the channel is what we expect (e.g. the order) + // in this example, we only allow a single channel per contract instance + // you can do more complex checks here + ensure!(!CHANNEL.exists(deps.storage), StdError::generic_err("channel already exists")); + + // we should check if the channel is what we expect (e.g. the order) if channel.order != IbcOrder::Ordered { return Err(StdError::generic_err("only ordered channels are supported")); } + // the OpenTry variant (on chain B) also has the counterparty version // we should check if it is what we expect if let Some(counter_version) = msg.counterparty_version() { @@ -83,12 +90,27 @@ pub fn ibc_channel_open( } } + // now, we save the channel ID to storage, so we can use it later + // this also prevents any further channel openings + CHANNEL.save(deps.storage, &ChannelInfo { + channel_id: channel.endpoint.channel_id.clone(), + finalized: false, + })?; + // return the channel version we support Ok(Some(Ibc3ChannelOpenResponse { version: IBC_APP_VERSION.to_string(), })) } +#[cw_serde] +struct ChannelInfo { + channel_id: String, + /// whether the channel is completely set up + finalized: bool, +} + +const CHANNEL: Item = Item::new("channel"); const IBC_APP_VERSION: &str = "my-protocol-v1"; ``` @@ -104,11 +126,17 @@ counterparty version. #### Permissions Opening a channel is generally a permissionless process, so make sure to keep -that in mind when implementing the entrypoints. You can add additional checks to -ensure that the channel is connecting to the correct counterparty or have a -state item containing a `bool` that is checked here to explicitly disable new -channels. Depending on the protocol, you might also want to limit yourself to a -single channel per contract. +that in mind when implementing the entrypoints. In the examples above, we only +allow a single channel per contract instance and always make sure not to +overwrite an existing channel. Note that we already save the channel in +`ibc_channel_open`. This causes overlapping channel openings to fail the channel +handshake. The drawback is that the contract can only connect to that one +channel, so if the handshake fails, the contract cannot connect to another +channel. + +You can add additional checks to ensure that the channel is connecting to the +correct counterparty or use a map to keep track of multiple channels connecting +to different counterparties. You can also be more restrictive and only allow the contract itself to initiate the channel creation handshake. This can be done by adding a state item to the @@ -234,6 +262,8 @@ chain B. The full data this entrypoint receives can be seen in the [`IbcChannelConnectMsg`] documentation. Here is more example code: ```rust filename="ibc.rs" template="core" +use cw_storage_plus::Item; + pub fn ibc_channel_connect( deps: DepsMut, env: Env, @@ -241,11 +271,27 @@ pub fn ibc_channel_connect( ) -> StdResult { let channel = msg.channel(); - // you probably want to save the `channel.endpoint.channel_id` to storage, - // so you can use it when sending packets + // in this example, we only allow a single channel per contract instance + // you can do more complex checks here + let mut channel_info = CHANNEL.load(deps.storage)?; + ensure!(!channel_info.finalized, StdError::generic_err("channel already finalized")); + debug_assert_eq!(channel_info.channel_id, channel.endpoint.channel_id, "channel ID mismatch"); + + // at this point, we are finished setting up the channel and can mark it as finalized + channel_info.finalized = true; + CHANNEL.save(deps.storage, &channel_info)?; Ok(IbcBasicResponse::new()) } + +#[cw_serde] +struct ChannelInfo { + channel_id: String, + /// whether the channel is completely set up + finalized: bool, +} + +const CHANNEL: Item = Item::new("channel"); ``` [`IbcChannelConnectMsg`]: