-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
295 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#[starknet::contract] | ||
pub mod aggregation_hook { | ||
use alexandria_bytes::{Bytes, BytesTrait}; | ||
use hyperlane_starknet::contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::{ | ||
StandardHookMetadata, VARIANT, | ||
}; | ||
use hyperlane_starknet::contracts::libs::message::Message; | ||
use hyperlane_starknet::interfaces::{ | ||
IPostDispatchHook, Types, IPostDispatchHookDispatcher, IPostDispatchHookDispatcherTrait, ETH_ADDRESS | ||
}; | ||
use starknet::{ContractAddress, get_contract_address}; | ||
use openzeppelin::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; | ||
|
||
#[storage] | ||
struct Storage { | ||
hooks: LegacyMap::<usize, ContractAddress>, | ||
hook_count: usize, | ||
} | ||
|
||
mod Errors { | ||
pub const INVALID_METADATA_VARIANT: felt252 = 'Invalid metadata variant'; | ||
pub const INSUFFICIENT_BALANCE: felt252 = 'Insufficient balance'; | ||
pub const INSUFFICIENT_FUNDS: felt252 = 'Insufficient funds'; | ||
} | ||
|
||
#[constructor] | ||
fn constructor(ref self: ContractState, hooks: Span<ContractAddress>) { | ||
let mut i = 0; | ||
loop { | ||
if i >= hooks.len() { | ||
break; | ||
} | ||
|
||
self.hooks.write(i, *hooks.at(i)); | ||
i += 1; | ||
}; | ||
|
||
self.hook_count.write(hooks.len()); | ||
} | ||
|
||
#[abi(embed_v0)] | ||
impl IPostDispatchHookImpl of IPostDispatchHook<ContractState> { | ||
fn hook_type(self: @ContractState) -> Types { | ||
Types::AGGREGATION(()) | ||
} | ||
|
||
fn supports_metadata(self: @ContractState, _metadata: Bytes) -> bool { | ||
_metadata.size() == 0 || StandardHookMetadata::variant(_metadata) == VARIANT.into() | ||
} | ||
|
||
fn post_dispatch( | ||
ref self: ContractState, _metadata: Bytes, _message: Message, _fee_amount: u256 | ||
) { | ||
assert(self.supports_metadata(_metadata.clone()), Errors::INVALID_METADATA_VARIANT); | ||
|
||
let token_dispatcher = IERC20Dispatcher { contract_address: ETH_ADDRESS() }; | ||
let agg_hook_address = get_contract_address(); | ||
|
||
let balance = token_dispatcher.balance_of(agg_hook_address); | ||
assert(balance >= _fee_amount, Errors::INSUFFICIENT_BALANCE); | ||
|
||
let hook_count = self.hook_count.read(); | ||
let mut remaining_fees = _fee_amount; | ||
let mut i = 0_usize; | ||
loop { | ||
if i >= hook_count { | ||
break; | ||
} | ||
|
||
let hook_address = self.hooks.read(i); | ||
let hook_dispatcher = IPostDispatchHookDispatcher { contract_address: hook_address }; | ||
|
||
let quote = hook_dispatcher.quote_dispatch(_metadata.clone(), _message.clone()); | ||
assert(quote <= remaining_fees, Errors::INSUFFICIENT_FUNDS); | ||
|
||
token_dispatcher.transfer(hook_address, quote); | ||
remaining_fees -= quote; | ||
|
||
IPostDispatchHookDispatcher { contract_address: hook_address } | ||
.post_dispatch(_metadata.clone(), _message.clone(), quote); | ||
|
||
i += 1; | ||
}; | ||
} | ||
|
||
fn quote_dispatch(ref self: ContractState, _metadata: Bytes, _message: Message) -> u256 { | ||
assert(self.supports_metadata(_metadata.clone()), Errors::INVALID_METADATA_VARIANT); | ||
|
||
let hook_count = self.hook_count.read(); | ||
let mut i = 0_usize; | ||
let mut total = 0_u256; | ||
loop { | ||
if i >= hook_count { | ||
break; | ||
} | ||
|
||
let contract_address = self.hooks.read(i); | ||
|
||
let value = IPostDispatchHookDispatcher { contract_address } | ||
.quote_dispatch(_metadata.clone(), _message.clone()); | ||
|
||
total += value; | ||
i += 1; | ||
}; | ||
|
||
total | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
use alexandria_bytes::{Bytes, BytesTrait}; | ||
use openzeppelin::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; | ||
use openzeppelin::access::ownable::interface::{IOwnableDispatcher, IOwnableDispatcherTrait}; | ||
use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; | ||
use hyperlane_starknet::contracts::mocks::hook::{IMockHookDispatcher, IMockHookDispatcherTrait}; | ||
use hyperlane_starknet::interfaces::{ | ||
Types, IPostDispatchHookDispatcher, IPostDispatchHookDispatcherTrait, | ||
}; | ||
use hyperlane_starknet::tests::setup::{setup_mock_token, setup_aggregation_hook, OWNER}; | ||
use snforge_std::{declare, ContractClassTrait, start_prank, CheatTarget, stop_prank}; | ||
use starknet::{ContractAddress}; | ||
|
||
fn _build_metadata() -> Bytes { | ||
let mut metadata = BytesTrait::new_empty(); | ||
let variant = 1; | ||
metadata.append_u16(variant); | ||
metadata | ||
} | ||
|
||
fn _build_hook_list(quotes: @Array<u256>) -> Span<ContractAddress> { | ||
let mut hooks = array![]; | ||
let mock_hook = declare("hook").unwrap(); | ||
|
||
let mut i = 0; | ||
loop { | ||
if i >= quotes.len() { | ||
break; | ||
} | ||
|
||
let (contract_address, _) = mock_hook.deploy(@array![]).unwrap(); | ||
IMockHookDispatcher { contract_address }.set_quote_dispatch(*quotes.at(i)); | ||
|
||
hooks.append(contract_address); | ||
|
||
i += 1; | ||
}; | ||
|
||
hooks.span() | ||
} | ||
|
||
fn _setup_eth_balance(token_dispatcher: IERC20Dispatcher, recipient: ContractAddress, amount: u256) { | ||
let ownable = IOwnableDispatcher { contract_address: token_dispatcher.contract_address }; | ||
start_prank(CheatTarget::One(ownable.contract_address), OWNER()); | ||
token_dispatcher.transfer(recipient, amount); | ||
assert_eq!(token_dispatcher.balance_of(recipient), amount); | ||
stop_prank(CheatTarget::One(ownable.contract_address)); | ||
} | ||
|
||
#[test] | ||
fn test_hook_type() { | ||
setup_mock_token(); | ||
let hooks = _build_hook_list(@array![100_u256, 200_u256, 300_u256]); | ||
let post_dispatch_hook = setup_aggregation_hook(@hooks); | ||
|
||
assert_eq!(post_dispatch_hook.hook_type(), Types::AGGREGATION(())); | ||
} | ||
|
||
#[test] | ||
fn test_aggregate_quote_dispatch() { | ||
// arrange | ||
setup_mock_token(); | ||
let hooks = _build_hook_list(@array![100_u256, 200_u256, 300_u256]); | ||
let post_dispatch_hook = setup_aggregation_hook(@hooks); | ||
|
||
let expected_quote = 600_u256; | ||
|
||
// act | ||
let quote = post_dispatch_hook.quote_dispatch(_build_metadata(), MessageTrait::default()); | ||
|
||
// assert | ||
assert_eq!(quote, expected_quote); | ||
|
||
let mut i = 0; | ||
loop { | ||
if i >= hooks.len() { | ||
break; | ||
} | ||
|
||
let contract_address = *hooks.at(i); | ||
assert_eq!(IMockHookDispatcher { contract_address }.get_quote_dispatch_calls(), 1); | ||
|
||
i += 1; | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_aggregate_post_dispatch() { | ||
// arrange | ||
let token_dispatcher = setup_mock_token(); | ||
|
||
let hooks = _build_hook_list(@array![100_u256, 200_u256, 300_u256]); | ||
let fee_amount = 600_u256; | ||
let post_dispatch_hook = setup_aggregation_hook(@hooks); | ||
|
||
_setup_eth_balance(token_dispatcher, post_dispatch_hook.contract_address, fee_amount); | ||
|
||
// act | ||
post_dispatch_hook.post_dispatch(_build_metadata(), MessageTrait::default(), fee_amount); | ||
|
||
// assert | ||
let mut i = 0; | ||
loop { | ||
if i >= hooks.len() { | ||
break; | ||
} | ||
|
||
let contract_address = *hooks.at(i); | ||
assert_eq!(IMockHookDispatcher { contract_address }.get_post_dispatch_calls(), 1); | ||
|
||
i += 1; | ||
} | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected: ('Insufficient balance',))] | ||
fn test_aggregate_post_dispatch_insufficient_balance() { | ||
// arrange | ||
setup_mock_token(); | ||
|
||
let hooks = _build_hook_list(@array![100_u256, 200_u256, 300_u256]); | ||
let fee_amount = 600_u256; | ||
let post_dispatch_hook = setup_aggregation_hook(@hooks); | ||
|
||
// act | ||
post_dispatch_hook.post_dispatch(_build_metadata(), MessageTrait::default(), fee_amount); | ||
} | ||
|
||
#[test] | ||
#[should_panic(expected: ('Insufficient funds',))] | ||
fn test_aggregate_post_dispatch_insufficient_funds() { | ||
// arrange | ||
let token_dispatcher = setup_mock_token(); | ||
|
||
let hooks = _build_hook_list(@array![100_u256, 200_u256, 300_u256]); | ||
let fee_amount = 599_u256; | ||
let post_dispatch_hook = setup_aggregation_hook(@hooks); | ||
|
||
_setup_eth_balance(token_dispatcher, post_dispatch_hook.contract_address, fee_amount); | ||
|
||
// act | ||
post_dispatch_hook.post_dispatch(_build_metadata(), MessageTrait::default(), fee_amount); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters