Skip to content

Commit

Permalink
feat: implemented recv_packet and write_acknowledgement
Browse files Browse the repository at this point in the history
  • Loading branch information
srdtrk committed Jun 2, 2024
1 parent 8ff2d10 commit d241117
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 19 deletions.
71 changes: 56 additions & 15 deletions contracts/ics26-router/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,10 @@ pub fn execute(
/// Will return an error if the handler returns an error.
#[cosmwasm_std::entry_point]
#[allow(clippy::needless_pass_by_value)]
pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result<Response, ContractError> {
match msg.id {
keys::reply::ON_RECV_PACKET => todo!(),
// TODO: Ensure that events are emitted for all the replies.
keys::reply::ON_RECV_PACKET => reply::write_acknowledgement(deps, env, msg.result),
_ => Err(ContractError::UnknownReplyId(msg.id)),
}
}
Expand All @@ -146,10 +147,7 @@ mod execute {

use cw_ibc_lite_ics02_client as ics02_client;
use cw_ibc_lite_shared::{
types::{
apps,
ibc::{Height, Packet},
},
types::{apps, ibc},
utils,
};

Expand Down Expand Up @@ -191,7 +189,7 @@ mod execute {
// Construct the packet.
let sequence =
state::helpers::new_sequence_send(deps.storage, &source_port, &source_channel)?;
let packet = Packet {
let packet = ibc::Packet {
sequence,
source_channel,
source_port,
Expand Down Expand Up @@ -223,17 +221,17 @@ mod execute {
deps: DepsMut,
_env: Env,
info: MessageInfo,
packet: Packet,
packet: ibc::Packet,
proof_commitment: Binary,
proof_height: Height,
proof_height: ibc::Height,
) -> Result<Response, ContractError> {
let ics02_address = state::ICS02_CLIENT_ADDRESS.load(deps.storage)?;
let ics02_contract = ics02_client::helpers::Ics02ClientContract::new(ics02_address);

let ibc_app_address = state::IBC_APPS.load(deps.storage, &packet.destination_port)?;
let ibc_app_contract = apps::helpers::IbcApplicationContract::new(ibc_app_address);

// Verify the
// Verify the counterparty.
let counterparty = ics02_contract
.query(&deps.querier)
.counterparty(&packet.destination_channel)?;
Expand Down Expand Up @@ -265,7 +263,9 @@ mod execute {
.verify_membership(verify_membership_msg)?;

state::helpers::set_packet_receipt(deps.storage, &packet)?;
state::helpers::save_packet_temp_store(deps.storage, &packet)?;

let event = events::recv_packet::success(&packet);
// NOTE: We must retreive a reply from the IBC app to set the acknowledgement.
let callback_msg = apps::callbacks::IbcAppCallbackMsg::OnRecvPacket {
packet,
Expand All @@ -276,18 +276,21 @@ mod execute {
keys::reply::ON_RECV_PACKET,
);

Ok(Response::new().add_submessage(recv_packet_callback))
// TODO: Ensure event emission is reverted if the callback fails.
Ok(Response::new()
.add_submessage(recv_packet_callback)
.add_event(event))
}

#[allow(clippy::needless_pass_by_value)]
pub fn acknowledgement(
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_packet: Packet,
_packet: ibc::Packet,
_acknowledgement: Binary,
_proof_acked: Binary,
_proof_height: Height,
_proof_height: ibc::Height,
) -> Result<Response, ContractError> {
todo!()
}
Expand All @@ -297,9 +300,9 @@ mod execute {
_deps: DepsMut,
_env: Env,
_info: MessageInfo,
_packet: Packet,
_packet: ibc::Packet,
_proof_unreceived: Binary,
_proof_height: Height,
_proof_height: ibc::Height,
_next_sequence_recv: u64,
) -> Result<Response, ContractError> {
todo!()
Expand Down Expand Up @@ -333,6 +336,44 @@ mod execute {
}
}

mod reply {
use cosmwasm_std::SubMsgResult;
use cw_ibc_lite_shared::types::ibc;

use crate::types::events;

use super::{state, ContractError, DepsMut, Env, Response};

/// Handles the reply to
/// [`cw_ibc_lite_shared::types::apps::callbacks::IbcAppCallbackMsg::OnRecvPacket`].
/// It writes the acknowledgement and emits the write acknowledgement events.
#[allow(clippy::needless_pass_by_value)]
pub fn write_acknowledgement(
deps: DepsMut,
_env: Env,
result: SubMsgResult,
) -> Result<Response, ContractError> {
match result {
SubMsgResult::Ok(resp) => {
let ack: ibc::Acknowledgement = resp
.data
.ok_or(ContractError::RecvPacketCallbackNoResponse)?
.into();
let packet = state::helpers::remove_packet_temp_store(deps.storage)?;

state::helpers::commit_packet_ack(deps.storage, &packet, &ack)?;
Ok(
Response::new()
.add_event(events::write_acknowledgement::success(&packet, &ack)),
)
}
SubMsgResult::Err(err) => {
unreachable!("unexpected `SubMsg::reply_on_success`, error: {err}")
}
}
}
}

mod query {
use super::{state, Binary, ContractError, Deps, Env};

Expand Down
59 changes: 59 additions & 0 deletions contracts/ics26-router/src/types/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
pub const EVENT_TYPE_REGISTER_IBC_APP: &str = "register_ibc_app";
/// `EVENT_TYPE_SEND_PACKET` is the event type for a send packet event
pub const EVENT_TYPE_SEND_PACKET: &str = "send_packet";
/// `EVENT_TYPE_RECV_PACKET` is the event type for a receive packet event
pub const EVENT_TYPE_RECV_PACKET: &str = "recv_packet";
/// `EVENT_TYPE_ACKNOWLEDGE_PACKET` is the event type for writing an acknowledgement
pub const EVENT_TYPE_WRITE_ACKNOWLEDGEMENT: &str = "write_acknowledgement";

/// `ATTRIBUTE_KEY_CONTRACT_ADDRESS` is the attribute key for the contract address
pub const ATTRIBUTE_KEY_CONTRACT_ADDRESS: &str = "contract_address";
Expand All @@ -25,6 +29,8 @@ pub const ATTRIBUTE_KEY_SRC_CHANNEL: &str = "packet_src_channel";
pub const ATTRIBUTE_KEY_DST_PORT: &str = "packet_dst_port";
/// `ATTRIBUTE_KEY_DST_CHANNEL` is the attribute key for the packet destination channel
pub const ATTRIBUTE_KEY_DST_CHANNEL: &str = "packet_dst_channel";
/// `ATTRIBUTE_KEY_ACK_DATA_HEX` is the attribute key for the packet acknowledgement data hex
pub const ATTRIBUTE_KEY_ACK_DATA_HEX: &str = "packet_ack_hex";

/// Contains event messages emitted during [`super::super::msg::ExecuteMsg::RegisterIbcApp`]
pub mod register_ibc_app {
Expand Down Expand Up @@ -72,3 +78,56 @@ pub mod send_packet {
])
}
}

/// Contains event messages emitted during [`super::super::msg::ExecuteMsg::RecvPacket`]
pub mod recv_packet {
use cosmwasm_std::{Attribute, Event, HexBinary};
use cw_ibc_lite_shared::types::ibc::Packet;

/// `recv_packet` is the event message for a receive packet event
#[must_use]
pub fn success(packet: &Packet) -> Event {
Event::new(super::EVENT_TYPE_RECV_PACKET).add_attributes(vec![
Attribute::new(
super::ATTRIBUTE_KEY_DATA_HEX,
HexBinary::from(packet.data.as_slice()).to_hex(),
),
Attribute::new(super::ATTRIBUTE_KEY_SRC_PORT, &packet.source_port),
Attribute::new(super::ATTRIBUTE_KEY_SRC_CHANNEL, &packet.source_channel),
Attribute::new(super::ATTRIBUTE_KEY_DST_PORT, &packet.destination_port),
Attribute::new(
super::ATTRIBUTE_KEY_DST_CHANNEL,
&packet.destination_channel,
),
])
}
}

/// Contains event messages emitted during the reply to
/// [`cw_ibc_lite_shared::types::apps::callbacks::IbcAppCallbackMsg::OnRecvPacket`]
pub mod write_acknowledgement {
use cosmwasm_std::{Attribute, Event, HexBinary};
use cw_ibc_lite_shared::types::ibc;

/// `write_acknowledgement` is the event message for writing an acknowledgement
#[must_use]
pub fn success(packet: &ibc::Packet, ack: &ibc::Acknowledgement) -> Event {
Event::new(super::EVENT_TYPE_WRITE_ACKNOWLEDGEMENT).add_attributes(vec![
Attribute::new(
super::ATTRIBUTE_KEY_DATA_HEX,
HexBinary::from(packet.data.as_slice()).to_hex(),
),
Attribute::new(
super::ATTRIBUTE_KEY_ACK_DATA_HEX,
HexBinary::from(ack.as_slice()).to_hex(),
),
Attribute::new(super::ATTRIBUTE_KEY_SRC_PORT, &packet.source_port),
Attribute::new(super::ATTRIBUTE_KEY_SRC_CHANNEL, &packet.source_channel),
Attribute::new(super::ATTRIBUTE_KEY_DST_PORT, &packet.destination_port),
Attribute::new(
super::ATTRIBUTE_KEY_DST_CHANNEL,
&packet.destination_channel,
),
])
}
}
70 changes: 66 additions & 4 deletions contracts/ics26-router/src/types/state.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! This module defines the state storage of the Contract.
use cosmwasm_std::Addr;
use cw_ibc_lite_shared::types::storage::PureItem;
use cw_ibc_lite_shared::types::{ibc, storage::PureItem};

use cw_storage_plus::{Item, Map};

Expand All @@ -19,6 +19,10 @@ pub const IBC_APPS: Map<&str, Addr> = Map::new("ibc_apps");
/// The item for storing the ics02-client router contract address.
pub const ICS02_CLIENT_ADDRESS: Item<Addr> = Item::new("ics02_client_address");

/// The item for storing [`ibc::Packet`] to the temporary store for replies.
// TODO: remove this in CosmWasm v2 since it introduces the ability to add custom data to reply.
pub const PACKET_TEMP_STORE: Item<ibc::Packet> = Item::new("recv_packet_temp_store");

/// A collection of methods to access the packet commitment state.
pub mod packet_commitment_item {
use super::{path, PureItem};
Expand Down Expand Up @@ -123,7 +127,7 @@ pub mod admin {
/// Contains state storage helpers.
pub mod helpers {
use cosmwasm_std::{StdResult, Storage};
use cw_ibc_lite_shared::types::{error::ContractError, ibc::Packet};
use cw_ibc_lite_shared::types::{error::ContractError, ibc};

/// Generates a new sequence number for sending packets.
///
Expand All @@ -145,7 +149,10 @@ pub mod helpers {
///
/// # Errors
/// Returns an error if the packet has already been committed.
pub fn commit_packet(storage: &mut dyn Storage, packet: &Packet) -> Result<(), ContractError> {
pub fn commit_packet(
storage: &mut dyn Storage,
packet: &ibc::Packet,
) -> Result<(), ContractError> {
let item = super::packet_commitment_item::new(
&packet.source_port,
&packet.source_channel,
Expand All @@ -169,7 +176,7 @@ pub mod helpers {
/// Returns an error if the receipt has already been committed.
pub fn set_packet_receipt(
storage: &mut dyn Storage,
packet: &Packet,
packet: &ibc::Packet,
) -> Result<(), ContractError> {
let item = super::packet_receipt_item::new(
&packet.destination_port,
Expand All @@ -186,4 +193,59 @@ pub mod helpers {
item.save(storage, &[1]);
Ok(())
}

/// Commits an acknowledgment to the provable packet acknowledgment store.
/// This is used to prove the `AcknowledgementPacket` in the counterparty chain.
///
/// # Errors
/// Returns an error if the acknowledgment has already been committed.
pub fn commit_packet_ack(
storage: &mut dyn Storage,
packet: &ibc::Packet,
ack: &ibc::Acknowledgement,
) -> Result<(), ContractError> {
let item = super::packet_ack_item::new(
&packet.destination_port,
&packet.destination_channel,
packet.sequence,
);

if item.exists(storage) {
return Err(ContractError::packet_already_commited(
item.as_slice().to_vec(),
));
}

item.save(storage, &ack.to_commitment_bytes());
Ok(())
}

/// Saves the packet to [`super::PACKET_TEMP_STORE`].
///
/// # Errors
/// Returns an error if the packet has already been committed.
pub fn save_packet_temp_store(
storage: &mut dyn Storage,
packet: &ibc::Packet,
) -> Result<(), ContractError> {
if super::PACKET_TEMP_STORE.exists(storage) {
return Err(ContractError::packet_already_commited(
super::PACKET_TEMP_STORE.as_slice().to_vec(),
));
}

Ok(super::PACKET_TEMP_STORE.save(storage, packet)?)
}

/// Loads and removes the packet from the temporary store for the reply to
///
/// # Errors
/// Returns an error if the packet identifier cannot be loaded.
pub fn remove_packet_temp_store(
storage: &mut dyn Storage,
) -> Result<ibc::Packet, ContractError> {
let packet = super::PACKET_TEMP_STORE.load(storage)?;
super::PACKET_TEMP_STORE.remove(storage);
Ok(packet)
}
}
5 changes: 5 additions & 0 deletions packages/shared/src/types/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ pub enum ContractError {
EmptyTimestamp,
#[error("packet already commited: key: {:02x?}", key)]
PacketAlreadyCommited { key: Vec<u8> },

#[error(
"recv packet callback must return an acknowledgement data, but it returned nothing, async acknowledgement is not supported"
)]
RecvPacketCallbackNoResponse,
}

impl ContractError {
Expand Down
36 changes: 36 additions & 0 deletions packages/shared/src/types/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub struct Packet {
pub timeout: IbcTimeout,
}

/// Acknowledgement is the data returned by an IBC application after processing a packet.
/// It is opaque to the relayer.
pub struct Acknowledgement(Vec<u8>);

/// Height is a monotonically increasing data type
/// that can be compared against another Height for the purposes of updating and
/// freezing clients
Expand Down Expand Up @@ -76,6 +80,38 @@ impl Packet {
}
}

impl Acknowledgement {
/// Creates a new acknowledgement from the given bytes.
#[must_use]
pub fn new(data: Vec<u8>) -> Self {
Self(data)
}

/// Returns the acknowledgement data as a slice.
#[must_use]
pub fn as_slice(&self) -> &[u8] {
&self.0
}

/// Returns the serialized commitment bytes of the acknowledgement.
#[must_use]
pub fn to_commitment_bytes(&self) -> Vec<u8> {
sha2::Sha256::digest(&self.0).to_vec()
}
}

impl From<cosmwasm_std::Binary> for Acknowledgement {
fn from(data: cosmwasm_std::Binary) -> Self {
Self(data.into())
}
}

impl From<Acknowledgement> for Vec<u8> {
fn from(ack: Acknowledgement) -> Self {
ack.0
}
}

impl From<Height> for ibc_proto::ibc::core::client::v1::Height {
fn from(height: Height) -> Self {
Self {
Expand Down

0 comments on commit d241117

Please sign in to comment.