diff --git a/cairo-contracts/packages/core/src/channel/components/handler.cairo b/cairo-contracts/packages/core/src/channel/components/handler.cairo index b0c2e4d8..72e74276 100644 --- a/cairo-contracts/packages/core/src/channel/components/handler.cairo +++ b/cairo-contracts/packages/core/src/channel/components/handler.cairo @@ -13,15 +13,15 @@ pub mod ChannelHandlerComponent { use starknet_ibc_core::client::{ClientHandlerComponent, ClientContractTrait, StatusTrait}; use starknet_ibc_core::host::{ PortId, PortIdTrait, ChannelId, ChannelIdTrait, Sequence, channel_end_key, receipt_key, - commitment_path + ack_key, commitment_path }; use starknet_ibc_core::router::{RouterHandlerComponent, IRouter}; use starknet_ibc_utils::{ValidateBasicTrait, ComputeKeyTrait}; #[storage] struct Storage { - pub channel_ends: Map, - pub packet_receipts: Map, + pub channel_ends: Map>, + pub packet_receipts: Map>, pub packet_acks: Map, } @@ -120,10 +120,12 @@ pub mod ChannelHandlerComponent { match chan_end_on_b.ordering { ChannelOrdering::Unordered => { - let _receipt = self - .read_packet_receipt( + let ack = self + .read_packet_ack( @msg.packet.port_id_on_a, @msg.packet.chan_id_on_a, @msg.packet.seq_on_a ); + + assert(ack.len() == 0, ChannelErrors::ACK_ALREADY_EXISTS); }, ChannelOrdering::Ordered => {} }; @@ -158,7 +160,11 @@ pub mod ChannelHandlerComponent { fn read_channel_end( self: @ComponentState, port_id: @PortId, channel_id: @ChannelId ) -> ChannelEnd { - self.channel_ends.read(channel_end_key(port_id, channel_id)) + let maybe_chan_end = self.channel_ends.read(channel_end_key(port_id, channel_id)); + + assert(maybe_chan_end.is_some(), ChannelErrors::MISSING_CHANNEL_END); + + maybe_chan_end.unwrap() } fn read_packet_receipt( @@ -166,9 +172,18 @@ pub mod ChannelHandlerComponent { port_id: @PortId, channel_id: @ChannelId, sequence: @Sequence - ) -> Receipt { + ) -> Option { self.packet_receipts.read(receipt_key(port_id, channel_id, sequence)) } + + fn read_packet_ack( + self: @ComponentState, + port_id: @PortId, + channel_id: @ChannelId, + sequence: @Sequence + ) -> ByteArray { + self.packet_acks.read(ack_key(port_id, channel_id, sequence)) + } } #[generate_trait] @@ -181,7 +196,9 @@ pub mod ChannelHandlerComponent { channel_id: @ChannelId, channel_end: ChannelEnd ) { - self.channel_ends.write(channel_end_key(port_id, channel_id), channel_end); + self + .channel_ends + .write(channel_end_key(port_id, channel_id), Option::Some(channel_end)); } fn write_packet_receipt( @@ -191,7 +208,19 @@ pub mod ChannelHandlerComponent { sequence: @Sequence, receipt: Receipt ) { - self.packet_receipts.write(receipt_key(port_id, channel_id, sequence), receipt); + self + .packet_receipts + .write(receipt_key(port_id, channel_id, sequence), Option::Some(receipt)); + } + + fn write_packet_ack( + ref self: ComponentState, + port_id: @PortId, + channel_id: @ChannelId, + sequence: @Sequence, + ack: ByteArray + ) { + self.packet_acks.write(ack_key(port_id, channel_id, sequence), ack); } } diff --git a/cairo-contracts/packages/core/src/channel/errors.cairo b/cairo-contracts/packages/core/src/channel/errors.cairo index 27ce6c54..72721b3d 100644 --- a/cairo-contracts/packages/core/src/channel/errors.cairo +++ b/cairo-contracts/packages/core/src/channel/errors.cairo @@ -1,9 +1,11 @@ pub mod ChannelErrors { + pub const ACK_ALREADY_EXISTS: felt252 = 'ICS04: ack already exists'; pub const EMPTY_COMMITMENT_PROOF: felt252 = 'ICS04: empty commitment proof'; + pub const INACTIVE_CLIENT: felt252 = 'ICS04: inactive client'; pub const INVALID_CHANNEL_STATE: felt252 = 'ICS04: invalid channel state'; pub const INVALID_COUNTERPARTY: felt252 = 'ICS04: invalid counterparty'; - pub const TIMED_OUT_PACKET: felt252 = 'ICS04: packet timed out'; - pub const INACTIVE_CLIENT: felt252 = 'ICS04: inactive client'; pub const INVALID_PROOF_HEIGHT: felt252 = 'ICS04: invalid proof height'; + pub const MISSING_CHANNEL_END: felt252 = 'ICS04: missing channel end'; + pub const TIMED_OUT_PACKET: felt252 = 'ICS04: packet timed out'; pub const UNSUPPORTED_PORT_ID: felt252 = 'ICS04: unsupported port id'; } diff --git a/cairo-contracts/packages/core/src/channel/types.cairo b/cairo-contracts/packages/core/src/channel/types.cairo index bcee0be5..b38bcfa0 100644 --- a/cairo-contracts/packages/core/src/channel/types.cairo +++ b/cairo-contracts/packages/core/src/channel/types.cairo @@ -38,7 +38,7 @@ impl PacketValidateBasicImpl of ValidateBasicTrait { fn validate_basic(self: @Packet) {} } -#[derive(Clone, Debug, Drop, Serde, starknet::Store)] +#[derive(Clone, Debug, Drop, PartialEq, Serde, starknet::Store)] pub struct ChannelEnd { pub state: ChannelState, pub ordering: ChannelOrdering, @@ -58,9 +58,10 @@ pub impl ChannelEndImpl of ChannelEndTrait { /// Returns true if the counterparty matches the given counterparty. fn counterparty_matches( - self: @ChannelEnd, counterparty_port_id: @PortId, counterparty_chan_id: @ChannelId + self: @ChannelEnd, counterparty_port_id: @PortId, counterparty_channel_id: @ChannelId ) -> bool { - self.remote.port_id == counterparty_port_id && self.remote.chan_id == counterparty_chan_id + self.remote.port_id == counterparty_port_id + && self.remote.channel_id == counterparty_channel_id } } @@ -73,7 +74,7 @@ pub enum ChannelState { Closed, } -#[derive(Clone, Debug, Drop, Serde, starknet::Store)] +#[derive(Clone, Debug, Drop, PartialEq, Serde, starknet::Store)] pub enum ChannelOrdering { Unordered, Ordered, @@ -82,7 +83,7 @@ pub enum ChannelOrdering { #[derive(Clone, Debug, Drop, PartialEq, Serde, starknet::Store)] pub struct Counterparty { pub port_id: PortId, - pub chan_id: ChannelId, + pub channel_id: ChannelId, } #[derive(Clone, Debug, Drop, Serde)] @@ -90,7 +91,5 @@ pub struct Acknowledgement { pub ack: felt252, } -#[derive(Clone, Debug, Drop, Serde, starknet::Store)] -pub enum Receipt { - Ok -} +#[derive(Clone, Debug, Drop, PartialEq, Serde, starknet::Store)] +pub struct Receipt {} diff --git a/cairo-contracts/packages/core/src/lib.cairo b/cairo-contracts/packages/core/src/lib.cairo index 622dc255..993808bb 100644 --- a/cairo-contracts/packages/core/src/lib.cairo +++ b/cairo-contracts/packages/core/src/lib.cairo @@ -1,5 +1,5 @@ pub mod router; - +pub mod tests; pub mod channel { mod app_call; mod components; @@ -19,7 +19,7 @@ pub mod channel { pub use msgs::{MsgRecvPacket, MsgRecvPacketImpl, MsgRecvPacketTrait}; pub use types::{ Packet, PacketImpl, PacketTrait, ChannelEnd, ChannelEndImpl, ChannelEndTrait, ChannelState, - ChannelOrdering, Counterparty, Acknowledgement, Receipt + ChannelOrdering, Counterparty, Acknowledgement, Receipt, }; } pub mod client { diff --git a/cairo-contracts/packages/core/src/tests.cairo b/cairo-contracts/packages/core/src/tests.cairo new file mode 100644 index 00000000..71ce97aa --- /dev/null +++ b/cairo-contracts/packages/core/src/tests.cairo @@ -0,0 +1,6 @@ +mod dummy; +pub(crate) mod mocks; + +#[cfg(test)] +mod test_channel_handler; +pub(crate) use dummy::{CLIENT_ID, PORT_ID, CHANNEL_ID, SEQUENCE, CHANNEL_END}; diff --git a/cairo-contracts/packages/core/src/tests/dummy.cairo b/cairo-contracts/packages/core/src/tests/dummy.cairo new file mode 100644 index 00000000..4e700950 --- /dev/null +++ b/cairo-contracts/packages/core/src/tests/dummy.cairo @@ -0,0 +1,27 @@ +use starknet_ibc_core::channel::{ChannelEnd, ChannelState, ChannelOrdering, Counterparty}; +use starknet_ibc_core::host::{ClientId, PortId, ChannelId, Sequence}; + +pub fn CLIENT_ID() -> ClientId { + ClientId { client_type: '07-cometbft', sequence: 0 } +} + +pub fn PORT_ID() -> PortId { + PortId { port_id: "transfer" } +} + +pub fn CHANNEL_ID() -> ChannelId { + ChannelId { channel_id: "channel-0" } +} + +pub fn SEQUENCE() -> Sequence { + Sequence { sequence: 1 } +} + +pub fn CHANNEL_END() -> ChannelEnd { + ChannelEnd { + state: ChannelState::Open, + ordering: ChannelOrdering::Ordered, + remote: Counterparty { port_id: PORT_ID(), channel_id: CHANNEL_ID(), }, + client_id: CLIENT_ID(), + } +} diff --git a/cairo-contracts/packages/core/src/tests/mocks.cairo b/cairo-contracts/packages/core/src/tests/mocks.cairo new file mode 100644 index 00000000..b4933899 --- /dev/null +++ b/cairo-contracts/packages/core/src/tests/mocks.cairo @@ -0,0 +1 @@ +pub(crate) mod channel_handler; diff --git a/cairo-contracts/packages/core/src/tests/mocks/channel_handler.cairo b/cairo-contracts/packages/core/src/tests/mocks/channel_handler.cairo new file mode 100644 index 00000000..23af99ee --- /dev/null +++ b/cairo-contracts/packages/core/src/tests/mocks/channel_handler.cairo @@ -0,0 +1,35 @@ +#[starknet::contract] +pub(crate) mod MockChannelHandler { + use starknet_ibc_core::channel::{ChannelHandlerComponent, ChannelEventEmitterComponent}; + + component!( + path: ChannelEventEmitterComponent, + storage: channel_emitter, + event: ChannelEventEmitterEvent + ); + component!(path: ChannelHandlerComponent, storage: channel_handler, event: ChannelHandlerEvent); + + impl ChannelInitializerImpl = ChannelHandlerComponent::ChannelInitializerImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + channel_emitter: ChannelEventEmitterComponent::Storage, + #[substorage(v0)] + channel_handler: ChannelHandlerComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ChannelEventEmitterEvent: ChannelEventEmitterComponent::Event, + #[flat] + ChannelHandlerEvent: ChannelHandlerComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.channel_handler.initializer(); + } +} diff --git a/cairo-contracts/packages/core/src/tests/test_channel_handler.cairo b/cairo-contracts/packages/core/src/tests/test_channel_handler.cairo new file mode 100644 index 00000000..f7e49641 --- /dev/null +++ b/cairo-contracts/packages/core/src/tests/test_channel_handler.cairo @@ -0,0 +1,51 @@ +use ChannelHandlerComponent::ChannelReaderTrait; +use snforge_std::{spy_events, test_address, start_cheat_caller_address}; +use starknet::ContractAddress; +use starknet::contract_address_const; +use starknet_ibc_core::channel::ChannelHandlerComponent::{ + ChannelInitializerImpl, ChannelWriterTrait +}; +use starknet_ibc_core::channel::ChannelHandlerComponent; +use starknet_ibc_core::tests::mocks::channel_handler::MockChannelHandler; +use starknet_ibc_core::tests::{CHANNEL_END, CHANNEL_ID, CLIENT_ID, PORT_ID, SEQUENCE}; + +type ComponentState = ChannelHandlerComponent::ComponentState; + +fn COMPONENT_STATE() -> ComponentState { + ChannelHandlerComponent::component_state_for_testing() +} + +fn setup() -> ComponentState { + let mut state = COMPONENT_STATE(); + state.initializer(); + state +} + +#[test] +fn test_read_empty_packet_receipt() { + let state = setup(); + let receipt_res = state.read_packet_receipt(@PORT_ID(), @CHANNEL_ID(), @SEQUENCE()); + assert!(receipt_res.is_none()); +} + +#[test] +fn test_read_empty_packet_ack() { + let state = setup(); + let ack_res = state.read_packet_ack(@PORT_ID(), @CHANNEL_ID(), @SEQUENCE()); + assert!(ack_res.len() == 0); +} + +#[test] +#[should_panic] +fn test_read_empty_channel_end() { + let state = setup(); + state.read_channel_end(@PORT_ID(), @CHANNEL_ID()); +} + +#[test] +fn test_channel_end_storage() { + let mut state = setup(); + state.write_channel_end(@PORT_ID(), @CHANNEL_ID(), CHANNEL_END()); + let chan_end_res = state.read_channel_end(@PORT_ID(), @CHANNEL_ID()); + assert_eq!(chan_end_res, CHANNEL_END()); +}