Skip to content

feat: Add IBCv2 timeout entrypoint #2454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions contracts/ibc2/schema/ibc2.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"type": "object",
"required": [
"ibc2_packet_receive_counter",
"ibc2_packet_timeout_counter",
"last_packet_seq",
"last_source_client"
],
Expand All @@ -47,6 +48,11 @@
"format": "uint32",
"minimum": 0.0
},
"ibc2_packet_timeout_counter": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"last_packet_seq": {
"type": "integer",
"format": "uint64",
Expand Down
6 changes: 6 additions & 0 deletions contracts/ibc2/schema/raw/response_to_query_state.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"type": "object",
"required": [
"ibc2_packet_receive_counter",
"ibc2_packet_timeout_counter",
"last_packet_seq",
"last_source_client"
],
Expand All @@ -13,6 +14,11 @@
"format": "uint32",
"minimum": 0.0
},
"ibc2_packet_timeout_counter": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"last_packet_seq": {
"type": "integer",
"format": "uint64",
Expand Down
31 changes: 28 additions & 3 deletions contracts/ibc2/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use cosmwasm_std::{
entry_point, from_json, to_json_vec, Binary, Deps, DepsMut, Empty, Env, Ibc2Msg,
Ibc2PacketReceiveMsg, Ibc2Payload, IbcAcknowledgement, IbcReceiveResponse, MessageInfo,
QueryResponse, Response, StdAck, StdError, StdResult,
Ibc2PacketReceiveMsg, Ibc2PacketTimeoutMsg, Ibc2Payload, IbcAcknowledgement, IbcBasicResponse,
IbcReceiveResponse, MessageInfo, QueryResponse, Response, StdAck, StdError, StdResult,
};

use crate::msg::{IbcPayload, QueryMsg};
Expand All @@ -18,6 +18,7 @@ pub fn instantiate(
STATE_KEY,
&to_json_vec(&State {
ibc2_packet_receive_counter: 0,
ibc2_packet_timeout_counter: 0,
last_source_client: "".to_owned(),
last_packet_seq: 0,
})?,
Expand Down Expand Up @@ -60,6 +61,7 @@ pub fn ibc2_packet_receive(
ibc2_packet_receive_counter: state.ibc2_packet_receive_counter + 1,
last_source_client: msg.source_client.clone(),
last_packet_seq: msg.packet_sequence,
..state
})?,
);
let new_payload = Ibc2Payload::new(
Expand All @@ -72,7 +74,7 @@ pub fn ibc2_packet_receive(
let new_msg = Ibc2Msg::SendPacket {
source_client: msg.source_client,
payloads: vec![new_payload],
timeout: env.block.time.plus_seconds(60_u64),
timeout: env.block.time.plus_minutes(1_u64),
};

let resp = if json_payload.response_without_ack {
Expand All @@ -95,3 +97,26 @@ pub fn ibc2_packet_receive(
Ok(resp)
}
}

#[entry_point]
pub fn ibc2_packet_timeout(
deps: DepsMut,
_env: Env,
_msg: Ibc2PacketTimeoutMsg,
) -> StdResult<IbcBasicResponse> {
let data = deps
.storage
.get(STATE_KEY)
.ok_or_else(|| StdError::generic_err("State not found."))?;
let state: State = from_json(data)?;

deps.storage.set(
STATE_KEY,
&to_json_vec(&State {
ibc2_packet_timeout_counter: state.ibc2_packet_timeout_counter + 1,
..state
})?,
);

Ok(IbcBasicResponse::default())
}
1 change: 1 addition & 0 deletions contracts/ibc2/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct State {
pub ibc2_packet_receive_counter: u32,
pub ibc2_packet_timeout_counter: u32,
pub last_source_client: String,
pub last_packet_seq: u64,
}
Expand Down
57 changes: 54 additions & 3 deletions packages/std/src/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::ibc::{
};
use crate::ibc::{IbcChannelOpenMsg, IbcChannelOpenResponse};
#[cfg(feature = "ibc2")]
use crate::ibc2::Ibc2PacketReceiveMsg;
use crate::ibc2::{Ibc2PacketReceiveMsg, Ibc2PacketTimeoutMsg};
use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage};
use crate::memory::{Owned, Region};
use crate::panic::install_panic_handler;
Expand Down Expand Up @@ -455,8 +455,7 @@ where
/// do_ibc_packet_timeout is designed for use with #[entry_point] to make a "C" extern
///
/// contract_fn is called when a packet that this contract previously sent has provably
/// timedout and will never be relayed to the calling chain. This generally behaves
/// like ick_ack_fn upon an acknowledgement containing an error.
/// timed out and will never be relayed to the destination chain.
///
/// - `Q`: custom query type (see QueryRequest)
/// - `C`: custom response message type (see CosmosMsg)
Expand Down Expand Up @@ -555,6 +554,35 @@ where
Region::from_vec(v).to_heap_ptr() as u32
}

