From c13b888f907494910de113845fbbe559d50112bf Mon Sep 17 00:00:00 2001 From: Rano | Ranadeep Date: Fri, 10 Jan 2025 12:27:19 +0100 Subject: [PATCH] feat(e2e): native token transfer from cosmos to starknet (#172) * fix ack type * rm redundant todo comment * relay cosmos ics20 packet to starknet * log client states * use simd ibc v9 with wasm * use cosmos_to_starknet_relay * infallible cosmos packet conversion to cairo packet * fix packet timeout logic in cairo * add crypto-bigint dep * refactor ibc packet to cairo packet conversion * ics20 transfer from cosmos to starknet * use cosmos.nix main branch * return all ibc token addresses * query starknet balance * add new wiring * add IbcTransferSendMessage * impl Display traits * fix sender receiver parsing * transfer back to cosmos from starknet * fix typo * 0x1 for ics20 ack success * fix trace prefix * fix sec nanosec conversion * consistent receipt with cosmos * use Vec for ack * add asserts after transfer back * cairo consistent ics20 type names --- .../src/transfer/components/transfer.cairo | 28 +- .../src/transfer/interfaces/transfer.cairo | 3 + .../packages/core/src/channel/types.cairo | 5 +- flake.lock | 44 ++- flake.nix | 2 +- relayer/Cargo.lock | 1 + .../src/components/encode_mut.rs | 2 + .../starknet-chain-components/Cargo.toml | 1 + .../src/components/chain.rs | 8 + .../src/components/encoding/cairo.rs | 6 +- .../src/impls/events/ack.rs | 9 +- .../src/impls/messages/packet.rs | 167 +++++++- .../src/impls/queries/packet_receipt.rs | 9 +- .../starknet_to_cosmos/channel_message.rs | 1 - .../src/types/messages/ibc/denom.rs | 23 ++ .../src/types/messages/ibc/ibc_transfer.rs | 44 ++- .../src/types/messages/ibc/packet.rs | 46 +-- .../src/contexts/chain.rs | 4 + .../src/contexts/encoding/cairo.rs | 4 +- .../src/tests/ics20.rs | 366 +++++++++++------- 20 files changed, 557 insertions(+), 216 deletions(-) diff --git a/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo b/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo index 02fe1778..0c1ce1b1 100644 --- a/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo +++ b/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo @@ -11,7 +11,7 @@ pub mod TokenTransferComponent { use starknet::ContractAddress; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, - StoragePointerWriteAccess + StoragePointerWriteAccess, Vec, VecTrait, MutableVecTrait }; use starknet::{get_contract_address, get_caller_address}; use starknet_ibc_apps::transfer::types::{ @@ -36,6 +36,7 @@ pub mod TokenTransferComponent { salt: felt252, ibc_token_key_to_address: Map, ibc_token_address_to_key: Map, + token_addresses: Vec, } #[event] @@ -282,6 +283,10 @@ pub mod TokenTransferComponent { address } + + fn ibc_token_addresses(self: @ComponentState) -> Array { + self.read_ibc_token_addresses() + } } // ----------------------------------------------------------- @@ -776,6 +781,8 @@ pub mod TokenTransferComponent { ) { let denom_key = denom.key(); + self.append_ibc_token_address(token_address); + self.write_ibc_token_key_to_address(denom_key, token_address); self.write_ibc_token_address_to_key(token_address, denom_key); @@ -839,6 +846,19 @@ pub mod TokenTransferComponent { self.ibc_token_key_to_address.read(token_key) } + fn read_ibc_token_addresses( + self: @ComponentState + ) -> Array { + let mut addresses = array![]; + for i in 0 + ..self + .token_addresses + .len() { + addresses.append(self.token_addresses.at(i).read()); + }; + addresses + } + fn read_ibc_token_key( self: @ComponentState, token_address: ContractAddress ) -> felt252 { @@ -860,6 +880,12 @@ pub mod TokenTransferComponent { self.salt.write(salt); } + fn append_ibc_token_address( + ref self: ComponentState, token_address: ContractAddress, + ) { + self.token_addresses.append().write(token_address); + } + fn write_ibc_token_key_to_address( ref self: ComponentState, token_key: felt252, diff --git a/cairo-contracts/packages/apps/src/transfer/interfaces/transfer.cairo b/cairo-contracts/packages/apps/src/transfer/interfaces/transfer.cairo index 2de2d033..dc91c804 100644 --- a/cairo-contracts/packages/apps/src/transfer/interfaces/transfer.cairo +++ b/cairo-contracts/packages/apps/src/transfer/interfaces/transfer.cairo @@ -25,5 +25,8 @@ pub trait ITransferQuery { /// ``` /// Hashing the denom is delegated to the client as it is more cost-efficient. fn ibc_token_address(self: @TContractState, token_key: felt252) -> ContractAddress; + + /// Return the contract addresses of all IBC tokens. + fn ibc_token_addresses(self: @TContractState) -> Array; } diff --git a/cairo-contracts/packages/core/src/channel/types.cairo b/cairo-contracts/packages/core/src/channel/types.cairo index 794a4eab..16c1db86 100644 --- a/cairo-contracts/packages/core/src/channel/types.cairo +++ b/cairo-contracts/packages/core/src/channel/types.cairo @@ -30,8 +30,9 @@ pub impl PacketImpl of PacketTrait { /// Checks if the packet is timed out. fn is_timed_out(self: @Packet, latest_height: @Height, latest_timestamp: @Timestamp) -> bool { - !(self.timeout_height_on_b > latest_height - && self.timeout_timestamp_on_b > latest_timestamp) + !((self.timeout_height_on_b.is_zero() || self.timeout_height_on_b > latest_height) + && (self.timeout_timestamp_on_b.is_zero() + || self.timeout_timestamp_on_b > latest_timestamp)) } } diff --git a/flake.lock b/flake.lock index 48f562f0..8e3af8ac 100644 --- a/flake.lock +++ b/flake.lock @@ -216,6 +216,7 @@ "ibc-go-v8-src": "ibc-go-v8-src", "ibc-go-v8-wasm-src": "ibc-go-v8-wasm-src", "ibc-go-v9-src": "ibc-go-v9-src", + "ibc-go-v9-wasm-src": "ibc-go-v9-wasm-src", "ibc-rs-src": "ibc-rs-src", "ica-src": "ica-src", "ignite-cli-src": "ignite-cli-src", @@ -262,14 +263,15 @@ "wasmvm_2_0_3-src": "wasmvm_2_0_3-src", "wasmvm_2_1_0-src": "wasmvm_2_1_0-src", "wasmvm_2_1_2-src": "wasmvm_2_1_2-src", - "wasmvm_2_1_3-src": "wasmvm_2_1_3-src" + "wasmvm_2_1_3-src": "wasmvm_2_1_3-src", + "wasmvm_2_1_4-src": "wasmvm_2_1_4-src" }, "locked": { - "lastModified": 1733317488, - "narHash": "sha256-Kr/h0A92hdsnFGAX4ujwLrwlk1voYJkkBp7pHwa4/ag=", + "lastModified": 1736331154, + "narHash": "sha256-4B1divgZCXyiMGqL5KRcJp9xDEulHAE2zb7hpbbkXxM=", "owner": "informalsystems", "repo": "cosmos.nix", - "rev": "a615496057a069e03674f804e2b089ac67aac925", + "rev": "662d2ec410e5d0a0a069aafe41458b2a2ace654d", "type": "github" }, "original": { @@ -1138,6 +1140,23 @@ "type": "github" } }, + "ibc-go-v9-wasm-src": { + "flake": false, + "locked": { + "lastModified": 1734690421, + "narHash": "sha256-E0LxbmwuGAM3c9OvncRpfvxeQHYbJS2PADGTuI0w38Y=", + "owner": "cosmos", + "repo": "ibc-go", + "rev": "ae2bfabb6df53c000c1212ae41b7d0a0743b851a", + "type": "github" + }, + "original": { + "owner": "cosmos", + "ref": "08-wasm/release/v0.5.x%2Bibc-go-v9.0.x-wasmvm-v2.1.x", + "repo": "ibc-go", + "type": "github" + } + }, "ibc-rs-src": { "flake": false, "locked": { @@ -2324,6 +2343,23 @@ "repo": "wasmvm", "type": "github" } + }, + "wasmvm_2_1_4-src": { + "flake": false, + "locked": { + "lastModified": 1733824920, + "narHash": "sha256-Bkrp8ocQftzfAAgZ8D8Gifi4qQJl32Urazf4ZAcCnpI=", + "owner": "CosmWasm", + "repo": "wasmvm", + "rev": "1ddd563a80546192e10a9cf38060b5ee5262808e", + "type": "github" + }, + "original": { + "owner": "CosmWasm", + "ref": "v2.1.4", + "repo": "wasmvm", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 16224a64..d16924a0 100644 --- a/flake.nix +++ b/flake.nix @@ -59,7 +59,7 @@ rust-1_79 = nixpkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain-1.79.toml; - wasm-simapp = cosmos-nix.ibc-go-v8-wasm-simapp; + wasm-simapp = cosmos-nix.ibc-go-v9-wasm-simapp; osmosis = cosmos-nix.osmosis; diff --git a/relayer/Cargo.lock b/relayer/Cargo.lock index 3bd553cb..2cf43c65 100644 --- a/relayer/Cargo.lock +++ b/relayer/Cargo.lock @@ -2216,6 +2216,7 @@ version = "0.1.0" dependencies = [ "cairo-lang-starknet-classes", "cgp", + "crypto-bigint", "hermes-cairo-encoding-components", "hermes-chain-components", "hermes-chain-type-components", diff --git a/relayer/crates/cairo-encoding-components/src/components/encode_mut.rs b/relayer/crates/cairo-encoding-components/src/components/encode_mut.rs index bd8d6ed3..c46ddb27 100644 --- a/relayer/crates/cairo-encoding-components/src/components/encode_mut.rs +++ b/relayer/crates/cairo-encoding-components/src/components/encode_mut.rs @@ -18,6 +18,8 @@ cgp_preset! { (ViaCairo, Felt): EncodeFelt, (ViaCairo, u128): EncodeU128, (ViaCairo, U256): EncodeU256, + // TODO(rano): ByteArray and Array are different types in Cairo + // for now, we CANNOT use Vec to deserialize to Array (ViaCairo, Vec): EncodeByteArray, (ViaCairo, Vec): EncodeList, (ViaCairo, bool): EncodeBool, diff --git a/relayer/crates/starknet-chain-components/Cargo.toml b/relayer/crates/starknet-chain-components/Cargo.toml index a6bb0a99..c6e201e6 100644 --- a/relayer/crates/starknet-chain-components/Cargo.toml +++ b/relayer/crates/starknet-chain-components/Cargo.toml @@ -35,3 +35,4 @@ serde = { workspace = true } serde_json = { workspace = true } starknet = { workspace = true } tonic = { workspace = true } +crypto-bigint = "0.5.5" diff --git a/relayer/crates/starknet-chain-components/src/components/chain.rs b/relayer/crates/starknet-chain-components/src/components/chain.rs index 3d2bcc4a..5b9d3217 100644 --- a/relayer/crates/starknet-chain-components/src/components/chain.rs +++ b/relayer/crates/starknet-chain-components/src/components/chain.rs @@ -41,10 +41,14 @@ pub use hermes_relayer_components::transaction::traits::submit_tx::TxSubmitterCo pub use hermes_relayer_components::transaction::traits::types::transaction::TransactionTypeComponent; pub use hermes_relayer_components::transaction::traits::types::tx_hash::TransactionHashTypeComponent; pub use hermes_relayer_components::transaction::traits::types::tx_response::TxResponseTypeComponent; +use hermes_test_components::chain::impls::ibc_transfer::SendIbcTransferMessage; use hermes_test_components::chain::traits::queries::balance::BalanceQuerierComponent; +use hermes_test_components::chain::traits::transfer::ibc_transfer::TokenIbcTransferrerComponent; +use hermes_test_components::chain::traits::transfer::string_memo::ProvideStringMemoType; pub use hermes_test_components::chain::traits::types::address::AddressTypeComponent; pub use hermes_test_components::chain::traits::types::amount::AmountTypeComponent; pub use hermes_test_components::chain::traits::types::denom::DenomTypeComponent; +use hermes_test_components::chain::traits::types::memo::MemoTypeComponent; use crate::components::types::StarknetChainTypes; use crate::impls::commitment_prefix::GetStarknetCommitmentPrefix; @@ -146,6 +150,10 @@ cgp_preset! { ProvideU256Amount, DenomTypeComponent: ProvideTokenAddressDenom, + MemoTypeComponent: + ProvideStringMemoType, + TokenIbcTransferrerComponent: + SendIbcTransferMessage, TransactionTypeComponent: ProvideCallTransaction, TransactionHashTypeComponent: diff --git a/relayer/crates/starknet-chain-components/src/components/encoding/cairo.rs b/relayer/crates/starknet-chain-components/src/components/encoding/cairo.rs index 48de308f..8f30538d 100644 --- a/relayer/crates/starknet-chain-components/src/components/encoding/cairo.rs +++ b/relayer/crates/starknet-chain-components/src/components/encoding/cairo.rs @@ -51,7 +51,8 @@ use crate::types::messages::ibc::denom::{ Denom, EncodeDenom, EncodePrefixedDenom, EncodeTracePrefix, PrefixedDenom, TracePrefix, }; use crate::types::messages::ibc::ibc_transfer::{ - EncodeIbcTransferMessage, EncodeParticipant, IbcTransferMessage, Participant, + EncodeMsgTransfer, EncodeParticipant, EncodeTransferPacketData, MsgTransfer, Participant, + TransferPacketData, }; use crate::types::messages::ibc::packet::{ AckStatus, Acknowledgement, EncodeAckStatus, EncodeAcknowledgement, EncodeMsgAckPacket, @@ -107,7 +108,8 @@ delegate_components! { (ViaCairo, Vec): EncodeList, (ViaCairo, PrefixedDenom): EncodePrefixedDenom, (ViaCairo, Participant): EncodeParticipant, - (ViaCairo, IbcTransferMessage): EncodeIbcTransferMessage, + (ViaCairo, TransferPacketData): EncodeTransferPacketData, + (ViaCairo, MsgTransfer): EncodeMsgTransfer, (ViaCairo, Height): EncodeHeight, (ViaCairo, Timestamp): EncodeTimestamp, (ViaCairo, Packet): EncodePacket, diff --git a/relayer/crates/starknet-chain-components/src/impls/events/ack.rs b/relayer/crates/starknet-chain-components/src/impls/events/ack.rs index 2c8b2a5d..9ee2a18c 100644 --- a/relayer/crates/starknet-chain-components/src/impls/events/ack.rs +++ b/relayer/crates/starknet-chain-components/src/impls/events/ack.rs @@ -56,8 +56,11 @@ where }) } - fn write_acknowledgement(_ack: &WriteAcknowledgementEvent) -> impl AsRef> + Send { - // TODO(rano): ack.acknowledgement.ack is Vec - vec![0x1] + fn write_acknowledgement(ack: &WriteAcknowledgementEvent) -> impl AsRef> + Send { + ack.acknowledgement + .ack + .iter() + .map(|&felt| felt.try_into().unwrap()) + .collect::>() } } diff --git a/relayer/crates/starknet-chain-components/src/impls/messages/packet.rs b/relayer/crates/starknet-chain-components/src/impls/messages/packet.rs index d759bfb0..e948885a 100644 --- a/relayer/crates/starknet-chain-components/src/impls/messages/packet.rs +++ b/relayer/crates/starknet-chain-components/src/impls/messages/packet.rs @@ -22,7 +22,9 @@ use hermes_chain_type_components::traits::types::address::HasAddressType; use hermes_encoding_components::traits::encode::CanEncode; use hermes_encoding_components::traits::has_encoding::HasEncoding; use hermes_encoding_components::traits::types::encoded::HasEncodedType; +use ibc::apps::transfer::types::packet::PacketData as IbcIcs20PacketData; use ibc::core::channel::types::packet::Packet as IbcPacket; +use ibc::core::channel::types::timeout::{TimeoutHeight, TimeoutTimestamp}; use ibc::core::client::types::Height; use starknet::accounts::Call; use starknet::core::types::Felt; @@ -31,6 +33,10 @@ use starknet::macros::selector; use crate::impls::types::message::StarknetMessage; use crate::traits::queries::address::CanQueryContractAddress; use crate::types::cosmos::height::Height as CairoHeight; +use crate::types::messages::ibc::denom::{Denom, PrefixedDenom, TracePrefix}; +use crate::types::messages::ibc::ibc_transfer::{ + Participant, TransferPacketData as CairoTransferPacketData, +}; use crate::types::messages::ibc::packet::{ Acknowledgement as CairoAck, MsgAckPacket, MsgRecvPacket, MsgTimeoutPacket, Packet as CairoPacket, Sequence, StateProof, @@ -45,8 +51,7 @@ where + HasAddressType
+ CanQueryContractAddress + HasEncoding - + CanRaiseError - + CanRaiseError<&'static str>, + + CanRaiseError, Counterparty: HasOutgoingPacketType + HasHeightType + HasCommitmentProofType @@ -54,11 +59,13 @@ where Chain, ReceivePacketPayload = ReceivePacketPayload, >, - Encoding: CanEncode + HasEncodedType>, + Encoding: CanEncode + + CanEncode + + HasEncodedType>, { async fn build_receive_packet_message( chain: &Chain, - packet: &Counterparty::OutgoingPacket, + packet: &IbcPacket, counterparty_payload: ReceivePacketPayload, ) -> Result { // FIXME: commitment proof should be in the ByteArray format, not Vec @@ -72,7 +79,7 @@ where }; let receive_packet_msg = MsgRecvPacket { - packet: CairoPacket::try_from(packet.clone()).map_err(Chain::raise_error)?, + packet: from_cosmos_to_cairo_packet(packet, chain.encoding()), proof_commitment_on_a, proof_height_on_a, }; @@ -105,14 +112,15 @@ where + CanQueryContractAddress + HasEncoding + CanRaiseError - + CanRaiseError<&'static str> + HasOutgoingPacketType + HasErrorType, Counterparty: HasAckPacketPayloadType> + HasHeightType + HasCommitmentProofType + HasAcknowledgementType>, - Encoding: CanEncode + HasEncodedType>, + Encoding: CanEncode + + CanEncode + + HasEncodedType>, { async fn build_ack_packet_message( chain: &Chain, @@ -130,10 +138,13 @@ where }; let ack_packet_msg = MsgAckPacket { - packet: CairoPacket::try_from(packet.clone()).map_err(Chain::raise_error)?, + packet: from_cosmos_to_cairo_packet(packet, chain.encoding()), acknowledgement: CairoAck { - // TODO(rano): cairo accepts Vec, but Cosmos sends Vec - ack: vec![Felt::ONE], + ack: counterparty_payload + .ack + .into_iter() + .map(Felt::from) + .collect(), }, proof_ack_on_b, proof_height_on_b, @@ -167,7 +178,6 @@ where + CanQueryContractAddress + HasEncoding + CanRaiseError - + CanRaiseError<&'static str> + HasOutgoingPacketType + HasErrorType, Counterparty: HasHeightType @@ -176,7 +186,9 @@ where Chain, TimeoutUnorderedPacketPayload = TimeoutUnorderedPacketPayload, >, - Encoding: CanEncode + HasEncodedType>, + Encoding: CanEncode + + CanEncode + + HasEncodedType>, { async fn build_timeout_unordered_packet_message( chain: &Chain, @@ -194,7 +206,7 @@ where }; let timeout_packet_msg = MsgTimeoutPacket { - packet: CairoPacket::try_from(packet.clone()).map_err(Chain::raise_error)?, + packet: from_cosmos_to_cairo_packet(packet, chain.encoding()), // Cairo only accepts unordered packets. // So, this sequence is ignored. next_seq_recv_on_b: Sequence { sequence: 1 }, @@ -221,3 +233,132 @@ where Ok(message) } } + +fn from_cosmos_to_cairo_packet(packet: &IbcPacket, encoding: &Encoding) -> CairoPacket +where + Encoding: CanEncode + HasEncodedType>, +{ + let sequence = packet.seq_on_a.value(); + let src_port_id = packet.port_id_on_a.to_string(); + let src_channel_id = packet.chan_id_on_a.to_string(); + let dst_port_id = packet.port_id_on_b.to_string(); + let dst_channel_id = packet.chan_id_on_b.to_string(); + + // TODO(rano): the packet data needs to serialized to Vec. + // to do that, we assume PacketData struct (i.e. ICS20) and construct it. + // ideally, Cairo contract should accept the serialized data directly. + + // deserialize to ibc ics20 packet message + + let ibc_ics20_packet_data: IbcIcs20PacketData = serde_json::from_slice(&packet.data).unwrap(); + + // convert to cairo packet message + + // TODO(rano): can't iter. need fix at ibc-rs side + // for now, using json hack + let trace_path_json = + serde_json::to_string(&ibc_ics20_packet_data.token.denom.trace_path).unwrap(); + + #[derive(serde::Deserialize)] + struct DummyTracePath { + pub port_id: String, + pub channel_id: String, + } + + let trace_path: Vec = serde_json::from_str(&trace_path_json).unwrap(); + + let denom = PrefixedDenom { + trace_path: trace_path + .into_iter() + .map( + |DummyTracePath { + port_id, + channel_id, + }| TracePrefix { + port_id, + channel_id, + }, + ) + .collect(), + base: Denom::Hosted( + ibc_ics20_packet_data + .token + .denom + .base_denom + .as_str() + .to_string(), + ), + }; + + let amount = { + let bytes = ibc_ics20_packet_data.token.amount.as_ref().0; + crypto_bigint::U256::from(bytes).into() + }; + + let sender_string = ibc_ics20_packet_data.sender.as_ref().to_string(); + let receiver_string = ibc_ics20_packet_data.receiver.as_ref().to_string(); + + // TODO(rano): the following is a hack + // do we really need Participant variants? + + let sender = sender_string + .parse() + .map(Participant::Native) + .unwrap_or_else(|_| Participant::External(sender_string)); + + let receiver = receiver_string + .parse() + .map(Participant::Native) + .unwrap_or_else(|_| Participant::External(receiver_string)); + + match (&sender, &receiver) { + (Participant::Native(_), Participant::Native(_)) => { + panic!("Native to Native transfer is not supported") + } + (Participant::External(_), Participant::External(_)) => { + panic!("External to External transfer is not supported") + } + _ => {} + } + + let memo = ibc_ics20_packet_data.memo.as_ref().to_string(); + + let cairo_ics20_packet_data = CairoTransferPacketData { + denom, + amount, + sender, + receiver, + memo, + }; + + // serialize to vec + + let data_felt = encoding.encode(&cairo_ics20_packet_data).unwrap(); + + let timeout_height = match packet.timeout_height_on_b { + TimeoutHeight::Never => CairoHeight { + revision_number: 0, + revision_height: 0, + }, + TimeoutHeight::At(height) => CairoHeight { + revision_number: height.revision_number(), + revision_height: height.revision_height(), + }, + }; + + let timeout_timestamp = match packet.timeout_timestamp_on_b { + TimeoutTimestamp::Never => 0, + TimeoutTimestamp::At(timeout_timestamp) => timeout_timestamp.nanoseconds() / 1_000_000_000, + }; + + CairoPacket { + sequence, + src_port_id, + src_channel_id, + dst_port_id, + dst_channel_id, + data: data_felt, + timeout_height, + timeout_timestamp, + } +} diff --git a/relayer/crates/starknet-chain-components/src/impls/queries/packet_receipt.rs b/relayer/crates/starknet-chain-components/src/impls/queries/packet_receipt.rs index cea75da3..5b84555b 100644 --- a/relayer/crates/starknet-chain-components/src/impls/queries/packet_receipt.rs +++ b/relayer/crates/starknet-chain-components/src/impls/queries/packet_receipt.rs @@ -85,10 +85,13 @@ where // TODO(rano): are these bytes correct? let receipt_bytes = if receipt_status { - b"SUCCESS".to_vec() + // 0x01 -> "AQ==" + br#"{"result":"AQ=="}"# } else { - b"FAIL".to_vec() - }; + // 0x00 -> "AA==" + br#"{"result":"AA=="}"# + } + .to_vec(); Ok((receipt_bytes, dummy_proof)) } diff --git a/relayer/crates/starknet-chain-components/src/impls/starknet_to_cosmos/channel_message.rs b/relayer/crates/starknet-chain-components/src/impls/starknet_to_cosmos/channel_message.rs index 1b99d042..06406423 100644 --- a/relayer/crates/starknet-chain-components/src/impls/starknet_to_cosmos/channel_message.rs +++ b/relayer/crates/starknet-chain-components/src/impls/starknet_to_cosmos/channel_message.rs @@ -102,7 +102,6 @@ where .parse() .map_err(Chain::raise_error)?; - // TODO(rano): how to get channel end here ? let channel_end = IbcChannelEnd { state: IbcChannelState::TryOpen, ordering, diff --git a/relayer/crates/starknet-chain-components/src/types/messages/ibc/denom.rs b/relayer/crates/starknet-chain-components/src/types/messages/ibc/denom.rs index b2495a49..8858f760 100644 --- a/relayer/crates/starknet-chain-components/src/types/messages/ibc/denom.rs +++ b/relayer/crates/starknet-chain-components/src/types/messages/ibc/denom.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use cgp::core::component::UseContext; use cgp::prelude::*; use hermes_cairo_encoding_components::impls::encode_mut::variant_from::EncodeVariantFrom; @@ -15,12 +17,33 @@ pub enum Denom { Hosted(String), } +impl Display for Denom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Denom::Native(denom) => write!(f, "{}", denom), + Denom::Hosted(denom) => write!(f, "{}", denom), + } + } +} + #[derive(Debug, HasField)] pub struct PrefixedDenom { pub trace_path: Vec, pub base: Denom, } +impl Display for PrefixedDenom { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for prefix in self.trace_path.iter().rev() { + write!(f, "{}/{}/", prefix.port_id, prefix.channel_id)?; + } + + write!(f, "{}", self.base)?; + + Ok(()) + } +} + #[derive(Debug, HasField)] pub struct TracePrefix { pub port_id: String, diff --git a/relayer/crates/starknet-chain-components/src/types/messages/ibc/ibc_transfer.rs b/relayer/crates/starknet-chain-components/src/types/messages/ibc/ibc_transfer.rs index e06e4e12..dad9d034 100644 --- a/relayer/crates/starknet-chain-components/src/types/messages/ibc/ibc_transfer.rs +++ b/relayer/crates/starknet-chain-components/src/types/messages/ibc/ibc_transfer.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use cgp::core::component::UseContext; use cgp::prelude::*; use hermes_cairo_encoding_components::impls::encode_mut::variant_from::EncodeVariantFrom; @@ -8,10 +10,14 @@ use hermes_encoding_components::traits::encode_mut::MutEncoderComponent; use hermes_encoding_components::traits::transform::{Transformer, TransformerRef}; use starknet::core::types::{Felt, U256}; +use super::channel::PortId; +use crate::types::channel_id::ChannelId; +use crate::types::cosmos::height::Height; +use crate::types::cosmos::timestamp::Timestamp; use crate::types::messages::ibc::denom::PrefixedDenom; #[derive(HasField)] -pub struct IbcTransferMessage { +pub struct TransferPacketData { pub denom: PrefixedDenom, pub amount: U256, pub sender: Participant, @@ -19,7 +25,7 @@ pub struct IbcTransferMessage { pub memo: String, } -pub type EncodeIbcTransferMessage = CombineEncoders< +pub type EncodeTransferPacketData = CombineEncoders< Product![ EncodeField, EncodeField, @@ -29,12 +35,46 @@ pub type EncodeIbcTransferMessage = CombineEncoders< ], >; +#[derive(HasField)] +pub struct MsgTransfer { + pub port_id_on_a: PortId, + pub chan_id_on_a: ChannelId, + pub packet_data: TransferPacketData, + pub timeout_height_on_b: Height, + pub timeout_timestamp_on_b: Timestamp, +} + +pub struct EncodeMsgTransfer; + +delegate_components! { + EncodeMsgTransfer { + MutEncoderComponent: CombineEncoders< + Product![ + EncodeField, + EncodeField, + EncodeField, + EncodeField, + EncodeField, + ], + >, + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Participant { Native(Felt), External(String), } +impl Display for Participant { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Participant::Native(address) => write!(f, "{}", address), + Participant::External(address) => write!(f, "{}", address), + } + } +} + pub struct EncodeParticipant; delegate_components! { diff --git a/relayer/crates/starknet-chain-components/src/types/messages/ibc/packet.rs b/relayer/crates/starknet-chain-components/src/types/messages/ibc/packet.rs index d07d5a28..569abb9b 100644 --- a/relayer/crates/starknet-chain-components/src/types/messages/ibc/packet.rs +++ b/relayer/crates/starknet-chain-components/src/types/messages/ibc/packet.rs @@ -7,8 +7,6 @@ use hermes_encoding_components::impls::encode_mut::from::DecodeFrom; use hermes_encoding_components::traits::decode_mut::MutDecoderComponent; use hermes_encoding_components::traits::encode_mut::MutEncoderComponent; use hermes_encoding_components::traits::transform::{Transformer, TransformerRef}; -use ibc::core::channel::types::packet::Packet as IbcPacket; -use ibc::core::channel::types::timeout::{TimeoutHeight, TimeoutTimestamp}; use starknet::core::types::Felt; use crate::types::cosmos::height::Height; @@ -25,46 +23,6 @@ pub struct Packet { pub timeout_timestamp: u64, } -impl TryFrom for Packet { - type Error = &'static str; - - fn try_from(packet: IbcPacket) -> Result { - let sequence = packet.seq_on_a.value(); - let src_port_id = packet.port_id_on_a.to_string(); - let src_channel_id = packet.chan_id_on_a.to_string(); - let dst_port_id = packet.port_id_on_b.to_string(); - let dst_channel_id = packet.chan_id_on_b.to_string(); - - // TODO(rano): implement conversion from felt to bytes - // let _data_bytes = packet.data; - let data_felt = vec![Felt::ONE]; - - let TimeoutHeight::At(timeout_height) = packet.timeout_height_on_b else { - return Err("timeout_height_on_b is unset"); - }; - - let timeout_height = Height { - revision_number: timeout_height.revision_number(), - revision_height: timeout_height.revision_height(), - }; - - let TimeoutTimestamp::At(timeout_timestamp) = packet.timeout_timestamp_on_b else { - return Err("timeout_timestamp_on_b is unset"); - }; - - Ok(Self { - sequence, - src_port_id, - src_channel_id, - dst_port_id, - dst_channel_id, - data: data_felt, - timeout_height, - timeout_timestamp: timeout_timestamp.nanoseconds() / 1_000_000, - }) - } -} - pub struct EncodePacket; delegate_components! { @@ -117,6 +75,8 @@ impl Transformer for EncodePacket { #[derive(HasField)] pub struct StateProof { + // TODO(rano): Array in Cairo + // Currently, Vec is serialized for ByteArray pub proof: Vec, } @@ -181,6 +141,8 @@ impl Transformer for EncodeMsgRecvPacket { #[derive(HasField, Debug, Clone)] pub struct Acknowledgement { + // TODO(rano): Array in Cairo + // Currently, Vec is serialized for ByteArray pub ack: Vec, } diff --git a/relayer/crates/starknet-chain-context/src/contexts/chain.rs b/relayer/crates/starknet-chain-context/src/contexts/chain.rs index 3ec6e5b8..31213d86 100644 --- a/relayer/crates/starknet-chain-context/src/contexts/chain.rs +++ b/relayer/crates/starknet-chain-context/src/contexts/chain.rs @@ -151,6 +151,7 @@ use hermes_starknet_chain_components::types::message_response::StarknetMessageRe use hermes_starknet_test_components::impls::types::wallet::ProvideStarknetWalletType; use hermes_test_components::chain::traits::queries::balance::CanQueryBalance; use hermes_test_components::chain::traits::types::address::HasAddressType; +use hermes_test_components::chain::traits::types::memo::HasMemoType; use hermes_test_components::chain::traits::types::wallet::WalletTypeComponent; use ibc::core::channel::types::packet::Packet; use ibc::core::host::types::identifiers::{PortId as IbcPortId, Sequence}; @@ -362,6 +363,9 @@ pub trait CanUseStarknetChain: + HasPacketReceiptType> + HasSequenceType + CanQueryBalance + + HasMemoType + // TODO(rano): need this to >::ibc_transfer_token + // + CanIbcTransferToken { } diff --git a/relayer/crates/starknet-chain-context/src/contexts/encoding/cairo.rs b/relayer/crates/starknet-chain-context/src/contexts/encoding/cairo.rs index e53152eb..c7fa9caf 100644 --- a/relayer/crates/starknet-chain-context/src/contexts/encoding/cairo.rs +++ b/relayer/crates/starknet-chain-context/src/contexts/encoding/cairo.rs @@ -42,7 +42,7 @@ use hermes_starknet_chain_components::types::messages::ibc::denom::{ Denom, PrefixedDenom, TracePrefix, }; use hermes_starknet_chain_components::types::messages::ibc::ibc_transfer::{ - IbcTransferMessage, Participant, + Participant, TransferPacketData, }; use hermes_starknet_chain_components::types::messages::ibc::packet::Packet; use hermes_starknet_chain_components::types::register::{MsgRegisterApp, MsgRegisterClient}; @@ -127,7 +127,7 @@ pub trait CanUseCairoEncoding: + CanEncodeAndDecode + CanEncodeAndDecode> + CanEncodeAndDecode - + CanEncode + + CanEncode + CanEncodeAndDecode + CanEncodeAndDecode + CanEncodeAndDecode diff --git a/relayer/crates/starknet-integration-tests/src/tests/ics20.rs b/relayer/crates/starknet-integration-tests/src/tests/ics20.rs index 52fa188a..aa5b94bd 100644 --- a/relayer/crates/starknet-integration-tests/src/tests/ics20.rs +++ b/relayer/crates/starknet-integration-tests/src/tests/ics20.rs @@ -4,47 +4,57 @@ use core::time::Duration; use std::path::PathBuf; use std::time::SystemTime; -use eyre::eyre; -use hermes_chain_components::traits::queries::chain_status::CanQueryChainHeight; -use hermes_chain_components::traits::queries::client_state::CanQueryClientState; +use hermes_chain_components::traits::queries::client_state::CanQueryClientStateWithLatestHeight; use hermes_cosmos_chain_components::types::channel::CosmosInitChannelOptions; use hermes_cosmos_chain_components::types::connection::CosmosInitConnectionOptions; use hermes_cosmos_integration_tests::init::init_test_runtime; use hermes_cosmos_relayer::contexts::build::CosmosBuilder; use hermes_cosmos_relayer::contexts::chain::CosmosChain; +use hermes_cosmos_test_components::chain::types::amount::Amount; use hermes_cosmos_wasm_relayer::context::cosmos_bootstrap::CosmosWithWasmClientBootstrap; +use hermes_encoding_components::traits::decode::CanDecode; use hermes_encoding_components::traits::encode::CanEncode; use hermes_error::types::Error; use hermes_relayer_components::chain::traits::send_message::CanSendSingleMessage; use hermes_relayer_components::relay::impls::channel::bootstrap::CanBootstrapChannel; use hermes_relayer_components::relay::impls::connection::bootstrap::CanBootstrapConnection; use hermes_relayer_components::relay::traits::client_creator::CanCreateClient; +use hermes_relayer_components::relay::traits::packet_relayer::CanRelayPacket; use hermes_relayer_components::relay::traits::target::{DestinationTarget, SourceTarget}; use hermes_runtime_components::traits::fs::read_file::CanReadFileAsString; use hermes_starknet_chain_components::impls::encoding::events::CanFilterDecodeEvents; use hermes_starknet_chain_components::impls::types::message::StarknetMessage; +use hermes_starknet_chain_components::traits::contract::call::CanCallContract; use hermes_starknet_chain_components::traits::contract::declare::CanDeclareContract; use hermes_starknet_chain_components::traits::contract::deploy::CanDeployContract; -use hermes_starknet_chain_components::traits::queries::token_balance::CanQueryTokenBalance; use hermes_starknet_chain_components::types::cosmos::height::Height; +use hermes_starknet_chain_components::types::cosmos::timestamp::Timestamp; use hermes_starknet_chain_components::types::events::ics20::IbcTransferEvent; use hermes_starknet_chain_components::types::events::packet::PacketRelayEvents; use hermes_starknet_chain_components::types::messages::ibc::channel::PortId; -use hermes_starknet_chain_components::types::messages::ibc::denom::{Denom, PrefixedDenom}; -use hermes_starknet_chain_components::types::messages::ibc::ibc_transfer::{ - IbcTransferMessage, Participant, +use hermes_starknet_chain_components::types::messages::ibc::denom::{ + Denom, PrefixedDenom, TracePrefix, }; -use hermes_starknet_chain_components::types::messages::ibc::packet::{ - MsgRecvPacket, Packet, StateProof, +use hermes_starknet_chain_components::types::messages::ibc::ibc_transfer::{ + MsgTransfer, Participant, TransferPacketData, }; use hermes_starknet_chain_components::types::payloads::client::StarknetCreateClientPayloadOptions; use hermes_starknet_chain_components::types::register::{MsgRegisterApp, MsgRegisterClient}; +use hermes_starknet_chain_context::contexts::chain::StarknetChain; use hermes_starknet_chain_context::contexts::encoding::cairo::StarknetCairoEncoding; use hermes_starknet_chain_context::contexts::encoding::event::StarknetEventEncoding; +use hermes_starknet_relayer::contexts::cosmos_to_starknet_relay::CosmosToStarknetRelay; use hermes_starknet_relayer::contexts::starknet_to_cosmos_relay::StarknetToCosmosRelay; use hermes_test_components::bootstrap::traits::chain::CanBootstrapChain; +use hermes_test_components::chain::traits::queries::balance::CanQueryBalance; +use hermes_test_components::chain::traits::transfer::ibc_transfer::CanIbcTransferToken; +use ibc::apps::transfer::types::packet::PacketData; +use ibc::core::channel::types::packet::Packet as IbcPacket; +use ibc::core::channel::types::timeout::{TimeoutHeight, TimeoutTimestamp}; +use ibc::core::client::types::Height as IbcHeight; use ibc::core::connection::types::version::Version as IbcConnectionVersion; use ibc::core::host::types::identifiers::{ConnectionId, PortId as IbcPortId}; +use ibc::primitives::Timestamp as IbcTimestamp; use sha2::{Digest, Sha256}; use starknet::accounts::Call; use starknet::core::types::Felt; @@ -240,6 +250,18 @@ fn test_starknet_ics20_contract() -> Result<(), Error> { info!("created client on Cosmos: {:?}", cosmos_client_id); + let client_state_on_starknet = starknet_chain + .query_client_state_with_latest_height(PhantomData::, &starknet_client_id) + .await?; + + info!("client state on Starknet: {:?}", client_state_on_starknet); + + let client_state_on_cosmos = cosmos_chain + .query_client_state_with_latest_height(PhantomData::, &cosmos_client_id) + .await?; + + info!("client state on Cosmos: {:?}", client_state_on_cosmos); + let ics20_contract_address = { let owner_call_data = cairo_encoding.encode(&ibc_core_address)?; let erc20_call_data = cairo_encoding.encode(&erc20_class_hash)?; @@ -262,7 +284,15 @@ fn test_starknet_ics20_contract() -> Result<(), Error> { starknet_chain.clone(), cosmos_chain.clone(), starknet_client_id.clone(), - cosmos_client_id, + cosmos_client_id.clone(), + ); + + let cosmos_to_starknet_relay = CosmosToStarknetRelay::new( + runtime.clone(), + cosmos_chain.clone(), + starknet_chain.clone(), + cosmos_client_id.clone(), + starknet_client_id.clone(), ); // connection handshake @@ -330,188 +360,244 @@ fn test_starknet_ics20_contract() -> Result<(), Error> { info!("starknet_channel_id: {:?}", starknet_channel_id); info!("cosmos_channel_id: {:?}", cosmos_channel_id); - // submit dummy PacketRecv message to IBC core contract + // submit ics20 transfer to Cosmos - // stub - let sender_address = "cosmos1wxeyh7zgn4tctjzs0vtqpc6p5cxq5t2muzl7ng".to_string(); + let wallet_cosmos_a = &cosmos_chain_driver.user_wallet_a; + let address_cosmos_a = &wallet_cosmos_a.address; + let wallet_starknet_b = &starknet_chain_driver.user_wallet_b; + let address_starknet_b = &wallet_starknet_b.account_address; + let transfer_quantity = 1000; + let denom_cosmos = &cosmos_chain_driver.genesis_config.transfer_denom; - let recipient_address = starknet_chain_driver.user_wallet_a.account_address; + let balance_cosmos_a_step_0 = cosmos_chain + .query_balance(address_cosmos_a, denom_cosmos) + .await?; - let amount = 99u32; + info!( + "cosmos balance before transfer: {:?}", + balance_cosmos_a_step_0 + ); - let starknet_client_state = { - starknet_chain - .query_client_state( - PhantomData::, - &starknet_client_id, - &starknet_chain.query_chain_height().await?, + let packet = >::ibc_transfer_token( + cosmos_chain, + &cosmos_channel_id, + &IbcPortId::transfer(), + wallet_cosmos_a, + address_starknet_b, + &Amount::new(transfer_quantity, denom_cosmos.clone()), + &None, + ) + .await?; + + // TODO(rano): how do I get the ics20 token contract address from starknet events + cosmos_to_starknet_relay.relay_packet(&packet).await?; + + let balance_cosmos_a_step_1 = cosmos_chain + .query_balance(address_cosmos_a, denom_cosmos) + .await?; + + info!( + "cosmos balance after transfer: {:?}", + balance_cosmos_a_step_1 + ); + + assert_eq!( + balance_cosmos_a_step_0.quantity, + balance_cosmos_a_step_1.quantity + transfer_quantity + ); + + // TODO(rano): we should use the poseidon hash of the ibc denom to get the token address + + let ics20_token_address = { + let output = starknet_chain + .call_contract( + &ics20_contract_address, + &selector!("ibc_token_addresses"), + &vec![], ) - .await? + .await?; + + let addresses: Vec = cairo_encoding.decode(&output)?; + + assert_eq!(addresses.len(), 1); + + addresses[0] }; - let mut msg_recv_packet = { - let transfer_message = IbcTransferMessage { - denom: PrefixedDenom { - trace_path: Vec::new(), - base: Denom::Hosted("uatom".into()), - }, - amount: amount.into(), - sender: Participant::External(sender_address.clone()), - receiver: Participant::Native(recipient_address), - memo: "".into(), + info!("ics20 token address: {:?}", ics20_token_address); + + let balance_starknet_b_step_1 = starknet_chain + .query_balance(address_starknet_b, &ics20_token_address) + .await?; + + info!( + "starknet balance after transfer: {:?}", + balance_starknet_b_step_1 + ); + + assert_eq!(balance_starknet_b_step_1.quantity, transfer_quantity.into()); + + // TODO(rano): send back the ics20 token to cosmos + + // create ibc transfer packet data + + let starknet_ic20_packet_data = { + let denom = PrefixedDenom { + trace_path: vec![TracePrefix { + port_id: ics20_port.to_string(), + channel_id: starknet_channel_id.channel_id.clone(), + }], + base: Denom::Hosted(denom_cosmos.to_string()), }; - let packet_data = cairo_encoding.encode(&transfer_message)?; + let amount = transfer_quantity.into(); + + let sender = Participant::Native(*address_starknet_b); - let current_starknet_height = starknet_chain.query_chain_height().await?; + let receiver = Participant::External(address_cosmos_a.clone()); + + let memo = String::new(); + + TransferPacketData { + denom, + amount, + sender, + receiver, + memo, + } + }; + + // create ibc transfer message + + let starknet_ics20_send_message = { let current_starknet_time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH)? .as_secs(); - let packet = Packet { - sequence: 1, - src_port_id: IbcPortId::transfer().to_string(), - src_channel_id: cosmos_channel_id.to_string(), - dst_port_id: IbcPortId::transfer().to_string(), - dst_channel_id: starknet_channel_id.channel_id, - data: packet_data, - // both timeout height and timestamp are checked - timeout_height: Height { + MsgTransfer { + port_id_on_a: PortId { + port_id: ics20_port.to_string(), + }, + chan_id_on_a: starknet_channel_id.clone(), + packet_data: starknet_ic20_packet_data, + timeout_height_on_b: Height { revision_number: 0, - revision_height: current_starknet_height + 100, + revision_height: 0, + }, + timeout_timestamp_on_b: Timestamp { + timestamp: current_starknet_time + 1800, }, - timeout_timestamp: current_starknet_time + 100, - }; - - MsgRecvPacket { - packet, - proof_commitment_on_a: StateProof { - proof: vec![Felt::ONE], - }, // stub - proof_height_on_a: starknet_client_state.latest_height.clone(), } }; - let token_address = { - let calldata = cairo_encoding.encode(&msg_recv_packet)?; + // submit to ics20 contract + + let (send_packet_event, send_ics20_event) = { + let call_data = cairo_encoding.encode(&starknet_ics20_send_message)?; let call = Call { - to: ibc_core_address, - selector: selector!("recv_packet"), - calldata, + to: ics20_contract_address, + selector: selector!("send_transfer"), + calldata: call_data, }; let message = StarknetMessage::new(call); - let response = starknet_chain.send_message(message.clone()).await?; + let response = starknet_chain.send_message(message).await?; - info!("IBC transfer response: {:?}", response); + info!("ICS20 send packet response: {:?}", response); - let ibc_packet_events: Vec = + let mut ibc_packet_events: Vec = event_encoding.filter_decode_events(&response.events)?; info!("IBC packet events: {:?}", ibc_packet_events); - let ibc_transfer_events: Vec = + let mut ibc_transfer_events: Vec = event_encoding.filter_decode_events(&response.events)?; info!("IBC transfer events: {:?}", ibc_transfer_events); - { - let receive_transfer_event = ibc_transfer_events - .iter() - .find_map(|event| { - if let IbcTransferEvent::Receive(event) = event { - Some(event) - } else { - None - } - }) - .ok_or_else(|| eyre!("expect create token event"))?; - - assert_eq!(receive_transfer_event.amount, amount.into()); - - assert_eq!( - receive_transfer_event.sender, - Participant::External(sender_address) - ); - assert_eq!( - receive_transfer_event.receiver, - Participant::Native(recipient_address) - ); - } - - let token_address = { - let create_token_event = ibc_transfer_events - .iter() - .find_map(|event| { - if let IbcTransferEvent::CreateToken(event) = event { - Some(event) - } else { - None - } - }) - .ok_or_else(|| eyre!("expect create token event"))?; - - assert_eq!(create_token_event.initial_supply, amount.into()); + assert_eq!(ibc_packet_events.len(), 1); + assert_eq!(ibc_transfer_events.len(), 1); - let token_address = create_token_event.address; - - info!("created token address: {:?}", token_address); - - token_address + let Some(PacketRelayEvents::Send(send_packet_event)) = ibc_packet_events.pop() else { + panic!("expected send packet event"); }; - { - let recipient_balance = starknet_chain - .query_token_balance(&token_address, &recipient_address) - .await?; - - info!("recipient balance after transfer: {}", recipient_balance); - - assert_eq!(recipient_balance.quantity, amount.into()); - } + let Some(IbcTransferEvent::Send(send_ics20_event)) = ibc_transfer_events.pop() else { + panic!("expected send ics20 event"); + }; - token_address + (send_packet_event, send_ics20_event) }; - { - // Send the same transfer message a second time - // but increase the packet sequence number - msg_recv_packet.packet.sequence += 1; - - let calldata = cairo_encoding.encode(&msg_recv_packet)?; + // create ibc packet - let call = Call { - to: ibc_core_address, - selector: selector!("recv_packet"), - calldata, + let starknet_ibc_packet = { + let timeout_height_on_b = IbcHeight::new( + send_packet_event.timeout_height_on_b.revision_number, + send_packet_event.timeout_height_on_b.revision_height, + ) + .map(TimeoutHeight::At) + .unwrap_or_else(|_| TimeoutHeight::Never); + + let timeout_timestamp_on_b = (send_packet_event.timeout_timestamp_on_b.timestamp > 0) + .then(|| { + TimeoutTimestamp::At(IbcTimestamp::from_nanoseconds( + send_packet_event.timeout_timestamp_on_b.timestamp * 1_000_000_000, + )) + }) + .unwrap_or(TimeoutTimestamp::Never); + + let ibc_transfer_packet_data = PacketData { + token: format!("{}{}", send_ics20_event.amount, send_ics20_event.denom).parse()?, + sender: send_ics20_event.sender.to_string().into(), + receiver: send_ics20_event.receiver.to_string().into(), + memo: send_ics20_event.memo.into(), }; - let message = StarknetMessage::new(call); + IbcPacket { + seq_on_a: send_packet_event.sequence_on_a.sequence.into(), + port_id_on_a: send_packet_event.port_id_on_a.port_id.parse()?, + chan_id_on_a: send_packet_event.channel_id_on_a.channel_id.parse()?, + port_id_on_b: send_packet_event.port_id_on_b.port_id.parse()?, + chan_id_on_b: send_packet_event.channel_id_on_b.channel_id.parse()?, + data: serde_json::to_vec(&ibc_transfer_packet_data).unwrap(), + timeout_height_on_b, + timeout_timestamp_on_b, + } + }; - let response = starknet_chain.send_message(message.clone()).await?; + // relay the packet via starknet to cosmos relay - let ibc_packet_events_2: Vec = - event_encoding.filter_decode_events(&response.events)?; + starknet_to_cosmos_relay + .relay_packet(&starknet_ibc_packet) + .await?; - info!("ibc_packet_events 2: {:?}", ibc_packet_events_2); + let balance_cosmos_a_step_2 = cosmos_chain + .query_balance(address_cosmos_a, denom_cosmos) + .await?; - let ibc_transfer_events_2: Vec = - event_encoding.filter_decode_events(&response.events)?; + info!( + "cosmos balance after transfer back: {:?}", + balance_cosmos_a_step_2 + ); - info!("ibc_transfer_events 2: {:?}", ibc_transfer_events_2); + assert_eq!( + balance_cosmos_a_step_0.quantity, + balance_cosmos_a_step_2.quantity + ); - { - let recipient_balance = starknet_chain - .query_token_balance(&token_address, &recipient_address) - .await?; + let balance_starknet_b_step_2 = starknet_chain + .query_balance(address_starknet_b, &ics20_token_address) + .await?; - info!("recipient balance after transfer: {}", recipient_balance); + info!( + "starknet balance after transfer back: {:?}", + balance_starknet_b_step_2 + ); - assert_eq!(recipient_balance.quantity, (amount * 2).into(),); - } - } + assert_eq!(balance_starknet_b_step_2.quantity, 0u64.into()); Ok(()) })