1#![cfg_attr(not(test), no_std)]
4#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
5#![cfg_attr(
6 not(test),
7 deny(
8 missing_docs,
9 rust_2018_idioms,
10 clippy::string_to_string,
11 clippy::std_instead_of_core,
12 clippy::string_add,
13 clippy::str_to_string,
14 clippy::infinite_loop,
15 clippy::unwrap_used,
16 clippy::expect_used,
17 clippy::panic,
18 clippy::cfg_not_test,
19 clippy::as_conversions,
20 clippy::alloc_instead_of_core,
21 clippy::float_arithmetic,
22 clippy::empty_docs,
23 clippy::empty_line_after_doc_comments,
24 clippy::empty_line_after_outer_attr,
25 clippy::suspicious_doc_comments,
26 clippy::redundant_locals,
27 clippy::redundant_comparisons,
28 clippy::out_of_bounds_indexing,
29 clippy::empty_loop,
30 clippy::cast_sign_loss,
31 clippy::cast_possible_truncation,
32 clippy::cast_possible_wrap,
33 clippy::cast_lossless,
34 clippy::arithmetic_side_effects,
35 clippy::dbg_macro,
36 clippy::print_stdout,
37 clippy::print_stderr,
38 clippy::shadow_unrelated,
39 clippy::useless_attribute,
40 clippy::zero_repeat_side_effects,
41 clippy::builtin_type_shadow,
42 clippy::unreachable
43 )
44)]
45
46extern crate alloc;
47
48mod msg;
49#[cfg(test)]
50pub(crate) mod tests;
51
52use alloc::borrow::ToOwned;
53use alloc::format;
54use alloc::string::{String, ToString};
55use alloc::vec;
56use alloc::vec::Vec;
57use core::fmt;
58
59use ibc_app_transfer_types::packet::PacketData;
60use ibc_app_transfer_types::{is_receiver_chain_source, Coin, PrefixedDenom, TracePrefix};
61use ibc_core_channel_types::acknowledgement::{
62 Acknowledgement, AcknowledgementStatus, StatusValue as AckStatusValue,
63};
64use ibc_core_channel_types::channel::{Counterparty, Order};
65use ibc_core_channel_types::error::ChannelError;
66use ibc_core_channel_types::packet::Packet;
67use ibc_core_channel_types::Version;
68use ibc_core_host_types::identifiers::{ChannelId, ConnectionId, PortId};
69use ibc_core_router::module::Module as IbcCoreModule;
70use ibc_core_router_types::event::{ModuleEvent, ModuleEventAttribute};
71use ibc_core_router_types::module::ModuleExtras;
72use ibc_middleware_module::MiddlewareModule;
73use ibc_middleware_module_macros::from_middleware;
74use ibc_primitives::*;
75use serde::{Deserialize, Serialize};
76
77#[doc(inline)]
78pub use self::msg::PacketMetadata;
79
80const MODULE: &str = "overflow-receive-middleware";
82
83#[derive(Debug)]
84enum MiddlewareError {
85 Message(String),
87 ForwardToNextMiddleware,
89}
90
91pub trait OverflowRecvContext {
93 type PacketMetadata: msg::PacketMetadata + Serialize + for<'de> Deserialize<'de> + Sized;
96
97 type Error: fmt::Display;
99
100 fn mint_coins_execute(
102 &mut self,
103 receiver: &<Self::PacketMetadata as PacketMetadata>::AccountId,
104 coin: &Coin<PrefixedDenom>,
105 ) -> Result<(), Self::Error>;
106
107 fn unescrow_coins_execute(
109 &mut self,
110 receiver: &<Self::PacketMetadata as PacketMetadata>::AccountId,
111 port: &PortId,
112 channel: &ChannelId,
113 coin: &Coin<PrefixedDenom>,
114 ) -> Result<(), Self::Error>;
115}
116
117#[derive(Debug)]
121pub struct OverflowReceiveMiddleware<M> {
122 next: M,
123}
124
125#[cfg_attr(coverage_nightly, coverage(off))]
126impl<M> OverflowReceiveMiddleware<M> {
127 pub fn next(&self) -> &M {
129 &self.next
130 }
131
132 pub fn next_mut(&mut self) -> &mut M {
134 &mut self.next
135 }
136
137 pub const fn wrap(next: M) -> Self {
139 Self { next }
140 }
141}
142
143from_middleware! {
144 #[cfg_attr(coverage_nightly, coverage(off))]
145 impl<M> IbcCoreModule for OverflowReceiveMiddleware<M>
146 where
147 M: IbcCoreModule + OverflowRecvContext,
148}
149
150impl<M> OverflowReceiveMiddleware<M>
151where
152 M: IbcCoreModule + OverflowRecvContext,
153{
154 fn on_recv_packet_execute_inner(
155 &mut self,
156 extras: &mut ModuleExtras,
157 packet: &Packet,
158 relayer: &Signer,
159 ) -> Result<Option<Acknowledgement>, MiddlewareError> {
160 let (transfer_pkt, orm_metadata) =
161 decode_overflow_receive_msg::<M::PacketMetadata>(packet)?;
162
163 let (override_amount, remainder_amount) = match transfer_pkt
164 .token
165 .amount
166 .checked_sub((*orm_metadata.target_amount()).into())
167 {
168 Some(amt) if *amt != [0u64, 0, 0, 0] => ((*orm_metadata.target_amount()).into(), amt),
169 Some(_ ) => return Err(MiddlewareError::ForwardToNextMiddleware),
170 None => {
171 return Err(MiddlewareError::Message(format!(
172 "Target amount ({}) is greater than the received amount ({})",
173 orm_metadata.target_amount(),
174 transfer_pkt.token.amount
175 )))
176 }
177 };
178
179 let mut attributes = vec![];
180
181 if is_receiver_chain_source(
182 packet.port_id_on_a.clone(),
183 packet.chan_id_on_a.clone(),
184 &transfer_pkt.token.denom,
185 ) {
186 let prefix = TracePrefix::new(packet.port_id_on_a.clone(), packet.chan_id_on_a.clone());
187 let coin = {
188 let mut c = Coin {
189 denom: transfer_pkt.token.denom.clone(),
190 amount: remainder_amount,
191 };
192 c.denom.remove_trace_prefix(&prefix);
193 c
194 };
195
196 self.next
197 .unescrow_coins_execute(
198 orm_metadata.overflow_receiver(),
199 &packet.port_id_on_b,
200 &packet.chan_id_on_b,
201 &coin,
202 )
203 .map_err(|err| {
204 MiddlewareError::Message(format!(
205 "Unescrow to {} failed: {err}",
206 orm_metadata.overflow_receiver()
207 ))
208 })?;
209
210 push_event_attr(&mut attributes, "operation", "unescrow");
211 push_event_attr(&mut attributes, "denom", coin.denom.to_string());
212 } else {
213 let prefix = TracePrefix::new(packet.port_id_on_b.clone(), packet.chan_id_on_b.clone());
214 let coin = {
215 let mut c = Coin {
216 denom: transfer_pkt.token.denom.clone(),
217 amount: remainder_amount,
218 };
219 c.denom.add_trace_prefix(prefix);
220 c
221 };
222
223 self.next
224 .mint_coins_execute(orm_metadata.overflow_receiver(), &coin)
225 .map_err(|err| {
226 MiddlewareError::Message(format!(
227 "Mint to {} failed: {err}",
228 orm_metadata.overflow_receiver()
229 ))
230 })?;
231
232 push_event_attr(&mut attributes, "operation", "mint");
233 push_event_attr(&mut attributes, "denom", coin.denom.to_string());
234 }
235
236 push_event_attr(&mut attributes, "amount", remainder_amount.to_string());
237 push_event_attr(
238 &mut attributes,
239 "overflow-receiver",
240 orm_metadata.overflow_receiver().to_string(),
241 );
242 push_event_attr(
243 &mut attributes,
244 "info",
245 "Successfully redirected funds with overflow receiver middleware",
246 );
247 emit_event_with_attrs(extras, attributes);
248
249 let override_packet = {
250 let ics20_packet_data = PacketData {
251 token: Coin {
252 denom: transfer_pkt.token.denom.clone(),
253 amount: override_amount,
254 },
255 memo: extract_next_memo_from_orm_packet::<M::PacketMetadata>(&transfer_pkt).into(),
256 ..transfer_pkt
257 };
258
259 let encoded_packet_data = serde_json::to_vec(&ics20_packet_data).map_err(|err| {
260 MiddlewareError::Message(format!("Failed to encode ICS-20 packet: {err}"))
261 })?;
262
263 Packet {
264 data: encoded_packet_data,
265 ..packet.clone()
266 }
267 };
268 let (next_extras, maybe_ack) = self.next.on_recv_packet_execute(&override_packet, relayer);
269 join_module_extras(extras, next_extras);
270
271 Ok(maybe_ack)
272 }
273}
274
275#[cfg_attr(coverage_nightly, coverage(off))]
276impl<M> MiddlewareModule for OverflowReceiveMiddleware<M>
277where
278 M: IbcCoreModule + OverflowRecvContext,
279{
280 type NextMiddleware = M;
281
282 #[inline]
283 fn next_middleware(&self) -> &M {
284 self.next()
285 }
286
287 #[inline]
288 fn next_middleware_mut(&mut self) -> &mut M {
289 self.next_mut()
290 }
291
292 fn middleware_on_recv_packet_execute(
293 &mut self,
294 packet: &Packet,
295 relayer: &Signer,
296 ) -> (ModuleExtras, Option<Acknowledgement>) {
297 let mut extras = ModuleExtras::empty();
298
299 match self.on_recv_packet_execute_inner(&mut extras, packet, relayer) {
300 Ok(maybe_ack) => (extras, maybe_ack),
301 Err(MiddlewareError::ForwardToNextMiddleware) => {
302 self.next.on_recv_packet_execute(packet, relayer)
303 }
304 Err(MiddlewareError::Message(err)) => (extras, Some(new_error_ack(err).into())),
305 }
306 }
307}
308
309#[cfg_attr(coverage_nightly, coverage(off))]
310#[inline]
311fn new_error_ack(message: impl fmt::Display) -> AcknowledgementStatus {
312 AcknowledgementStatus::error(
313 #[allow(clippy::expect_used)]
316 AckStatusValue::new(format!("{MODULE} error: {message}"))
317 .expect("Acknowledgement error must not be empty"),
318 )
319}
320
321#[inline]
322fn event_attr<K, V>(key: K, value: V) -> ModuleEventAttribute
323where
324 K: Into<String>,
325 V: Into<String>,
326{
327 ModuleEventAttribute {
328 key: key.into(),
329 value: value.into(),
330 }
331}
332
333#[inline]
334fn push_event_attr<K, V>(attributes: &mut Vec<ModuleEventAttribute>, key: K, value: V)
335where
336 K: Into<String>,
337 V: Into<String>,
338{
339 attributes.push(event_attr(key, value));
340}
341
342#[inline]
343fn emit_event_with_attrs(extras: &mut ModuleExtras, attributes: Vec<ModuleEventAttribute>) {
344 extras.events.push(ModuleEvent {
345 kind: MODULE.to_owned(),
346 attributes,
347 });
348}
349
350fn decode_ics20_msg(packet: &Packet) -> Result<PacketData, MiddlewareError> {
351 serde_json::from_slice(&packet.data).map_err(|_| {
352 MiddlewareError::ForwardToNextMiddleware
355 })
356}
357
358fn decode_overflow_receive_msg<Msg>(packet: &Packet) -> Result<(PacketData, Msg), MiddlewareError>
359where
360 Msg: msg::PacketMetadata + for<'de> Deserialize<'de>,
361{
362 let transfer_pkt = decode_ics20_msg(packet)?;
363
364 let json_obj_memo: serde_json::Map<String, serde_json::Value> =
365 serde_json::from_str(transfer_pkt.memo.as_ref()).map_err(|_| {
366 MiddlewareError::ForwardToNextMiddleware
369 })?;
370
371 if !Msg::is_overflow_receive_msg(&json_obj_memo) {
372 return Err(MiddlewareError::ForwardToNextMiddleware);
375 }
376
377 serde_json::from_value(json_obj_memo.into()).map_or_else(
378 |err| Err(MiddlewareError::Message(err.to_string())),
379 |msg| Ok((transfer_pkt, msg)),
380 )
381}
382
383fn join_module_extras(first: &mut ModuleExtras, mut second: ModuleExtras) {
384 first.events.append(&mut second.events);
385 first.log.append(&mut second.log);
386}
387
388#[inline]
390fn extract_next_memo_from_orm_packet<Msg>(src_packet_data: &PacketData) -> String
391where
392 Msg: PacketMetadata,
393{
394 #[allow(clippy::unwrap_used, clippy::unreachable)]
395 let serde_json::Value::Object(memo_obj) =
396 serde_json::from_str(src_packet_data.memo.as_ref()).unwrap()
397 else {
398 unreachable!()
399 };
400
401 #[allow(clippy::unwrap_used)]
402 serde_json::to_string(&Msg::strip_middleware_msg(memo_obj)).unwrap()
403}