/// do_ibc2_packet_timeout is designed for use with #[entry_point] to make a "C" extern
///
/// contract_fn is called when a packet that this contract previously sent has provably
/// timed out and will never be relayed to the destination chain.
///
/// - `Q`: custom query type (see QueryRequest)
/// - `C`: custom response message type (see CosmosMsg)
/// - `E`: error type for responses
#[cfg(feature = "ibc2")]
pub fn do_ibc2_packet_timeout<Q, C, E>(
contract_fn: &dyn Fn(DepsMut<Q>, Env, Ibc2PacketTimeoutMsg) -> Result<IbcBasicResponse<C>, E>,
env_ptr: u32,
msg_ptr: u32,
) -> u32
where
Q: CustomQuery,
C: CustomMsg,
E: ToString,
{
install_panic_handler();
let res = _do_ibc2_packet_timeout(
contract_fn,
env_ptr as *mut Region<Owned>,
msg_ptr as *mut Region<Owned>,
);
let v = to_json_vec(&res).unwrap();
Region::from_vec(v).to_heap_ptr() as u32
}

fn _do_instantiate<Q, M, C, E>(
instantiate_fn: &dyn Fn(DepsMut<Q>, Env, MessageInfo, M) -> Result<Response<C>, E>,
env_ptr: *mut Region<Owned>,
Expand Down Expand Up @@ -945,3 +973,26 @@ where
let mut deps = make_dependencies();
contract_fn(deps.as_mut(), env, msg).into()
}

#[cfg(feature = "ibc2")]
fn _do_ibc2_packet_timeout<Q, C, E>(
contract_fn: &dyn Fn(DepsMut<Q>, Env, Ibc2PacketTimeoutMsg) -> Result<IbcBasicResponse<C>, E>,
env_ptr: *mut Region<Owned>,
msg_ptr: *mut Region<Owned>,
) -> ContractResult<IbcBasicResponse<C>>
where
Q: CustomQuery,
C: CustomMsg,
E: ToString,
{
let env: Vec<u8> =
unsafe { Region::from_heap_ptr(ptr::NonNull::new(env_ptr).unwrap()).into_vec() };
let msg: Vec<u8> =
unsafe { Region::from_heap_ptr(ptr::NonNull::new(msg_ptr).unwrap()).into_vec() };

let env: Env = try_into_contract_result!(from_json(env));
let msg: Ibc2PacketTimeoutMsg = try_into_contract_result!(from_json(msg));

let mut deps = make_dependencies();
contract_fn(deps.as_mut(), env, msg).into()
}
45 changes: 44 additions & 1 deletion packages/std/src/ibc2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,19 @@
},
}

/// The message that is passed into `ibc2_packet_receive`
/// IBC2PacketReceiveMsg represents a message received via the IBC2 protocol.
/// The message that is passed into `ibc2_packet_receive`.
/// It contains the payload data along with metadata about the source and relayer.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[non_exhaustive]
pub struct Ibc2PacketReceiveMsg {
/// The actual data being transmitted via IBC2.
pub payload: Ibc2Payload,
/// The address of the entity that relayed the packet.
pub relayer: Addr,
/// The identifier of the source IBC client.
pub source_client: String,
/// The unique sequence number of the received packet.
pub packet_sequence: u64,
}

Expand All @@ -89,6 +95,43 @@
}
}

/// IBC2PacketTimeoutMsg represents a timeout event for a packet that was not
/// successfully delivered within the expected timeframe in the IBC2 protocol.
/// It includes details about the source and destination clients, and the sequence
/// number of the timed-out packet.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[non_exhaustive]
pub struct Ibc2PacketTimeoutMsg {
/// The data associated with the timed-out packet.
pub payload: Ibc2Payload,
/// The identifier of the client that originally sent the packet.
pub source_client: String,
/// The identifier of the client that was the intended recipient.
pub destination_client: String,
/// The sequence number of the timed-out packet.
pub packet_sequence: u64,
/// The address of the relayer responsible for the packet.
pub relayer: Addr,
}

impl Ibc2PacketTimeoutMsg {
pub fn new(
payload: Ibc2Payload,
source_client: String,
destination_client: String,
packet_sequence: u64,
relayer: Addr,
) -> Self {
Self {
payload,
source_client,
destination_client,
packet_sequence,
relayer,
}
}

Check warning on line 132 in packages/std/src/ibc2.rs

View check run for this annotation

Codecov / codecov/patch

packages/std/src/ibc2.rs#L118-L132

Added lines #L118 - L132 were not covered by tests
}

