From db0ca30a5d45d3b6cfdc4d6c44d1a47e96c64a40 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 22 Apr 2024 20:33:31 +0200 Subject: [PATCH 01/26] init: mailbox --- Scarb.lock | 14 +- Scarb.toml | 10 +- crates/hyperlane_interfaces/Scarb.toml | 13 -- crates/hyperlane_interfaces/src/lib.cairo | 72 ------- src/contracts/libs/message.cairo | 57 +++++ src/contracts/mailbox.cairo | 251 ++++++++++++++++++++++ src/interfaces.cairo | 126 +++++++++++ src/lib.cairo | 7 + 8 files changed, 459 insertions(+), 91 deletions(-) delete mode 100644 crates/hyperlane_interfaces/Scarb.toml delete mode 100644 crates/hyperlane_interfaces/src/lib.cairo create mode 100644 src/contracts/libs/message.cairo create mode 100644 src/contracts/mailbox.cairo create mode 100644 src/interfaces.cairo create mode 100644 src/lib.cairo diff --git a/Scarb.lock b/Scarb.lock index e9f77ba..7389f62 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -54,8 +54,20 @@ dependencies = [ ] [[package]] -name = "hyperlane_interfaces" +name = "hyperlane_starknet" version = "0.1.0" dependencies = [ "alexandria_bytes", + "openzeppelin", + "snforge_std", ] + +[[package]] +name = "openzeppelin" +version = "0.10.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=f22438c#f22438cca0e81c807d7c1742e83050ebd0ffcb3b" + +[[package]] +name = "snforge_std" +version = "0.22.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.22.0#9b215944c6c5871c738381b4ded61bbf06e7ba35" diff --git a/Scarb.toml b/Scarb.toml index 197dc49..8563566 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -1,5 +1,4 @@ -[workspace] -members = ["crates/hyperlane_interfaces"] +[package] name = "hyperlane_starknet" description = "Implementation of the Hyperlane protocol on Starknet." version = "0.1.0" @@ -8,14 +7,15 @@ cairo-version = "2.6.3" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html -[workspace.dependencies] +[dependencies] starknet = "2.6.3" alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", rev = "f22438c" } -[workspace.dev-dependencies] +[dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.22.0" } -[workspace.tool.fmt] +[tool.fmt] sort-module-level-items = true [[target.starknet-contract]] diff --git a/crates/hyperlane_interfaces/Scarb.toml b/crates/hyperlane_interfaces/Scarb.toml deleted file mode 100644 index 2ef1c20..0000000 --- a/crates/hyperlane_interfaces/Scarb.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "hyperlane_interfaces" -version = "0.1.0" -description = "Interfaces to interact with the hyperlane protocol." -homepage = "https://github.com/astraly-labs/hyperlane-starknet/tree/main/crates/hyperlane_interfaces" -edition = "2023_11" - -[tool] -fmt.workspace = true - -[dependencies] -starknet.workspace = true -alexandria_bytes.workspace = true diff --git a/crates/hyperlane_interfaces/src/lib.cairo b/crates/hyperlane_interfaces/src/lib.cairo deleted file mode 100644 index b5d5572..0000000 --- a/crates/hyperlane_interfaces/src/lib.cairo +++ /dev/null @@ -1,72 +0,0 @@ -use alexandria_bytes::Bytes; -use starknet::ContractAddress; - -#[starknet::interface] -trait IMailbox { - fn local_domain(self: @TContractState) -> u32; - - fn delivered(self: @TContractState, message_id: Bytes) -> bool; - - fn default_ism(self: @TContractState) -> ContractAddress; - - fn default_hook(self: @TContractState) -> ContractAddress; - - fn required_hook(self: @TContractState) -> ContractAddress; - - fn latest_dispatched_id(self: @TContractState) -> Bytes; - - fn dispatch( - ref self: TContractState, - destination_domain: u32, - recipient_address: Bytes, - message_body: Bytes, - custom_hook_metadata: Option, - custom_hook: Option, - ) -> Bytes; - - fn quote_dispatch( - ref self: TContractState, - destination_domain: u32, - recipient_address: Bytes, - message_body: Bytes, - custom_hook_metadata: Option, - custom_hook: Option, - ) -> u256; - - fn process(ref self: TContractState, metadata: Bytes, message: Bytes,); - - fn recipient_ism(ref self: TContractState, recipient: ContractAddress) -> ContractAddress; -} - -#[derive(Serde)] -pub enum ModuleType { - UNUSED, - ROUTING, - AGGREGATION, - LEGACY_MULTISIG, - MERKLE_ROOT_MULTISIG, - MESSAGE_ID_MULTISIG, - NULL, // used with relayer carrying no metadata - CCIP_READ, -} - -#[starknet::interface] -trait IInterchainSecurityModule { - /// Returns an enum that represents the type of security model encoded by this ISM. - /// Relayers infer how to fetch and format metadata. - fn module_type(self: @TContractState) -> ModuleType; - - /// Defines a security model responsible for verifying interchain messages based on the provided metadata. - /// Returns true if the message was verified. - /// - /// # Arguments - /// * `_metadata` - Off-chain metadata provided by a relayer, specific to the security model encoded by - /// the module (e.g. validator signatures) - /// * `_message` - Hyperlane encoded interchain message - fn verify(self: @TContractState, metadata: Bytes, message: Bytes) -> bool; -} - -#[starknet::interface] -trait ISpecifiesInterchainSecurityModule { - fn interchain_security_module(self: @TContractState) -> ContractAddress; -} diff --git a/src/contracts/libs/message.cairo b/src/contracts/libs/message.cairo new file mode 100644 index 0000000..25b2450 --- /dev/null +++ b/src/contracts/libs/message.cairo @@ -0,0 +1,57 @@ +use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; +use core::poseidon::poseidon_hash_span; + +use starknet::{ContractAddress, contract_address_const}; + + +#[derive(Serde, starknet::Store, Drop, Clone)] +pub struct Message { + pub version: u8, + pub nonce: u32, + pub origin: u32, + pub sender: ContractAddress, + pub destination: u32, + pub recipient: ContractAddress, + pub body: Bytes, +} + +#[generate_trait] +pub impl MessageImpl of MessageTrait { + fn default() -> Message { + Message { + version: 3_u8, + nonce: 0_u32, + origin: 0_u32, + sender: contract_address_const::<0>(), + destination: 0_u32, + recipient: contract_address_const::<0>(), + body: BytesTrait::new_empty(), + } + } + + fn id(self: Message) -> felt252 { + let message_array: Array = array![ + self.version.into(), + self.nonce.into(), + self.origin.into(), + self.sender.into(), + self.destination.into(), + self.recipient.into() + ]; + poseidon_hash_span(message_array.span()) + } + + fn format_message(_version: u8, _nonce: u32, _origin_domain: u32, _sender: ContractAddress, _destination_domain: u32, _recipient: ContractAddress, _message_body: Bytes)-> u256{ + // POSEIDON MAY BE BETTER HERE + let mut bytes: Bytes = BytesTrait::new(0, array![]); + bytes.append_u8(_version); + bytes.append_u32(_nonce); + bytes.append_u32(_origin_domain); + bytes.append_address(_sender); + bytes.append_u32(_destination_domain); + bytes.append_address(_recipient); + // bytes.append_bytes31(_message_body); + let keccak_hash = bytes.keccak(); + keccak_hash + } +} diff --git a/src/contracts/mailbox.cairo b/src/contracts/mailbox.cairo new file mode 100644 index 0000000..c04537c --- /dev/null +++ b/src/contracts/mailbox.cairo @@ -0,0 +1,251 @@ +#[starknet::contract] +mod mailbox { + use core::starknet::event::EventEmitter; +use alexandria_bytes::{Bytes, BytesTrait}; + use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + use hyperlane_starknet::interfaces::{ + IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, IInterchainSecurityModuleDispatcher, + IInterchainSecurityModuleDispatcherTrait, IPostDispatchHookDispatcher, + IPostDispatchHookDispatcherTrait, HYPERLANE_VERSION, IMessageRecipientDispatcher, IMessageRecipientDispatcherTrait, + }; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use starknet::{ContractAddress, ClassHash, get_caller_address, get_block_number}; + + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[derive(Drop, Serde, starknet::Store)] + struct Delivery { + processor: ContractAddress, + block_number: u64, + } + + + #[storage] + struct Storage { + local_domain: u32, + nonce: u32, + latest_dispatched_id: Bytes, + default_ism: ContractAddress, + default_hook: ContractAddress, + required_hook: ContractAddress, + deliveries: LegacyMap::, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + DefaultIsmSet: DefaultIsmSet, + DefaultHookSet: DefaultHookSet, + RequiredHookSet: RequiredHookSet, + Process: Process, + ProcessId: ProcessId, + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + #[derive(starknet::Event, Drop)] + struct DefaultIsmSet { + module: ContractAddress + } + + #[derive(starknet::Event, Drop)] + struct DefaultHookSet { + hook: ContractAddress + } + + #[derive(starknet::Event, Drop)] + struct RequiredHookSet { + hook: ContractAddress + } + + #[derive(starknet::Event, Drop)] + struct Process { + origin: u32, + sender: ContractAddress, + recipient: ContractAddress + } + + #[derive(starknet::Event, Drop)] + struct ProcessId { + id: felt252 + } + + + + mod Errors { + pub const WRONG_HYPERLANE_VERSION: felt252 = 'Wrong hyperlane version'; + pub const UNEXPECTED_DESTINATION: felt252 = 'Unexpected destination'; + pub const ALREADY_DELIVERED: felt252 = 'Mailbox: already delivered'; + pub const ISM_VERIFICATION_FAILED: felt252 = 'Mailbox:ism verification failed'; + } + + #[constructor] + fn constructor(ref self: ContractState, _local_domain: u32, owner: ContractAddress) { + self.local_domain.write(_local_domain); + self.ownable.initializer(owner); + } + + #[abi(embed_v0)] + impl Upgradeable of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + } + } + + + #[abi(embed_v0)] + impl IMailboxImpl of IMailbox { + fn initializer( + ref self: ContractState, + _default_ism: ContractAddress, + _default_hook: ContractAddress, + _required_hook: ContractAddress + ) { + self.set_default_ism(_default_ism); + self.set_default_hook(_default_hook); + self.set_required_hook(_required_hook); + } + + fn get_local_domain(self: @ContractState) -> u32 { + self.local_domain.read() + } + + fn get_default_ism(self: @ContractState) -> ContractAddress { + self.default_ism.read() + } + + fn get_default_hook(self: @ContractState) -> ContractAddress { + self.default_hook.read() + } + + fn get_required_hook(self: @ContractState) -> ContractAddress { + self.required_hook.read() + } + + fn get_latest_dispatched_id(self: @ContractState) -> Bytes { + self.latest_dispatched_id.read() + } + + + fn set_default_ism(ref self: ContractState, _module: ContractAddress) { + self.ownable.assert_only_owner(); + self.default_ism.write(_module); + self.emit(DefaultIsmSet { module: _module }); + } + + fn set_default_hook(ref self: ContractState, _hook: ContractAddress) { + self.ownable.assert_only_owner(); + self.default_hook.write(_hook); + self.emit(DefaultHookSet { hook: _hook }); + } + + fn set_required_hook(ref self: ContractState, _hook: ContractAddress) { + self.ownable.assert_only_owner(); + self.required_hook.write(_hook); + self.emit(RequiredHookSet { hook: _hook }); + } + + fn set_local_domain(ref self: ContractState, _local_domain: u32) { + self.ownable.assert_only_owner(); + self.local_domain.write(_local_domain); + } + + fn dispatch( + ref self: ContractState, + _destination_domain: u32, + _recipient_address: ContractAddress, + _message_body: Bytes, + _custom_hook_metadata: Option, + _custom_hook: Option + ) -> Bytes { + BytesTrait::new_empty() + } + + fn delivered(self: @ContractState, _message_id: felt252) -> bool { + self.deliveries.read(_message_id).block_number > 0 + } + + fn process(ref self: ContractState, _metadata: Bytes, _message: Message) { + assert(_message.version == HYPERLANE_VERSION, Errors::WRONG_HYPERLANE_VERSION); + assert( + _message.destination == self.local_domain.read(), Errors::UNEXPECTED_DESTINATION + ); + let id = _message.clone().id(); + let caller = get_caller_address(); + let block_number = get_block_number(); + assert(!self.delivered(id), Errors::ALREADY_DELIVERED); + let recipient_ism = self.recipient_ism(_message.recipient); + let ism = IInterchainSecurityModuleDispatcher{contract_address: recipient_ism}; + self.deliveries.write(id,Delivery {processor: caller, block_number: block_number}); + self.emit( + Process { + origin: _message.origin, + sender: _message.sender, + recipient : _message.recipient + } + ); + self.emit( + ProcessId { + id: id + } + ); + assert(ism.verify(_metadata, _message.clone()),Errors::ISM_VERIFICATION_FAILED); + let message_recipient = IMessageRecipientDispatcher{contract_address: _message.recipient}; + message_recipient.handle(_message.origin, _message.sender, _message.body); + + + + } + fn quote_dispatch( + ref self: ContractState, + _destination_domain: u32, + _recipient_address: ContractAddress, + _message_body: Bytes, + _custom_hook_metadata: Option, + _custom_hook: Option, + ) -> u256 { + let hook_address = match _custom_hook { + Option::Some(hook) => hook, + Option::None(()) => self.default_hook.read() + }; + let hook_metadata = match _custom_hook_metadata { + Option::Some(hook_metadata) => hook_metadata, + Option::None(()) => BytesTrait::new_empty(), + }; + let message = build_message(@self,_destination_domain, _recipient_address, _message_body); + let required_hook_address = self.required_hook.read(); + let required_hook = IPostDispatchHookDispatcher{contract_address: required_hook_address}; + let hook = IPostDispatchHookDispatcher{contract_address: hook_address}; + required_hook.quote_dispatch(hook_metadata.clone(), message.clone()) - hook.quote_dispatch(hook_metadata, message) + + } + + fn recipient_ism(ref self: ContractState, _recipient: ContractAddress) -> ContractAddress { + //TODO + + self.default_ism.read() + } + } + + fn build_message(self: @ContractState, _destination_domain: u32, _recipient_address:ContractAddress, _message_body: Bytes) -> u256{ + let nonce = self.nonce.read(); + let local_domain = self.local_domain.read(); + let caller = get_caller_address(); + MessageTrait::format_message(HYPERLANE_VERSION,nonce,local_domain,caller, _destination_domain, _recipient_address, _message_body) + } +} + diff --git a/src/interfaces.cairo b/src/interfaces.cairo new file mode 100644 index 0000000..ac15487 --- /dev/null +++ b/src/interfaces.cairo @@ -0,0 +1,126 @@ +use alexandria_bytes::Bytes; +use hyperlane_starknet::contracts::libs::message::Message; +use starknet::ContractAddress; + +#[derive(Serde)] +pub enum Types { + UNUSED, + ROUTING, + AGGREGATION, + MERKLE_TREE, + INTERCHAIN_GAS_PAYMASTER, + FALLBACK_ROUTING, + ID_AUTH_ISM, + PAUSABLE, + PROTOCOL_FEE, + LAYER_ZERO_V1, + Rate_Limited_Hook +} + + +#[derive(Serde)] +pub enum ModuleType { + UNUSED, + ROUTING, + AGGREGATION, + LEGACY_MULTISIG, + MERKLE_ROOT_MULTISIG, + MESSAGE_ID_MULTISIG, + NULL, // used with relayer carrying no metadata + CCIP_READ, +} + +pub const HYPERLANE_VERSION: u8 = 3; + +#[starknet::interface] +pub trait IMailbox { + fn initializer( + ref self: TContractState, + _default_ism: ContractAddress, + _default_hook: ContractAddress, + _required_hook: ContractAddress + ); + + fn get_local_domain(self: @TContractState) -> u32; + + fn delivered(self: @TContractState, _message_id: felt252) -> bool; + + fn get_default_ism(self: @TContractState) -> ContractAddress; + + fn get_default_hook(self: @TContractState) -> ContractAddress; + + fn get_required_hook(self: @TContractState) -> ContractAddress; + + fn get_latest_dispatched_id(self: @TContractState) -> Bytes; + + fn dispatch( + ref self: TContractState, + _destination_domain: u32, + _recipient_address: ContractAddress, + _message_body: Bytes, + _custom_hook_metadata: Option, + _custom_hook: Option, + ) -> Bytes; + + fn quote_dispatch( + ref self: TContractState, + _destination_domain: u32, + _recipient_address: ContractAddress, + _message_body: Bytes, + _custom_hook_metadata: Option, + _custom_hook: Option, + ) -> u256; + + fn process(ref self: TContractState, _metadata: Bytes, _message: Message,); + + fn recipient_ism(ref self: TContractState, _recipient: ContractAddress) -> ContractAddress; + + fn set_default_ism(ref self: TContractState, _module: ContractAddress); + + fn set_default_hook(ref self: TContractState, _hook: ContractAddress); + + fn set_required_hook(ref self: TContractState, _hook: ContractAddress); + + fn set_local_domain(ref self: TContractState, _local_domain: u32); +} + + +#[starknet::interface] +pub trait IInterchainSecurityModule { + /// Returns an enum that represents the type of security model encoded by this ISM. + /// Relayers infer how to fetch and format metadata. + fn module_type(self: @TContractState) -> ModuleType; + + /// Defines a security model responsible for verifying interchain messages based on the provided metadata. + /// Returns true if the message was verified. + /// + /// # Arguments + /// * `_metadata` - Off-chain metadata provided by a relayer, specific to the security model encoded by + /// the module (e.g. validator signatures) + /// * `_message` - Hyperlane encoded interchain message + fn verify(self: @TContractState, _metadata: Bytes, _message: Message) -> bool; +} + +#[starknet::interface] +pub trait ISpecifiesInterchainSecurityModule { + fn interchain_security_module(self: @TContractState) -> ContractAddress; +} + + +#[starknet::interface] +pub trait IPostDispatchHook { + fn get_hook_type(self: @TContractState) -> Types; + + fn supports_metadata(self: @TContractState, _metadata: Bytes) -> bool; + + fn post_dispatch(ref self: TContractState, _metadata: Bytes, _message: Bytes); + + fn quote_dispatch(ref self: TContractState, _metadata: Bytes, _message: Bytes) -> u256; +} + + + +#[starknet::interface] +pub trait IMessageRecipient{ + fn handle(self: @TContractState, origin: u32, _sender: ContractAddress, _message: Bytes); +} \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo new file mode 100644 index 0000000..1465642 --- /dev/null +++ b/src/lib.cairo @@ -0,0 +1,7 @@ +mod interfaces; +mod contracts { + pub mod mailbox; + pub mod libs { + pub mod message; + } +} From c7a547ee4cd5e084b81d9055f39a21fdff99f5b6 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Thu, 25 Apr 2024 14:12:52 +0200 Subject: [PATCH 02/26] feat: mailbox client + fix mailbox/message --- Scarb.lock | 1 + Scarb.toml | 1 + src/contracts/client/mailboxclient.cairo | 131 ++++++++++++++++ src/contracts/libs/message.cairo | 46 +++--- src/contracts/mailbox.cairo | 187 +++++++++++++++++------ src/interfaces.cairo | 56 ++++++- src/lib.cairo | 3 + src/utils/keccak256.cairo | 8 + 8 files changed, 356 insertions(+), 77 deletions(-) create mode 100644 src/contracts/client/mailboxclient.cairo create mode 100644 src/utils/keccak256.cairo diff --git a/Scarb.lock b/Scarb.lock index 7389f62..5079c91 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -58,6 +58,7 @@ name = "hyperlane_starknet" version = "0.1.0" dependencies = [ "alexandria_bytes", + "alexandria_math", "openzeppelin", "snforge_std", ] diff --git a/Scarb.toml b/Scarb.toml index 8563566..913a8f5 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -10,6 +10,7 @@ cairo-version = "2.6.3" [dependencies] starknet = "2.6.3" alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git" } +alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git" } openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", rev = "f22438c" } [dev-dependencies] diff --git a/src/contracts/client/mailboxclient.cairo b/src/contracts/client/mailboxclient.cairo new file mode 100644 index 0000000..c65d89b --- /dev/null +++ b/src/contracts/client/mailboxclient.cairo @@ -0,0 +1,131 @@ +#[starknet::contract] +mod mailboxclient { + use hyperlane_starknet::interfaces::{ + IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, IInterchainSecurityModuleDispatcher, + IInterchainSecurityModuleDispatcherTrait, + }; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use starknet::{ContractAddress, contract_address_const}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + #[storage] + struct Storage { + mailbox: ContractAddress, + local_domain: u32, + hook: ContractAddress, + interchain_security_module: ContractAddress, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + + #[constructor] + fn constructor(ref self: ContractState, _mailbox: ContractAddress, _owner: ContractAddress) { + self.mailbox.write(mailbox); + let mailbox = IMailboxDispatcher { contract_address: _mailbox }; + let local_domain = mailbox.get_local_domain(); + self.ownable.initializer(owner); + } + #[abi(embed_v0)] + impl Upgradeable of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + } + } + + + #[abi(embed_v0)] + impl IMailboxClientImpl of IMailboxClient { + fn set_hook(ref self: ContractState, _hook: ContractAddress) { + self.ownable.assert_only_owner(); + self.hook.write(_hook); + } + + fn set_interchain_security_module(ref self: ContractState, _module: ContractAddress) { + self.ownable.assert_only_owner(); + self.interchain_security_module.write(_module); + } + + fn _MailboxClient_initialize( + ref self: ContractState, + _hook: ContractAddress, + _interchain_security_module: ContractAddress, + ) { + self.ownable.assert_only_owner(); + set_hook(_hook); + set_interchain_security_module(_interchain_security_module); + } + + fn _is_latest_dispatched(self: @ContractState, _id: u256) -> bool { + let mailbox_address = self.mailbox.read(); + let mailbox = IMailbox { contract_address: mailbox_address }; + mailbox.latest_dispatched_id() == _id + } + + fn _is_delivered(self: @ContractState, _id: u256) -> bool { + let mailbox_address = self.mailbox.read(); + let mailbox = IMailbox { contract_address: mailbox_address }; + mailbox.delivered(_id) + } + + fn _dispatch( + self: @ContractState, + _destination_domain: u32, + _recipient: ContractAddress, + _message_body: Bytes, + _value: Option, + _hook_metadata: Option, + _hook: Option + ) { + // TODO: refer to Interchain Gas Payment for msg.value usage + let value = match _value { + Option::Some(val) => val, + Option::None(()) => starknet::get_tx_info().unbox().max_fee.into() + }; + let hook_metadata = match _hook_metadata { + Option::Some(metadata) => metadata, + Option::None(()) => BytesTrait::new_empty() + }; + let hook = match _hook { + Option::Some(address) => address, + Option::None(()) => contract_address_const::<0>() + }; + let mailbox_address = self.mailbox.read(); + let mailbox = IMailbox { contract_address: mailbox_address }; + mailbox.dispatch(_destination_domain, _recipient, _message_body, _hook_metadata, hook); + } + + fn quote_dispatch( + self: @ContractState, + _destination_domain: u32, + _recipient: ContractAddress, + _message_body: Bytes, + _hook_metadata: Option, + _hook: Option + ) { + let hook_metadata = match _hook_metadata { + Option::Some(metadata) => metadata, + Option::None(()) => BytesTrait::new_empty() + }; + let hook = match _hook { + Option::Some(address) => address, + Option::None(()) => contract_address_const::<0>() + }; + let mailbox_address = self.mailbox.read(); + let mailbox = IMailbox { contract_address: mailbox_address }; + mailbox + .quote_dispatch( + _destination_domain, _recipient, _message_body, _hook_metadata, hook + ); + } + } +} diff --git a/src/contracts/libs/message.cairo b/src/contracts/libs/message.cairo index 25b2450..6da4250 100644 --- a/src/contracts/libs/message.cairo +++ b/src/contracts/libs/message.cairo @@ -1,6 +1,7 @@ use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; +use core::keccak::keccak_u256s_be_inputs; use core::poseidon::poseidon_hash_span; - +use hyperlane_starknet::utils::keccak256::reverse_endianness; use starknet::{ContractAddress, contract_address_const}; @@ -29,29 +30,26 @@ pub impl MessageImpl of MessageTrait { } } - fn id(self: Message) -> felt252 { - let message_array: Array = array![ - self.version.into(), - self.nonce.into(), - self.origin.into(), - self.sender.into(), - self.destination.into(), - self.recipient.into() + fn format_message(message: Message) -> u256 { + let sender: felt252 = message.sender.into(); + let recipient: felt252 = message.recipient.into(); + + let mut input: Array = array![ + message.version.into(), + message.origin.into(), + sender.into(), + message.destination.into(), + recipient.into(), + message.body.size().into() ]; - poseidon_hash_span(message_array.span()) + let mut message_data = message.body.data(); + loop { + match message_data.pop_front() { + Option::Some(data) => { input.append(data.into()); }, + Option::None(_) => { break (); } + }; + }; + let hash = keccak_u256s_be_inputs(input.span()); + reverse_endianness(hash) } - - fn format_message(_version: u8, _nonce: u32, _origin_domain: u32, _sender: ContractAddress, _destination_domain: u32, _recipient: ContractAddress, _message_body: Bytes)-> u256{ - // POSEIDON MAY BE BETTER HERE - let mut bytes: Bytes = BytesTrait::new(0, array![]); - bytes.append_u8(_version); - bytes.append_u32(_nonce); - bytes.append_u32(_origin_domain); - bytes.append_address(_sender); - bytes.append_u32(_destination_domain); - bytes.append_address(_recipient); - // bytes.append_bytes31(_message_body); - let keccak_hash = bytes.keccak(); - keccak_hash - } } diff --git a/src/contracts/mailbox.cairo b/src/contracts/mailbox.cairo index c04537c..f0520b6 100644 --- a/src/contracts/mailbox.cairo +++ b/src/contracts/mailbox.cairo @@ -1,16 +1,21 @@ #[starknet::contract] mod mailbox { + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; + use core::starknet::SyscallResultTrait; use core::starknet::event::EventEmitter; -use alexandria_bytes::{Bytes, BytesTrait}; use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; use hyperlane_starknet::interfaces::{ IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, IPostDispatchHookDispatcher, - IPostDispatchHookDispatcherTrait, HYPERLANE_VERSION, IMessageRecipientDispatcher, IMessageRecipientDispatcherTrait, + ISpecifiesInterchainSecurityModuleDispatcher, + ISpecifiesInterchainSecurityModuleDispatcherTrait, IPostDispatchHookDispatcherTrait, + HYPERLANE_VERSION, IMessageRecipientDispatcher, IMessageRecipientDispatcherTrait, }; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; - use starknet::{ContractAddress, ClassHash, get_caller_address, get_block_number}; + use starknet::{ + ContractAddress, ClassHash, get_caller_address, get_block_number, contract_address_const + }; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); @@ -31,11 +36,11 @@ use alexandria_bytes::{Bytes, BytesTrait}; struct Storage { local_domain: u32, nonce: u32, - latest_dispatched_id: Bytes, + latest_dispatched_id: u256, default_ism: ContractAddress, default_hook: ContractAddress, required_hook: ContractAddress, - deliveries: LegacyMap::, + deliveries: LegacyMap::, #[substorage(v0)] ownable: OwnableComponent::Storage, #[substorage(v0)] @@ -48,8 +53,10 @@ use alexandria_bytes::{Bytes, BytesTrait}; DefaultIsmSet: DefaultIsmSet, DefaultHookSet: DefaultHookSet, RequiredHookSet: RequiredHookSet, - Process: Process, + Process: Process, ProcessId: ProcessId, + Dispatch: Dispatch, + DispatchId: DispatchId, #[flat] OwnableEvent: OwnableComponent::Event, #[flat] @@ -73,16 +80,28 @@ use alexandria_bytes::{Bytes, BytesTrait}; #[derive(starknet::Event, Drop)] struct Process { - origin: u32, - sender: ContractAddress, + origin: u32, + sender: ContractAddress, recipient: ContractAddress } #[derive(starknet::Event, Drop)] struct ProcessId { - id: felt252 + id: u256 } + #[derive(starknet::Event, Drop)] + struct Dispatch { + sender: ContractAddress, + destination_domain: u32, + recipient_address: ContractAddress, + message: u256 + } + + #[derive(starknet::Event, Drop)] + struct DispatchId { + id: u256 + } mod Errors { @@ -90,6 +109,7 @@ use alexandria_bytes::{Bytes, BytesTrait}; pub const UNEXPECTED_DESTINATION: felt252 = 'Unexpected destination'; pub const ALREADY_DELIVERED: felt252 = 'Mailbox: already delivered'; pub const ISM_VERIFICATION_FAILED: felt252 = 'Mailbox:ism verification failed'; + pub const NO_ISM_FOUND: felt252 = 'ISM: no ISM found'; } #[constructor] @@ -136,7 +156,7 @@ use alexandria_bytes::{Bytes, BytesTrait}; self.required_hook.read() } - fn get_latest_dispatched_id(self: @ContractState) -> Bytes { + fn get_latest_dispatched_id(self: @ContractState) -> u256 { self.latest_dispatched_id.read() } @@ -171,11 +191,50 @@ use alexandria_bytes::{Bytes, BytesTrait}; _message_body: Bytes, _custom_hook_metadata: Option, _custom_hook: Option - ) -> Bytes { - BytesTrait::new_empty() + ) -> u256 { + let hook = match _custom_hook { + Option::Some(hook) => hook, + Option::None(()) => self.default_hook.read(), + }; + let hook_metadata = match _custom_hook_metadata { + Option::Some(hook_metadata) => hook_metadata, + Option::None(()) => BytesTrait::new_empty() + }; + + let message = build_message( + @self, _destination_domain, _recipient_address, _message_body + ); + let id = message; + self.latest_dispatched_id.write(id); + let current_nonce = self.nonce.read(); + self.nonce.write(current_nonce + 1); + let caller = get_caller_address(); + self + .emit( + Dispatch { + sender: caller, + destination_domain: _destination_domain, + recipient_address: _recipient_address, + message: message + } + ); + self.emit(DispatchId { id: id }); + let required_hook_address = self.required_hook.read(); + let required_hook = IPostDispatchHookDispatcher { + contract_address: required_hook_address + }; + let hook = IPostDispatchHookDispatcher { contract_address: hook }; + let mut required_value = required_hook.quote_dispatch(hook_metadata.clone(), message); + let max_fee = starknet::get_tx_info().unbox().max_fee.into(); + if (max_fee < required_value) { + required_value = max_fee; + } + required_hook.post_dispatch(hook_metadata.clone(), message); + hook.post_dispatch(hook_metadata, message); + id } - fn delivered(self: @ContractState, _message_id: felt252) -> bool { + fn delivered(self: @ContractState, _message_id: u256) -> bool { self.deliveries.read(_message_id).block_number > 0 } @@ -184,31 +243,27 @@ use alexandria_bytes::{Bytes, BytesTrait}; assert( _message.destination == self.local_domain.read(), Errors::UNEXPECTED_DESTINATION ); - let id = _message.clone().id(); + let id = MessageTrait::format_message(_message.clone()); let caller = get_caller_address(); let block_number = get_block_number(); assert(!self.delivered(id), Errors::ALREADY_DELIVERED); - let recipient_ism = self.recipient_ism(_message.recipient); - let ism = IInterchainSecurityModuleDispatcher{contract_address: recipient_ism}; - self.deliveries.write(id,Delivery {processor: caller, block_number: block_number}); - self.emit( - Process { - origin: _message.origin, - sender: _message.sender, - recipient : _message.recipient - } - ); - self.emit( - ProcessId { - id: id - } - ); - assert(ism.verify(_metadata, _message.clone()),Errors::ISM_VERIFICATION_FAILED); - let message_recipient = IMessageRecipientDispatcher{contract_address: _message.recipient}; + let recipient_ism = self.recipient_ism(_message.recipient); + let ism = IInterchainSecurityModuleDispatcher { contract_address: recipient_ism }; + self.deliveries.write(id, Delivery { processor: caller, block_number: block_number }); + self + .emit( + Process { + origin: _message.origin, + sender: _message.sender, + recipient: _message.recipient + } + ); + self.emit(ProcessId { id: id }); + assert(ism.verify(_metadata, _message.clone()), Errors::ISM_VERIFICATION_FAILED); + let message_recipient = IMessageRecipientDispatcher { + contract_address: _message.recipient + }; message_recipient.handle(_message.origin, _message.sender, _message.body); - - - } fn quote_dispatch( ref self: ContractState, @@ -219,33 +274,75 @@ use alexandria_bytes::{Bytes, BytesTrait}; _custom_hook: Option, ) -> u256 { let hook_address = match _custom_hook { - Option::Some(hook) => hook, + Option::Some(hook) => hook, Option::None(()) => self.default_hook.read() }; let hook_metadata = match _custom_hook_metadata { - Option::Some(hook_metadata) => hook_metadata, + Option::Some(hook_metadata) => hook_metadata, Option::None(()) => BytesTrait::new_empty(), }; - let message = build_message(@self,_destination_domain, _recipient_address, _message_body); + let message = build_message( + @self, _destination_domain, _recipient_address, _message_body.clone() + ); let required_hook_address = self.required_hook.read(); - let required_hook = IPostDispatchHookDispatcher{contract_address: required_hook_address}; - let hook = IPostDispatchHookDispatcher{contract_address: hook_address}; - required_hook.quote_dispatch(hook_metadata.clone(), message.clone()) - hook.quote_dispatch(hook_metadata, message) - + let required_hook = IPostDispatchHookDispatcher { + contract_address: required_hook_address + }; + let hook = IPostDispatchHookDispatcher { contract_address: hook_address }; + required_hook.quote_dispatch(hook_metadata.clone(), message.clone()) + - hook.quote_dispatch(hook_metadata, message) } fn recipient_ism(ref self: ContractState, _recipient: ContractAddress) -> ContractAddress { - //TODO - + let mut call_data: Array = ArrayTrait::new(); + let mut res = starknet::syscalls::call_contract_syscall( + _recipient, selector!("interchain_security_module"), call_data.span() + ); + let mut ism_res = match res { + Result::Ok(ism) => ism, + Result::Err(revert_reason) => { + assert(revert_reason == array!['ENTRYPOINT_FAILED'], Errors::NO_ISM_FOUND); + array![].span() + } + }; + if (ism_res.len() != 0) { + let ism_address = Serde::::deserialize(ref ism_res).unwrap(); + if (ism_address != contract_address_const::<0>()) { + return ism_address; + } + } self.default_ism.read() } + + fn processor(self: @ContractState, _id: u256) -> ContractAddress { + self.deliveries.read(_id).processor + } + + fn processed_at(self: @ContractState, _id: u256) -> u64 { + self.deliveries.read(_id).block_number + } } - fn build_message(self: @ContractState, _destination_domain: u32, _recipient_address:ContractAddress, _message_body: Bytes) -> u256{ + fn build_message( + self: @ContractState, + _destination_domain: u32, + _recipient_address: ContractAddress, + _message_body: Bytes + ) -> u256 { let nonce = self.nonce.read(); let local_domain = self.local_domain.read(); let caller = get_caller_address(); - MessageTrait::format_message(HYPERLANE_VERSION,nonce,local_domain,caller, _destination_domain, _recipient_address, _message_body) + MessageTrait::format_message( + Message { + version: HYPERLANE_VERSION, + nonce: nonce, + origin: local_domain, + sender: caller, + destination: _destination_domain, + recipient: _recipient_address, + body: _message_body + } + ) } } diff --git a/src/interfaces.cairo b/src/interfaces.cairo index ac15487..53d08e6 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -43,7 +43,7 @@ pub trait IMailbox { fn get_local_domain(self: @TContractState) -> u32; - fn delivered(self: @TContractState, _message_id: felt252) -> bool; + fn delivered(self: @TContractState, _message_id: u256) -> bool; fn get_default_ism(self: @TContractState) -> ContractAddress; @@ -51,7 +51,7 @@ pub trait IMailbox { fn get_required_hook(self: @TContractState) -> ContractAddress; - fn get_latest_dispatched_id(self: @TContractState) -> Bytes; + fn get_latest_dispatched_id(self: @TContractState) -> u256; fn dispatch( ref self: TContractState, @@ -60,7 +60,7 @@ pub trait IMailbox { _message_body: Bytes, _custom_hook_metadata: Option, _custom_hook: Option, - ) -> Bytes; + ) -> u256; fn quote_dispatch( ref self: TContractState, @@ -82,6 +82,10 @@ pub trait IMailbox { fn set_required_hook(ref self: TContractState, _hook: ContractAddress); fn set_local_domain(ref self: TContractState, _local_domain: u32); + + fn processor(self: @TContractState, _id: u256) -> ContractAddress; + + fn processed_at(self: @TContractState, _id: u256) -> u64; } @@ -113,14 +117,50 @@ pub trait IPostDispatchHook { fn supports_metadata(self: @TContractState, _metadata: Bytes) -> bool; - fn post_dispatch(ref self: TContractState, _metadata: Bytes, _message: Bytes); + fn post_dispatch(ref self: TContractState, _metadata: Bytes, _message: u256); - fn quote_dispatch(ref self: TContractState, _metadata: Bytes, _message: Bytes) -> u256; + fn quote_dispatch(ref self: TContractState, _metadata: Bytes, _message: u256) -> u256; } - #[starknet::interface] -pub trait IMessageRecipient{ +pub trait IMessageRecipient { fn handle(self: @TContractState, origin: u32, _sender: ContractAddress, _message: Bytes); -} \ No newline at end of file +} + + +#[starknet::interface] +pub trait IMailboxClient { + fn set_hook(ref self: TContractState, _hook: ContractAddress); + + fn set_interchain_security_module(ref self: TContractState, _module: ContractAddress); + + fn _MailboxClient_initialize( + ref self: TContractState, + _hook: ContractAddress, + _interchain_security_module: ContractAddress, + _owner: ContractAddress + ); + + fn _is_latest_dispatched(self: @TContractState, _id: u256) -> bool; + + fn _is_delivered(self: @TContractState, _id: u256) -> bool; + + fn _dispatch( + self: @TContractState, + _destination_domain: u32, + _recipient: ContractAddress, + _message_body: Bytes, + _value: Option, + _hook_metadata: Option, + ) -> Bytes; + + fn quote_dispatch( + self: @TContractState, + _destination_domain: u32, + _recipient: ContractAddress, + _message_body: Bytes, + _hook_metadata: Option, + _hook: Option + ); +} diff --git a/src/lib.cairo b/src/lib.cairo index 1465642..e7bebf5 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -5,3 +5,6 @@ mod contracts { pub mod message; } } +mod utils { + pub mod keccak256; +} diff --git a/src/utils/keccak256.cairo b/src/utils/keccak256.cairo new file mode 100644 index 0000000..1a67982 --- /dev/null +++ b/src/utils/keccak256.cairo @@ -0,0 +1,8 @@ +use core::integer::u128_byte_reverse; + +/// Reverse the endianness of an u256 +pub fn reverse_endianness(value: u256) -> u256 { + let new_low = u128_byte_reverse(value.high); + let new_high = u128_byte_reverse(value.low); + u256 { low: new_low, high: new_high } +} From 427d7463409d486f80facc5630f070c8dcffeea1 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Thu, 25 Apr 2024 21:04:43 +0200 Subject: [PATCH 03/26] fix: msg.value and view function --- src/contracts/client/mailboxclient.cairo | 6 ------ src/contracts/mailbox.cairo | 2 +- src/interfaces.cairo | 13 +++++++++++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/contracts/client/mailboxclient.cairo b/src/contracts/client/mailboxclient.cairo index c65d89b..2549f4b 100644 --- a/src/contracts/client/mailboxclient.cairo +++ b/src/contracts/client/mailboxclient.cairo @@ -82,15 +82,9 @@ mod mailboxclient { _destination_domain: u32, _recipient: ContractAddress, _message_body: Bytes, - _value: Option, _hook_metadata: Option, _hook: Option ) { - // TODO: refer to Interchain Gas Payment for msg.value usage - let value = match _value { - Option::Some(val) => val, - Option::None(()) => starknet::get_tx_info().unbox().max_fee.into() - }; let hook_metadata = match _hook_metadata { Option::Some(metadata) => metadata, Option::None(()) => BytesTrait::new_empty() diff --git a/src/contracts/mailbox.cairo b/src/contracts/mailbox.cairo index f0520b6..d1def19 100644 --- a/src/contracts/mailbox.cairo +++ b/src/contracts/mailbox.cairo @@ -293,7 +293,7 @@ mod mailbox { - hook.quote_dispatch(hook_metadata, message) } - fn recipient_ism(ref self: ContractState, _recipient: ContractAddress) -> ContractAddress { + fn recipient_ism(self: @ContractState, _recipient: ContractAddress) -> ContractAddress { let mut call_data: Array = ArrayTrait::new(); let mut res = starknet::syscalls::call_contract_syscall( _recipient, selector!("interchain_security_module"), call_data.span() diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 53d08e6..2d280fb 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -73,7 +73,7 @@ pub trait IMailbox { fn process(ref self: TContractState, _metadata: Bytes, _message: Message,); - fn recipient_ism(ref self: TContractState, _recipient: ContractAddress) -> ContractAddress; + fn recipient_ism(self: @TContractState, _recipient: ContractAddress) -> ContractAddress; fn set_default_ism(ref self: TContractState, _module: ContractAddress); @@ -86,6 +86,8 @@ pub trait IMailbox { fn processor(self: @TContractState, _id: u256) -> ContractAddress; fn processed_at(self: @TContractState, _id: u256) -> u64; + + } @@ -151,7 +153,6 @@ pub trait IMailboxClient { _destination_domain: u32, _recipient: ContractAddress, _message_body: Bytes, - _value: Option, _hook_metadata: Option, ) -> Bytes; @@ -164,3 +165,11 @@ pub trait IMailboxClient { _hook: Option ); } + + +#[starknet::interface] +pub trait IInterchainGasPaymaster { + fn pay_for_gas(ref self: TContractState, _message_id: u256, _destination_domain: u32, _gas_amount: u256, _payment: u256); + + fn quote_gas_payment(ref self: TContractState, _destination_domain: u32, _gas_amount: u256) -> u256; +} \ No newline at end of file From 9e59675dded8b5901837a8ccdc6455c7b9eb0939 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Fri, 26 Apr 2024 13:57:52 +0200 Subject: [PATCH 04/26] feat: router --- Scarb.lock | 6 + Scarb.toml | 2 + src/contracts/client/gasrouter.cairo | 0 src/contracts/client/mailboxclient.cairo | 58 +++++----- src/contracts/client/router.cairo | 137 +++++++++++++++++++++++ src/interfaces.cairo | 23 +++- src/lib.cairo | 4 + 7 files changed, 198 insertions(+), 32 deletions(-) create mode 100644 src/contracts/client/gasrouter.cairo create mode 100644 src/contracts/client/router.cairo diff --git a/Scarb.lock b/Scarb.lock index 5079c91..ed7dbf0 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -53,12 +53,18 @@ dependencies = [ "alexandria_data_structures", ] +[[package]] +name = "alexandria_storage" +version = "0.3.0" +source = "git+https://github.com/keep-starknet-strange/alexandria.git#22349afa62ad9be63f83b520be04c68515902561" + [[package]] name = "hyperlane_starknet" version = "0.1.0" dependencies = [ "alexandria_bytes", "alexandria_math", + "alexandria_storage", "openzeppelin", "snforge_std", ] diff --git a/Scarb.toml b/Scarb.toml index 913a8f5..2e1412e 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -12,6 +12,8 @@ starknet = "2.6.3" alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git" } alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git" } openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", rev = "f22438c" } +alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git" } + [dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.22.0" } diff --git a/src/contracts/client/gasrouter.cairo b/src/contracts/client/gasrouter.cairo new file mode 100644 index 0000000..e69de29 diff --git a/src/contracts/client/mailboxclient.cairo b/src/contracts/client/mailboxclient.cairo index 2549f4b..3f916b1 100644 --- a/src/contracts/client/mailboxclient.cairo +++ b/src/contracts/client/mailboxclient.cairo @@ -2,11 +2,12 @@ mod mailboxclient { use hyperlane_starknet::interfaces::{ IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, IInterchainSecurityModuleDispatcher, - IInterchainSecurityModuleDispatcherTrait, + IInterchainSecurityModuleDispatcherTrait, IMailboxClient, }; + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; - use starknet::{ContractAddress, contract_address_const}; + use starknet::{ContractAddress, contract_address_const, ClassHash}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); @@ -14,6 +15,8 @@ mod mailboxclient { impl OwnableImpl = OwnableComponent::OwnableImpl; impl OwnableInternalImpl = OwnableComponent::InternalImpl; impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] struct Storage { mailbox: ContractAddress, @@ -26,13 +29,24 @@ mod mailboxclient { upgradeable: UpgradeableComponent::Storage, } + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + #[constructor] fn constructor(ref self: ContractState, _mailbox: ContractAddress, _owner: ContractAddress) { - self.mailbox.write(mailbox); + self.mailbox.write(_mailbox); let mailbox = IMailboxDispatcher { contract_address: _mailbox }; let local_domain = mailbox.get_local_domain(); - self.ownable.initializer(owner); + self.local_domain.write(local_domain); + self.ownable.initializer(_owner); } #[abi(embed_v0)] impl Upgradeable of IUpgradeable { @@ -61,19 +75,19 @@ mod mailboxclient { _interchain_security_module: ContractAddress, ) { self.ownable.assert_only_owner(); - set_hook(_hook); - set_interchain_security_module(_interchain_security_module); + self.set_hook(_hook); + self.set_interchain_security_module(_interchain_security_module); } fn _is_latest_dispatched(self: @ContractState, _id: u256) -> bool { let mailbox_address = self.mailbox.read(); - let mailbox = IMailbox { contract_address: mailbox_address }; - mailbox.latest_dispatched_id() == _id + let mailbox = IMailboxDispatcher { contract_address: mailbox_address }; + mailbox.get_latest_dispatched_id() == _id } fn _is_delivered(self: @ContractState, _id: u256) -> bool { let mailbox_address = self.mailbox.read(); - let mailbox = IMailbox { contract_address: mailbox_address }; + let mailbox = IMailboxDispatcher { contract_address: mailbox_address }; mailbox.delivered(_id) } @@ -84,18 +98,10 @@ mod mailboxclient { _message_body: Bytes, _hook_metadata: Option, _hook: Option - ) { - let hook_metadata = match _hook_metadata { - Option::Some(metadata) => metadata, - Option::None(()) => BytesTrait::new_empty() - }; - let hook = match _hook { - Option::Some(address) => address, - Option::None(()) => contract_address_const::<0>() - }; + ) -> u256{ let mailbox_address = self.mailbox.read(); - let mailbox = IMailbox { contract_address: mailbox_address }; - mailbox.dispatch(_destination_domain, _recipient, _message_body, _hook_metadata, hook); + let mailbox = IMailboxDispatcher { contract_address: mailbox_address }; + mailbox.dispatch(_destination_domain, _recipient, _message_body, _hook_metadata, _hook) } fn quote_dispatch( @@ -106,19 +112,11 @@ mod mailboxclient { _hook_metadata: Option, _hook: Option ) { - let hook_metadata = match _hook_metadata { - Option::Some(metadata) => metadata, - Option::None(()) => BytesTrait::new_empty() - }; - let hook = match _hook { - Option::Some(address) => address, - Option::None(()) => contract_address_const::<0>() - }; let mailbox_address = self.mailbox.read(); - let mailbox = IMailbox { contract_address: mailbox_address }; + let mailbox = IMailboxDispatcher { contract_address: mailbox_address }; mailbox .quote_dispatch( - _destination_domain, _recipient, _message_body, _hook_metadata, hook + _destination_domain, _recipient, _message_body, _hook_metadata, _hook ); } } diff --git a/src/contracts/client/router.cairo b/src/contracts/client/router.cairo new file mode 100644 index 0000000..056fb6f --- /dev/null +++ b/src/contracts/client/router.cairo @@ -0,0 +1,137 @@ +#[starknet::contract] +mod router { + + use starknet::{ContractAddress, get_caller_address, ClassHash, contract_address_const}; + use alexandria_storage::list::{List, ListTrait}; + use hyperlane_starknet::contracts::libs::message::Message; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use hyperlane_starknet::interfaces::IRouter; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + type Domain = u32; + #[storage] + struct Storage { + routers: LegacyMap, + mailbox: ContractAddress, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + + mod Errors { + pub const LENGTH_MISMATCH: felt252 = 'Domains and Router len mismatch'; + pub const CALLER_IS_NOT_MAILBOX: felt252 = 'Caller is not mailbox'; + pub const NO_ROUTER_FOR_DOMAIN: felt252 = 'No router for domain'; + pub const ENROLLED_ROUTER_AND_SENDER_MISMATCH: felt252 = 'Enrolled router/sender mismatch'; + + } + + #[constructor] + fn constructor(ref self: ContractState, _mailbox: ContractAddress) { + self.mailbox.write(_mailbox); + } + + #[abi(embed_v0)] + impl Upgradeable of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable._upgrade(new_class_hash); + } + } + + #[abi(embed_v0)] + impl IRouterImpl of IRouter { + + + fn routers(self: @ContractState, _domain: u32) -> ContractAddress { + self.routers.read(_domain) + } + + fn unenroll_remote_router(ref self: ContractState, _domain: u32){ + self.ownable.assert_only_owner(); + _unenroll_remote_router(ref self, _domain); + + } + + fn enroll_remote_router(ref self: ContractState, _domain: u32, _router: ContractAddress) { + self.ownable.assert_only_owner(); + _enroll_remote_router(ref self , _domain, _router); + } + + fn enroll_remote_routers(ref self: ContractState, _domains: Span, _routers: Span){ + self.ownable.assert_only_owner(); + assert(_domains.len()==_routers.len(), Errors::LENGTH_MISMATCH); + let length = _domains.len(); + let mut cur_idx = 0; + loop { + if (cur_idx == length){ + break(); + } + _enroll_remote_router(ref self, *_domains.at(cur_idx), *_routers.at(cur_idx)); + cur_idx +=1 ; + } + } + + fn unenroll_remote_routers(ref self: ContractState, _domains: Span){ + self.ownable.assert_only_owner(); + let length = _domains.len(); + let mut cur_idx = 0; + loop { + if (cur_idx == length){ + break(); + } + _unenroll_remote_router(ref self, *_domains.at(cur_idx)); + cur_idx +=1 ; + } + } + + fn handle(self: @ContractState, _origin: u32, _sender: ContractAddress, _message: Message) { + let caller = get_caller_address(); + assert(caller ==self.mailbox.read(),Errors::CALLER_IS_NOT_MAILBOX); + let router = _must_have_remote_router(self,_origin); + assert(router == _sender , Errors::ENROLLED_ROUTER_AND_SENDER_MISMATCH); + _handle(_origin, _sender, _message); + } + + } + + fn _unenroll_remote_router(ref self: ContractState, _domain: u32) { + self.routers.write(_domain, contract_address_const::<0>()); + } + + fn _enroll_remote_router(ref self: ContractState, _domain :u32, _address: ContractAddress){ + self.routers.write(_domain, _address); + } + + fn _must_have_remote_router(self: @ContractState, _domain: u32) -> ContractAddress { + let router = self.routers.read(_domain); + assert(router!=0.try_into().unwrap(), Errors::NO_ROUTER_FOR_DOMAIN); + router + } + + fn _is_remote_Router(self: @ContractState, _domain: u32, _address: ContractAddress) -> bool { + let router = self.routers.read(_domain); + router == _address + } + + fn _handle(_origin: u32, _sender: ContractAddress, _message: Message) { + + } +} \ No newline at end of file diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 2d280fb..ed7bf86 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -141,7 +141,6 @@ pub trait IMailboxClient { ref self: TContractState, _hook: ContractAddress, _interchain_security_module: ContractAddress, - _owner: ContractAddress ); fn _is_latest_dispatched(self: @TContractState, _id: u256) -> bool; @@ -154,7 +153,8 @@ pub trait IMailboxClient { _recipient: ContractAddress, _message_body: Bytes, _hook_metadata: Option, - ) -> Bytes; + _hook: Option + ) -> u256; fn quote_dispatch( self: @TContractState, @@ -172,4 +172,23 @@ pub trait IInterchainGasPaymaster { fn pay_for_gas(ref self: TContractState, _message_id: u256, _destination_domain: u32, _gas_amount: u256, _payment: u256); fn quote_gas_payment(ref self: TContractState, _destination_domain: u32, _gas_amount: u256) -> u256; +} + + +use alexandria_storage::list::{List, ListTrait}; + +#[starknet::interface] +pub trait IRouter { + + fn routers(self: @TContractState, _domain: u32) -> ContractAddress; + + fn unenroll_remote_router(ref self: TContractState, _domain: u32); + + fn enroll_remote_router(ref self: TContractState, _domain: u32, _router: ContractAddress); + + fn enroll_remote_routers(ref self: TContractState, _domains: Span, _routers: Span); + + fn unenroll_remote_routers(ref self: TContractState, _domains: Span); + + fn handle(self: @TContractState, _origin: u32, _sender: ContractAddress, _message: Message); } \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo index e7bebf5..99f714f 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -4,6 +4,10 @@ mod contracts { pub mod libs { pub mod message; } + pub mod client { + pub mod mailboxclient; + pub mod router; + } } mod utils { pub mod keccak256; From 8e416c8e2e99c40efa6345b54aa228e2d5c91773 Mon Sep 17 00:00:00 2001 From: 0xevolve Date: Fri, 26 Apr 2024 18:48:28 +0100 Subject: [PATCH 05/26] feat: add nonce getter --- src/contracts/mailbox.cairo | 4 ++++ src/interfaces.cairo | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/contracts/mailbox.cairo b/src/contracts/mailbox.cairo index d1def19..d5a7fbf 100644 --- a/src/contracts/mailbox.cairo +++ b/src/contracts/mailbox.cairo @@ -238,6 +238,10 @@ mod mailbox { self.deliveries.read(_message_id).block_number > 0 } + fn nonce(self: @ContractState) -> u32 { + self.nonce.read() + } + fn process(ref self: ContractState, _metadata: Bytes, _message: Message) { assert(_message.version == HYPERLANE_VERSION, Errors::WRONG_HYPERLANE_VERSION); assert( diff --git a/src/interfaces.cairo b/src/interfaces.cairo index ed7bf86..6b979b7 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -45,6 +45,8 @@ pub trait IMailbox { fn delivered(self: @TContractState, _message_id: u256) -> bool; + fn nonce(self: @TContractState) -> u32; + fn get_default_ism(self: @TContractState) -> ContractAddress; fn get_default_hook(self: @TContractState) -> ContractAddress; From fc3d1bb2f905913b47aef9bc4807ce11f8a2e3b4 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Sat, 27 Apr 2024 17:25:12 +0200 Subject: [PATCH 06/26] fix + tests --- Scarb.lock | 7 - Scarb.toml | 2 - src/contracts/client/gasrouter.cairo | 0 src/contracts/client/mailboxclient.cairo | 5 +- src/contracts/client/router.cairo | 62 +++--- src/contracts/mailbox.cairo | 101 +++++---- src/contracts/mocks/message_recipient.cairo | 39 ++++ src/interfaces.cairo | 34 ++- src/lib.cairo | 9 + src/tests/setup.cairo | 66 ++++++ src/tests/test_mailbox.cairo | 225 ++++++++++++++++++++ 11 files changed, 455 insertions(+), 95 deletions(-) delete mode 100644 src/contracts/client/gasrouter.cairo create mode 100644 src/contracts/mocks/message_recipient.cairo create mode 100644 src/tests/setup.cairo create mode 100644 src/tests/test_mailbox.cairo diff --git a/Scarb.lock b/Scarb.lock index ed7dbf0..7389f62 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -53,18 +53,11 @@ dependencies = [ "alexandria_data_structures", ] -[[package]] -name = "alexandria_storage" -version = "0.3.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git#22349afa62ad9be63f83b520be04c68515902561" - [[package]] name = "hyperlane_starknet" version = "0.1.0" dependencies = [ "alexandria_bytes", - "alexandria_math", - "alexandria_storage", "openzeppelin", "snforge_std", ] diff --git a/Scarb.toml b/Scarb.toml index 2e1412e..b3b678a 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -10,9 +10,7 @@ cairo-version = "2.6.3" [dependencies] starknet = "2.6.3" alexandria_bytes = { git = "https://github.com/keep-starknet-strange/alexandria.git" } -alexandria_math = { git = "https://github.com/keep-starknet-strange/alexandria.git" } openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", rev = "f22438c" } -alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git" } [dev-dependencies] diff --git a/src/contracts/client/gasrouter.cairo b/src/contracts/client/gasrouter.cairo deleted file mode 100644 index e69de29..0000000 diff --git a/src/contracts/client/mailboxclient.cairo b/src/contracts/client/mailboxclient.cairo index 3f916b1..a01ec49 100644 --- a/src/contracts/client/mailboxclient.cairo +++ b/src/contracts/client/mailboxclient.cairo @@ -1,10 +1,10 @@ #[starknet::contract] mod mailboxclient { + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; use hyperlane_starknet::interfaces::{ IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, IMailboxClient, }; - use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; use starknet::{ContractAddress, contract_address_const, ClassHash}; @@ -39,7 +39,6 @@ mod mailboxclient { } - #[constructor] fn constructor(ref self: ContractState, _mailbox: ContractAddress, _owner: ContractAddress) { self.mailbox.write(_mailbox); @@ -98,7 +97,7 @@ mod mailboxclient { _message_body: Bytes, _hook_metadata: Option, _hook: Option - ) -> u256{ + ) -> u256 { let mailbox_address = self.mailbox.read(); let mailbox = IMailboxDispatcher { contract_address: mailbox_address }; mailbox.dispatch(_destination_domain, _recipient, _message_body, _hook_metadata, _hook) diff --git a/src/contracts/client/router.cairo b/src/contracts/client/router.cairo index 056fb6f..0bd031e 100644 --- a/src/contracts/client/router.cairo +++ b/src/contracts/client/router.cairo @@ -1,12 +1,11 @@ #[starknet::contract] mod router { - - use starknet::{ContractAddress, get_caller_address, ClassHash, contract_address_const}; - use alexandria_storage::list::{List, ListTrait}; use hyperlane_starknet::contracts::libs::message::Message; + use hyperlane_starknet::interfaces::IRouter; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; - use hyperlane_starknet::interfaces::IRouter; + + use starknet::{ContractAddress, get_caller_address, ClassHash, contract_address_const}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); #[abi(embed_v0)] @@ -17,7 +16,7 @@ mod router { type Domain = u32; #[storage] struct Storage { - routers: LegacyMap, + routers: LegacyMap, mailbox: ContractAddress, #[substorage(v0)] ownable: OwnableComponent::Storage, @@ -40,14 +39,13 @@ mod router { pub const CALLER_IS_NOT_MAILBOX: felt252 = 'Caller is not mailbox'; pub const NO_ROUTER_FOR_DOMAIN: felt252 = 'No router for domain'; pub const ENROLLED_ROUTER_AND_SENDER_MISMATCH: felt252 = 'Enrolled router/sender mismatch'; - } #[constructor] fn constructor(ref self: ContractState, _mailbox: ContractAddress) { self.mailbox.write(_mailbox); } - + #[abi(embed_v0)] impl Upgradeable of IUpgradeable { fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { @@ -58,80 +56,76 @@ mod router { #[abi(embed_v0)] impl IRouterImpl of IRouter { - - fn routers(self: @ContractState, _domain: u32) -> ContractAddress { self.routers.read(_domain) } - fn unenroll_remote_router(ref self: ContractState, _domain: u32){ + fn unenroll_remote_router(ref self: ContractState, _domain: u32) { self.ownable.assert_only_owner(); _unenroll_remote_router(ref self, _domain); - } fn enroll_remote_router(ref self: ContractState, _domain: u32, _router: ContractAddress) { self.ownable.assert_only_owner(); - _enroll_remote_router(ref self , _domain, _router); + _enroll_remote_router(ref self, _domain, _router); } - fn enroll_remote_routers(ref self: ContractState, _domains: Span, _routers: Span){ + fn enroll_remote_routers( + ref self: ContractState, _domains: Span, _routers: Span + ) { self.ownable.assert_only_owner(); - assert(_domains.len()==_routers.len(), Errors::LENGTH_MISMATCH); + assert(_domains.len() == _routers.len(), Errors::LENGTH_MISMATCH); let length = _domains.len(); let mut cur_idx = 0; loop { - if (cur_idx == length){ - break(); + if (cur_idx == length) { + break (); } _enroll_remote_router(ref self, *_domains.at(cur_idx), *_routers.at(cur_idx)); - cur_idx +=1 ; + cur_idx += 1; } } - fn unenroll_remote_routers(ref self: ContractState, _domains: Span){ + fn unenroll_remote_routers(ref self: ContractState, _domains: Span) { self.ownable.assert_only_owner(); let length = _domains.len(); let mut cur_idx = 0; loop { - if (cur_idx == length){ - break(); + if (cur_idx == length) { + break (); } _unenroll_remote_router(ref self, *_domains.at(cur_idx)); - cur_idx +=1 ; + cur_idx += 1; } - } + } fn handle(self: @ContractState, _origin: u32, _sender: ContractAddress, _message: Message) { - let caller = get_caller_address(); - assert(caller ==self.mailbox.read(),Errors::CALLER_IS_NOT_MAILBOX); - let router = _must_have_remote_router(self,_origin); - assert(router == _sender , Errors::ENROLLED_ROUTER_AND_SENDER_MISMATCH); + let caller = get_caller_address(); + assert(caller == self.mailbox.read(), Errors::CALLER_IS_NOT_MAILBOX); + let router = _must_have_remote_router(self, _origin); + assert(router == _sender, Errors::ENROLLED_ROUTER_AND_SENDER_MISMATCH); _handle(_origin, _sender, _message); } - } fn _unenroll_remote_router(ref self: ContractState, _domain: u32) { self.routers.write(_domain, contract_address_const::<0>()); } - fn _enroll_remote_router(ref self: ContractState, _domain :u32, _address: ContractAddress){ + fn _enroll_remote_router(ref self: ContractState, _domain: u32, _address: ContractAddress) { self.routers.write(_domain, _address); } fn _must_have_remote_router(self: @ContractState, _domain: u32) -> ContractAddress { let router = self.routers.read(_domain); - assert(router!=0.try_into().unwrap(), Errors::NO_ROUTER_FOR_DOMAIN); + assert(router != 0.try_into().unwrap(), Errors::NO_ROUTER_FOR_DOMAIN); router } - + fn _is_remote_Router(self: @ContractState, _domain: u32, _address: ContractAddress) -> bool { let router = self.routers.read(_domain); router == _address } - fn _handle(_origin: u32, _sender: ContractAddress, _message: Message) { - - } -} \ No newline at end of file + fn _handle(_origin: u32, _sender: ContractAddress, _message: Message) {} +} diff --git a/src/contracts/mailbox.cairo b/src/contracts/mailbox.cairo index d1def19..c28913d 100644 --- a/src/contracts/mailbox.cairo +++ b/src/contracts/mailbox.cairo @@ -1,5 +1,5 @@ #[starknet::contract] -mod mailbox { +pub mod mailbox { use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; use core::starknet::SyscallResultTrait; use core::starknet::event::EventEmitter; @@ -49,7 +49,7 @@ mod mailbox { #[event] #[derive(Drop, starknet::Event)] - enum Event { + pub enum Event { DefaultIsmSet: DefaultIsmSet, DefaultHookSet: DefaultHookSet, RequiredHookSet: RequiredHookSet, @@ -64,43 +64,43 @@ mod mailbox { } #[derive(starknet::Event, Drop)] - struct DefaultIsmSet { - module: ContractAddress + pub struct DefaultIsmSet { + pub module: ContractAddress } #[derive(starknet::Event, Drop)] - struct DefaultHookSet { - hook: ContractAddress + pub struct DefaultHookSet { + pub hook: ContractAddress } #[derive(starknet::Event, Drop)] - struct RequiredHookSet { - hook: ContractAddress + pub struct RequiredHookSet { + pub hook: ContractAddress } #[derive(starknet::Event, Drop)] - struct Process { - origin: u32, - sender: ContractAddress, - recipient: ContractAddress + pub struct Process { + pub origin: u32, + pub sender: ContractAddress, + pub recipient: ContractAddress } #[derive(starknet::Event, Drop)] - struct ProcessId { - id: u256 + pub struct ProcessId { + pub id: u256 } #[derive(starknet::Event, Drop)] - struct Dispatch { - sender: ContractAddress, - destination_domain: u32, - recipient_address: ContractAddress, - message: u256 + pub struct Dispatch { + pub sender: ContractAddress, + pub destination_domain: u32, + pub recipient_address: ContractAddress, + pub message: u256 } #[derive(starknet::Event, Drop)] - struct DispatchId { - id: u256 + pub struct DispatchId { + pub id: u256 } @@ -110,6 +110,8 @@ mod mailbox { pub const ALREADY_DELIVERED: felt252 = 'Mailbox: already delivered'; pub const ISM_VERIFICATION_FAILED: felt252 = 'Mailbox:ism verification failed'; pub const NO_ISM_FOUND: felt252 = 'ISM: no ISM found'; + pub const NEW_OWNER_IS_ZERO: felt252 = 'Ownable: new owner cannot be 0'; + pub const ALREADY_OWNER: felt252 = 'Ownable: already owner'; } #[constructor] @@ -140,6 +142,7 @@ mod mailbox { self.set_required_hook(_required_hook); } + fn get_local_domain(self: @ContractState) -> u32 { self.local_domain.read() } @@ -219,18 +222,21 @@ mod mailbox { } ); self.emit(DispatchId { id: id }); - let required_hook_address = self.required_hook.read(); - let required_hook = IPostDispatchHookDispatcher { - contract_address: required_hook_address - }; - let hook = IPostDispatchHookDispatcher { contract_address: hook }; - let mut required_value = required_hook.quote_dispatch(hook_metadata.clone(), message); - let max_fee = starknet::get_tx_info().unbox().max_fee.into(); - if (max_fee < required_value) { - required_value = max_fee; - } - required_hook.post_dispatch(hook_metadata.clone(), message); - hook.post_dispatch(hook_metadata, message); + + // + // HOOKS + // + + // let required_hook_address = self.required_hook.read(); + // let required_hook = IPostDispatchHookDispatcher { + // contract_address: required_hook_address + // }; + // if (hook != contract_address_const::<0>() ){ + // let hook = IPostDispatchHookDispatcher { contract_address: hook }; + // hook.post_dispatch(hook_metadata.clone(), message); + // } + // required_hook.post_dispatch(hook_metadata, message); + id } @@ -247,8 +253,18 @@ mod mailbox { let caller = get_caller_address(); let block_number = get_block_number(); assert(!self.delivered(id), Errors::ALREADY_DELIVERED); - let recipient_ism = self.recipient_ism(_message.recipient); - let ism = IInterchainSecurityModuleDispatcher { contract_address: recipient_ism }; + + // + // ISM + // + + // let recipient_ism = self.recipient_ism(_message.recipient); + // let ism = IInterchainSecurityModuleDispatcher { contract_address: recipient_ism }; + + // + // + // + self.deliveries.write(id, Delivery { processor: caller, block_number: block_number }); self .emit( @@ -259,14 +275,23 @@ mod mailbox { } ); self.emit(ProcessId { id: id }); - assert(ism.verify(_metadata, _message.clone()), Errors::ISM_VERIFICATION_FAILED); + + // + // ISM + // + + // assert(ism.verify(_metadata, _message.clone()), Errors::ISM_VERIFICATION_FAILED); + + // + // + // let message_recipient = IMessageRecipientDispatcher { contract_address: _message.recipient }; message_recipient.handle(_message.origin, _message.sender, _message.body); } fn quote_dispatch( - ref self: ContractState, + self: @ContractState, _destination_domain: u32, _recipient_address: ContractAddress, _message_body: Bytes, @@ -282,7 +307,7 @@ mod mailbox { Option::None(()) => BytesTrait::new_empty(), }; let message = build_message( - @self, _destination_domain, _recipient_address, _message_body.clone() + self, _destination_domain, _recipient_address, _message_body.clone() ); let required_hook_address = self.required_hook.read(); let required_hook = IPostDispatchHookDispatcher { diff --git a/src/contracts/mocks/message_recipient.cairo b/src/contracts/mocks/message_recipient.cairo new file mode 100644 index 0000000..e2e7e93 --- /dev/null +++ b/src/contracts/mocks/message_recipient.cairo @@ -0,0 +1,39 @@ +#[starknet::contract] +pub mod message_recipient { + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; + use hyperlane_starknet::interfaces::{ + IMessageRecipient, IMessageRecipientDispatcher, IMessageRecipientDispatcherTrait + }; + use starknet::ContractAddress; + + + #[storage] + struct Storage { + origin: u32, + sender: ContractAddress, + message: Bytes + } + + #[abi(embed_v0)] + impl IMessageRecipientImpl of IMessageRecipient { + fn handle( + ref self: ContractState, _origin: u32, _sender: ContractAddress, _message: Bytes + ) { + self.message.write(_message); + self.origin.write(_origin); + self.sender.write(_sender); + } + + fn get_origin(self: @ContractState) -> u32 { + self.origin.read() + } + + fn get_sender(self: @ContractState) -> ContractAddress { + self.sender.read() + } + + fn get_message(self: @ContractState) -> Bytes { + self.message.read() + } + } +} diff --git a/src/interfaces.cairo b/src/interfaces.cairo index ed7bf86..07a97ca 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -63,7 +63,7 @@ pub trait IMailbox { ) -> u256; fn quote_dispatch( - ref self: TContractState, + self: @TContractState, _destination_domain: u32, _recipient_address: ContractAddress, _message_body: Bytes, @@ -86,8 +86,6 @@ pub trait IMailbox { fn processor(self: @TContractState, _id: u256) -> ContractAddress; fn processed_at(self: @TContractState, _id: u256) -> u64; - - } @@ -127,7 +125,13 @@ pub trait IPostDispatchHook { #[starknet::interface] pub trait IMessageRecipient { - fn handle(self: @TContractState, origin: u32, _sender: ContractAddress, _message: Bytes); + fn handle(ref self: TContractState, _origin: u32, _sender: ContractAddress, _message: Bytes); + + fn get_origin(self: @TContractState) -> u32; + + fn get_sender(self: @TContractState) -> ContractAddress; + + fn get_message(self: @TContractState) -> Bytes; } @@ -169,26 +173,34 @@ pub trait IMailboxClient { #[starknet::interface] pub trait IInterchainGasPaymaster { - fn pay_for_gas(ref self: TContractState, _message_id: u256, _destination_domain: u32, _gas_amount: u256, _payment: u256); + fn pay_for_gas( + ref self: TContractState, + _message_id: u256, + _destination_domain: u32, + _gas_amount: u256, + _payment: u256 + ); - fn quote_gas_payment(ref self: TContractState, _destination_domain: u32, _gas_amount: u256) -> u256; + fn quote_gas_payment( + ref self: TContractState, _destination_domain: u32, _gas_amount: u256 + ) -> u256; } -use alexandria_storage::list::{List, ListTrait}; - #[starknet::interface] pub trait IRouter { - fn routers(self: @TContractState, _domain: u32) -> ContractAddress; fn unenroll_remote_router(ref self: TContractState, _domain: u32); fn enroll_remote_router(ref self: TContractState, _domain: u32, _router: ContractAddress); - fn enroll_remote_routers(ref self: TContractState, _domains: Span, _routers: Span); + fn enroll_remote_routers( + ref self: TContractState, _domains: Span, _routers: Span + ); fn unenroll_remote_routers(ref self: TContractState, _domains: Span); fn handle(self: @TContractState, _origin: u32, _sender: ContractAddress, _message: Message); -} \ No newline at end of file +} + diff --git a/src/lib.cairo b/src/lib.cairo index 99f714f..71d0b62 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -8,7 +8,16 @@ mod contracts { pub mod mailboxclient; pub mod router; } + pub mod mocks { + pub mod message_recipient; + } } mod utils { pub mod keccak256; } + +#[cfg(test)] +mod tests { + pub mod setup; + pub mod test_mailbox; +} diff --git a/src/tests/setup.cairo b/src/tests/setup.cairo new file mode 100644 index 0000000..0ef29fd --- /dev/null +++ b/src/tests/setup.cairo @@ -0,0 +1,66 @@ +use core::result::ResultTrait; +use hyperlane_starknet::contracts::mocks::message_recipient::message_recipient; +use hyperlane_starknet::interfaces::{ + IMailboxDispatcher, IMailboxDispatcherTrait, IMessageRecipientDispatcher, + IMessageRecipientDispatcherTrait +}; +use snforge_std::{ + declare, ContractClassTrait, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn +}; + +use starknet::{ContractAddress, contract_address_const}; + +pub const LOCAL_DOMAIN: u32 = 534352; +pub const DESTINATION_DOMAIN: u32 = 9841001; + +pub fn OWNER() -> ContractAddress { + contract_address_const::<'OWNER'>() +} + +pub fn NEW_OWNER() -> ContractAddress { + contract_address_const::<'NEW_OWNER'>() +} + +pub fn DEFAULT_ISM() -> ContractAddress { + contract_address_const::<'DEFAULT_ISM'>() +} + +pub fn DEFAULT_HOOK() -> ContractAddress { + contract_address_const::<'DEFAULT_HOOK'>() +} + +pub fn REQUIRED_HOOK() -> ContractAddress { + contract_address_const::<'REQUIRED_HOOK'>() +} + +pub fn NEW_DEFAULT_ISM() -> ContractAddress { + contract_address_const::<'NEW_DEFAULT_ISM'>() +} + +pub fn NEW_DEFAULT_HOOK() -> ContractAddress { + contract_address_const::<'NEW_DEFAULT_HOOK'>() +} + +pub fn NEW_REQUIRED_HOOK() -> ContractAddress { + contract_address_const::<'NEW_REQUIRED_HOOK'>() +} + +pub fn RECIPIENT_ADDRESS() -> ContractAddress { + contract_address_const::<'RECIPIENT_ADDRESS'>() +} + +pub fn setup() -> (IMailboxDispatcher, EventSpy) { + let mailbox_class = declare("mailbox").unwrap(); + let (mailbox_addr, _) = mailbox_class + .deploy(@array![LOCAL_DOMAIN.into(), OWNER().into()]) + .unwrap(); + let mut spy = spy_events(SpyOn::One(mailbox_addr)); + (IMailboxDispatcher { contract_address: mailbox_addr }, spy) +} + +pub fn mock_setup() -> IMessageRecipientDispatcher { + let message_recipient_class = declare("message_recipient").unwrap(); + + let (message_recipient_addr, _) = message_recipient_class.deploy(@array![]).unwrap(); + IMessageRecipientDispatcher { contract_address: message_recipient_addr } +} diff --git a/src/tests/test_mailbox.cairo b/src/tests/test_mailbox.cairo new file mode 100644 index 0000000..88f3750 --- /dev/null +++ b/src/tests/test_mailbox.cairo @@ -0,0 +1,225 @@ +use alexandria_bytes::{Bytes, BytesTrait}; +use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; +use hyperlane_starknet::contracts::mailbox::mailbox; +use hyperlane_starknet::interfaces::IMessageRecipientDispatcherTrait; +use hyperlane_starknet::interfaces::{ + IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, HYPERLANE_VERSION +}; +use hyperlane_starknet::tests::setup::{ + setup, mock_setup, OWNER, LOCAL_DOMAIN, NEW_OWNER, DEFAULT_ISM, DEFAULT_HOOK, REQUIRED_HOOK, + NEW_DEFAULT_ISM, NEW_DEFAULT_HOOK, NEW_REQUIRED_HOOK, DESTINATION_DOMAIN, RECIPIENT_ADDRESS +}; +use openzeppelin::access::ownable::OwnableComponent; +use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; +use snforge_std::cheatcodes::events::EventAssertions; +use snforge_std::{start_prank, CheatTarget, stop_prank}; + +#[test] +fn test_local_domain() { + let (mailbox, _) = setup(); + assert(mailbox.get_local_domain() == LOCAL_DOMAIN, 'Wrong local domain'); +} + +#[test] +fn test_owner() { + let (mailbox, _) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + assert(ownable.owner() == OWNER(), 'Wrong contract owner'); +} + +#[test] +fn test_transfer_ownership() { + let (mailbox, mut spy) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + ownable.transfer_ownership(NEW_OWNER()); + stop_prank(CheatTarget::One(ownable.contract_address)); + assert(ownable.owner() == NEW_OWNER(), 'Failed transfer ownership'); + + let expected_event = OwnableComponent::OwnershipTransferred { + previous_owner: OWNER(), new_owner: NEW_OWNER() + }; + spy + .assert_emitted( + @array![ + ( + ownable.contract_address, + OwnableComponent::Event::OwnershipTransferred(expected_event) + ) + ] + ); +} + +#[test] +fn test_initializer() { + let (mailbox, _) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + mailbox.initializer(DEFAULT_ISM(), DEFAULT_HOOK(), REQUIRED_HOOK()); + assert(mailbox.get_default_hook() == DEFAULT_HOOK(), 'Failed to set default hook'); + assert(mailbox.get_required_hook() == REQUIRED_HOOK(), 'Failed to set required hook'); + assert(mailbox.get_default_ism() == DEFAULT_ISM(), 'Failed to set default ism'); +} + +#[test] +fn test_set_default_hook() { + let (mailbox, mut spy) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + mailbox.set_default_hook(NEW_DEFAULT_HOOK()); + assert(mailbox.get_default_hook() == NEW_DEFAULT_HOOK(), 'Failed to set default hook'); + let expected_event = mailbox::Event::DefaultHookSet( + mailbox::DefaultHookSet { hook: NEW_DEFAULT_HOOK() } + ); + spy.assert_emitted(@array![(mailbox.contract_address, expected_event)]); +} + +#[test] +fn test_set_required_hook() { + let (mailbox, mut spy) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + mailbox.set_required_hook(NEW_REQUIRED_HOOK()); + assert(mailbox.get_required_hook() == NEW_REQUIRED_HOOK(), 'Failed to set required hook'); + let expected_event = mailbox::Event::RequiredHookSet( + mailbox::RequiredHookSet { hook: NEW_REQUIRED_HOOK() } + ); + spy.assert_emitted(@array![(mailbox.contract_address, expected_event)]); +} + +#[test] +fn test_set_default_ism() { + let (mailbox, mut spy) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + mailbox.set_default_ism(NEW_DEFAULT_ISM()); + assert(mailbox.get_default_ism() == NEW_DEFAULT_ISM(), 'Failed to set default ism'); + let expected_event = mailbox::Event::DefaultIsmSet( + mailbox::DefaultIsmSet { module: NEW_DEFAULT_ISM() } + ); + spy.assert_emitted(@array![(mailbox.contract_address, expected_event)]); +} +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_set_default_hook_fails_if_not_owner() { + let (mailbox, _) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); + mailbox.set_default_hook(NEW_DEFAULT_HOOK()); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_set_required_hook_fails_if_not_owner() { + let (mailbox, _) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); + mailbox.set_required_hook(NEW_REQUIRED_HOOK()); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_set_default_ism_fails_if_not_owner() { + let (mailbox, _) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); + mailbox.set_default_ism(NEW_DEFAULT_ISM()); +} + +#[test] +fn test_dispatch() { + let (mailbox, mut spy) = setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + let array = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000 + ]; + + let message_body = BytesTrait::new(42, array); + let message = Message { + version: HYPERLANE_VERSION, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: OWNER(), + destination: DESTINATION_DOMAIN, + recipient: RECIPIENT_ADDRESS(), + body: message_body.clone() + }; + let message_id = MessageTrait::format_message(message.clone()); + mailbox + .dispatch( + DESTINATION_DOMAIN, RECIPIENT_ADDRESS(), message_body, Option::None, Option::None + ); + let expected_event = mailbox::Event::Dispatch( + mailbox::Dispatch { + sender: OWNER(), + destination_domain: DESTINATION_DOMAIN, + recipient_address: RECIPIENT_ADDRESS(), + message: message_id + } + ); + let expected_event_id = mailbox::Event::DispatchId(mailbox::DispatchId { id: message_id }); + + spy + .assert_emitted( + @array![ + (mailbox.contract_address, expected_event), + (mailbox.contract_address, expected_event_id) + ] + ); + + assert(mailbox.get_latest_dispatched_id() == message_id, 'Failed to fetch latest id'); +} + + +#[test] +fn test_process() { + let (mailbox, mut spy) = setup(); + let mock_recipient = mock_setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + mailbox.set_local_domain(DESTINATION_DOMAIN); + let array = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000 + ]; + + let message_body = BytesTrait::new(42, array); + let message = Message { + version: HYPERLANE_VERSION, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: OWNER(), + destination: DESTINATION_DOMAIN, + recipient: mock_recipient.contract_address, + body: message_body.clone() + }; + let message_id = MessageTrait::format_message(message.clone()); + let metadata = message_body; + mailbox.process(metadata.clone(), message); + let expected_event = mailbox::Event::Process( + mailbox::Process { + origin: LOCAL_DOMAIN, sender: OWNER(), recipient: mock_recipient.contract_address, + } + ); + let expected_event_id = mailbox::Event::ProcessId(mailbox::ProcessId { id: message_id }); + + spy + .assert_emitted( + @array![ + (mailbox.contract_address, expected_event), + (mailbox.contract_address, expected_event_id) + ] + ); + let block_number = starknet::get_block_number(); + assert(mailbox.delivered(message_id), 'Failed to delivered(id)'); + assert(mailbox.processor(message_id) == OWNER(), 'Wrong processor'); + assert(mailbox.processed_at(message_id) == block_number, 'Wrong processed block number'); + assert(mock_recipient.get_origin() == LOCAL_DOMAIN, 'Failed to retrieve origin'); + assert(mock_recipient.get_sender() == OWNER(), 'Failed to retrieve sender'); + assert(mock_recipient.get_message() == metadata, 'Failed to retrieve metadata'); + +} From 580cc95a330843ee751c59673ca6a560ef477325 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 29 Apr 2024 10:47:05 +0200 Subject: [PATCH 07/26] feat:docs --- src/contracts/mailbox.cairo | 101 ++++++++++++++++++++++++++++++++++- src/tests/test_mailbox.cairo | 89 ++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 1 deletion(-) diff --git a/src/contracts/mailbox.cairo b/src/contracts/mailbox.cairo index 8b9fa12..2bd7775 100644 --- a/src/contracts/mailbox.cairo +++ b/src/contracts/mailbox.cairo @@ -34,12 +34,19 @@ pub mod mailbox { #[storage] struct Storage { + // Domain of chain on which the contract is deployed local_domain: u32, + // A monotonically increasing nonce for outbound unique message IDs. nonce: u32, + // The latest dispatched message ID used for auth in post-dispatch hooks. latest_dispatched_id: u256, + // The default ISM, used if the recipient fails to specify one. default_ism: ContractAddress, + // The default post dispatch hook, used for post processing of opting-in dispatches. default_hook: ContractAddress, + // The required post dispatch hook, used for post processing of ALL dispatches. required_hook: ContractAddress, + // Mapping of message ID to delivery context that processed the message. deliveries: LegacyMap::, #[substorage(v0)] ownable: OwnableComponent::Storage, @@ -163,30 +170,66 @@ pub mod mailbox { self.latest_dispatched_id.read() } - + /// Sets the default ISM for the Mailbox. + /// Callable only by the admin + /// + /// # Arguments + /// + /// * `_hook` - The new default ISM fn set_default_ism(ref self: ContractState, _module: ContractAddress) { self.ownable.assert_only_owner(); self.default_ism.write(_module); self.emit(DefaultIsmSet { module: _module }); } + /// Sets the default post dispatch hook for the Mailbox. + /// Callable only by the admin + /// + /// # Arguments + /// + /// * `_hook` - The new default post dispatch hook. fn set_default_hook(ref self: ContractState, _hook: ContractAddress) { self.ownable.assert_only_owner(); self.default_hook.write(_hook); self.emit(DefaultHookSet { hook: _hook }); } + /// Sets the required post dispatch hook for the Mailbox. + /// Callable only by the admin + /// + /// # Arguments + /// + /// * `_hook` - The new required post dispatch hook. fn set_required_hook(ref self: ContractState, _hook: ContractAddress) { self.ownable.assert_only_owner(); self.required_hook.write(_hook); self.emit(RequiredHookSet { hook: _hook }); } + /// Sets the domain of chain for the mailbox + /// Callable only by the admin + /// + /// # Arguments + /// + /// * `_local_domain` - The new local domain fn set_local_domain(ref self: ContractState, _local_domain: u32) { self.ownable.assert_only_owner(); self.local_domain.write(_local_domain); } + /// Dispatches a message to the destination domain & recipient using the default hook and empty metadata. + /// + /// # Arguments + /// + /// * `_destination_domain` - Domain of destination chain + /// * `_recipient_address` - Address of recipient on destination chain + /// * `_message_body` - Raw bytes content of message body + /// * `_custom_hook_metadata` - Metadata used by the post dispatch hook + /// * `_custom_hook` - Custom hook to use instead of the default + /// + /// # Returns + /// + /// * The message ID inserted into the Mailbox's merkle tree fn dispatch( ref self: ContractState, _destination_domain: u32, @@ -240,6 +283,15 @@ pub mod mailbox { id } + /// Returns true if the message has been processed. + /// + /// # Arguments + /// + /// * `_message_id` - The message ID to check. + /// + /// # Returns + /// + /// * True if the message has been delivered. fn delivered(self: @ContractState, _message_id: u256) -> bool { self.deliveries.read(_message_id).block_number > 0 } @@ -248,6 +300,12 @@ pub mod mailbox { self.nonce.read() } + /// Attempts to deliver `_message` to its recipient. Verifies `_message` via the recipient's ISM using the provided `_metadata` + /// + /// # Arguments + /// + /// * `_metadata` - Metadata used by the ISM to verify `_message`. + /// * `_message` - Formatted Hyperlane message (ref: message.cairo) fn process(ref self: ContractState, _metadata: Bytes, _message: Message) { assert(_message.version == HYPERLANE_VERSION, Errors::WRONG_HYPERLANE_VERSION); assert( @@ -294,6 +352,20 @@ pub mod mailbox { }; message_recipient.handle(_message.origin, _message.sender, _message.body); } + + /// Computes quote for dispatching a message to the destination domain & recipient. + /// + /// # Arguments + /// + /// * `_destination_domain` - Domain of destination chain + /// * `_recipient_address` - Address of recipient on destination chain + /// * `_message_body` - Raw bytes content of message body + /// * `_custom_hook_metadata` - Metadata used by the post dispatch hook + /// * `_custom_hook` - Custom hook to use instead of the default + /// + /// # Returns + /// + /// * The message ID inserted into the Mailbox's merkle tree fn quote_dispatch( self: @ContractState, _destination_domain: u32, @@ -322,6 +394,15 @@ pub mod mailbox { - hook.quote_dispatch(hook_metadata, message) } + /// Returns the ISM to use for the recipient, defaulting to the default ISM if none is specified. + /// + /// # Arguments + /// + /// * `_recipient` - The message recipient whose ISM should be returned. + /// + /// # Returns + /// + /// * The ISM to use for `_recipient` fn recipient_ism(self: @ContractState, _recipient: ContractAddress) -> ContractAddress { let mut call_data: Array = ArrayTrait::new(); let mut res = starknet::syscalls::call_contract_syscall( @@ -343,10 +424,28 @@ pub mod mailbox { self.default_ism.read() } + /// Returns the account that processed the message. + /// + /// # Arguments + /// + /// * `_id` - The message ID to check. + /// + /// # Returns + /// + /// * The account that processed the message. fn processor(self: @ContractState, _id: u256) -> ContractAddress { self.deliveries.read(_id).processor } + /// Returns the account that processed the message. + /// + /// # Arguments + /// + /// * `_id` - The message ID to check. + /// + /// # Returns + /// + /// * The number of the block that the message was processed at. fn processed_at(self: @ContractState, _id: u256) -> u64 { self.deliveries.read(_id).block_number } diff --git a/src/tests/test_mailbox.cairo b/src/tests/test_mailbox.cairo index 88f3750..4360238 100644 --- a/src/tests/test_mailbox.cairo +++ b/src/tests/test_mailbox.cairo @@ -221,5 +221,94 @@ fn test_process() { assert(mock_recipient.get_origin() == LOCAL_DOMAIN, 'Failed to retrieve origin'); assert(mock_recipient.get_sender() == OWNER(), 'Failed to retrieve sender'); assert(mock_recipient.get_message() == metadata, 'Failed to retrieve metadata'); +} + + +#[test] +#[should_panic(expected: ('Wrong hyperlane version',))] +fn test_process_fails_if_version_mismatch() { + let (mailbox, _) = setup(); + let mock_recipient = mock_setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + mailbox.set_local_domain(DESTINATION_DOMAIN); + let array = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000 + ]; + let message_body = BytesTrait::new(42, array); + let message = Message { + version: HYPERLANE_VERSION + 1, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: OWNER(), + destination: DESTINATION_DOMAIN, + recipient: mock_recipient.contract_address, + body: message_body.clone() + }; + let metadata = message_body; + mailbox.process(metadata.clone(), message); } + +#[test] +#[should_panic(expected: ('Unexpected destination',))] +fn test_process_fails_if_destination_domain_does_not_match_local_domain() { + let (mailbox, _) = setup(); + let mock_recipient = mock_setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + mailbox.set_local_domain(DESTINATION_DOMAIN); + let array = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000 + ]; + + let message_body = BytesTrait::new(42, array); + let message = Message { + version: HYPERLANE_VERSION, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: OWNER(), + destination: DESTINATION_DOMAIN + 1, + recipient: mock_recipient.contract_address, + body: message_body.clone() + }; + let metadata = message_body; + mailbox.process(metadata.clone(), message); +} + + +#[test] +#[should_panic(expected: ('Mailbox: already delivered',))] +fn test_process_fails_if_already_delivered() { + let (mailbox, _) = setup(); + let mock_recipient = mock_setup(); + let ownable = IOwnableDispatcher { contract_address: mailbox.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + mailbox.set_local_domain(DESTINATION_DOMAIN); + let array = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000 + ]; + + let message_body = BytesTrait::new(42, array); + let message = Message { + version: HYPERLANE_VERSION, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: OWNER(), + destination: DESTINATION_DOMAIN, + recipient: mock_recipient.contract_address, + body: message_body.clone() + }; + let metadata = message_body; + mailbox.process(metadata.clone(), message.clone()); + let message_id = MessageTrait::format_message(message.clone()); + assert(mailbox.delivered(message_id), 'Delivered status did not change'); + mailbox.process(metadata.clone(), message); +} + From 0e1797c65855057363fd0a056a6e0e1562ffbe02 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Fri, 10 May 2024 11:31:44 +0200 Subject: [PATCH 08/26] Ism integration --- .../multisig/merkleroot_multisig_ism.cairo | 89 +++++++++++ .../multisig/messageid_multisig_ism.cairo | 103 ++++++++++++ .../isms/multisig/multisig_ism.cairo | 108 +++++++++++++ .../isms/routing/domain_routing_ism.cairo | 150 ++++++++++++++++++ src/contracts/libs/checkpoint_lib.cairo | 45 ++++++ src/contracts/libs/message.cairo | 1 - .../multisig/message_id_ism_metadata.cairo | 49 ++++++ src/interfaces.cairo | 62 ++++++-- src/lib.cairo | 15 ++ 9 files changed, 611 insertions(+), 11 deletions(-) create mode 100644 src/contracts/isms/multisig/merkleroot_multisig_ism.cairo create mode 100644 src/contracts/isms/multisig/messageid_multisig_ism.cairo create mode 100644 src/contracts/isms/multisig/multisig_ism.cairo create mode 100644 src/contracts/isms/routing/domain_routing_ism.cairo create mode 100644 src/contracts/libs/checkpoint_lib.cairo create mode 100644 src/contracts/libs/multisig/message_id_ism_metadata.cairo diff --git a/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo b/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo new file mode 100644 index 0000000..0352d2b --- /dev/null +++ b/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo @@ -0,0 +1,89 @@ +#[starknet::contract] +pub mod merkleroot_multisig_ism { + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; + + + use core::ecdsa::check_ecdsa_signature; + use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + use hyperlane_starknet::interfaces::{ + IMultisigIsm, IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, + ModuleType, IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, + }; + + use starknet::ContractAddress; + #[storage] + struct Storage {} + + mod Errors { + pub const NO_MULTISIG_THRESHOLD_FOR_MESSAGE: felt252 = 'No MultisigISM treshold present'; + pub const VERIFICATION_FAILED_THRESHOLD_NOT_REACHED: felt252 = 'Verify failed, < threshold'; + } + impl IMerklerootMultisigIsmImpl of IInterchainSecurityModule { + fn module_type(self: @ContractState) -> ModuleType { + ModuleType::MERKLE_ROOT_MULTISIG(starknet::get_contract_address()) + } + + fn verify( + self: @ContractState, + _metadata: Span, + _message: Message, + _validator_configuration: ContractAddress + ) -> bool { + let digest = digest(_metadata.clone(), _message.clone()); + let validator_configuration = IMultisigIsmDispatcher { + contract_address: _validator_configuration + }; + let (validators, threshold) = validator_configuration + .validators_and_threshold(_message); + assert(threshold > 0, Errors::NO_MULTISIG_THRESHOLD_FOR_MESSAGE); + let validator_count = validators.len(); + let mut unmatched_signatures = 0; + let mut matched_signatures = 0; + let mut i = 0; + + // for each couple (sig_s, sig_r) extracted from the metadata + loop { + if (i == threshold) { + break (); + } + let (signature_r, signature_s) = get_signature_at(_metadata.clone(), i); + + // we loop on the validators list public kew in order to find a match + let mut cur_idx = 0; + let is_signer_in_list = loop { + if (cur_idx == validators.len()) { + break false; + } + let signer = *validators.at(cur_idx).public_key; + if check_ecdsa_signature(digest, signer, signature_r, signature_s) { + // we found a match + break true; + } + cur_idx += 1; + }; + if (!is_signer_in_list) { + unmatched_signatures += 1; + } else { + matched_signatures += 1; + } + assert( + unmatched_signatures < validator_count - threshold, + Errors::VERIFICATION_FAILED_THRESHOLD_NOT_REACHED + ); + i += 1; + }; + assert( + matched_signatures >= threshold, Errors::VERIFICATION_FAILED_THRESHOLD_NOT_REACHED + ); + true + } + } + + fn digest(_metadata: Span, _message: Message) -> felt252 { + return 0; + } + + fn get_signature_at(_metadata: Span, index: u32) -> (felt252, felt252) { + (0, 0) + } +} diff --git a/src/contracts/isms/multisig/messageid_multisig_ism.cairo b/src/contracts/isms/multisig/messageid_multisig_ism.cairo new file mode 100644 index 0000000..f50cb4e --- /dev/null +++ b/src/contracts/isms/multisig/messageid_multisig_ism.cairo @@ -0,0 +1,103 @@ +#[starknet::contract] +pub mod messageid_multisig_ism { + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; + use core::ecdsa::check_ecdsa_signature; + use starknet::eth_signature::is_eth_signature_valid; + use starknet::EthAddress; + use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + use hyperlane_starknet::interfaces::{ + IMultisigIsm, IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, + ModuleType, IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, + }; + use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::CheckpointLib; + use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; + use starknet::ContractAddress; + use starknet::secp256_trait::{Signature,signature_from_vrs}; + #[storage] + struct Storage {} + + mod Errors { + pub const NO_MULTISIG_THRESHOLD_FOR_MESSAGE: felt252 = 'No MultisigISM treshold present'; + pub const VERIFICATION_FAILED_THRESHOLD_NOT_REACHED: felt252 = 'Verify failed, < threshold'; + } + impl IMessageidMultisigIsmImpl of IInterchainSecurityModule { + fn module_type(self: @ContractState) -> ModuleType { + ModuleType::MESSAGE_ID_MULTISIG(starknet::get_contract_address()) + } + + fn verify( + self: @ContractState, + _metadata: Span, + _message: Message, + _validator_configuration: ContractAddress + ) -> bool { + let digest = digest(_metadata.clone(), _message.clone()); + let validator_configuration = IMultisigIsmDispatcher { + contract_address: _validator_configuration + }; + let (validators, threshold) = validator_configuration + .validators_and_threshold(_message); + assert(threshold > 0, Errors::NO_MULTISIG_THRESHOLD_FOR_MESSAGE); + let validator_count = validators.len(); + let mut unmatched_signatures = 0; + let mut matched_signatures = 0; + let mut i = 0; + + // for each couple (sig_s, sig_r) extracted from the metadata + loop { + if (i == threshold) { + break (); + } + let signature = get_signature_at(_metadata.clone(), i); + + // we loop on the validators list public kew in order to find a match + let mut cur_idx = 0; + let is_signer_in_list = loop { + if (cur_idx == validators.len()) { + break false; + } + let signer = *validators.at(cur_idx).public_key; + if bool_is_eth_signature_valid(digest.into(), signature, signer.try_into().unwrap()) { + // we found a match + break true; + } + cur_idx += 1; + }; + if (!is_signer_in_list) { + unmatched_signatures += 1; + } else { + matched_signatures += 1; + } + assert( + unmatched_signatures < validator_count - threshold, + Errors::VERIFICATION_FAILED_THRESHOLD_NOT_REACHED + ); + i += 1; + }; + assert( + matched_signatures >= threshold, Errors::VERIFICATION_FAILED_THRESHOLD_NOT_REACHED + ); + true + } + } + + fn digest(_metadata: Span, _message: Message) -> felt252 { + let origin_merkle_tree_hook = MessageIdIsmMetadata::origin_merkle_tree_hook(_metadata.clone()); + let root = MessageIdIsmMetadata::root(_metadata.clone()); + let index = MessageIdIsmMetadata::index(_metadata.clone()); + CheckpointLib::digest(_message.origin,origin_merkle_tree_hook.into(), root.into(), index, MessageTrait::format_message(_message)).try_into().unwrap() + } + + fn get_signature_at(_metadata: Span, _index: u32) -> Signature { + let (v,r,s) = MessageIdIsmMetadata::signature_at(_metadata, _index); + signature_from_vrs(v.into(),r,s) + + } + + fn bool_is_eth_signature_valid(msg_hash: u256, signature: Signature, signer: EthAddress) -> bool { + match is_eth_signature_valid(msg_hash, signature, signer) { + Result::Ok(()) => true, + Result::Err(_) => false + } + } +} diff --git a/src/contracts/isms/multisig/multisig_ism.cairo b/src/contracts/isms/multisig/multisig_ism.cairo new file mode 100644 index 0000000..39071a3 --- /dev/null +++ b/src/contracts/isms/multisig/multisig_ism.cairo @@ -0,0 +1,108 @@ +#[starknet::contract] +pub mod multisig_ism { + use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + use hyperlane_starknet::interfaces::IMultisigIsm; + use starknet::{ContractAddress, contract_address_const}; + use starknet::eth_address::EthAddress; + + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + type Index = felt252; + + #[derive(Serde, Copy, starknet::Store, Drop, partialEq)] + pub struct ValidatorInformations { + pub address: EthAddress, + pub public_key: felt252 + } + + #[storage] + struct Storage { + validators: LegacyMap, + threshold: u32, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + + + mod Errors { + pub const VALIDATOR_ADDRESS_CANNOT_BE_NULL: felt252 = 'Validator address cannot be 0'; + pub const VALIDATOR_PUBLIC_KEY_CANNOT_BE_NULL: felt252 = 'Validator pk cannot be 0'; + } + + #[abi(embed_v0)] + impl IMultisigIsmImpl of IMultisigIsm { + fn get_validators(self: @ContractState) -> Span { + build_validators_span(self) + } + + fn get_threshold(self: @ContractState) -> u32 { + self.threshold.read() + } + + fn set_validators(ref self: ContractState, _validators: Span) { + self.ownable.assert_only_owner(); + let mut cur_idx = 0; + + loop { + if (cur_idx == _validators.len()) { + break (); + } + let validator = *_validators.at(cur_idx); + assert( + validator.address != 0.try_into().unwrap(), + Errors::VALIDATOR_ADDRESS_CANNOT_BE_NULL + ); + assert(validator.public_key != 0, Errors::VALIDATOR_PUBLIC_KEY_CANNOT_BE_NULL); + self.validators.write(cur_idx.into(), validator); + cur_idx += 1; + } + } + + fn set_threshold(ref self: ContractState, _threshold: u32) { + self.ownable.assert_only_owner(); + self.threshold.write(_threshold); + } + + fn validators_and_threshold( + self: @ContractState, _message: Message + ) -> (Span, u32) { + // USER CONTRACT DEFINITION HERE + // USER CAN SPECIFY VALIDATORS SELECTION CONDITIONS + let threshold = self.threshold.read(); + (build_validators_span(self), threshold) + } + } + + fn build_validators_span(self: @ContractState) -> Span { + let mut validators = ArrayTrait::new(); + let mut cur_idx = 0; + loop { + let validator = self.validators.read(cur_idx); + if (validator.address ==0.try_into().unwrap()) { + break (); + } + validators.append(validator); + cur_idx += 1; + }; + validators.span() + } +} diff --git a/src/contracts/isms/routing/domain_routing_ism.cairo b/src/contracts/isms/routing/domain_routing_ism.cairo new file mode 100644 index 0000000..c324f04 --- /dev/null +++ b/src/contracts/isms/routing/domain_routing_ism.cairo @@ -0,0 +1,150 @@ +#[starknet::contract] +pub mod domain_routing_ism { + use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + use hyperlane_starknet::interfaces::IDomainRoutingIsm; + + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use core::panic_with_felt252; + + use starknet::{ContractAddress, contract_address_const}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + type Domain = u32; + type Index = u32; + #[storage] + struct Storage { + modules: LegacyMap, + domains: LegacyMap, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + mod Errors { + pub const LENGTH_MISMATCH: felt252 = 'Length mismatch'; + pub const ORIGIN_NOT_FOUND: felt252 = 'Origin not found'; + pub const MODULE_CANNOT_BE_ZERO: felt252 = 'Module cannot be zero'; + pub const DOMAIN_NOT_FOUND : felt252= 'Domain not found'; + } + + #[constructor] + fn constructor(ref self: ContractState, _owner: ContractAddress) { + self.ownable.initializer(_owner); + } + + impl IDomainRoutingIsmImpl of IDomainRoutingIsm { + fn initialize( + ref self: ContractState, _domains: Span, _modules: Span + ) { + self.ownable.assert_only_owner(); + assert(_domains.len() == _modules.len(), Errors::LENGTH_MISMATCH); + let mut cur_idx = 0; + loop { + if (cur_idx == _domains.len()) { + break (); + } + _set(ref self, *_domains.at(cur_idx), *_modules.at(cur_idx)); + cur_idx += 1; + } + } + + fn set(ref self: ContractState, _domain: u32, _module: ContractAddress) { + self.ownable.assert_only_owner(); + assert(_module != contract_address_const::<0>(), Errors::MODULE_CANNOT_BE_ZERO); + _set(ref self, _domain, _module); + } + + fn remove(ref self: ContractState, _domain: u32) { + self.ownable.assert_only_owner(); + _remove(ref self, _domain); + } + + fn domains(self: @ContractState) -> Span { + let mut current_domain = self.domains.read(0); + let mut domains = array![]; + loop { + let next_domain = self.domains.read(current_domain); + if next_domain == 0 { + break (); + } + domains.append(current_domain); + current_domain = next_domain; + }; + domains.span() + } + + fn module(self: @ContractState, _origin: u32) -> ContractAddress { + let module = self.modules.read(_origin); + assert(module != contract_address_const::<0>(), Errors::ORIGIN_NOT_FOUND); + module + } + + fn route(self: @ContractState, _message: Message) -> ContractAddress { + self.modules.read(_message.origin) + } + } + + fn find_last_domain(self: @ContractState) -> u32 { + let mut current_domain = self.domains.read(0); + loop { + let next_domain = self.domains.read(current_domain); + if next_domain == 0 { + break current_domain; + } + current_domain = next_domain; + } + } + + fn find_domain_index(self: @ContractState, _domain: u32) -> Option { + let mut current_domain = 0; + loop { + let next_domain = self.domains.read(current_domain); + if next_domain == _domain { + break Option::Some(current_domain); + } else if next_domain == 0 { + break Option::None(()); + } + current_domain = next_domain; + } + } + + fn _remove(ref self: ContractState, _domain: u32) { + let domain_index = match find_domain_index(@self,_domain){ + Option::Some(index) => index, + Option::None(()) => { + panic_with_felt252(Errors::DOMAIN_NOT_FOUND); + 0 + } + }; + let next_domain = self.domains.read(_domain); + self.domains.write(domain_index, next_domain); + } + + fn _set(ref self: ContractState, _domain: u32, _module: ContractAddress) { + match find_domain_index(@self,_domain) { + Option::Some(_) =>{} , + Option::None(()) => { + let latest_domain = find_last_domain(@self); + self.domains.write(latest_domain, _domain); + } + } + self.modules.write(_domain, _module); + } +} diff --git a/src/contracts/libs/checkpoint_lib.cairo b/src/contracts/libs/checkpoint_lib.cairo new file mode 100644 index 0000000..6d8fe7e --- /dev/null +++ b/src/contracts/libs/checkpoint_lib.cairo @@ -0,0 +1,45 @@ + + +pub mod checkpoint_lib { + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; + use core::keccak::keccak_u256s_be_inputs; + use hyperlane_starknet::contracts::libs::message::Message; + use hyperlane_starknet::utils::keccak256::reverse_endianness; + + + + + pub trait CheckpointLib { + fn digest(_origin: u32, _origin_merkle_tree_hook: u256, _checkpoint_root: u256, _checkpoint_index: u32, _message_id: u256) -> u256; + fn domain_hash(_origin: u32, _origin_merkle_tree_hook: u256) -> u256 ; + } + const HYPERLANE: felt252 = 'HYPERLANE'; + const ETH_SIGNED_MESSAGE: felt252 = '\x19Ethereum Signed Message:\n'; + + impl CheckpointLibImpl of CheckpointLib { + fn digest(_origin: u32, _origin_merkle_tree_hook: u256, _checkpoint_root: u256, _checkpoint_index: u32, _message_id: u256) -> u256 { + let domain_hash = CheckpointLib::domain_hash(_origin, _origin_merkle_tree_hook); + let mut input: Array = array![ + ETH_SIGNED_MESSAGE.into(), + domain_hash.into(), + _checkpoint_root.into(), + _checkpoint_index.into(), + _message_id.into(), + ]; + let hash = keccak_u256s_be_inputs(input.span()); + reverse_endianness(hash) + + } + + fn domain_hash(_origin: u32, _origin_merkle_tree_hook: u256) -> u256 { + let mut input : Array = array![ + _origin.into(), + _origin_merkle_tree_hook.into(), + HYPERLANE.into() + ]; + let hash = keccak_u256s_be_inputs(input.span()); + reverse_endianness(hash) + + } + } +} \ No newline at end of file diff --git a/src/contracts/libs/message.cairo b/src/contracts/libs/message.cairo index 6da4250..8fa5763 100644 --- a/src/contracts/libs/message.cairo +++ b/src/contracts/libs/message.cairo @@ -1,6 +1,5 @@ use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; use core::keccak::keccak_u256s_be_inputs; -use core::poseidon::poseidon_hash_span; use hyperlane_starknet::utils::keccak256::reverse_endianness; use starknet::{ContractAddress, contract_address_const}; diff --git a/src/contracts/libs/multisig/message_id_ism_metadata.cairo b/src/contracts/libs/multisig/message_id_ism_metadata.cairo new file mode 100644 index 0000000..bcb64b4 --- /dev/null +++ b/src/contracts/libs/multisig/message_id_ism_metadata.cairo @@ -0,0 +1,49 @@ +pub mod message_id_ism_metadata { + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; + + + pub trait MessageIdIsmMetadata { + fn origin_merkle_tree_hook(_metadata: Span) -> u256; + fn root(_metadata: Span) -> u256; + fn index(_metadata: Span) -> u32; + fn signature_at(_metadata: Span, _index : u32) -> (u8, u256, u256); + } + const ORIGIN_MERKLE_TREE_OFFSET: u32 = 0; + const MERKLE_ROOT_OFFSET : u32= 32; + const MERKLE_INDEX_OFFSET : u32= 64; + const SIGNATURES_OFFSET : u32= 68; + const SIGNATURE_LENGTH : u32= 65; + + impl MessagIdIsmMetadataImpl of MessageIdIsmMetadata { + + fn origin_merkle_tree_hook(_metadata:Span) -> u256 { + let merkle_tree_hook_bytes = _metadata[0]; + let (_, felt) = merkle_tree_hook_bytes.read_u256(1); + felt + } + + fn root(_metadata: Span) -> u256 { + let root_bytes = _metadata[1]; + let (_, felt) = root_bytes.read_u256(1); + felt + } + + fn index(_metadata: Span) -> u32 { + let index_bytes = _metadata[2]; + let (_, felt) = index_bytes.read_u32(0); + felt + } + + fn signature_at(_metadata:Span, _index: u32) -> (u8, u256, u256) { + // the first signer index is 0 + let signature_initial_index = 5; + let (_,r) = _metadata[signature_initial_index + 2*_index].read_u256(1); + let (_,s) = _metadata[signature_initial_index + 2*_index + 1].read_u256(1); + let (_,v) = _metadata[signature_initial_index + 2*_index + 1].read_u8(0); + (v,r,s) + } + + + } + +} \ No newline at end of file diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 7d751ba..19549d9 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -1,4 +1,5 @@ use alexandria_bytes::Bytes; +use hyperlane_starknet::contracts::isms::multisig::multisig_ism::multisig_ism::ValidatorInformations; use hyperlane_starknet::contracts::libs::message::Message; use starknet::ContractAddress; @@ -20,16 +21,17 @@ pub enum Types { #[derive(Serde)] pub enum ModuleType { - UNUSED, - ROUTING, - AGGREGATION, - LEGACY_MULTISIG, - MERKLE_ROOT_MULTISIG, - MESSAGE_ID_MULTISIG, + UNUSED: ContractAddress, + ROUTING: ContractAddress, + AGGREGATION: ContractAddress, + LEGACY_MULTISIG: ContractAddress, + MERKLE_ROOT_MULTISIG: ContractAddress, + MESSAGE_ID_MULTISIG: ContractAddress, NULL, // used with relayer carrying no metadata - CCIP_READ, + CCIP_READ: ContractAddress, } + pub const HYPERLANE_VERSION: u8 = 3; #[starknet::interface] @@ -104,12 +106,12 @@ pub trait IInterchainSecurityModule { /// * `_metadata` - Off-chain metadata provided by a relayer, specific to the security model encoded by /// the module (e.g. validator signatures) /// * `_message` - Hyperlane encoded interchain message - fn verify(self: @TContractState, _metadata: Bytes, _message: Message) -> bool; + fn verify(self: @TContractState, _metadata: Span, _message: Message,_validator_configuration: ContractAddress) -> bool; } #[starknet::interface] pub trait ISpecifiesInterchainSecurityModule { - fn interchain_security_module(self: @TContractState) -> ContractAddress; + fn interchain_security_module(self: @TContractState) -> ModuleType; } @@ -188,7 +190,6 @@ pub trait IInterchainGasPaymaster { ) -> u256; } - #[starknet::interface] pub trait IRouter { fn routers(self: @TContractState, _domain: u32) -> ContractAddress; @@ -206,3 +207,44 @@ pub trait IRouter { fn handle(self: @TContractState, _origin: u32, _sender: ContractAddress, _message: Message); } + +#[starknet::interface] +pub trait IMultisigIsm { + fn validators_and_threshold( + self: @TContractState, _message: Message + ) -> (Span, u32); + + fn get_validators(self: @TContractState) -> Span; + + fn get_threshold(self: @TContractState) -> u32; + + fn set_validators(ref self: TContractState, _validators: Span); + + fn set_threshold(ref self: TContractState, _threshold: u32); +} + +#[starknet::interface] +pub trait IDefaultFallbackRoutingIsm { + /// Returns an enum that represents the type of security model encoded by this ISM. + /// Relayers infer how to fetch and format metadata. + fn module_type(self: @TContractState) -> ModuleType; + + fn route(self: @TContractState, _message: Message) -> ContractAddress; + + fn verify(self: @TContractState, _metadata: Bytes, _message: Message) -> bool; +} + +#[starknet::interface] +pub trait IDomainRoutingIsm { + fn initialize(ref self: TContractState, _domains: Span, _modules: Span); + + fn set(ref self: TContractState, _domain: u32, _module: ContractAddress); + + fn remove(ref self: TContractState, _domain: u32); + + fn domains(self: @TContractState) -> Span; + + fn module(self: @TContractState, _origin: u32) -> ContractAddress; + + fn route(self: @TContractState, _message: Message) -> ContractAddress; +} diff --git a/src/lib.cairo b/src/lib.cairo index 71d0b62..a4f69d7 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -2,7 +2,11 @@ mod interfaces; mod contracts { pub mod mailbox; pub mod libs { + pub mod multisig { + pub mod message_id_ism_metadata; + } pub mod message; + pub mod checkpoint_lib; } pub mod client { pub mod mailboxclient; @@ -11,6 +15,16 @@ mod contracts { pub mod mocks { pub mod message_recipient; } + pub mod isms { + pub mod multisig { + pub mod merkleroot_multisig_ism; + pub mod messageid_multisig_ism; + pub mod multisig_ism; + } + pub mod routing { + pub mod domain_routing_ism; + } + } } mod utils { pub mod keccak256; @@ -20,4 +34,5 @@ mod utils { mod tests { pub mod setup; pub mod test_mailbox; + pub mod test_message_id_ism; } From f79c05ad7f49ba423e63253202d76d1aa7ee62aa Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 13 May 2024 14:49:43 +0200 Subject: [PATCH 09/26] feat: validator announce --- src/contracts/client/mailboxclient.cairo | 13 ++ .../multisig/merkleroot_multisig_ism.cairo | 17 +- .../multisig/messageid_multisig_ism.cairo | 57 +++-- .../isms/multisig/multisig_ism.cairo | 19 +- .../isms/multisig/validator_annonce.cairo | 191 +++++++++++++++++ .../isms/routing/domain_routing_ism.cairo | 13 +- src/contracts/libs/checkpoint_lib.cairo | 51 +++-- .../multisig/message_id_ism_metadata.cairo | 76 +++---- src/interfaces.cairo | 37 +++- src/lib.cairo | 8 +- src/tests/test_multisig.cairo | 196 ++++++++++++++++++ src/utils/store_arrays.cairo | 71 +++++++ 12 files changed, 636 insertions(+), 113 deletions(-) create mode 100644 src/contracts/isms/multisig/validator_annonce.cairo create mode 100644 src/tests/test_multisig.cairo create mode 100644 src/utils/store_arrays.cairo diff --git a/src/contracts/client/mailboxclient.cairo b/src/contracts/client/mailboxclient.cairo index a01ec49..9da4b76 100644 --- a/src/contracts/client/mailboxclient.cairo +++ b/src/contracts/client/mailboxclient.cairo @@ -68,6 +68,19 @@ mod mailboxclient { self.interchain_security_module.write(_module); } + fn get_local_domain(self: @ContractState) -> u32 { + self.local_domain.read() + } + + fn get_hook(self: @ContractState) -> ContractAddress { + self.hook.read() + } + + fn get_interchain_security_module(self: @ContractState) -> ContractAddress { + self.interchain_security_module.read() + } + + fn _MailboxClient_initialize( ref self: ContractState, _hook: ContractAddress, diff --git a/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo b/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo index 0352d2b..f9a65aa 100644 --- a/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo +++ b/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo @@ -6,8 +6,9 @@ pub mod merkleroot_multisig_ism { use core::ecdsa::check_ecdsa_signature; use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; use hyperlane_starknet::interfaces::{ - IMultisigIsm, IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, - ModuleType, IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, + IMultisigIsm, IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, ModuleType, + IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, + IInterchainSecurityModuleDispatcherTrait, }; use starknet::ContractAddress; @@ -18,6 +19,8 @@ pub mod merkleroot_multisig_ism { pub const NO_MULTISIG_THRESHOLD_FOR_MESSAGE: felt252 = 'No MultisigISM treshold present'; pub const VERIFICATION_FAILED_THRESHOLD_NOT_REACHED: felt252 = 'Verify failed, < threshold'; } + + #[abi(embed_v0)] impl IMerklerootMultisigIsmImpl of IInterchainSecurityModule { fn module_type(self: @ContractState) -> ModuleType { ModuleType::MERKLE_ROOT_MULTISIG(starknet::get_contract_address()) @@ -25,7 +28,7 @@ pub mod merkleroot_multisig_ism { fn verify( self: @ContractState, - _metadata: Span, + _metadata: Bytes, _message: Message, _validator_configuration: ContractAddress ) -> bool { @@ -54,8 +57,8 @@ pub mod merkleroot_multisig_ism { if (cur_idx == validators.len()) { break false; } - let signer = *validators.at(cur_idx).public_key; - if check_ecdsa_signature(digest, signer, signature_r, signature_s) { + let signer = *validators.at(cur_idx).address; + if check_ecdsa_signature(digest, signer.try_into().unwrap(), signature_r, signature_s) { // we found a match break true; } @@ -79,11 +82,11 @@ pub mod merkleroot_multisig_ism { } } - fn digest(_metadata: Span, _message: Message) -> felt252 { + fn digest(_metadata: Bytes, _message: Message) -> felt252 { return 0; } - fn get_signature_at(_metadata: Span, index: u32) -> (felt252, felt252) { + fn get_signature_at(_metadata: Bytes, index: u32) -> (felt252, felt252) { (0, 0) } } diff --git a/src/contracts/isms/multisig/messageid_multisig_ism.cairo b/src/contracts/isms/multisig/messageid_multisig_ism.cairo index f50cb4e..1d4269c 100644 --- a/src/contracts/isms/multisig/messageid_multisig_ism.cairo +++ b/src/contracts/isms/multisig/messageid_multisig_ism.cairo @@ -2,24 +2,27 @@ pub mod messageid_multisig_ism { use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; use core::ecdsa::check_ecdsa_signature; - use starknet::eth_signature::is_eth_signature_valid; - use starknet::EthAddress; + use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::CheckpointLib; use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; use hyperlane_starknet::interfaces::{ - IMultisigIsm, IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, - ModuleType, IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, + IMultisigIsm, IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, ModuleType, + IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, + IInterchainSecurityModuleDispatcherTrait, }; - use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::CheckpointLib; - use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; use starknet::ContractAddress; - use starknet::secp256_trait::{Signature,signature_from_vrs}; + use starknet::EthAddress; + use starknet::eth_signature::is_eth_signature_valid; + use starknet::secp256_trait::{Signature, signature_from_vrs}; #[storage] struct Storage {} mod Errors { pub const NO_MULTISIG_THRESHOLD_FOR_MESSAGE: felt252 = 'No MultisigISM treshold present'; pub const VERIFICATION_FAILED_THRESHOLD_NOT_REACHED: felt252 = 'Verify failed, < threshold'; + pub const EMPTY_METADATA: felt252 = 'Empty metadata'; } + #[abi(embed_v0)] impl IMessageidMultisigIsmImpl of IInterchainSecurityModule { fn module_type(self: @ContractState) -> ModuleType { ModuleType::MESSAGE_ID_MULTISIG(starknet::get_contract_address()) @@ -27,10 +30,11 @@ pub mod messageid_multisig_ism { fn verify( self: @ContractState, - _metadata: Span, + _metadata: Bytes, _message: Message, _validator_configuration: ContractAddress ) -> bool { + assert(_metadata.clone().data().len()>0, Errors::EMPTY_METADATA); let digest = digest(_metadata.clone(), _message.clone()); let validator_configuration = IMultisigIsmDispatcher { contract_address: _validator_configuration @@ -56,8 +60,10 @@ pub mod messageid_multisig_ism { if (cur_idx == validators.len()) { break false; } - let signer = *validators.at(cur_idx).public_key; - if bool_is_eth_signature_valid(digest.into(), signature, signer.try_into().unwrap()) { + let signer = *validators.at(cur_idx).address; + if bool_is_eth_signature_valid( + digest.into(), signature, signer.try_into().unwrap() + ) { // we found a match break true; } @@ -81,22 +87,33 @@ pub mod messageid_multisig_ism { } } - fn digest(_metadata: Span, _message: Message) -> felt252 { - let origin_merkle_tree_hook = MessageIdIsmMetadata::origin_merkle_tree_hook(_metadata.clone()); + fn digest(_metadata: Bytes, _message: Message) -> felt252 { + let origin_merkle_tree_hook = MessageIdIsmMetadata::origin_merkle_tree_hook( + _metadata.clone() + ); let root = MessageIdIsmMetadata::root(_metadata.clone()); - let index = MessageIdIsmMetadata::index(_metadata.clone()); - CheckpointLib::digest(_message.origin,origin_merkle_tree_hook.into(), root.into(), index, MessageTrait::format_message(_message)).try_into().unwrap() + let index = MessageIdIsmMetadata::index(_metadata.clone()); + CheckpointLib::digest( + _message.origin, + origin_merkle_tree_hook.into(), + root.into(), + index, + MessageTrait::format_message(_message) + ) + .try_into() + .unwrap() } - fn get_signature_at(_metadata: Span, _index: u32) -> Signature { - let (v,r,s) = MessageIdIsmMetadata::signature_at(_metadata, _index); - signature_from_vrs(v.into(),r,s) - + fn get_signature_at(_metadata: Bytes, _index: u32) -> Signature { + let (v, r, s) = MessageIdIsmMetadata::signature_at(_metadata, _index); + signature_from_vrs(v.into(), r, s) } - fn bool_is_eth_signature_valid(msg_hash: u256, signature: Signature, signer: EthAddress) -> bool { + fn bool_is_eth_signature_valid( + msg_hash: u256, signature: Signature, signer: EthAddress + ) -> bool { match is_eth_signature_valid(msg_hash, signature, signer) { - Result::Ok(()) => true, + Result::Ok(()) => true, Result::Err(_) => false } } diff --git a/src/contracts/isms/multisig/multisig_ism.cairo b/src/contracts/isms/multisig/multisig_ism.cairo index 39071a3..6acdc77 100644 --- a/src/contracts/isms/multisig/multisig_ism.cairo +++ b/src/contracts/isms/multisig/multisig_ism.cairo @@ -2,11 +2,10 @@ pub mod multisig_ism { use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; use hyperlane_starknet::interfaces::IMultisigIsm; - use starknet::{ContractAddress, contract_address_const}; - use starknet::eth_address::EthAddress; - use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use starknet::eth_address::EthAddress; + use starknet::{ContractAddress, contract_address_const}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); @@ -16,10 +15,10 @@ pub mod multisig_ism { impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; type Index = felt252; - #[derive(Serde, Copy, starknet::Store, Drop, partialEq)] + #[derive(Serde, Copy, starknet::Store, Drop, PartialEq)] pub struct ValidatorInformations { pub address: EthAddress, - pub public_key: felt252 + pub public_key: u256 } #[storage] @@ -31,7 +30,7 @@ pub mod multisig_ism { #[substorage(v0)] upgradeable: UpgradeableComponent::Storage, } - + #[event] #[derive(Drop, starknet::Event)] pub enum Event { @@ -41,6 +40,10 @@ pub mod multisig_ism { UpgradeableEvent: UpgradeableComponent::Event, } + #[constructor] + fn constructor(ref self: ContractState, _owner: ContractAddress) { + self.ownable.initializer(_owner); + } mod Errors { @@ -71,7 +74,7 @@ pub mod multisig_ism { validator.address != 0.try_into().unwrap(), Errors::VALIDATOR_ADDRESS_CANNOT_BE_NULL ); - assert(validator.public_key != 0, Errors::VALIDATOR_PUBLIC_KEY_CANNOT_BE_NULL); + // assert(validator.public_key != 0, Errors::VALIDATOR_PUBLIC_KEY_CANNOT_BE_NULL); self.validators.write(cur_idx.into(), validator); cur_idx += 1; } @@ -97,7 +100,7 @@ pub mod multisig_ism { let mut cur_idx = 0; loop { let validator = self.validators.read(cur_idx); - if (validator.address ==0.try_into().unwrap()) { + if (validator.address == 0.try_into().unwrap()) { break (); } validators.append(validator); diff --git a/src/contracts/isms/multisig/validator_annonce.cairo b/src/contracts/isms/multisig/validator_annonce.cairo new file mode 100644 index 0000000..f3dc425 --- /dev/null +++ b/src/contracts/isms/multisig/validator_annonce.cairo @@ -0,0 +1,191 @@ +#[starknet::contract] +pub mod validator_annonce { + use alexandria_bytes::{Bytes, BytesTrait}; + use core::keccak::keccak_u256s_be_inputs; + use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::{ + HYPERLANE_ANNOUNCEMENT, ETH_SIGNED_MESSAGE + }; + use hyperlane_starknet::interfaces::IValidatorAnnounce; + use hyperlane_starknet::interfaces::{IMailboxClientDispatcher, IMailboxClientDispatcherTrait}; + use hyperlane_starknet::utils::keccak256::reverse_endianness; + use hyperlane_starknet::utils::store_arrays::StoreFelt252Array; + + use starknet::ContractAddress; + use starknet::EthAddress; + use starknet::eth_signature::is_eth_signature_valid; + use starknet::secp256_trait::{Signature, signature_from_vrs}; + + #[storage] + struct Storage { + mailboxclient: ContractAddress, + storage_location: LegacyMap::>, + replay_protection: LegacyMap::, + validators: LegacyMap::, + } + + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + ValidatorAnnouncement: ValidatorAnnouncement + } + + #[derive(starknet::Event, Drop)] + pub struct ValidatorAnnouncement { + pub validator: EthAddress, + pub storage_location: felt252 + } + + pub mod Errors { + pub const REPLAY_PROTECTION_ERROR: felt252 = 'Announce already occured'; + pub const WRONG_SIGNER: felt252 = 'Wrong signer'; + } + + #[constructor] + fn constructor(ref self: ContractState, _mailbox_client: ContractAddress) { + self.mailboxclient.write(_mailbox_client); + } + + #[abi(embed_v0)] + impl IValidatorAnnonceImpl of IValidatorAnnounce { + fn announce( + ref self: ContractState, + _validator: EthAddress, + _storage_location: felt252, + _signature: Bytes + ) -> bool { + let felt252_validator: felt252 = _validator.into(); + let mut input: Array = array![ + felt252_validator.into(), _storage_location.into(), + ]; + let replay_id = keccak_hash(input.span()); + assert(!self.replay_protection.read(replay_id), Errors::REPLAY_PROTECTION_ERROR); + let announcement_digest = self.get_announcement_digest(_storage_location); + let signature: Signature = convert_to_signature(_signature); + assert( + bool_is_eth_signature_valid(announcement_digest, signature, _validator), + Errors::WRONG_SIGNER + ); + match find_validators_index(@self, _validator) { + Option::Some(_) => {}, + Option::None(()) => { + let last_validator = find_last_validator(@self); + self.validators.write(last_validator, _validator); + } + }; + let mut storage_locations = self.storage_location.read(_validator); + storage_locations.append(_storage_location); + self + .emit( + ValidatorAnnouncement { + validator: _validator, storage_location: _storage_location + } + ); + true + } + + fn get_announced_storage_locations( + self: @ContractState, mut _validators: Span + ) -> Span> { + let mut metadata = array![]; + loop { + match _validators.pop_front() { + Option::Some(validator) => { + let validator_metadata = self.storage_location.read(*validator); + metadata.append(validator_metadata.span()) + }, + Option::None(()) => { break (); } + } + }; + metadata.span() + } + + fn get_announced_validators(self: @ContractState) -> Span { + build_validators_array(self) + } + fn get_announcement_digest(self: @ContractState, _storage_location: felt252) -> u256 { + let domain_hash = domain_hash(self); + let mut input: Array = array![ + ETH_SIGNED_MESSAGE.into(), domain_hash.into(), _storage_location.into(), + ]; + let hash = keccak_u256s_be_inputs(input.span()); + reverse_endianness(hash) + } + } + + + fn convert_to_signature(_signature: Bytes) -> Signature { + let (_, r) = _signature.read_u256(0); + let (_, s) = _signature.read_u256(32); + let (_, v) = _signature.read_u256(64); + signature_from_vrs(v.try_into().unwrap(), r, s) + } + fn keccak_hash(_input: Span) -> u256 { + let hash = keccak_u256s_be_inputs(_input); + reverse_endianness(hash) + } + + + fn domain_hash(self: @ContractState) -> u256 { + let mailboxclient = IMailboxClientDispatcher { + contract_address: self.mailboxclient.read() + }; + let mailboxcient_address: felt252 = self.mailboxclient.read().try_into().unwrap(); + let mut input: Array = array![ + mailboxclient.get_local_domain().into(), + mailboxcient_address.try_into().unwrap(), + HYPERLANE_ANNOUNCEMENT.into() + ]; + let hash = keccak_u256s_be_inputs(input.span()); + reverse_endianness(hash) + } + + + fn bool_is_eth_signature_valid( + msg_hash: u256, signature: Signature, signer: EthAddress + ) -> bool { + match is_eth_signature_valid(msg_hash, signature, signer) { + Result::Ok(()) => true, + Result::Err(_) => false + } + } + + fn find_validators_index(self: @ContractState, _validator: EthAddress) -> Option { + let mut current_validator: EthAddress = 0.try_into().unwrap(); + loop { + let next_validator = self.validators.read(current_validator); + if next_validator == _validator { + break Option::Some(current_validator); + } else if next_validator == 0.try_into().unwrap() { + break Option::None(()); + } + current_validator = next_validator; + } + } + + fn find_last_validator(self: @ContractState) -> EthAddress { + let mut current_validator = self.validators.read(0.try_into().unwrap()); + loop { + let next_validator = self.validators.read(current_validator); + if next_validator == 0.try_into().unwrap() { + break current_validator; + } + current_validator = next_validator; + } + } + + fn build_validators_array(self: @ContractState) -> Span { + let mut index = 0.try_into().unwrap(); + let mut validators = array![]; + loop { + let validator = self.validators.read(index); + if (validator == 0.try_into().unwrap()) { + break (); + } + validators.append(validator); + index = validator; + }; + + validators.span() + } +} diff --git a/src/contracts/isms/routing/domain_routing_ism.cairo b/src/contracts/isms/routing/domain_routing_ism.cairo index c324f04..146d291 100644 --- a/src/contracts/isms/routing/domain_routing_ism.cairo +++ b/src/contracts/isms/routing/domain_routing_ism.cairo @@ -1,11 +1,11 @@ #[starknet::contract] pub mod domain_routing_ism { + use core::panic_with_felt252; use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; use hyperlane_starknet::interfaces::IDomainRoutingIsm; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; - use core::panic_with_felt252; use starknet::{ContractAddress, contract_address_const}; @@ -41,7 +41,7 @@ pub mod domain_routing_ism { pub const LENGTH_MISMATCH: felt252 = 'Length mismatch'; pub const ORIGIN_NOT_FOUND: felt252 = 'Origin not found'; pub const MODULE_CANNOT_BE_ZERO: felt252 = 'Module cannot be zero'; - pub const DOMAIN_NOT_FOUND : felt252= 'Domain not found'; + pub const DOMAIN_NOT_FOUND: felt252 = 'Domain not found'; } #[constructor] @@ -49,6 +49,7 @@ pub mod domain_routing_ism { self.ownable.initializer(_owner); } + #[abi(embed_v0)] impl IDomainRoutingIsmImpl of IDomainRoutingIsm { fn initialize( ref self: ContractState, _domains: Span, _modules: Span @@ -126,8 +127,8 @@ pub mod domain_routing_ism { } fn _remove(ref self: ContractState, _domain: u32) { - let domain_index = match find_domain_index(@self,_domain){ - Option::Some(index) => index, + let domain_index = match find_domain_index(@self, _domain) { + Option::Some(index) => index, Option::None(()) => { panic_with_felt252(Errors::DOMAIN_NOT_FOUND); 0 @@ -138,8 +139,8 @@ pub mod domain_routing_ism { } fn _set(ref self: ContractState, _domain: u32, _module: ContractAddress) { - match find_domain_index(@self,_domain) { - Option::Some(_) =>{} , + match find_domain_index(@self, _domain) { + Option::Some(_) => {}, Option::None(()) => { let latest_domain = find_last_domain(@self); self.domains.write(latest_domain, _domain); diff --git a/src/contracts/libs/checkpoint_lib.cairo b/src/contracts/libs/checkpoint_lib.cairo index 6d8fe7e..e44f1bd 100644 --- a/src/contracts/libs/checkpoint_lib.cairo +++ b/src/contracts/libs/checkpoint_lib.cairo @@ -1,45 +1,50 @@ - - pub mod checkpoint_lib { use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; use core::keccak::keccak_u256s_be_inputs; use hyperlane_starknet::contracts::libs::message::Message; use hyperlane_starknet::utils::keccak256::reverse_endianness; - - - + pub trait CheckpointLib { - fn digest(_origin: u32, _origin_merkle_tree_hook: u256, _checkpoint_root: u256, _checkpoint_index: u32, _message_id: u256) -> u256; - fn domain_hash(_origin: u32, _origin_merkle_tree_hook: u256) -> u256 ; + fn digest( + _origin: u32, + _origin_merkle_tree_hook: u256, + _checkpoint_root: u256, + _checkpoint_index: u32, + _message_id: u256 + ) -> u256; + fn domain_hash(_origin: u32, _origin_merkle_tree_hook: u256) -> u256; } const HYPERLANE: felt252 = 'HYPERLANE'; - const ETH_SIGNED_MESSAGE: felt252 = '\x19Ethereum Signed Message:\n'; + pub const ETH_SIGNED_MESSAGE: felt252 = '\x19Ethereum Signed Message:\n'; + pub const HYPERLANE_ANNOUNCEMENT: felt252 = 'HYPERLANE_ANNOUNCEMENT'; impl CheckpointLibImpl of CheckpointLib { - fn digest(_origin: u32, _origin_merkle_tree_hook: u256, _checkpoint_root: u256, _checkpoint_index: u32, _message_id: u256) -> u256 { + fn digest( + _origin: u32, + _origin_merkle_tree_hook: u256, + _checkpoint_root: u256, + _checkpoint_index: u32, + _message_id: u256 + ) -> u256 { let domain_hash = CheckpointLib::domain_hash(_origin, _origin_merkle_tree_hook); let mut input: Array = array![ - ETH_SIGNED_MESSAGE.into(), - domain_hash.into(), - _checkpoint_root.into(), - _checkpoint_index.into(), - _message_id.into(), - ]; + ETH_SIGNED_MESSAGE.into(), + domain_hash.into(), + _checkpoint_root.into(), + _checkpoint_index.into(), + _message_id.into(), + ]; let hash = keccak_u256s_be_inputs(input.span()); reverse_endianness(hash) - } - + fn domain_hash(_origin: u32, _origin_merkle_tree_hook: u256) -> u256 { - let mut input : Array = array![ - _origin.into(), - _origin_merkle_tree_hook.into(), - HYPERLANE.into() + let mut input: Array = array![ + _origin.into(), _origin_merkle_tree_hook.into(), HYPERLANE.into() ]; let hash = keccak_u256s_be_inputs(input.span()); reverse_endianness(hash) - } } -} \ No newline at end of file +} diff --git a/src/contracts/libs/multisig/message_id_ism_metadata.cairo b/src/contracts/libs/multisig/message_id_ism_metadata.cairo index bcb64b4..ce94986 100644 --- a/src/contracts/libs/multisig/message_id_ism_metadata.cairo +++ b/src/contracts/libs/multisig/message_id_ism_metadata.cairo @@ -2,48 +2,38 @@ pub mod message_id_ism_metadata { use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; - pub trait MessageIdIsmMetadata { - fn origin_merkle_tree_hook(_metadata: Span) -> u256; - fn root(_metadata: Span) -> u256; - fn index(_metadata: Span) -> u32; - fn signature_at(_metadata: Span, _index : u32) -> (u8, u256, u256); - } - const ORIGIN_MERKLE_TREE_OFFSET: u32 = 0; - const MERKLE_ROOT_OFFSET : u32= 32; - const MERKLE_INDEX_OFFSET : u32= 64; - const SIGNATURES_OFFSET : u32= 68; - const SIGNATURE_LENGTH : u32= 65; - - impl MessagIdIsmMetadataImpl of MessageIdIsmMetadata { - - fn origin_merkle_tree_hook(_metadata:Span) -> u256 { - let merkle_tree_hook_bytes = _metadata[0]; - let (_, felt) = merkle_tree_hook_bytes.read_u256(1); - felt - } - - fn root(_metadata: Span) -> u256 { - let root_bytes = _metadata[1]; - let (_, felt) = root_bytes.read_u256(1); - felt - } - - fn index(_metadata: Span) -> u32 { - let index_bytes = _metadata[2]; - let (_, felt) = index_bytes.read_u32(0); - felt + pub trait MessageIdIsmMetadata { + fn origin_merkle_tree_hook(_metadata: Bytes) -> u256; + fn root(_metadata: Bytes) -> u256; + fn index(_metadata: Bytes) -> u32; + fn signature_at(_metadata: Bytes, _index: u32) -> (u8, u256, u256); } - - fn signature_at(_metadata:Span, _index: u32) -> (u8, u256, u256) { - // the first signer index is 0 - let signature_initial_index = 5; - let (_,r) = _metadata[signature_initial_index + 2*_index].read_u256(1); - let (_,s) = _metadata[signature_initial_index + 2*_index + 1].read_u256(1); - let (_,v) = _metadata[signature_initial_index + 2*_index + 1].read_u8(0); - (v,r,s) + pub const ORIGIN_MERKLE_TREE_HOOK_OFFSET: u32 = 0; + pub const ROOT_OFFSET: u32 = 32; + pub const INDEX_OFFSET: u32 = 64; + pub const SIGNATURE_OFFSET: u32 = 96; + impl MessagIdIsmMetadataImpl of MessageIdIsmMetadata { + fn origin_merkle_tree_hook(_metadata: Bytes) -> u256 { + let (_, felt) = _metadata.read_u256(ORIGIN_MERKLE_TREE_HOOK_OFFSET); + felt + } + + fn root(_metadata: Bytes) -> u256 { + let (_, felt) = _metadata.read_u256(ROOT_OFFSET); + felt + } + + fn index(_metadata: Bytes) -> u32 { + let (_, felt) = _metadata.read_u32(INDEX_OFFSET); + felt + } + + fn signature_at(_metadata: Bytes, _index: u32) -> (u8, u256, u256) { + // the first signer index is 0 + let (_, r) = _metadata.read_u256(SIGNATURE_OFFSET + 96 * _index); + let (_, s) = _metadata.read_u256(SIGNATURE_OFFSET + 96 * _index ); + let (_, v) = _metadata.read_u8(SIGNATURE_OFFSET + 96 * _index + 32); + (v, r, s) + } } - - - } - -} \ No newline at end of file +} diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 19549d9..4f0c6ba 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -1,8 +1,9 @@ use alexandria_bytes::Bytes; +use core::array::ArrayTrait; use hyperlane_starknet::contracts::isms::multisig::multisig_ism::multisig_ism::ValidatorInformations; use hyperlane_starknet::contracts::libs::message::Message; use starknet::ContractAddress; - +use starknet::EthAddress; #[derive(Serde)] pub enum Types { UNUSED, @@ -19,7 +20,7 @@ pub enum Types { } -#[derive(Serde)] +#[derive(Serde, Drop, PartialEq)] pub enum ModuleType { UNUSED: ContractAddress, ROUTING: ContractAddress, @@ -106,7 +107,12 @@ pub trait IInterchainSecurityModule { /// * `_metadata` - Off-chain metadata provided by a relayer, specific to the security model encoded by /// the module (e.g. validator signatures) /// * `_message` - Hyperlane encoded interchain message - fn verify(self: @TContractState, _metadata: Span, _message: Message,_validator_configuration: ContractAddress) -> bool; + fn verify( + self: @TContractState, + _metadata: Bytes, + _message: Message, + _validator_configuration: ContractAddress + ) -> bool; } #[starknet::interface] @@ -151,6 +157,12 @@ pub trait IMailboxClient { _interchain_security_module: ContractAddress, ); + fn get_hook(self: @TContractState) -> ContractAddress; + + fn get_local_domain(self: @TContractState) -> u32; + + fn get_interchain_security_module(self: @TContractState) -> ContractAddress; + fn _is_latest_dispatched(self: @TContractState, _id: u256) -> bool; fn _is_delivered(self: @TContractState, _id: u256) -> bool; @@ -248,3 +260,22 @@ pub trait IDomainRoutingIsm { fn route(self: @TContractState, _message: Message) -> ContractAddress; } + + +#[starknet::interface] +pub trait IValidatorAnnounce { + fn get_announced_validators(self: @TContractState) -> Span; + + fn get_announced_storage_locations( + self: @TContractState, _validators: Span + ) -> Span>; + + fn announce( + ref self: TContractState, + _validator: EthAddress, + _storage_location: felt252, + _signature: Bytes + ) -> bool; + + fn get_announcement_digest(self: @TContractState, _storage_location: felt252) -> u256; +} diff --git a/src/lib.cairo b/src/lib.cairo index a4f69d7..e3735a4 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -2,11 +2,11 @@ mod interfaces; mod contracts { pub mod mailbox; pub mod libs { + pub mod checkpoint_lib; + pub mod message; pub mod multisig { pub mod message_id_ism_metadata; } - pub mod message; - pub mod checkpoint_lib; } pub mod client { pub mod mailboxclient; @@ -20,6 +20,7 @@ mod contracts { pub mod merkleroot_multisig_ism; pub mod messageid_multisig_ism; pub mod multisig_ism; + pub mod validator_annonce; } pub mod routing { pub mod domain_routing_ism; @@ -28,11 +29,12 @@ mod contracts { } mod utils { pub mod keccak256; + pub mod store_arrays; } #[cfg(test)] mod tests { pub mod setup; pub mod test_mailbox; - pub mod test_message_id_ism; + pub mod test_multisig; } diff --git a/src/tests/test_multisig.cairo b/src/tests/test_multisig.cairo new file mode 100644 index 0000000..68786d7 --- /dev/null +++ b/src/tests/test_multisig.cairo @@ -0,0 +1,196 @@ +use alexandria_bytes::{Bytes, BytesTrait}; +use alexandria_data_structures::array_ext::ArrayTraitExt; +use core::array::ArrayTrait; +use core::array::SpanTrait; +use hyperlane_starknet::contracts::isms::multisig::multisig_ism::multisig_ism::ValidatorInformations; +use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; +use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; +use hyperlane_starknet::contracts::mailbox::mailbox; +use hyperlane_starknet::interfaces::IMessageRecipientDispatcherTrait; +use hyperlane_starknet::interfaces::IMultisigIsmDispatcherTrait; +use hyperlane_starknet::interfaces::{ + IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, HYPERLANE_VERSION, ModuleType, + IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait +}; +use hyperlane_starknet::tests::setup::{ + setup, mock_setup, setup_messageid_multisig_ism, setup_multisig_ism, OWNER, NEW_OWNER, + VALIDATOR_ADDRESS, VALIDATOR_PUBLIC_KEY,setup_validator_announce, get_message_and_signature, LOCAL_DOMAIN, DESTINATION_DOMAIN,RECIPIENT_ADDRESS +}; +use openzeppelin::access::ownable::OwnableComponent; +use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; +use snforge_std::cheatcodes::events::EventAssertions; +use snforge_std::{start_prank, CheatTarget, stop_prank}; +use starknet::eth_address::EthAddress; +#[test] +fn test_set_validators() { + let new_validators = array![ + ValidatorInformations { address: VALIDATOR_ADDRESS(), public_key: VALIDATOR_PUBLIC_KEY() } + ] + .span(); + let validators = setup_multisig_ism(); + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + validators.set_validators(new_validators); + let validators_span = validators.get_validators(); + assert(validators_span == new_validators, 'wrong validator address def'); +} + + +#[test] +fn test_set_threshold() { + let new_threshold = 3; + let validators = setup_multisig_ism(); + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + validators.set_threshold(new_threshold); + assert(validators.get_threshold() == new_threshold, 'wrong validator threshold'); +} + + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_set_validators_fails_if_caller_not_owner() { + let new_validators = array![ + ValidatorInformations { address: VALIDATOR_ADDRESS(), public_key: VALIDATOR_PUBLIC_KEY() } + ] + .span(); + let validators = setup_multisig_ism(); + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); + validators.set_validators(new_validators); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_set_threshold_fails_if_caller_not_owner() { + let new_threshold = 3; + let validators = setup_multisig_ism(); + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); + validators.set_threshold(new_threshold); +} + + +#[test] +fn test_message_id_ism_metadata() { + let origin_merkle_tree_hook = array![ + // origin_merkle_tree_hook + 0x01020304050607080910111213141516, + 0x16151413121110090807060504030201]; + let root = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080920111213141516, + ]; + let index = array![0x00000013000000000000000000000000]; + let index_u32 = 0x13; + let signature_1 = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080920111213141516, + 0x01020304050607080910000000000000, + 0x02010304050607080910111213141516, + 0x00000003000000000000000000000000 + ]; + let signature_2 = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000, + 0x00000002000000000000000000000000 + ]; + let signature_3 = array![ + 0x01020304050607080910111213141516, + 0x13092450000011115450564500700000, + 0x01020304050607080910000000000000, + 0x01020304050607080910111213141516, + 0x00000002000000000000000000000000 + ]; + let signature_1_v = 0x3; + let signature_2_v = 0x2; + let signature_3_v = 0x2; + let mut metadata = origin_merkle_tree_hook.concat(@root); + metadata = metadata.concat(@index); + metadata = metadata.concat(@signature_1); + metadata = metadata.concat(@signature_2); + metadata = metadata.concat(@signature_3); + let bytes_metadata = BytesTrait::new(496, metadata); + assert( + MessageIdIsmMetadata::origin_merkle_tree_hook(bytes_metadata.clone()) == u256 {low: *origin_merkle_tree_hook.at(1), high: *origin_merkle_tree_hook.at(0)}, + 'wrong merkle tree hook' + ); + assert( + MessageIdIsmMetadata::root(bytes_metadata.clone()) == u256 {low: *root.at(1), high: *root.at(0)}, + 'wrong root' + ); + assert( + MessageIdIsmMetadata::index(bytes_metadata.clone()) == index_u32, + 'wrong index' + ); + let (test, test_1, test_2) = MessageIdIsmMetadata::signature_at(bytes_metadata.clone(),1); + println!("aight {}, {}, {}",test, test_1, test_2); + assert( + MessageIdIsmMetadata::signature_at(bytes_metadata.clone(),0) == (signature_1_v, u256{low:*signature_1.at(1), high: *signature_1.at(0) }, u256 {low: *signature_1.at(3), high: *signature_1.at(2)}), + 'wrong signature 1' + ); + assert( + MessageIdIsmMetadata::signature_at(bytes_metadata.clone(),1) == (signature_2_v, u256{low:*signature_2.at(1), high: *signature_2.at(0) }, u256 {low: *signature_2.at(3), high: *signature_2.at(2)}), + 'wrong signature 2' + ); + assert( + MessageIdIsmMetadata::signature_at(bytes_metadata.clone(),2) == (signature_3_v, u256{low:*signature_3.at(1), high: *signature_3.at(0) }, u256 {low: *signature_3.at(3), high: *signature_3.at(2)}), + 'wrong signature 3' + ); +} + + +#[test] +fn test_message_id_multisig_module_type() { + let messageid = setup_messageid_multisig_ism(); + assert( + messageid.module_type() == ModuleType::MESSAGE_ID_MULTISIG(messageid.contract_address), + 'Wrong module type' + ); +} + + +#[test] +fn test_message_id_multisig_verify() { + let array = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000 + ]; + let message_body = BytesTrait::new(42, array); + let message = Message { + version: HYPERLANE_VERSION, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: OWNER(), + destination: DESTINATION_DOMAIN, + recipient: RECIPIENT_ADDRESS(), + body: message_body.clone() + }; + let messageid = setup_messageid_multisig_ism(); + let (msg_hash, signature, public_key_x, public_key_y, eth_address) = get_message_and_signature(false); + let validators = setup_multisig_ism(); + let new_validators = array![ + ValidatorInformations { address: eth_address, public_key: 0.try_into().unwrap()} + ]; + let metadata = array![ + 0x01020304050607080910111213141516, + 0x16151413121110090807060504030201, + 0x01020304050607080910111213141516, + 0x01020304050607080920111213141516, + 0x00000010000000000000000000000000, + signature.r.high, + signature.r.low , + signature.s.high, + signature.s.low, + 0x00000010000000000000000000000000 + ]; + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + validators.set_validators(new_validators.span()); + validators.set_threshold(1); + let bytes_metadata = BytesTrait::new(42, metadata); + assert(messageid.verify(bytes_metadata, message,validators.contract_address) == true, 'verification failed'); +} \ No newline at end of file diff --git a/src/utils/store_arrays.cairo b/src/utils/store_arrays.cairo new file mode 100644 index 0000000..e8eb6d0 --- /dev/null +++ b/src/utils/store_arrays.cairo @@ -0,0 +1,71 @@ +use starknet::storage_access::{Store, StorageBaseAddress,}; +// ************************************************************************* +// IMPORTS +// ************************************************************************* + +// Code from Satoru +// Core lib imports. +use starknet::{ContractAddress, SyscallResult,}; + + +pub impl StoreFelt252Array of Store> { + fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult> { + StoreFelt252Array::read_at_offset(address_domain, base, 0) + } + + fn write( + address_domain: u32, base: StorageBaseAddress, value: Array + ) -> SyscallResult<()> { + StoreFelt252Array::write_at_offset(address_domain, base, 0, value) + } + + fn read_at_offset( + address_domain: u32, base: StorageBaseAddress, mut offset: u8 + ) -> SyscallResult> { + let mut arr: Array = array![]; + + // Read the stored array's length. If the length is superior to 255, the read will fail. + let len: u8 = Store::::read_at_offset(address_domain, base, offset).unwrap(); + offset += 1; + + // Sequentially read all stored elements and append them to the array. + let exit = len + offset; + loop { + if offset >= exit { + break; + } + + let value = Store::::read_at_offset(address_domain, base, offset).unwrap(); + arr.append(value); + offset += Store::::size(); + }; + + // Return the array. + Result::Ok(arr) + } + + fn write_at_offset( + address_domain: u32, base: StorageBaseAddress, mut offset: u8, mut value: Array + ) -> SyscallResult<()> { + // // Store the length of the array in the first storage slot. + let len: u8 = value.len().try_into().expect('Storage - Span too large'); + Store::::write_at_offset(address_domain, base, offset, len); + offset += 1; + + // Store the array elements sequentially + loop { + match value.pop_front() { + Option::Some(element) => { + Store::::write_at_offset(address_domain, base, offset, element) + .unwrap(); + offset += Store::::size(); + }, + Option::None(_) => { break Result::Ok(()); } + }; + } + } + + fn size() -> u8 { + 1 + } +} From ff168b35fd3e91b9bd3e85e496d5746e77e59f38 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 13 May 2024 15:00:54 +0200 Subject: [PATCH 10/26] feat: mock ism --- src/contracts/mocks/ism.cairo | 27 +++++++++++++++++++++++++++ src/lib.cairo | 1 + 2 files changed, 28 insertions(+) create mode 100644 src/contracts/mocks/ism.cairo diff --git a/src/contracts/mocks/ism.cairo b/src/contracts/mocks/ism.cairo new file mode 100644 index 0000000..ba678cc --- /dev/null +++ b/src/contracts/mocks/ism.cairo @@ -0,0 +1,27 @@ +#[starknet::contract] +pub mod ism { + use starknet::ContractAddress; + use hyperlane_starknet::interfaces::{IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, + IInterchainSecurityModuleDispatcherTrait, ModuleType}; + use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + + use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; + + #[storage] + struct Storage {} + #[abi(embed_v0)] + impl IMessageidMultisigIsmImpl of IInterchainSecurityModule { + fn module_type(self: @ContractState) -> ModuleType { + ModuleType::MESSAGE_ID_MULTISIG(starknet::get_contract_address()) + } + + fn verify( + self: @ContractState, + _metadata: Bytes, + _message: Message, + _validator_configuration: ContractAddress + ) -> bool { + true + } + } +} \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo index e3735a4..6838a97 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -14,6 +14,7 @@ mod contracts { } pub mod mocks { pub mod message_recipient; + pub mod ism; } pub mod isms { pub mod multisig { From ad56529501748b95ae944b735beb565c5f960225 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 13 May 2024 15:10:56 +0200 Subject: [PATCH 11/26] fix: typo --- src/contracts/isms/multisig/messageid_multisig_ism.cairo | 2 +- .../{validator_annonce.cairo => validator_announce.cairo} | 2 +- src/lib.cairo | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/contracts/isms/multisig/{validator_annonce.cairo => validator_announce.cairo} (99%) diff --git a/src/contracts/isms/multisig/messageid_multisig_ism.cairo b/src/contracts/isms/multisig/messageid_multisig_ism.cairo index 1d4269c..3eb1478 100644 --- a/src/contracts/isms/multisig/messageid_multisig_ism.cairo +++ b/src/contracts/isms/multisig/messageid_multisig_ism.cairo @@ -32,7 +32,7 @@ pub mod messageid_multisig_ism { self: @ContractState, _metadata: Bytes, _message: Message, - _validator_configuration: ContractAddress + _validator_configuration: ContractAddress, ) -> bool { assert(_metadata.clone().data().len()>0, Errors::EMPTY_METADATA); let digest = digest(_metadata.clone(), _message.clone()); diff --git a/src/contracts/isms/multisig/validator_annonce.cairo b/src/contracts/isms/multisig/validator_announce.cairo similarity index 99% rename from src/contracts/isms/multisig/validator_annonce.cairo rename to src/contracts/isms/multisig/validator_announce.cairo index f3dc425..d488571 100644 --- a/src/contracts/isms/multisig/validator_annonce.cairo +++ b/src/contracts/isms/multisig/validator_announce.cairo @@ -1,5 +1,5 @@ #[starknet::contract] -pub mod validator_annonce { +pub mod validator_announce { use alexandria_bytes::{Bytes, BytesTrait}; use core::keccak::keccak_u256s_be_inputs; use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::{ diff --git a/src/lib.cairo b/src/lib.cairo index 6838a97..470de08 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -21,7 +21,7 @@ mod contracts { pub mod merkleroot_multisig_ism; pub mod messageid_multisig_ism; pub mod multisig_ism; - pub mod validator_annonce; + pub mod validator_announce; } pub mod routing { pub mod domain_routing_ism; From 5a5c5a08113b7620ac27d3a24b0a53eee4805f22 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 13 May 2024 15:11:58 +0200 Subject: [PATCH 12/26] fix: fmt --- .../multisig/merkleroot_multisig_ism.cairo | 4 +- .../multisig/messageid_multisig_ism.cairo | 4 +- .../multisig/message_id_ism_metadata.cairo | 2 +- src/contracts/mocks/ism.cairo | 13 +-- src/lib.cairo | 2 +- src/tests/test_multisig.cairo | 80 ++++++++++++------- 6 files changed, 65 insertions(+), 40 deletions(-) diff --git a/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo b/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo index f9a65aa..2c75912 100644 --- a/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo +++ b/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo @@ -58,7 +58,9 @@ pub mod merkleroot_multisig_ism { break false; } let signer = *validators.at(cur_idx).address; - if check_ecdsa_signature(digest, signer.try_into().unwrap(), signature_r, signature_s) { + if check_ecdsa_signature( + digest, signer.try_into().unwrap(), signature_r, signature_s + ) { // we found a match break true; } diff --git a/src/contracts/isms/multisig/messageid_multisig_ism.cairo b/src/contracts/isms/multisig/messageid_multisig_ism.cairo index 3eb1478..31735c9 100644 --- a/src/contracts/isms/multisig/messageid_multisig_ism.cairo +++ b/src/contracts/isms/multisig/messageid_multisig_ism.cairo @@ -32,9 +32,9 @@ pub mod messageid_multisig_ism { self: @ContractState, _metadata: Bytes, _message: Message, - _validator_configuration: ContractAddress, + _validator_configuration: ContractAddress, ) -> bool { - assert(_metadata.clone().data().len()>0, Errors::EMPTY_METADATA); + assert(_metadata.clone().data().len() > 0, Errors::EMPTY_METADATA); let digest = digest(_metadata.clone(), _message.clone()); let validator_configuration = IMultisigIsmDispatcher { contract_address: _validator_configuration diff --git a/src/contracts/libs/multisig/message_id_ism_metadata.cairo b/src/contracts/libs/multisig/message_id_ism_metadata.cairo index ce94986..14d7d24 100644 --- a/src/contracts/libs/multisig/message_id_ism_metadata.cairo +++ b/src/contracts/libs/multisig/message_id_ism_metadata.cairo @@ -31,7 +31,7 @@ pub mod message_id_ism_metadata { fn signature_at(_metadata: Bytes, _index: u32) -> (u8, u256, u256) { // the first signer index is 0 let (_, r) = _metadata.read_u256(SIGNATURE_OFFSET + 96 * _index); - let (_, s) = _metadata.read_u256(SIGNATURE_OFFSET + 96 * _index ); + let (_, s) = _metadata.read_u256(SIGNATURE_OFFSET + 96 * _index); let (_, v) = _metadata.read_u8(SIGNATURE_OFFSET + 96 * _index + 32); (v, r, s) } diff --git a/src/contracts/mocks/ism.cairo b/src/contracts/mocks/ism.cairo index ba678cc..80b523a 100644 --- a/src/contracts/mocks/ism.cairo +++ b/src/contracts/mocks/ism.cairo @@ -1,11 +1,12 @@ #[starknet::contract] pub mod ism { - use starknet::ContractAddress; - use hyperlane_starknet::interfaces::{IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, - IInterchainSecurityModuleDispatcherTrait, ModuleType}; - use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; - use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; + use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + use hyperlane_starknet::interfaces::{ + IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, + IInterchainSecurityModuleDispatcherTrait, ModuleType + }; + use starknet::ContractAddress; #[storage] struct Storage {} @@ -24,4 +25,4 @@ pub mod ism { true } } -} \ No newline at end of file +} diff --git a/src/lib.cairo b/src/lib.cairo index 470de08..5f9ac2a 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -13,8 +13,8 @@ mod contracts { pub mod router; } pub mod mocks { - pub mod message_recipient; pub mod ism; + pub mod message_recipient; } pub mod isms { pub mod multisig { diff --git a/src/tests/test_multisig.cairo b/src/tests/test_multisig.cairo index 68786d7..bcc2353 100644 --- a/src/tests/test_multisig.cairo +++ b/src/tests/test_multisig.cairo @@ -14,7 +14,8 @@ use hyperlane_starknet::interfaces::{ }; use hyperlane_starknet::tests::setup::{ setup, mock_setup, setup_messageid_multisig_ism, setup_multisig_ism, OWNER, NEW_OWNER, - VALIDATOR_ADDRESS, VALIDATOR_PUBLIC_KEY,setup_validator_announce, get_message_and_signature, LOCAL_DOMAIN, DESTINATION_DOMAIN,RECIPIENT_ADDRESS + VALIDATOR_ADDRESS, VALIDATOR_PUBLIC_KEY, setup_validator_announce, get_message_and_signature, + LOCAL_DOMAIN, DESTINATION_DOMAIN, RECIPIENT_ADDRESS }; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; @@ -75,12 +76,9 @@ fn test_set_threshold_fails_if_caller_not_owner() { fn test_message_id_ism_metadata() { let origin_merkle_tree_hook = array![ // origin_merkle_tree_hook - 0x01020304050607080910111213141516, - 0x16151413121110090807060504030201]; - let root = array![ - 0x01020304050607080910111213141516, - 0x01020304050607080920111213141516, + 0x01020304050607080910111213141516, 0x16151413121110090807060504030201 ]; + let root = array![0x01020304050607080910111213141516, 0x01020304050607080920111213141516,]; let index = array![0x00000013000000000000000000000000]; let index_u32 = 0x13; let signature_1 = array![ @@ -114,29 +112,48 @@ fn test_message_id_ism_metadata() { metadata = metadata.concat(@signature_3); let bytes_metadata = BytesTrait::new(496, metadata); assert( - MessageIdIsmMetadata::origin_merkle_tree_hook(bytes_metadata.clone()) == u256 {low: *origin_merkle_tree_hook.at(1), high: *origin_merkle_tree_hook.at(0)}, + MessageIdIsmMetadata::origin_merkle_tree_hook( + bytes_metadata.clone() + ) == u256 { low: *origin_merkle_tree_hook.at(1), high: *origin_merkle_tree_hook.at(0) }, 'wrong merkle tree hook' ); assert( - MessageIdIsmMetadata::root(bytes_metadata.clone()) == u256 {low: *root.at(1), high: *root.at(0)}, + MessageIdIsmMetadata::root( + bytes_metadata.clone() + ) == u256 { low: *root.at(1), high: *root.at(0) }, 'wrong root' ); + assert(MessageIdIsmMetadata::index(bytes_metadata.clone()) == index_u32, 'wrong index'); + let (test, test_1, test_2) = MessageIdIsmMetadata::signature_at(bytes_metadata.clone(), 1); + println!("aight {}, {}, {}", test, test_1, test_2); assert( - MessageIdIsmMetadata::index(bytes_metadata.clone()) == index_u32, - 'wrong index' - ); - let (test, test_1, test_2) = MessageIdIsmMetadata::signature_at(bytes_metadata.clone(),1); - println!("aight {}, {}, {}",test, test_1, test_2); - assert( - MessageIdIsmMetadata::signature_at(bytes_metadata.clone(),0) == (signature_1_v, u256{low:*signature_1.at(1), high: *signature_1.at(0) }, u256 {low: *signature_1.at(3), high: *signature_1.at(2)}), + MessageIdIsmMetadata::signature_at( + bytes_metadata.clone(), 0 + ) == ( + signature_1_v, + u256 { low: *signature_1.at(1), high: *signature_1.at(0) }, + u256 { low: *signature_1.at(3), high: *signature_1.at(2) } + ), 'wrong signature 1' ); assert( - MessageIdIsmMetadata::signature_at(bytes_metadata.clone(),1) == (signature_2_v, u256{low:*signature_2.at(1), high: *signature_2.at(0) }, u256 {low: *signature_2.at(3), high: *signature_2.at(2)}), + MessageIdIsmMetadata::signature_at( + bytes_metadata.clone(), 1 + ) == ( + signature_2_v, + u256 { low: *signature_2.at(1), high: *signature_2.at(0) }, + u256 { low: *signature_2.at(3), high: *signature_2.at(2) } + ), 'wrong signature 2' ); assert( - MessageIdIsmMetadata::signature_at(bytes_metadata.clone(),2) == (signature_3_v, u256{low:*signature_3.at(1), high: *signature_3.at(0) }, u256 {low: *signature_3.at(3), high: *signature_3.at(2)}), + MessageIdIsmMetadata::signature_at( + bytes_metadata.clone(), 2 + ) == ( + signature_3_v, + u256 { low: *signature_3.at(1), high: *signature_3.at(0) }, + u256 { low: *signature_3.at(3), high: *signature_3.at(2) } + ), 'wrong signature 3' ); } @@ -170,27 +187,32 @@ fn test_message_id_multisig_verify() { body: message_body.clone() }; let messageid = setup_messageid_multisig_ism(); - let (msg_hash, signature, public_key_x, public_key_y, eth_address) = get_message_and_signature(false); + let (msg_hash, signature, public_key_x, public_key_y, eth_address) = get_message_and_signature( + false + ); let validators = setup_multisig_ism(); let new_validators = array![ - ValidatorInformations { address: eth_address, public_key: 0.try_into().unwrap()} + ValidatorInformations { address: eth_address, public_key: 0.try_into().unwrap() } ]; - let metadata = array![ + let metadata = array![ 0x01020304050607080910111213141516, - 0x16151413121110090807060504030201, - 0x01020304050607080910111213141516, - 0x01020304050607080920111213141516, - 0x00000010000000000000000000000000, + 0x16151413121110090807060504030201, + 0x01020304050607080910111213141516, + 0x01020304050607080920111213141516, + 0x00000010000000000000000000000000, signature.r.high, - signature.r.low , + signature.r.low, signature.s.high, - signature.s.low, + signature.s.low, 0x00000010000000000000000000000000 - ]; + ]; let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); validators.set_validators(new_validators.span()); validators.set_threshold(1); let bytes_metadata = BytesTrait::new(42, metadata); - assert(messageid.verify(bytes_metadata, message,validators.contract_address) == true, 'verification failed'); -} \ No newline at end of file + assert( + messageid.verify(bytes_metadata, message, validators.contract_address) == true, + 'verification failed' + ); +} From 060938f51fdf46a740fe2abcb391917dd1aeac5a Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 13 May 2024 15:14:43 +0200 Subject: [PATCH 13/26] fix: remove test_multisig --- src/tests/test_multisig.cairo | 218 ---------------------------------- 1 file changed, 218 deletions(-) delete mode 100644 src/tests/test_multisig.cairo diff --git a/src/tests/test_multisig.cairo b/src/tests/test_multisig.cairo deleted file mode 100644 index bcc2353..0000000 --- a/src/tests/test_multisig.cairo +++ /dev/null @@ -1,218 +0,0 @@ -use alexandria_bytes::{Bytes, BytesTrait}; -use alexandria_data_structures::array_ext::ArrayTraitExt; -use core::array::ArrayTrait; -use core::array::SpanTrait; -use hyperlane_starknet::contracts::isms::multisig::multisig_ism::multisig_ism::ValidatorInformations; -use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; -use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; -use hyperlane_starknet::contracts::mailbox::mailbox; -use hyperlane_starknet::interfaces::IMessageRecipientDispatcherTrait; -use hyperlane_starknet::interfaces::IMultisigIsmDispatcherTrait; -use hyperlane_starknet::interfaces::{ - IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, HYPERLANE_VERSION, ModuleType, - IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait -}; -use hyperlane_starknet::tests::setup::{ - setup, mock_setup, setup_messageid_multisig_ism, setup_multisig_ism, OWNER, NEW_OWNER, - VALIDATOR_ADDRESS, VALIDATOR_PUBLIC_KEY, setup_validator_announce, get_message_and_signature, - LOCAL_DOMAIN, DESTINATION_DOMAIN, RECIPIENT_ADDRESS -}; -use openzeppelin::access::ownable::OwnableComponent; -use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; -use snforge_std::cheatcodes::events::EventAssertions; -use snforge_std::{start_prank, CheatTarget, stop_prank}; -use starknet::eth_address::EthAddress; -#[test] -fn test_set_validators() { - let new_validators = array![ - ValidatorInformations { address: VALIDATOR_ADDRESS(), public_key: VALIDATOR_PUBLIC_KEY() } - ] - .span(); - let validators = setup_multisig_ism(); - let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; - start_prank(CheatTarget::One(ownable.contract_address), OWNER()); - validators.set_validators(new_validators); - let validators_span = validators.get_validators(); - assert(validators_span == new_validators, 'wrong validator address def'); -} - - -#[test] -fn test_set_threshold() { - let new_threshold = 3; - let validators = setup_multisig_ism(); - let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; - start_prank(CheatTarget::One(ownable.contract_address), OWNER()); - validators.set_threshold(new_threshold); - assert(validators.get_threshold() == new_threshold, 'wrong validator threshold'); -} - - -#[test] -#[should_panic(expected: ('Caller is not the owner',))] -fn test_set_validators_fails_if_caller_not_owner() { - let new_validators = array![ - ValidatorInformations { address: VALIDATOR_ADDRESS(), public_key: VALIDATOR_PUBLIC_KEY() } - ] - .span(); - let validators = setup_multisig_ism(); - let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; - start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); - validators.set_validators(new_validators); -} - -#[test] -#[should_panic(expected: ('Caller is not the owner',))] -fn test_set_threshold_fails_if_caller_not_owner() { - let new_threshold = 3; - let validators = setup_multisig_ism(); - let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; - start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); - validators.set_threshold(new_threshold); -} - - -#[test] -fn test_message_id_ism_metadata() { - let origin_merkle_tree_hook = array![ - // origin_merkle_tree_hook - 0x01020304050607080910111213141516, 0x16151413121110090807060504030201 - ]; - let root = array![0x01020304050607080910111213141516, 0x01020304050607080920111213141516,]; - let index = array![0x00000013000000000000000000000000]; - let index_u32 = 0x13; - let signature_1 = array![ - 0x01020304050607080910111213141516, - 0x01020304050607080920111213141516, - 0x01020304050607080910000000000000, - 0x02010304050607080910111213141516, - 0x00000003000000000000000000000000 - ]; - let signature_2 = array![ - 0x01020304050607080910111213141516, - 0x01020304050607080910111213141516, - 0x01020304050607080910111213141516, - 0x01020304050607080910000000000000, - 0x00000002000000000000000000000000 - ]; - let signature_3 = array![ - 0x01020304050607080910111213141516, - 0x13092450000011115450564500700000, - 0x01020304050607080910000000000000, - 0x01020304050607080910111213141516, - 0x00000002000000000000000000000000 - ]; - let signature_1_v = 0x3; - let signature_2_v = 0x2; - let signature_3_v = 0x2; - let mut metadata = origin_merkle_tree_hook.concat(@root); - metadata = metadata.concat(@index); - metadata = metadata.concat(@signature_1); - metadata = metadata.concat(@signature_2); - metadata = metadata.concat(@signature_3); - let bytes_metadata = BytesTrait::new(496, metadata); - assert( - MessageIdIsmMetadata::origin_merkle_tree_hook( - bytes_metadata.clone() - ) == u256 { low: *origin_merkle_tree_hook.at(1), high: *origin_merkle_tree_hook.at(0) }, - 'wrong merkle tree hook' - ); - assert( - MessageIdIsmMetadata::root( - bytes_metadata.clone() - ) == u256 { low: *root.at(1), high: *root.at(0) }, - 'wrong root' - ); - assert(MessageIdIsmMetadata::index(bytes_metadata.clone()) == index_u32, 'wrong index'); - let (test, test_1, test_2) = MessageIdIsmMetadata::signature_at(bytes_metadata.clone(), 1); - println!("aight {}, {}, {}", test, test_1, test_2); - assert( - MessageIdIsmMetadata::signature_at( - bytes_metadata.clone(), 0 - ) == ( - signature_1_v, - u256 { low: *signature_1.at(1), high: *signature_1.at(0) }, - u256 { low: *signature_1.at(3), high: *signature_1.at(2) } - ), - 'wrong signature 1' - ); - assert( - MessageIdIsmMetadata::signature_at( - bytes_metadata.clone(), 1 - ) == ( - signature_2_v, - u256 { low: *signature_2.at(1), high: *signature_2.at(0) }, - u256 { low: *signature_2.at(3), high: *signature_2.at(2) } - ), - 'wrong signature 2' - ); - assert( - MessageIdIsmMetadata::signature_at( - bytes_metadata.clone(), 2 - ) == ( - signature_3_v, - u256 { low: *signature_3.at(1), high: *signature_3.at(0) }, - u256 { low: *signature_3.at(3), high: *signature_3.at(2) } - ), - 'wrong signature 3' - ); -} - - -#[test] -fn test_message_id_multisig_module_type() { - let messageid = setup_messageid_multisig_ism(); - assert( - messageid.module_type() == ModuleType::MESSAGE_ID_MULTISIG(messageid.contract_address), - 'Wrong module type' - ); -} - - -#[test] -fn test_message_id_multisig_verify() { - let array = array![ - 0x01020304050607080910111213141516, - 0x01020304050607080910111213141516, - 0x01020304050607080910000000000000 - ]; - let message_body = BytesTrait::new(42, array); - let message = Message { - version: HYPERLANE_VERSION, - nonce: 0, - origin: LOCAL_DOMAIN, - sender: OWNER(), - destination: DESTINATION_DOMAIN, - recipient: RECIPIENT_ADDRESS(), - body: message_body.clone() - }; - let messageid = setup_messageid_multisig_ism(); - let (msg_hash, signature, public_key_x, public_key_y, eth_address) = get_message_and_signature( - false - ); - let validators = setup_multisig_ism(); - let new_validators = array![ - ValidatorInformations { address: eth_address, public_key: 0.try_into().unwrap() } - ]; - let metadata = array![ - 0x01020304050607080910111213141516, - 0x16151413121110090807060504030201, - 0x01020304050607080910111213141516, - 0x01020304050607080920111213141516, - 0x00000010000000000000000000000000, - signature.r.high, - signature.r.low, - signature.s.high, - signature.s.low, - 0x00000010000000000000000000000000 - ]; - let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; - start_prank(CheatTarget::One(ownable.contract_address), OWNER()); - validators.set_validators(new_validators.span()); - validators.set_threshold(1); - let bytes_metadata = BytesTrait::new(42, metadata); - assert( - messageid.verify(bytes_metadata, message, validators.contract_address) == true, - 'verification failed' - ); -} From 3573bb954aec68f3b818d2420942aaa367b0c847 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 13 May 2024 15:16:27 +0200 Subject: [PATCH 14/26] fix: opp --- src/contracts/mailbox.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/mailbox.cairo b/src/contracts/mailbox.cairo index 2bd7775..bdf0f29 100644 --- a/src/contracts/mailbox.cairo +++ b/src/contracts/mailbox.cairo @@ -391,7 +391,7 @@ pub mod mailbox { }; let hook = IPostDispatchHookDispatcher { contract_address: hook_address }; required_hook.quote_dispatch(hook_metadata.clone(), message.clone()) - - hook.quote_dispatch(hook_metadata, message) + + hook.quote_dispatch(hook_metadata, message) } /// Returns the ISM to use for the recipient, defaulting to the default ISM if none is specified. From 3c3e1cd976b46475604804f86c6eaf7c70fa7e85 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 13 May 2024 15:21:07 +0200 Subject: [PATCH 15/26] fix: mailbox review --- src/contracts/client/mailboxclient.cairo | 4 ++-- src/contracts/mailbox.cairo | 4 ++-- src/interfaces.cairo | 4 +--- src/tests/test_mailbox.cairo | 6 ++---- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/contracts/client/mailboxclient.cairo b/src/contracts/client/mailboxclient.cairo index 9da4b76..d6a7e58 100644 --- a/src/contracts/client/mailboxclient.cairo +++ b/src/contracts/client/mailboxclient.cairo @@ -123,13 +123,13 @@ mod mailboxclient { _message_body: Bytes, _hook_metadata: Option, _hook: Option - ) { + ) -> u256 { let mailbox_address = self.mailbox.read(); let mailbox = IMailboxDispatcher { contract_address: mailbox_address }; mailbox .quote_dispatch( _destination_domain, _recipient, _message_body, _hook_metadata, _hook - ); + ) } } } diff --git a/src/contracts/mailbox.cairo b/src/contracts/mailbox.cairo index bdf0f29..d703c2e 100644 --- a/src/contracts/mailbox.cairo +++ b/src/contracts/mailbox.cairo @@ -3,13 +3,13 @@ pub mod mailbox { use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; use core::starknet::SyscallResultTrait; use core::starknet::event::EventEmitter; - use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; + use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait, HYPERLANE_VERSION}; use hyperlane_starknet::interfaces::{ IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, IPostDispatchHookDispatcher, ISpecifiesInterchainSecurityModuleDispatcher, ISpecifiesInterchainSecurityModuleDispatcherTrait, IPostDispatchHookDispatcherTrait, - HYPERLANE_VERSION, IMessageRecipientDispatcher, IMessageRecipientDispatcherTrait, + IMessageRecipientDispatcher, IMessageRecipientDispatcherTrait, }; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; diff --git a/src/interfaces.cairo b/src/interfaces.cairo index 4f0c6ba..c1298d2 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -33,8 +33,6 @@ pub enum ModuleType { } -pub const HYPERLANE_VERSION: u8 = 3; - #[starknet::interface] pub trait IMailbox { fn initializer( @@ -183,7 +181,7 @@ pub trait IMailboxClient { _message_body: Bytes, _hook_metadata: Option, _hook: Option - ); + ) -> u256; } diff --git a/src/tests/test_mailbox.cairo b/src/tests/test_mailbox.cairo index 4360238..024cbbb 100644 --- a/src/tests/test_mailbox.cairo +++ b/src/tests/test_mailbox.cairo @@ -1,10 +1,8 @@ use alexandria_bytes::{Bytes, BytesTrait}; -use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; +use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait, HYPERLANE_VERSION}; use hyperlane_starknet::contracts::mailbox::mailbox; use hyperlane_starknet::interfaces::IMessageRecipientDispatcherTrait; -use hyperlane_starknet::interfaces::{ - IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, HYPERLANE_VERSION -}; +use hyperlane_starknet::interfaces::{IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait}; use hyperlane_starknet::tests::setup::{ setup, mock_setup, OWNER, LOCAL_DOMAIN, NEW_OWNER, DEFAULT_ISM, DEFAULT_HOOK, REQUIRED_HOOK, NEW_DEFAULT_ISM, NEW_DEFAULT_HOOK, NEW_REQUIRED_HOOK, DESTINATION_DOMAIN, RECIPIENT_ADDRESS From 73c9a2945d1bb2ed0601a737afce339110573f3d Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Mon, 13 May 2024 16:33:34 +0200 Subject: [PATCH 16/26] fix: comment multisig test --- src/lib.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.cairo b/src/lib.cairo index 5f9ac2a..7e10430 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -37,5 +37,5 @@ mod utils { mod tests { pub mod setup; pub mod test_mailbox; - pub mod test_multisig; + // pub mod test_multisig; } From e43980f47c871016cbeb22f17493fe4b536dfb76 Mon Sep 17 00:00:00 2001 From: 0xevolve Date: Tue, 14 May 2024 13:07:23 +0100 Subject: [PATCH 17/26] fix: fmt --- src/lib.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.cairo b/src/lib.cairo index 7e10430..f3f4486 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -37,5 +37,5 @@ mod utils { mod tests { pub mod setup; pub mod test_mailbox; - // pub mod test_multisig; +// pub mod test_multisig; } From 2a7764fadd2c2c7edbb12d8dafd051fa1f690727 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 15 May 2024 11:39:57 +0200 Subject: [PATCH 18/26] corrections + tests --- .../multisig/merkleroot_multisig_ism.cairo | 4 +- .../multisig/messageid_multisig_ism.cairo | 31 +-- .../isms/multisig/multisig_ism.cairo | 22 +- .../isms/multisig/validator_announce.cairo | 4 +- .../multisig/message_id_ism_metadata.cairo | 8 +- src/interfaces.cairo | 7 +- src/lib.cairo | 2 +- src/tests/setup.cairo | 90 +++++++- src/tests/test_multisig.cairo | 217 ++++++++++++++++++ 9 files changed, 331 insertions(+), 54 deletions(-) create mode 100644 src/tests/test_multisig.cairo diff --git a/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo b/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo index 2c75912..974c7bc 100644 --- a/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo +++ b/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo @@ -51,13 +51,13 @@ pub mod merkleroot_multisig_ism { } let (signature_r, signature_s) = get_signature_at(_metadata.clone(), i); - // we loop on the validators list public kew in order to find a match + // we loop on the validators list public key in order to find a match let mut cur_idx = 0; let is_signer_in_list = loop { if (cur_idx == validators.len()) { break false; } - let signer = *validators.at(cur_idx).address; + let signer = *validators.at(cur_idx); if check_ecdsa_signature( digest, signer.try_into().unwrap(), signature_r, signature_s ) { diff --git a/src/contracts/isms/multisig/messageid_multisig_ism.cairo b/src/contracts/isms/multisig/messageid_multisig_ism.cairo index 31735c9..4b18cf3 100644 --- a/src/contracts/isms/multisig/messageid_multisig_ism.cairo +++ b/src/contracts/isms/multisig/messageid_multisig_ism.cairo @@ -19,7 +19,7 @@ pub mod messageid_multisig_ism { mod Errors { pub const NO_MULTISIG_THRESHOLD_FOR_MESSAGE: felt252 = 'No MultisigISM treshold present'; - pub const VERIFICATION_FAILED_THRESHOLD_NOT_REACHED: felt252 = 'Verify failed, < threshold'; + pub const NO_MATCH_FOR_SIGNATURE: felt252 = 'No match for given signature'; pub const EMPTY_METADATA: felt252 = 'Empty metadata'; } #[abi(embed_v0)] @@ -42,8 +42,6 @@ pub mod messageid_multisig_ism { let (validators, threshold) = validator_configuration .validators_and_threshold(_message); assert(threshold > 0, Errors::NO_MULTISIG_THRESHOLD_FOR_MESSAGE); - let validator_count = validators.len(); - let mut unmatched_signatures = 0; let mut matched_signatures = 0; let mut i = 0; @@ -53,41 +51,28 @@ pub mod messageid_multisig_ism { break (); } let signature = get_signature_at(_metadata.clone(), i); - - // we loop on the validators list public kew in order to find a match + // we loop on the validators list public key in order to find a match let mut cur_idx = 0; let is_signer_in_list = loop { if (cur_idx == validators.len()) { break false; } - let signer = *validators.at(cur_idx).address; - if bool_is_eth_signature_valid( - digest.into(), signature, signer.try_into().unwrap() - ) { + let signer = *validators.at(cur_idx); + if bool_is_eth_signature_valid(digest, signature, signer) { // we found a match break true; } cur_idx += 1; }; - if (!is_signer_in_list) { - unmatched_signatures += 1; - } else { - matched_signatures += 1; - } - assert( - unmatched_signatures < validator_count - threshold, - Errors::VERIFICATION_FAILED_THRESHOLD_NOT_REACHED - ); + assert(is_signer_in_list, Errors::NO_MATCH_FOR_SIGNATURE); i += 1; }; - assert( - matched_signatures >= threshold, Errors::VERIFICATION_FAILED_THRESHOLD_NOT_REACHED - ); + println!("matched_signatures: {}", matched_signatures); true } } - fn digest(_metadata: Bytes, _message: Message) -> felt252 { + fn digest(_metadata: Bytes, _message: Message) -> u256 { let origin_merkle_tree_hook = MessageIdIsmMetadata::origin_merkle_tree_hook( _metadata.clone() ); @@ -100,8 +85,6 @@ pub mod messageid_multisig_ism { index, MessageTrait::format_message(_message) ) - .try_into() - .unwrap() } fn get_signature_at(_metadata: Bytes, _index: u32) -> Signature { diff --git a/src/contracts/isms/multisig/multisig_ism.cairo b/src/contracts/isms/multisig/multisig_ism.cairo index 6acdc77..cba6460 100644 --- a/src/contracts/isms/multisig/multisig_ism.cairo +++ b/src/contracts/isms/multisig/multisig_ism.cairo @@ -15,15 +15,10 @@ pub mod multisig_ism { impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; type Index = felt252; - #[derive(Serde, Copy, starknet::Store, Drop, PartialEq)] - pub struct ValidatorInformations { - pub address: EthAddress, - pub public_key: u256 - } #[storage] struct Storage { - validators: LegacyMap, + validators: LegacyMap, threshold: u32, #[substorage(v0)] ownable: OwnableComponent::Storage, @@ -48,12 +43,11 @@ pub mod multisig_ism { mod Errors { pub const VALIDATOR_ADDRESS_CANNOT_BE_NULL: felt252 = 'Validator address cannot be 0'; - pub const VALIDATOR_PUBLIC_KEY_CANNOT_BE_NULL: felt252 = 'Validator pk cannot be 0'; } #[abi(embed_v0)] impl IMultisigIsmImpl of IMultisigIsm { - fn get_validators(self: @ContractState) -> Span { + fn get_validators(self: @ContractState) -> Span { build_validators_span(self) } @@ -61,7 +55,7 @@ pub mod multisig_ism { self.threshold.read() } - fn set_validators(ref self: ContractState, _validators: Span) { + fn set_validators(ref self: ContractState, _validators: Span) { self.ownable.assert_only_owner(); let mut cur_idx = 0; @@ -71,10 +65,8 @@ pub mod multisig_ism { } let validator = *_validators.at(cur_idx); assert( - validator.address != 0.try_into().unwrap(), - Errors::VALIDATOR_ADDRESS_CANNOT_BE_NULL + validator != 0.try_into().unwrap(), Errors::VALIDATOR_ADDRESS_CANNOT_BE_NULL ); - // assert(validator.public_key != 0, Errors::VALIDATOR_PUBLIC_KEY_CANNOT_BE_NULL); self.validators.write(cur_idx.into(), validator); cur_idx += 1; } @@ -87,7 +79,7 @@ pub mod multisig_ism { fn validators_and_threshold( self: @ContractState, _message: Message - ) -> (Span, u32) { + ) -> (Span, u32) { // USER CONTRACT DEFINITION HERE // USER CAN SPECIFY VALIDATORS SELECTION CONDITIONS let threshold = self.threshold.read(); @@ -95,12 +87,12 @@ pub mod multisig_ism { } } - fn build_validators_span(self: @ContractState) -> Span { + fn build_validators_span(self: @ContractState) -> Span { let mut validators = ArrayTrait::new(); let mut cur_idx = 0; loop { let validator = self.validators.read(cur_idx); - if (validator.address == 0.try_into().unwrap()) { + if (validator == 0.try_into().unwrap()) { break (); } validators.append(validator); diff --git a/src/contracts/isms/multisig/validator_announce.cairo b/src/contracts/isms/multisig/validator_announce.cairo index d488571..4d91469 100644 --- a/src/contracts/isms/multisig/validator_announce.cairo +++ b/src/contracts/isms/multisig/validator_announce.cairo @@ -130,10 +130,10 @@ pub mod validator_announce { let mailboxclient = IMailboxClientDispatcher { contract_address: self.mailboxclient.read() }; - let mailboxcient_address: felt252 = self.mailboxclient.read().try_into().unwrap(); + let mailboxclient_address: felt252 = self.mailboxclient.read().try_into().unwrap(); let mut input: Array = array![ mailboxclient.get_local_domain().into(), - mailboxcient_address.try_into().unwrap(), + mailboxclient_address.try_into().unwrap(), HYPERLANE_ANNOUNCEMENT.into() ]; let hash = keccak_u256s_be_inputs(input.span()); diff --git a/src/contracts/libs/multisig/message_id_ism_metadata.cairo b/src/contracts/libs/multisig/message_id_ism_metadata.cairo index 14d7d24..9f93bea 100644 --- a/src/contracts/libs/multisig/message_id_ism_metadata.cairo +++ b/src/contracts/libs/multisig/message_id_ism_metadata.cairo @@ -11,7 +11,7 @@ pub mod message_id_ism_metadata { pub const ORIGIN_MERKLE_TREE_HOOK_OFFSET: u32 = 0; pub const ROOT_OFFSET: u32 = 32; pub const INDEX_OFFSET: u32 = 64; - pub const SIGNATURE_OFFSET: u32 = 96; + pub const SIGNATURE_OFFSET: u32 = 80; impl MessagIdIsmMetadataImpl of MessageIdIsmMetadata { fn origin_merkle_tree_hook(_metadata: Bytes) -> u256 { let (_, felt) = _metadata.read_u256(ORIGIN_MERKLE_TREE_HOOK_OFFSET); @@ -30,9 +30,9 @@ pub mod message_id_ism_metadata { fn signature_at(_metadata: Bytes, _index: u32) -> (u8, u256, u256) { // the first signer index is 0 - let (_, r) = _metadata.read_u256(SIGNATURE_OFFSET + 96 * _index); - let (_, s) = _metadata.read_u256(SIGNATURE_OFFSET + 96 * _index); - let (_, v) = _metadata.read_u8(SIGNATURE_OFFSET + 96 * _index + 32); + let (index_r, r) = _metadata.read_u256(SIGNATURE_OFFSET + 80 * _index); + let (index_s, s) = _metadata.read_u256(index_r); + let (_, v) = _metadata.read_u8(index_s); (v, r, s) } } diff --git a/src/interfaces.cairo b/src/interfaces.cairo index c1298d2..fa00866 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -1,6 +1,5 @@ use alexandria_bytes::Bytes; use core::array::ArrayTrait; -use hyperlane_starknet::contracts::isms::multisig::multisig_ism::multisig_ism::ValidatorInformations; use hyperlane_starknet::contracts::libs::message::Message; use starknet::ContractAddress; use starknet::EthAddress; @@ -222,13 +221,13 @@ pub trait IRouter { pub trait IMultisigIsm { fn validators_and_threshold( self: @TContractState, _message: Message - ) -> (Span, u32); + ) -> (Span, u32); - fn get_validators(self: @TContractState) -> Span; + fn get_validators(self: @TContractState) -> Span; fn get_threshold(self: @TContractState) -> u32; - fn set_validators(ref self: TContractState, _validators: Span); + fn set_validators(ref self: TContractState, _validators: Span); fn set_threshold(ref self: TContractState, _threshold: u32); } diff --git a/src/lib.cairo b/src/lib.cairo index f3f4486..5f9ac2a 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -37,5 +37,5 @@ mod utils { mod tests { pub mod setup; pub mod test_mailbox; -// pub mod test_multisig; + pub mod test_multisig; } diff --git a/src/tests/setup.cairo b/src/tests/setup.cairo index 0ef29fd..3320f4b 100644 --- a/src/tests/setup.cairo +++ b/src/tests/setup.cairo @@ -2,13 +2,17 @@ use core::result::ResultTrait; use hyperlane_starknet::contracts::mocks::message_recipient::message_recipient; use hyperlane_starknet::interfaces::{ IMailboxDispatcher, IMailboxDispatcherTrait, IMessageRecipientDispatcher, - IMessageRecipientDispatcherTrait + IMessageRecipientDispatcherTrait, IInterchainSecurityModule, + IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, + IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, IValidatorAnnounceDispatcher, + IValidatorAnnounceDispatcherTrait, IMailboxClientDispatcher, IMailboxClientDispatcherTrait }; use snforge_std::{ declare, ContractClassTrait, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn }; +use starknet::secp256_trait::Signature; -use starknet::{ContractAddress, contract_address_const}; +use starknet::{ContractAddress, contract_address_const, EthAddress}; pub const LOCAL_DOMAIN: u32 = 534352; pub const DESTINATION_DOMAIN: u32 = 9841001; @@ -49,6 +53,14 @@ pub fn RECIPIENT_ADDRESS() -> ContractAddress { contract_address_const::<'RECIPIENT_ADDRESS'>() } +pub fn VALIDATOR_ADDRESS() -> EthAddress { + 'VALIDATOR_ADDRESS'.try_into().unwrap() +} + +pub fn VALIDATOR_PUBLIC_KEY() -> u256 { + 'VALIDATOR_PUBLIC_KEY' +} + pub fn setup() -> (IMailboxDispatcher, EventSpy) { let mailbox_class = declare("mailbox").unwrap(); let (mailbox_addr, _) = mailbox_class @@ -64,3 +76,77 @@ pub fn mock_setup() -> IMessageRecipientDispatcher { let (message_recipient_addr, _) = message_recipient_class.deploy(@array![]).unwrap(); IMessageRecipientDispatcher { contract_address: message_recipient_addr } } + +pub fn setup_messageid_multisig_ism() -> IInterchainSecurityModuleDispatcher { + let messageid_multisig_class = declare("messageid_multisig_ism").unwrap(); + + let (messageid_multisig_addr, _) = messageid_multisig_class.deploy(@array![]).unwrap(); + IInterchainSecurityModuleDispatcher { contract_address: messageid_multisig_addr } +} + +pub fn setup_multisig_ism() -> IMultisigIsmDispatcher { + let multisig_ism_class = declare("multisig_ism").unwrap(); + let (multisig_ism_addr, _) = multisig_ism_class.deploy(@array![OWNER().into()]).unwrap(); + IMultisigIsmDispatcher { contract_address: multisig_ism_addr } +} + +pub fn setup_mailbox_client() -> IMailboxClientDispatcher { + let (mailbox, _) = setup(); + let mailboxclient_class = declare("mailboxclient").unwrap(); + let (mailboxclient_addr, _) = mailboxclient_class + .deploy(@array![mailbox.contract_address.into(), OWNER().into()]) + .unwrap(); + IMailboxClientDispatcher { contract_address: mailboxclient_addr } +} + + +pub fn setup_validator_announce() -> IValidatorAnnounceDispatcher { + let validator_announce_class = declare("validator_announce").unwrap(); + let mailboxclient = setup_mailbox_client(); + let (validator_announce_addr, _) = validator_announce_class + .deploy(@array![mailboxclient.contract_address.into()]) + .unwrap(); + IValidatorAnnounceDispatcher { contract_address: validator_announce_addr } +} + + +// Configuration from the main cairo repo: https://github.com/starkware-libs/cairo/blob/main/corelib/src/test/secp256k1_test.cairo +pub fn get_message_and_signature(y_parity: bool) -> (u256, Array, Array) { + let msg_hash = 0xfbff8940be2153ce000c0e1933bf32e179c60f53c45f56b4ac84b2e90f1f6214; + let validators_array: Array = array![ + 0x2cb1a91F2F23D6eC7FD22d2f7996f55B71EB32dc.try_into().unwrap(), + 0x0fb1A81BcefDEc06154279219F227938D00B1c12.try_into().unwrap(), + 0xF650b555CFDEfF61d225058e26326266E69660c2.try_into().unwrap(), + 0x03aC66d13dc1B5b10fc363fC32f324ca947CDac1.try_into().unwrap(), + 0x5711B186cdCAFD9E7aa1f78c0A0c30d3C7A2Af77.try_into().unwrap() + ]; + let signatures = array![ + Signature { + r: 0xb994fec0137776002d05dcf847bbba338285f1210c9ca7829109578ac876519f, + s: 0x0a42bb91f22ef042ca82fdcf8c8a5846e0debbce509dc2a0ce28a988dcbe4a16, + y_parity + }, + Signature { + r: 0xf81a5dd3f871ad2d27a3b538e73663d723f8263fb3d289514346d43d000175f5, + s: 0x083df770623e9ae52a7bb154473961e24664bb003bdfdba6100fb5e540875ce1, + y_parity + }, + Signature { + r: 0x76b194f951f94492ca582dab63dc413b9ac1ca9992c22bc2186439e9ab8fdd3c, + s: 0x62a6a6f402edaa53e9bdc715070a61edb0d98d4e14e182f60bdd4ae932b40b29, + y_parity + }, + Signature { + r: 0x35932eefd85897d868aaacd4ba7aee81a2384e42ba062133f6d37fdfebf94ad4, + s: 0x78cce49db96ee27c3f461800388ac95101476605baa64a194b7dd4d56d2d4a4d, + y_parity + }, + Signature { + r: 0x6b38d4353d69396e91c57542254348d16459d448ab887574e9476a6ff76d49a1, + s: 0x3527627295bde423d7d799afef22affac4f00c70a5b651ad14c8879aeb9b6e03, + y_parity + } + ]; + + (msg_hash, validators_array, signatures) +} diff --git a/src/tests/test_multisig.cairo b/src/tests/test_multisig.cairo new file mode 100644 index 0000000..c98937f --- /dev/null +++ b/src/tests/test_multisig.cairo @@ -0,0 +1,217 @@ +use alexandria_bytes::{Bytes, BytesTrait}; +use alexandria_data_structures::array_ext::ArrayTraitExt; +use core::array::ArrayTrait; +use core::array::SpanTrait; +use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait, HYPERLANE_VERSION}; +use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; +use hyperlane_starknet::contracts::mailbox::mailbox; +use hyperlane_starknet::interfaces::IMessageRecipientDispatcherTrait; +use hyperlane_starknet::interfaces::IMultisigIsmDispatcherTrait; +use hyperlane_starknet::interfaces::{ + IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, ModuleType, + IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait +}; +use hyperlane_starknet::tests::setup::{ + setup, mock_setup, setup_messageid_multisig_ism, setup_multisig_ism, OWNER, NEW_OWNER, + VALIDATOR_ADDRESS, VALIDATOR_PUBLIC_KEY, setup_validator_announce, get_message_and_signature, + LOCAL_DOMAIN, DESTINATION_DOMAIN, RECIPIENT_ADDRESS +}; +use openzeppelin::access::ownable::OwnableComponent; +use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; +use snforge_std::cheatcodes::events::EventAssertions; +use snforge_std::{start_prank, CheatTarget, stop_prank}; +use starknet::eth_address::EthAddress; +use starknet::secp256_trait::Signature; +#[test] +fn test_set_validators() { + let new_validators = array![VALIDATOR_ADDRESS()].span(); + let validators = setup_multisig_ism(); + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + validators.set_validators(new_validators); + let validators_span = validators.get_validators(); + assert(validators_span == new_validators, 'wrong validator address def'); +} + + +#[test] +fn test_set_threshold() { + let new_threshold = 3; + let validators = setup_multisig_ism(); + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + validators.set_threshold(new_threshold); + assert(validators.get_threshold() == new_threshold, 'wrong validator threshold'); +} + + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_set_validators_fails_if_caller_not_owner() { + let new_validators = array![VALIDATOR_ADDRESS(),].span(); + let validators = setup_multisig_ism(); + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); + validators.set_validators(new_validators); +} + +#[test] +#[should_panic(expected: ('Caller is not the owner',))] +fn test_set_threshold_fails_if_caller_not_owner() { + let new_threshold = 3; + let validators = setup_multisig_ism(); + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); + validators.set_threshold(new_threshold); +} + + +#[test] +fn test_message_id_ism_metadata() { + let origin_merkle_tree_hook = array![ + // origin_merkle_tree_hook + 0x02030405060708091011121314151623, 0x16151413121110090807060504030201 + ]; + let root = array![0x01000304050607080910111213141516, 0x01020304050607080920111213141516,]; + let index = array![0x00000013000000000000000000000000]; + let index_u32 = 0x13; + let signature_1 = array![ + 0x09020304050607080910111213141516, + 0x01020304050607080920111213141516, + 0x01020304050607080910000000000000, + 0x02010304050607080910111213141516, + 0x03000000000000000000000000000000 + ]; + let signature_2 = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000, + 0x02000000000000000000000000000000 + ]; + let signature_3 = array![ + 0x01020304050607080910111213141516, + 0x13092450000011115450564500700000, + 0x01020304050607080910000000000000, + 0x01020304050607080910111213141516, + 0x02000000000000000000000000000000 + ]; + let signature_1_v = 0x3; + let signature_2_v = 0x2; + let signature_3_v = 0x2; + let mut metadata = origin_merkle_tree_hook.concat(@root); + metadata = metadata.concat(@index); + metadata = metadata.concat(@signature_1); + metadata = metadata.concat(@signature_2); + metadata = metadata.concat(@signature_3); + let bytes_metadata = BytesTrait::new(496, metadata); + assert( + MessageIdIsmMetadata::origin_merkle_tree_hook( + bytes_metadata.clone() + ) == u256 { low: *origin_merkle_tree_hook.at(1), high: *origin_merkle_tree_hook.at(0) }, + 'wrong merkle tree hook' + ); + assert( + MessageIdIsmMetadata::root( + bytes_metadata.clone() + ) == u256 { low: *root.at(1), high: *root.at(0) }, + 'wrong root' + ); + assert(MessageIdIsmMetadata::index(bytes_metadata.clone()) == index_u32, 'wrong index'); + assert( + MessageIdIsmMetadata::signature_at( + bytes_metadata.clone(), 0 + ) == ( + signature_1_v, + u256 { low: *signature_1.at(1), high: *signature_1.at(0) }, + u256 { low: *signature_1.at(3), high: *signature_1.at(2) } + ), + 'wrong signature 1' + ); + assert( + MessageIdIsmMetadata::signature_at( + bytes_metadata.clone(), 1 + ) == ( + signature_2_v, + u256 { low: *signature_2.at(1), high: *signature_2.at(0) }, + u256 { low: *signature_2.at(3), high: *signature_2.at(2) } + ), + 'wrong signature 2' + ); + assert( + MessageIdIsmMetadata::signature_at( + bytes_metadata.clone(), 2 + ) == ( + signature_3_v, + u256 { low: *signature_3.at(1), high: *signature_3.at(0) }, + u256 { low: *signature_3.at(3), high: *signature_3.at(2) } + ), + 'wrong signature 3' + ); +} + + +#[test] +fn test_message_id_multisig_module_type() { + let messageid = setup_messageid_multisig_ism(); + assert( + messageid.module_type() == ModuleType::MESSAGE_ID_MULTISIG(messageid.contract_address), + 'Wrong module type' + ); +} + + +#[test] +fn test_message_id_multisig_verify() { + let array = array![ + 0x01020304050607080910111213141516, + 0x01020304050607080910111213141516, + 0x01020304050607080910000000000000 + ]; + let message_body = BytesTrait::new(42, array); + let message = Message { + version: HYPERLANE_VERSION, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: OWNER(), + destination: DESTINATION_DOMAIN, + recipient: RECIPIENT_ADDRESS(), + body: message_body.clone() + }; + let messageid = setup_messageid_multisig_ism(); + let (msg_hash, validators_address, signatures) = get_message_and_signature(false); + let validators = setup_multisig_ism(); + let metadata = array![ + 0x01020304050607080910111213141516, + 0x16151413121110090807060504030201, + 0x01020304050607080910111213141516, + 0x01020304050607080920111213141516, + 0x00000010000000000000000000000000, + *signatures.at(0).r.high, + *signatures.at(0).r.low, + *signatures.at(0).s.high, + *signatures.at(0).s.low, + *signatures.at(1).r.high, + *signatures.at(1).r.low, + *signatures.at(1).s.high, + *signatures.at(1).s.low, + *signatures.at(2).r.high, + *signatures.at(2).r.low, + *signatures.at(2).s.high, + *signatures.at(2).s.low, + *signatures.at(3).r.high, + *signatures.at(3).r.low, + *signatures.at(3).s.high, + *signatures.at(3).s.low, + 0x00000010000000000000000000000000 + ]; + let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; + start_prank(CheatTarget::One(ownable.contract_address), OWNER()); + validators.set_validators(validators_address.span()); + validators.set_threshold(3); + let bytes_metadata = BytesTrait::new(496, metadata); + assert( + messageid.verify(bytes_metadata, message, validators.contract_address) == true, + 'verification failed' + ); +} From 6e364f4c38323d63d55a7125740ef7f63259f0a0 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 15 May 2024 14:19:24 +0200 Subject: [PATCH 19/26] refactor --- .github/workflows/release.yml | 2 +- .snfoundry_cache/.prev_tests_failed | 1 + .gitignore => contracts/.gitignore | 0 Scarb.lock => contracts/Scarb.lock | 0 Scarb.toml => contracts/Scarb.toml | 0 .../src}/contracts/client/mailboxclient.cairo | 0 .../src}/contracts/client/router.cairo | 0 .../multisig/merkleroot_multisig_ism.cairo | 0 .../multisig/messageid_multisig_ism.cairo | 97 +++++++++++++++-- .../isms/multisig/validator_announce.cairo | 0 .../isms/routing/domain_routing_ism.cairo | 0 .../src}/contracts/libs/checkpoint_lib.cairo | 0 .../src}/contracts/libs/message.cairo | 0 .../multisig/message_id_ism_metadata.cairo | 0 .../src}/contracts/mailbox.cairo | 0 .../src}/contracts/mocks/ism.cairo | 20 +++- .../contracts/mocks/message_recipient.cairo | 0 {src => contracts/src}/interfaces.cairo | 28 ++--- {src => contracts/src}/lib.cairo | 3 - {src => contracts/src}/tests/setup.cairo | 9 +- .../src}/tests/test_mailbox.cairo | 0 .../src}/tests/test_multisig.cairo | 15 ++- {src => contracts/src}/utils/keccak256.cairo | 0 .../src}/utils/store_arrays.cairo | 0 .../isms/multisig/multisig_ism.cairo | 103 ------------------ 25 files changed, 128 insertions(+), 150 deletions(-) create mode 100644 .snfoundry_cache/.prev_tests_failed rename .gitignore => contracts/.gitignore (100%) rename Scarb.lock => contracts/Scarb.lock (100%) rename Scarb.toml => contracts/Scarb.toml (100%) rename {src => contracts/src}/contracts/client/mailboxclient.cairo (100%) rename {src => contracts/src}/contracts/client/router.cairo (100%) rename {src => contracts/src}/contracts/isms/multisig/merkleroot_multisig_ism.cairo (100%) rename {src => contracts/src}/contracts/isms/multisig/messageid_multisig_ism.cairo (53%) rename {src => contracts/src}/contracts/isms/multisig/validator_announce.cairo (100%) rename {src => contracts/src}/contracts/isms/routing/domain_routing_ism.cairo (100%) rename {src => contracts/src}/contracts/libs/checkpoint_lib.cairo (100%) rename {src => contracts/src}/contracts/libs/message.cairo (100%) rename {src => contracts/src}/contracts/libs/multisig/message_id_ism_metadata.cairo (100%) rename {src => contracts/src}/contracts/mailbox.cairo (100%) rename {src => contracts/src}/contracts/mocks/ism.cairo (60%) rename {src => contracts/src}/contracts/mocks/message_recipient.cairo (100%) rename {src => contracts/src}/interfaces.cairo (98%) rename {src => contracts/src}/lib.cairo (87%) rename {src => contracts/src}/tests/setup.cairo (93%) rename {src => contracts/src}/tests/test_mailbox.cairo (100%) rename {src => contracts/src}/tests/test_multisig.cairo (95%) rename {src => contracts/src}/utils/keccak256.cairo (100%) rename {src => contracts/src}/utils/store_arrays.cairo (100%) delete mode 100644 src/contracts/isms/multisig/multisig_ism.cairo diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dcc7e9e..997ced2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - name: Archive contracts run: | mkdir -p filtered_artifacts - find ./target/dev -type f -name '*.contract_class.json' -exec cp {} filtered_artifacts/ \; + find ./contracts/target/dev -type f -name '*.contract_class.json' -exec cp {} filtered_artifacts/ \; - name: Generate checksums run: | cd filtered_artifacts diff --git a/.snfoundry_cache/.prev_tests_failed b/.snfoundry_cache/.prev_tests_failed new file mode 100644 index 0000000..44ee8b0 --- /dev/null +++ b/.snfoundry_cache/.prev_tests_failed @@ -0,0 +1 @@ +hyperlane_starknet::tests::test_multisig::test_message_id_multisig_verify diff --git a/.gitignore b/contracts/.gitignore similarity index 100% rename from .gitignore rename to contracts/.gitignore diff --git a/Scarb.lock b/contracts/Scarb.lock similarity index 100% rename from Scarb.lock rename to contracts/Scarb.lock diff --git a/Scarb.toml b/contracts/Scarb.toml similarity index 100% rename from Scarb.toml rename to contracts/Scarb.toml diff --git a/src/contracts/client/mailboxclient.cairo b/contracts/src/contracts/client/mailboxclient.cairo similarity index 100% rename from src/contracts/client/mailboxclient.cairo rename to contracts/src/contracts/client/mailboxclient.cairo diff --git a/src/contracts/client/router.cairo b/contracts/src/contracts/client/router.cairo similarity index 100% rename from src/contracts/client/router.cairo rename to contracts/src/contracts/client/router.cairo diff --git a/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo b/contracts/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo similarity index 100% rename from src/contracts/isms/multisig/merkleroot_multisig_ism.cairo rename to contracts/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo diff --git a/src/contracts/isms/multisig/messageid_multisig_ism.cairo b/contracts/src/contracts/isms/multisig/messageid_multisig_ism.cairo similarity index 53% rename from src/contracts/isms/multisig/messageid_multisig_ism.cairo rename to contracts/src/contracts/isms/multisig/messageid_multisig_ism.cairo index 4b18cf3..8f52a9e 100644 --- a/src/contracts/isms/multisig/messageid_multisig_ism.cairo +++ b/contracts/src/contracts/isms/multisig/messageid_multisig_ism.cairo @@ -5,23 +5,54 @@ pub mod messageid_multisig_ism { use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::CheckpointLib; use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; - use hyperlane_starknet::interfaces::{ - IMultisigIsm, IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, ModuleType, + use hyperlane_starknet::interfaces::{ ModuleType, IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, }; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + #[abi(embed_v0)] + impl OwnableImpl = OwnableComponent::OwnableImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; use starknet::ContractAddress; use starknet::EthAddress; use starknet::eth_signature::is_eth_signature_valid; use starknet::secp256_trait::{Signature, signature_from_vrs}; #[storage] - struct Storage {} + struct Storage { + validators: LegacyMap, + threshold: u32, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } mod Errors { pub const NO_MULTISIG_THRESHOLD_FOR_MESSAGE: felt252 = 'No MultisigISM treshold present'; pub const NO_MATCH_FOR_SIGNATURE: felt252 = 'No match for given signature'; pub const EMPTY_METADATA: felt252 = 'Empty metadata'; + pub const VALIDATOR_ADDRESS_CANNOT_BE_NULL: felt252 = 'Validator address cannot be 0'; + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + + #[constructor] + fn constructor(ref self: ContractState, _owner: ContractAddress) { + self.ownable.initializer(_owner); } + #[abi(embed_v0)] impl IMessageidMultisigIsmImpl of IInterchainSecurityModule { fn module_type(self: @ContractState) -> ModuleType { @@ -32,15 +63,10 @@ pub mod messageid_multisig_ism { self: @ContractState, _metadata: Bytes, _message: Message, - _validator_configuration: ContractAddress, ) -> bool { assert(_metadata.clone().data().len() > 0, Errors::EMPTY_METADATA); let digest = digest(_metadata.clone(), _message.clone()); - let validator_configuration = IMultisigIsmDispatcher { - contract_address: _validator_configuration - }; - let (validators, threshold) = validator_configuration - .validators_and_threshold(_message); + let (validators, threshold) = self.validators_and_threshold(_message); assert(threshold > 0, Errors::NO_MULTISIG_THRESHOLD_FOR_MESSAGE); let mut matched_signatures = 0; let mut i = 0; @@ -67,9 +93,46 @@ pub mod messageid_multisig_ism { assert(is_signer_in_list, Errors::NO_MATCH_FOR_SIGNATURE); i += 1; }; - println!("matched_signatures: {}", matched_signatures); true } + fn get_validators(self: @ContractState) -> Span { + build_validators_span(self) + } + + fn get_threshold(self: @ContractState) -> u32 { + self.threshold.read() + } + + fn set_validators(ref self: ContractState, _validators: Span) { + self.ownable.assert_only_owner(); + let mut cur_idx = 0; + + loop { + if (cur_idx == _validators.len()) { + break (); + } + let validator = *_validators.at(cur_idx); + assert( + validator != 0.try_into().unwrap(), Errors::VALIDATOR_ADDRESS_CANNOT_BE_NULL + ); + self.validators.write(cur_idx.into(), validator); + cur_idx += 1; + } + } + + fn set_threshold(ref self: ContractState, _threshold: u32) { + self.ownable.assert_only_owner(); + self.threshold.write(_threshold); + } + + fn validators_and_threshold( + self: @ContractState, _message: Message + ) -> (Span, u32) { + // USER CONTRACT DEFINITION HERE + // USER CAN SPECIFY VALIDATORS SELECTION CONDITIONS + let threshold = self.threshold.read(); + (build_validators_span(self), threshold) + } } fn digest(_metadata: Bytes, _message: Message) -> u256 { @@ -100,4 +163,18 @@ pub mod messageid_multisig_ism { Result::Err(_) => false } } + + fn build_validators_span(self: @ContractState) -> Span { + let mut validators = ArrayTrait::new(); + let mut cur_idx = 0; + loop { + let validator = self.validators.read(cur_idx); + if (validator == 0.try_into().unwrap()) { + break (); + } + validators.append(validator); + cur_idx += 1; + }; + validators.span() + } } diff --git a/src/contracts/isms/multisig/validator_announce.cairo b/contracts/src/contracts/isms/multisig/validator_announce.cairo similarity index 100% rename from src/contracts/isms/multisig/validator_announce.cairo rename to contracts/src/contracts/isms/multisig/validator_announce.cairo diff --git a/src/contracts/isms/routing/domain_routing_ism.cairo b/contracts/src/contracts/isms/routing/domain_routing_ism.cairo similarity index 100% rename from src/contracts/isms/routing/domain_routing_ism.cairo rename to contracts/src/contracts/isms/routing/domain_routing_ism.cairo diff --git a/src/contracts/libs/checkpoint_lib.cairo b/contracts/src/contracts/libs/checkpoint_lib.cairo similarity index 100% rename from src/contracts/libs/checkpoint_lib.cairo rename to contracts/src/contracts/libs/checkpoint_lib.cairo diff --git a/src/contracts/libs/message.cairo b/contracts/src/contracts/libs/message.cairo similarity index 100% rename from src/contracts/libs/message.cairo rename to contracts/src/contracts/libs/message.cairo diff --git a/src/contracts/libs/multisig/message_id_ism_metadata.cairo b/contracts/src/contracts/libs/multisig/message_id_ism_metadata.cairo similarity index 100% rename from src/contracts/libs/multisig/message_id_ism_metadata.cairo rename to contracts/src/contracts/libs/multisig/message_id_ism_metadata.cairo diff --git a/src/contracts/mailbox.cairo b/contracts/src/contracts/mailbox.cairo similarity index 100% rename from src/contracts/mailbox.cairo rename to contracts/src/contracts/mailbox.cairo diff --git a/src/contracts/mocks/ism.cairo b/contracts/src/contracts/mocks/ism.cairo similarity index 60% rename from src/contracts/mocks/ism.cairo rename to contracts/src/contracts/mocks/ism.cairo index 80b523a..818b06d 100644 --- a/src/contracts/mocks/ism.cairo +++ b/contracts/src/contracts/mocks/ism.cairo @@ -7,6 +7,7 @@ pub mod ism { IInterchainSecurityModuleDispatcherTrait, ModuleType }; use starknet::ContractAddress; + use starknet::EthAddress; #[storage] struct Storage {} @@ -20,9 +21,26 @@ pub mod ism { self: @ContractState, _metadata: Bytes, _message: Message, - _validator_configuration: ContractAddress ) -> bool { true } + + fn validators_and_threshold( + self: @ContractState, _message: Message + ) -> (Span, u32) { + (array![].span(),0) + } + + fn get_validators(self: @ContractState) -> Span{ + array![].span() + } + + fn get_threshold(self: @ContractState) -> u32{ + 0 + } + + fn set_validators(ref self: ContractState, _validators: Span){} + + fn set_threshold(ref self: ContractState, _threshold: u32){} } } diff --git a/src/contracts/mocks/message_recipient.cairo b/contracts/src/contracts/mocks/message_recipient.cairo similarity index 100% rename from src/contracts/mocks/message_recipient.cairo rename to contracts/src/contracts/mocks/message_recipient.cairo diff --git a/src/interfaces.cairo b/contracts/src/interfaces.cairo similarity index 98% rename from src/interfaces.cairo rename to contracts/src/interfaces.cairo index fa00866..df5c698 100644 --- a/src/interfaces.cairo +++ b/contracts/src/interfaces.cairo @@ -108,8 +108,19 @@ pub trait IInterchainSecurityModule { self: @TContractState, _metadata: Bytes, _message: Message, - _validator_configuration: ContractAddress ) -> bool; + + fn validators_and_threshold( + self: @TContractState, _message: Message + ) -> (Span, u32); + + fn get_validators(self: @TContractState) -> Span; + + fn get_threshold(self: @TContractState) -> u32; + + fn set_validators(ref self: TContractState, _validators: Span); + + fn set_threshold(ref self: TContractState, _threshold: u32); } #[starknet::interface] @@ -217,21 +228,6 @@ pub trait IRouter { } -#[starknet::interface] -pub trait IMultisigIsm { - fn validators_and_threshold( - self: @TContractState, _message: Message - ) -> (Span, u32); - - fn get_validators(self: @TContractState) -> Span; - - fn get_threshold(self: @TContractState) -> u32; - - fn set_validators(ref self: TContractState, _validators: Span); - - fn set_threshold(ref self: TContractState, _threshold: u32); -} - #[starknet::interface] pub trait IDefaultFallbackRoutingIsm { /// Returns an enum that represents the type of security model encoded by this ISM. diff --git a/src/lib.cairo b/contracts/src/lib.cairo similarity index 87% rename from src/lib.cairo rename to contracts/src/lib.cairo index 5f9ac2a..f80fa41 100644 --- a/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -18,9 +18,7 @@ mod contracts { } pub mod isms { pub mod multisig { - pub mod merkleroot_multisig_ism; pub mod messageid_multisig_ism; - pub mod multisig_ism; pub mod validator_announce; } pub mod routing { @@ -37,5 +35,4 @@ mod utils { mod tests { pub mod setup; pub mod test_mailbox; - pub mod test_multisig; } diff --git a/src/tests/setup.cairo b/contracts/src/tests/setup.cairo similarity index 93% rename from src/tests/setup.cairo rename to contracts/src/tests/setup.cairo index 3320f4b..f38d58d 100644 --- a/src/tests/setup.cairo +++ b/contracts/src/tests/setup.cairo @@ -3,8 +3,7 @@ use hyperlane_starknet::contracts::mocks::message_recipient::message_recipient; use hyperlane_starknet::interfaces::{ IMailboxDispatcher, IMailboxDispatcherTrait, IMessageRecipientDispatcher, IMessageRecipientDispatcherTrait, IInterchainSecurityModule, - IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, - IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, IValidatorAnnounceDispatcher, + IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, IValidatorAnnounceDispatcher, IValidatorAnnounceDispatcherTrait, IMailboxClientDispatcher, IMailboxClientDispatcherTrait }; use snforge_std::{ @@ -84,12 +83,6 @@ pub fn setup_messageid_multisig_ism() -> IInterchainSecurityModuleDispatcher { IInterchainSecurityModuleDispatcher { contract_address: messageid_multisig_addr } } -pub fn setup_multisig_ism() -> IMultisigIsmDispatcher { - let multisig_ism_class = declare("multisig_ism").unwrap(); - let (multisig_ism_addr, _) = multisig_ism_class.deploy(@array![OWNER().into()]).unwrap(); - IMultisigIsmDispatcher { contract_address: multisig_ism_addr } -} - pub fn setup_mailbox_client() -> IMailboxClientDispatcher { let (mailbox, _) = setup(); let mailboxclient_class = declare("mailboxclient").unwrap(); diff --git a/src/tests/test_mailbox.cairo b/contracts/src/tests/test_mailbox.cairo similarity index 100% rename from src/tests/test_mailbox.cairo rename to contracts/src/tests/test_mailbox.cairo diff --git a/src/tests/test_multisig.cairo b/contracts/src/tests/test_multisig.cairo similarity index 95% rename from src/tests/test_multisig.cairo rename to contracts/src/tests/test_multisig.cairo index c98937f..6964712 100644 --- a/src/tests/test_multisig.cairo +++ b/contracts/src/tests/test_multisig.cairo @@ -6,13 +6,12 @@ use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait, HYPERL use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; use hyperlane_starknet::contracts::mailbox::mailbox; use hyperlane_starknet::interfaces::IMessageRecipientDispatcherTrait; -use hyperlane_starknet::interfaces::IMultisigIsmDispatcherTrait; use hyperlane_starknet::interfaces::{ IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, ModuleType, - IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait + IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, IInterchainSecurityModule }; use hyperlane_starknet::tests::setup::{ - setup, mock_setup, setup_messageid_multisig_ism, setup_multisig_ism, OWNER, NEW_OWNER, + setup, mock_setup, setup_messageid_multisig_ism, OWNER, NEW_OWNER, VALIDATOR_ADDRESS, VALIDATOR_PUBLIC_KEY, setup_validator_announce, get_message_and_signature, LOCAL_DOMAIN, DESTINATION_DOMAIN, RECIPIENT_ADDRESS }; @@ -25,7 +24,7 @@ use starknet::secp256_trait::Signature; #[test] fn test_set_validators() { let new_validators = array![VALIDATOR_ADDRESS()].span(); - let validators = setup_multisig_ism(); + let validators = setup_messageid_multisig_ism(); let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); validators.set_validators(new_validators); @@ -37,7 +36,7 @@ fn test_set_validators() { #[test] fn test_set_threshold() { let new_threshold = 3; - let validators = setup_multisig_ism(); + let validators = setup_messageid_multisig_ism(); let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), OWNER()); validators.set_threshold(new_threshold); @@ -49,7 +48,7 @@ fn test_set_threshold() { #[should_panic(expected: ('Caller is not the owner',))] fn test_set_validators_fails_if_caller_not_owner() { let new_validators = array![VALIDATOR_ADDRESS(),].span(); - let validators = setup_multisig_ism(); + let validators = setup_messageid_multisig_ism(); let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); validators.set_validators(new_validators); @@ -59,7 +58,7 @@ fn test_set_validators_fails_if_caller_not_owner() { #[should_panic(expected: ('Caller is not the owner',))] fn test_set_threshold_fails_if_caller_not_owner() { let new_threshold = 3; - let validators = setup_multisig_ism(); + let validators = setup_messageid_multisig_ism(); let ownable = IOwnableDispatcher { contract_address: validators.contract_address }; start_prank(CheatTarget::One(ownable.contract_address), NEW_OWNER()); validators.set_threshold(new_threshold); @@ -180,7 +179,7 @@ fn test_message_id_multisig_verify() { }; let messageid = setup_messageid_multisig_ism(); let (msg_hash, validators_address, signatures) = get_message_and_signature(false); - let validators = setup_multisig_ism(); + let validators = setup_messageid_multisig_ism(); let metadata = array![ 0x01020304050607080910111213141516, 0x16151413121110090807060504030201, diff --git a/src/utils/keccak256.cairo b/contracts/src/utils/keccak256.cairo similarity index 100% rename from src/utils/keccak256.cairo rename to contracts/src/utils/keccak256.cairo diff --git a/src/utils/store_arrays.cairo b/contracts/src/utils/store_arrays.cairo similarity index 100% rename from src/utils/store_arrays.cairo rename to contracts/src/utils/store_arrays.cairo diff --git a/src/contracts/isms/multisig/multisig_ism.cairo b/src/contracts/isms/multisig/multisig_ism.cairo deleted file mode 100644 index cba6460..0000000 --- a/src/contracts/isms/multisig/multisig_ism.cairo +++ /dev/null @@ -1,103 +0,0 @@ -#[starknet::contract] -pub mod multisig_ism { - use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; - use hyperlane_starknet::interfaces::IMultisigIsm; - use openzeppelin::access::ownable::OwnableComponent; - use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; - use starknet::eth_address::EthAddress; - use starknet::{ContractAddress, contract_address_const}; - - component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); - component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); - #[abi(embed_v0)] - impl OwnableImpl = OwnableComponent::OwnableImpl; - impl OwnableInternalImpl = OwnableComponent::InternalImpl; - impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - type Index = felt252; - - - #[storage] - struct Storage { - validators: LegacyMap, - threshold: u32, - #[substorage(v0)] - ownable: OwnableComponent::Storage, - #[substorage(v0)] - upgradeable: UpgradeableComponent::Storage, - } - - #[event] - #[derive(Drop, starknet::Event)] - pub enum Event { - #[flat] - OwnableEvent: OwnableComponent::Event, - #[flat] - UpgradeableEvent: UpgradeableComponent::Event, - } - - #[constructor] - fn constructor(ref self: ContractState, _owner: ContractAddress) { - self.ownable.initializer(_owner); - } - - - mod Errors { - pub const VALIDATOR_ADDRESS_CANNOT_BE_NULL: felt252 = 'Validator address cannot be 0'; - } - - #[abi(embed_v0)] - impl IMultisigIsmImpl of IMultisigIsm { - fn get_validators(self: @ContractState) -> Span { - build_validators_span(self) - } - - fn get_threshold(self: @ContractState) -> u32 { - self.threshold.read() - } - - fn set_validators(ref self: ContractState, _validators: Span) { - self.ownable.assert_only_owner(); - let mut cur_idx = 0; - - loop { - if (cur_idx == _validators.len()) { - break (); - } - let validator = *_validators.at(cur_idx); - assert( - validator != 0.try_into().unwrap(), Errors::VALIDATOR_ADDRESS_CANNOT_BE_NULL - ); - self.validators.write(cur_idx.into(), validator); - cur_idx += 1; - } - } - - fn set_threshold(ref self: ContractState, _threshold: u32) { - self.ownable.assert_only_owner(); - self.threshold.write(_threshold); - } - - fn validators_and_threshold( - self: @ContractState, _message: Message - ) -> (Span, u32) { - // USER CONTRACT DEFINITION HERE - // USER CAN SPECIFY VALIDATORS SELECTION CONDITIONS - let threshold = self.threshold.read(); - (build_validators_span(self), threshold) - } - } - - fn build_validators_span(self: @ContractState) -> Span { - let mut validators = ArrayTrait::new(); - let mut cur_idx = 0; - loop { - let validator = self.validators.read(cur_idx); - if (validator == 0.try_into().unwrap()) { - break (); - } - validators.append(validator); - cur_idx += 1; - }; - validators.span() - } -} From f029dc352058ec2b677000d41c4994b9c83b0056 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 15 May 2024 14:19:53 +0200 Subject: [PATCH 20/26] fix: fmt --- .../multisig/messageid_multisig_ism.cairo | 20 +++++++--------- contracts/src/contracts/mocks/ism.cairo | 24 ++++++++----------- contracts/src/interfaces.cairo | 6 +---- contracts/src/tests/setup.cairo | 5 ++-- contracts/src/tests/test_multisig.cairo | 9 +++---- 5 files changed, 27 insertions(+), 37 deletions(-) diff --git a/contracts/src/contracts/isms/multisig/messageid_multisig_ism.cairo b/contracts/src/contracts/isms/multisig/messageid_multisig_ism.cairo index 8f52a9e..4cd2b3a 100644 --- a/contracts/src/contracts/isms/multisig/messageid_multisig_ism.cairo +++ b/contracts/src/contracts/isms/multisig/messageid_multisig_ism.cairo @@ -5,22 +5,22 @@ pub mod messageid_multisig_ism { use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::CheckpointLib; use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; - use hyperlane_starknet::interfaces::{ ModuleType, - IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, + use hyperlane_starknet::interfaces::{ + ModuleType, IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, }; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; + use starknet::ContractAddress; + use starknet::EthAddress; + use starknet::eth_signature::is_eth_signature_valid; + use starknet::secp256_trait::{Signature, signature_from_vrs}; component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); #[abi(embed_v0)] impl OwnableImpl = OwnableComponent::OwnableImpl; impl OwnableInternalImpl = OwnableComponent::InternalImpl; impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; - use starknet::ContractAddress; - use starknet::EthAddress; - use starknet::eth_signature::is_eth_signature_valid; - use starknet::secp256_trait::{Signature, signature_from_vrs}; #[storage] struct Storage { validators: LegacyMap, @@ -47,7 +47,7 @@ pub mod messageid_multisig_ism { UpgradeableEvent: UpgradeableComponent::Event, } - + #[constructor] fn constructor(ref self: ContractState, _owner: ContractAddress) { self.ownable.initializer(_owner); @@ -59,11 +59,7 @@ pub mod messageid_multisig_ism { ModuleType::MESSAGE_ID_MULTISIG(starknet::get_contract_address()) } - fn verify( - self: @ContractState, - _metadata: Bytes, - _message: Message, - ) -> bool { + fn verify(self: @ContractState, _metadata: Bytes, _message: Message,) -> bool { assert(_metadata.clone().data().len() > 0, Errors::EMPTY_METADATA); let digest = digest(_metadata.clone(), _message.clone()); let (validators, threshold) = self.validators_and_threshold(_message); diff --git a/contracts/src/contracts/mocks/ism.cairo b/contracts/src/contracts/mocks/ism.cairo index 818b06d..77482d5 100644 --- a/contracts/src/contracts/mocks/ism.cairo +++ b/contracts/src/contracts/mocks/ism.cairo @@ -17,30 +17,26 @@ pub mod ism { ModuleType::MESSAGE_ID_MULTISIG(starknet::get_contract_address()) } - fn verify( - self: @ContractState, - _metadata: Bytes, - _message: Message, - ) -> bool { + fn verify(self: @ContractState, _metadata: Bytes, _message: Message,) -> bool { true } fn validators_and_threshold( self: @ContractState, _message: Message ) -> (Span, u32) { - (array![].span(),0) + (array![].span(), 0) } - - fn get_validators(self: @ContractState) -> Span{ + + fn get_validators(self: @ContractState) -> Span { array![].span() } - - fn get_threshold(self: @ContractState) -> u32{ + + fn get_threshold(self: @ContractState) -> u32 { 0 } - - fn set_validators(ref self: ContractState, _validators: Span){} - - fn set_threshold(ref self: ContractState, _threshold: u32){} + + fn set_validators(ref self: ContractState, _validators: Span) {} + + fn set_threshold(ref self: ContractState, _threshold: u32) {} } } diff --git a/contracts/src/interfaces.cairo b/contracts/src/interfaces.cairo index df5c698..b87b1f7 100644 --- a/contracts/src/interfaces.cairo +++ b/contracts/src/interfaces.cairo @@ -104,11 +104,7 @@ pub trait IInterchainSecurityModule { /// * `_metadata` - Off-chain metadata provided by a relayer, specific to the security model encoded by /// the module (e.g. validator signatures) /// * `_message` - Hyperlane encoded interchain message - fn verify( - self: @TContractState, - _metadata: Bytes, - _message: Message, - ) -> bool; + fn verify(self: @TContractState, _metadata: Bytes, _message: Message,) -> bool; fn validators_and_threshold( self: @TContractState, _message: Message diff --git a/contracts/src/tests/setup.cairo b/contracts/src/tests/setup.cairo index f38d58d..3e167f1 100644 --- a/contracts/src/tests/setup.cairo +++ b/contracts/src/tests/setup.cairo @@ -3,8 +3,9 @@ use hyperlane_starknet::contracts::mocks::message_recipient::message_recipient; use hyperlane_starknet::interfaces::{ IMailboxDispatcher, IMailboxDispatcherTrait, IMessageRecipientDispatcher, IMessageRecipientDispatcherTrait, IInterchainSecurityModule, - IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, IValidatorAnnounceDispatcher, - IValidatorAnnounceDispatcherTrait, IMailboxClientDispatcher, IMailboxClientDispatcherTrait + IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, + IValidatorAnnounceDispatcher, IValidatorAnnounceDispatcherTrait, IMailboxClientDispatcher, + IMailboxClientDispatcherTrait }; use snforge_std::{ declare, ContractClassTrait, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn diff --git a/contracts/src/tests/test_multisig.cairo b/contracts/src/tests/test_multisig.cairo index 6964712..8caf13d 100644 --- a/contracts/src/tests/test_multisig.cairo +++ b/contracts/src/tests/test_multisig.cairo @@ -8,12 +8,13 @@ use hyperlane_starknet::contracts::mailbox::mailbox; use hyperlane_starknet::interfaces::IMessageRecipientDispatcherTrait; use hyperlane_starknet::interfaces::{ IMailbox, IMailboxDispatcher, IMailboxDispatcherTrait, ModuleType, - IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, IInterchainSecurityModule + IInterchainSecurityModuleDispatcher, IInterchainSecurityModuleDispatcherTrait, + IInterchainSecurityModule }; use hyperlane_starknet::tests::setup::{ - setup, mock_setup, setup_messageid_multisig_ism, OWNER, NEW_OWNER, - VALIDATOR_ADDRESS, VALIDATOR_PUBLIC_KEY, setup_validator_announce, get_message_and_signature, - LOCAL_DOMAIN, DESTINATION_DOMAIN, RECIPIENT_ADDRESS + setup, mock_setup, setup_messageid_multisig_ism, OWNER, NEW_OWNER, VALIDATOR_ADDRESS, + VALIDATOR_PUBLIC_KEY, setup_validator_announce, get_message_and_signature, LOCAL_DOMAIN, + DESTINATION_DOMAIN, RECIPIENT_ADDRESS }; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; From c5539e9ff05662b3c0e4003043aff926dba45c9e Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 15 May 2024 14:26:23 +0200 Subject: [PATCH 21/26] fix: working dir --- .github/workflows/release.yml | 2 ++ .github/workflows/test.yml | 2 ++ .snfoundry_cache/.prev_tests_failed | 1 - 3 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 .snfoundry_cache/.prev_tests_failed diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 997ced2..c32b618 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,8 @@ on: - 'v*.*.*' jobs: + env: + working-directory: ./contracts artifact: permissions: contents: write diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 356e114..494d8fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,6 +3,8 @@ on: push: pull_request: jobs: + env: + working-directory: ./contracts check: runs-on: ubuntu-latest steps: diff --git a/.snfoundry_cache/.prev_tests_failed b/.snfoundry_cache/.prev_tests_failed deleted file mode 100644 index 44ee8b0..0000000 --- a/.snfoundry_cache/.prev_tests_failed +++ /dev/null @@ -1 +0,0 @@ -hyperlane_starknet::tests::test_multisig::test_message_id_multisig_verify From f5784df67c73c3fa6d2d6260f051a1c979b8e8e4 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 15 May 2024 14:30:32 +0200 Subject: [PATCH 22/26] fix: ci working directory --- .github/workflows/release.yml | 6 ++++++ .github/workflows/test.yml | 11 ++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c32b618..93b800b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,12 +18,15 @@ jobs: - uses: actions/checkout@v4 - uses: software-mansion/setup-scarb@v1 - name: Build contracts + working-directory: ./contracts run: scarb build - name: Archive contracts + working-directory: ./contracts run: | mkdir -p filtered_artifacts find ./contracts/target/dev -type f -name '*.contract_class.json' -exec cp {} filtered_artifacts/ \; - name: Generate checksums + working-directory: ./contracts run: | cd filtered_artifacts for file in *; do @@ -31,6 +34,7 @@ jobs: md5sum "$file" > "$file.md5" done - name: Build artifact zip + working-directory: ./contracts run: | cd filtered_artifacts zip -r ../hyperlane-starknet-${{ github.ref_name }}.zip . @@ -39,11 +43,13 @@ jobs: md5sum hyperlane-starknet-${{ github.ref_name }}.zip > hyperlane-starknet-${{ github.ref_name }}.CHECKSUM.MD5 - name: Find zip files + working-directory: ./contracts run: | find ./filtered_artifacts -type f -name '*.zip' -exec echo "::set-output name=zip_files::{}" \; id: find_zip_files - name: Release Artifact + working-directory: ./contracts uses: softprops/action-gh-release@v1 with: files: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 494d8fa..fa3c584 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,14 +3,15 @@ on: push: pull_request: jobs: - env: - working-directory: ./contracts check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: software-mansion/setup-scarb@v1 - uses: foundry-rs/setup-snfoundry@v3 - - run: scarb fmt --check - - run: scarb build - - run: snforge test \ No newline at end of file + - working-directory: ./contracts + run: scarb fmt --check + - working-directory: ./contracts + run: scarb build + - working-directory: ./contracts + run: snforge test \ No newline at end of file From 817aa0706bc49295a1936990e3a85c7beab41d31 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 15 May 2024 14:34:54 +0200 Subject: [PATCH 23/26] fix: typo --- .github/workflows/release.yml | 12 ++++++------ .github/workflows/test.yml | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 93b800b..dd98c94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,15 +18,15 @@ jobs: - uses: actions/checkout@v4 - uses: software-mansion/setup-scarb@v1 - name: Build contracts - working-directory: ./contracts + working-directory: ${{ env.working-directory}} run: scarb build - name: Archive contracts - working-directory: ./contracts + working-directory: ${{ env.working-directory}} run: | mkdir -p filtered_artifacts find ./contracts/target/dev -type f -name '*.contract_class.json' -exec cp {} filtered_artifacts/ \; - name: Generate checksums - working-directory: ./contracts + working-directory: ${{ env.working-directory}} run: | cd filtered_artifacts for file in *; do @@ -34,7 +34,7 @@ jobs: md5sum "$file" > "$file.md5" done - name: Build artifact zip - working-directory: ./contracts + working-directory: ${{ env.working-directory}} run: | cd filtered_artifacts zip -r ../hyperlane-starknet-${{ github.ref_name }}.zip . @@ -43,13 +43,13 @@ jobs: md5sum hyperlane-starknet-${{ github.ref_name }}.zip > hyperlane-starknet-${{ github.ref_name }}.CHECKSUM.MD5 - name: Find zip files - working-directory: ./contracts + working-directory: ${{ env.working-directory}} run: | find ./filtered_artifacts -type f -name '*.zip' -exec echo "::set-output name=zip_files::{}" \; id: find_zip_files - name: Release Artifact - working-directory: ./contracts + working-directory: ${{ env.working-directory}} uses: softprops/action-gh-release@v1 with: files: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa3c584..9eb65e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,15 +3,17 @@ on: push: pull_request: jobs: + env: + working-directory: ./contracts check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: software-mansion/setup-scarb@v1 - uses: foundry-rs/setup-snfoundry@v3 - - working-directory: ./contracts + - working-directory: ${{ env.working-directory}} run: scarb fmt --check - - working-directory: ./contracts + - working-directory: ${{ env.working-directory}} run: scarb build - - working-directory: ./contracts + - working-directory: ${{ env.working-directory}} run: snforge test \ No newline at end of file From bee2d655a56fae842b9df3f8a819a533b37c7d41 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 15 May 2024 14:37:08 +0200 Subject: [PATCH 24/26] fix: typo --- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd98c94..1a9818f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,14 +6,14 @@ on: - 'v*.*.*' jobs: - env: - working-directory: ./contracts artifact: permissions: contents: write pull-requests: write name: artifact runs-on: ubuntu-latest + env: + working-directory: ./contracts steps: - uses: actions/checkout@v4 - uses: software-mansion/setup-scarb@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9eb65e7..8d503ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,10 +3,10 @@ on: push: pull_request: jobs: - env: - working-directory: ./contracts check: runs-on: ubuntu-latest + env: + working-directory: ./contracts steps: - uses: actions/checkout@v3 - uses: software-mansion/setup-scarb@v1 From 30d118ce53100b7671b1c2992e55b74f1bbc5e45 Mon Sep 17 00:00:00 2001 From: JordyRo1 Date: Wed, 15 May 2024 14:47:23 +0200 Subject: [PATCH 25/26] fix: release dir --- .github/workflows/release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a9818f..2a14d4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,12 +49,11 @@ jobs: id: find_zip_files - name: Release Artifact - working-directory: ${{ env.working-directory}} uses: softprops/action-gh-release@v1 with: files: | - hyperlane-starknet-${{ github.ref_name }}.zip - hyperlane-starknet-${{ github.ref_name }}.CHECKSUM - hyperlane-starknet-${{ github.ref_name }}.CHECKSUM.MD5 + ./contracts/hyperlane-starknet-${{ github.ref_name }}.zip + ./contracts/hyperlane-starknet-${{ github.ref_name }}.CHECKSUM + ./contracts/hyperlane-starknet-${{ github.ref_name }}.CHECKSUM.MD5 \ No newline at end of file From be370f0561e4887bc906c0da1fc21180cf51ffff Mon Sep 17 00:00:00 2001 From: 0xevolve Date: Wed, 15 May 2024 13:56:09 +0100 Subject: [PATCH 26/26] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a14d4a..c0937ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: working-directory: ${{ env.working-directory}} run: | mkdir -p filtered_artifacts - find ./contracts/target/dev -type f -name '*.contract_class.json' -exec cp {} filtered_artifacts/ \; + find ./target/dev -type f -name '*.contract_class.json' -exec cp {} filtered_artifacts/ \; - name: Generate checksums working-directory: ${{ env.working-directory}} run: |