Skip to content

Commit

Permalink
feat: implemented first half of recv_packet
Browse files Browse the repository at this point in the history
srdtrk committed Jun 1, 2024
1 parent 8e21f47 commit 8ff2d10
Showing 10 changed files with 853 additions and 15 deletions.
682 changes: 681 additions & 1 deletion Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -46,3 +46,4 @@ ibc-client-tendermint = { version = "0.53.0", default-features = false, features
cw-ibc-lite-shared = { version = "0.1.0", path = "./packages/shared/" }
cw-ibc-lite-ics02-client = { version = "0.1.0", path = "./contracts/ics02-client/", default-features = false }
sha2 = "0.10.8"
ibc-proto = "0.44.0"
1 change: 1 addition & 0 deletions contracts/ics26-router/Cargo.toml
Original file line number Diff line number Diff line change
@@ -27,3 +27,4 @@ thiserror = { workspace = true }
ibc-core-host = { workspace = true }
cw-ibc-lite-shared = { workspace = true }
cw-ibc-lite-ics02-client = { workspace = true }
ibc-client-cw = { workspace = true }
88 changes: 77 additions & 11 deletions contracts/ics26-router/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! This module handles the execution logic of the contract.
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response};
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response};

use cw_ibc_lite_ics02_client as ics02_client;

@@ -112,6 +112,19 @@ pub fn execute(
}
}

/// Handles the replies to the submessages.
///
/// # Errors
/// 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> {
match msg.id {
keys::reply::ON_RECV_PACKET => todo!(),
_ => Err(ContractError::UnknownReplyId(msg.id)),
}
}

