Skip to content
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

Implement ICS-26 router for RecvPacket in {Validation,Execution}Context #377

Merged
merged 16 commits into from
Jan 31, 2023
19 changes: 14 additions & 5 deletions crates/ibc/src/applications/transfer/acknowledgement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,21 @@ impl Display for TokenTransferAcknowledgement {

impl From<TokenTransferAcknowledgement> for Vec<u8> {
fn from(ack: TokenTransferAcknowledgement) -> Self {
// WARNING: Make sure all branches always return a non-empty vector.
// Otherwise, the conversion to `Acknowledgement` will panic.
plafer marked this conversation as resolved.
Show resolved Hide resolved
match ack {
TokenTransferAcknowledgement::Success(_) => r#"{"result":"AQ=="}"#.as_bytes().into(),
// TokenTransferAcknowledgement::Error(s) => s.as_bytes(),
TokenTransferAcknowledgement::Error(s) => alloc::format!(r#"{{"error":"{s}"}}"#).into(),
}
}
}

impl From<TokenTransferAcknowledgement> for Acknowledgement {
fn from(ack: TokenTransferAcknowledgement) -> Self {
ack.into()
let v: Vec<u8> = ack.into();

v.try_into()
.expect("token transfer internal error: ack is never supposed to be empty")
}
}

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

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

#[test]
fn test_ack_error_to_vec() {
let ack_error = TokenTransferAcknowledgement::Error(
let ack_error: Vec<u8> = TokenTransferAcknowledgement::Error(
"cannot unmarshal ICS-20 transfer packet data".to_string(),
);
)
.into();

// Check that it's the same output as ibc-go
// Note: this also implicitly checks that the ack bytes are non-empty,
// which would make the conversion to `Acknowledgement` panic
assert_eq!(
Vec::<u8>::from(ack_error),
ack_error,
r#"{"error":"cannot unmarshal ICS-20 transfer packet data"}"#.as_bytes()
);
}
Expand Down
32 changes: 32 additions & 0 deletions crates/ibc/src/applications/transfer/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ pub fn on_timeout_packet(
pub use val_exec_ctx::*;
#[cfg(feature = "val_exec_ctx")]
mod val_exec_ctx {
use crate::applications::transfer::relay::on_recv_packet::process_recv_packet_execute;

pub use super::*;

#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -498,6 +500,36 @@ mod val_exec_ctx {
) -> Result<ModuleExtras, TokenTransferError> {
Ok(ModuleExtras::empty())
}

pub fn on_recv_packet_execute(
ctx: &mut impl TokenTransferContext,
packet: &Packet,
) -> (ModuleExtras, Acknowledgement) {
let data = match serde_json::from_slice::<PacketData>(&packet.data) {
Ok(data) => data,
Err(_) => {
let ack = TokenTransferAcknowledgement::Error(
TokenTransferError::PacketDataDeserialization.to_string(),
);
return (ModuleExtras::empty(), ack.into());
}
};

let (mut extras, ack) = match process_recv_packet_execute(ctx, packet, data.clone()) {
Ok(extras) => (extras, TokenTransferAcknowledgement::success()),
Err((extras, error)) => (extras, TokenTransferAcknowledgement::from_error(error)),
};

let recv_event = RecvEvent {
receiver: data.receiver,
denom: data.token.denom,
amount: data.token.amount,
success: ack.is_successful(),
};
extras.events.push(recv_event.into());

(extras, ack.into())
}
}

#[cfg(test)]
Expand Down
75 changes: 75 additions & 0 deletions crates/ibc/src/applications/transfer/relay/on_recv_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,78 @@ pub fn process_recv_packet<Ctx: 'static + TokenTransferContext>(

Ok(())
}

#[cfg(feature = "val_exec_ctx")]
pub use val_exec_ctx::*;
#[cfg(feature = "val_exec_ctx")]
mod val_exec_ctx {
pub use super::*;
use crate::core::ics04_channel::handler::ModuleExtras;

pub fn process_recv_packet_execute<Ctx: TokenTransferContext>(
ctx: &mut Ctx,
packet: &Packet,
data: PacketData,
) -> Result<ModuleExtras, (ModuleExtras, TokenTransferError)> {
Farhad-Shabani marked this conversation as resolved.
Show resolved Hide resolved
if !ctx.is_receive_enabled() {
return Err((ModuleExtras::empty(), TokenTransferError::ReceiveDisabled));
}

let receiver_account = data.receiver.clone().try_into().map_err(|_| {
(
ModuleExtras::empty(),
TokenTransferError::ParseAccountFailure,
)
})?;

let extras = if is_receiver_chain_source(
packet.port_on_a.clone(),
packet.chan_on_a.clone(),
&data.token.denom,
) {
// sender chain is not the source, unescrow tokens
let prefix = TracePrefix::new(packet.port_on_a.clone(), packet.chan_on_a.clone());
let coin = {
let mut c = data.token;
c.denom.remove_trace_prefix(&prefix);
c
};

let escrow_address = ctx
.get_channel_escrow_address(&packet.port_on_b, &packet.chan_on_b)
.map_err(|token_err| (ModuleExtras::empty(), token_err))?;

ctx.send_coins(&escrow_address, &receiver_account, &coin)
.map_err(|token_err| (ModuleExtras::empty(), token_err))?;

ModuleExtras::empty()
} else {
// sender chain is the source, mint vouchers
let prefix = TracePrefix::new(packet.port_on_b.clone(), packet.chan_on_b.clone());
let coin = {
let mut c = data.token;
c.denom.add_trace_prefix(prefix);
c
};

let extras = {
let denom_trace_event = DenomTraceEvent {
trace_hash: ctx.denom_hash_string(&coin.denom),
denom: coin.denom.clone(),
};
ModuleExtras {
events: vec![denom_trace_event.into()],
log: Vec::new(),
}
};
let extras_closure = extras.clone();

ctx.mint_coins(&receiver_account, &coin)
.map_err(|token_err| (extras_closure, token_err))?;

extras
};

Ok(extras)
}
}
74 changes: 74 additions & 0 deletions crates/ibc/src/clients/ics07_tendermint/client_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,39 @@ impl Ics2ClientState for ClientState {
verify_membership(client_state, prefix, proof, root, path, value)
}

#[cfg(feature = "val_exec_ctx")]
fn new_verify_packet_data(
&self,
ctx: &dyn ValidationContext,
height: Height,
connection_end: &ConnectionEnd,
proof: &CommitmentProofBytes,
root: &CommitmentRoot,
port_id: &PortId,
channel_id: &ChannelId,
sequence: Sequence,
commitment: PacketCommitment,
) -> Result<(), ClientError> {
let client_state = downcast_tm_client_state(self)?;
client_state.verify_height(height)?;
new_verify_delay_passed(ctx, height, connection_end)?;

let commitment_path = CommitmentsPath {
port_id: port_id.clone(),
channel_id: channel_id.clone(),
sequence,
};

verify_membership(
client_state,
connection_end.counterparty().prefix(),
proof,
root,
commitment_path,
commitment.into_vec(),
)
}

fn verify_packet_data(
&self,
ctx: &dyn ChannelReader,
Expand Down Expand Up @@ -1172,6 +1205,47 @@ fn verify_delay_passed(
.map_err(|e| e.into())
}

#[cfg(feature = "val_exec_ctx")]
fn new_verify_delay_passed(
ctx: &dyn ValidationContext,
height: Height,
connection_end: &ConnectionEnd,
) -> Result<(), ClientError> {
let current_timestamp = ctx.host_timestamp().map_err(|e| ClientError::Other {
description: e.to_string(),
})?;
let current_height = ctx.host_height().map_err(|e| ClientError::Other {
description: e.to_string(),
})?;

let client_id = connection_end.client_id();
let processed_time =
ctx.client_update_time(client_id, &height)
.map_err(|_| Error::ProcessedTimeNotFound {
client_id: client_id.clone(),
height,
})?;
let processed_height = ctx.client_update_height(client_id, &height).map_err(|_| {
Error::ProcessedHeightNotFound {
client_id: client_id.clone(),
height,
}
})?;

let delay_period_time = connection_end.delay_period();
let delay_period_height = ctx.block_delay(&delay_period_time);

ClientState::verify_delay_passed(
current_timestamp,
current_height,
processed_time,
processed_height,
delay_period_time,
delay_period_height,
)
.map_err(|e| e.into())
}

fn downcast_tm_client_state(cs: &dyn Ics2ClientState) -> Result<&ClientState, ClientError> {
cs.as_any()
.downcast_ref::<ClientState>()
Expand Down
Loading