Skip to content

Commit e845af6

Browse files
authored
Implement ICS-26 router for RecvPacket in {Validation,Execution}Context (#377)
* packet validation scaffoldinig * recv_packet validation * remove todo * recv_packet_validate() * execute scaffolding * Module::on_recv_packet_execute * token transfer on_recv_packet (with a FIXME) * recv_packet_execute * refactor expression for readability * add comment * ack cannot be empty * token transfer ack * clippy * fix token transfer recv_packet * typo
1 parent 6bf011a commit e845af6

File tree

14 files changed

+632
-47
lines changed

14 files changed

+632
-47
lines changed

crates/ibc/src/applications/transfer/acknowledgement.rs

+14-5
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,21 @@ impl Display for TokenTransferAcknowledgement {
5656

5757
impl From<TokenTransferAcknowledgement> for Vec<u8> {
5858
fn from(ack: TokenTransferAcknowledgement) -> Self {
59+
// WARNING: Make sure all branches always return a non-empty vector.
60+
// Otherwise, the conversion to `Acknowledgement` will panic.
5961
match ack {
6062
TokenTransferAcknowledgement::Success(_) => r#"{"result":"AQ=="}"#.as_bytes().into(),
61-
// TokenTransferAcknowledgement::Error(s) => s.as_bytes(),
6263
TokenTransferAcknowledgement::Error(s) => alloc::format!(r#"{{"error":"{s}"}}"#).into(),
6364
}
6465
}
6566
}
6667

6768
impl From<TokenTransferAcknowledgement> for Acknowledgement {
6869
fn from(ack: TokenTransferAcknowledgement) -> Self {
69-
ack.into()
70+
let v: Vec<u8> = ack.into();
71+
72+
v.try_into()
73+
.expect("token transfer internal error: ack is never supposed to be empty")
7074
}
7175
}
7276

@@ -98,18 +102,23 @@ mod test {
98102
let ack_success: Vec<u8> = TokenTransferAcknowledgement::success().into();
99103

100104
// Check that it's the same output as ibc-go
105+
// Note: this also implicitly checks that the ack bytes are non-empty,
106+
// which would make the conversion to `Acknowledgement` panic
101107
assert_eq!(ack_success, r#"{"result":"AQ=="}"#.as_bytes());
102108
}
103109

104110
#[test]
105111
fn test_ack_error_to_vec() {
106-
let ack_error = TokenTransferAcknowledgement::Error(
112+
let ack_error: Vec<u8> = TokenTransferAcknowledgement::Error(
107113
"cannot unmarshal ICS-20 transfer packet data".to_string(),
108-
);
114+
)
115+
.into();
109116

110117
// Check that it's the same output as ibc-go
118+
// Note: this also implicitly checks that the ack bytes are non-empty,
119+
// which would make the conversion to `Acknowledgement` panic
111120
assert_eq!(
112-
Vec::<u8>::from(ack_error),
121+
ack_error,
113122
r#"{"error":"cannot unmarshal ICS-20 transfer packet data"}"#.as_bytes()
114123
);
115124
}

crates/ibc/src/applications/transfer/context.rs

+32
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,8 @@ pub fn on_timeout_packet(
339339
pub use val_exec_ctx::*;
340340
#[cfg(feature = "val_exec_ctx")]
341341
mod val_exec_ctx {
342+
use crate::applications::transfer::relay::on_recv_packet::process_recv_packet_execute;
343+
342344
pub use super::*;
343345

344346
#[allow(clippy::too_many_arguments)]
@@ -498,6 +500,36 @@ mod val_exec_ctx {
498500
) -> Result<ModuleExtras, TokenTransferError> {
499501
Ok(ModuleExtras::empty())
500502
}
503+
504+
pub fn on_recv_packet_execute(
505+
ctx: &mut impl TokenTransferContext,
506+
packet: &Packet,
507+
) -> (ModuleExtras, Acknowledgement) {
508+
let data = match serde_json::from_slice::<PacketData>(&packet.data) {
509+
Ok(data) => data,
510+
Err(_) => {
511+
let ack = TokenTransferAcknowledgement::Error(
512+
TokenTransferError::PacketDataDeserialization.to_string(),
513+
);
514+
return (ModuleExtras::empty(), ack.into());
515+
}
516+
};
517+
518+
let (mut extras, ack) = match process_recv_packet_execute(ctx, packet, data.clone()) {
519+
Ok(extras) => (extras, TokenTransferAcknowledgement::success()),
520+
Err((extras, error)) => (extras, TokenTransferAcknowledgement::from_error(error)),
521+
};
522+
523+
let recv_event = RecvEvent {
524+
receiver: data.receiver,
525+
denom: data.token.denom,
526+
amount: data.token.amount,
527+
success: ack.is_successful(),
528+
};
529+
extras.events.push(recv_event.into());
530+
531+
(extras, ack.into())
532+
}
501533
}
502534

503535
#[cfg(test)]

crates/ibc/src/applications/transfer/relay/on_recv_packet.rs

+75
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,78 @@ pub fn process_recv_packet<Ctx: 'static + TokenTransferContext>(
6060

6161
Ok(())
6262
}
63+
64+
#[cfg(feature = "val_exec_ctx")]
65+
pub use val_exec_ctx::*;
66+
#[cfg(feature = "val_exec_ctx")]
67+
mod val_exec_ctx {
68+
pub use super::*;
69+
use crate::core::ics04_channel::handler::ModuleExtras;
70+
71+
pub fn process_recv_packet_execute<Ctx: TokenTransferContext>(
72+
ctx: &mut Ctx,
73+
packet: &Packet,
74+
data: PacketData,
75+
) -> Result<ModuleExtras, (ModuleExtras, TokenTransferError)> {
76+
if !ctx.is_receive_enabled() {
77+
return Err((ModuleExtras::empty(), TokenTransferError::ReceiveDisabled));
78+
}
79+
80+
let receiver_account = data.receiver.clone().try_into().map_err(|_| {
81+
(
82+
ModuleExtras::empty(),
83+
TokenTransferError::ParseAccountFailure,
84+
)
85+
})?;
86+
87+
let extras = if is_receiver_chain_source(
88+
packet.port_on_a.clone(),
89+
packet.chan_on_a.clone(),
90+
&data.token.denom,
91+
) {
92+
// sender chain is not the source, unescrow tokens
93+
let prefix = TracePrefix::new(packet.port_on_a.clone(), packet.chan_on_a.clone());
94+
let coin = {
95+
let mut c = data.token;
96+
c.denom.remove_trace_prefix(&prefix);
97+
c
98+
};
99+
100+
let escrow_address = ctx
101+
.get_channel_escrow_address(&packet.port_on_b, &packet.chan_on_b)
102+
.map_err(|token_err| (ModuleExtras::empty(), token_err))?;
103+
104+
ctx.send_coins(&escrow_address, &receiver_account, &coin)
105+
.map_err(|token_err| (ModuleExtras::empty(), token_err))?;
106+
107+
ModuleExtras::empty()
108+
} else {
109+
// sender chain is the source, mint vouchers
110+
let prefix = TracePrefix::new(packet.port_on_b.clone(), packet.chan_on_b.clone());
111+
let coin = {
112+
let mut c = data.token;
113+
c.denom.add_trace_prefix(prefix);
114+
c
115+
};
116+
117+
let extras = {
118+
let denom_trace_event = DenomTraceEvent {
119+
trace_hash: ctx.denom_hash_string(&coin.denom),
120+
denom: coin.denom.clone(),
121+
};
122+
ModuleExtras {
123+
events: vec![denom_trace_event.into()],
124+
log: Vec::new(),
125+
}
126+
};
127+
let extras_closure = extras.clone();
128+
129+
ctx.mint_coins(&receiver_account, &coin)
130+
.map_err(|token_err| (extras_closure, token_err))?;
131+
132+
extras
133+
};
134+
135+
Ok(extras)
136+
}
137+
}

crates/ibc/src/clients/ics07_tendermint/client_state.rs

+74
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,39 @@ impl Ics2ClientState for ClientState {
966966
verify_membership(client_state, prefix, proof, root, path, value)
967967
}
968968

969+
#[cfg(feature = "val_exec_ctx")]
970+
fn new_verify_packet_data(
971+
&self,
972+
ctx: &dyn ValidationContext,
973+
height: Height,
974+
connection_end: &ConnectionEnd,
975+
proof: &CommitmentProofBytes,
976+
root: &CommitmentRoot,
977+
port_id: &PortId,
978+
channel_id: &ChannelId,
979+
sequence: Sequence,
980+
commitment: PacketCommitment,
981+
) -> Result<(), ClientError> {
982+
let client_state = downcast_tm_client_state(self)?;
983+
client_state.verify_height(height)?;
984+
new_verify_delay_passed(ctx, height, connection_end)?;
985+
986+
let commitment_path = CommitmentsPath {
987+
port_id: port_id.clone(),
988+
channel_id: channel_id.clone(),
989+
sequence,
990+
};
991+
992+
verify_membership(
993+
client_state,
994+
connection_end.counterparty().prefix(),
995+
proof,
996+
root,
997+
commitment_path,
998+
commitment.into_vec(),
999+
)
1000+
}
1001+
9691002
fn verify_packet_data(
9701003
&self,
9711004
ctx: &dyn ChannelReader,
@@ -1172,6 +1205,47 @@ fn verify_delay_passed(
11721205
.map_err(|e| e.into())
11731206
}
11741207

1208+
#[cfg(feature = "val_exec_ctx")]
1209+
fn new_verify_delay_passed(
1210+
ctx: &dyn ValidationContext,
1211+
height: Height,
1212+
connection_end: &ConnectionEnd,
1213+
) -> Result<(), ClientError> {
1214+
let current_timestamp = ctx.host_timestamp().map_err(|e| ClientError::Other {
1215+
description: e.to_string(),
1216+
})?;
1217+
let current_height = ctx.host_height().map_err(|e| ClientError::Other {
1218+
description: e.to_string(),
1219+
})?;
1220+
1221+
let client_id = connection_end.client_id();
1222+
let processed_time =
1223+
ctx.client_update_time(client_id, &height)
1224+
.map_err(|_| Error::ProcessedTimeNotFound {
1225+
client_id: client_id.clone(),
1226+
height,
1227+
})?;
1228+
let processed_height = ctx.client_update_height(client_id, &height).map_err(|_| {
1229+
Error::ProcessedHeightNotFound {
1230+
client_id: client_id.clone(),
1231+
height,
1232+
}
1233+
})?;
1234+
1235+
let delay_period_time = connection_end.delay_period();
1236+
let delay_period_height = ctx.block_delay(&delay_period_time);
1237+
1238+
ClientState::verify_delay_passed(
1239+
current_timestamp,
1240+
current_height,
1241+
processed_time,
1242+
processed_height,
1243+
delay_period_time,
1244+
delay_period_height,
1245+
)
1246+
.map_err(|e| e.into())
1247+
}
1248+
11751249
fn downcast_tm_client_state(cs: &dyn Ics2ClientState) -> Result<&ClientState, ClientError> {
11761250
cs.as_any()
11771251
.downcast_ref::<ClientState>()

0 commit comments

Comments
 (0)