/// Handles the query messages by routing them to the respective handlers.
///
/// # Errors
@@ -129,9 +142,9 @@ mod execute {

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

use cosmwasm_std::{Binary, IbcTimeout};
use cosmwasm_std::{Binary, IbcTimeout, SubMsg};

use cw_ibc_lite_ics02_client as client;
use cw_ibc_lite_ics02_client as ics02_client;
use cw_ibc_lite_shared::{
types::{
apps,
@@ -140,6 +153,8 @@ mod execute {
utils,
};

use ibc_client_cw::types::VerifyMembershipMsgRaw;

#[allow(clippy::too_many_arguments, clippy::needless_pass_by_value)]
pub fn send_packet(
deps: DepsMut,
@@ -153,15 +168,16 @@ mod execute {
timeout: IbcTimeout,
) -> Result<Response, ContractError> {
let ics02_address = state::ICS02_CLIENT_ADDRESS.load(deps.storage)?;
let ics02_contract = client::helpers::Ics02ClientContract::new(ics02_address);
let ics02_contract = ics02_client::helpers::Ics02ClientContract::new(ics02_address);

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

// Ensure the counterparty is the destination channel.
let counterparty_id = ics02_contract
.query(&deps.querier)
.counterparty(&source_channel)?;
.counterparty(&source_channel)?
.client_id;
if counterparty_id != dest_channel {
return Err(ContractError::invalid_counterparty(
counterparty_id,
@@ -204,14 +220,63 @@ mod execute {

#[allow(clippy::needless_pass_by_value)]
pub fn recv_packet(
_deps: DepsMut,
deps: DepsMut,
_env: Env,
_info: MessageInfo,
_packet: Packet,
_proof_commitment: Binary,
_proof_height: Height,
info: MessageInfo,
packet: Packet,
proof_commitment: Binary,
proof_height: Height,
) -> Result<Response, ContractError> {
todo!()
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
let counterparty = ics02_contract
.query(&deps.querier)
.counterparty(&packet.destination_channel)?;
if counterparty.client_id != packet.source_channel {
return Err(ContractError::invalid_counterparty(
counterparty.client_id,
packet.source_channel,
));
}

// NOTE: Verify the packet commitment.
let counterparty_commitment_path = state::packet_commitment_item::new(
&packet.source_port,
&packet.source_channel,
packet.sequence,
)
.try_into()?;
let verify_membership_msg = VerifyMembershipMsgRaw {
proof: proof_commitment.into(),
path: counterparty_commitment_path,
value: packet.to_commitment_bytes(),
height: proof_height.into(),
delay_time_period: 0,
delay_block_period: 0,
};
let _ = ics02_contract
.query(&deps.querier)
.client_querier(&packet.destination_channel)?
.verify_membership(verify_membership_msg)?;

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

// NOTE: We must retreive a reply from the IBC app to set the acknowledgement.
let callback_msg = apps::callbacks::IbcAppCallbackMsg::OnRecvPacket {
packet,
relayer: info.sender.into(),
};
let recv_packet_callback = SubMsg::reply_on_success(
ibc_app_contract.call(callback_msg)?,
keys::reply::ON_RECV_PACKET,
);

Ok(Response::new().add_submessage(recv_packet_callback))
}

#[allow(clippy::needless_pass_by_value)]
@@ -251,6 +316,7 @@ mod execute {
let contract_address = deps.api.addr_validate(&contract_address)?;
let port_id = if let Some(port_id) = port_id {
// NOTE: Only the admin can register an IBC app with a custom port ID.
// TODO: Add restrictions to the custom port ID. Such as not using `/`.
state::admin::assert_admin(&env, &deps.querier, &info.sender)?;
port_id
} else {
4 changes: 2 additions & 2 deletions contracts/ics26-router/src/types/keys.rs
Original file line number Diff line number Diff line change
@@ -16,6 +16,6 @@ pub const ICS02_CLIENT_SALT: &str = "ics02_client";

/// Contains the reply ids for various `SubMsg` replies
pub mod reply {
/// `ON_SEND_PACKET` is the reply id for the `OnSendPacket` callback reply
pub const ON_SEND_PACKET: u64 = 1;
/// `ON_RECV_PACKET` is the reply id for the `on_recv_packet` reply
pub const ON_RECV_PACKET: u64 = 1;
}
25 changes: 25 additions & 0 deletions contracts/ics26-router/src/types/state.rs
Original file line number Diff line number Diff line change
@@ -161,4 +161,29 @@ pub mod helpers {
item.save(storage, &packet.to_commitment_bytes());
Ok(())
}

/// Sets the packet receipt in the provable packet receipt store.
/// This is used to prevent replay.
///
/// # Errors
/// Returns an error if the receipt has already been committed.
pub fn set_packet_receipt(
storage: &mut dyn Storage,
packet: &Packet,
) -> Result<(), ContractError> {
let item = super::packet_receipt_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, &[1]);
Ok(())
}
}
1 change: 1 addition & 0 deletions packages/shared/Cargo.toml
Original file line number Diff line number Diff line change
@@ -17,3 +17,4 @@ ibc-core-host = { workspace = true }
ibc-client-cw = { workspace = true }
cw-ownable = { workspace = true }
sha2 = { workspace = true }
ibc-proto = { workspace = true }
6 changes: 6 additions & 0 deletions packages/shared/src/types/error.rs
Original file line number Diff line number Diff line change
@@ -12,6 +12,10 @@ pub enum ContractError {
Std(#[from] StdError),
#[error("{0}")]
OwnershipError(#[from] cw_ownable::OwnershipError),
#[error("{0}")]
FromUTF8Error(#[from] std::string::FromUtf8Error),
#[error("{0}")]
UTF8Error(#[from] std::str::Utf8Error),

#[error("unauthorized")]
Unauthorized,
@@ -23,6 +27,8 @@ pub enum ContractError {
source_type: String,
target_type: String,
},
#[error("unknown reply id: {0}")]
UnknownReplyId(u64),

#[error("counterparty already provided")]
CounterpartyAlreadyProvided,
16 changes: 16 additions & 0 deletions packages/shared/src/types/ibc.rs
Original file line number Diff line number Diff line change
@@ -43,6 +43,13 @@ pub struct Height {
pub revision_height: u64,
}

// /// `MerklePath` is the path used to verify commitment proofs, which can be an
// /// arbitrary structured object (defined by a commitment type).
// /// `MerklePath` is represented from root-to-leaf
// pub struct MerklePath {
// pub key_path: Vec<String>,
// }

impl Packet {
/// `to_commitment_bytes` serializes the packet to commitment bytes as per [ibc-lite go implementation](https://github.com/cosmos/ibc-go/blob/2b40562bcd59ce820ddd7d6732940728487cf94e/modules/core/04-channel/types/packet.go#L38)
///
@@ -68,3 +75,12 @@ impl Packet {
sha2::Sha256::digest(&buf).to_vec()
}
}

impl From<Height> for ibc_proto::ibc::core::client::v1::Height {
fn from(height: Height) -> Self {
Self {
revision_number: height.revision_number,
revision_height: height.revision_height,
}
}
}
44 changes: 43 additions & 1 deletion packages/shared/src/types/storage.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! This module defines the `CosmWasm` storage helper types.
use cosmwasm_std::{Addr, CustomQuery, QuerierWrapper, StdResult, Storage};
use ibc_client_cw::types::MerklePath;

use super::error::ContractError;

@@ -56,7 +57,7 @@ impl PureItem {
}

/// If you import [`PureItem`] from the remote contract, this will let you read the data
/// from a remote contract using [`WasmQuery::Raw`]. Returns `Ok(None)` if no data is set.
/// from a remote contract using [`cosmwasm_std::WasmQuery::Raw`]. Returns `Ok(None)` if no data is set.
///
/// # Errors
/// It only returns error on some runtime issue, not on any data cases.
@@ -67,6 +68,47 @@ impl PureItem {
) -> StdResult<Option<Vec<u8>>> {
querier.query_wasm_raw(remote_contract, self.as_slice())
}

/// This will convert the [`PureItem`] into a [`MerklePath`].
/// This is useful when you want to use the key for remote proofs.
///
/// # Errors
/// It returns an error if the key is not valid UTF-8.
pub fn into_merkle_path_with_prefix(
self,
merkle_prefix: Option<MerklePath>,
) -> Result<MerklePath, ContractError> {
if let Some(mut prefix) = merkle_prefix {
let last_mut = prefix.key_path.last_mut();
if let Some(last) = last_mut {
let key = String::from_utf8(self.storage_key)?;
last.push_str(&key);
Ok(prefix)
} else {
self.try_into()
}
} else {
self.try_into()
}
}
}

impl TryFrom<PureItem> for MerklePath {
type Error = ContractError;

fn try_from(item: PureItem) -> Result<Self, Self::Error> {
Ok(Self {
key_path: vec![item.try_into()?],
})
}
}

impl TryFrom<PureItem> for String {
type Error = ContractError;

fn try_from(item: PureItem) -> Result<Self, Self::Error> {
Ok(Self::from_utf8(item.storage_key)?)
}
}

/// Includes the helpers for constructing a [`cosmwasm_std::DepsMut`] from an [`cosmwasm_std::Deps`].

0 comments on commit 8ff2d10

Please sign in to comment.