Skip to content

Commit

Permalink
Merge branch 'main' into feat/add-more-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
EvolveArt committed May 28, 2024
2 parents bf44838 + b71a495 commit b8d105d
Show file tree
Hide file tree
Showing 20 changed files with 775 additions and 67 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ jobs:

- uses: software-mansion/setup-scarb@v1
- uses: foundry-rs/setup-snfoundry@v3

with:
starknet-foundry-version: '0.22.0'
- working-directory: ${{ env.working-directory}}
run: scarb fmt --check

Expand Down
1 change: 1 addition & 0 deletions contracts/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scarb 2.6.3
4 changes: 2 additions & 2 deletions contracts/Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ dependencies = [

[[package]]
name = "openzeppelin"
version = "0.10.0"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?rev=f22438c#f22438cca0e81c807d7c1742e83050ebd0ffcb3b"
version = "0.13.0"
source = "git+https://github.com/OpenZeppelin/cairo-contracts.git#978b4e75209da355667d8954d2450e32bd71fe49"

[[package]]
name = "snforge_std"
Expand Down
2 changes: 1 addition & 1 deletion contracts/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ cairo-version = "2.6.3"
[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" }
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git" }


[dev-dependencies]
Expand Down
78 changes: 78 additions & 0 deletions contracts/src/contracts/hooks/merkle_tree_hook.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#[starknet::contract]
pub mod merkle_tree_hook {
use alexandria_bytes::Bytes;
use hyperlane_starknet::contracts::libs::merkle_lib::merkle_lib::{Tree, IMerkleLib};
use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait};
use hyperlane_starknet::interfaces::{
IMailboxClientDispatcher, IMailboxClientDispatcherTrait, Types, IMerkleTreeHook
};
use starknet::ContractAddress;


#[storage]
struct Storage {
mailbox_client: ContractAddress,
tree: Tree
}


pub mod Errors {
pub const MESSAGE_NOT_DISPATCHING: felt252 = 'Message not dispatching';
}

#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
InsertedIntoTree: InsertedIntoTree
}

#[derive(starknet::Event, Drop)]
pub struct InsertedIntoTree {
pub id: u256,
pub index: u32
}

#[constructor]
fn constructor(ref self: ContractState, _mailbox_client: ContractAddress) {
self.mailbox_client.write(_mailbox_client);
}

#[abi(embed_v0)]
impl IMerkleTreeHookImpl of IMerkleTreeHook<ContractState> {
fn count(self: @ContractState) -> u32 {
self.tree.read().count.try_into().unwrap()
}

fn root(self: @ContractState) -> u256 {
self.tree.read().root()
}

fn tree(self: @ContractState) -> Tree {
self.tree.read()
}

fn latest_checkpoint(self: @ContractState) -> (u256, u32) {
(self.root(), self.count())
}

fn hook_type(self: @ContractState) -> Types {
Types::MERKLE_TREE(())
}
}

fn _post_dispatch(ref self: ContractState, _metadata: Bytes, _message: Message) {
let (id, _) = MessageTrait::format_message(_message);
let mailbox_client = IMailboxClientDispatcher {
contract_address: self.mailbox_client.read()
};
assert(mailbox_client._is_latest_dispatched(id), Errors::MESSAGE_NOT_DISPATCHING);
let index = self.count();
let mut tree = self.tree.read();
IMerkleLib::insert(ref tree, id);
self.emit(InsertedIntoTree { id, index });
}

fn _quote_dispatch(ref self: ContractState, _metadata: Bytes, _message: Message) -> u256 {
0_u256
}
}
150 changes: 150 additions & 0 deletions contracts/src/contracts/isms/aggregation/aggregation.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#[starknet::contract]
pub mod aggregation {
use alexandria_bytes::Bytes;
use hyperlane_starknet::contracts::libs::aggregation_ism_metadata::aggregation_ism_metadata::AggregationIsmMetadata;
use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait};
use hyperlane_starknet::interfaces::{
IAggregationDispatcher, IAggregation, IAggregationDispatcherTrait, ModuleType,
IInterchainSecurityModule, 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<ContractState>;
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>;
impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>;


#[storage]
struct Storage {
modules: LegacyMap::<ContractAddress, ContractAddress>,
threshold: u8,
#[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,
}


pub mod Errors {
pub const VERIFICATION_FAILED: felt252 = 'Verification failed';
pub const THRESHOLD_NOT_REACHED: felt252 = 'Threshold not reached';
pub const MODULE_ADDRESS_CANNOT_BE_NULL: felt252 = 'Module address cannot be null';
pub const THRESHOLD_NOT_SET: felt252 = 'Threshold not set';
}

#[constructor]
fn constructor(ref self: ContractState, _owner: ContractAddress) {
self.ownable.initializer(_owner);
}


#[abi(embed_v0)]
impl IAggregationImpl of IAggregation<ContractState> {
fn module_type(self: @ContractState) -> ModuleType {
ModuleType::AGGREGATION(starknet::get_contract_address())
}

fn modules_and_threshold(
self: @ContractState, _message: Message
) -> (Span<ContractAddress>, u8) {
// THE USER CAN DEFINE HERE CONDITIONS FOR THE MODULE AND THRESHOLD SELECTION
let threshold = self.threshold.read();
(build_modules_span(self), threshold)
}

fn verify(self: @ContractState, _metadata: Bytes, _message: Message,) -> bool {
let (isms, mut threshold) = self.modules_and_threshold(_message.clone());
assert(threshold != 0, Errors::THRESHOLD_NOT_SET);
let modules = build_modules_span(self);
let mut cur_idx: u8 = 0;
loop {
if (cur_idx.into() == isms.len()) {
break ();
}
if (!AggregationIsmMetadata::has_metadata(_metadata.clone(), cur_idx)) {
cur_idx += 1;
continue;
}
let ism = IInterchainSecurityModuleDispatcher {
contract_address: *modules.at(cur_idx.into())
};
let metadata = AggregationIsmMetadata::metadata_at(_metadata.clone(), cur_idx);
assert(ism.verify(metadata, _message.clone()), Errors::VERIFICATION_FAILED);
threshold -= 1;
cur_idx += 1;
};
assert(threshold == 0, Errors::THRESHOLD_NOT_REACHED);
true
}

fn get_modules(self: @ContractState) -> Span<ContractAddress> {
build_modules_span(self)
}

fn get_threshold(self: @ContractState) -> u8 {
self.threshold.read()
}

fn set_modules(ref self: ContractState, _modules: Span<ContractAddress>) {
self.ownable.assert_only_owner();
let mut last_module = find_last_module(@self);
let mut cur_idx = 0;
loop {
if (cur_idx == _modules.len()) {
break ();
}
let module = *_modules.at(cur_idx);
assert(
module != contract_address_const::<0>(), Errors::MODULE_ADDRESS_CANNOT_BE_NULL
);
self.modules.write(last_module, module);
cur_idx += 1;
last_module = module;
}
}

fn set_threshold(ref self: ContractState, _threshold: u8) {
self.ownable.assert_only_owner();
self.threshold.write(_threshold);
}
}

fn find_last_module(self: @ContractState) -> ContractAddress {
let mut current_module = self.modules.read(contract_address_const::<0>());
loop {
let next_module = self.modules.read(current_module);
if next_module == contract_address_const::<0>() {
break current_module;
}
current_module = next_module;
}
}

fn build_modules_span(self: @ContractState) -> Span<ContractAddress> {
let mut cur_address = contract_address_const::<0>();
let mut modules = array![];
loop {
let next_address = self.modules.read(cur_address);
if (next_address == contract_address_const::<0>()) {
break ();
}
modules.append(cur_address);
cur_address = next_address
};
modules.span()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ pub mod messageid_multisig_ism {
let digest = digest(_metadata.clone(), _message.clone());
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;

// for each couple (sig_s, sig_r) extracted from the metadata
Expand Down
40 changes: 23 additions & 17 deletions contracts/src/contracts/isms/multisig/validator_announce.cairo
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#[starknet::contract]
pub mod validator_announce {
use alexandria_bytes::{Bytes, BytesTrait};
use alexandria_data_structures::array_ext::ArrayTraitExt;
use core::keccak::keccak_u256s_be_inputs;
use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::{
HYPERLANE_ANNOUNCEMENT, ETH_SIGNED_MESSAGE
HYPERLANE_ANNOUNCEMENT
};
use hyperlane_starknet::interfaces::IValidatorAnnounce;
use hyperlane_starknet::interfaces::{IMailboxClientDispatcher, IMailboxClientDispatcherTrait};
use hyperlane_starknet::utils::keccak256::reverse_endianness;
use hyperlane_starknet::utils::keccak256::{reverse_endianness, to_eth_signature};
use hyperlane_starknet::utils::store_arrays::StoreFelt252Array;

use starknet::ContractAddress;
Expand All @@ -33,7 +34,7 @@ pub mod validator_announce {
#[derive(starknet::Event, Drop)]
pub struct ValidatorAnnouncement {
pub validator: EthAddress,
pub storage_location: felt252
pub storage_location: Array<felt252>
}

pub mod Errors {
Expand All @@ -51,16 +52,22 @@ pub mod validator_announce {
fn announce(
ref self: ContractState,
_validator: EthAddress,
_storage_location: felt252,
mut _storage_location: Array<felt252>,
_signature: Bytes
) -> bool {
let felt252_validator: felt252 = _validator.into();
let mut input: Array<u256> = array![
felt252_validator.into(), _storage_location.into(),
];
let mut _input: Array<u256> = array![felt252_validator.into()];
let mut u256_storage_location: Array<u256> = array![];
loop {
match _storage_location.pop_front() {
Option::Some(storage) => u256_storage_location.append(storage.into()),
Option::None(()) => { break (); },
}
};
let input = _input.concat(@u256_storage_location);
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 announcement_digest = self.get_announcement_digest(u256_storage_location);
let signature: Signature = convert_to_signature(_signature);
assert(
bool_is_eth_signature_valid(announcement_digest, signature, _validator),
Expand All @@ -73,8 +80,7 @@ pub mod validator_announce {
self.validators.write(last_validator, _validator);
}
};
let mut storage_locations = self.storage_location.read(_validator);
storage_locations.append(_storage_location);
self.storage_location.write(_validator, _storage_location.clone());
self
.emit(
ValidatorAnnouncement {
Expand Down Expand Up @@ -103,21 +109,21 @@ pub mod validator_announce {
fn get_announced_validators(self: @ContractState) -> Span<EthAddress> {
build_validators_array(self)
}
fn get_announcement_digest(self: @ContractState, _storage_location: felt252) -> u256 {
fn get_announcement_digest(self: @ContractState, _storage_location: Array<u256>) -> u256 {
let domain_hash = domain_hash(self);
let mut input: Array<u256> = array![
ETH_SIGNED_MESSAGE.into(), domain_hash.into(), _storage_location.into(),
];
let hash = keccak_u256s_be_inputs(input.span());
reverse_endianness(hash)
let arguments = keccak_u256s_be_inputs(
array![domain_hash.into()].concat(@_storage_location).span()
);
let reverse_args = reverse_endianness(arguments);
to_eth_signature(reverse_args)
}
}


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);
let (_, v) = _signature.read_u8(64);
signature_from_vrs(v.try_into().unwrap(), r, s)
}
fn keccak_hash(_input: Span<u256>) -> u256 {
Expand Down
Loading

0 comments on commit b8d105d

Please sign in to comment.