From adbe2354e65af6e90d6081e9c7f1c4534068429f Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 1 Jun 2022 14:43:53 +0200 Subject: [PATCH] Complete ICS20 Implementation (#1989) * Add ics26 Module trait * newtype for Acknowledgement * Define Router interface * Improve Router trait * Update mock router impl * Test for router API * Disallow duplicate module_ids in MockRouter * Add RouterBuilder for seal-style API * Fix failing test * Fix CI after merge * Chainable RouterBuilder::add_route() * Fix test * Add Router::has_route() trait method * Add comments * Separate mutating/non-mutating trait methods for shadow-paging style API * Make most Module trait methods optional * Default impl for MockModule::on_chan_open_try() * Acknowledgement trait * Extend router test * Simplify OnRechPacketResult using FnOnce() * Cleanup * Use Cow for CapabilityName::new() * Use Cow for CapabilityName::new() * Use newtype ModuleId instead of trait assoc type * Module callbacks' args as refs * Fix mock impl * WIP channel callbacks * Fix ModuleId ctor validation * TypedCapability * Use typed capabilities * Add Router::route_mut() * Implement pre-dispatch channel message validation * Avoid cloning message during channel msg dispatch * Add ChannelReader::lookup_module_by_channel() * Complete ics4 dispatch with callbacks * Fix compile errors from rebase * Fix CI test * Improve mock impl for capabilities * cargo fmt * Set channel version returned from on_chan_open_try() * Implement packet handler verification * Add missing check already received packets on ordered channels * Avoid cloning message during packet msg dispatch * Implement RecvPacket NoOp * cargo fmt and remove unused errors * Make on_recv_packet() Ack result optional * Don't return Result from on_recv_packet() and pass GenericAcknowledgement to on_acknowledgement_packet() * Implement packet callbacks * Allow callbacks to write logs and emit events * Fix test * Add .changelog entry * Add a comment for state rollback expectation from dispatch() * ics26_routing::handler::deliver() takes single message as input * ics26_routing::handler::deliver() returns logs as well * Remove ctx_ro * Callbacks return ModuleOutput * Return HandlerOutputBuilder from channel and packet dispatch fn * Revert "Callbacks return ModuleOutput" This reverts commit 1d430c9f86168816b55cd9185c6c632c4190118d. * Address review feedback for comments * Extract ChannelMsg::lookup_module() * Add ICS20 Denom type * Add ICS20 TracePrefix & TracePath type * Define Coin and Decimal types * Impl conversions from/to RawDenomTrace * Make better use of derive-more * Impl conversions for Coin type * Add HashedDenom and polish DenomTrace impl * Define PacketData domain type and conversions * Use Coin domain type in MsgTransfer * Fix usage of Coin type in relayer code * Always panic on decimal arith overflow * Polish generic Signer impl * Implement ICS20 signer type * Coin type with generic Denom param * Use IbcCoin in MsgTransfer and PrefixedCoin in PacketData * Minor refactoring * Fix test_util * Impl AsRef<[u8]> for GenericAcknowledgement * Ics20Context is no longer a supertrait of Ics26Context * Add ICS20 Ack type * Add ICS20 event enum placeholder * Define all ICS20 expected keepers and context * Update send_transfer() for recent context changes * Give mut ref to set_channel_escrow_address() * Define ICS20 callback functions * Fix test build * Move denom derive functions to integration tests * Add version to chan_open_try callback * Rename ChannelId::counter() to sequence() * Add AppModule error variant to ChannelError * Implement validation helpers for callbacks * Define Ics20 callback errors * Implement channel handshake callbacks * Fix clippy errors * Impl Deserialize for PacketData * Manually implement AsRef for Signer * Fix ICS20 Ack success from impl * Add more ICS20 errors * Improve ack type ctor * Add ctor for TracePrefix * Provide denom trace methods to remove prefix and check for empty trace path * Implement conversion from PrefixedCoin to IbcCoin * Add Ics20Reader trait methods to check send/receive enabled * Provided trait method get_channel_escrow_address() * Add BankReader trait for is_blocked_account() * Add FromStr bound for Ics20Context::AccountId * Use IbcCoins in Bank traits * Impl on_recv_packet() for cases where receiver chain is source * Fallible OnRecvPacket::write_fn() * Complete on_recv_packet impl * Fix test build * Fix clippy errors * Implement remaining packet callbacks for ICS20 * Make set_denom_trace() fallible * Don't derive AsRef * Complete send_transfer impl * Manual deserialize impl for Acknowledgement * Add ctor for Acknowledgement * Handle packet-data deserialize error separately * Use U256 for Denom and move bigint.rs to modules/src * Rename Denom to Amount * Cleanup * Fix trait definitions * Use source enum * Fix AccountReader trait * Validate port_id * More refactoring * Rename Signer to Address * Use Address instead of Signer where applicable * Fix send_transfer packet creation * Fix clippy warnings * Define ICS20 events * Extract relay code into separate files * Fix clippy warnings * Make HandlerOutput/Builder generic over events * Define ModuleEvent * Add AppModule variant to IbcEvent * Impl Display for Acknowledgement * Derive serde for ModuleId * Add ModuleId to ModuleEvent * Impl conversion from tuple for ModuleEventAttribute * Impl conversions from ICS20 events to ModuleEvent * Add event for transfer * Remove bech32 validation from Address * Change MsgTransfer receiver type to Address * Extract MockContext IbcStore * Improve conversion from Signer to AccountId * Add deliver method to Module trait * Implement conversions for MsgTransfer to/from Protobuf Any * Store packet result directly in send_transfer() * Make all ICS20 mods public * Make all IbsStore fields public to enable access for app modules * Implement Ics20Context for DummyTransferModule * Fix failing test * Manual Clone impl for MockContext * Fix failing test * Revert "Fix failing test" This reverts commit 40aec618a22df1c8a2592e63028cd1262957f5a1. * Fix MockContext Clone impl * Update trait definitions after merge with master * Replace ModuleOutput with ModuleOutputBuilder in callbacks * Add inout param ModuleOutputBuilder to Ics20 callback handlers * Emit ICS20 receive packet events * Module::deliver() must be able to emit IbcEvents * Allow HandlerOutputBuilder to merge HandlerOutput * Emit transfer event * Add AckStatusEvent * Emit ICS20 ack events * Emit ICS20 timeout events * Remove ABCI error code * Remove #[allow(unused)] * Add log for send transfer * MsgReceipt abstraction * Handle missing IbcEvent to AbciEvent conversions for RecvPacket and TimeoutOnClose events * Implement ModuleEvent to AbciEvent conversion * Make send_transfer() public * Allow empty TracePaths * Fix TracePath multiple prefix parse bug * Fix TracePath empty str parse bug * Improve DenomTrace FromStr * Add denom validation test * Add denom trace and serde tests * TracePath tests * Allow Denom with '/' * Fix HashedDenom FromStr impl for empty hash * Minor refactoring * Add IbcCoin tests * Add .changelog entry * Fix clippy errors * Disallow empty Signer and replace Address with it * Use ToString as trait bound for ICS20 AccountId * Change AccountId trait bound from FromStr to TryFrom * Remove Signer::new() * Delete OCap related TODO * Remove the PortKeeper * Remove Module::deliver() * Fix clippy warnings * Rename transfer module * Rename mod relay * Fix tests failing due to empty signer * Rename PORT_ID const to PORT_ID_STR * Fix escrow addr gen * Test cosmos escrow addr gen * Rename receiver account * Remove `has_denom_trace()` * Remove `BankReader::is_blocked_account()` * Remove `AccountId::ToString` bound * Remove `AccountReader` * Mint/burn into user accounts directly * Use `chunks_exact()` instead of `windows().step_by()` * Rename `DenomTrace::has_prefix()` to `trace_starts_with()` * Move trace related methods into `TracePath` * Fix add/remove prefix * Add test for add/remove trace prefix * Set denom trace only if not already set * Use truncate instead of drain in cosmos_adr028_escrow_address() * Rename DenomTrace as PrefixedDenom * Modify all `BankKeeper` methods to use `PrefixedCoin` instead of `IbcCoin` * Rename `Denom` as `BaseDenom` * Impl Into and checked_add/sub() for Amount * Add more TracePath tests * Test TracePath is_empty() * Remove IbcCoin * Remove HashedDenom and HashedCoin * Fix test compilation * Add comment for send_transfer() * Functions for determining source chain * Minor refactoring * cargo fmt * Derive serde for PacketCommitment and AcknowledgementCommitment * docstring and comment Co-authored-by: Philippe Laferriere --- .../improvements/ibc/1759-complete-ics20.md | 1 + Cargo.lock | 2 +- modules/Cargo.toml | 3 +- .../ics20_fungible_token_transfer/context.rs | 5 - .../ics20_fungible_token_transfer/denom.rs | 28 -- .../ics20_fungible_token_transfer/error.rs | 46 -- .../relay_application_logic.rs | 3 - .../relay_application_logic/send_transfer.rs | 51 -- modules/src/applications/mod.rs | 2 +- .../applications/transfer/acknowledgement.rs | 61 +++ modules/src/applications/transfer/context.rs | 348 +++++++++++++ modules/src/applications/transfer/denom.rs | 473 ++++++++++++++++++ modules/src/applications/transfer/error.rs | 140 ++++++ modules/src/applications/transfer/events.rs | 172 +++++++ .../mod.rs | 12 +- .../msgs.rs | 0 .../msgs/transfer.rs | 78 ++- modules/src/applications/transfer/packet.rs | 43 ++ modules/src/applications/transfer/relay.rs | 40 ++ .../transfer/relay/on_ack_packet.rs | 19 + .../transfer/relay/on_recv_packet.rs | 68 +++ .../transfer/relay/on_timeout_packet.rs | 13 + .../transfer/relay/send_transfer.rs | 115 +++++ {relayer/src/util => modules/src}/bigint.rs | 0 modules/src/core/ics02_client/error.rs | 10 +- .../ics02_client/handler/update_client.rs | 9 +- .../core/ics02_client/msgs/create_client.rs | 2 +- .../src/core/ics02_client/msgs/misbehavior.rs | 5 +- .../core/ics02_client/msgs/update_client.rs | 2 +- .../core/ics02_client/msgs/upgrade_client.rs | 2 +- modules/src/core/ics03_connection/error.rs | 5 +- .../ics03_connection/msgs/conn_open_ack.rs | 2 +- .../msgs/conn_open_confirm.rs | 2 +- .../ics03_connection/msgs/conn_open_init.rs | 2 +- .../ics03_connection/msgs/conn_open_try.rs | 2 +- modules/src/core/ics04_channel/commitment.rs | 6 +- modules/src/core/ics04_channel/error.rs | 12 +- modules/src/core/ics04_channel/events.rs | 24 + modules/src/core/ics04_channel/handler.rs | 9 +- .../ics04_channel/msgs/acknowledgement.rs | 11 +- .../ics04_channel/msgs/chan_close_confirm.rs | 2 +- .../ics04_channel/msgs/chan_close_init.rs | 2 +- .../core/ics04_channel/msgs/chan_open_ack.rs | 2 +- .../ics04_channel/msgs/chan_open_confirm.rs | 2 +- .../core/ics04_channel/msgs/chan_open_init.rs | 2 +- .../core/ics04_channel/msgs/chan_open_try.rs | 2 +- .../core/ics04_channel/msgs/recv_packet.rs | 5 +- .../src/core/ics04_channel/msgs/timeout.rs | 5 +- .../ics04_channel/msgs/timeout_on_close.rs | 2 +- modules/src/core/ics04_channel/version.rs | 4 +- modules/src/core/ics05_port/context.rs | 5 - modules/src/core/ics24_host/identifier.rs | 2 +- modules/src/core/ics26_routing/context.rs | 41 +- modules/src/core/ics26_routing/error.rs | 4 +- modules/src/core/ics26_routing/handler.rs | 187 ++++--- modules/src/core/ics26_routing/msgs.rs | 8 - modules/src/events.rs | 81 ++- modules/src/handler.rs | 34 +- modules/src/lib.rs | 1 + modules/src/mock/context.rs | 460 +++++++++++------ modules/src/serializers.rs | 27 + modules/src/signer.rs | 48 +- modules/src/test_utils.rs | 339 ++++++++++++- modules/tests/runner/mod.rs | 4 +- proto/src/lib.rs | 3 + relayer-cli/src/commands/tx/transfer.rs | 2 +- relayer/Cargo.toml | 3 +- relayer/src/chain/cosmos.rs | 4 +- relayer/src/channel/version.rs | 4 +- relayer/src/transfer.rs | 49 +- relayer/src/util.rs | 1 - .../src/tests/manual/simulation.rs | 4 +- tools/test-framework/src/ibc/denom.rs | 31 +- tools/test-framework/src/relayer/transfer.rs | 23 +- 74 files changed, 2654 insertions(+), 567 deletions(-) create mode 100644 .changelog/unreleased/improvements/ibc/1759-complete-ics20.md delete mode 100644 modules/src/applications/ics20_fungible_token_transfer/context.rs delete mode 100644 modules/src/applications/ics20_fungible_token_transfer/denom.rs delete mode 100644 modules/src/applications/ics20_fungible_token_transfer/error.rs delete mode 100644 modules/src/applications/ics20_fungible_token_transfer/relay_application_logic.rs delete mode 100644 modules/src/applications/ics20_fungible_token_transfer/relay_application_logic/send_transfer.rs create mode 100644 modules/src/applications/transfer/acknowledgement.rs create mode 100644 modules/src/applications/transfer/context.rs create mode 100644 modules/src/applications/transfer/denom.rs create mode 100644 modules/src/applications/transfer/error.rs create mode 100644 modules/src/applications/transfer/events.rs rename modules/src/applications/{ics20_fungible_token_transfer => transfer}/mod.rs (66%) rename modules/src/applications/{ics20_fungible_token_transfer => transfer}/msgs.rs (100%) rename modules/src/applications/{ics20_fungible_token_transfer => transfer}/msgs/transfer.rs (62%) create mode 100644 modules/src/applications/transfer/packet.rs create mode 100644 modules/src/applications/transfer/relay.rs create mode 100644 modules/src/applications/transfer/relay/on_ack_packet.rs create mode 100644 modules/src/applications/transfer/relay/on_recv_packet.rs create mode 100644 modules/src/applications/transfer/relay/on_timeout_packet.rs create mode 100644 modules/src/applications/transfer/relay/send_transfer.rs rename {relayer/src/util => modules/src}/bigint.rs (100%) diff --git a/.changelog/unreleased/improvements/ibc/1759-complete-ics20.md b/.changelog/unreleased/improvements/ibc/1759-complete-ics20.md new file mode 100644 index 0000000000..8d7cbbbbad --- /dev/null +++ b/.changelog/unreleased/improvements/ibc/1759-complete-ics20.md @@ -0,0 +1 @@ +- Complete ICS20 implementation ([#1759](https://github.com/informalsystems/ibc-rs/issues/1759)) diff --git a/Cargo.lock b/Cargo.lock index 109d9347c4..0ea314a894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1488,6 +1488,7 @@ dependencies = [ "time 0.3.9", "tracing", "tracing-subscriber", + "uint", ] [[package]] @@ -1578,7 +1579,6 @@ dependencies = [ "tonic", "tracing", "tracing-subscriber", - "uint", "uuid 1.1.0", ] diff --git a/modules/Cargo.toml b/modules/Cargo.toml index 93854d5b80..9a6b3374e1 100644 --- a/modules/Cargo.toml +++ b/modules/Cargo.toml @@ -42,7 +42,8 @@ subtle-encoding = { version = "0.5", default-features = false } sha2 = { version = "0.10.2", default-features = false } flex-error = { version = "0.4.4", default-features = false } num-traits = { version = "0.2.15", default-features = false } -derive_more = { version = "0.99.17", default-features = false, features = ["from", "display"] } +derive_more = { version = "0.99.17", default-features = false, features = ["from", "into", "display"] } +uint = { version = "0.9", default-features = false } [dependencies.tendermint] version = "=0.23.7" diff --git a/modules/src/applications/ics20_fungible_token_transfer/context.rs b/modules/src/applications/ics20_fungible_token_transfer/context.rs deleted file mode 100644 index 58b7e6aaca..0000000000 --- a/modules/src/applications/ics20_fungible_token_transfer/context.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::core::ics04_channel::context::{ChannelKeeper, ChannelReader}; - -/// Captures all the dependencies which the ICS20 module requires to be able to dispatch and -/// process IBC messages. -pub trait Ics20Context: ChannelReader + ChannelKeeper {} diff --git a/modules/src/applications/ics20_fungible_token_transfer/denom.rs b/modules/src/applications/ics20_fungible_token_transfer/denom.rs deleted file mode 100644 index 542e25f00a..0000000000 --- a/modules/src/applications/ics20_fungible_token_transfer/denom.rs +++ /dev/null @@ -1,28 +0,0 @@ -use sha2::{Digest, Sha256}; -use subtle_encoding::hex; - -use crate::core::ics24_host::identifier::{ChannelId, PortId}; -use crate::prelude::*; - -use super::error::Error; - -pub fn derive_ibc_denom( - port_id: &PortId, - channel_id: &ChannelId, - denom: &str, -) -> Result { - let transfer_path = format!("{}/{}/{}", port_id, channel_id, denom); - derive_ibc_denom_with_path(&transfer_path) -} - -/// Derive the transferred token denomination using -/// -pub fn derive_ibc_denom_with_path(transfer_path: &str) -> Result { - let mut hasher = Sha256::new(); - hasher.update(transfer_path.as_bytes()); - - let denom_bytes = hasher.finalize(); - let denom_hex = String::from_utf8(hex::encode_upper(denom_bytes)).map_err(Error::utf8)?; - - Ok(format!("ibc/{}", denom_hex)) -} diff --git a/modules/src/applications/ics20_fungible_token_transfer/error.rs b/modules/src/applications/ics20_fungible_token_transfer/error.rs deleted file mode 100644 index a7cb6a81b6..0000000000 --- a/modules/src/applications/ics20_fungible_token_transfer/error.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::core::ics04_channel::error as channel_error; -use crate::core::ics24_host::error::ValidationError; -use crate::core::ics24_host::identifier::{ChannelId, PortId}; -use crate::prelude::*; - -use alloc::string::FromUtf8Error; -use flex_error::{define_error, DisplayOnly}; - -define_error! { - #[derive(Debug, PartialEq, Eq)] - Error { - UnknowMessageTypeUrl - { url: String } - | e | { format_args!("unrecognized ICS-20 transfer message type URL {0}", e.url) }, - - Ics04Channel - [ channel_error::Error ] - |_ | { "Ics04 channel error" }, - - DestinationChannelNotFound - { port_id: PortId, channel_id: ChannelId } - | e | { format_args!("destination channel not found in the counterparty of port_id {0} and channel_id {1} ", e.port_id, e.channel_id) }, - - InvalidPortId - { context: String } - [ ValidationError ] - | _ | { "invalid port identifier" }, - - InvalidChannelId - { context: String } - [ ValidationError ] - | _ | { "invalid channel identifier" }, - - InvalidPacketTimeoutHeight - { context: String } - | _ | { "invalid packet timeout height value" }, - - InvalidPacketTimeoutTimestamp - { timestamp: u64 } - | _ | { "invalid packet timeout timestamp value" }, - - Utf8 - [ DisplayOnly ] - | _ | { "utf8 decoding error" }, - } -} diff --git a/modules/src/applications/ics20_fungible_token_transfer/relay_application_logic.rs b/modules/src/applications/ics20_fungible_token_transfer/relay_application_logic.rs deleted file mode 100644 index eb612947c2..0000000000 --- a/modules/src/applications/ics20_fungible_token_transfer/relay_application_logic.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! This module implements the processing logic for ICS20 (token transfer) message. - -pub mod send_transfer; diff --git a/modules/src/applications/ics20_fungible_token_transfer/relay_application_logic/send_transfer.rs b/modules/src/applications/ics20_fungible_token_transfer/relay_application_logic/send_transfer.rs deleted file mode 100644 index 5b8be1c3c7..0000000000 --- a/modules/src/applications/ics20_fungible_token_transfer/relay_application_logic/send_transfer.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::applications::ics20_fungible_token_transfer::context::Ics20Context; -use crate::applications::ics20_fungible_token_transfer::error::Error; -use crate::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use crate::core::ics04_channel::handler::send_packet::send_packet; -use crate::core::ics04_channel::packet::Packet; -use crate::core::ics04_channel::packet::PacketResult; -use crate::handler::HandlerOutput; -use crate::prelude::*; - -pub(crate) fn send_transfer( - ctx: &Ctx, - msg: MsgTransfer, -) -> Result, Error> -where - Ctx: Ics20Context, -{ - let source_channel_end = ctx - .channel_end(&(msg.source_port.clone(), msg.source_channel)) - .map_err(Error::ics04_channel)?; - - let destination_port = source_channel_end.counterparty().port_id().clone(); - let destination_channel = source_channel_end - .counterparty() - .channel_id() - .ok_or_else(|| { - Error::destination_channel_not_found(msg.source_port.clone(), msg.source_channel) - })?; - - // get the next sequence - let sequence = ctx - .get_next_sequence_send(&(msg.source_port.clone(), msg.source_channel)) - .map_err(Error::ics04_channel)?; - - //TODO: Application LOGIC. - - let packet = Packet { - sequence, - source_port: msg.source_port, - source_channel: msg.source_channel, - destination_port, - destination_channel: *destination_channel, - data: vec![0], - timeout_height: msg.timeout_height, - timeout_timestamp: msg.timeout_timestamp, - }; - - let handler_output = send_packet(ctx, packet).map_err(Error::ics04_channel)?; - - //TODO: add event/atributes and writes to the store issued by the application logic for packet sending. - Ok(handler_output) -} diff --git a/modules/src/applications/mod.rs b/modules/src/applications/mod.rs index 9e6905bb39..6de19dfe0d 100644 --- a/modules/src/applications/mod.rs +++ b/modules/src/applications/mod.rs @@ -1,3 +1,3 @@ //! Various packet encoding semantics which underpin the various types of transactions. -pub mod ics20_fungible_token_transfer; +pub mod transfer; diff --git a/modules/src/applications/transfer/acknowledgement.rs b/modules/src/applications/transfer/acknowledgement.rs new file mode 100644 index 0000000000..cd7a6272dd --- /dev/null +++ b/modules/src/applications/transfer/acknowledgement.rs @@ -0,0 +1,61 @@ +use super::error::Error; +use crate::core::ics26_routing::context::Acknowledgement as AckTrait; +use crate::prelude::*; +use core::fmt::{Display, Formatter}; + +use serde::{Deserialize, Deserializer}; + +/// A string constant included in error acknowledgements. +/// NOTE: Changing this const is state machine breaking as acknowledgements are written into state +pub const ACK_ERR_STR: &str = "error handling packet on destination chain: see events for details"; +pub const ACK_SUCCESS_B64: &[u8] = b"AQ=="; + +#[derive(Clone, Debug)] +pub enum Acknowledgement { + /// Equivalent to b"AQ==" (i.e. `base64::encode(0x01)`) + Success(Vec), + /// Error Acknowledgement + Error(String), +} + +impl Acknowledgement { + pub fn success() -> Self { + Self::Success(ACK_SUCCESS_B64.to_vec()) + } + + pub fn from_error(err: Error) -> Self { + Self::Error(format!("{}: {}", ACK_ERR_STR, err)) + } +} + +impl AsRef<[u8]> for Acknowledgement { + fn as_ref(&self) -> &[u8] { + match self { + Acknowledgement::Success(b) => b.as_slice(), + Acknowledgement::Error(s) => s.as_bytes(), + } + } +} + +impl<'de> Deserialize<'de> for Acknowledgement { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + let ack = if s.as_bytes() == ACK_SUCCESS_B64 { + Self::Success(ACK_SUCCESS_B64.to_vec()) + } else { + Self::Error(s) + }; + Ok(ack) + } +} + +impl Display for Acknowledgement { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + Acknowledgement::Success(_) => write!(f, "AQ=="), + Acknowledgement::Error(err_str) => write!(f, "{}", err_str), + } + } +} + +impl AckTrait for Acknowledgement {} diff --git a/modules/src/applications/transfer/context.rs b/modules/src/applications/transfer/context.rs new file mode 100644 index 0000000000..13220ca87b --- /dev/null +++ b/modules/src/applications/transfer/context.rs @@ -0,0 +1,348 @@ +use sha2::{Digest, Sha256}; +use subtle_encoding::hex; + +use super::error::Error as Ics20Error; +use crate::applications::transfer::acknowledgement::Acknowledgement; +use crate::applications::transfer::events::{AckEvent, AckStatusEvent, RecvEvent, TimeoutEvent}; +use crate::applications::transfer::packet::PacketData; +use crate::applications::transfer::relay::on_ack_packet::process_ack_packet; +use crate::applications::transfer::relay::on_recv_packet::process_recv_packet; +use crate::applications::transfer::relay::on_timeout_packet::process_timeout_packet; +use crate::applications::transfer::{PrefixedCoin, PrefixedDenom, VERSION}; +use crate::core::ics04_channel::channel::{Counterparty, Order}; +use crate::core::ics04_channel::context::{ChannelKeeper, ChannelReader}; +use crate::core::ics04_channel::msgs::acknowledgement::Acknowledgement as GenericAcknowledgement; +use crate::core::ics04_channel::packet::Packet; +use crate::core::ics04_channel::Version; +use crate::core::ics05_port::context::PortReader; +use crate::core::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; +use crate::core::ics26_routing::context::{ModuleOutputBuilder, OnRecvPacketAck}; +use crate::prelude::*; +use crate::signer::Signer; + +pub trait Ics20Keeper: + ChannelKeeper + BankKeeper::AccountId> +{ + type AccountId; +} + +pub trait Ics20Reader: ChannelReader + PortReader { + type AccountId: TryFrom; + + /// get_port returns the portID for the transfer module. + fn get_port(&self) -> Result; + + /// Returns the escrow account id for a port and channel combination + fn get_channel_escrow_address( + &self, + port_id: &PortId, + channel_id: ChannelId, + ) -> Result<::AccountId, Ics20Error> { + let hash = cosmos_adr028_escrow_address(port_id, channel_id); + String::from_utf8(hex::encode_upper(hash)) + .expect("hex encoded bytes are not valid UTF8") + .parse::() + .map_err(Ics20Error::signer)? + .try_into() + .map_err(|_| Ics20Error::parse_account_failure()) + } + + /// Returns true iff send is enabled. + fn is_send_enabled(&self) -> bool; + + /// Returns true iff receive is enabled. + fn is_receive_enabled(&self) -> bool; + + /// Returns a hash of the prefixed denom. + /// Implement only if the host chain supports hashed denominations. + fn denom_hash_string(&self, _denom: &PrefixedDenom) -> Option { + None + } +} + +// https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-028-public-key-addresses.md +fn cosmos_adr028_escrow_address(port_id: &PortId, channel_id: ChannelId) -> Vec { + let contents = format!("{}/{}", port_id, channel_id); + + let mut hasher = Sha256::new(); + hasher.update(VERSION.as_bytes()); + hasher.update([0]); + hasher.update(contents.as_bytes()); + + let mut hash = hasher.finalize().to_vec(); + hash.truncate(20); + hash +} + +pub trait BankKeeper { + type AccountId; + + /// This function should enable sending ibc fungible tokens from one account to another + fn send_coins( + &mut self, + from: &Self::AccountId, + to: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error>; + + /// This function to enable minting ibc tokens to a user account + fn mint_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error>; + + /// This function should enable burning of minted tokens in a user account + fn burn_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error>; +} + +/// Captures all the dependencies which the ICS20 module requires to be able to dispatch and +/// process IBC messages. +pub trait Ics20Context: + Ics20Keeper::AccountId> + + Ics20Reader::AccountId> +{ + type AccountId: TryFrom; +} + +fn validate_transfer_channel_params( + ctx: &mut impl Ics20Context, + order: Order, + port_id: &PortId, + channel_id: &ChannelId, + version: &Version, +) -> Result<(), Ics20Error> { + if channel_id.sequence() > (u32::MAX as u64) { + return Err(Ics20Error::chan_seq_exceeds_limit(channel_id.sequence())); + } + + if order != Order::Unordered { + return Err(Ics20Error::channel_not_unordered(order)); + } + + let bound_port = ctx.get_port()?; + if port_id != &bound_port { + return Err(Ics20Error::invalid_port(port_id.clone(), bound_port)); + } + + if version != &Version::ics20() { + return Err(Ics20Error::invalid_version(version.clone())); + } + + Ok(()) +} + +fn validate_counterparty_version(counterparty_version: &Version) -> Result<(), Ics20Error> { + if counterparty_version == &Version::ics20() { + Ok(()) + } else { + Err(Ics20Error::invalid_counterparty_version( + counterparty_version.clone(), + )) + } +} + +#[allow(clippy::too_many_arguments)] +pub fn on_chan_open_init( + ctx: &mut impl Ics20Context, + _output: &mut ModuleOutputBuilder, + order: Order, + _connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &ChannelId, + _counterparty: &Counterparty, + version: &Version, +) -> Result<(), Ics20Error> { + validate_transfer_channel_params(ctx, order, port_id, channel_id, version) +} + +#[allow(clippy::too_many_arguments)] +pub fn on_chan_open_try( + ctx: &mut impl Ics20Context, + _output: &mut ModuleOutputBuilder, + order: Order, + _connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &ChannelId, + _counterparty: &Counterparty, + version: &Version, + counterparty_version: &Version, +) -> Result { + validate_transfer_channel_params(ctx, order, port_id, channel_id, version)?; + validate_counterparty_version(counterparty_version)?; + Ok(Version::ics20()) +} + +pub fn on_chan_open_ack( + _ctx: &mut impl Ics20Context, + _output: &mut ModuleOutputBuilder, + _port_id: &PortId, + _channel_id: &ChannelId, + counterparty_version: &Version, +) -> Result<(), Ics20Error> { + validate_counterparty_version(counterparty_version)?; + Ok(()) +} + +pub fn on_chan_open_confirm( + _ctx: &mut impl Ics20Context, + _output: &mut ModuleOutputBuilder, + _port_id: &PortId, + _channel_id: &ChannelId, +) -> Result<(), Ics20Error> { + Ok(()) +} + +pub fn on_chan_close_init( + _ctx: &mut impl Ics20Context, + _output: &mut ModuleOutputBuilder, + _port_id: &PortId, + _channel_id: &ChannelId, +) -> Result<(), Ics20Error> { + Err(Ics20Error::cant_close_channel()) +} + +pub fn on_chan_close_confirm( + _ctx: &mut impl Ics20Context, + _output: &mut ModuleOutputBuilder, + _port_id: &PortId, + _channel_id: &ChannelId, +) -> Result<(), Ics20Error> { + Ok(()) +} + +pub fn on_recv_packet( + ctx: &Ctx, + output: &mut ModuleOutputBuilder, + packet: &Packet, + _relayer: &Signer, +) -> OnRecvPacketAck { + let data = match serde_json::from_slice::(&packet.data) { + Ok(data) => data, + Err(_) => { + return OnRecvPacketAck::Failed(Box::new(Acknowledgement::Error( + Ics20Error::packet_data_deserialization().to_string(), + ))) + } + }; + + let ack = match process_recv_packet(ctx, output, packet, data.clone()) { + Ok(write_fn) => OnRecvPacketAck::Successful(Box::new(Acknowledgement::success()), write_fn), + Err(e) => OnRecvPacketAck::Failed(Box::new(Acknowledgement::from_error(e))), + }; + + let recv_event = RecvEvent { + receiver: data.receiver, + denom: data.token.denom, + amount: data.token.amount, + success: ack.is_successful(), + }; + output.emit(recv_event.into()); + + ack +} + +pub fn on_acknowledgement_packet( + ctx: &mut impl Ics20Context, + output: &mut ModuleOutputBuilder, + packet: &Packet, + acknowledgement: &GenericAcknowledgement, + _relayer: &Signer, +) -> Result<(), Ics20Error> { + let data = serde_json::from_slice::(&packet.data) + .map_err(|_| Ics20Error::packet_data_deserialization())?; + + let acknowledgement = serde_json::from_slice::(acknowledgement.as_ref()) + .map_err(|_| Ics20Error::ack_deserialization())?; + + process_ack_packet(ctx, packet, &data, &acknowledgement)?; + + let ack_event = AckEvent { + receiver: data.receiver, + denom: data.token.denom, + amount: data.token.amount, + acknowledgement: acknowledgement.clone(), + }; + output.emit(ack_event.into()); + output.emit(AckStatusEvent { acknowledgement }.into()); + + Ok(()) +} + +pub fn on_timeout_packet( + ctx: &mut impl Ics20Context, + output: &mut ModuleOutputBuilder, + packet: &Packet, + _relayer: &Signer, +) -> Result<(), Ics20Error> { + let data = serde_json::from_slice::(&packet.data) + .map_err(|_| Ics20Error::packet_data_deserialization())?; + + process_timeout_packet(ctx, packet, &data)?; + + let timeout_event = TimeoutEvent { + refund_receiver: data.sender, + refund_denom: data.token.denom, + refund_amount: data.token.amount, + }; + output.emit(timeout_event.into()); + + Ok(()) +} + +#[cfg(test)] +pub(crate) mod test { + use subtle_encoding::bech32; + + use crate::applications::transfer::context::cosmos_adr028_escrow_address; + use crate::applications::transfer::error::Error as Ics20Error; + use crate::applications::transfer::msgs::transfer::MsgTransfer; + use crate::applications::transfer::relay::send_transfer::send_transfer; + use crate::applications::transfer::PrefixedCoin; + use crate::core::ics04_channel::error::Error; + use crate::handler::HandlerOutputBuilder; + use crate::prelude::*; + use crate::test_utils::DummyTransferModule; + + pub(crate) fn deliver( + ctx: &mut DummyTransferModule, + output: &mut HandlerOutputBuilder<()>, + msg: MsgTransfer, + ) -> Result<(), Error> { + send_transfer(ctx, output, msg).map_err(|e: Ics20Error| Error::app_module(e.to_string())) + } + + #[test] + fn test_cosmos_escrow_address() { + fn assert_eq_escrow_address(port_id: &str, channel_id: &str, address: &str) { + let port_id = port_id.parse().unwrap(); + let channel_id = channel_id.parse().unwrap(); + let gen_address = { + let addr = cosmos_adr028_escrow_address(&port_id, channel_id); + bech32::encode("cosmos", addr) + }; + assert_eq!(gen_address, address.to_owned()) + } + + // addresses obtained using `gaiad query ibc-transfer escrow-address [port-id] [channel-id]` + assert_eq_escrow_address( + "transfer", + "channel-141", + "cosmos1x54ltnyg88k0ejmk8ytwrhd3ltm84xehrnlslf", + ); + assert_eq_escrow_address( + "transfer", + "channel-207", + "cosmos1ju6tlfclulxumtt2kglvnxduj5d93a64r5czge", + ); + assert_eq_escrow_address( + "transfer", + "channel-187", + "cosmos177x69sver58mcfs74x6dg0tv6ls4s3xmmcaw53", + ); + } +} diff --git a/modules/src/applications/transfer/denom.rs b/modules/src/applications/transfer/denom.rs new file mode 100644 index 0000000000..998f731a40 --- /dev/null +++ b/modules/src/applications/transfer/denom.rs @@ -0,0 +1,473 @@ +use core::fmt; +use core::str::FromStr; + +use derive_more::{Display, From, Into}; +use ibc_proto::cosmos::base::v1beta1::Coin as RawCoin; +use ibc_proto::ibc::applications::transfer::v1::DenomTrace as RawDenomTrace; +use serde::{Deserialize, Serialize}; + +use super::error::Error; +use crate::bigint::U256; +use crate::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::prelude::*; +use crate::serializers::serde_string; + +/// A `Coin` type with fully qualified `PrefixedDenom`. +pub type PrefixedCoin = Coin; + +/// A `Coin` type with an unprefixed denomination. +pub type BaseCoin = Coin; + +/// Base denomination type +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, Display)] +#[serde(transparent)] +pub struct BaseDenom(String); + +impl FromStr for BaseDenom { + type Err = Error; + + fn from_str(s: &str) -> Result { + if s.trim().is_empty() { + Err(Error::empty_base_denom()) + } else { + Ok(BaseDenom(s.to_owned())) + } + } +} + +#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] +pub struct TracePrefix { + port_id: PortId, + channel_id: ChannelId, +} + +impl TracePrefix { + pub fn new(port_id: PortId, channel_id: ChannelId) -> Self { + Self { + port_id, + channel_id, + } + } +} + +impl fmt::Display for TracePrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.port_id, self.channel_id) + } +} + +/// A full trace path modelled as a collection of `TracePrefix`s. +// Internally, the `TracePath` is modelled as a `Vec` but with the order reversed, i.e. +// "transfer/channel-0/transfer/channel-1/uatom" => `["transfer/channel-1", "transfer/channel-0"]` +// This is done for ease of addition/removal of prefixes. +#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, From)] +pub struct TracePath(Vec); + +impl TracePath { + /// Returns true iff this path starts with the specified prefix + pub fn starts_with(&self, prefix: &TracePrefix) -> bool { + self.0.last().map(|p| p == prefix).unwrap_or(false) + } + + /// Removes the specified prefix from the path if there is a match, otherwise does nothing. + pub fn remove_prefix(&mut self, prefix: &TracePrefix) { + if self.starts_with(prefix) { + self.0.pop(); + } + } + + /// Adds the specified prefix to the path. + pub fn add_prefix(&mut self, prefix: TracePrefix) { + self.0.push(prefix) + } + + /// Returns true if the path is empty and false otherwise. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl<'a> TryFrom> for TracePath { + type Error = Error; + + fn try_from(v: Vec<&'a str>) -> Result { + if v.len() % 2 != 0 { + return Err(Error::invalid_trace_length(v.len())); + } + + let mut trace = vec![]; + let id_pairs = v.chunks_exact(2).map(|paths| (paths[0], paths[1])); + for (pos, (port_id, channel_id)) in id_pairs.rev().enumerate() { + let port_id = + PortId::from_str(port_id).map_err(|e| Error::invalid_trace_port_id(pos, e))?; + let channel_id = ChannelId::from_str(channel_id) + .map_err(|e| Error::invalid_trace_channel_id(pos, e))?; + trace.push(TracePrefix { + port_id, + channel_id, + }); + } + + Ok(trace.into()) + } +} + +impl FromStr for TracePath { + type Err = Error; + + fn from_str(s: &str) -> Result { + let parts = { + let parts: Vec<&str> = s.split('/').collect(); + if parts.len() == 1 && parts[0].trim().is_empty() { + vec![] + } else { + parts + } + }; + parts.try_into() + } +} + +impl fmt::Display for TracePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let path = self + .0 + .iter() + .rev() + .map(|prefix| prefix.to_string()) + .collect::>() + .join("/"); + write!(f, "{}", path) + } +} + +/// A type that contains the base denomination for ICS20 and the source tracing information path. +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct PrefixedDenom { + /// A series of `{port-id}/{channel-id}`s for tracing the source of the token. + #[serde(with = "serde_string")] + trace_path: TracePath, + /// Base denomination of the relayed fungible token. + base_denom: BaseDenom, +} + +impl PrefixedDenom { + /// Removes the specified prefix from the trace path if there is a match, otherwise does nothing. + pub fn remove_trace_prefix(&mut self, prefix: &TracePrefix) { + self.trace_path.remove_prefix(prefix) + } + + /// Adds the specified prefix to the trace path. + pub fn add_trace_prefix(&mut self, prefix: TracePrefix) { + self.trace_path.add_prefix(prefix) + } +} + +/// Returns true if the denomination originally came from the sender chain and +/// false otherwise. +/// +/// Note: It is better to think of the "source" chain as the chain that +/// escrows/unescrows the token, while the other chain mints/burns the tokens, +/// respectively. A chain being the "source" of a token does NOT mean it is the +/// original creator of the token (e.g. "uatom"), as "source" might suggest. +/// +/// This means that in any given transfer, a chain can very well be the source +/// of a token of which it is not the creator. For example, let +/// +/// A: sender chain in this transfer, port "transfer" and channel "c2b" (to B) +/// B: receiver chain in this transfer, port "transfer" and channel "c2a" (to A) +/// token denom: "transfer/someOtherChannel/someDenom" +/// +/// A, initiator of the transfer, needs to figure out if it should escrow the +/// tokens, or burn them. If B had originally sent the token to A in a previous +/// transfer, then A would have stored the token as "transfer/c2b/someDenom". +/// Now, A is sending to B, so to check if B is the source of the token, we need +/// to check if the token starts with "transfer/c2b". In this example, it +/// doesn't, so the token doesn't originate from B. A is considered the source, +/// even though it is not the creator of the token. Specifically, the token was +/// created by the chain at the other end of A's port "transfer" and channel +/// "someOtherChannel". +pub fn is_sender_chain_source( + source_port: PortId, + source_channel: ChannelId, + denom: &PrefixedDenom, +) -> bool { + !is_receiver_chain_source(source_port, source_channel, denom) +} + +/// Returns true if the denomination originally came from the receiving chain and false otherwise. +pub fn is_receiver_chain_source( + source_port: PortId, + source_channel: ChannelId, + denom: &PrefixedDenom, +) -> bool { + // For example, let + // A: sender chain in this transfer, port "transfer" and channel "c2b" (to B) + // B: receiver chain in this transfer, port "transfer" and channel "c2a" (to A) + // + // If B had originally sent the token in a previous tranfer, then A would have stored the token as + // "transfer/c2b/{token_denom}". Now, A is sending to B, so to check if B is the source of the token, + // we need to check if the token starts with "transfer/c2b". + let prefix = TracePrefix::new(source_port, source_channel); + denom.trace_path.starts_with(&prefix) +} + +impl FromStr for PrefixedDenom { + type Err = Error; + + fn from_str(s: &str) -> Result { + let mut parts: Vec<&str> = s.split('/').collect(); + let last_part = parts.pop().expect("split() returned an empty iterator"); + + let (base_denom, trace_path) = { + if last_part == s { + (BaseDenom::from_str(s)?, TracePath::default()) + } else { + let base_denom = BaseDenom::from_str(last_part)?; + let trace_path = TracePath::try_from(parts)?; + (base_denom, trace_path) + } + }; + + Ok(Self { + trace_path, + base_denom, + }) + } +} + +impl TryFrom for PrefixedDenom { + type Error = Error; + + fn try_from(value: RawDenomTrace) -> Result { + let base_denom = BaseDenom::from_str(&value.base_denom)?; + let trace_path = TracePath::from_str(&value.path)?; + Ok(Self { + trace_path, + base_denom, + }) + } +} + +impl From for RawDenomTrace { + fn from(value: PrefixedDenom) -> Self { + Self { + path: value.trace_path.to_string(), + base_denom: value.base_denom.to_string(), + } + } +} + +impl From for PrefixedDenom { + fn from(denom: BaseDenom) -> Self { + Self { + trace_path: Default::default(), + base_denom: denom, + } + } +} + +impl fmt::Display for PrefixedDenom { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.trace_path.0.is_empty() { + write!(f, "{}", self.base_denom) + } else { + write!(f, "{}/{}", self.trace_path, self.base_denom) + } + } +} + +/// A type for representing token transfer amounts. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Display, From, Into)] +pub struct Amount(U256); + +impl Amount { + pub fn checked_add(self, rhs: Self) -> Option { + self.0.checked_add(rhs.0).map(Self) + } + + pub fn checked_sub(self, rhs: Self) -> Option { + self.0.checked_sub(rhs.0).map(Self) + } +} + +impl FromStr for Amount { + type Err = Error; + + fn from_str(s: &str) -> Result { + let amount = U256::from_str_radix(s, 10).map_err(Error::invalid_amount)?; + Ok(Self(amount)) + } +} + +impl From for Amount { + fn from(v: u64) -> Self { + Self(v.into()) + } +} + +/// Coin defines a token with a denomination and an amount. +#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub struct Coin { + /// Denomination + pub denom: D, + /// Amount + #[serde(with = "serde_string")] + pub amount: Amount, +} + +impl TryFrom for Coin +where + Error: From<::Err>, +{ + type Error = Error; + + fn try_from(proto: RawCoin) -> Result, Self::Error> { + let denom = D::from_str(&proto.denom)?; + let amount = Amount::from_str(&proto.amount)?; + Ok(Self { denom, amount }) + } +} + +impl From> for RawCoin { + fn from(coin: Coin) -> RawCoin { + RawCoin { + denom: coin.denom.to_string(), + amount: coin.amount.to_string(), + } + } +} + +impl From for PrefixedCoin { + fn from(coin: BaseCoin) -> PrefixedCoin { + PrefixedCoin { + denom: coin.denom.into(), + amount: coin.amount, + } + } +} + +impl fmt::Display for PrefixedCoin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}-{}", self.amount, self.denom) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_denom_validation() -> Result<(), Error> { + assert!(BaseDenom::from_str("").is_err(), "empty base denom"); + assert!(BaseDenom::from_str("uatom").is_ok(), "valid base denom"); + assert!(PrefixedDenom::from_str("").is_err(), "empty denom trace"); + assert!( + PrefixedDenom::from_str("transfer/channel-0/").is_err(), + "empty base denom with trace" + ); + assert!(PrefixedDenom::from_str("/uatom").is_err(), "empty prefix"); + assert!(PrefixedDenom::from_str("//uatom").is_err(), "empty ids"); + assert!( + PrefixedDenom::from_str("transfer/").is_err(), + "single trace" + ); + assert!( + PrefixedDenom::from_str("transfer/atom").is_err(), + "single trace with base denom" + ); + assert!( + PrefixedDenom::from_str("transfer/channel-0/uatom").is_ok(), + "valid single trace info" + ); + assert!( + PrefixedDenom::from_str("transfer/channel-0/transfer/channel-1/uatom").is_ok(), + "valid multiple trace info" + ); + assert!( + PrefixedDenom::from_str("(transfer)/channel-0/uatom").is_err(), + "invalid port" + ); + assert!( + PrefixedDenom::from_str("transfer/(channel-0)/uatom").is_err(), + "invalid channel" + ); + + Ok(()) + } + + #[test] + fn test_denom_trace() -> Result<(), Error> { + assert_eq!( + PrefixedDenom::from_str("transfer/channel-0/uatom")?, + PrefixedDenom { + trace_path: "transfer/channel-0".parse()?, + base_denom: "uatom".parse()? + }, + "valid single trace info" + ); + assert_eq!( + PrefixedDenom::from_str("transfer/channel-0/transfer/channel-1/uatom")?, + PrefixedDenom { + trace_path: "transfer/channel-0/transfer/channel-1".parse()?, + base_denom: "uatom".parse()? + }, + "valid multiple trace info" + ); + + Ok(()) + } + + #[test] + fn test_denom_serde() -> Result<(), Error> { + let dt_str = "transfer/channel-0/uatom"; + let dt = PrefixedDenom::from_str(dt_str)?; + assert_eq!(dt.to_string(), dt_str, "valid single trace info"); + + let dt_str = "transfer/channel-0/transfer/channel-1/uatom"; + let dt = PrefixedDenom::from_str(dt_str)?; + assert_eq!(dt.to_string(), dt_str, "valid multiple trace info"); + + Ok(()) + } + + #[test] + fn test_trace_path() -> Result<(), Error> { + assert!(TracePath::from_str("").is_ok(), "empty trace path"); + assert!( + TracePath::from_str("transfer/uatom").is_err(), + "invalid trace path: bad ChannelId" + ); + assert!( + TracePath::from_str("transfer//uatom").is_err(), + "malformed trace path: missing ChannelId" + ); + assert!( + TracePath::from_str("transfer/channel-0/").is_err(), + "malformed trace path: trailing delimiter" + ); + + let prefix_1 = TracePrefix::new("transfer".parse().unwrap(), "channel-1".parse().unwrap()); + let prefix_2 = TracePrefix::new("transfer".parse().unwrap(), "channel-0".parse().unwrap()); + let mut trace_path = TracePath(vec![prefix_1.clone()]); + + trace_path.add_prefix(prefix_2.clone()); + assert_eq!( + TracePath::from_str("transfer/channel-0/transfer/channel-1")?, + trace_path + ); + assert_eq!( + TracePath(vec![prefix_1.clone(), prefix_2.clone()]), + trace_path + ); + + trace_path.remove_prefix(&prefix_2); + assert_eq!(TracePath::from_str("transfer/channel-1")?, trace_path); + assert_eq!(TracePath(vec![prefix_1.clone()]), trace_path); + + trace_path.remove_prefix(&prefix_1); + assert!(trace_path.is_empty()); + + Ok(()) + } +} diff --git a/modules/src/applications/transfer/error.rs b/modules/src/applications/transfer/error.rs new file mode 100644 index 0000000000..c9f71fdc33 --- /dev/null +++ b/modules/src/applications/transfer/error.rs @@ -0,0 +1,140 @@ +use alloc::string::FromUtf8Error; + +use flex_error::{define_error, DisplayOnly, TraceError}; +use subtle_encoding::Error as EncodingError; +use tendermint_proto::Error as TendermintProtoError; +use uint::FromStrRadixErr; + +use crate::core::ics04_channel::channel::Order; +use crate::core::ics04_channel::error as channel_error; +use crate::core::ics04_channel::Version; +use crate::core::ics24_host::error::ValidationError; +use crate::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::prelude::*; +use crate::signer::SignerError; + +define_error! { + #[derive(Debug, PartialEq, Eq)] + Error { + UnknowMessageTypeUrl + { url: String } + | e | { format_args!("unrecognized ICS-20 transfer message type URL {0}", e.url) }, + + Ics04Channel + [ channel_error::Error ] + |_ | { "Ics04 channel error" }, + + DestinationChannelNotFound + { port_id: PortId, channel_id: ChannelId } + | e | { format_args!("destination channel not found in the counterparty of port_id {0} and channel_id {1} ", e.port_id, e.channel_id) }, + + InvalidPortId + { context: String } + [ ValidationError ] + | _ | { "invalid port identifier" }, + + InvalidChannelId + { context: String } + [ ValidationError ] + | _ | { "invalid channel identifier" }, + + InvalidPacketTimeoutHeight + { context: String } + | _ | { "invalid packet timeout height value" }, + + InvalidPacketTimeoutTimestamp + { timestamp: u64 } + | _ | { "invalid packet timeout timestamp value" }, + + Utf8 + [ DisplayOnly ] + | _ | { "utf8 decoding error" }, + + EmptyBaseDenom + |_| { "base denomination is empty" }, + + InvalidTracePortId + { pos: usize } + [ ValidationError ] + | e | { format_args!("invalid port id in trace at position: {0}", e.pos) }, + + InvalidTraceChannelId + { pos: usize } + [ ValidationError ] + | e | { format_args!("invalid channel id in trace at position: {0}", e.pos) }, + + InvalidTraceLength + { len: usize } + | e | { format_args!("trace length must be even but got: {0}", e.len) }, + + InvalidAmount + [ TraceError ] + | _ | { "invalid amount" }, + + InvalidToken + | _ | { "invalid token" }, + + Signer + [ SignerError ] + | _ | { "failed to parse signer" }, + + MissingDenomIbcPrefix + | _ | { "missing 'ibc/' prefix in denomination" }, + + MalformedHashDenom + | _ | { "hashed denom must be of the form 'ibc/{Hash}'" }, + + ParseHex + [ TraceError ] + | _ | { "invalid hex string" }, + + ChanSeqExceedsLimit + { sequence: u64 } + | e | { format_args!("channel sequence ({0}) exceeds limit of {1}", e.sequence, u32::MAX) }, + + ChannelNotUnordered + { order: Order } + | e | { format_args!("expected '{0}' channel, got '{1}'", Order::Unordered, e.order) }, + + InvalidVersion + { version: Version } + | e | { format_args!("expected version '{0}', got '{1}'", Version::ics20(), e.version) }, + + InvalidCounterpartyVersion + { version: Version } + | e | { format_args!("expected counterparty version '{0}', got '{1}'", Version::ics20(), e.version) }, + + CantCloseChannel + | _ | { "channel cannot be closed" }, + + PacketDataDeserialization + | _ | { "failed to deserialize packet data" }, + + AckDeserialization + | _ | { "failed to deserialize acknowledgement" }, + + ReceiveDisabled + | _ | { "receive is not enabled" }, + + SendDisabled + | _ | { "send is not enabled" }, + + ParseAccountFailure + | _ | { "failed to parse as AccountId" }, + + InvalidPort + { port_id: PortId, exp_port_id: PortId } + | e | { format_args!("invalid port: '{0}', expected '{1}'", e.port_id, e.exp_port_id) }, + + TraceNotFound + | _ | { "no trace associated with specified hash" }, + + DecodeRawMsg + [ TraceError ] + | _ | { "error decoding raw msg" }, + + UnknownMsgType + { msg_type: String } + | e | { format_args!("unknown msg type: {0}", e.msg_type) }, + } +} diff --git a/modules/src/applications/transfer/events.rs b/modules/src/applications/transfer/events.rs new file mode 100644 index 0000000000..590638f448 --- /dev/null +++ b/modules/src/applications/transfer/events.rs @@ -0,0 +1,172 @@ +use crate::applications::transfer::acknowledgement::Acknowledgement; +use crate::applications::transfer::{Amount, PrefixedDenom, MODULE_ID_STR}; +use crate::events::ModuleEvent; +use crate::prelude::*; +use crate::signer::Signer; + +const EVENT_TYPE_PACKET: &str = "fungible_token_packet"; +const EVENT_TYPE_TIMEOUT: &str = "timeout"; +const EVENT_TYPE_DENOM_TRACE: &str = "denomination_trace"; +const EVENT_TYPE_TRANSFER: &str = "ibc_transfer"; + +pub enum Event { + Recv(RecvEvent), + Ack(AckEvent), + AckStatus(AckStatusEvent), + Timeout(TimeoutEvent), + DenomTrace(DenomTraceEvent), + Transfer(TransferEvent), +} + +pub struct RecvEvent { + pub receiver: Signer, + pub denom: PrefixedDenom, + pub amount: Amount, + pub success: bool, +} + +impl From for ModuleEvent { + fn from(ev: RecvEvent) -> Self { + let RecvEvent { + receiver, + denom, + amount, + success, + } = ev; + Self { + kind: EVENT_TYPE_PACKET.to_string(), + module_name: MODULE_ID_STR.parse().expect("invalid ModuleId"), + attributes: vec![ + ("receiver", receiver).into(), + ("denom", denom).into(), + ("amount", amount).into(), + ("success", success).into(), + ], + } + } +} + +pub struct AckEvent { + pub receiver: Signer, + pub denom: PrefixedDenom, + pub amount: Amount, + pub acknowledgement: Acknowledgement, +} + +impl From for ModuleEvent { + fn from(ev: AckEvent) -> Self { + let AckEvent { + receiver, + denom, + amount, + acknowledgement, + } = ev; + Self { + kind: EVENT_TYPE_PACKET.to_string(), + module_name: MODULE_ID_STR.parse().expect("invalid ModuleId"), + attributes: vec![ + ("receiver", receiver).into(), + ("denom", denom).into(), + ("amount", amount).into(), + ("acknowledgement", acknowledgement).into(), + ], + } + } +} + +pub struct AckStatusEvent { + pub acknowledgement: Acknowledgement, +} + +impl From for ModuleEvent { + fn from(ev: AckStatusEvent) -> Self { + let AckStatusEvent { acknowledgement } = ev; + let mut event = Self { + kind: EVENT_TYPE_PACKET.to_string(), + module_name: MODULE_ID_STR.parse().expect("invalid ModuleId"), + attributes: vec![], + }; + let attr_label = match acknowledgement { + Acknowledgement::Success(_) => "success", + Acknowledgement::Error(_) => "error", + }; + event + .attributes + .push((attr_label, acknowledgement.to_string()).into()); + event + } +} + +pub struct TimeoutEvent { + pub refund_receiver: Signer, + pub refund_denom: PrefixedDenom, + pub refund_amount: Amount, +} + +impl From for ModuleEvent { + fn from(ev: TimeoutEvent) -> Self { + let TimeoutEvent { + refund_receiver, + refund_denom, + refund_amount, + } = ev; + Self { + kind: EVENT_TYPE_TIMEOUT.to_string(), + module_name: MODULE_ID_STR.parse().expect("invalid ModuleId"), + attributes: vec![ + ("refund_receiver", refund_receiver).into(), + ("refund_denom", refund_denom).into(), + ("refund_amount", refund_amount).into(), + ], + } + } +} + +pub struct DenomTraceEvent { + pub trace_hash: Option, + pub denom: PrefixedDenom, +} + +impl From for ModuleEvent { + fn from(ev: DenomTraceEvent) -> Self { + let DenomTraceEvent { trace_hash, denom } = ev; + let mut ev = Self { + kind: EVENT_TYPE_DENOM_TRACE.to_string(), + module_name: MODULE_ID_STR.parse().expect("invalid ModuleId"), + attributes: vec![("denom", denom).into()], + }; + if let Some(hash) = trace_hash { + ev.attributes.push(("trace_hash", hash).into()); + } + ev + } +} + +pub struct TransferEvent { + pub sender: Signer, + pub receiver: Signer, +} + +impl From for ModuleEvent { + fn from(ev: TransferEvent) -> Self { + let TransferEvent { sender, receiver } = ev; + Self { + kind: EVENT_TYPE_TRANSFER.to_string(), + module_name: MODULE_ID_STR.parse().expect("invalid ModuleId"), + attributes: vec![("sender", sender).into(), ("receiver", receiver).into()], + } + } +} + +impl From for ModuleEvent { + fn from(ev: Event) -> Self { + match ev { + Event::Recv(ev) => ev.into(), + Event::Ack(ev) => ev.into(), + Event::AckStatus(ev) => ev.into(), + Event::Timeout(ev) => ev.into(), + Event::DenomTrace(ev) => ev.into(), + Event::Transfer(ev) => ev.into(), + } + } +} diff --git a/modules/src/applications/ics20_fungible_token_transfer/mod.rs b/modules/src/applications/transfer/mod.rs similarity index 66% rename from modules/src/applications/ics20_fungible_token_transfer/mod.rs rename to modules/src/applications/transfer/mod.rs index e28b4e7c55..1a8a87a89a 100644 --- a/modules/src/applications/ics20_fungible_token_transfer/mod.rs +++ b/modules/src/applications/transfer/mod.rs @@ -1,17 +1,23 @@ //! ICS 20: Token Transfer implementation allows for multi-chain denomination handling, which //! constitutes a "fungible token transfer bridge module" between the IBC routing module and an //! asset tracking module. +pub mod acknowledgement; pub mod context; +pub mod denom; pub mod error; +pub mod events; pub mod msgs; -pub mod relay_application_logic; +pub mod packet; +pub mod relay; -mod denom; pub use denom::*; +/// Module identifier for the ICS20 application. +pub const MODULE_ID_STR: &str = "transfer"; + /// The port identifier that the ICS20 applications /// typically bind with. -pub const PORT_ID: &str = "transfer"; +pub const PORT_ID_STR: &str = "transfer"; /// ICS20 application current version. pub const VERSION: &str = "ics20-1"; diff --git a/modules/src/applications/ics20_fungible_token_transfer/msgs.rs b/modules/src/applications/transfer/msgs.rs similarity index 100% rename from modules/src/applications/ics20_fungible_token_transfer/msgs.rs rename to modules/src/applications/transfer/msgs.rs diff --git a/modules/src/applications/ics20_fungible_token_transfer/msgs/transfer.rs b/modules/src/applications/transfer/msgs/transfer.rs similarity index 62% rename from modules/src/applications/ics20_fungible_token_transfer/msgs/transfer.rs rename to modules/src/applications/transfer/msgs/transfer.rs index 60170d2c73..ee9c8afd1f 100644 --- a/modules/src/applications/ics20_fungible_token_transfer/msgs/transfer.rs +++ b/modules/src/applications/transfer/msgs/transfer.rs @@ -2,11 +2,12 @@ use crate::prelude::*; +use ibc_proto::cosmos::base::v1beta1::Coin; +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::applications::transfer::v1::MsgTransfer as RawMsgTransfer; use tendermint_proto::Protobuf; -use ibc_proto::ibc::apps::transfer::v1::MsgTransfer as RawMsgTransfer; - -use crate::applications::ics20_fungible_token_transfer::error::Error; +use crate::applications::transfer::error::Error; use crate::core::ics02_client::height::Height; use crate::core::ics24_host::identifier::{ChannelId, PortId}; use crate::signer::Signer; @@ -15,15 +16,21 @@ use crate::tx_msg::Msg; pub const TYPE_URL: &str = "/ibc.applications.transfer.v1.MsgTransfer"; -/// Message definition for the "packet receiving" datagram. +/// Message used to build an ICS20 token transfer packet. +/// +/// Note that this message is not a packet yet, as it lacks the proper sequence +/// number, and destination port/channel. This is by design. The sender of the +/// packet, which might be the user of a command line application, should only +/// have to specify the information related to the transfer of the token, and +/// let the library figure out how to build the packet properly. #[derive(Clone, Debug, PartialEq)] -pub struct MsgTransfer { +pub struct MsgTransfer { /// the port on which the packet will be sent pub source_port: PortId, /// the channel by which the packet will be sent pub source_channel: ChannelId, /// the tokens to be transferred - pub token: Option, + pub token: C, /// the sender address pub sender: Signer, /// the recipient address on the destination chain @@ -49,8 +56,6 @@ impl Msg for MsgTransfer { } } -impl Protobuf for MsgTransfer {} - impl TryFrom for MsgTransfer { type Error = Error; @@ -74,9 +79,9 @@ impl TryFrom for MsgTransfer { .source_channel .parse() .map_err(|e| Error::invalid_channel_id(raw_msg.source_channel.clone(), e))?, - token: raw_msg.token, - sender: raw_msg.sender.into(), - receiver: raw_msg.receiver.into(), + token: raw_msg.token.ok_or_else(Error::invalid_token)?, + sender: raw_msg.sender.parse().map_err(Error::signer)?, + receiver: raw_msg.receiver.parse().map_err(Error::signer)?, timeout_height, timeout_timestamp, }) @@ -88,7 +93,7 @@ impl From for RawMsgTransfer { RawMsgTransfer { source_port: domain_msg.source_port.to_string(), source_channel: domain_msg.source_channel.to_string(), - token: domain_msg.token, + token: Some(domain_msg.token), sender: domain_msg.sender.to_string(), receiver: domain_msg.receiver.to_string(), timeout_height: Some(domain_msg.timeout_height.into()), @@ -97,30 +102,59 @@ impl From for RawMsgTransfer { } } +impl Protobuf for MsgTransfer {} + +impl TryFrom for MsgTransfer { + type Error = Error; + + fn try_from(raw: Any) -> Result { + match raw.type_url.as_str() { + TYPE_URL => MsgTransfer::decode_vec(&raw.value).map_err(Error::decode_raw_msg), + _ => Err(Error::unknown_msg_type(raw.type_url)), + } + } +} + +impl From for Any { + fn from(msg: MsgTransfer) -> Self { + Self { + type_url: TYPE_URL.to_string(), + value: msg + .encode_vec() + .expect("encoding to `Any` from `MsgTranfer`"), + } + } +} + #[cfg(test)] pub mod test_util { use core::ops::Add; use core::time::Duration; + use super::MsgTransfer; + use crate::bigint::U256; + use crate::signer::Signer; use crate::{ + applications::transfer::{BaseCoin, PrefixedCoin}, core::ics24_host::identifier::{ChannelId, PortId}, - test_utils::get_dummy_account_id, + test_utils::get_dummy_bech32_account, timestamp::Timestamp, Height, }; - use super::MsgTransfer; - - // Returns a dummy `RawMsgTransfer`, for testing only! - pub fn get_dummy_msg_transfer(height: u64) -> MsgTransfer { - let id = get_dummy_account_id(); - + // Returns a dummy ICS20 `MsgTransfer`, for testing only! + pub fn get_dummy_msg_transfer(height: u64) -> MsgTransfer { + let address: Signer = get_dummy_bech32_account().as_str().parse().unwrap(); MsgTransfer { source_port: PortId::default(), source_channel: ChannelId::default(), - token: None, - sender: id.clone(), - receiver: id, + token: BaseCoin { + denom: "uatom".parse().unwrap(), + amount: U256::from(10).into(), + } + .into(), + sender: address.clone(), + receiver: address, timeout_timestamp: Timestamp::now().add(Duration::from_secs(10)).unwrap(), timeout_height: Height { revision_number: 0, diff --git a/modules/src/applications/transfer/packet.rs b/modules/src/applications/transfer/packet.rs new file mode 100644 index 0000000000..643ee5e465 --- /dev/null +++ b/modules/src/applications/transfer/packet.rs @@ -0,0 +1,43 @@ +use alloc::string::ToString; +use core::convert::TryFrom; +use core::str::FromStr; + +use ibc_proto::ibc::applications::transfer::v2::FungibleTokenPacketData as RawPacketData; +use serde::{Deserialize, Serialize}; + +use super::error::Error; +use super::{Amount, PrefixedCoin, PrefixedDenom}; +use crate::signer::Signer; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PacketData { + pub token: PrefixedCoin, + pub sender: Signer, + pub receiver: Signer, +} + +impl TryFrom for PacketData { + type Error = Error; + + fn try_from(raw_pkt_data: RawPacketData) -> Result { + // This denom may be prefixed or unprefixed. + let denom = PrefixedDenom::from_str(&raw_pkt_data.denom)?; + let amount = Amount::from_str(&raw_pkt_data.amount)?; + Ok(Self { + token: PrefixedCoin { denom, amount }, + sender: raw_pkt_data.sender.parse().map_err(Error::signer)?, + receiver: raw_pkt_data.receiver.parse().map_err(Error::signer)?, + }) + } +} + +impl From for RawPacketData { + fn from(pkt_data: PacketData) -> Self { + Self { + denom: pkt_data.token.denom.to_string(), + amount: pkt_data.token.amount.to_string(), + sender: pkt_data.sender.to_string(), + receiver: pkt_data.receiver.to_string(), + } + } +} diff --git a/modules/src/applications/transfer/relay.rs b/modules/src/applications/transfer/relay.rs new file mode 100644 index 0000000000..63fb15103b --- /dev/null +++ b/modules/src/applications/transfer/relay.rs @@ -0,0 +1,40 @@ +//! This module implements the processing logic for ICS20 (token transfer) message. +use crate::applications::transfer::context::Ics20Context; +use crate::applications::transfer::error::Error as Ics20Error; +use crate::applications::transfer::is_sender_chain_source; +use crate::applications::transfer::packet::PacketData; +use crate::core::ics04_channel::packet::Packet; +use crate::prelude::*; + +pub mod on_ack_packet; +pub mod on_recv_packet; +pub mod on_timeout_packet; +pub mod send_transfer; + +fn refund_packet_token( + ctx: &mut impl Ics20Context, + packet: &Packet, + data: &PacketData, +) -> Result<(), Ics20Error> { + let sender = data + .sender + .clone() + .try_into() + .map_err(|_| Ics20Error::parse_account_failure())?; + + if is_sender_chain_source( + packet.source_port.clone(), + packet.source_channel, + &data.token.denom, + ) { + // unescrow tokens back to sender + let escrow_address = + ctx.get_channel_escrow_address(&packet.source_port, packet.source_channel)?; + + ctx.send_coins(&escrow_address, &sender, &data.token) + } + // mint vouchers back to sender + else { + ctx.mint_coins(&sender, &data.token) + } +} diff --git a/modules/src/applications/transfer/relay/on_ack_packet.rs b/modules/src/applications/transfer/relay/on_ack_packet.rs new file mode 100644 index 0000000000..a254de1fc9 --- /dev/null +++ b/modules/src/applications/transfer/relay/on_ack_packet.rs @@ -0,0 +1,19 @@ +use crate::applications::transfer::acknowledgement::Acknowledgement; +use crate::applications::transfer::context::Ics20Context; +use crate::applications::transfer::error::Error as Ics20Error; +use crate::applications::transfer::packet::PacketData; +use crate::applications::transfer::relay::refund_packet_token; +use crate::core::ics04_channel::packet::Packet; + +pub fn process_ack_packet( + ctx: &mut impl Ics20Context, + packet: &Packet, + data: &PacketData, + ack: &Acknowledgement, +) -> Result<(), Ics20Error> { + if matches!(ack, Acknowledgement::Error(_)) { + refund_packet_token(ctx, packet, data)?; + } + + Ok(()) +} diff --git a/modules/src/applications/transfer/relay/on_recv_packet.rs b/modules/src/applications/transfer/relay/on_recv_packet.rs new file mode 100644 index 0000000000..f6069ee10a --- /dev/null +++ b/modules/src/applications/transfer/relay/on_recv_packet.rs @@ -0,0 +1,68 @@ +use crate::applications::transfer::context::Ics20Context; +use crate::applications::transfer::error::Error as Ics20Error; +use crate::applications::transfer::events::DenomTraceEvent; +use crate::applications::transfer::packet::PacketData; +use crate::applications::transfer::{is_receiver_chain_source, TracePrefix}; +use crate::core::ics04_channel::packet::Packet; +use crate::core::ics26_routing::context::{ModuleOutputBuilder, WriteFn}; +use crate::prelude::*; + +pub fn process_recv_packet( + ctx: &Ctx, + output: &mut ModuleOutputBuilder, + packet: &Packet, + data: PacketData, +) -> Result, Ics20Error> { + if !ctx.is_receive_enabled() { + return Err(Ics20Error::receive_disabled()); + } + + let receiver_account = data + .receiver + .clone() + .try_into() + .map_err(|_| Ics20Error::parse_account_failure())?; + + if is_receiver_chain_source( + packet.source_port.clone(), + packet.source_channel, + &data.token.denom, + ) { + // sender chain is not the source, unescrow tokens + let prefix = TracePrefix::new(packet.source_port.clone(), packet.source_channel); + let coin = { + let mut c = data.token; + c.denom.remove_trace_prefix(&prefix); + c + }; + + let escrow_address = + ctx.get_channel_escrow_address(&packet.destination_port, packet.destination_channel)?; + + Ok(Box::new(move |ctx| { + let ctx = ctx.downcast_mut::().unwrap(); + ctx.send_coins(&escrow_address, &receiver_account, &coin) + .map_err(|e| e.to_string()) + })) + } else { + // sender chain is the source, mint vouchers + let prefix = TracePrefix::new(packet.destination_port.clone(), packet.destination_channel); + let coin = { + let mut c = data.token; + c.denom.add_trace_prefix(prefix); + c + }; + + let denom_trace_event = DenomTraceEvent { + trace_hash: ctx.denom_hash_string(&coin.denom), + denom: coin.denom.clone(), + }; + output.emit(denom_trace_event.into()); + + Ok(Box::new(move |ctx| { + let ctx = ctx.downcast_mut::().unwrap(); + ctx.mint_coins(&receiver_account, &coin) + .map_err(|e| e.to_string()) + })) + } +} diff --git a/modules/src/applications/transfer/relay/on_timeout_packet.rs b/modules/src/applications/transfer/relay/on_timeout_packet.rs new file mode 100644 index 0000000000..192a3dd9b6 --- /dev/null +++ b/modules/src/applications/transfer/relay/on_timeout_packet.rs @@ -0,0 +1,13 @@ +use crate::applications::transfer::context::Ics20Context; +use crate::applications::transfer::error::Error as Ics20Error; +use crate::applications::transfer::packet::PacketData; +use crate::applications::transfer::relay::refund_packet_token; +use crate::core::ics04_channel::packet::Packet; + +pub fn process_timeout_packet( + ctx: &mut impl Ics20Context, + packet: &Packet, + data: &PacketData, +) -> Result<(), Ics20Error> { + refund_packet_token(ctx, packet, data) +} diff --git a/modules/src/applications/transfer/relay/send_transfer.rs b/modules/src/applications/transfer/relay/send_transfer.rs new file mode 100644 index 0000000000..6925c6776c --- /dev/null +++ b/modules/src/applications/transfer/relay/send_transfer.rs @@ -0,0 +1,115 @@ +use crate::applications::transfer::context::Ics20Context; +use crate::applications::transfer::error::Error; +use crate::applications::transfer::events::TransferEvent; +use crate::applications::transfer::msgs::transfer::MsgTransfer; +use crate::applications::transfer::packet::PacketData; +use crate::applications::transfer::{is_sender_chain_source, Coin, PrefixedCoin}; +use crate::core::ics04_channel::handler::send_packet::send_packet; +use crate::core::ics04_channel::packet::Packet; +use crate::events::ModuleEvent; +use crate::handler::{HandlerOutput, HandlerOutputBuilder}; +use crate::prelude::*; + +/// This function handles the transfer sending logic. +/// If this method returns an error, the runtime is expected to rollback all state modifications to +/// the `Ctx` caused by all messages from the transaction that this `msg` is a part of. +pub fn send_transfer( + ctx: &mut Ctx, + output: &mut HandlerOutputBuilder<()>, + msg: MsgTransfer, +) -> Result<(), Error> +where + Ctx: Ics20Context, + C: TryInto, +{ + if !ctx.is_send_enabled() { + return Err(Error::send_disabled()); + } + + let source_channel_end = ctx + .channel_end(&(msg.source_port.clone(), msg.source_channel)) + .map_err(Error::ics04_channel)?; + + let destination_port = source_channel_end.counterparty().port_id().clone(); + let destination_channel = *source_channel_end + .counterparty() + .channel_id() + .ok_or_else(|| { + Error::destination_channel_not_found(msg.source_port.clone(), msg.source_channel) + })?; + + // get the next sequence + let sequence = ctx + .get_next_sequence_send(&(msg.source_port.clone(), msg.source_channel)) + .map_err(Error::ics04_channel)?; + + let token = msg.token.try_into().map_err(|_| Error::invalid_token())?; + let denom = token.denom.clone(); + let coin = Coin { + denom: denom.clone(), + amount: token.amount, + }; + + let sender = msg + .sender + .clone() + .try_into() + .map_err(|_| Error::parse_account_failure())?; + + if is_sender_chain_source(msg.source_port.clone(), msg.source_channel, &denom) { + let escrow_address = + ctx.get_channel_escrow_address(&msg.source_port, msg.source_channel)?; + ctx.send_coins(&sender, &escrow_address, &coin)?; + } else { + ctx.burn_coins(&sender, &coin)?; + } + + let data = { + let data = PacketData { + token: coin, + sender: msg.sender.clone(), + receiver: msg.receiver.clone(), + }; + serde_json::to_vec(&data).expect("PacketData's infallible Serialize impl failed") + }; + + let packet = Packet { + sequence, + source_port: msg.source_port, + source_channel: msg.source_channel, + destination_port, + destination_channel, + data, + timeout_height: msg.timeout_height, + timeout_timestamp: msg.timeout_timestamp, + }; + + let HandlerOutput { + result, + log, + events, + } = send_packet(ctx, packet).map_err(Error::ics04_channel)?; + + ctx.store_packet_result(result) + .map_err(Error::ics04_channel)?; + + output.merge_output( + HandlerOutput::builder() + .with_log(log) + .with_events(events) + .with_result(()), + ); + + output.log(format!( + "IBC fungible token transfer: {} --({})--> {}", + msg.sender, token, msg.receiver + )); + + let transfer_event = TransferEvent { + sender: msg.sender, + receiver: msg.receiver, + }; + output.emit(ModuleEvent::from(transfer_event).into()); + + Ok(()) +} diff --git a/relayer/src/util/bigint.rs b/modules/src/bigint.rs similarity index 100% rename from relayer/src/util/bigint.rs rename to modules/src/bigint.rs diff --git a/modules/src/core/ics02_client/error.rs b/modules/src/core/ics02_client/error.rs index 810dc88f7f..fee8717689 100644 --- a/modules/src/core/ics02_client/error.rs +++ b/modules/src/core/ics02_client/error.rs @@ -1,6 +1,8 @@ use crate::prelude::*; use flex_error::{define_error, TraceError}; +use tendermint::Error as TendermintError; +use tendermint_proto::Error as TendermintProtoError; use crate::clients::ics07_tendermint::error::Error as Ics07Error; use crate::core::ics02_client::client_type::ClientType; @@ -8,12 +10,10 @@ use crate::core::ics02_client::height::HeightError; use crate::core::ics23_commitment::error::Error as Ics23Error; use crate::core::ics24_host::error::ValidationError; use crate::core::ics24_host::identifier::ClientId; +use crate::signer::SignerError; use crate::timestamp::Timestamp; use crate::Height; -use tendermint::Error as TendermintError; -use tendermint_proto::Error as TendermintProtoError; - define_error! { #[derive(Debug, PartialEq, Eq)] Error { @@ -268,6 +268,10 @@ define_error! { InvalidAnyConsensusState [ TraceError ] | _ | { "invalid any client consensus state" }, + + Signer + [ SignerError ] + | _ | { "failed to parse signer" }, } } diff --git a/modules/src/core/ics02_client/handler/update_client.rs b/modules/src/core/ics02_client/handler/update_client.rs index 20072e736a..fd4e7a5fb3 100644 --- a/modules/src/core/ics02_client/handler/update_client.rs +++ b/modules/src/core/ics02_client/handler/update_client.rs @@ -437,9 +437,7 @@ mod tests { let block_ref = ctx_b.host_block(client_height); let latest_header: AnyHeader = match block_ref.cloned().map(Into::into).unwrap() { AnyHeader::Tendermint(mut theader) => { - let cons_state = ctx - .latest_consensus_states(&client_id, &client_height) - .clone(); + let cons_state = ctx.latest_consensus_states(&client_id, &client_height); if let AnyConsensusState::Tendermint(tcs) = cons_state { theader.signed_header.header.time = tcs.timestamp; theader.trusted_height = Height::new(1, 11) @@ -475,10 +473,7 @@ mod tests { Update(upd_res) => { assert_eq!(upd_res.client_id, client_id); assert!(!upd_res.client_state.is_frozen()); - assert_eq!( - upd_res.client_state, - ctx.latest_client_states(&client_id).clone() - ); + assert_eq!(upd_res.client_state, ctx.latest_client_states(&client_id)); assert_eq!(upd_res.client_state.latest_height(), msg.header.height(),) } _ => panic!("update handler result has incorrect type"), diff --git a/modules/src/core/ics02_client/msgs/create_client.rs b/modules/src/core/ics02_client/msgs/create_client.rs index 5e9e531986..469bdc27e0 100644 --- a/modules/src/core/ics02_client/msgs/create_client.rs +++ b/modules/src/core/ics02_client/msgs/create_client.rs @@ -72,7 +72,7 @@ impl TryFrom for MsgCreateAnyClient { MsgCreateAnyClient::new( AnyClientState::try_from(raw_client_state)?, AnyConsensusState::try_from(raw_consensus_state)?, - raw.signer.into(), + raw.signer.parse().map_err(Error::signer)?, ) } } diff --git a/modules/src/core/ics02_client/msgs/misbehavior.rs b/modules/src/core/ics02_client/msgs/misbehavior.rs index 6f0ff000e6..648aaf9d2f 100644 --- a/modules/src/core/ics02_client/msgs/misbehavior.rs +++ b/modules/src/core/ics02_client/msgs/misbehavior.rs @@ -1,8 +1,7 @@ use crate::prelude::*; -use tendermint_proto::Protobuf; - use ibc_proto::ibc::core::client::v1::MsgSubmitMisbehaviour as RawMsgSubmitMisbehaviour; +use tendermint_proto::Protobuf; use crate::core::ics02_client::error::Error; use crate::core::ics02_client::misbehaviour::AnyMisbehaviour; @@ -52,7 +51,7 @@ impl TryFrom for MsgSubmitAnyMisbehaviour { .parse() .map_err(Error::invalid_raw_misbehaviour)?, misbehaviour: AnyMisbehaviour::try_from(raw_misbehaviour)?, - signer: raw.signer.into(), + signer: raw.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics02_client/msgs/update_client.rs b/modules/src/core/ics02_client/msgs/update_client.rs index b38875e5ae..b7cdc4c53d 100644 --- a/modules/src/core/ics02_client/msgs/update_client.rs +++ b/modules/src/core/ics02_client/msgs/update_client.rs @@ -60,7 +60,7 @@ impl TryFrom for MsgUpdateAnyClient { .parse() .map_err(Error::invalid_msg_update_client_id)?, header: AnyHeader::try_from(raw_header)?, - signer: raw.signer.into(), + signer: raw.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics02_client/msgs/upgrade_client.rs b/modules/src/core/ics02_client/msgs/upgrade_client.rs index 162e630d00..34ed86ea83 100644 --- a/modules/src/core/ics02_client/msgs/upgrade_client.rs +++ b/modules/src/core/ics02_client/msgs/upgrade_client.rs @@ -110,7 +110,7 @@ impl TryFrom for MsgUpgradeAnyClient { .map_err(Error::invalid_upgrade_client_proof)?, proof_upgrade_consensus_state: RawMerkleProof::try_from(cs_bytes) .map_err(Error::invalid_upgrade_consensus_state_proof)?, - signer: proto_msg.signer.into(), + signer: proto_msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics03_connection/error.rs b/modules/src/core/ics03_connection/error.rs index 7790009220..619f14b2e1 100644 --- a/modules/src/core/ics03_connection/error.rs +++ b/modules/src/core/ics03_connection/error.rs @@ -3,7 +3,9 @@ use crate::core::ics03_connection::version::Version; use crate::core::ics24_host::error::ValidationError; use crate::core::ics24_host::identifier::{ClientId, ConnectionId}; use crate::proofs::ProofError; +use crate::signer::SignerError; use crate::Height; + use flex_error::define_error; define_error! { @@ -90,7 +92,8 @@ define_error! { [ client_error::Error ] | _ | { "error verifying connnection state" }, - InvalidSigner + Signer + [ SignerError ] | _ | { "invalid signer" }, ConnectionNotFound diff --git a/modules/src/core/ics03_connection/msgs/conn_open_ack.rs b/modules/src/core/ics03_connection/msgs/conn_open_ack.rs index 0e70132e4a..60a42712ff 100644 --- a/modules/src/core/ics03_connection/msgs/conn_open_ack.rs +++ b/modules/src/core/ics03_connection/msgs/conn_open_ack.rs @@ -99,7 +99,7 @@ impl TryFrom for MsgConnectionOpenAck { proof_height, ) .map_err(Error::invalid_proof)?, - signer: msg.signer.into(), + signer: msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics03_connection/msgs/conn_open_confirm.rs b/modules/src/core/ics03_connection/msgs/conn_open_confirm.rs index 3044a6b8f2..8a15be9860 100644 --- a/modules/src/core/ics03_connection/msgs/conn_open_confirm.rs +++ b/modules/src/core/ics03_connection/msgs/conn_open_confirm.rs @@ -59,7 +59,7 @@ impl TryFrom for MsgConnectionOpenConfirm { proof_height, ) .map_err(Error::invalid_proof)?, - signer: msg.signer.into(), + signer: msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics03_connection/msgs/conn_open_init.rs b/modules/src/core/ics03_connection/msgs/conn_open_init.rs index 7ce701e946..196fbcf119 100644 --- a/modules/src/core/ics03_connection/msgs/conn_open_init.rs +++ b/modules/src/core/ics03_connection/msgs/conn_open_init.rs @@ -53,7 +53,7 @@ impl TryFrom for MsgConnectionOpenInit { .try_into()?, version: msg.version.map(|version| version.try_into()).transpose()?, delay_period: Duration::from_nanos(msg.delay_period), - signer: msg.signer.into(), + signer: msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics03_connection/msgs/conn_open_try.rs b/modules/src/core/ics03_connection/msgs/conn_open_try.rs index 3bdad32bba..4709733489 100644 --- a/modules/src/core/ics03_connection/msgs/conn_open_try.rs +++ b/modules/src/core/ics03_connection/msgs/conn_open_try.rs @@ -126,7 +126,7 @@ impl TryFrom for MsgConnectionOpenTry { ) .map_err(Error::invalid_proof)?, delay_period: Duration::from_nanos(msg.delay_period), - signer: msg.signer.into(), + signer: msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics04_channel/commitment.rs b/modules/src/core/ics04_channel/commitment.rs index a6e981eb4e..100a989627 100644 --- a/modules/src/core/ics04_channel/commitment.rs +++ b/modules/src/core/ics04_channel/commitment.rs @@ -1,7 +1,9 @@ use crate::prelude::*; +use serde_derive::{Deserialize, Serialize}; + /// Packet commitment -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct PacketCommitment(Vec); impl PacketCommitment { @@ -17,7 +19,7 @@ impl From> for PacketCommitment { } /// Acknowledgement commitment to be stored -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct AcknowledgementCommitment(Vec); impl AcknowledgementCommitment { diff --git a/modules/src/core/ics04_channel/error.rs b/modules/src/core/ics04_channel/error.rs index d9429af2fa..18bf45aa97 100644 --- a/modules/src/core/ics04_channel/error.rs +++ b/modules/src/core/ics04_channel/error.rs @@ -7,6 +7,7 @@ use crate::core::ics24_host::error::ValidationError; use crate::core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; use crate::prelude::*; use crate::proofs::ProofError; +use crate::signer::SignerError; use crate::timestamp::Timestamp; use crate::Height; @@ -56,7 +57,8 @@ define_error! { [ TraceError ] | _ | { "invalid version" }, - InvalidSigner + Signer + [ SignerError ] | _ | { "invalid signer address" }, InvalidProof @@ -337,6 +339,14 @@ define_error! { ImplementationSpecific | _ | { "implementation specific error" }, + + AppModule + { description: String } + | e | { + format_args!( + "application module error: {0}", + e.description) + }, } } diff --git a/modules/src/core/ics04_channel/events.rs b/modules/src/core/ics04_channel/events.rs index 63a8c81cae..35b4865ef6 100644 --- a/modules/src/core/ics04_channel/events.rs +++ b/modules/src/core/ics04_channel/events.rs @@ -850,6 +850,18 @@ impl From for IbcEvent { } } +impl TryFrom for AbciEvent { + type Error = Error; + + fn try_from(v: ReceivePacket) -> Result { + let attributes = Vec::::try_from(v.packet)?; + Ok(AbciEvent { + type_str: IbcEventType::ReceivePacket.as_str().to_string(), + attributes, + }) + } +} + impl core::fmt::Display for ReceivePacket { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { write!(f, "ReceivePacket - h:{}, {}", self.height, self.packet) @@ -1066,6 +1078,18 @@ impl From for IbcEvent { } } +impl TryFrom for AbciEvent { + type Error = Error; + + fn try_from(v: TimeoutOnClosePacket) -> Result { + let attributes = Vec::::try_from(v.packet)?; + Ok(AbciEvent { + type_str: IbcEventType::TimeoutOnClose.as_str().to_string(), + attributes, + }) + } +} + impl core::fmt::Display for TimeoutOnClosePacket { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { write!( diff --git a/modules/src/core/ics04_channel/handler.rs b/modules/src/core/ics04_channel/handler.rs index cb8c96d06e..0f428ea3cd 100644 --- a/modules/src/core/ics04_channel/handler.rs +++ b/modules/src/core/ics04_channel/handler.rs @@ -7,7 +7,7 @@ use crate::core::ics04_channel::msgs::ChannelMsg; use crate::core::ics04_channel::{msgs::PacketMsg, packet::PacketResult}; use crate::core::ics24_host::identifier::{ChannelId, PortId}; use crate::core::ics26_routing::context::{ - Ics26Context, ModuleId, ModuleOutput, OnRecvPacketAck, Router, + Ics26Context, ModuleId, ModuleOutputBuilder, OnRecvPacketAck, Router, }; use crate::handler::{HandlerOutput, HandlerOutputBuilder}; @@ -87,7 +87,7 @@ pub fn channel_callback( module_id: &ModuleId, msg: &ChannelMsg, mut result: ChannelResult, - module_output: &mut ModuleOutput, + module_output: &mut ModuleOutputBuilder, ) -> Result where Ctx: Ics26Context, @@ -115,6 +115,7 @@ where &msg.port_id, &result.channel_id, msg.channel.counterparty(), + msg.channel.version(), &msg.counterparty_version, )?; result.channel_end.version = version; @@ -191,7 +192,7 @@ pub fn packet_callback( ctx: &mut Ctx, module_id: &ModuleId, msg: &PacketMsg, - module_output: &mut ModuleOutput, + module_output: &mut ModuleOutputBuilder, ) -> Result<(), Error> where Ctx: Ics26Context, @@ -206,7 +207,7 @@ where let result = cb.on_recv_packet(module_output, &msg.packet, &msg.signer); match result { OnRecvPacketAck::Nil(write_fn) | OnRecvPacketAck::Successful(_, write_fn) => { - write_fn(cb.as_any_mut()); + write_fn(cb.as_any_mut()).map_err(Error::app_module)?; } OnRecvPacketAck::Failed(_) => {} } diff --git a/modules/src/core/ics04_channel/msgs/acknowledgement.rs b/modules/src/core/ics04_channel/msgs/acknowledgement.rs index a5806f096b..95bf93279f 100644 --- a/modules/src/core/ics04_channel/msgs/acknowledgement.rs +++ b/modules/src/core/ics04_channel/msgs/acknowledgement.rs @@ -32,6 +32,12 @@ impl From> for Acknowledgement { } } +impl AsRef<[u8]> for Acknowledgement { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + /// /// Message definition for packet acknowledgements. /// @@ -107,7 +113,7 @@ impl TryFrom for MsgAcknowledgement { .ok_or_else(Error::missing_packet)? .try_into()?, acknowledgement: raw_msg.acknowledgement.into(), - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, proofs, }) } @@ -160,6 +166,7 @@ mod test { use crate::core::ics04_channel::error::Error; use crate::core::ics04_channel::msgs::acknowledgement::test_util::get_dummy_raw_msg_acknowledgement; use crate::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; + use crate::test_utils::get_dummy_bech32_account; #[test] fn msg_acknowledgment_try_from_raw() { @@ -197,7 +204,7 @@ mod test { Test { name: "Empty signer".to_string(), raw: RawMsgAcknowledgement { - signer: "".to_string(), + signer: get_dummy_bech32_account(), ..default_raw_msg.clone() }, want_pass: true, diff --git a/modules/src/core/ics04_channel/msgs/chan_close_confirm.rs b/modules/src/core/ics04_channel/msgs/chan_close_confirm.rs index f5ca89607e..2dc6bd27dc 100644 --- a/modules/src/core/ics04_channel/msgs/chan_close_confirm.rs +++ b/modules/src/core/ics04_channel/msgs/chan_close_confirm.rs @@ -73,7 +73,7 @@ impl TryFrom for MsgChannelCloseConfirm { port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, proofs, - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics04_channel/msgs/chan_close_init.rs b/modules/src/core/ics04_channel/msgs/chan_close_init.rs index 3dfac2565f..ceb85b5421 100644 --- a/modules/src/core/ics04_channel/msgs/chan_close_init.rs +++ b/modules/src/core/ics04_channel/msgs/chan_close_init.rs @@ -53,7 +53,7 @@ impl TryFrom for MsgChannelCloseInit { Ok(MsgChannelCloseInit { port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics04_channel/msgs/chan_open_ack.rs b/modules/src/core/ics04_channel/msgs/chan_open_ack.rs index 46161037af..14cd6d85c6 100644 --- a/modules/src/core/ics04_channel/msgs/chan_open_ack.rs +++ b/modules/src/core/ics04_channel/msgs/chan_open_ack.rs @@ -84,7 +84,7 @@ impl TryFrom for MsgChannelOpenAck { .map_err(Error::identifier)?, counterparty_version: raw_msg.counterparty_version.into(), proofs, - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics04_channel/msgs/chan_open_confirm.rs b/modules/src/core/ics04_channel/msgs/chan_open_confirm.rs index a283e516d7..7ad004adb3 100644 --- a/modules/src/core/ics04_channel/msgs/chan_open_confirm.rs +++ b/modules/src/core/ics04_channel/msgs/chan_open_confirm.rs @@ -68,7 +68,7 @@ impl TryFrom for MsgChannelOpenConfirm { port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, proofs, - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics04_channel/msgs/chan_open_init.rs b/modules/src/core/ics04_channel/msgs/chan_open_init.rs index c95dd2cbb2..b5b4130a90 100644 --- a/modules/src/core/ics04_channel/msgs/chan_open_init.rs +++ b/modules/src/core/ics04_channel/msgs/chan_open_init.rs @@ -55,7 +55,7 @@ impl TryFrom for MsgChannelOpenInit { .channel .ok_or_else(Error::missing_channel)? .try_into()?, - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, }) } } diff --git a/modules/src/core/ics04_channel/msgs/chan_open_try.rs b/modules/src/core/ics04_channel/msgs/chan_open_try.rs index 68122e2d44..5af004d529 100644 --- a/modules/src/core/ics04_channel/msgs/chan_open_try.rs +++ b/modules/src/core/ics04_channel/msgs/chan_open_try.rs @@ -104,7 +104,7 @@ impl TryFrom for MsgChannelOpenTry { .try_into()?, counterparty_version: raw_msg.counterparty_version.into(), proofs, - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(ChannelError::signer)?, }; msg.validate_basic() diff --git a/modules/src/core/ics04_channel/msgs/recv_packet.rs b/modules/src/core/ics04_channel/msgs/recv_packet.rs index 076fd079f0..7aad7c6eec 100644 --- a/modules/src/core/ics04_channel/msgs/recv_packet.rs +++ b/modules/src/core/ics04_channel/msgs/recv_packet.rs @@ -72,7 +72,7 @@ impl TryFrom for MsgRecvPacket { .ok_or_else(Error::missing_packet)? .try_into()?, proofs, - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, }) } } @@ -129,6 +129,7 @@ mod test { use crate::core::ics04_channel::error::Error; use crate::core::ics04_channel::msgs::recv_packet::test_util::get_dummy_raw_msg_recv_packet; use crate::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; + use crate::test_utils::get_dummy_bech32_account; #[test] fn msg_recv_packet_try_from_raw() { @@ -165,7 +166,7 @@ mod test { Test { name: "Empty signer".to_string(), raw: RawMsgRecvPacket { - signer: "".to_string(), + signer: get_dummy_bech32_account(), ..default_raw_msg }, want_pass: true, diff --git a/modules/src/core/ics04_channel/msgs/timeout.rs b/modules/src/core/ics04_channel/msgs/timeout.rs index 74efc8dd07..a2887e1013 100644 --- a/modules/src/core/ics04_channel/msgs/timeout.rs +++ b/modules/src/core/ics04_channel/msgs/timeout.rs @@ -81,7 +81,7 @@ impl TryFrom for MsgTimeout { .ok_or_else(Error::missing_packet)? .try_into()?, next_sequence_recv: Sequence::from(raw_msg.next_sequence_recv), - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, proofs, }) } @@ -134,6 +134,7 @@ mod test { use crate::core::ics04_channel::error::Error; use crate::core::ics04_channel::msgs::timeout::test_util::get_dummy_raw_msg_timeout; use crate::core::ics04_channel::msgs::timeout::MsgTimeout; + use crate::test_utils::get_dummy_bech32_account; #[test] fn msg_timeout_try_from_raw() { @@ -180,7 +181,7 @@ mod test { Test { name: "Empty signer".to_string(), raw: RawMsgTimeout { - signer: "".to_string(), + signer: get_dummy_bech32_account(), ..default_raw_msg }, want_pass: true, diff --git a/modules/src/core/ics04_channel/msgs/timeout_on_close.rs b/modules/src/core/ics04_channel/msgs/timeout_on_close.rs index 64ef629cf4..05fda53a54 100644 --- a/modules/src/core/ics04_channel/msgs/timeout_on_close.rs +++ b/modules/src/core/ics04_channel/msgs/timeout_on_close.rs @@ -80,7 +80,7 @@ impl TryFrom for MsgTimeoutOnClose { .ok_or_else(Error::missing_packet)? .try_into()?, next_sequence_recv: Sequence::from(raw_msg.next_sequence_recv), - signer: raw_msg.signer.into(), + signer: raw_msg.signer.parse().map_err(Error::signer)?, proofs, }) } diff --git a/modules/src/core/ics04_channel/version.rs b/modules/src/core/ics04_channel/version.rs index a5c611bf7a..b0f7918dd0 100644 --- a/modules/src/core/ics04_channel/version.rs +++ b/modules/src/core/ics04_channel/version.rs @@ -7,7 +7,7 @@ use core::fmt; use core::str::FromStr; use serde_derive::{Deserialize, Serialize}; -use crate::applications::ics20_fungible_token_transfer; +use crate::applications::transfer; use crate::prelude::*; /// The version field for a `ChannelEnd`. @@ -24,7 +24,7 @@ impl Version { } pub fn ics20() -> Self { - Self::new(ics20_fungible_token_transfer::VERSION.to_string()) + Self::new(transfer::VERSION.to_string()) } pub fn empty() -> Self { diff --git a/modules/src/core/ics05_port/context.rs b/modules/src/core/ics05_port/context.rs index 87a4390b24..2c59d4a592 100644 --- a/modules/src/core/ics05_port/context.rs +++ b/modules/src/core/ics05_port/context.rs @@ -8,8 +8,3 @@ pub trait PortReader { /// Return the module_id associated with a given port_id fn lookup_module_by_port(&self, port_id: &PortId) -> Result; } - -pub trait PortKeeper: PortReader { - /// Binds a module to a port. - fn bind_module_to_port(&mut self, module_id: ModuleId, port_id: PortId) -> Result<(), Error>; -} diff --git a/modules/src/core/ics24_host/identifier.rs b/modules/src/core/ics24_host/identifier.rs index 3b86c8b499..21f53b9363 100644 --- a/modules/src/core/ics24_host/identifier.rs +++ b/modules/src/core/ics24_host/identifier.rs @@ -354,7 +354,7 @@ impl ChannelId { Self(counter) } - pub fn counter(&self) -> u64 { + pub fn sequence(&self) -> u64 { self.0 } diff --git a/modules/src/core/ics26_routing/context.rs b/modules/src/core/ics26_routing/context.rs index c070c9b151..c22cc0a14c 100644 --- a/modules/src/core/ics26_routing/context.rs +++ b/modules/src/core/ics26_routing/context.rs @@ -5,7 +5,8 @@ use core::any::Any; use core::fmt::Debug; use core::{fmt, str::FromStr}; -use crate::applications::ics20_fungible_token_transfer::context::Ics20Context; +use serde::{Deserialize, Serialize}; + use crate::core::ics02_client::context::{ClientKeeper, ClientReader}; use crate::core::ics03_connection::context::{ConnectionKeeper, ConnectionReader}; use crate::core::ics04_channel::channel::{Counterparty, Order}; @@ -16,8 +17,8 @@ use crate::core::ics04_channel::packet::Packet; use crate::core::ics04_channel::Version; use crate::core::ics05_port::context::PortReader; use crate::core::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; -use crate::events::IbcEvent; -use crate::handler::HandlerOutput; +use crate::events::ModuleEvent; +use crate::handler::HandlerOutputBuilder; use crate::signer::Signer; /// This trait captures all the functional dependencies (i.e., context) which the ICS26 module @@ -31,7 +32,6 @@ pub trait Ics26Context: + ChannelKeeper + ChannelReader + PortReader - + Ics20Context { type Router: Router; @@ -43,7 +43,7 @@ pub trait Ics26Context: #[derive(Debug, PartialEq)] pub struct InvalidModuleId; -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] pub struct ModuleId(String); impl ModuleId { @@ -79,7 +79,7 @@ impl Borrow for ModuleId { /// Types implementing this trait are expected to implement `From` pub trait Acknowledgement: AsRef<[u8]> {} -pub type WriteFn = dyn FnOnce(&mut dyn Any); +pub type WriteFn = dyn FnOnce(&mut dyn Any) -> Result<(), String>; pub enum OnRecvPacketAck { Nil(Box), @@ -87,15 +87,19 @@ pub enum OnRecvPacketAck { Failed(Box), } -pub type ModuleEvent = IbcEvent; +impl OnRecvPacketAck { + pub fn is_successful(&self) -> bool { + matches!(self, OnRecvPacketAck::Successful(_, _)) + } +} -pub type ModuleOutput = HandlerOutput<(), ModuleEvent>; +pub type ModuleOutputBuilder = HandlerOutputBuilder<(), ModuleEvent>; pub trait Module: Debug + Send + Sync + AsAnyMut + 'static { #[allow(clippy::too_many_arguments)] fn on_chan_open_init( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _order: Order, _connection_hops: &[ConnectionId], _port_id: &PortId, @@ -109,18 +113,19 @@ pub trait Module: Debug + Send + Sync + AsAnyMut + 'static { #[allow(clippy::too_many_arguments)] fn on_chan_open_try( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _order: Order, _connection_hops: &[ConnectionId], _port_id: &PortId, _channel_id: &ChannelId, _counterparty: &Counterparty, + _version: &Version, _counterparty_version: &Version, ) -> Result; fn on_chan_open_ack( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _port_id: &PortId, _channel_id: &ChannelId, _counterparty_version: &Version, @@ -130,7 +135,7 @@ pub trait Module: Debug + Send + Sync + AsAnyMut + 'static { fn on_chan_open_confirm( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _port_id: &PortId, _channel_id: &ChannelId, ) -> Result<(), Error> { @@ -139,7 +144,7 @@ pub trait Module: Debug + Send + Sync + AsAnyMut + 'static { fn on_chan_close_init( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _port_id: &PortId, _channel_id: &ChannelId, ) -> Result<(), Error> { @@ -148,7 +153,7 @@ pub trait Module: Debug + Send + Sync + AsAnyMut + 'static { fn on_chan_close_confirm( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _port_id: &PortId, _channel_id: &ChannelId, ) -> Result<(), Error> { @@ -157,16 +162,16 @@ pub trait Module: Debug + Send + Sync + AsAnyMut + 'static { fn on_recv_packet( &self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _packet: &Packet, _relayer: &Signer, ) -> OnRecvPacketAck { - OnRecvPacketAck::Nil(Box::new(|_| {})) + OnRecvPacketAck::Nil(Box::new(|_| Ok(()))) } fn on_acknowledgement_packet( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _packet: &Packet, _acknowledgement: &GenericAcknowledgement, _relayer: &Signer, @@ -176,7 +181,7 @@ pub trait Module: Debug + Send + Sync + AsAnyMut + 'static { fn on_timeout_packet( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _packet: &Packet, _relayer: &Signer, ) -> Result<(), Error> { diff --git a/modules/src/core/ics26_routing/error.rs b/modules/src/core/ics26_routing/error.rs index 2c4af31ae6..260a688cdd 100644 --- a/modules/src/core/ics26_routing/error.rs +++ b/modules/src/core/ics26_routing/error.rs @@ -1,7 +1,7 @@ use crate::prelude::*; use flex_error::{define_error, TraceError}; -use crate::applications::ics20_fungible_token_transfer; +use crate::applications::transfer; use crate::core::ics02_client; use crate::core::ics03_connection; use crate::core::ics04_channel; @@ -22,7 +22,7 @@ define_error! { | _ | { "ICS04 channel error" }, Ics20FungibleTokenTransfer - [ ics20_fungible_token_transfer::error::Error ] + [ transfer::error::Error ] | _ | { "ICS20 fungible token transfer error" }, UnknownMessageTypeUrl diff --git a/modules/src/core/ics26_routing/handler.rs b/modules/src/core/ics26_routing/handler.rs index 3b8d13ef46..b5d5362ec5 100644 --- a/modules/src/core/ics26_routing/handler.rs +++ b/modules/src/core/ics26_routing/handler.rs @@ -2,7 +2,6 @@ use crate::prelude::*; use ibc_proto::google::protobuf::Any; -use crate::applications::ics20_fungible_token_transfer::relay_application_logic::send_transfer::send_transfer as ics20_msg_dispatcher; use crate::core::ics02_client::handler::dispatch as ics2_msg_dispatcher; use crate::core::ics03_connection::handler::dispatch as ics3_msg_dispatcher; use crate::core::ics04_channel::handler::{ @@ -14,17 +13,24 @@ use crate::core::ics04_channel::handler::{ packet_dispatch as ics4_packet_msg_dispatcher, }; use crate::core::ics04_channel::packet::PacketResult; -use crate::core::ics26_routing::context::Ics26Context; +use crate::core::ics26_routing::context::{Ics26Context, ModuleOutputBuilder}; use crate::core::ics26_routing::error::Error; use crate::core::ics26_routing::msgs::Ics26Envelope::{ - self, Ics20Msg, Ics2Msg, Ics3Msg, Ics4ChannelMsg, Ics4PacketMsg, + self, Ics2Msg, Ics3Msg, Ics4ChannelMsg, Ics4PacketMsg, }; use crate::{events::IbcEvent, handler::HandlerOutput}; +/// Result of message execution - comprises of events emitted and logs entries created during the +/// execution of a transaction message. +pub struct MsgReceipt { + pub events: Vec, + pub log: Vec, +} + /// Mimics the DeliverTx ABCI interface, but for a single message and at a slightly lower level. /// No need for authentication info or signature checks here. /// Returns a vector of all events that got generated as a byproduct of processing `message`. -pub fn deliver(ctx: &mut Ctx, message: Any) -> Result<(Vec, Vec), Error> +pub fn deliver(ctx: &mut Ctx, message: Any) -> Result where Ctx: Ics26Context, { @@ -32,9 +38,9 @@ where let envelope = decode(message)?; // Process the envelope, and accumulate any events that were generated. - let output = dispatch(ctx, envelope)?; + let HandlerOutput { log, events, .. } = dispatch(ctx, envelope)?; - Ok((output.events, output.log)) + Ok(MsgReceipt { events, log }) } /// Attempts to convert a message into a [Ics26Envelope] message @@ -83,7 +89,7 @@ where let (mut handler_builder, channel_result) = ics4_msg_dispatcher(ctx, &msg).map_err(Error::ics04_channel)?; - let mut module_output = HandlerOutput::builder().with_result(()); + let mut module_output = ModuleOutputBuilder::new(); let cb_result = ics4_callback(ctx, &module_id, &msg, channel_result, &mut module_output); handler_builder.merge(module_output); @@ -96,20 +102,6 @@ where handler_builder.with_result(()) } - Ics20Msg(msg) => { - let handler_output = - ics20_msg_dispatcher(ctx, msg).map_err(Error::ics20_fungible_token_transfer)?; - - // Apply any results to the host chain store. - ctx.store_packet_result(handler_output.result) - .map_err(Error::ics04_channel)?; - - HandlerOutput::builder() - .with_log(handler_output.log) - .with_events(handler_output.events) - .with_result(()) - } - Ics4PacketMsg(msg) => { let module_id = get_module_for_packet_msg(ctx, &msg).map_err(Error::ics04_channel)?; let (mut handler_builder, packet_result) = @@ -119,7 +111,7 @@ where return Ok(handler_builder.with_result(())); } - let mut module_output = HandlerOutput::builder().with_result(()); + let mut module_output = ModuleOutputBuilder::new(); let cb_result = ics4_packet_callback(ctx, &module_id, &msg, &mut module_output); handler_builder.merge(module_output); cb_result.map_err(Error::ics04_channel)?; @@ -141,14 +133,10 @@ mod tests { use test_log::test; + use crate::applications::transfer::context::test::deliver as ics20_deliver; + use crate::applications::transfer::PrefixedCoin; use crate::core::ics02_client::client_consensus::AnyConsensusState; use crate::core::ics02_client::client_state::AnyClientState; - use crate::events::IbcEvent; - use crate::{ - applications::ics20_fungible_token_transfer::msgs::transfer::test_util::get_dummy_msg_transfer, - core::ics23_commitment::commitment::test_util::get_dummy_merkle_proof, - }; - use crate::core::ics02_client::msgs::{ create_client::MsgCreateAnyClient, update_client::MsgUpdateAnyClient, upgrade_client::MsgUpgradeAnyClient, ClientMsg, @@ -171,15 +159,24 @@ mod tests { timeout_on_close::{test_util::get_dummy_raw_msg_timeout_on_close, MsgTimeoutOnClose}, ChannelMsg, PacketMsg, }; + use crate::core::ics23_commitment::commitment::test_util::get_dummy_merkle_proof; + use crate::events::IbcEvent; + use crate::{ + applications::transfer::msgs::transfer::test_util::get_dummy_msg_transfer, + applications::transfer::msgs::transfer::MsgTransfer, + applications::transfer::packet::PacketData, applications::transfer::MODULE_ID_STR, + }; use crate::core::ics24_host::identifier::ConnectionId; - use crate::core::ics26_routing::context::{ModuleId, RouterBuilder}; + use crate::core::ics26_routing::context::{Ics26Context, ModuleId, Router, RouterBuilder}; + use crate::core::ics26_routing::error::Error; use crate::core::ics26_routing::handler::dispatch; use crate::core::ics26_routing::msgs::Ics26Envelope; + use crate::handler::HandlerOutputBuilder; use crate::mock::client_state::{MockClientState, MockConsensusState}; use crate::mock::context::{MockContext, MockRouterBuilder}; use crate::mock::header::MockHeader; - use crate::test_utils::{get_dummy_account_id, DummyModule}; + use crate::test_utils::{get_dummy_account_id, DummyTransferModule}; use crate::timestamp::Timestamp; use crate::Height; @@ -189,10 +186,28 @@ mod tests { /// to work with the context and correctly store results (i.e., the `ClientKeeper`, /// `ConnectionKeeper`, and `ChannelKeeper` traits). fn routing_module_and_keepers() { + #[derive(Clone, Debug)] + enum TestMsg { + Ics26(Ics26Envelope), + Ics20(MsgTransfer), + } + + impl From for TestMsg { + fn from(msg: Ics26Envelope) -> Self { + Self::Ics26(msg) + } + } + + impl From> for TestMsg { + fn from(msg: MsgTransfer) -> Self { + Self::Ics20(msg) + } + } + // Test parameters struct Test { name: String, - msg: Ics26Envelope, + msg: TestMsg, want_pass: bool, } let default_signer = get_dummy_account_id(); @@ -207,16 +222,18 @@ mod tests { let upgrade_client_height_second = Height::new(1, 1); - let module = DummyModule::default(); - let module_id: ModuleId = "dummymodule".parse().unwrap(); - - let router = MockRouterBuilder::default() - .add_route(module_id.clone(), module) - .unwrap() - .build(); + let transfer_module_id: ModuleId = MODULE_ID_STR.parse().unwrap(); // We reuse this same context across all tests. Nothing in particular needs parametrizing. - let mut ctx = MockContext::default().with_router(router); + let mut ctx = { + let ctx = MockContext::default(); + let module = DummyTransferModule::new(ctx.ibc_store_share()); + let router = MockRouterBuilder::default() + .add_route(transfer_module_id.clone(), module) + .unwrap() + .build(); + ctx.with_router(router) + }; let create_client_msg = MsgCreateAnyClient::new( AnyClientState::from(MockClientState::new(MockHeader::new(start_client_height))), @@ -283,6 +300,20 @@ mod tests { msg_to_on_close.packet.timeout_height = msg_transfer_two.timeout_height; msg_to_on_close.packet.timeout_timestamp = msg_transfer_two.timeout_timestamp; + let denom = msg_transfer_two.token.denom.clone(); + let packet_data = { + let data = PacketData { + token: PrefixedCoin { + denom, + amount: msg_transfer_two.token.amount, + }, + sender: msg_transfer_two.sender.clone(), + receiver: msg_transfer_two.receiver.clone(), + }; + serde_json::to_vec(&data).expect("PacketData's infallible Serialize impl failed") + }; + msg_to_on_close.packet.data = packet_data; + let msg_recv_packet = MsgRecvPacket::try_from(get_dummy_raw_msg_recv_packet(35)).unwrap(); // First, create a client.. @@ -298,7 +329,7 @@ mod tests { res ); - ctx.scope_port_to_module(msg_chan_init.port_id.clone(), module_id); + ctx.scope_port_to_module(msg_chan_init.port_id.clone(), transfer_module_id.clone()); // Figure out the ID of the client that was just created. let mut events = res.unwrap().events; @@ -322,7 +353,8 @@ mod tests { .with_timestamp(Timestamp::now()) .into(), signer: default_signer.clone(), - })), + })) + .into(), want_pass: true, }, Test { @@ -331,14 +363,16 @@ mod tests { client_id: client_id.clone(), header: MockHeader::new(update_client_height).into(), signer: default_signer.clone(), - })), + })) + .into(), want_pass: false, }, Test { name: "Connection open init succeeds".to_string(), msg: Ics26Envelope::Ics3Msg(ConnectionMsg::ConnectionOpenInit( msg_conn_init.with_client_id(client_id.clone()), - )), + )) + .into(), want_pass: true, }, Test { @@ -346,50 +380,54 @@ mod tests { .to_string(), msg: Ics26Envelope::Ics3Msg(ConnectionMsg::ConnectionOpenTry(Box::new( incorrect_msg_conn_try, - ))), + ))) + .into(), want_pass: false, }, Test { name: "Connection open try succeeds".to_string(), msg: Ics26Envelope::Ics3Msg(ConnectionMsg::ConnectionOpenTry(Box::new( correct_msg_conn_try.with_client_id(client_id.clone()), - ))), + ))) + .into(), want_pass: true, }, Test { name: "Connection open ack succeeds".to_string(), msg: Ics26Envelope::Ics3Msg(ConnectionMsg::ConnectionOpenAck(Box::new( msg_conn_ack, - ))), + ))) + .into(), want_pass: true, }, // ICS04 Test { name: "Channel open init succeeds".to_string(), - msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenInit(msg_chan_init)), + msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenInit(msg_chan_init)) + .into(), want_pass: true, }, Test { name: "Channel open init fail due to missing connection".to_string(), msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenInit( incorrect_msg_chan_init, - )), + )) + .into(), want_pass: false, }, Test { name: "Channel open try succeeds".to_string(), - msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenTry(msg_chan_try)), + msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenTry(msg_chan_try)).into(), want_pass: true, }, Test { name: "Channel open ack succeeds".to_string(), - msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenAck(msg_chan_ack)), + msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelOpenAck(msg_chan_ack)).into(), want_pass: true, }, - //ICS20-04-packet Test { name: "Packet send".to_string(), - msg: Ics26Envelope::Ics20Msg(msg_transfer), + msg: msg_transfer.into(), want_pass: true, }, // The client update is required in this test, because the proof associated with @@ -402,22 +440,24 @@ mod tests { .with_timestamp(Timestamp::now()) .into(), signer: default_signer.clone(), - })), + })) + .into(), want_pass: true, }, Test { name: "Receive packet".to_string(), - msg: Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket(msg_recv_packet.clone())), + msg: Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket(msg_recv_packet.clone())) + .into(), want_pass: true, }, Test { name: "Re-Receive packet".to_string(), - msg: Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket(msg_recv_packet)), + msg: Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket(msg_recv_packet)).into(), want_pass: true, }, Test { name: "Packet send".to_string(), - msg: Ics26Envelope::Ics20Msg(msg_transfer_two), + msg: msg_transfer_two.into(), want_pass: true, }, Test { @@ -426,7 +466,8 @@ mod tests { client_id: client_id.clone(), header: MockHeader::new(update_client_height_after_second_send).into(), signer: default_signer.clone(), - })), + })) + .into(), want_pass: true, }, //ICS04-close channel @@ -434,20 +475,22 @@ mod tests { name: "Channel close init succeeds".to_string(), msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelCloseInit( msg_chan_close_init, - )), + )) + .into(), want_pass: true, }, Test { name: "Channel close confirm fails cause channel is already closed".to_string(), msg: Ics26Envelope::Ics4ChannelMsg(ChannelMsg::ChannelCloseConfirm( msg_chan_close_confirm, - )), + )) + .into(), want_pass: false, }, //ICS04-to_on_close Test { name: "Timeout on close".to_string(), - msg: Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket(msg_to_on_close)), + msg: Ics26Envelope::Ics4PacketMsg(PacketMsg::ToClosePacket(msg_to_on_close)).into(), want_pass: true, }, Test { @@ -463,7 +506,8 @@ mod tests { get_dummy_merkle_proof(), get_dummy_merkle_proof(), default_signer.clone(), - ))), + ))) + .into(), want_pass: true, }, Test { @@ -479,7 +523,8 @@ mod tests { get_dummy_merkle_proof(), get_dummy_merkle_proof(), default_signer, - ))), + ))) + .into(), want_pass: false, }, ] @@ -487,7 +532,23 @@ mod tests { .collect(); for test in tests { - let res = dispatch(&mut ctx, test.msg.clone()); + let res = match test.msg.clone() { + TestMsg::Ics26(msg) => dispatch(&mut ctx, msg).map(|_| ()), + TestMsg::Ics20(msg) => { + let transfer_module = + ctx.router_mut().get_route_mut(&transfer_module_id).unwrap(); + ics20_deliver( + transfer_module + .as_any_mut() + .downcast_mut::() + .unwrap(), + &mut HandlerOutputBuilder::new(), + msg, + ) + .map(|_| ()) + .map_err(Error::ics04_channel) + } + }; assert_eq!( test.want_pass, diff --git a/modules/src/core/ics26_routing/msgs.rs b/modules/src/core/ics26_routing/msgs.rs index 362f88144d..3f2306e6e9 100644 --- a/modules/src/core/ics26_routing/msgs.rs +++ b/modules/src/core/ics26_routing/msgs.rs @@ -2,7 +2,6 @@ use crate::prelude::*; use ibc_proto::google::protobuf::Any; -use crate::applications::ics20_fungible_token_transfer::msgs::{transfer, transfer::MsgTransfer}; use crate::core::ics02_client::msgs::{create_client, update_client, upgrade_client, ClientMsg}; use crate::core::ics03_connection::msgs::{ conn_open_ack, conn_open_confirm, conn_open_init, conn_open_try, ConnectionMsg, @@ -21,7 +20,6 @@ pub enum Ics26Envelope { Ics3Msg(ConnectionMsg), Ics4ChannelMsg(ChannelMsg), Ics4PacketMsg(PacketMsg), - Ics20Msg(MsgTransfer), } impl TryFrom for Ics26Envelope { @@ -123,12 +121,6 @@ impl TryFrom for Ics26Envelope { ChannelMsg::ChannelCloseConfirm(domain_msg), )) } - // ICS20 - 04 - Send packet - transfer::TYPE_URL => { - let domain_msg = transfer::MsgTransfer::decode_vec(&any_msg.value) - .map_err(Error::malformed_message_bytes)?; - Ok(Ics26Envelope::Ics20Msg(domain_msg)) - } // ICS04 packet messages recv_packet::TYPE_URL => { let domain_msg = recv_packet::MsgRecvPacket::decode_vec(&any_msg.value) diff --git a/modules/src/events.rs b/modules/src/events.rs index 3093f70c05..58e6d54e2b 100644 --- a/modules/src/events.rs +++ b/modules/src/events.rs @@ -6,6 +6,7 @@ use core::str::FromStr; use flex_error::{define_error, TraceError}; use prost::alloc::fmt::Formatter; use serde_derive::{Deserialize, Serialize}; +use tendermint::abci::tag::Tag; use tendermint::abci::Event as AbciEvent; use crate::core::ics02_client::error as client_error; @@ -19,6 +20,7 @@ use crate::core::ics04_channel::events as ChannelEvents; use crate::core::ics04_channel::events::Attributes as ChannelAttributes; use crate::core::ics04_channel::packet::Packet; use crate::core::ics24_host::error::ValidationError; +use crate::core::ics26_routing::context::ModuleId; use crate::timestamp::ParseTimestampError; use crate::Height; @@ -62,6 +64,10 @@ define_error! { IncorrectEventType { event: String } | e | { format_args!("incorrect event type: {}", e.event) }, + + MalformedModuleEvent + { event: ModuleEvent } + | e | { format_args!("module event cannot use core event types: {:?}", e.event) }, } } @@ -89,6 +95,7 @@ impl WithBlockDataType { const NEW_BLOCK_EVENT: &str = "new_block"; const EMPTY_EVENT: &str = "empty"; const CHAIN_ERROR_EVENT: &str = "chain_error"; +const APP_MODULE_EVENT: &str = "app_module"; /// Client event types const CREATE_CLIENT_EVENT: &str = "create_client"; const UPDATE_CLIENT_EVENT: &str = "update_client"; @@ -138,6 +145,7 @@ pub enum IbcEventType { AckPacket, Timeout, TimeoutOnClose, + AppModule, Empty, ChainError, } @@ -166,6 +174,7 @@ impl IbcEventType { IbcEventType::AckPacket => ACK_PACKET_EVENT, IbcEventType::Timeout => TIMEOUT_EVENT, IbcEventType::TimeoutOnClose => TIMEOUT_ON_CLOSE_EVENT, + IbcEventType::AppModule => APP_MODULE_EVENT, IbcEventType::Empty => EMPTY_EVENT, IbcEventType::ChainError => CHAIN_ERROR_EVENT, } @@ -200,6 +209,7 @@ impl FromStr for IbcEventType { TIMEOUT_ON_CLOSE_EVENT => Ok(IbcEventType::TimeoutOnClose), EMPTY_EVENT => Ok(IbcEventType::Empty), CHAIN_ERROR_EVENT => Ok(IbcEventType::ChainError), + // from_str() for `APP_MODULE_EVENT` MUST fail because a `ModuleEvent`'s type isn't constant _ => Err(Error::incorrect_event_type(s.to_string())), } } @@ -234,6 +244,8 @@ pub enum IbcEvent { TimeoutPacket(ChannelEvents::TimeoutPacket), TimeoutOnClosePacket(ChannelEvents::TimeoutOnClosePacket), + AppModule(ModuleEvent), + Empty(String), // Special event, signifying empty response ChainError(String), // Special event, signifying an error on CheckTx or DeliverTx } @@ -285,6 +297,8 @@ impl fmt::Display for IbcEvent { IbcEvent::TimeoutPacket(ev) => write!(f, "TimeoutPacketEv({})", ev), IbcEvent::TimeoutOnClosePacket(ev) => write!(f, "TimeoutOnClosePacketEv({})", ev), + IbcEvent::AppModule(ev) => write!(f, "AppModuleEv({:?})", ev), + IbcEvent::Empty(ev) => write!(f, "EmptyEv({})", ev), IbcEvent::ChainError(ev) => write!(f, "ChainErrorEv({})", ev), } @@ -311,10 +325,15 @@ impl TryFrom for AbciEvent { IbcEvent::CloseInitChannel(event) => event.into(), IbcEvent::CloseConfirmChannel(event) => event.into(), IbcEvent::SendPacket(event) => event.try_into().map_err(Error::channel)?, + IbcEvent::ReceivePacket(event) => event.try_into().map_err(Error::channel)?, IbcEvent::WriteAcknowledgement(event) => event.try_into().map_err(Error::channel)?, IbcEvent::AcknowledgePacket(event) => event.try_into().map_err(Error::channel)?, IbcEvent::TimeoutPacket(event) => event.try_into().map_err(Error::channel)?, - _ => return Err(Error::incorrect_event_type(event.to_string())), + IbcEvent::TimeoutOnClosePacket(event) => event.try_into().map_err(Error::channel)?, + IbcEvent::AppModule(event) => event.try_into()?, + IbcEvent::NewBlock(_) | IbcEvent::Empty(_) | IbcEvent::ChainError(_) => { + return Err(Error::incorrect_event_type(event.to_string())) + } }) } } @@ -420,6 +439,7 @@ impl IbcEvent { IbcEvent::AcknowledgePacket(_) => IbcEventType::AckPacket, IbcEvent::TimeoutPacket(_) => IbcEventType::Timeout, IbcEvent::TimeoutOnClosePacket(_) => IbcEventType::TimeoutOnClose, + IbcEvent::AppModule(_) => IbcEventType::AppModule, IbcEvent::Empty(_) => IbcEventType::Empty, IbcEvent::ChainError(_) => IbcEventType::ChainError, } @@ -465,6 +485,65 @@ impl IbcEvent { } } +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct ModuleEvent { + pub kind: String, + pub module_name: ModuleId, + pub attributes: Vec, +} + +impl TryFrom for AbciEvent { + type Error = Error; + + fn try_from(event: ModuleEvent) -> Result { + if IbcEventType::from_str(event.kind.as_str()).is_ok() { + return Err(Error::malformed_module_event(event)); + } + + let attributes = event.attributes.into_iter().map(Into::into).collect(); + Ok(AbciEvent { + type_str: event.kind, + attributes, + }) + } +} + +impl From for IbcEvent { + fn from(e: ModuleEvent) -> Self { + IbcEvent::AppModule(e) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct ModuleEventAttribute { + pub key: String, + pub value: String, +} + +impl From<(K, V)> for ModuleEventAttribute { + fn from((k, v): (K, V)) -> Self { + Self { + key: k.to_string(), + value: v.to_string(), + } + } +} + +impl From for Tag { + fn from(attr: ModuleEventAttribute) -> Self { + Self { + key: attr + .key + .parse() + .expect("Key::from_str() impl is infallible"), + value: attr + .key + .parse() + .expect("Value::from_str() impl is infallible"), + } + } +} + #[derive(Debug, Clone, Serialize)] pub struct RawObject<'a> { pub height: Height, diff --git a/modules/src/handler.rs b/modules/src/handler.rs index bb52bce2d0..f8bf203ebb 100644 --- a/modules/src/handler.rs +++ b/modules/src/handler.rs @@ -11,20 +11,20 @@ pub struct HandlerOutput { pub events: Vec, } -impl HandlerOutput { - pub fn builder() -> HandlerOutputBuilder { +impl HandlerOutput { + pub fn builder() -> HandlerOutputBuilder { HandlerOutputBuilder::new() } } #[derive(Clone, Debug, Default)] -pub struct HandlerOutputBuilder { +pub struct HandlerOutputBuilder { log: Vec, - events: Vec, + events: Vec, marker: PhantomData, } -impl HandlerOutputBuilder { +impl HandlerOutputBuilder { pub fn new() -> Self { Self { log: Vec::new(), @@ -42,16 +42,16 @@ impl HandlerOutputBuilder { self.log.push(log.into()); } - pub fn with_events(mut self, mut events: Vec) -> Self { + pub fn with_events(mut self, mut events: Vec) -> Self { self.events.append(&mut events); self } - pub fn emit(&mut self, event: IbcEvent) { + pub fn emit(&mut self, event: E) { self.events.push(event); } - pub fn with_result(self, result: T) -> HandlerOutput { + pub fn with_result(self, result: T) -> HandlerOutput { HandlerOutput { result, log: self.log, @@ -59,13 +59,21 @@ impl HandlerOutputBuilder { } } - pub fn merge(&mut self, other: HandlerOutput<()>) { + pub fn merge>(&mut self, other: HandlerOutputBuilder<(), Event>) { + let HandlerOutputBuilder { + mut log, events, .. + } = other; + self.log.append(&mut log); + self.events + .append(&mut events.into_iter().map(Into::into).collect()); + } + + pub fn merge_output>(&mut self, other: HandlerOutput<(), Event>) { let HandlerOutput { - mut log, - mut events, - .. + mut log, events, .. } = other; self.log.append(&mut log); - self.events.append(&mut events); + self.events + .append(&mut events.into_iter().map(Into::into).collect()); } } diff --git a/modules/src/lib.rs b/modules/src/lib.rs index 208780e900..758f180223 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -52,6 +52,7 @@ extern crate std; mod prelude; pub mod applications; +pub mod bigint; pub mod clients; pub mod core; pub mod events; diff --git a/modules/src/mock/context.rs b/modules/src/mock/context.rs index 9650076d91..4c59475803 100644 --- a/modules/src/mock/context.rs +++ b/modules/src/mock/context.rs @@ -9,12 +9,12 @@ use core::cmp::min; use core::fmt::Debug; use core::ops::{Add, Sub}; use core::time::Duration; +use std::sync::Mutex; use ibc_proto::google::protobuf::Any; use sha2::Digest; use tracing::debug; -use crate::applications::ics20_fungible_token_transfer::context::Ics20Context; use crate::clients::ics07_tendermint::client_state::test_util::get_dummy_tendermint_client_state; use crate::core::ics02_client::client_consensus::{AnyConsensusState, AnyConsensusStateWithHeight}; use crate::core::ics02_client::client_state::AnyClientState; @@ -36,7 +36,7 @@ use crate::core::ics05_port::error::Error; use crate::core::ics23_commitment::commitment::CommitmentPrefix; use crate::core::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}; use crate::core::ics26_routing::context::{Ics26Context, Module, ModuleId, Router, RouterBuilder}; -use crate::core::ics26_routing::handler::{deliver, dispatch}; +use crate::core::ics26_routing::handler::{deliver, dispatch, MsgReceipt}; use crate::core::ics26_routing::msgs::Ics26Envelope; use crate::events::IbcEvent; use crate::mock::client_state::{MockClientRecord, MockClientState, MockConsensusState}; @@ -51,7 +51,7 @@ use crate::Height; pub const DEFAULT_BLOCK_TIME_SECS: u64 = 3; /// A context implementing the dependencies necessary for testing any IBC module. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct MockContext { /// The type of host chain underlying this mock context. host_chain_type: HostType, @@ -66,60 +66,12 @@ pub struct MockContext { /// blocks, ascending order by their height (latest block is on the last position). history: Vec, - /// The set of all clients, indexed by their id. - clients: BTreeMap, - - /// Tracks the processed time for clients header updates - client_processed_times: BTreeMap<(ClientId, Height), Timestamp>, - - /// Tracks the processed height for the clients - client_processed_heights: BTreeMap<(ClientId, Height), Height>, - - /// Counter for the client identifiers, necessary for `increase_client_counter` and the - /// `client_counter` methods. - client_ids_counter: u64, - - /// Association between client ids and connection ids. - client_connections: BTreeMap, - - /// All the connections in the store. - connections: BTreeMap, - - /// Counter for connection identifiers (see `increase_connection_counter`). - connection_ids_counter: u64, - - /// Association between connection ids and channel ids. - connection_channels: BTreeMap>, - - /// Counter for channel identifiers (see `increase_channel_counter`). - channel_ids_counter: u64, - - /// All the channels in the store. TODO Make new key PortId X ChanneId - channels: BTreeMap<(PortId, ChannelId), ChannelEnd>, - - /// Tracks the sequence number for the next packet to be sent. - next_sequence_send: BTreeMap<(PortId, ChannelId), Sequence>, - - /// Tracks the sequence number for the next packet to be received. - next_sequence_recv: BTreeMap<(PortId, ChannelId), Sequence>, - - /// Tracks the sequence number for the next packet to be acknowledged. - next_sequence_ack: BTreeMap<(PortId, ChannelId), Sequence>, - - packet_acknowledgement: BTreeMap<(PortId, ChannelId, Sequence), AcknowledgementCommitment>, - - /// Maps ports to the the module that owns it - port_to_module: BTreeMap, - - /// Constant-size commitments to packets data fields - packet_commitment: BTreeMap<(PortId, ChannelId, Sequence), PacketCommitment>, - - // Used by unordered channel - packet_receipt: BTreeMap<(PortId, ChannelId, Sequence), Receipt>, - /// Average time duration between blocks block_time: Duration, + /// An object that stores all IBC related data. + pub ibc_store: Arc>, + /// ICS26 router impl router: MockRouter, } @@ -138,6 +90,26 @@ impl Default for MockContext { } } +/// A manual clone impl is provided because the tests are oblivious to the fact that the `ibc_store` +/// is a shared ptr. +impl Clone for MockContext { + fn clone(&self) -> Self { + let ibc_store = { + let ibc_store = self.ibc_store.lock().unwrap().clone(); + Arc::new(Mutex::new(ibc_store)) + }; + Self { + host_chain_type: self.host_chain_type, + host_chain_id: self.host_chain_id.clone(), + max_history_size: self.max_history_size, + history: self.history.clone(), + block_time: self.block_time, + ibc_store, + router: self.router.clone(), + } + } +} + /// Implementation of internal interface for use in testing. The methods in this interface should /// _not_ be accessible to any Ics handler. impl MockContext { @@ -191,24 +163,8 @@ impl MockContext { ) }) .collect(), - connections: Default::default(), - client_ids_counter: 0, - clients: Default::default(), - client_processed_times: Default::default(), - client_processed_heights: Default::default(), - client_connections: Default::default(), - channels: Default::default(), - connection_channels: Default::default(), - next_sequence_send: Default::default(), - next_sequence_recv: Default::default(), - next_sequence_ack: Default::default(), - port_to_module: Default::default(), - packet_commitment: Default::default(), - packet_receipt: Default::default(), - packet_acknowledgement: Default::default(), - connection_ids_counter: 0, - channel_ids_counter: 0, block_time, + ibc_store: Arc::new(Mutex::new(MockIbcStore::default())), router: Default::default(), } } @@ -227,7 +183,7 @@ impl MockContext { /// `consensus_state_height` is None, then the client will be initialized with a consensus /// state matching the same height as the client state (`client_state_height`). pub fn with_client_parametrized( - mut self, + self, client_id: &ClientId, client_state_height: Height, client_type: Option, @@ -267,12 +223,16 @@ impl MockContext { client_state, consensus_states, }; - self.clients.insert(client_id.clone(), client_record); + self.ibc_store + .lock() + .unwrap() + .clients + .insert(client_id.clone(), client_record); self } pub fn with_client_parametrized_history( - mut self, + self, client_id: &ClientId, client_state_height: Height, client_type: Option, @@ -336,17 +296,25 @@ impl MockContext { consensus_states, }; - self.clients.insert(client_id.clone(), client_record); + self.ibc_store + .lock() + .unwrap() + .clients + .insert(client_id.clone(), client_record); self } /// Associates a connection to this context. pub fn with_connection( - mut self, + self, connection_id: ConnectionId, connection_end: ConnectionEnd, ) -> Self { - self.connections.insert(connection_id, connection_end); + self.ibc_store + .lock() + .unwrap() + .connections + .insert(connection_id, connection_end); self } @@ -357,9 +325,10 @@ impl MockContext { chan_id: ChannelId, channel_end: ChannelEnd, ) -> Self { - let mut channels = self.channels.clone(); + let mut channels = self.ibc_store.lock().unwrap().channels.clone(); channels.insert((port_id, chan_id), channel_end); - Self { channels, ..self } + self.ibc_store.lock().unwrap().channels = channels; + self } pub fn with_send_sequence( @@ -368,12 +337,10 @@ impl MockContext { chan_id: ChannelId, seq_number: Sequence, ) -> Self { - let mut next_sequence_send = self.next_sequence_send.clone(); + let mut next_sequence_send = self.ibc_store.lock().unwrap().next_sequence_send.clone(); next_sequence_send.insert((port_id, chan_id), seq_number); - Self { - next_sequence_send, - ..self - } + self.ibc_store.lock().unwrap().next_sequence_send = next_sequence_send; + self } pub fn with_recv_sequence( @@ -382,12 +349,10 @@ impl MockContext { chan_id: ChannelId, seq_number: Sequence, ) -> Self { - let mut next_sequence_recv = self.next_sequence_recv.clone(); + let mut next_sequence_recv = self.ibc_store.lock().unwrap().next_sequence_recv.clone(); next_sequence_recv.insert((port_id, chan_id), seq_number); - Self { - next_sequence_recv, - ..self - } + self.ibc_store.lock().unwrap().next_sequence_recv = next_sequence_recv; + self } pub fn with_ack_sequence( @@ -396,12 +361,10 @@ impl MockContext { chan_id: ChannelId, seq_number: Sequence, ) -> Self { - let mut next_sequence_ack = self.next_sequence_send.clone(); + let mut next_sequence_ack = self.ibc_store.lock().unwrap().next_sequence_send.clone(); next_sequence_ack.insert((port_id, chan_id), seq_number); - Self { - next_sequence_ack, - ..self - } + self.ibc_store.lock().unwrap().next_sequence_ack = next_sequence_ack; + self } pub fn with_height(self, target_height: Height) -> Self { @@ -432,12 +395,10 @@ impl MockContext { seq: Sequence, data: PacketCommitment, ) -> Self { - let mut packet_commitment = self.packet_commitment.clone(); + let mut packet_commitment = self.ibc_store.lock().unwrap().packet_commitment.clone(); packet_commitment.insert((port_id, chan_id, seq), data); - Self { - packet_commitment, - ..self - } + self.ibc_store.lock().unwrap().packet_commitment = packet_commitment; + self } pub fn with_router(self, router: MockRouter) -> Self { @@ -519,15 +480,23 @@ impl MockContext { pub fn add_port(&mut self, port_id: PortId) { let module_id = ModuleId::new(format!("module{}", port_id).into()).unwrap(); - self.port_to_module.insert(port_id, module_id); + self.ibc_store + .lock() + .unwrap() + .port_to_module + .insert(port_id, module_id); } pub fn scope_port_to_module(&mut self, port_id: PortId, module_id: ModuleId) { - self.port_to_module.insert(port_id, module_id); + self.ibc_store + .lock() + .unwrap() + .port_to_module + .insert(port_id, module_id); } pub fn consensus_states(&self, client_id: &ClientId) -> Vec { - self.clients[client_id] + self.ibc_store.lock().unwrap().clients[client_id] .consensus_states .iter() .map(|(k, v)| AnyConsensusStateWithHeight { @@ -537,19 +506,24 @@ impl MockContext { .collect() } - pub fn latest_client_states(&self, client_id: &ClientId) -> &AnyClientState { - self.clients[client_id].client_state.as_ref().unwrap() + pub fn latest_client_states(&self, client_id: &ClientId) -> AnyClientState { + self.ibc_store.lock().unwrap().clients[client_id] + .client_state + .as_ref() + .unwrap() + .clone() } pub fn latest_consensus_states( &self, client_id: &ClientId, height: &Height, - ) -> &AnyConsensusState { - self.clients[client_id] + ) -> AnyConsensusState { + self.ibc_store.lock().unwrap().clients[client_id] .consensus_states .get(height) .unwrap() + .clone() } #[inline] @@ -559,6 +533,65 @@ impl MockContext { .expect("history cannot be empty") .height() } + + pub fn ibc_store_share(&self) -> Arc> { + self.ibc_store.clone() + } +} + +/// An object that stores all IBC related data. +#[derive(Clone, Debug, Default)] +pub struct MockIbcStore { + /// The set of all clients, indexed by their id. + pub clients: BTreeMap, + + /// Tracks the processed time for clients header updates + pub client_processed_times: BTreeMap<(ClientId, Height), Timestamp>, + + /// Tracks the processed height for the clients + pub client_processed_heights: BTreeMap<(ClientId, Height), Height>, + + /// Counter for the client identifiers, necessary for `increase_client_counter` and the + /// `client_counter` methods. + pub client_ids_counter: u64, + + /// Association between client ids and connection ids. + pub client_connections: BTreeMap, + + /// All the connections in the store. + pub connections: BTreeMap, + + /// Counter for connection identifiers (see `increase_connection_counter`). + pub connection_ids_counter: u64, + + /// Association between connection ids and channel ids. + pub connection_channels: BTreeMap>, + + /// Counter for channel identifiers (see `increase_channel_counter`). + pub channel_ids_counter: u64, + + /// All the channels in the store. TODO Make new key PortId X ChanneId + pub channels: BTreeMap<(PortId, ChannelId), ChannelEnd>, + + /// Tracks the sequence number for the next packet to be sent. + pub next_sequence_send: BTreeMap<(PortId, ChannelId), Sequence>, + + /// Tracks the sequence number for the next packet to be received. + pub next_sequence_recv: BTreeMap<(PortId, ChannelId), Sequence>, + + /// Tracks the sequence number for the next packet to be acknowledged. + pub next_sequence_ack: BTreeMap<(PortId, ChannelId), Sequence>, + + pub packet_acknowledgement: BTreeMap<(PortId, ChannelId, Sequence), AcknowledgementCommitment>, + + /// Maps ports to the the module that owns it + pub port_to_module: BTreeMap, + + /// Constant-size commitments to packets data fields + pub packet_commitment: BTreeMap<(PortId, ChannelId, Sequence), PacketCommitment>, + + // Used by unordered channel + pub packet_receipt: BTreeMap<(PortId, ChannelId, Sequence), Receipt>, } #[derive(Default)] @@ -604,11 +637,9 @@ impl Ics26Context for MockContext { } } -impl Ics20Context for MockContext {} - impl PortReader for MockContext { fn lookup_module_by_port(&self, port_id: &PortId) -> Result { - match self.port_to_module.get(port_id) { + match self.ibc_store.lock().unwrap().port_to_module.get(port_id) { Some(mod_id) => Ok(mod_id.clone()), None => Err(Ics05Error::unknown_port(port_id.clone())), } @@ -617,7 +648,7 @@ impl PortReader for MockContext { impl ChannelReader for MockContext { fn channel_end(&self, pcid: &(PortId, ChannelId)) -> Result { - match self.channels.get(pcid) { + match self.ibc_store.lock().unwrap().channels.get(pcid) { Some(channel_end) => Ok(channel_end.clone()), None => Err(Ics04Error::channel_not_found(pcid.0.clone(), pcid.1)), } @@ -631,7 +662,7 @@ impl ChannelReader for MockContext { &self, cid: &ConnectionId, ) -> Result, Ics04Error> { - match self.connection_channels.get(cid) { + match self.ibc_store.lock().unwrap().connection_channels.get(cid) { Some(pcid) => Ok(pcid.clone()), None => Err(Ics04Error::missing_channel()), } @@ -655,7 +686,13 @@ impl ChannelReader for MockContext { &self, port_channel_id: &(PortId, ChannelId), ) -> Result { - match self.next_sequence_send.get(port_channel_id) { + match self + .ibc_store + .lock() + .unwrap() + .next_sequence_send + .get(port_channel_id) + { Some(sequence) => Ok(*sequence), None => Err(Ics04Error::missing_next_send_seq(port_channel_id.clone())), } @@ -665,7 +702,13 @@ impl ChannelReader for MockContext { &self, port_channel_id: &(PortId, ChannelId), ) -> Result { - match self.next_sequence_recv.get(port_channel_id) { + match self + .ibc_store + .lock() + .unwrap() + .next_sequence_recv + .get(port_channel_id) + { Some(sequence) => Ok(*sequence), None => Err(Ics04Error::missing_next_recv_seq(port_channel_id.clone())), } @@ -675,7 +718,13 @@ impl ChannelReader for MockContext { &self, port_channel_id: &(PortId, ChannelId), ) -> Result { - match self.next_sequence_ack.get(port_channel_id) { + match self + .ibc_store + .lock() + .unwrap() + .next_sequence_ack + .get(port_channel_id) + { Some(sequence) => Ok(*sequence), None => Err(Ics04Error::missing_next_ack_seq(port_channel_id.clone())), } @@ -685,7 +734,7 @@ impl ChannelReader for MockContext { &self, key: &(PortId, ChannelId, Sequence), ) -> Result { - match self.packet_commitment.get(key) { + match self.ibc_store.lock().unwrap().packet_commitment.get(key) { Some(commitment) => Ok(commitment.clone()), None => Err(Ics04Error::packet_commitment_not_found(key.2)), } @@ -695,7 +744,7 @@ impl ChannelReader for MockContext { &self, key: &(PortId, ChannelId, Sequence), ) -> Result { - match self.packet_receipt.get(key) { + match self.ibc_store.lock().unwrap().packet_receipt.get(key) { Some(receipt) => Ok(receipt.clone()), None => Err(Ics04Error::packet_receipt_not_found(key.2)), } @@ -705,7 +754,13 @@ impl ChannelReader for MockContext { &self, key: &(PortId, ChannelId, Sequence), ) -> Result { - match self.packet_acknowledgement.get(key) { + match self + .ibc_store + .lock() + .unwrap() + .packet_acknowledgement + .get(key) + { Some(ack) => Ok(ack.clone()), None => Err(Ics04Error::packet_acknowledgement_not_found(key.2)), } @@ -738,6 +793,9 @@ impl ChannelReader for MockContext { height: Height, ) -> Result { match self + .ibc_store + .lock() + .unwrap() .client_processed_times .get(&(client_id.clone(), height)) { @@ -755,6 +813,9 @@ impl ChannelReader for MockContext { height: Height, ) -> Result { match self + .ibc_store + .lock() + .unwrap() .client_processed_heights .get(&(client_id.clone(), height)) { @@ -767,7 +828,7 @@ impl ChannelReader for MockContext { } fn channel_counter(&self) -> Result { - Ok(self.channel_ids_counter) + Ok(self.ibc_store.lock().unwrap().channel_ids_counter) } fn max_expected_time_per_block(&self) -> Duration { @@ -781,7 +842,11 @@ impl ChannelKeeper for MockContext { key: (PortId, ChannelId, Sequence), commitment: PacketCommitment, ) -> Result<(), Ics04Error> { - self.packet_commitment.insert(key, commitment); + self.ibc_store + .lock() + .unwrap() + .packet_commitment + .insert(key, commitment); Ok(()) } @@ -790,7 +855,11 @@ impl ChannelKeeper for MockContext { key: (PortId, ChannelId, Sequence), ack_commitment: AcknowledgementCommitment, ) -> Result<(), Ics04Error> { - self.packet_acknowledgement.insert(key, ack_commitment); + self.ibc_store + .lock() + .unwrap() + .packet_acknowledgement + .insert(key, ack_commitment); Ok(()) } @@ -798,7 +867,11 @@ impl ChannelKeeper for MockContext { &mut self, key: (PortId, ChannelId, Sequence), ) -> Result<(), Ics04Error> { - self.packet_acknowledgement.remove(&key); + self.ibc_store + .lock() + .unwrap() + .packet_acknowledgement + .remove(&key); Ok(()) } @@ -807,7 +880,10 @@ impl ChannelKeeper for MockContext { cid: ConnectionId, port_channel_id: &(PortId, ChannelId), ) -> Result<(), Ics04Error> { - self.connection_channels + self.ibc_store + .lock() + .unwrap() + .connection_channels .entry(cid) .or_insert_with(Vec::new) .push(port_channel_id.clone()); @@ -819,7 +895,11 @@ impl ChannelKeeper for MockContext { port_channel_id: (PortId, ChannelId), channel_end: &ChannelEnd, ) -> Result<(), Ics04Error> { - self.channels.insert(port_channel_id, channel_end.clone()); + self.ibc_store + .lock() + .unwrap() + .channels + .insert(port_channel_id, channel_end.clone()); Ok(()) } @@ -828,7 +908,11 @@ impl ChannelKeeper for MockContext { port_channel_id: (PortId, ChannelId), seq: Sequence, ) -> Result<(), Ics04Error> { - self.next_sequence_send.insert(port_channel_id, seq); + self.ibc_store + .lock() + .unwrap() + .next_sequence_send + .insert(port_channel_id, seq); Ok(()) } @@ -837,7 +921,11 @@ impl ChannelKeeper for MockContext { port_channel_id: (PortId, ChannelId), seq: Sequence, ) -> Result<(), Ics04Error> { - self.next_sequence_recv.insert(port_channel_id, seq); + self.ibc_store + .lock() + .unwrap() + .next_sequence_recv + .insert(port_channel_id, seq); Ok(()) } @@ -846,19 +934,27 @@ impl ChannelKeeper for MockContext { port_channel_id: (PortId, ChannelId), seq: Sequence, ) -> Result<(), Ics04Error> { - self.next_sequence_ack.insert(port_channel_id, seq); + self.ibc_store + .lock() + .unwrap() + .next_sequence_ack + .insert(port_channel_id, seq); Ok(()) } fn increase_channel_counter(&mut self) { - self.channel_ids_counter += 1; + self.ibc_store.lock().unwrap().channel_ids_counter += 1; } fn delete_packet_commitment( &mut self, key: (PortId, ChannelId, Sequence), ) -> Result<(), Ics04Error> { - self.packet_commitment.remove(&key); + self.ibc_store + .lock() + .unwrap() + .packet_commitment + .remove(&key); Ok(()) } @@ -867,14 +963,18 @@ impl ChannelKeeper for MockContext { key: (PortId, ChannelId, Sequence), receipt: Receipt, ) -> Result<(), Ics04Error> { - self.packet_receipt.insert(key, receipt); + self.ibc_store + .lock() + .unwrap() + .packet_receipt + .insert(key, receipt); Ok(()) } } impl ConnectionReader for MockContext { fn connection_end(&self, cid: &ConnectionId) -> Result { - match self.connections.get(cid) { + match self.ibc_store.lock().unwrap().connections.get(cid) { Some(connection_end) => Ok(connection_end.clone()), None => Err(Ics03Error::connection_not_found(cid.clone())), } @@ -913,7 +1013,7 @@ impl ConnectionReader for MockContext { } fn connection_counter(&self) -> Result { - Ok(self.connection_ids_counter) + Ok(self.ibc_store.lock().unwrap().connection_ids_counter) } } @@ -923,7 +1023,10 @@ impl ConnectionKeeper for MockContext { connection_id: ConnectionId, connection_end: &ConnectionEnd, ) -> Result<(), Ics03Error> { - self.connections + self.ibc_store + .lock() + .unwrap() + .connections .insert(connection_id, connection_end.clone()); Ok(()) } @@ -933,26 +1036,29 @@ impl ConnectionKeeper for MockContext { connection_id: ConnectionId, client_id: &ClientId, ) -> Result<(), Ics03Error> { - self.client_connections + self.ibc_store + .lock() + .unwrap() + .client_connections .insert(client_id.clone(), connection_id); Ok(()) } fn increase_connection_counter(&mut self) { - self.connection_ids_counter += 1; + self.ibc_store.lock().unwrap().connection_ids_counter += 1; } } impl ClientReader for MockContext { fn client_type(&self, client_id: &ClientId) -> Result { - match self.clients.get(client_id) { + match self.ibc_store.lock().unwrap().clients.get(client_id) { Some(client_record) => Ok(client_record.client_type), None => Err(Ics02Error::client_not_found(client_id.clone())), } } fn client_state(&self, client_id: &ClientId) -> Result { - match self.clients.get(client_id) { + match self.ibc_store.lock().unwrap().clients.get(client_id) { Some(client_record) => client_record .client_state .clone() @@ -966,7 +1072,7 @@ impl ClientReader for MockContext { client_id: &ClientId, height: Height, ) -> Result { - match self.clients.get(client_id) { + match self.ibc_store.lock().unwrap().clients.get(client_id) { Some(client_record) => match client_record.consensus_states.get(&height) { Some(consensus_state) => Ok(consensus_state.clone()), None => Err(Ics02Error::consensus_state_not_found( @@ -987,7 +1093,8 @@ impl ClientReader for MockContext { client_id: &ClientId, height: Height, ) -> Result, Ics02Error> { - let client_record = self + let ibc_store = self.ibc_store.lock().unwrap(); + let client_record = ibc_store .clients .get(client_id) .ok_or_else(|| Ics02Error::client_not_found(client_id.clone()))?; @@ -1014,7 +1121,8 @@ impl ClientReader for MockContext { client_id: &ClientId, height: Height, ) -> Result, Ics02Error> { - let client_record = self + let ibc_store = self.ibc_store.lock().unwrap(); + let client_record = ibc_store .clients .get(client_id) .ok_or_else(|| Ics02Error::client_not_found(client_id.clone()))?; @@ -1060,7 +1168,7 @@ impl ClientReader for MockContext { } fn client_counter(&self) -> Result { - Ok(self.client_ids_counter) + Ok(self.ibc_store.lock().unwrap().client_ids_counter) } } @@ -1070,11 +1178,15 @@ impl ClientKeeper for MockContext { client_id: ClientId, client_type: ClientType, ) -> Result<(), Ics02Error> { - let mut client_record = self.clients.entry(client_id).or_insert(MockClientRecord { - client_type, - consensus_states: Default::default(), - client_state: Default::default(), - }); + let mut ibc_store = self.ibc_store.lock().unwrap(); + let client_record = ibc_store + .clients + .entry(client_id) + .or_insert(MockClientRecord { + client_type, + consensus_states: Default::default(), + client_state: Default::default(), + }); client_record.client_type = client_type; Ok(()) @@ -1085,11 +1197,15 @@ impl ClientKeeper for MockContext { client_id: ClientId, client_state: AnyClientState, ) -> Result<(), Ics02Error> { - let mut client_record = self.clients.entry(client_id).or_insert(MockClientRecord { - client_type: client_state.client_type(), - consensus_states: Default::default(), - client_state: Default::default(), - }); + let mut ibc_store = self.ibc_store.lock().unwrap(); + let client_record = ibc_store + .clients + .entry(client_id) + .or_insert(MockClientRecord { + client_type: client_state.client_type(), + consensus_states: Default::default(), + client_state: Default::default(), + }); client_record.client_state = Some(client_state); Ok(()) @@ -1101,11 +1217,15 @@ impl ClientKeeper for MockContext { height: Height, consensus_state: AnyConsensusState, ) -> Result<(), Ics02Error> { - let client_record = self.clients.entry(client_id).or_insert(MockClientRecord { - client_type: ClientType::Mock, - consensus_states: Default::default(), - client_state: Default::default(), - }); + let mut ibc_store = self.ibc_store.lock().unwrap(); + let client_record = ibc_store + .clients + .entry(client_id) + .or_insert(MockClientRecord { + client_type: ClientType::Mock, + consensus_states: Default::default(), + client_state: Default::default(), + }); client_record .consensus_states @@ -1114,7 +1234,7 @@ impl ClientKeeper for MockContext { } fn increase_client_counter(&mut self) { - self.client_ids_counter += 1 + self.ibc_store.lock().unwrap().client_ids_counter += 1 } fn store_update_time( @@ -1124,6 +1244,9 @@ impl ClientKeeper for MockContext { timestamp: Timestamp, ) -> Result<(), Ics02Error> { let _ = self + .ibc_store + .lock() + .unwrap() .client_processed_times .insert((client_id, height), timestamp); Ok(()) @@ -1136,6 +1259,9 @@ impl ClientKeeper for MockContext { host_height: Height, ) -> Result<(), Ics02Error> { let _ = self + .ibc_store + .lock() + .unwrap() .client_processed_heights .insert((client_id, height), host_height); Ok(()) @@ -1161,7 +1287,8 @@ impl Ics18Context for MockContext { // Forward call to Ics26 delivery method. let mut all_events = vec![]; for msg in msgs { - let (mut events, _) = deliver(self, msg).map_err(Ics18Error::transaction_failed)?; + let MsgReceipt { mut events, .. } = + deliver(self, msg).map_err(Ics18Error::transaction_failed)?; all_events.append(&mut events); } self.advance_host_chain_height(); // Advance chain height @@ -1186,13 +1313,15 @@ mod tests { use crate::core::ics24_host::identifier::ChainId; use crate::core::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; use crate::core::ics26_routing::context::{ - Acknowledgement, Module, ModuleId, ModuleOutput, OnRecvPacketAck, Router, RouterBuilder, + Acknowledgement, Module, ModuleId, ModuleOutputBuilder, OnRecvPacketAck, Router, + RouterBuilder, }; use crate::mock::context::MockContext; use crate::mock::context::MockRouterBuilder; use crate::mock::host::HostType; use crate::prelude::*; use crate::signer::Signer; + use crate::test_utils::get_dummy_bech32_account; use crate::Height; #[test] @@ -1356,12 +1485,13 @@ mod tests { impl Module for FooModule { fn on_chan_open_try( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _order: Order, _connection_hops: &[ConnectionId], _port_id: &PortId, _channel_id: &ChannelId, _counterparty: &Counterparty, + _version: &Version, counterparty_version: &Version, ) -> Result { Ok(counterparty_version.clone()) @@ -1369,7 +1499,7 @@ mod tests { fn on_recv_packet( &self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _packet: &Packet, _relayer: &Signer, ) -> OnRecvPacketAck { @@ -1378,6 +1508,7 @@ mod tests { Box::new(|module| { let module = module.downcast_mut::().unwrap(); module.counter += 1; + Ok(()) }), ) } @@ -1389,12 +1520,13 @@ mod tests { impl Module for BarModule { fn on_chan_open_try( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _order: Order, _connection_hops: &[ConnectionId], _port_id: &PortId, _channel_id: &ChannelId, _counterparty: &Counterparty, + _version: &Version, counterparty_version: &Version, ) -> Result { Ok(counterparty_version.clone()) @@ -1420,9 +1552,9 @@ mod tests { let module_id = ModuleId::from_str(module_id).unwrap(); let m = ctx.router.get_route_mut(&module_id).unwrap(); let result = m.on_recv_packet( - &mut ModuleOutput::builder().with_result(()), + &mut ModuleOutputBuilder::new(), &Packet::default(), - &Signer::new(""), + &get_dummy_bech32_account().parse().unwrap(), ); (module_id, result) }; @@ -1440,7 +1572,7 @@ mod tests { _ => None, }) .for_each(|(mid, write_fn)| { - write_fn(ctx.router.get_route_mut(&mid).unwrap().as_any_mut()) + write_fn(ctx.router.get_route_mut(&mid).unwrap().as_any_mut()).unwrap() }); } } diff --git a/modules/src/serializers.rs b/modules/src/serializers.rs index be74530812..bd6f346336 100644 --- a/modules/src/serializers.rs +++ b/modules/src/serializers.rs @@ -9,3 +9,30 @@ where let hex = Hex::upper_case().encode_to_string(data).unwrap(); hex.serialize(serializer) } + +pub mod serde_string { + use alloc::string::String; + use core::fmt::Display; + use core::str::FromStr; + + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &T, serializer: S) -> Result + where + T: Display, + S: Serializer, + { + serializer.collect_str(value) + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(de::Error::custom) + } +} diff --git a/modules/src/signer.rs b/modules/src/signer.rs index a5e4ea2432..21c62bf116 100644 --- a/modules/src/signer.rs +++ b/modules/src/signer.rs @@ -1,36 +1,36 @@ -use crate::prelude::*; -use core::{convert::Infallible, fmt::Display, str::FromStr}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct Signer(String); +use core::str::FromStr; -impl Signer { - pub fn new(s: impl ToString) -> Self { - Self(s.to_string()) - } +use crate::prelude::*; - pub fn as_str(&self) -> &str { - &self.0 - } -} +use derive_more::Display; +use flex_error::define_error; +use serde::{Deserialize, Serialize}; -impl Display for Signer { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.0) +define_error! { + #[derive(Debug, PartialEq, Eq)] + SignerError { + EmptySigner + | _ | { "signer cannot be empty" }, } } -impl From for Signer { - fn from(s: String) -> Self { - Self(s) - } -} +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Display)] +pub struct Signer(String); impl FromStr for Signer { - type Err = Infallible; + type Err = SignerError; fn from_str(s: &str) -> Result { - Ok(Self(s.to_string())) + let s = s.to_string(); + if s.trim().is_empty() { + return Err(SignerError::empty_signer()); + } + Ok(Self(s)) + } +} + +impl AsRef for Signer { + fn as_ref(&self) -> &str { + self.0.as_str() } } diff --git a/modules/src/test_utils.rs b/modules/src/test_utils.rs index 82302219b0..9dbbe0596b 100644 --- a/modules/src/test_utils.rs +++ b/modules/src/test_utils.rs @@ -1,14 +1,30 @@ -#![allow(dead_code)] +use std::sync::{Arc, Mutex}; +use std::time::Duration; -use crate::prelude::*; use tendermint::{block, consensus, evidence, public_key::Algorithm}; -use crate::core::ics04_channel::channel::{Counterparty, Order}; +use crate::applications::transfer::context::{BankKeeper, Ics20Context, Ics20Keeper, Ics20Reader}; +use crate::applications::transfer::{error::Error as Ics20Error, PrefixedCoin}; +use crate::core::ics02_client::client_consensus::AnyConsensusState; +use crate::core::ics02_client::client_state::AnyClientState; +use crate::core::ics02_client::error::Error as Ics02Error; +use crate::core::ics03_connection::connection::ConnectionEnd; +use crate::core::ics03_connection::error::Error as Ics03Error; +use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, Order}; +use crate::core::ics04_channel::commitment::{AcknowledgementCommitment, PacketCommitment}; +use crate::core::ics04_channel::context::{ChannelKeeper, ChannelReader}; use crate::core::ics04_channel::error::Error; +use crate::core::ics04_channel::packet::{Receipt, Sequence}; use crate::core::ics04_channel::Version; -use crate::core::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; -use crate::core::ics26_routing::context::{Module, ModuleOutput}; +use crate::core::ics05_port::context::PortReader; +use crate::core::ics05_port::error::Error as PortError; +use crate::core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; +use crate::core::ics26_routing::context::{Module, ModuleId, ModuleOutputBuilder}; +use crate::mock::context::MockIbcStore; +use crate::prelude::*; use crate::signer::Signer; +use crate::timestamp::Timestamp; +use crate::Height; // Needed in mocks. pub fn default_consensus_params() -> consensus::Params { @@ -44,20 +60,325 @@ pub fn get_dummy_bech32_account() -> String { "cosmos1wxeyh7zgn4tctjzs0vtqpc6p5cxq5t2muzl7ng".to_string() } -#[derive(Debug, Default)] -pub struct DummyModule; +#[derive(Debug)] +pub struct DummyTransferModule { + ibc_store: Arc>, +} -impl Module for DummyModule { +impl DummyTransferModule { + pub fn new(ibc_store: Arc>) -> Self { + Self { ibc_store } + } +} + +impl Module for DummyTransferModule { fn on_chan_open_try( &mut self, - _output: &mut ModuleOutput, + _output: &mut ModuleOutputBuilder, _order: Order, _connection_hops: &[ConnectionId], _port_id: &PortId, _channel_id: &ChannelId, _counterparty: &Counterparty, + _version: &Version, counterparty_version: &Version, ) -> Result { Ok(counterparty_version.clone()) } } + +impl Ics20Keeper for DummyTransferModule { + type AccountId = Signer; +} + +impl ChannelKeeper for DummyTransferModule { + fn store_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + commitment: PacketCommitment, + ) -> Result<(), Error> { + self.ibc_store + .lock() + .unwrap() + .packet_commitment + .insert(key, commitment); + Ok(()) + } + + fn delete_packet_commitment( + &mut self, + _key: (PortId, ChannelId, Sequence), + ) -> Result<(), Error> { + unimplemented!() + } + + fn store_packet_receipt( + &mut self, + _key: (PortId, ChannelId, Sequence), + _receipt: Receipt, + ) -> Result<(), Error> { + unimplemented!() + } + + fn store_packet_acknowledgement( + &mut self, + _key: (PortId, ChannelId, Sequence), + _ack: AcknowledgementCommitment, + ) -> Result<(), Error> { + unimplemented!() + } + + fn delete_packet_acknowledgement( + &mut self, + _key: (PortId, ChannelId, Sequence), + ) -> Result<(), Error> { + unimplemented!() + } + + fn store_connection_channels( + &mut self, + _conn_id: ConnectionId, + _port_channel_id: &(PortId, ChannelId), + ) -> Result<(), Error> { + unimplemented!() + } + + fn store_channel( + &mut self, + _port_channel_id: (PortId, ChannelId), + _channel_end: &ChannelEnd, + ) -> Result<(), Error> { + unimplemented!() + } + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Error> { + self.ibc_store + .lock() + .unwrap() + .next_sequence_send + .insert(port_channel_id, seq); + Ok(()) + } + + fn store_next_sequence_recv( + &mut self, + _port_channel_id: (PortId, ChannelId), + _seq: Sequence, + ) -> Result<(), Error> { + unimplemented!() + } + + fn store_next_sequence_ack( + &mut self, + _port_channel_id: (PortId, ChannelId), + _seq: Sequence, + ) -> Result<(), Error> { + unimplemented!() + } + + fn increase_channel_counter(&mut self) { + unimplemented!() + } +} + +impl PortReader for DummyTransferModule { + fn lookup_module_by_port(&self, _port_id: &PortId) -> Result { + unimplemented!() + } +} + +impl BankKeeper for DummyTransferModule { + type AccountId = Signer; + + fn send_coins( + &mut self, + _from: &Self::AccountId, + _to: &Self::AccountId, + _amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + Ok(()) + } + + fn mint_coins( + &mut self, + _account: &Self::AccountId, + _amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + Ok(()) + } + + fn burn_coins( + &mut self, + _account: &Self::AccountId, + _amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + Ok(()) + } +} + +impl Ics20Reader for DummyTransferModule { + type AccountId = Signer; + + fn get_port(&self) -> Result { + Ok(PortId::transfer()) + } + + fn is_send_enabled(&self) -> bool { + true + } + + fn is_receive_enabled(&self) -> bool { + true + } +} + +impl ChannelReader for DummyTransferModule { + fn channel_end(&self, pcid: &(PortId, ChannelId)) -> Result { + match self.ibc_store.lock().unwrap().channels.get(pcid) { + Some(channel_end) => Ok(channel_end.clone()), + None => Err(Error::channel_not_found(pcid.0.clone(), pcid.1)), + } + } + + fn connection_end(&self, cid: &ConnectionId) -> Result { + match self.ibc_store.lock().unwrap().connections.get(cid) { + Some(connection_end) => Ok(connection_end.clone()), + None => Err(Ics03Error::connection_not_found(cid.clone())), + } + .map_err(Error::ics03_connection) + } + + fn connection_channels(&self, _cid: &ConnectionId) -> Result, Error> { + unimplemented!() + } + + fn client_state(&self, client_id: &ClientId) -> Result { + match self.ibc_store.lock().unwrap().clients.get(client_id) { + Some(client_record) => client_record + .client_state + .clone() + .ok_or_else(|| Ics02Error::client_not_found(client_id.clone())), + None => Err(Ics02Error::client_not_found(client_id.clone())), + } + .map_err(|e| Error::ics03_connection(Ics03Error::ics02_client(e))) + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + match self.ibc_store.lock().unwrap().clients.get(client_id) { + Some(client_record) => match client_record.consensus_states.get(&height) { + Some(consensus_state) => Ok(consensus_state.clone()), + None => Err(Ics02Error::consensus_state_not_found( + client_id.clone(), + height, + )), + }, + None => Err(Ics02Error::consensus_state_not_found( + client_id.clone(), + height, + )), + } + .map_err(|e| Error::ics03_connection(Ics03Error::ics02_client(e))) + } + + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + match self + .ibc_store + .lock() + .unwrap() + .next_sequence_send + .get(port_channel_id) + { + Some(sequence) => Ok(*sequence), + None => Err(Error::missing_next_send_seq(port_channel_id.clone())), + } + } + + fn get_next_sequence_recv( + &self, + _port_channel_id: &(PortId, ChannelId), + ) -> Result { + unimplemented!() + } + + fn get_next_sequence_ack( + &self, + _port_channel_id: &(PortId, ChannelId), + ) -> Result { + unimplemented!() + } + + fn get_packet_commitment( + &self, + _key: &(PortId, ChannelId, Sequence), + ) -> Result { + unimplemented!() + } + + fn get_packet_receipt(&self, _key: &(PortId, ChannelId, Sequence)) -> Result { + unimplemented!() + } + + fn get_packet_acknowledgement( + &self, + _key: &(PortId, ChannelId, Sequence), + ) -> Result { + unimplemented!() + } + + fn hash(&self, value: Vec) -> Vec { + use sha2::Digest; + + sha2::Sha256::digest(value).to_vec() + } + + fn host_height(&self) -> Height { + Height::zero() + } + + fn host_consensus_state(&self, _height: Height) -> Result { + unimplemented!() + } + + fn pending_host_consensus_state(&self) -> Result { + unimplemented!() + } + + fn client_update_time( + &self, + _client_id: &ClientId, + _height: Height, + ) -> Result { + unimplemented!() + } + + fn client_update_height( + &self, + _client_id: &ClientId, + _height: Height, + ) -> Result { + unimplemented!() + } + + fn channel_counter(&self) -> Result { + unimplemented!() + } + + fn max_expected_time_per_block(&self) -> Duration { + unimplemented!() + } +} + +impl Ics20Context for DummyTransferModule { + type AccountId = Signer; +} diff --git a/modules/tests/runner/mod.rs b/modules/tests/runner/mod.rs index 8c8183a697..061b1ef81f 100644 --- a/modules/tests/runner/mod.rs +++ b/modules/tests/runner/mod.rs @@ -163,7 +163,9 @@ impl IbcTestRunner { } fn signer() -> Signer { - Signer::new("") + "cosmos1wxeyh7zgn4tctjzs0vtqpc6p5cxq5t2muzl7ng" + .parse() + .unwrap() } pub fn counterparty(client_id: u64, connection_id: Option) -> Counterparty { diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 3efe874c0b..fb56558e28 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -153,6 +153,9 @@ pub mod ibc { pub mod v1 { include_proto!("ibc.applications.transfer.v1.rs"); } + pub mod v2 { + include_proto!("ibc.applications.transfer.v2.rs"); + } } pub mod interchain_accounts { pub mod v1 { diff --git a/relayer-cli/src/commands/tx/transfer.rs b/relayer-cli/src/commands/tx/transfer.rs index 8875e19172..860a7c51e2 100644 --- a/relayer-cli/src/commands/tx/transfer.rs +++ b/relayer-cli/src/commands/tx/transfer.rs @@ -3,6 +3,7 @@ use abscissa_core::{config::Override, Command, FrameworkErrorKind, Runnable}; use core::time::Duration; use ibc::{ + applications::transfer::Amount, core::{ ics02_client::client_state::ClientState, ics02_client::height::Height, @@ -14,7 +15,6 @@ use ibc_relayer::chain::handle::ChainHandle; use ibc_relayer::chain::requests::{ QueryChannelRequest, QueryClientStateRequest, QueryConnectionRequest, }; -use ibc_relayer::transfer::Amount; use ibc_relayer::{ config::Config, transfer::{build_and_send_transfer_messages, TransferOptions}, diff --git a/relayer/Cargo.toml b/relayer/Cargo.toml index a624793ef6..80956ac780 100644 --- a/relayer/Cargo.toml +++ b/relayer/Cargo.toml @@ -37,7 +37,7 @@ serde_json = { version = "1" } bytes = "1.1.0" prost = { version = "0.10" } prost-types = { version = "0.10" } -tonic = { version = "0.7", features = ["tls", "tls-roots"] } +tonic = { version = "0.7.2", features = ["tls", "tls-roots"] } futures = "0.3.21" crossbeam-channel = "0.5.4" k256 = { version = "0.10.4", features = ["ecdsa-core", "ecdsa", "sha256"]} @@ -58,7 +58,6 @@ flex-error = { version = "0.4.4", default-features = false } signature = "1.4.0" anyhow = "1.0" semver = "1.0" -uint = "0.9" humantime = "2.1.0" nanoid = "0.4.0" regex = "1.5.5" diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index fc671656c3..34b931dae7 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -603,7 +603,9 @@ impl ChainEndpoint for CosmosSdkChain { .map_err(|e| Error::key_not_found(self.config.key_name.clone(), e))?; let bech32 = encode_to_bech32(&key.address.to_hex(), &self.config.account_prefix)?; - Ok(Signer::new(bech32)) + bech32 + .parse() + .map_err(|e| Error::ics02(ClientError::signer(e))) } /// Get the chain configuration diff --git a/relayer/src/channel/version.rs b/relayer/src/channel/version.rs index 56218a267e..dd2d74cb4d 100644 --- a/relayer/src/channel/version.rs +++ b/relayer/src/channel/version.rs @@ -5,13 +5,13 @@ //! handshake. use ibc::{ - applications::ics20_fungible_token_transfer, + applications::transfer, core::{ics04_channel::Version, ics24_host::identifier::PortId}, }; /// Returns the default channel version, depending on the the given [`PortId`]. pub fn default_by_port(port_id: &PortId) -> Option { - if port_id.as_str() == ics20_fungible_token_transfer::PORT_ID { + if port_id.as_str() == transfer::PORT_ID_STR { // https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#forwards-compatibility Some(Version::ics20()) } else { diff --git a/relayer/src/transfer.rs b/relayer/src/transfer.rs index 45b5930c5d..b21f4dd73b 100644 --- a/relayer/src/transfer.rs +++ b/relayer/src/transfer.rs @@ -1,23 +1,22 @@ -use core::fmt::{Display, Formatter}; -use core::str::FromStr; use core::time::Duration; use flex_error::{define_error, DetailOnly}; -use ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; +use ibc::applications::transfer::error::Error as Ics20Error; +use ibc::applications::transfer::msgs::transfer::MsgTransfer; +use ibc::applications::transfer::Amount; use ibc::core::ics24_host::identifier::{ChainId, ChannelId, PortId}; use ibc::events::IbcEvent; use ibc::signer::Signer; use ibc::timestamp::{Timestamp, TimestampOverflowError}; use ibc::tx_msg::Msg; use ibc::Height; +use ibc_proto::cosmos::base::v1beta1::Coin; use ibc_proto::google::protobuf::Any; -use uint::FromStrRadixErr; use crate::chain::handle::ChainHandle; use crate::chain::tracking::TrackedMsgs; use crate::chain::ChainStatus; use crate::error::Error; -use crate::util::bigint::U256; define_error! { TransferError { @@ -55,34 +54,15 @@ define_error! { e.event) }, + TokenTransfer + [ Ics20Error ] + |_| { "Token transfer error" }, + ZeroTimeout | _ | { "packet timeout height and packet timeout timestamp cannot both be 0" }, } } -#[derive(Clone, Copy, Debug, Default)] -pub struct Amount(pub U256); - -impl Display for Amount { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl FromStr for Amount { - type Err = FromStrRadixErr; - - fn from_str(s: &str) -> Result { - Ok(Self(U256::from_str_radix(s, 10)?)) - } -} - -impl From for Amount { - fn from(amount: u64) -> Self { - Self(amount.into()) - } -} - #[derive(Copy, Clone)] pub struct TransferTimeout { pub timeout_height: Height, @@ -150,10 +130,10 @@ pub fn build_transfer_message( let msg = MsgTransfer { source_port: packet_src_port_id, source_channel: packet_src_channel_id, - token: Some(ibc_proto::cosmos::base::v1beta1::Coin { + token: Coin { denom, amount: amount.to_string(), - }), + }, sender, receiver, timeout_height, @@ -168,10 +148,7 @@ pub fn build_and_send_transfer_messages Result, TransferError> { - let receiver = match &opts.receiver { - None => packet_dst_chain.get_signer().map_err(TransferError::key)?, - Some(r) => r.clone().into(), - }; + let receiver = packet_dst_chain.get_signer().map_err(TransferError::key)?; let sender = packet_src_chain.get_signer().map_err(TransferError::key)?; @@ -188,10 +165,10 @@ pub fn build_and_send_transfer_messages( let transfer_options = TransferOptions { packet_src_port_id: channel.port_a.value().clone(), packet_src_channel_id: *channel.channel_id_a.value(), - amount: Amount(amount.into()), + amount: amount.into(), denom: denom.value().to_string(), receiver: Some(recipient.value().0.clone()), timeout_height_offset, diff --git a/tools/test-framework/src/ibc/denom.rs b/tools/test-framework/src/ibc/denom.rs index e0059c3887..ec4bcabc6c 100644 --- a/tools/test-framework/src/ibc/denom.rs +++ b/tools/test-framework/src/ibc/denom.rs @@ -4,7 +4,9 @@ use core::fmt::{self, Display}; use eyre::Report as Error; -use ibc::applications::ics20_fungible_token_transfer as token_transfer; +use ibc::core::ics24_host::identifier::{ChannelId, PortId}; +use sha2::{Digest, Sha256}; +use subtle_encoding::hex; use crate::types::id::{TaggedChannelIdRef, TaggedPortIdRef}; use crate::types::tagged::*; @@ -56,10 +58,30 @@ pub fn derive_ibc_denom( channel_id: &TaggedChannelIdRef, denom: &TaggedDenomRef, ) -> Result, Error> { + fn derive_denom( + port_id: &PortId, + channel_id: &ChannelId, + denom: &str, + ) -> Result { + let transfer_path = format!("{}/{}/{}", port_id, channel_id, denom); + derive_denom_with_path(&transfer_path) + } + + /// Derive the transferred token denomination using + /// + fn derive_denom_with_path(transfer_path: &str) -> Result { + let mut hasher = Sha256::new(); + hasher.update(transfer_path.as_bytes()); + + let denom_bytes = hasher.finalize(); + let denom_hex = String::from_utf8(hex::encode_upper(denom_bytes))?; + + Ok(format!("ibc/{}", denom_hex)) + } + match denom.value() { Denom::Base(denom) => { - let hashed = - token_transfer::derive_ibc_denom(port_id.value(), channel_id.value(), denom)?; + let hashed = derive_denom(port_id.value(), channel_id.value(), denom)?; Ok(MonoTagged::new(Denom::Ibc { path: format!("{}/{}", port_id, channel_id), @@ -69,8 +91,7 @@ pub fn derive_ibc_denom( } Denom::Ibc { path, denom, .. } => { let new_path = format!("{}/{}/{}", port_id, channel_id, path); - let hashed = - token_transfer::derive_ibc_denom_with_path(&format!("{}/{}", new_path, denom))?; + let hashed = derive_denom_with_path(&format!("{}/{}", new_path, denom))?; Ok(MonoTagged::new(Denom::Ibc { path: new_path, diff --git a/tools/test-framework/src/relayer/transfer.rs b/tools/test-framework/src/relayer/transfer.rs index d4ac86aff0..3af20851a5 100644 --- a/tools/test-framework/src/relayer/transfer.rs +++ b/tools/test-framework/src/relayer/transfer.rs @@ -5,12 +5,14 @@ use core::ops::Add; use core::time::Duration; -use ibc::signer::Signer; + +use ibc::applications::transfer::error::Error as Ics20Error; use ibc::timestamp::Timestamp; use ibc::Height; use ibc_proto::google::protobuf::Any; use ibc_relayer::chain::cosmos::types::config::TxConfig; use ibc_relayer::transfer::build_transfer_message as raw_build_transfer_message; +use ibc_relayer::transfer::TransferError; use crate::error::{handle_generic_error, Error}; use crate::ibc::denom::Denom; @@ -31,13 +33,26 @@ pub fn build_transfer_message( .add(Duration::from_secs(60)) .map_err(handle_generic_error)?; + let sender = sender + .value() + .address + .0 + .parse() + .map_err(|e| TransferError::token_transfer(Ics20Error::signer(e)))?; + + let receiver = recipient + .value() + .0 + .parse() + .map_err(|e| TransferError::token_transfer(Ics20Error::signer(e)))?; + Ok(raw_build_transfer_message( (*port_id.value()).clone(), **channel_id.value(), amount.into(), - denom.value().to_string(), - Signer::new(sender.value().address.0.clone()), - Signer::new(recipient.value().0.clone()), + denom.to_string(), + sender, + receiver, Height::zero(), timeout_timestamp, ))