Skip to content

Commit cb4fc7f

Browse files
committed
feat: Add IBCv2 timeout entrypoint
1 parent a5deb27 commit cb4fc7f

File tree

14 files changed

+251
-21
lines changed

14 files changed

+251
-21
lines changed

contracts/ibc2/schema/ibc2.json

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"type": "object",
3939
"required": [
4040
"ibc2_packet_receive_counter",
41+
"ibc2_packet_timeout_counter",
4142
"last_packet_seq",
4243
"last_source_client"
4344
],
@@ -47,6 +48,11 @@
4748
"format": "uint32",
4849
"minimum": 0.0
4950
},
51+
"ibc2_packet_timeout_counter": {
52+
"type": "integer",
53+
"format": "uint32",
54+
"minimum": 0.0
55+
},
5056
"last_packet_seq": {
5157
"type": "integer",
5258
"format": "uint64",

contracts/ibc2/schema/raw/response_to_query_state.json

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"type": "object",
55
"required": [
66
"ibc2_packet_receive_counter",
7+
"ibc2_packet_timeout_counter",
78
"last_packet_seq",
89
"last_source_client"
910
],
@@ -13,6 +14,11 @@
1314
"format": "uint32",
1415
"minimum": 0.0
1516
},
17+
"ibc2_packet_timeout_counter": {
18+
"type": "integer",
19+
"format": "uint32",
20+
"minimum": 0.0
21+
},
1622
"last_packet_seq": {
1723
"type": "integer",
1824
"format": "uint64",

contracts/ibc2/src/contract.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use cosmwasm_std::{
22
entry_point, from_json, to_json_vec, Binary, Deps, DepsMut, Empty, Env, Ibc2Msg,
3-
Ibc2PacketReceiveMsg, Ibc2Payload, IbcAcknowledgement, IbcReceiveResponse, MessageInfo,
4-
QueryResponse, Response, StdAck, StdError, StdResult,
3+
Ibc2PacketReceiveMsg, Ibc2PacketTimeoutMsg, Ibc2Payload, IbcAcknowledgement, IbcBasicResponse,
4+
IbcReceiveResponse, MessageInfo, QueryResponse, Response, StdAck, StdError, StdResult,
55
};
66

77
use crate::msg::{IbcPayload, QueryMsg};
@@ -18,6 +18,7 @@ pub fn instantiate(
1818
STATE_KEY,
1919
&to_json_vec(&State {
2020
ibc2_packet_receive_counter: 0,
21+
ibc2_packet_timeout_counter: 0,
2122
last_source_client: "".to_owned(),
2223
last_packet_seq: 0,
2324
})?,
@@ -60,6 +61,7 @@ pub fn ibc2_packet_receive(
6061
ibc2_packet_receive_counter: state.ibc2_packet_receive_counter + 1,
6162
last_source_client: msg.source_client.clone(),
6263
last_packet_seq: msg.packet_sequence,
64+
..state
6365
})?,
6466
);
6567
let new_payload = Ibc2Payload::new(
@@ -72,7 +74,7 @@ pub fn ibc2_packet_receive(
7274
let new_msg = Ibc2Msg::SendPacket {
7375
source_client: msg.source_client,
7476
payloads: vec![new_payload],
75-
timeout: env.block.time.plus_seconds(60_u64),
77+
timeout: env.block.time.plus_seconds(36_u64),
7678
};
7779

7880
let resp = if json_payload.response_without_ack {
@@ -95,3 +97,26 @@ pub fn ibc2_packet_receive(
9597
Ok(resp)
9698
}
9799
}
100+
101+
#[entry_point]
102+
pub fn ibc2_packet_timeout(
103+
deps: DepsMut,
104+
_env: Env,
105+
_msg: Ibc2PacketTimeoutMsg,
106+
) -> StdResult<IbcBasicResponse> {
107+
let data = deps
108+
.storage
109+
.get(STATE_KEY)
110+
.ok_or_else(|| StdError::generic_err("State not found."))?;
111+
let state: State = from_json(data)?;
112+
113+
deps.storage.set(
114+
STATE_KEY,
115+
&to_json_vec(&State {
116+
ibc2_packet_timeout_counter: state.ibc2_packet_timeout_counter + 1,
117+
..state
118+
})?,
119+
);
120+
121+
Ok(IbcBasicResponse::default())
122+
}

contracts/ibc2/src/state.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
44
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
55
pub struct State {
66
pub ibc2_packet_receive_counter: u32,
7+
pub ibc2_packet_timeout_counter: u32,
78
pub last_source_client: String,
89
pub last_packet_seq: u64,
910
}

packages/std/src/exports.rs

+54-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use crate::ibc::{
2323
};
2424
use crate::ibc::{IbcChannelOpenMsg, IbcChannelOpenResponse};
2525
#[cfg(feature = "ibc2")]
26-
use crate::ibc2::Ibc2PacketReceiveMsg;
26+
use crate::ibc2::{Ibc2PacketReceiveMsg, Ibc2PacketTimeoutMsg};
2727
use crate::imports::{ExternalApi, ExternalQuerier, ExternalStorage};
2828
use crate::memory::{Owned, Region};
2929
use crate::panic::install_panic_handler;
@@ -555,6 +555,36 @@ where
555555
Region::from_vec(v).to_heap_ptr() as u32
556556
}
557557

558+
/// do_ibc_packet_timeout is designed for use with #[entry_point] to make a "C" extern
559+
///
560+
/// contract_fn is called when a packet that this contract previously sent has provably
561+
/// timedout and will never be relayed to the calling chain. This generally behaves
562+
/// like ick_ack_fn upon an acknowledgement containing an error.
563+
///
564+
/// - `Q`: custom query type (see QueryRequest)
565+
/// - `C`: custom response message type (see CosmosMsg)
566+
/// - `E`: error type for responses
567+
#[cfg(feature = "ibc2")]
568+
pub fn do_ibc2_packet_timeout<Q, C, E>(
569+
contract_fn: &dyn Fn(DepsMut<Q>, Env, Ibc2PacketTimeoutMsg) -> Result<IbcBasicResponse<C>, E>,
570+
env_ptr: u32,
571+
msg_ptr: u32,
572+
) -> u32
573+
where
574+
Q: CustomQuery,
575+
C: CustomMsg,
576+
E: ToString,
577+
{
578+
install_panic_handler();
579+
let res = _do_ibc2_packet_timeout(
580+
contract_fn,
581+
env_ptr as *mut Region<Owned>,
582+
msg_ptr as *mut Region<Owned>,
583+
);
584+
let v = to_json_vec(&res).unwrap();
585+
Region::from_vec(v).to_heap_ptr() as u32
586+
}
587+
558588
fn _do_instantiate<Q, M, C, E>(
559589
instantiate_fn: &dyn Fn(DepsMut<Q>, Env, MessageInfo, M) -> Result<Response<C>, E>,
560590
env_ptr: *mut Region<Owned>,
@@ -945,3 +975,26 @@ where
945975
let mut deps = make_dependencies();
946976
contract_fn(deps.as_mut(), env, msg).into()
947977
}
978+
979+
#[cfg(feature = "ibc2")]
980+
fn _do_ibc2_packet_timeout<Q, C, E>(
981+
contract_fn: &dyn Fn(DepsMut<Q>, Env, Ibc2PacketTimeoutMsg) -> Result<IbcBasicResponse<C>, E>,
982+
env_ptr: *mut Region<Owned>,
983+
msg_ptr: *mut Region<Owned>,
984+
) -> ContractResult<IbcBasicResponse<C>>
985+
where
986+
Q: CustomQuery,
987+
C: CustomMsg,
988+
E: ToString,
989+
{
990+
let env: Vec<u8> =
991+
unsafe { Region::from_heap_ptr(ptr::NonNull::new(env_ptr).unwrap()).into_vec() };
992+
let msg: Vec<u8> =
993+
unsafe { Region::from_heap_ptr(ptr::NonNull::new(msg_ptr).unwrap()).into_vec() };
994+
995+
let env: Env = try_into_contract_result!(from_json(env));
996+
let msg: Ibc2PacketTimeoutMsg = try_into_contract_result!(from_json(msg));
997+
998+
let mut deps = make_dependencies();
999+
contract_fn(deps.as_mut(), env, msg).into()
1000+
}

packages/std/src/ibc2.rs

+46-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,14 @@ pub enum Ibc2Msg {
6363
},
6464
}
6565