#[cfg(test)]
mod tests {
use serde_json::to_string;
Expand Down
6 changes: 3 additions & 3 deletions packages/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub use crate::ibc::{
IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, IbcTimeoutBlock, IbcTimeoutCallbackMsg,
TransferMsgBuilder,
};
pub use crate::ibc2::{Ibc2Msg, Ibc2PacketReceiveMsg, Ibc2Payload};
pub use crate::ibc2::{Ibc2Msg, Ibc2PacketReceiveMsg, Ibc2PacketTimeoutMsg, Ibc2Payload};
#[cfg(feature = "iterator")]
pub use crate::iterator::{Order, Record};
pub use crate::math::{
Expand Down Expand Up @@ -124,15 +124,15 @@ mod imports;
#[cfg(target_arch = "wasm32")]
mod memory; // Used by exports and imports only. This assumes pointers are 32 bit long, which makes it untestable on dev machines.

#[cfg(all(feature = "ibc2", target_arch = "wasm32"))]
pub use crate::exports::do_ibc2_packet_receive;
#[cfg(all(feature = "cosmwasm_2_2", target_arch = "wasm32"))]
pub use crate::exports::do_migrate_with_info;
#[cfg(target_arch = "wasm32")]
pub use crate::exports::{
do_execute, do_ibc_destination_callback, do_ibc_source_callback, do_instantiate, do_migrate,
do_query, do_reply, do_sudo,
};
#[cfg(all(feature = "ibc2", target_arch = "wasm32"))]
pub use crate::exports::{do_ibc2_packet_receive, do_ibc2_packet_timeout};
#[cfg(all(feature = "stargate", target_arch = "wasm32"))]
pub use crate::exports::{
do_ibc_channel_close, do_ibc_channel_connect, do_ibc_channel_open, do_ibc_packet_ack,
Expand Down
21 changes: 20 additions & 1 deletion packages/std/src/testing/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::ibc::{
IbcTimeoutBlock,
};
#[cfg(feature = "ibc2")]
use crate::ibc2::{Ibc2PacketReceiveMsg, Ibc2Payload};
use crate::ibc2::{Ibc2PacketReceiveMsg, Ibc2PacketTimeoutMsg, Ibc2Payload};
#[cfg(feature = "cosmwasm_1_1")]
use crate::query::SupplyResponse;
use crate::query::{
Expand Down Expand Up @@ -532,6 +532,25 @@ pub fn mock_ibc2_packet_recv(data: &impl Serialize) -> StdResult<Ibc2PacketRecei
))
}

/// Creates a Ibc2PacketTimeoutMsg for testing ibc2_packet_timeout.
#[cfg(feature = "ibc2")]
pub fn mock_ibc2_packet_timeout(data: &impl Serialize) -> StdResult<Ibc2PacketTimeoutMsg> {
let payload = Ibc2Payload {
source_port: "wasm2srcport".to_string(),
destination_port: "wasm2destport".to_string(),
version: "v2".to_string(),
encoding: "json".to_string(),
value: to_json_binary(data)?,
};
Ok(Ibc2PacketTimeoutMsg::new(
payload,
"source_client".to_string(),
"destination_client".to_string(),
1,
Addr::unchecked("relayer"),
))
}

/// Creates a IbcPacket for testing ibc_packet_{ack,timeout}. You set a few key parameters that are
/// often parsed. If you want to set more, use this as a default and mutate other fields.
/// The difference from mock_ibc_packet_recv is if `my_channel_id` is src or dest.
Expand Down
4 changes: 2 additions & 2 deletions packages/std/src/testing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ pub use assertions::assert_approx_eq_impl;
pub use assertions::assert_hash_works_impl;

pub use message_info::message_info;
#[cfg(feature = "ibc2")]
pub use mock::mock_ibc2_packet_recv;
#[cfg(feature = "cosmwasm_1_3")]
pub use mock::DistributionQuerier;
#[cfg(feature = "staking")]
Expand All @@ -24,6 +22,8 @@ pub use mock::{
mock_wasmd_attr, BankQuerier, MockApi, MockQuerier, MockQuerierCustomHandlerResult,
MOCK_CONTRACT_ADDR,
};
#[cfg(feature = "ibc2")]
pub use mock::{mock_ibc2_packet_recv, mock_ibc2_packet_timeout};
#[cfg(feature = "stargate")]
pub use mock::{
mock_ibc_channel, mock_ibc_channel_close_confirm, mock_ibc_channel_close_init,
Expand Down
6 changes: 4 additions & 2 deletions packages/vm/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,9 @@ where
has_ibc_entry_points: REQUIRED_IBC_EXPORTS
.iter()
.all(|required| exports.contains(required.as_ref())),
has_ibc2_entry_points: exports.contains(REQUIRED_IBC2_EXPORT.as_ref()),
has_ibc2_entry_points: REQUIRED_IBC2_EXPORT
.iter()
.all(|required| exports.contains(required.as_ref())),
entrypoints,
required_capabilities: required_capabilities_from_module(&module)
.into_iter()
Expand Down Expand Up @@ -1530,7 +1532,7 @@ mod tests {
let checksum5 = cache.store_code(IBC2, true, true).unwrap();
let report5 = cache.analyze(&checksum5).unwrap();
let mut ibc2_contract_entrypoints = BTreeSet::from([E::Instantiate, E::Query]);
ibc2_contract_entrypoints.extend([REQUIRED_IBC2_EXPORT]);
ibc2_contract_entrypoints.extend(REQUIRED_IBC2_EXPORT);
assert_eq!(
report5,
AnalysisReport {
Expand Down
Loading
Loading