diff --git a/cairo-contracts/packages/apps/src/tests/transfer.cairo b/cairo-contracts/packages/apps/src/tests/transfer.cairo index 0524cdf9..adfd544d 100644 --- a/cairo-contracts/packages/apps/src/tests/transfer.cairo +++ b/cairo-contracts/packages/apps/src/tests/transfer.cairo @@ -1,15 +1,19 @@ +use TokenTransferComponent::TransferValidationTrait; use openzeppelin_testing::events::EventSpyExt; use snforge_std::cheatcodes::events::EventSpy; use snforge_std::spy_events; +use starknet::class_hash::class_hash_const; use starknet_ibc_apps::transfer::ERC20Contract; use starknet_ibc_apps::transfer::TokenTransferComponent::{ - TransferInitializerImpl, TransferReaderImpl + TransferInitializerImpl, TransferReaderImpl, TransferWriterImpl, IBCTokenAddress }; use starknet_ibc_apps::transfer::TokenTransferComponent; use starknet_ibc_core::router::{AppContract, AppContractTrait}; use starknet_ibc_testkit::configs::{TransferAppConfigTrait, TransferAppConfig}; use starknet_ibc_testkit::dummies::CLASS_HASH; -use starknet_ibc_testkit::dummies::{SUPPLY, OWNER, NAME, SYMBOL, COSMOS, STARKNET}; +use starknet_ibc_testkit::dummies::{ + AMOUNT, SUPPLY, OWNER, NAME, SYMBOL, COSMOS, STARKNET, HOSTED_DENOM, EMPTY_MEMO +}; use starknet_ibc_testkit::event_spy::TransferEventSpyExt; use starknet_ibc_testkit::handles::{ERC20Handle, AppHandle}; use starknet_ibc_testkit::mocks::MockTransferApp; @@ -48,6 +52,29 @@ fn test_init_state() { assert_eq!(class_hash, CLASS_HASH()); } +#[test] +#[should_panic(expected: 'ICS20: erc20 class hash is 0')] +fn test_missing_class_hash() { + let mut state = setup_component(); + state.write_erc20_class_hash(class_hash_const::<0>()); + state.read_erc20_class_hash(); +} + +#[test] +#[should_panic(expected: 'ICS20: salt is 0')] +fn test_missing_salt() { + let mut state = setup_component(); + state.write_salt(0); + state.read_salt(); +} + +#[test] +#[should_panic(expected: 'ICS20: missing token address')] +fn test_missing_ibc_token_address() { + let state = setup_component(); + state.ibc_token_address(0); +} + #[test] fn test_escrow_ok() { let (ics20, mut erc20, cfg, mut spy) = setup(); @@ -112,7 +139,7 @@ fn test_mint_ok() { let prefixed_denom = cfg.prefix_hosted_denom(); - let token_address = ics20.ibc_token_address(prefixed_denom.key()).unwrap(); + let token_address = ics20.ibc_token_address(prefixed_denom.key()); // Assert the `CreateTokenEvent` emitted. spy.assert_create_token_event(ics20.address, NAME(), SYMBOL(), token_address, cfg.amount); @@ -154,7 +181,7 @@ fn test_burn_ok() { let prefixed_denom = cfg.prefix_hosted_denom(); - let token_address = ics20.ibc_token_address(prefixed_denom.key()).unwrap(); + let token_address = ics20.ibc_token_address(prefixed_denom.key()); let erc20: ERC20Contract = token_address.into(); @@ -176,3 +203,10 @@ fn test_burn_ok() { // Chekck the total supply of the ERC20 contract. erc20.assert_total_supply(0); } + +#[test] +#[should_panic(expected: 'ICS20: missing token address')] +fn test_burn_non_existence_ibc_token() { + let state = setup_component(); + state.burn_validate(OWNER(), HOSTED_DENOM(), AMOUNT, EMPTY_MEMO()); +} diff --git a/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo b/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo index 5d30652d..11d6724d 100644 --- a/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo +++ b/cairo-contracts/packages/apps/src/transfer/components/transfer.cairo @@ -116,7 +116,7 @@ pub mod TokenTransferComponent { self.write_erc20_class_hash(erc20_class_hash); - self.write_salt(0); + self.write_salt(1); } } @@ -203,14 +203,12 @@ pub mod TokenTransferComponent { > of ITokenAddress> { fn ibc_token_address( self: @ComponentState, token_key: felt252 - ) -> Option { - let token_address = self.read_ibc_token_address(token_key); + ) -> ContractAddress { + let address = self.read_ibc_token_address(token_key); - if token_address.is_non_zero() { - Option::Some(token_address) - } else { - Option::None - } + assert(address.is_non_zero(), TransferErrors::ZERO_TOKEN_ADDRESS); + + address } } @@ -499,6 +497,8 @@ pub mod TokenTransferComponent { ) { let token = self.get_token(denom.key()); + assert(token.is_non_zero(), TransferErrors::ZERO_TOKEN_ADDRESS); + let balance = token.balance_of(account); assert(balance >= amount, TransferErrors::INSUFFICIENT_BALANCE); @@ -691,13 +691,26 @@ pub mod TokenTransferComponent { TContractState, +HasComponent, +Drop > of TransferReaderTrait { fn read_erc20_class_hash(self: @ComponentState) -> ClassHash { - self.erc20_class_hash.read() + let class_hash = self.erc20_class_hash.read(); + + assert(class_hash.is_non_zero(), TransferErrors::ZERO_ERC20_CLASS_HASH); + + class_hash } fn read_salt(self: @ComponentState) -> felt252 { - self.salt.read() + let salt = self.salt.read(); + + assert(salt.is_non_zero(), TransferErrors::ZERO_SALT); + + salt } + // NOTE: The `read_ibc_token_address` and `read_ibc_token_key` methods + // do not reject cases where the value might be zero (non-existent). As + // these methods are also called internally where there is logic for + // handling non-existent cases. + fn read_ibc_token_address( self: @ComponentState, token_key: felt252 ) -> ContractAddress { diff --git a/cairo-contracts/packages/apps/src/transfer/errors.cairo b/cairo-contracts/packages/apps/src/transfer/errors.cairo index 9632afd1..250c3c05 100644 --- a/cairo-contracts/packages/apps/src/transfer/errors.cairo +++ b/cairo-contracts/packages/apps/src/transfer/errors.cairo @@ -4,6 +4,8 @@ pub mod TransferErrors { pub const ZERO_OWNER: felt252 = 'ICS20: owner is 0'; pub const ZERO_ERC20_CLASS_HASH: felt252 = 'ICS20: erc20 class hash is 0'; pub const ZERO_AMOUNT: felt252 = 'ICS20: transfer amount is 0'; + pub const ZERO_SALT: felt252 = 'ICS20: salt is 0'; + pub const ZERO_TOKEN_ADDRESS: felt252 = 'ICS20: missing token address'; pub const INVALID_DENOM: felt252 = 'ICS20: invalid denom'; pub const INVALID_PACKET_DATA: felt252 = 'ICS20: invalid packet data'; pub const INVALID_OWNER: felt252 = 'ICS20: invalid owner'; diff --git a/cairo-contracts/packages/apps/src/transfer/interfaces/transfer.cairo b/cairo-contracts/packages/apps/src/transfer/interfaces/transfer.cairo index 9516d6fb..7a71f61f 100644 --- a/cairo-contracts/packages/apps/src/transfer/interfaces/transfer.cairo +++ b/cairo-contracts/packages/apps/src/transfer/interfaces/transfer.cairo @@ -24,6 +24,6 @@ pub trait ITokenAddress { /// } /// ``` /// Hashing the denom is delegated to the client as it is more cost-efficient. - fn ibc_token_address(self: @TContractState, token_key: felt252) -> Option; + fn ibc_token_address(self: @TContractState, token_key: felt252) -> ContractAddress; } diff --git a/cairo-contracts/packages/clients/src/cometbft/client_state.cairo b/cairo-contracts/packages/clients/src/cometbft/client_state.cairo index 9d8a8969..9e22d5f7 100644 --- a/cairo-contracts/packages/clients/src/cometbft/client_state.cairo +++ b/cairo-contracts/packages/clients/src/cometbft/client_state.cairo @@ -1,5 +1,6 @@ +use core::num::traits::Zero; use starknet_ibc_clients::cometbft::CometErrors; -use starknet_ibc_core::client::{Height, HeightPartialOrd, Status}; +use starknet_ibc_core::client::{Height, HeightPartialOrd, Status, StatusTrait}; #[derive(Clone, Debug, Drop, Hash, PartialEq, Serde, starknet::Store)] pub struct CometClientState { @@ -10,6 +11,12 @@ pub struct CometClientState { #[generate_trait] pub impl CometClientStateImpl of CometClientStateTrait { + fn is_non_zero(self: @CometClientState) -> bool { + !(self.latest_height.is_zero() + && self.trusting_period.is_zero() + && self.status.is_expired()) + } + fn deserialize(client_state: Array,) -> CometClientState { let mut client_state_span = client_state.span(); diff --git a/cairo-contracts/packages/clients/src/cometbft/component.cairo b/cairo-contracts/packages/clients/src/cometbft/component.cairo index 42b439a6..a6d1e887 100644 --- a/cairo-contracts/packages/clients/src/cometbft/component.cairo +++ b/cairo-contracts/packages/clients/src/cometbft/component.cairo @@ -1,5 +1,6 @@ #[starknet::component] pub mod CometClientComponent { + use core::num::traits::Zero; use starknet::storage::{ Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess @@ -91,8 +92,6 @@ pub mod CometClientComponent { let latest_consensus_state = self .read_consensus_state(client_sequence, comet_client_state.latest_height.clone()); - assert(!latest_consensus_state.is_zero(), CometErrors::ZERO_CONSENSUS_STATE); - self._status(comet_client_state, latest_consensus_state, client_sequence) } @@ -168,8 +167,6 @@ pub mod CometClientComponent { let latest_consensus_state = self .read_consensus_state(client_sequence, comet_client_state.latest_height.clone()); - assert(!latest_consensus_state.is_zero(), CometErrors::ZERO_CONSENSUS_STATE); - let status = self ._status(comet_client_state, latest_consensus_state, msg.client_id.sequence); @@ -295,10 +292,7 @@ pub mod CometClientComponent { // TODO: Implement consensus state pruning mechanism. - let maybe_consensus_state = self - .read_consensus_state(client_sequence, header_height.clone()); - - if maybe_consensus_state.is_zero() { + if !self.consensus_state_exists(client_sequence, header_height.clone()) { let mut client_state = self.read_client_state(client_sequence); client_state.update(header_height.clone()); @@ -353,11 +347,7 @@ pub mod CometClientComponent { fn _root(self: @ComponentState, client_sequence: u64) -> ByteArray { let latest_height = self.latest_height(client_sequence); - let latest_consensus_state = self.read_consensus_state(client_sequence, latest_height); - - assert(!latest_consensus_state.is_zero(), CometErrors::ZERO_CONSENSUS_STATE); - - latest_consensus_state.root + self.read_consensus_state(client_sequence, latest_height).root } fn _status( @@ -427,25 +417,47 @@ pub mod CometClientComponent { fn read_client_state( self: @ComponentState, client_sequence: u64 ) -> CometClientState { - self.client_states.read(client_sequence) + let client_state = self.client_states.read(client_sequence); + + assert(client_state.is_non_zero(), CometErrors::MISSING_CLIENT_STATE); + + client_state } fn read_consensus_state( self: @ComponentState, client_sequence: u64, height: Height ) -> CometConsensusState { - self.consensus_states.read((client_sequence, height)) + let consensus_state = self.consensus_states.read((client_sequence, height)); + + assert(consensus_state.is_non_zero(), CometErrors::MISSING_CONSENSUS_STATE); + + consensus_state + } + + fn consensus_state_exists( + self: @ComponentState, client_sequence: u64, height: Height + ) -> bool { + self.consensus_states.read((client_sequence, height)).is_non_zero() } fn read_client_processed_time( self: @ComponentState, client_sequence: u64, height: Height ) -> Timestamp { - self.client_processed_times.read((client_sequence, height)).into() + let processed_time = self.client_processed_times.read((client_sequence, height)); + + assert(processed_time.is_non_zero(), CometErrors::MISSING_CLIENT_PROCESSED_TIME); + + processed_time.into() } fn read_client_processed_height( self: @ComponentState, client_sequence: u64, height: Height ) -> u64 { - self.client_processed_heights.read((client_sequence, height)) + let processed_height = self.client_processed_heights.read((client_sequence, height)); + + assert(processed_height.is_non_zero(), CometErrors::MISSING_CLIENT_PROCESSED_HEIGHT); + + processed_height } } diff --git a/cairo-contracts/packages/clients/src/cometbft/consensus_state.cairo b/cairo-contracts/packages/clients/src/cometbft/consensus_state.cairo index 97a9b550..93f402f7 100644 --- a/cairo-contracts/packages/clients/src/cometbft/consensus_state.cairo +++ b/cairo-contracts/packages/clients/src/cometbft/consensus_state.cairo @@ -10,8 +10,8 @@ pub struct CometConsensusState { #[generate_trait] pub impl CometConsensusStateImpl of CometConsensusStateTrait { - fn is_zero(self: @CometConsensusState) -> bool { - self.root.len() == 0 && self.timestamp.is_zero() + fn is_non_zero(self: @CometConsensusState) -> bool { + !(self.root.len() == 0 && self.timestamp.is_zero()) } fn timestamp(self: @CometConsensusState) -> u64 { diff --git a/cairo-contracts/packages/clients/src/cometbft/errors.cairo b/cairo-contracts/packages/clients/src/cometbft/errors.cairo index 97031be6..4ff1a06e 100644 --- a/cairo-contracts/packages/clients/src/cometbft/errors.cairo +++ b/cairo-contracts/packages/clients/src/cometbft/errors.cairo @@ -5,5 +5,8 @@ pub mod CometErrors { pub const INVALID_CONSENSUS_STATE: felt252 = 'ICS07: invalid consensus state'; pub const INVALID_HEADER: felt252 = 'ICS07: invalid header'; pub const INVALID_HEADER_TIMESTAMP: felt252 = 'ICS07: invalid header timestamp'; - pub const ZERO_CONSENSUS_STATE: felt252 = 'ICS07: zero consensus state'; + pub const MISSING_CLIENT_STATE: felt252 = 'ICS07: missing client state'; + pub const MISSING_CONSENSUS_STATE: felt252 = 'ICS07: missing consensus state'; + pub const MISSING_CLIENT_PROCESSED_TIME: felt252 = 'ICS07: missing processed time'; + pub const MISSING_CLIENT_PROCESSED_HEIGHT: felt252 = 'ICS07: missing processed height'; } diff --git a/cairo-contracts/packages/clients/src/tests/cometbft.cairo b/cairo-contracts/packages/clients/src/tests/cometbft.cairo index 43849f83..cec4a97f 100644 --- a/cairo-contracts/packages/clients/src/tests/cometbft.cairo +++ b/cairo-contracts/packages/clients/src/tests/cometbft.cairo @@ -60,3 +60,31 @@ fn test_update_client_ok() { assert_eq!(state.latest_height(0), updating_height); assert!(state.status(0).is_active()); } + +#[test] +#[should_panic(expected: 'ICS07: missing client state')] +fn test_missing_client_state() { + let mut state = setup(); + state.read_client_state(0); +} + +#[test] +#[should_panic(expected: 'ICS07: missing consensus state')] +fn test_missing_consensus_state() { + let mut state = setup(); + state.read_consensus_state(0, HEIGHT(5)); +} + +#[test] +#[should_panic(expected: 'ICS07: missing processed time')] +fn test_missing_client_processed_time() { + let mut state = setup(); + state.read_client_processed_time(0, HEIGHT(5)); +} + +#[test] +#[should_panic(expected: 'ICS07: missing processed height')] +fn test_missing_client_processed_height() { + let mut state = setup(); + state.read_client_processed_height(0, HEIGHT(5)); +} diff --git a/cairo-contracts/packages/contracts/src/tests/channel.cairo b/cairo-contracts/packages/contracts/src/tests/channel.cairo index 38f3396c..474a3dd9 100644 --- a/cairo-contracts/packages/contracts/src/tests/channel.cairo +++ b/cairo-contracts/packages/contracts/src/tests/channel.cairo @@ -1,4 +1,3 @@ -use core::num::traits::Zero; use snforge_std::{spy_events, EventSpy}; use starknet_ibc_apps::transfer::{ERC20Contract, SUCCESS_ACK}; use starknet_ibc_core::channel::{ChannelEndTrait, ChannelOrdering, AckStatus}; @@ -115,7 +114,7 @@ fn test_recv_packet_ok() { spy.assert_recv_packet_event(core.address, ChannelOrdering::Unordered, msg.packet.clone()); // Fetch the token address. - let token_address = ics20.ibc_token_address(prefixed_denom.key()).unwrap(); + let token_address = ics20.ibc_token_address(prefixed_denom.key()); let erc20: ERC20Contract = token_address.into(); @@ -141,6 +140,7 @@ fn test_recv_packet_ok() { } #[test] +#[should_panic(expected: 'ICS04: missing commitment')] fn test_successful_ack_packet_ok() { // ----------------------------------------------------------- // Setup Essentials @@ -209,18 +209,17 @@ fn test_successful_ack_packet_ok() { spy.assert_ack_packet_event(core.address, ChannelOrdering::Unordered, msg.packet.clone()); - let commitment = core + // Check the balance of the sender. + erc20.assert_balance(OWNER(), SUPPLY - transfer_cfg.amount); + + core .packet_commitment( msg_transfer.port_id_on_a.clone(), msg_transfer.chan_id_on_a.clone(), seq_on_a ); - - assert!(commitment.is_zero()); - - // Check the balance of the sender. - erc20.assert_balance(OWNER(), SUPPLY - transfer_cfg.amount); } #[test] +#[should_panic(expected: 'ICS04: missing commitment')] fn test_failure_ack_packet_ok() { // ----------------------------------------------------------- // Setup Essentials @@ -291,19 +290,17 @@ fn test_failure_ack_packet_ok() { spy.assert_ack_packet_event(core.address, ChannelOrdering::Unordered, msg.packet.clone()); - let commitment = core + // Check if the balance of the sender to ensure the refund. + erc20.assert_balance(OWNER(), SUPPLY); + + core .packet_commitment( msg_transfer.port_id_on_a.clone(), msg_transfer.chan_id_on_a.clone(), seq_on_a ); - - assert!(commitment.is_zero()); - - // Check if the balance of the sender to ensure the refund. - erc20.assert_balance(OWNER(), SUPPLY); } #[test] -#[should_panic(expected: 'ICS04: packet not sent')] +#[should_panic(expected: 'ICS04: missing commitment')] fn test_ack_packet_for_never_sent_packet() { // ----------------------------------------------------------- // Setup Essentials diff --git a/cairo-contracts/packages/contracts/src/tests/transfer.cairo b/cairo-contracts/packages/contracts/src/tests/transfer.cairo index 68332b37..43faec9f 100644 --- a/cairo-contracts/packages/contracts/src/tests/transfer.cairo +++ b/cairo-contracts/packages/contracts/src/tests/transfer.cairo @@ -124,7 +124,7 @@ fn test_mint_burn_roundtrip() { let prefixed_denom = transfer_cfg.prefix_hosted_denom(); // Fetch the token address. - let token_address = ics20.ibc_token_address(prefixed_denom.key()).unwrap(); + let token_address = ics20.ibc_token_address(prefixed_denom.key()); // Assert the `CreateTokenEvent` emitted. spy diff --git a/cairo-contracts/packages/core/src/channel/components/handler.cairo b/cairo-contracts/packages/core/src/channel/components/handler.cairo index 1885f5a1..73c55abd 100644 --- a/cairo-contracts/packages/core/src/channel/components/handler.cairo +++ b/cairo-contracts/packages/core/src/channel/components/handler.cairo @@ -13,7 +13,7 @@ pub mod ChannelHandlerComponent { use starknet_ibc_core::channel::{ ChannelEventEmitterComponent, IChannelHandler, IChannelQuery, MsgRecvPacket, MsgAckPacket, ChannelEnd, ChannelEndTrait, ChannelErrors, PacketTrait, ChannelOrdering, Receipt, - AcknowledgementTrait, Packet, Acknowledgement + ReceiptTrait, AcknowledgementTrait, Packet, Acknowledgement }; use starknet_ibc_core::client::{ ClientHandlerComponent, ClientContract, ClientContractTrait, HeightImpl @@ -29,9 +29,9 @@ pub mod ChannelHandlerComponent { #[storage] pub struct Storage { - pub channel_ends: Map>, + pub channel_ends: Map, pub packet_commitments: Map, - pub packet_receipts: Map>, + pub packet_receipts: Map, pub packet_acks: Map, pub send_sequences: Map, pub recv_sequences: Map, @@ -76,7 +76,7 @@ pub mod ChannelHandlerComponent { impl RouterHandler: RouterHandlerComponent::HasComponent > of IChannelHandler> { fn send_packet(ref self: ComponentState, packet: Packet) { - let chan_end_on_a = self.get_channel_end(@packet.port_id_on_a, @packet.chan_id_on_a); + let chan_end_on_a = self.read_channel_end(@packet.port_id_on_a, @packet.chan_id_on_a); self.send_packet_validate(packet.clone(), chan_end_on_a.clone()); @@ -85,7 +85,7 @@ pub mod ChannelHandlerComponent { fn recv_packet(ref self: ComponentState, msg: MsgRecvPacket) { let chan_end_on_b = self - .get_channel_end(@msg.packet.port_id_on_b, @msg.packet.chan_id_on_b); + .read_channel_end(@msg.packet.port_id_on_b, @msg.packet.chan_id_on_b); self.recv_packet_validate(msg.clone(), chan_end_on_b.clone()); @@ -94,7 +94,7 @@ pub mod ChannelHandlerComponent { fn ack_packet(ref self: ComponentState, msg: MsgAckPacket) { let chan_end_on_a = self - .get_channel_end(@msg.packet.port_id_on_a, @msg.packet.chan_id_on_b); + .read_channel_end(@msg.packet.port_id_on_a, @msg.packet.chan_id_on_b); self.ack_packet_validate(msg.clone(), chan_end_on_a.clone()); @@ -113,11 +113,7 @@ pub mod ChannelHandlerComponent { fn channel_end( self: @ComponentState, port_id: PortId, channel_id: ChannelId ) -> ChannelEnd { - let maybe_chan_end = self.read_channel_end(@port_id, @channel_id); - - assert(maybe_chan_end.is_some(), ChannelErrors::MISSING_CHANNEL_END); - - maybe_chan_end.unwrap() + self.read_channel_end(@port_id, @channel_id) } fn packet_commitment( @@ -135,9 +131,7 @@ pub mod ChannelHandlerComponent { channel_id: ChannelId, sequence: Sequence ) -> bool { - let receipt = self.read_packet_receipt(@port_id, @channel_id, @sequence); - - receipt.is_some() + self.read_packet_receipt(@port_id, @channel_id, @sequence).is_ok() } fn packet_acknowledgement( @@ -267,17 +261,19 @@ pub mod ChannelHandlerComponent { match @chan_end_on_b.ordering { ChannelOrdering::Unordered => { - let reciept_resp = self + let receipt = self .read_packet_receipt( @msg.packet.port_id_on_b, @msg.packet.chan_id_on_b, @msg.packet.seq_on_a ); - assert(reciept_resp.is_none(), ChannelErrors::PACKET_ALREADY_RECEIVED); + assert(receipt.is_none(), ChannelErrors::MISSING_PACKET_RECEIPT); - self - .verify_ack_not_exists( + let ack_exists = self + .packet_ack_exists( @msg.packet.port_id_on_b, @msg.packet.chan_id_on_b, @msg.packet.seq_on_a ); + + assert(!ack_exists, ChannelErrors::ACK_ALREADY_EXISTS); }, ChannelOrdering::Ordered => { let next_sequence_recv = self @@ -294,12 +290,14 @@ pub mod ChannelHandlerComponent { // sequence, we check if the ack not exists. As the // existance means the packet was already relayed. if next_sequence_recv == msg.packet.seq_on_a { - self - .verify_ack_not_exists( + let ack_exists = self + .packet_ack_exists( @msg.packet.port_id_on_b, @msg.packet.chan_id_on_b, @msg.packet.seq_on_a ); + + assert(!ack_exists, ChannelErrors::ACK_ALREADY_EXISTS); } } }; @@ -372,17 +370,6 @@ pub mod ChannelHandlerComponent { msg.proof_commitment_on_a.clone() ); } - - fn verify_ack_not_exists( - self: @ComponentState, - port_id: @PortId, - channel_id: @ChannelId, - sequence: @Sequence - ) { - let ack = self.read_packet_ack(port_id, channel_id, sequence); - - assert(ack.is_zero(), ChannelErrors::ACK_ALREADY_EXISTS); - } } #[generate_trait] @@ -482,11 +469,9 @@ pub mod ChannelHandlerComponent { fn verify_packet_commitment_matches( self: @ComponentState, packet: @Packet ) { - let packet_commitment = self + let _packet_commitment = self .read_packet_commitment(packet.port_id_on_a, packet.chan_id_on_a, packet.seq_on_a); - assert(packet_commitment.is_non_zero(), ChannelErrors::PACKET_NOT_SENT); - let _expected_packet_commitment = packet.compute_commitment(); // assert(packet_commitment == expected_packet_commitment, // ChannelErrors::MISMATCHED_PACKET_COMMITMENT); @@ -512,18 +497,6 @@ pub mod ChannelHandlerComponent { impl ClientHandler: ClientHandlerComponent::HasComponent, impl RouterHandler: RouterHandlerComponent::HasComponent > of ChannelAccessTrait { - fn get_channel_end( - self: @ComponentState, - local_port_id: @PortId, - local_channel_id: @ChannelId, - ) -> ChannelEnd { - let maybe_chan_end = self.read_channel_end(local_port_id, local_channel_id); - - assert(maybe_chan_end.is_some(), ChannelErrors::MISSING_CHANNEL_END); - - maybe_chan_end.unwrap() - } - fn get_client( self: @ComponentState, client_type: felt252 ) -> ClientContract { @@ -549,8 +522,12 @@ pub mod ChannelHandlerComponent { > of ChannelReaderTrait { fn read_channel_end( self: @ComponentState, port_id: @PortId, channel_id: @ChannelId - ) -> Option { - self.channel_ends.read(channel_end_key(port_id, channel_id)) + ) -> ChannelEnd { + let channel_end = self.channel_ends.read(channel_end_key(port_id, channel_id)); + + assert(!channel_end.is_zero(), ChannelErrors::MISSING_CHANNEL_END); + + channel_end } fn read_packet_commitment( @@ -559,7 +536,13 @@ pub mod ChannelHandlerComponent { channel_id: @ChannelId, sequence: @Sequence ) -> felt252 { - self.packet_commitments.read(commitment_key(port_id, channel_id, sequence)) + let commitment = self + .packet_commitments + .read(commitment_key(port_id, channel_id, sequence)); + + assert(commitment.is_non_zero(), ChannelErrors::MISSING_PACKET_COMMITMENT); + + commitment } fn read_packet_receipt( @@ -567,7 +550,7 @@ pub mod ChannelHandlerComponent { port_id: @PortId, channel_id: @ChannelId, sequence: @Sequence - ) -> Option { + ) -> Receipt { self.packet_receipts.read(receipt_key(port_id, channel_id, sequence)) } @@ -577,7 +560,20 @@ pub mod ChannelHandlerComponent { channel_id: @ChannelId, sequence: @Sequence ) -> felt252 { - self.packet_acks.read(ack_key(port_id, channel_id, sequence)) + let ack = self.packet_acks.read(ack_key(port_id, channel_id, sequence)); + + assert(ack.is_non_zero(), ChannelErrors::MISSING_PACKET_ACK); + + ack + } + + fn packet_ack_exists( + self: @ComponentState, + port_id: @PortId, + channel_id: @ChannelId, + sequence: @Sequence + ) -> bool { + self.packet_acks.read(ack_key(port_id, channel_id, sequence)).is_non_zero() } fn read_next_sequence_send( @@ -609,9 +605,7 @@ pub mod ChannelHandlerComponent { channel_id: @ChannelId, channel_end: ChannelEnd ) { - self - .channel_ends - .write(channel_end_key(port_id, channel_id), Option::Some(channel_end)); + self.channel_ends.write(channel_end_key(port_id, channel_id), channel_end); } fn write_packet_commitment( @@ -642,9 +636,7 @@ pub mod ChannelHandlerComponent { sequence: @Sequence, receipt: Receipt ) { - self - .packet_receipts - .write(receipt_key(port_id, channel_id, sequence), Option::Some(receipt)); + self.packet_receipts.write(receipt_key(port_id, channel_id, sequence), receipt); } fn write_packet_ack( diff --git a/cairo-contracts/packages/core/src/channel/errors.cairo b/cairo-contracts/packages/core/src/channel/errors.cairo index 45229e8f..4c1684bd 100644 --- a/cairo-contracts/packages/core/src/channel/errors.cairo +++ b/cairo-contracts/packages/core/src/channel/errors.cairo @@ -7,9 +7,11 @@ pub mod ChannelErrors { pub const INVALID_COUNTERPARTY: felt252 = 'ICS04: invalid counterparty'; pub const INVALID_PACKET_SEQUENCE: felt252 = 'ICS04: invalid packet sequence'; pub const MISSING_CHANNEL_END: felt252 = 'ICS04: missing channel end'; + pub const MISSING_PACKET_RECEIPT: felt252 = 'ICS04: missing packet receipt'; + pub const MISSING_PACKET_COMMITMENT: felt252 = 'ICS04: missing commitment'; + pub const MISSING_PACKET_ACK: felt252 = 'ICS04: missing packet ack'; pub const MISSING_PACKET_TIMEOUT: felt252 = 'ICS04: missing packet timeout'; pub const MISMATCHED_PACKET_COMMITMENT: felt252 = 'ICS04: mismatched commitment'; - pub const PACKET_NOT_SENT: felt252 = 'ICS04: packet not sent'; pub const PACKET_ALREADY_RECEIVED: felt252 = 'ICS04: packet already received'; pub const TIMED_OUT_PACKET: felt252 = 'ICS04: packet timed out'; } diff --git a/cairo-contracts/packages/core/src/channel/types.cairo b/cairo-contracts/packages/core/src/channel/types.cairo index 92d85fa9..b4cdb5d0 100644 --- a/cairo-contracts/packages/core/src/channel/types.cairo +++ b/cairo-contracts/packages/core/src/channel/types.cairo @@ -1,7 +1,9 @@ use core::num::traits::Zero; use starknet_ibc_core::channel::ChannelErrors; use starknet_ibc_core::client::{Height, Timestamp, HeightPartialOrd, TimestampPartialOrd}; -use starknet_ibc_core::host::{ClientId, ChannelId, PortId, Sequence}; +use starknet_ibc_core::host::{ + ClientId, ClientIdTrait, ChannelId, ChannelIdTrait, PortId, PortIdTrait, Sequence +}; use starknet_ibc_utils::ValidateBasic; #[derive(Clone, Debug, Drop, Serde)] @@ -79,6 +81,14 @@ pub impl ChannelEndImpl of ChannelEndTrait { && self.remote.channel_id == counterparty_channel_id } + /// Returns true if all the fields are in the zero state. + fn is_zero(self: @ChannelEnd) -> bool { + self.state == @ChannelState::Uninitialized + && self.ordering == @ChannelOrdering::Unordered + && self.remote.is_zero() + && self.client_id.is_zero() + } + /// Validates the channel end be in the open state and the counterparty /// parameters match with the expected one. fn validate( @@ -94,6 +104,7 @@ pub impl ChannelEndImpl of ChannelEndTrait { #[derive(Clone, Debug, Drop, PartialEq, Serde, starknet::Store)] pub enum ChannelState { + #[default] Uninitialized, Init, TryOpen, @@ -103,6 +114,7 @@ pub enum ChannelState { #[derive(Clone, Debug, Drop, PartialEq, Serde, starknet::Store)] pub enum ChannelOrdering { + #[default] Unordered, Ordered, } @@ -113,11 +125,45 @@ pub struct Counterparty { pub channel_id: ChannelId, } +#[generate_trait] +pub impl CounterpartyImpl of CounterpartyTrait { + fn is_zero(self: @Counterparty) -> bool { + self.port_id.is_zero() && self.channel_id.is_zero() + } +} + #[derive(Clone, Debug, Drop, PartialEq, Serde, starknet::Store)] pub enum Receipt { + #[default] + None, Ok } +#[generate_trait] +pub impl ReceiptImpl of ReceiptTrait { + fn is_ok(self: @Receipt) -> bool { + self == @Receipt::Ok + } + + fn is_none(self: @Receipt) -> bool { + self == @Receipt::None + } +} + +pub impl ReceiptZero of Zero { + fn is_zero(self: @Receipt) -> bool { + self == @Receipt::None + } + + fn is_non_zero(self: @Receipt) -> bool { + !self.is_zero() + } + + fn zero() -> Receipt { + Receipt::None + } +} + #[derive(Clone, Debug, Drop, PartialEq, Serde)] pub struct Acknowledgement { pub ack: Array, diff --git a/cairo-contracts/packages/core/src/client/components/handler.cairo b/cairo-contracts/packages/core/src/client/components/handler.cairo index e4031ea4..0c2d48e7 100644 --- a/cairo-contracts/packages/core/src/client/components/handler.cairo +++ b/cairo-contracts/packages/core/src/client/components/handler.cairo @@ -116,11 +116,7 @@ pub mod ClientHandlerComponent { fn get_client( self: @ComponentState, client_type: felt252 ) -> ClientContract { - let client_address = self.read_supported_client(client_type); - - assert(client_address.is_non_zero(), ClientErrors::ZERO_CLIENT_ADDRESS); - - client_address.into() + self.read_supported_client(client_type).into() } } @@ -135,7 +131,11 @@ pub mod ClientHandlerComponent { fn read_supported_client( self: @ComponentState, client_type: felt252 ) -> ContractAddress { - self.supported_clients.read(client_type) + let client_address = self.supported_clients.read(client_type); + + assert(client_address.is_non_zero(), ClientErrors::ZERO_CLIENT_ADDRESS); + + client_address } } diff --git a/cairo-contracts/packages/core/src/client/types.cairo b/cairo-contracts/packages/core/src/client/types.cairo index a7673028..939cb01f 100644 --- a/cairo-contracts/packages/core/src/client/types.cairo +++ b/cairo-contracts/packages/core/src/client/types.cairo @@ -31,6 +31,8 @@ pub impl HeightsIntoUpdateResponse of Into, UpdateResponse> { #[derive(Clone, Debug, Drop, Hash, PartialEq, Serde, starknet::Store)] pub enum Status { Active, + // The default for cases when a client state isn't found during a storage call. + #[default] Expired, Frozen: Height, } diff --git a/cairo-contracts/packages/core/src/host/identifiers.cairo b/cairo-contracts/packages/core/src/host/identifiers.cairo index a00f5402..ce3671c8 100644 --- a/cairo-contracts/packages/core/src/host/identifiers.cairo +++ b/cairo-contracts/packages/core/src/host/identifiers.cairo @@ -18,6 +18,10 @@ pub impl ClientIdImpl of ClientIdTrait { ClientId { client_type, sequence } } + fn is_zero(self: @ClientId) -> bool { + self.sequence.is_zero() && self.client_type.is_zero() + } + fn validate(self: @ClientId, client_id_hash: felt252) {} } @@ -57,6 +61,10 @@ pub impl ChannelIdImpl of ChannelIdTrait { sequence.try_into().unwrap() } + fn is_zero(self: @ChannelId) -> bool { + self.channel_id.len() == 0 + } + fn validate(self: @ChannelId) { let channel_id_len = self.channel_id.len(); @@ -96,6 +104,10 @@ pub impl PortIdImpl of PortIdTrait { port_id } + fn is_zero(self: @PortId) -> bool { + self.port_id.len() == 0 + } + fn validate(self: @PortId, port_id_hash: felt252) { self.validate_basic(); assert(self.key() == port_id_hash, HostErrors::INVALID_PORT_ID); diff --git a/cairo-contracts/packages/core/src/lib.cairo b/cairo-contracts/packages/core/src/lib.cairo index 0a84caae..0be52b04 100644 --- a/cairo-contracts/packages/core/src/lib.cairo +++ b/cairo-contracts/packages/core/src/lib.cairo @@ -2,6 +2,7 @@ mod tests { mod channel; mod client; + mod router; } pub mod router { mod app_call; @@ -34,7 +35,7 @@ pub mod channel { pub use types::{ Packet, PacketImpl, PacketTrait, ChannelEnd, ChannelEndImpl, ChannelEndTrait, ChannelState, ChannelOrdering, Counterparty, Acknowledgement, AcknowledgementImpl, AcknowledgementTrait, - AckStatus, AckStatusImpl, AckStatusTrait, Receipt + AckStatus, AckStatusImpl, AckStatusTrait, Receipt, ReceiptImpl, ReceiptTrait }; mod components { pub mod events; diff --git a/cairo-contracts/packages/core/src/router/component.cairo b/cairo-contracts/packages/core/src/router/component.cairo index 0d2583a6..62e03637 100644 --- a/cairo-contracts/packages/core/src/router/component.cairo +++ b/cairo-contracts/packages/core/src/router/component.cairo @@ -35,16 +35,10 @@ pub mod RouterHandlerComponent { impl CoreRouterHandlerImpl< TContractState, +HasComponent, +Drop > of IRouter> { - fn get_app_address( + fn app_address( self: @ComponentState, port_id: ByteArray - ) -> Option { - let app_address = self.read_app_address(@PortIdImpl::new(port_id)); - - if app_address.is_non_zero() { - Option::Some(app_address) - } else { - Option::None - } + ) -> ContractAddress { + self.read_app_address(@PortIdImpl::new(port_id)) } fn bind_port_id( @@ -69,11 +63,7 @@ pub mod RouterHandlerComponent { TContractState, +HasComponent, +Drop > of RouterInternalTrait { fn get_app(self: @ComponentState, port_id: @PortId) -> AppContract { - let maybe_app_address = self.read_app_address(port_id); - - assert(maybe_app_address.is_non_zero(), RouterErrors::UNSUPPORTED_PORT_ID); - - maybe_app_address.into() + self.read_app_address(port_id).into() } } @@ -88,7 +78,11 @@ pub mod RouterHandlerComponent { fn read_app_address( self: @ComponentState, port_id: @PortId ) -> ContractAddress { - self.port_id_to_app.read(port_id.key()) + let app_address = self.port_id_to_app.read(port_id.key()); + + assert(app_address.is_non_zero(), RouterErrors::UNSUPPORTED_PORT_ID); + + app_address } } diff --git a/cairo-contracts/packages/core/src/router/interface.cairo b/cairo-contracts/packages/core/src/router/interface.cairo index f9122ce0..2718a6cf 100644 --- a/cairo-contracts/packages/core/src/router/interface.cairo +++ b/cairo-contracts/packages/core/src/router/interface.cairo @@ -2,7 +2,7 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IRouter { - fn get_app_address(self: @TContractState, port_id: ByteArray) -> Option; + fn app_address(self: @TContractState, port_id: ByteArray) -> ContractAddress; fn bind_port_id(ref self: TContractState, port_id: ByteArray, app_address: ContractAddress); diff --git a/cairo-contracts/packages/core/src/tests/channel.cairo b/cairo-contracts/packages/core/src/tests/channel.cairo index d3d35932..48e1b463 100644 --- a/cairo-contracts/packages/core/src/tests/channel.cairo +++ b/cairo-contracts/packages/core/src/tests/channel.cairo @@ -3,7 +3,9 @@ use core::num::traits::Zero; use starknet_ibc_core::channel::ChannelHandlerComponent::{ ChannelInitializerImpl, ChannelWriterTrait }; -use starknet_ibc_core::channel::ChannelHandlerComponent; +use starknet_ibc_core::channel::{ + ChannelHandlerComponent, ChannelState, ChannelOrdering, Receipt, ReceiptTrait +}; use starknet_ibc_testkit::dummies::{CHANNEL_END, CHANNEL_ID, PORT_ID, SEQUENCE}; use starknet_ibc_testkit::mocks::MockChannelHandler; @@ -22,26 +24,64 @@ fn setup() -> ComponentState { #[test] fn test_intial_state() { let state = setup(); - let channel_end_resp = state.read_channel_end(@PORT_ID(), @CHANNEL_ID(0)); - assert!(channel_end_resp.is_some()); + let channel_end = state.read_channel_end(@PORT_ID(), @CHANNEL_ID(0)); + assert_eq!(channel_end.state, ChannelState::Open); + assert_eq!(channel_end.ordering, ChannelOrdering::Unordered); - let channel_end_resp = state.read_channel_end(@PORT_ID(), @CHANNEL_ID(10)); - assert!(channel_end_resp.is_none()); + let next_sequence_recv = state.read_next_sequence_recv(@PORT_ID(), @CHANNEL_ID(0)); + assert!(next_sequence_recv.is_zero()); - let receipt_resp = state.read_packet_receipt(@PORT_ID(), @CHANNEL_ID(0), @SEQUENCE(0)); - assert!(receipt_resp.is_none()); + let next_sequence_send = state.read_next_sequence_send(@PORT_ID(), @CHANNEL_ID(1)); + assert!(next_sequence_send.is_zero()); +} - let ack_resp = state.read_packet_ack(@PORT_ID(), @CHANNEL_ID(0), @SEQUENCE(0)); - assert!(ack_resp.is_zero()); +#[test] +fn test_write_read_channel_end_ok() { + let mut state = setup(); + state.write_channel_end(@PORT_ID(), @CHANNEL_ID(10), CHANNEL_END(1)); + let channel_end = state.read_channel_end(@PORT_ID(), @CHANNEL_ID(10)); + assert_eq!(channel_end, CHANNEL_END(1)); +} - let next_seq_resp = state.read_next_sequence_recv(@PORT_ID(), @CHANNEL_ID(0)); - assert!(next_seq_resp.is_zero()); +#[test] +#[should_panic(expected: 'ICS04: missing channel end')] +fn test_missing_channel_end() { + let state = setup(); + state.read_channel_end(@PORT_ID(), @CHANNEL_ID(10)); } #[test] -fn test_write_channel_end_ok() { +fn test_write_read_packet_receipt_ok() { let mut state = setup(); - state.write_channel_end(@PORT_ID(), @CHANNEL_ID(1), CHANNEL_END(10)); - let chan_end_res = state.read_channel_end(@PORT_ID(), @CHANNEL_ID(1)); - assert_eq!(chan_end_res, Option::Some(CHANNEL_END(10))); + state.write_packet_receipt(@PORT_ID(), @CHANNEL_ID(10), @SEQUENCE(10), Receipt::Ok); + let receipt = state.read_packet_receipt(@PORT_ID(), @CHANNEL_ID(10), @SEQUENCE(10)); + assert_eq!(receipt, Receipt::Ok); +} + +#[test] +fn test_missing_packet_receipt() { + let state = setup(); + let receipt = state.read_packet_receipt(@PORT_ID(), @CHANNEL_ID(0), @SEQUENCE(0)); + assert!(receipt.is_none()); +} + +#[test] +#[should_panic(expected: 'ICS04: missing commitment')] +fn test_missing_packet_commitment() { + let state = setup(); + state.read_packet_commitment(@PORT_ID(), @CHANNEL_ID(0), @SEQUENCE(0)); +} + +#[test] +#[should_panic(expected: 'ICS04: missing packet ack')] +fn test_missing_packet_ack() { + let state = setup(); + state.read_packet_ack(@PORT_ID(), @CHANNEL_ID(0), @SEQUENCE(0)); +} + +#[test] +fn test_packet_ack_existence() { + let state = setup(); + let if_exists = state.packet_ack_exists(@PORT_ID(), @CHANNEL_ID(0), @SEQUENCE(0)); + assert!(!if_exists); } diff --git a/cairo-contracts/packages/core/src/tests/client.cairo b/cairo-contracts/packages/core/src/tests/client.cairo index 893048d3..3c4df9b8 100644 --- a/cairo-contracts/packages/core/src/tests/client.cairo +++ b/cairo-contracts/packages/core/src/tests/client.cairo @@ -1,9 +1,7 @@ -use ClientHandlerComponent::ClientReaderTrait; -use core::num::traits::Zero; use snforge_std::{spy_events, test_address}; use starknet_ibc_core::client::ClientHandlerComponent::{ ClientInitializerImpl, CoreRegisterClientImpl, CoreClientHandlerImpl, EventEmitterImpl, - ClientInternalImpl + ClientInternalImpl, ClientReaderTrait }; use starknet_ibc_core::client::{ClientHandlerComponent, CreateResponse}; use starknet_ibc_testkit::dummies::{CLIENT, CLIENT_TYPE, CLIENT_ID, HEIGHT}; @@ -22,13 +20,6 @@ fn setup() -> ComponentState { state } -#[test] -fn test_intial_state() { - let state = setup(); - let supported_client = state.read_supported_client(0); - assert!(supported_client.is_zero()); -} - #[test] fn test_register_client() { let mut state = setup(); diff --git a/cairo-contracts/packages/core/src/tests/router.cairo b/cairo-contracts/packages/core/src/tests/router.cairo new file mode 100644 index 00000000..84ca9e3d --- /dev/null +++ b/cairo-contracts/packages/core/src/tests/router.cairo @@ -0,0 +1,38 @@ +use starknet::contract_address_const; +use starknet_ibc_core::router::RouterHandlerComponent::{RouterInitializerImpl, CoreRouterHandler}; +use starknet_ibc_core::router::RouterHandlerComponent; +use starknet_ibc_testkit::mocks::MockRouterHandler; + +type ComponentState = RouterHandlerComponent::ComponentState; + +fn COMPONENT_STATE() -> ComponentState { + RouterHandlerComponent::component_state_for_testing() +} + +fn setup() -> ComponentState { + let mut state = COMPONENT_STATE(); + state.initializer(); + state +} + +#[test] +#[should_panic(expected: 'ICS26: unsupported port id')] +fn test_missing_app_address() { + let mut state = setup(); + state.app_address("transfer"); +} + +#[test] +#[should_panic(expected: 'ICS26: unsupported port id')] +fn test_bind_release_port_id_ok() { + let mut state = setup(); + let port_id = "transfer"; + let app_address = contract_address_const::<'transfer'>(); + + state.bind_port_id(port_id.clone(), app_address); + let stored_app_address = state.app_address(port_id.clone()); + assert_eq!(stored_app_address, app_address); + + state.release_port_id(port_id.clone()); + state.app_address(port_id); +} diff --git a/cairo-contracts/packages/testkit/src/configs/transfer.cairo b/cairo-contracts/packages/testkit/src/configs/transfer.cairo index 8c4b6041..91e5d144 100644 --- a/cairo-contracts/packages/testkit/src/configs/transfer.cairo +++ b/cairo-contracts/packages/testkit/src/configs/transfer.cairo @@ -5,7 +5,9 @@ use starknet_ibc_apps::transfer::types::{ use starknet_ibc_core::channel::{Packet, MsgRecvPacket, MsgAckPacket, Acknowledgement}; use starknet_ibc_core::client::Timestamp; use starknet_ibc_core::host::{ChannelId, Sequence}; -use starknet_ibc_testkit::dummies::{PUBKEY, NAME, AMOUNT, EMPTY_MEMO, PORT_ID, CHANNEL_ID, HEIGHT}; +use starknet_ibc_testkit::dummies::{ + NATIVE_DENOM, HOSTED_DENOM, AMOUNT, EMPTY_MEMO, PORT_ID, CHANNEL_ID, HEIGHT +}; #[derive(Clone, Debug, Drop, Serde)] pub struct TransferAppConfig { @@ -19,15 +21,9 @@ pub struct TransferAppConfig { #[generate_trait] pub impl TransferAppConfigImpl of TransferAppConfigTrait { fn default() -> TransferAppConfig { - let native_denom = PrefixedDenom { - trace_path: array![], base: Denom::Native(PUBKEY().into()) - }; - - let hosted_denom = PrefixedDenom { trace_path: array![], base: Denom::Hosted(NAME()) }; - TransferAppConfig { - native_denom, - hosted_denom, + native_denom: NATIVE_DENOM(), + hosted_denom: HOSTED_DENOM(), chan_id_on_a: CHANNEL_ID(1), chan_id_on_b: CHANNEL_ID(0), amount: AMOUNT, diff --git a/cairo-contracts/packages/testkit/src/dummies/transfer.cairo b/cairo-contracts/packages/testkit/src/dummies/transfer.cairo index e4d00718..e0a493fb 100644 --- a/cairo-contracts/packages/testkit/src/dummies/transfer.cairo +++ b/cairo-contracts/packages/testkit/src/dummies/transfer.cairo @@ -2,7 +2,7 @@ use core::serde::Serde; use starknet::class_hash::class_hash_const; use starknet::contract_address_const; use starknet::{ContractAddress, ClassHash}; -use starknet_ibc_apps::transfer::types::{Participant, Memo}; +use starknet_ibc_apps::transfer::types::{Denom, Participant, PrefixedDenom, Memo}; pub const SUPPLY: u256 = 2000; pub const DECIMALS: u8 = 18_u8; @@ -40,6 +40,14 @@ pub fn COSMOS() -> Participant { serialized_address.into() } +pub fn NATIVE_DENOM() -> PrefixedDenom { + PrefixedDenom { trace_path: array![], base: Denom::Native(PUBKEY().into()) } +} + +pub fn HOSTED_DENOM() -> PrefixedDenom { + PrefixedDenom { trace_path: array![], base: Denom::Hosted(NAME()) } +} + pub fn EMPTY_MEMO() -> Memo { Memo { memo: "" } } diff --git a/cairo-contracts/packages/testkit/src/handles/app.cairo b/cairo-contracts/packages/testkit/src/handles/app.cairo index e02fdefd..2155ce1b 100644 --- a/cairo-contracts/packages/testkit/src/handles/app.cairo +++ b/cairo-contracts/packages/testkit/src/handles/app.cairo @@ -34,7 +34,7 @@ pub impl AppHandleImpl of AppHandle { IAppCallbackDispatcher { contract_address: *self.address } } - fn ibc_token_address(self: @AppContract, token_key: felt252) -> Option { + fn ibc_token_address(self: @AppContract, token_key: felt252) -> ContractAddress { ITokenAddressDispatcher { contract_address: *self.address }.ibc_token_address(token_key) } diff --git a/cairo-contracts/packages/testkit/src/lib.cairo b/cairo-contracts/packages/testkit/src/lib.cairo index ea27cdcb..2b99b065 100644 --- a/cairo-contracts/packages/testkit/src/lib.cairo +++ b/cairo-contracts/packages/testkit/src/lib.cairo @@ -4,11 +4,13 @@ pub mod mocks { mod channel; mod client; mod cometbft; + mod router; mod transfer; pub use channel::MockChannelHandler; pub use client::MockClientHandler; pub use cometbft::MockCometClient; + pub use router::MockRouterHandler; pub use transfer::MockTransferApp; } pub mod configs { @@ -26,8 +28,8 @@ pub mod dummies { HEIGHT, CLIENT, CLIENT_TYPE, CLIENT_ID, PORT_ID, CHANNEL_ID, SEQUENCE, CHANNEL_END }; pub use transfer::{ - NAME, SYMBOL, PUBKEY, AMOUNT, SUPPLY, OWNER, STARKNET, COSMOS, SALT, DECIMALS, CLASS_HASH, - EMPTY_MEMO + NAME, SYMBOL, PUBKEY, AMOUNT, SUPPLY, OWNER, STARKNET, COSMOS, NATIVE_DENOM, HOSTED_DENOM, + SALT, DECIMALS, CLASS_HASH, EMPTY_MEMO }; } pub mod event_spy { diff --git a/cairo-contracts/packages/testkit/src/mocks/router.cairo b/cairo-contracts/packages/testkit/src/mocks/router.cairo new file mode 100644 index 00000000..c17478ca --- /dev/null +++ b/cairo-contracts/packages/testkit/src/mocks/router.cairo @@ -0,0 +1,26 @@ +#[starknet::contract] +pub mod MockRouterHandler { + use starknet_ibc_core::router::{RouterHandlerComponent}; + + component!(path: RouterHandlerComponent, storage: router_handler, event: RouterHandlerEvent); + + impl RouterInitializerImpl = RouterHandlerComponent::RouterInitializerImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + router_handler: RouterHandlerComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + RouterHandlerEvent: RouterHandlerComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.router_handler.initializer(); + } +}