66-
/// The message that is passed into `ibc2_packet_receive`
66+
/// IBC2PacketReceiveMsg represents a message received via the IBC2 protocol.
67+
/// The message that is passed into `ibc2_packet_receive`.
68+
/// It contains the payload data along with metadata about the source and relayer.
69+
/// Fields:
70+
/// - `payload`: The actual data being transmitted via IBC2.
71+
/// - `relayer`: The identifier of the entity that relayed the packet.
72+
/// - `source_client`: The identifier of the source IBC client.
73+
/// - `packet_sequence`: The unique sequence number of the received packet.
6774
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
6875
#[non_exhaustive]
6976
pub struct Ibc2PacketReceiveMsg {
@@ -89,6 +96,44 @@ impl Ibc2PacketReceiveMsg {
8996
}
9097
}
9198

99+
/// IBC2PacketTimeoutMsg represents a timeout event for a packet that was not
100+
/// successfully delivered within the expected timeframe in the IBC2 protocol.
101+
/// It includes details about the source and destination clients, and the sequence
102+
/// number of the timed-out packet.
103+
/// Fields:
104+
/// - `payload`: The data associated with the timed-out packet.
105+
/// - `source_client`: The identifier of the client that originally sent the packet.
106+
/// - `destination_client`: The identifier of the client that was the intended recipient.
107+
/// - `packet_sequence`: The sequence number of the timed-out packet.
108+
/// - `relayer`: The identifier of the relayer responsible for the packet.
109+
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
110+
#[non_exhaustive]
111+
pub struct Ibc2PacketTimeoutMsg {
112+
pub payload: Ibc2Payload,
113+
pub source_client: String,
114+
pub destination_client: String,
115+
pub packet_sequence: u64,
116+
pub relayer: Addr,
117+
}
118+
119+
impl Ibc2PacketTimeoutMsg {
120+
pub fn new(
121+
payload: Ibc2Payload,
122+
source_client: String,
123+
destination_client: String,
124+
packet_sequence: u64,
125+
relayer: Addr,
126+
) -> Self {
127+
Self {
128+
payload,
129+
source_client,
130+
destination_client,
131+
packet_sequence,
132+
relayer,
133+
}
134+
}
135+
}
136+
92137
#[cfg(test)]
93138
mod tests {
94139
use serde_json::to_string;

packages/std/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub use crate::ibc::{
7777
IbcSourceCallbackMsg, IbcSrcCallback, IbcTimeout, IbcTimeoutBlock, IbcTimeoutCallbackMsg,
7878
TransferMsgBuilder,
7979
};
80-
pub use crate::ibc2::{Ibc2Msg, Ibc2PacketReceiveMsg, Ibc2Payload};
80+
pub use crate::ibc2::{Ibc2Msg, Ibc2PacketReceiveMsg, Ibc2PacketTimeoutMsg, Ibc2Payload};
8181
#[cfg(feature = "iterator")]
8282
pub use crate::iterator::{Order, Record};
8383
pub use crate::math::{
@@ -124,15 +124,15 @@ mod imports;
124124
#[cfg(target_arch = "wasm32")]
125125
mod memory; // Used by exports and imports only. This assumes pointers are 32 bit long, which makes it untestable on dev machines.
126126

127-
#[cfg(all(feature = "ibc2", target_arch = "wasm32"))]
128-
pub use crate::exports::do_ibc2_packet_receive;
129127
#[cfg(all(feature = "cosmwasm_2_2", target_arch = "wasm32"))]
130128
pub use crate::exports::do_migrate_with_info;
131129
#[cfg(target_arch = "wasm32")]
132130
pub use crate::exports::{
133131
do_execute, do_ibc_destination_callback, do_ibc_source_callback, do_instantiate, do_migrate,
134132
do_query, do_reply, do_sudo,
135133
};
134+
#[cfg(all(feature = "ibc2", target_arch = "wasm32"))]
135+
pub use crate::exports::{do_ibc2_packet_receive, do_ibc2_packet_timeout};
136136
#[cfg(all(feature = "stargate", target_arch = "wasm32"))]
137137
pub use crate::exports::{
138138
do_ibc_channel_close, do_ibc_channel_connect, do_ibc_channel_open, do_ibc_packet_ack,

packages/std/src/testing/mock.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::ibc::{
2424
IbcTimeoutBlock,
2525
};
2626
#[cfg(feature = "ibc2")]
27-
use crate::ibc2::{Ibc2PacketReceiveMsg, Ibc2Payload};
27+
use crate::ibc2::{Ibc2PacketReceiveMsg, Ibc2PacketTimeoutMsg, Ibc2Payload};
2828
#[cfg(feature = "cosmwasm_1_1")]
2929
use crate::query::SupplyResponse;
3030
use crate::query::{
@@ -532,6 +532,27 @@ pub fn mock_ibc2_packet_recv(data: &impl Serialize) -> StdResult<Ibc2PacketRecei
532532
))
533533
}
534534

535+
/// Creates a Ibc2PacketTimeoutMsg for testing ibc2_packet_timeout. You set a few key parameters that are
536+
/// often parsed. If you want to set more, use this as a default and mutate other fields.
537+
/// The difference from mock_ibc_packet_recv is if `my_channel_id` is src or dest.
538+
#[cfg(feature = "ibc2")]
539+
pub fn mock_ibc2_packet_timeout(data: &impl Serialize) -> StdResult<Ibc2PacketTimeoutMsg> {
540+
let payload = Ibc2Payload {
541+
source_port: "wasm2srcport".to_string(),
542+
destination_port: "wasm2destport".to_string(),
543+
version: "v2".to_string(),
544+
encoding: "json".to_string(),
545+
value: to_json_binary(data)?,
546+
};
547+
Ok(Ibc2PacketTimeoutMsg::new(
548+
payload,
549+
"source_client".to_string(),
550+
"destination_client".to_string(),
551+
1,
552+
Addr::unchecked("relayer"),
553+
))
554+
}
555+
535556
/// Creates a IbcPacket for testing ibc_packet_{ack,timeout}. You set a few key parameters that are
536557
/// often parsed. If you want to set more, use this as a default and mutate other fields.
537558
/// The difference from mock_ibc_packet_recv is if `my_channel_id` is src or dest.

packages/std/src/testing/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ pub use assertions::assert_approx_eq_impl;
1313
pub use assertions::assert_hash_works_impl;
1414

1515
pub use message_info::message_info;
16-
#[cfg(feature = "ibc2")]
17-
pub use mock::mock_ibc2_packet_recv;
1816
#[cfg(feature = "cosmwasm_1_3")]
1917
pub use mock::DistributionQuerier;
2018
#[cfg(feature = "staking")]
@@ -24,6 +22,8 @@ pub use mock::{
2422
mock_wasmd_attr, BankQuerier, MockApi, MockQuerier, MockQuerierCustomHandlerResult,
2523
MOCK_CONTRACT_ADDR,
2624
};
25+
#[cfg(feature = "ibc2")]
26+
pub use mock::{mock_ibc2_packet_recv, mock_ibc2_packet_timeout};
2727
#[cfg(feature = "stargate")]
2828
pub use mock::{
2929
mock_ibc_channel, mock_ibc_channel_close_confirm, mock_ibc_channel_close_init,

packages/vm/src/cache.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,9 @@ where
340340
has_ibc_entry_points: REQUIRED_IBC_EXPORTS
341341
.iter()
342342
.all(|required| exports.contains(required.as_ref())),
343-
has_ibc2_entry_points: exports.contains(REQUIRED_IBC2_EXPORT.as_ref()),
343+
has_ibc2_entry_points: REQUIRED_IBC2_EXPORT
344+
.iter()
345+
.all(|required| exports.contains(required.as_ref())),
344346
entrypoints,
345347
required_capabilities: required_capabilities_from_module(&module)
346348
.into_iter()
@@ -1530,7 +1532,7 @@ mod tests {
15301532
let checksum5 = cache.store_code(IBC2, true, true).unwrap();
15311533
let report5 = cache.analyze(&checksum5).unwrap();
15321534
let mut ibc2_contract_entrypoints = BTreeSet::from([E::Instantiate, E::Query]);
1533-
ibc2_contract_entrypoints.extend([REQUIRED_IBC2_EXPORT]);
1535+
ibc2_contract_entrypoints.extend(REQUIRED_IBC2_EXPORT);
15341536
assert_eq!(
15351537
report5,
15361538
AnalysisReport {

0 commit comments

Comments
 (0)