diff --git a/cairo/crates/contracts/tests/libs/test_rate_limited.cairo b/cairo/crates/contracts/tests/libs/test_rate_limited.cairo index 1ff3ca1..587c0b2 100644 --- a/cairo/crates/contracts/tests/libs/test_rate_limited.cairo +++ b/cairo/crates/contracts/tests/libs/test_rate_limited.cairo @@ -110,7 +110,7 @@ fn test_ctor_should_panic_when_low_capacity() { let rate_limited_contract = declare("MockRateLimited").unwrap(); let owner = starknet::contract_address_const::<'OWNER'>(); let mut ctor_calldata: Array = array![]; - (RateLimitedComponent::DURATION - 1).serialize(ref ctor_calldata); + (RateLimitedComponent::DURATION.into() - 1_u256).serialize(ref ctor_calldata); owner.serialize(ref ctor_calldata); rate_limited_contract.deploy(@ctor_calldata).unwrap(); } diff --git a/cairo/crates/mocks/src/mock_mailbox.cairo b/cairo/crates/mocks/src/mock_mailbox.cairo index beb3885..30bb1c8 100644 --- a/cairo/crates/mocks/src/mock_mailbox.cairo +++ b/cairo/crates/mocks/src/mock_mailbox.cairo @@ -271,12 +271,43 @@ pub mod MockMailbox { ); self.emit(DispatchId { id }); + // HOOKS + + let required_hook_address = self.required_hook.read(); let required_hook = ITestPostDispatchHookDispatcher { - contract_address: self.required_hook.read() + contract_address: required_hook_address }; + let mut required_fee = required_hook + .quote_dispatch(hook_metadata.clone(), message.clone()); + let hook_dispatcher = ITestPostDispatchHookDispatcher { contract_address: hook }; + let default_fee = hook_dispatcher + .quote_dispatch(hook_metadata.clone(), message.clone()); + + assert(fee_amount >= required_fee + default_fee, Errors::NOT_ENOUGH_FEE_PROVIDED); + + let caller_address = get_caller_address(); + let contract_address = get_contract_address(); + + let token_dispatcher = ERC20ABIDispatcher { contract_address: self.eth_address.read() }; + let user_balance = token_dispatcher.balanceOf(caller_address); + assert(user_balance >= required_fee + default_fee, Errors::INSUFFICIENT_BALANCE); + + assert( + token_dispatcher.allowance(caller_address, contract_address) >= fee_amount, + Errors::INSUFFICIENT_ALLOWANCE + ); + + if (required_fee > 0) { + token_dispatcher.transferFrom(caller_address, required_hook_address, required_fee); + } + required_hook.post_dispatch(hook_metadata.clone(), message.clone()); - let hook = ITestPostDispatchHookDispatcher { contract_address: hook }; - hook.post_dispatch(hook_metadata, message.clone()); + if (default_fee > 0) { + token_dispatcher.transferFrom(caller_address, hook, default_fee); + } + + hook_dispatcher.post_dispatch(hook_metadata, message.clone()); + let remote_mailbox = self.remote_mailboxes.read(destination_domain); assert!(remote_mailbox != contract_address_const::<0>()); IMockMailboxDispatcher { contract_address: remote_mailbox } @@ -539,78 +570,4 @@ pub mod MockMailbox { } ) } - #[generate_trait] - impl Private of PrivateTrait { - fn _dispatch( - ref self: ContractState, - _destination_domain: u32, - _recipient_address: u256, - _message_body: Bytes, - _fee_amount: u256, - _custom_hook_metadata: Option, - _custom_hook: Option - ) -> 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 (id, message) = build_message( - @self, _destination_domain, _recipient_address, _message_body - ); - self.latest_dispatched_id.write(id); - let current_nonce = self.nonce.read(); - self.nonce.write(current_nonce + 1); - let caller: felt252 = get_caller_address().into(); - self - .emit( - Dispatch { - sender: caller.into(), - destination_domain: _destination_domain, - recipient_address: _recipient_address, - message: message.clone() - } - ); - self.emit(DispatchId { id: id }); - - // HOOKS - - let required_hook_address = self.required_hook.read(); - let required_hook = ITestPostDispatchHookDispatcher { - contract_address: required_hook_address - }; - let mut required_fee = required_hook - .quote_dispatch(hook_metadata.clone(), message.clone()); - let hook_dispatcher = ITestPostDispatchHookDispatcher { contract_address: hook }; - let default_fee = hook_dispatcher - .quote_dispatch(hook_metadata.clone(), message.clone()); - - assert(_fee_amount >= required_fee + default_fee, Errors::NOT_ENOUGH_FEE_PROVIDED); - - let caller_address = get_caller_address(); - let contract_address = get_contract_address(); - - let token_dispatcher = ERC20ABIDispatcher { contract_address: self.eth_address.read() }; - let user_balance = token_dispatcher.balanceOf(caller_address); - assert(user_balance >= required_fee + default_fee, Errors::INSUFFICIENT_BALANCE); - - assert( - token_dispatcher.allowance(caller_address, contract_address) >= _fee_amount, - Errors::INSUFFICIENT_ALLOWANCE - ); - - if (required_fee > 0) { - token_dispatcher.transfer_from(caller_address, required_hook_address, required_fee); - } - required_hook.post_dispatch(hook_metadata.clone(), message.clone()); - if (default_fee > 0) { - token_dispatcher.transfer_from(caller_address, hook, default_fee); - } - hook_dispatcher.post_dispatch(hook_metadata, message.clone()); - id - } - } } diff --git a/cairo/crates/mocks/src/test_interchain_gas_payment.cairo b/cairo/crates/mocks/src/test_interchain_gas_payment.cairo index f42dcb5..ee4485a 100644 --- a/cairo/crates/mocks/src/test_interchain_gas_payment.cairo +++ b/cairo/crates/mocks/src/test_interchain_gas_payment.cairo @@ -7,6 +7,7 @@ pub trait ITestInterchainGasPayment { fn get_default_gas_usage(self: @TContractState) -> u256; fn gas_price(self: @TContractState) -> u256; fn post_dispatch(ref self: TContractState, metadata: Bytes, message: Message); + fn quote_dispatch(ref self: TContractState, _metadata: Bytes, _message: Message) -> u256; } #[starknet::contract] @@ -20,6 +21,8 @@ pub mod TestInterchainGasPayment { impl OwnableInternalImpl = OwnableComponent::InternalImpl; + pub const DEFAULT_GAS_LIMIT: u256 = 10_000; + #[storage] struct Storage { gas_price: u256, @@ -54,7 +57,12 @@ pub mod TestInterchainGasPayment { fn gas_price(self: @ContractState) -> u256 { self.gas_price.read() } + fn post_dispatch(ref self: ContractState, metadata: Bytes, message: Message) {} + + fn quote_dispatch(ref self: ContractState, _metadata: Bytes, _message: Message) -> u256 { + self.quote_gas_payment(DEFAULT_GAS_LIMIT) + } } #[generate_trait] diff --git a/cairo/crates/token/tests/hyp_erc20/common.cairo b/cairo/crates/token/tests/hyp_erc20/common.cairo index f0d1c4f..c40f218 100644 --- a/cairo/crates/token/tests/hyp_erc20/common.cairo +++ b/cairo/crates/token/tests/hyp_erc20/common.cairo @@ -133,15 +133,16 @@ pub struct Setup { pub igp: ITestInterchainGasPaymentDispatcher, pub erc20_token: ITestERC20Dispatcher, pub eth_token: MockEthDispatcher, - pub mock_mailbox_contract: ContractClass + pub mock_mailbox_contract: ContractClass, + pub test_post_dispatch_hook_contract: ContractClass } pub fn setup() -> Setup { let contract = declare("TestISM").unwrap(); let (default_ism, _) = contract.deploy(@array![]).unwrap(); - let contract = declare("TestPostDispatchHook").unwrap(); - let (noop_hook, _) = contract.deploy(@array![]).unwrap(); + let test_post_dispatch_hook_contract = declare("TestPostDispatchHook").unwrap(); + let (noop_hook, _) = test_post_dispatch_hook_contract.deploy(@array![]).unwrap(); let noop_hook = ITestPostDispatchHookDispatcher { contract_address: noop_hook }; let contract = declare("Ether").unwrap(); @@ -149,7 +150,7 @@ pub fn setup() -> Setup { starknet::get_contract_address().serialize(ref calldata); let (eth_address, _) = contract.deploy_at(@calldata, ETH_ADDRESS()).unwrap(); let eth_token = MockEthDispatcher { contract_address: eth_address }; - eth_token.mint(ALICE(), 10 * E18); + eth_token.mint(ALICE(), 20 * E18); let mock_mailbox_contract = declare("MockMailbox").unwrap(); let (local_mailbox, _) = mock_mailbox_contract @@ -262,7 +263,8 @@ pub fn setup() -> Setup { igp, erc20_token, eth_token, - mock_mailbox_contract + mock_mailbox_contract, + test_post_dispatch_hook_contract } } @@ -406,10 +408,8 @@ pub fn perform_remote_transfer_and_gas_with_hook( } pub fn test_transfer_with_hook_specified(setup: @Setup, fee: u256, metadata: Bytes) { - let contract = declare("TestPostDispatchHook").unwrap(); - let (hook, _) = contract.deploy(@array![]).unwrap(); - let hook = ITestPostDispatchHookDispatcher { contract_address: hook }; - + let (hook_address, _) = setup.test_post_dispatch_hook_contract.deploy(@array![]).unwrap(); + let hook = ITestPostDispatchHookDispatcher { contract_address: hook_address }; hook.set_fee(fee); start_prank(CheatTarget::One((*setup).primary_token.contract_address), ALICE()); @@ -417,11 +417,13 @@ pub fn test_transfer_with_hook_specified(setup: @Setup, fee: u256, metadata: Byt contract_address: (*setup).primary_token.contract_address }; primary_token.approve((*setup).local_token.contract_address, TRANSFER_AMT); + stop_prank(CheatTarget::One((*setup).primary_token.contract_address)); let message_id = perform_remote_transfer_and_gas_with_hook( - setup, 0, TRANSFER_AMT, hook.contract_address, metadata + setup, fee, TRANSFER_AMT, hook.contract_address, metadata ); - + let eth_dispatcher = IERC20Dispatcher { contract_address: *setup.eth_token.contract_address }; + assert_eq!(eth_dispatcher.balance_of(hook_address), fee, "fee didnt transferred"); assert!(hook.message_dispatched(message_id) == true, "Hook did not dispatch"); } diff --git a/cairo/crates/token/tests/hyp_erc20/hyp_erc20_collateral_test.cairo b/cairo/crates/token/tests/hyp_erc20/hyp_erc20_collateral_test.cairo index 8f21b7b..7c16b8b 100644 --- a/cairo/crates/token/tests/hyp_erc20/hyp_erc20_collateral_test.cairo +++ b/cairo/crates/token/tests/hyp_erc20/hyp_erc20_collateral_test.cairo @@ -2,6 +2,7 @@ use alexandria_bytes::{Bytes, BytesTrait}; use contracts::client::gas_router_component::{ GasRouterComponent::GasRouterConfig, IGasRouterDispatcher, IGasRouterDispatcherTrait }; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; use contracts::utils::utils::U256TryIntoContractAddress; use core::integer::BoundedInt; use mocks::{ @@ -66,7 +67,6 @@ fn perform_remote_transfer_collateral( setup: @Setup, collateral: @IHypERC20TestDispatcher, msg_value: u256, - extra_gas: u256, amount: u256, approve: bool ) { @@ -108,12 +108,65 @@ fn process_transfers_collateral( stop_prank(CheatTarget::One((*setup).remote_token.contract_address)); } +pub fn perform_remote_transfer_collateral_and_gas_with_hook( + setup: @Setup, + collateral: @IHypERC20TestDispatcher, + msg_value: u256, + amount: u256, + hook: ContractAddress, + hook_metadata: Bytes +) -> u256 { + // Approve + start_prank(CheatTarget::One(*setup.primary_token.contract_address), ALICE()); + (*setup.primary_token).approve(*collateral.contract_address, amount); + stop_prank(CheatTarget::One(*setup.primary_token.contract_address)); + + // Remote transfer + start_prank(CheatTarget::One(*collateral.contract_address), ALICE()); + let bob_felt: felt252 = BOB().into(); + let bob_address: u256 = bob_felt.into(); + let message_id = (*collateral) + .transfer_remote( + DESTINATION, + bob_address, + amount, + msg_value, + Option::Some(hook_metadata), + Option::Some(hook) + ); + + process_transfers_collateral(setup, collateral, BOB(), amount); + + let remote_token = IERC20Dispatcher { + contract_address: (*setup).remote_token.contract_address + }; + assert_eq!(remote_token.balance_of(BOB()), amount); + + stop_prank(CheatTarget::One(*collateral.contract_address)); + message_id +} + +pub fn test_transfer_collateral_with_hook_specified( + setup: @Setup, collateral: @IHypERC20TestDispatcher, fee: u256, metadata: Bytes +) { + let (hook_address, _) = setup.test_post_dispatch_hook_contract.deploy(@array![]).unwrap(); + let hook = ITestPostDispatchHookDispatcher { contract_address: hook_address }; + hook.set_fee(fee); + + let message_id = perform_remote_transfer_collateral_and_gas_with_hook( + setup, collateral, fee, TRANSFER_AMT, hook.contract_address, metadata + ); + let eth_dispatcher = IERC20Dispatcher { contract_address: *setup.eth_token.contract_address }; + assert_eq!(eth_dispatcher.balance_of(hook_address), fee, "fee didnt transferred"); + assert!(hook.message_dispatched(message_id) == true, "Hook did not dispatch"); +} + #[test] fn test_remote_transfer() { let (collateral, setup) = setup_hyp_erc20_collateral(); let balance_before = collateral.balance_of(ALICE()); start_prank(CheatTarget::One(collateral.contract_address), ALICE()); - perform_remote_transfer_collateral(@setup, @collateral, REQUIRED_VALUE, 0, TRANSFER_AMT, true); + perform_remote_transfer_collateral(@setup, @collateral, REQUIRED_VALUE, TRANSFER_AMT, true); stop_prank(CheatTarget::One(collateral.contract_address)); // Check balance after transfer assert_eq!( @@ -128,7 +181,7 @@ fn test_remote_transfer() { fn test_remote_transfer_invalid_allowance() { let (collateral, setup) = setup_hyp_erc20_collateral(); start_prank(CheatTarget::One(collateral.contract_address), ALICE()); - perform_remote_transfer_collateral(@setup, @collateral, REQUIRED_VALUE, 0, TRANSFER_AMT, false); + perform_remote_transfer_collateral(@setup, @collateral, REQUIRED_VALUE, TRANSFER_AMT, false); stop_prank(CheatTarget::One(collateral.contract_address)); } @@ -142,9 +195,10 @@ fn test_remote_transfer_with_custom_gas_config() { collateral.set_hook(setup.igp.contract_address); let config = array![GasRouterConfig { domain: DESTINATION, gas: GAS_LIMIT }]; collateral.set_destination_gas(Option::Some(config), Option::None, Option::None); + let gas_price = setup.igp.gas_price(); // Do a remote transfer perform_remote_transfer_collateral( - @setup, @collateral, REQUIRED_VALUE, setup.igp.gas_price(), TRANSFER_AMT, true + @setup, @collateral, REQUIRED_VALUE + GAS_LIMIT * gas_price, TRANSFER_AMT, true ); stop_prank(CheatTarget::One(collateral.contract_address)); @@ -154,4 +208,24 @@ fn test_remote_transfer_with_custom_gas_config() { balance_before - TRANSFER_AMT, "Incorrect balance after transfer" ); + let eth_dispatcher = IERC20Dispatcher { contract_address: setup.eth_token.contract_address }; + assert_eq!( + eth_dispatcher.balance_of(setup.igp.contract_address), + GAS_LIMIT * gas_price, + "Gas fee didnt transferred" + ); +} + +#[test] +fn test_erc20_remote_transfer_collateral_with_hook_specified(mut fee: u256, metadata: u256) { + let fee = fee % (TRANSFER_AMT / 10); + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + let (collateral, setup) = setup_hyp_erc20_collateral(); + + let balance_before = collateral.balance_of(ALICE()); + test_transfer_collateral_with_hook_specified(@setup, @collateral, fee, metadata_bytes); + let balance_after = collateral.balance_of(ALICE()); + assert_eq!(balance_after, balance_before - TRANSFER_AMT); } diff --git a/cairo/crates/token/tests/hyp_erc20/hyp_erc20_lockbox_test.cairo b/cairo/crates/token/tests/hyp_erc20/hyp_erc20_lockbox_test.cairo index 95138d0..ad81a82 100644 --- a/cairo/crates/token/tests/hyp_erc20/hyp_erc20_lockbox_test.cairo +++ b/cairo/crates/token/tests/hyp_erc20/hyp_erc20_lockbox_test.cairo @@ -1,13 +1,18 @@ use alexandria_bytes::{Bytes, BytesTrait}; use contracts::client::gas_router_component::GasRouterComponent::GasRouterConfig; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; use core::integer::BoundedInt; use mocks::test_interchain_gas_payment::ITestInterchainGasPaymentDispatcherTrait; use mocks::{ test_erc20::{ITestERC20Dispatcher, ITestERC20DispatcherTrait}, xerc20_lockbox_test::{IXERC20LockboxTestDispatcher, IXERC20LockboxTestDispatcherTrait}, - xerc20_test::{IXERC20TestDispatcher, IXERC20TestDispatcherTrait} + xerc20_test::{IXERC20TestDispatcher, IXERC20TestDispatcherTrait}, + test_post_dispatch_hook::{ + ITestPostDispatchHookDispatcher, ITestPostDispatchHookDispatcherTrait + }, }; use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use snforge_std::{ declare, ContractClassTrait, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn, start_prank, stop_prank, EventFetcher, event_name_hash @@ -19,7 +24,6 @@ use super::common::{ set_custom_gas_config, REQUIRED_VALUE, GAS_LIMIT, DESTINATION, Setup, ZERO_SUPPLY }; - const MAX_INT: u256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; #[starknet::interface] @@ -106,11 +110,12 @@ fn setup_lockbox() -> (Setup, IHypERC20LockboxTestDispatcher) { .approve(xerc20.contract_address, BoundedInt::max()); ERC20ABIDispatcher { contract_address: setup.eth_token.contract_address } .approve(lockbox.contract_address, BoundedInt::max()); + ERC20ABIDispatcher { contract_address: setup.eth_token.contract_address } + .approve(xerc20lockbox.contract_address, BoundedInt::max()); stop_prank(CheatTarget::One(setup.eth_token.contract_address)); let remote_token_address: felt252 = setup.remote_token.contract_address.into(); xerc20lockbox.enroll_remote_router(DESTINATION, remote_token_address.into()); - setup.primary_token = setup.erc20_token; setup.primary_token.transfer(ALICE(), 1000 * E18); enroll_remote_router(@setup, xerc20lockbox); @@ -142,6 +147,104 @@ fn test_erc20_lockbox_transfer() { assert_eq!(xerc20lockbox.balance_of(ALICE()), balance_before - TRANSFER_AMT); } +#[test] +fn test_remote_transfer_with_custom_gas_config() { + let (setup, xerc20lockbox) = setup_lockbox(); + + start_prank(CheatTarget::One(setup.primary_token.contract_address), ALICE()); + setup.primary_token.approve(xerc20lockbox.contract_address, TRANSFER_AMT); + stop_prank(CheatTarget::One(setup.primary_token.contract_address)); + + // Check balance before transfer + let balance_before = xerc20lockbox.balance_of(ALICE()); + // Set custom gas config + xerc20lockbox.set_hook(setup.igp.contract_address); + let config = array![GasRouterConfig { domain: DESTINATION, gas: GAS_LIMIT }]; + xerc20lockbox.set_destination_gas(Option::Some(config), Option::None, Option::None); + let gas_price = setup.igp.gas_price(); + start_prank(CheatTarget::One(xerc20lockbox.contract_address), ALICE()); + // Do a remote transfer + perform_remote_transfer_and_gas( + @setup, xerc20lockbox, REQUIRED_VALUE, TRANSFER_AMT, GAS_LIMIT * gas_price, + ); + + stop_prank(CheatTarget::One(xerc20lockbox.contract_address)); + // Check balance after transfer + assert_eq!( + xerc20lockbox.balance_of(ALICE()), + balance_before - TRANSFER_AMT, + "Incorrect balance after transfer" + ); + let eth_dispatcher = IERC20Dispatcher { contract_address: setup.eth_token.contract_address }; + assert_eq!( + eth_dispatcher.balance_of(setup.igp.contract_address), + GAS_LIMIT * gas_price, + "Gas fee didnt transferred" + ); +} + +pub fn perform_remote_transfer_erc20_lockbox_and_gas_with_hook( + setup: @Setup, + local_token: IHypERC20LockboxTestDispatcher, + msg_value: u256, + amount: u256, + hook: ContractAddress, + hook_metadata: Bytes +) -> u256 { + // Remote transfer + start_prank(CheatTarget::One(local_token.contract_address), ALICE()); + let bob_felt: felt252 = BOB().into(); + let bob_address: u256 = bob_felt.into(); + let message_id = local_token + .transfer_remote( + DESTINATION, + bob_address, + amount, + msg_value, + Option::Some(hook_metadata), + Option::Some(hook) + ); + + process_transfers(setup, local_token, BOB(), amount); + + let remote_token = IERC20Dispatcher { + contract_address: (*setup).remote_token.contract_address + }; + assert_eq!(remote_token.balance_of(BOB()), amount); + + stop_prank(CheatTarget::One(local_token.contract_address)); + message_id +} + +#[test] +fn test_erc20_lockbox_remote_transfer_with_hook_specified(mut fee: u256, metadata: u256) { + let fee = fee % (TRANSFER_AMT / 10); + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + let (setup, xerc20lockbox) = setup_lockbox(); + + let (hook_address, _) = setup.test_post_dispatch_hook_contract.deploy(@array![]).unwrap(); + let hook = ITestPostDispatchHookDispatcher { contract_address: hook_address }; + hook.set_fee(fee); + + start_prank(CheatTarget::One(setup.primary_token.contract_address), ALICE()); + setup.primary_token.approve(xerc20lockbox.contract_address, TRANSFER_AMT); + stop_prank(CheatTarget::One(setup.primary_token.contract_address)); + + let balance_before = xerc20lockbox.balance_of(ALICE()); + + let message_id = perform_remote_transfer_erc20_lockbox_and_gas_with_hook( + @setup, xerc20lockbox, fee, TRANSFER_AMT, hook.contract_address, metadata_bytes + ); + + let balance_after = xerc20lockbox.balance_of(ALICE()); + assert_eq!(balance_after, balance_before - TRANSFER_AMT); + let eth_dispatcher = IERC20Dispatcher { contract_address: setup.eth_token.contract_address }; + assert_eq!(eth_dispatcher.balance_of(hook_address), fee, "fee didnt transferred"); + assert!(hook.message_dispatched(message_id) == true, "Hook did not dispatch"); +} + #[test] fn test_erc20_lockbox_handle() { let (setup, local_token) = setup_lockbox(); diff --git a/cairo/crates/token/tests/hyp_erc20/hyp_erc20_test.cairo b/cairo/crates/token/tests/hyp_erc20/hyp_erc20_test.cairo index f6f7626..e2453eb 100644 --- a/cairo/crates/token/tests/hyp_erc20/hyp_erc20_test.cairo +++ b/cairo/crates/token/tests/hyp_erc20/hyp_erc20_test.cairo @@ -1,4 +1,7 @@ +use alexandria_bytes::{Bytes, BytesTrait}; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; use mocks::test_interchain_gas_payment::ITestInterchainGasPaymentDispatcherTrait; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use snforge_std::{ declare, ContractClassTrait, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn, start_prank, stop_prank, EventFetcher, event_name_hash @@ -7,7 +10,8 @@ use super::common::{ setup, TOTAL_SUPPLY, DECIMALS, ORIGIN, TRANSFER_AMT, perform_remote_transfer_with_emit, perform_remote_transfer_and_gas, ALICE, BOB, E18, IHypERC20TestDispatcher, IHypERC20TestDispatcherTrait, enroll_remote_router, enroll_local_router, - perform_remote_transfer, set_custom_gas_config, REQUIRED_VALUE, GAS_LIMIT + perform_remote_transfer, set_custom_gas_config, REQUIRED_VALUE, GAS_LIMIT, + test_transfer_with_hook_specified }; #[test] @@ -86,4 +90,30 @@ fn test_erc20_remote_transfer_with_custom_gas_config() { perform_remote_transfer_and_gas(@setup, REQUIRED_VALUE, TRANSFER_AMT, GAS_LIMIT * gas_price); let balance_after = erc20_token.balance_of(ALICE()); assert_eq!(balance_after, balance_before - TRANSFER_AMT); + let eth_dispatcher = IERC20Dispatcher { contract_address: setup.eth_token.contract_address }; + assert_eq!( + eth_dispatcher.balance_of(setup.igp.contract_address), + GAS_LIMIT * gas_price, + "Gas fee didnt transferred" + ); +} + +#[test] +fn test_erc20_remote_transfer_with_hook_specified(mut fee: u256, metadata: u256) { + let fee = fee % (TRANSFER_AMT / 10); + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + let setup = setup(); + let erc20_token = setup.local_token; + enroll_remote_router(@setup); + enroll_local_router(@setup); + + let local_token_address: felt252 = setup.local_token.contract_address.into(); + setup.remote_token.enroll_remote_router(ORIGIN, local_token_address.into()); + + let balance_before = erc20_token.balance_of(ALICE()); + test_transfer_with_hook_specified(@setup, fee, metadata_bytes); + let balance_after = erc20_token.balance_of(ALICE()); + assert_eq!(balance_after, balance_before - TRANSFER_AMT); } diff --git a/cairo/crates/token/tests/hyp_erc20/hyp_fiat_token_test.cairo b/cairo/crates/token/tests/hyp_erc20/hyp_fiat_token_test.cairo index d42329d..d3ceb97 100644 --- a/cairo/crates/token/tests/hyp_erc20/hyp_fiat_token_test.cairo +++ b/cairo/crates/token/tests/hyp_erc20/hyp_fiat_token_test.cairo @@ -1,6 +1,9 @@ +use alexandria_bytes::{Bytes, BytesTrait}; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; use core::integer::BoundedInt; use mocks::test_erc20::{ITestERC20Dispatcher, ITestERC20DispatcherTrait}; use mocks::test_interchain_gas_payment::ITestInterchainGasPaymentDispatcherTrait; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use snforge_std::{ declare, ContractClassTrait, CheatTarget, EventSpy, EventAssertions, spy_events, SpyOn, start_prank, stop_prank, EventFetcher, event_name_hash @@ -11,7 +14,7 @@ use super::common::{ perform_remote_transfer_with_emit, perform_remote_transfer_and_gas, ALICE, BOB, E18, IHypERC20TestDispatcher, IHypERC20TestDispatcherTrait, enroll_remote_router, enroll_local_router, perform_remote_transfer, set_custom_gas_config, REQUIRED_VALUE, GAS_LIMIT, - Setup, handle_local_transfer + Setup, handle_local_transfer, test_transfer_with_hook_specified }; fn fiat_token_setup() -> Setup { @@ -61,6 +64,48 @@ fn test_fiat_token_remote_transfer() { assert_eq!(balance_after, balance_before - TRANSFER_AMT); } +#[test] +fn test_fiat_token_remote_transfer_with_custom_gas_config() { + let setup = fiat_token_setup(); + + set_custom_gas_config(@setup); + + let gas_price = setup.igp.gas_price(); + + start_prank(CheatTarget::One((setup).primary_token.contract_address), ALICE()); + setup.primary_token.approve(setup.local_token.contract_address, TRANSFER_AMT); + stop_prank(CheatTarget::One(setup.primary_token.contract_address)); + + let balance_before = setup.local_token.balance_of(ALICE()); + perform_remote_transfer_and_gas(@setup, REQUIRED_VALUE, TRANSFER_AMT, GAS_LIMIT * gas_price); + let balance_after = setup.local_token.balance_of(ALICE()); + assert_eq!(balance_after, balance_before - TRANSFER_AMT); + let eth_dispatcher = IERC20Dispatcher { contract_address: setup.eth_token.contract_address }; + assert_eq!( + eth_dispatcher.balance_of(setup.igp.contract_address), + GAS_LIMIT * gas_price, + "Gas fee didnt transferred" + ); +} + +#[test] +fn test_fiat_token_remote_transfer_with_hook_specified(mut fee: u256, metadata: u256) { + let fee = fee % (TRANSFER_AMT / 10); + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + let setup = fiat_token_setup(); + + start_prank(CheatTarget::One((setup).primary_token.contract_address), ALICE()); + setup.primary_token.approve(setup.local_token.contract_address, TRANSFER_AMT); + stop_prank(CheatTarget::One(setup.primary_token.contract_address)); + + let balance_before = setup.local_token.balance_of(ALICE()); + test_transfer_with_hook_specified(@setup, fee, metadata_bytes); + let balance_after = setup.local_token.balance_of(ALICE()); + assert_eq!(balance_after, balance_before - TRANSFER_AMT); +} + #[test] fn test_fiat_token_handle() { let setup = fiat_token_setup(); diff --git a/cairo/crates/token/tests/hyp_erc20/hyp_xerc20_test.cairo b/cairo/crates/token/tests/hyp_erc20/hyp_xerc20_test.cairo index 1a36fe1..fb55585 100644 --- a/cairo/crates/token/tests/hyp_erc20/hyp_xerc20_test.cairo +++ b/cairo/crates/token/tests/hyp_erc20/hyp_xerc20_test.cairo @@ -1,13 +1,22 @@ +use alexandria_bytes::{Bytes, BytesTrait}; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; use core::integer::BoundedInt; use mocks::xerc20_test::{XERC20Test, IXERC20TestDispatcher, IXERC20TestDispatcherTrait}; -use mocks::{test_erc20::{ITestERC20Dispatcher, ITestERC20DispatcherTrait},}; +use mocks::{ + test_erc20::{ITestERC20Dispatcher, ITestERC20DispatcherTrait}, + test_interchain_gas_payment::{ + ITestInterchainGasPaymentDispatcher, ITestInterchainGasPaymentDispatcherTrait + }, +}; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use snforge_std::{declare, ContractClassTrait, CheatTarget, start_prank, stop_prank,}; use starknet::ContractAddress; use super::common::{ - Setup, TOTAL_SUPPLY, DECIMALS, ORIGIN, TRANSFER_AMT, ALICE, BOB, E18, REQUIRED_VALUE, + Setup, TOTAL_SUPPLY, DECIMALS, ORIGIN, TRANSFER_AMT, ALICE, BOB, E18, REQUIRED_VALUE, GAS_LIMIT, DESTINATION, IHypERC20TestDispatcher, IHypERC20TestDispatcherTrait, setup, perform_remote_transfer_and_gas, enroll_remote_router, enroll_local_router, - perform_remote_transfer, handle_local_transfer + perform_remote_transfer, handle_local_transfer, test_transfer_with_hook_specified, + set_custom_gas_config }; fn setup_xerc20() -> Setup { @@ -87,3 +96,38 @@ fn test_handle() { assert_eq!(total_supply_after, total_supply_before + TRANSFER_AMT); assert_eq!(balance_after, balance_before + TRANSFER_AMT); } + +#[test] +fn test_erc20_remote_transfer_with_custom_gas_config() { + let mut setup = setup_xerc20(); + let xerc20 = setup.local_token; + + set_custom_gas_config(@setup); + + let gas_price = setup.igp.gas_price(); + let balance_before = xerc20.balance_of(ALICE()); + perform_remote_transfer_and_gas(@setup, REQUIRED_VALUE, TRANSFER_AMT, GAS_LIMIT * gas_price); + let balance_after = xerc20.balance_of(ALICE()); + assert_eq!(balance_after, balance_before - TRANSFER_AMT); + let eth_dispatcher = IERC20Dispatcher { contract_address: setup.eth_token.contract_address }; + assert_eq!( + eth_dispatcher.balance_of(setup.igp.contract_address), + GAS_LIMIT * gas_price, + "Gas fee didnt transferred" + ); +} + +#[test] +fn test_erc20_remote_transfer_with_hook_specified(mut fee: u256, metadata: u256) { + let fee = fee % (TRANSFER_AMT / 10); + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + let mut setup = setup_xerc20(); + let xerc20 = setup.local_token; + + let balance_before = xerc20.balance_of(ALICE()); + test_transfer_with_hook_specified(@setup, fee, metadata_bytes); + let balance_after = xerc20.balance_of(ALICE()); + assert_eq!(balance_after, balance_before - TRANSFER_AMT); +} diff --git a/cairo/crates/token/tests/hyp_erc721/common.cairo b/cairo/crates/token/tests/hyp_erc721/common.cairo index cf1980f..e21a2e4 100644 --- a/cairo/crates/token/tests/hyp_erc721/common.cairo +++ b/cairo/crates/token/tests/hyp_erc721/common.cairo @@ -37,6 +37,7 @@ use token::components::token_router::{ITokenRouterDispatcher, ITokenRouterDispat pub const E18: u256 = 1_000_000_000_000_000_000; const PUB_KEY: felt252 = 0x1; const ZERO_SUPPLY: u256 = 0; +const GAS_LIMIT: u256 = 10_000; pub fn ZERO_ADDRESS() -> ContractAddress { starknet::contract_address_const::<'0x0'>() } @@ -65,6 +66,7 @@ pub const TRANSFER_ID: u256 = 0; pub fn URI() -> ByteArray { "http://bit.ly/3reJLpx" } +pub const FEE_CAP: u256 = 10 * E18; #[starknet::interface] pub trait IHypErc721Test { @@ -145,6 +147,7 @@ pub struct Setup { pub eth_token: MockEthDispatcher, pub alice: ContractAddress, pub bob: ContractAddress, + pub test_post_dispatch_hook_contract: ContractClass } pub fn setup() -> Setup { @@ -157,8 +160,8 @@ pub fn setup() -> Setup { let (remote_primary_token, _) = contract.deploy(@calldata).unwrap(); let remote_primary_token = ITestERC721Dispatcher { contract_address: remote_primary_token }; - let contract = declare("TestPostDispatchHook").unwrap(); - let (noop_hook, _) = contract.deploy(@array![]).unwrap(); + let test_post_dispatch_hook_contract = declare("TestPostDispatchHook").unwrap(); + let (noop_hook, _) = test_post_dispatch_hook_contract.deploy(@array![]).unwrap(); let noop_hook = ITestPostDispatchHookDispatcher { contract_address: noop_hook }; let contract = declare("TestISM").unwrap(); @@ -169,7 +172,7 @@ pub fn setup() -> Setup { starknet::get_contract_address().serialize(ref calldata); let (eth_address, _) = contract.deploy_at(@calldata, ETH_ADDRESS()).unwrap(); let eth_token = MockEthDispatcher { contract_address: eth_address }; - eth_token.mint(starknet::get_contract_address(), 10 * E18); + eth_token.mint(starknet::get_contract_address(), FEE_CAP); let contract = declare("MockMailbox").unwrap(); let (local_mailbox, _) = contract .deploy( @@ -255,6 +258,7 @@ pub fn setup() -> Setup { eth_token, alice, bob, + test_post_dispatch_hook_contract, } } @@ -317,6 +321,40 @@ pub fn perform_remote_transfer(setup: @Setup, msg_value: u256, token_id: u256) { assert_eq!((*setup).remote_token.balance_of((*setup).bob), 1); } +pub fn perform_remote_transfer_and_gas_with_hook( + setup: @Setup, msg_value: u256, token_id: u256, hook: ContractAddress, hook_metadata: Bytes +) -> u256 { + let alice_address: felt252 = (*setup).alice.into(); + let message_id = (*setup) + .local_token + .transfer_remote( + DESTINATION, + alice_address.into(), + token_id, + msg_value, + Option::Some(hook_metadata), + Option::Some(hook) + ); + process_transfer(setup, (*setup).bob, token_id); + assert_eq!((*setup).remote_token.balance_of((*setup).bob), 1); + message_id +} + +pub fn test_transfer_with_hook_specified( + setup: @Setup, token_id: u256, fee: u256, metadata: Bytes +) { + let (hook_address, _) = setup.test_post_dispatch_hook_contract.deploy(@array![]).unwrap(); + let hook = ITestPostDispatchHookDispatcher { contract_address: hook_address }; + hook.set_fee(fee); + + let message_id = perform_remote_transfer_and_gas_with_hook( + setup, fee, token_id, hook.contract_address, metadata + ); + let eth_dispatcher = IERC20Dispatcher { contract_address: *setup.eth_token.contract_address }; + assert_eq!(eth_dispatcher.balance_of(hook_address), fee, "fee didnt transferred"); + assert!(hook.message_dispatched(message_id) == true, "Hook did not dispatch"); +} + #[test] fn test_erc721_setup() { let _ = setup(); diff --git a/cairo/crates/token/tests/hyp_erc721/hyp_erc721_collateral_test.cairo b/cairo/crates/token/tests/hyp_erc721/hyp_erc721_collateral_test.cairo index 0ac594c..5cc0907 100644 --- a/cairo/crates/token/tests/hyp_erc721/hyp_erc721_collateral_test.cairo +++ b/cairo/crates/token/tests/hyp_erc721/hyp_erc721_collateral_test.cairo @@ -1,5 +1,6 @@ -use alexandria_bytes::Bytes; +use alexandria_bytes::{Bytes, BytesTrait}; use contracts::client::router_component::{IRouterDispatcher, IRouterDispatcherTrait}; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; use core::integer::BoundedInt; use mocks::test_erc721::{ITestERC721Dispatcher, ITestERC721DispatcherTrait}; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; @@ -8,7 +9,7 @@ use starknet::ContractAddress; use super::common::{ setup, DESTINATION, INITIAL_SUPPLY, Setup, IHypErc721TestDispatcher, IHypErc721TestDispatcherTrait, ALICE, BOB, deploy_remote_token, perform_remote_transfer, - ZERO_ADDRESS + ZERO_ADDRESS, test_transfer_with_hook_specified, FEE_CAP }; use token::components::token_router::{ITokenRouterDispatcher, ITokenRouterDispatcherTrait}; @@ -55,6 +56,22 @@ fn test_erc721_collateral_remote_transfer() { ); } +#[test] +fn test_erc721__collateral_remote_transfer_with_hook_specified(mut fee: u256, metadata: u256) { + let fee = fee % FEE_CAP; + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + + let mut setup = setup_erc721_collateral(); + let setup = deploy_remote_token(setup, false); + setup.local_primary_token.approve(setup.local_token.contract_address, 0); + test_transfer_with_hook_specified(@setup, 0, fee, metadata_bytes); + assert_eq!( + setup.local_token.balance_of(starknet::get_contract_address()), INITIAL_SUPPLY * 2 - 2 + ); +} + #[test] #[should_panic] fn test_erc721_collateral_remote_transfer_revert_unowned() { diff --git a/cairo/crates/token/tests/hyp_erc721/hyp_erc721_collateral_uri_storage_test.cairo b/cairo/crates/token/tests/hyp_erc721/hyp_erc721_collateral_uri_storage_test.cairo index 9366369..240abcf 100644 --- a/cairo/crates/token/tests/hyp_erc721/hyp_erc721_collateral_uri_storage_test.cairo +++ b/cairo/crates/token/tests/hyp_erc721/hyp_erc721_collateral_uri_storage_test.cairo @@ -1,5 +1,6 @@ -use alexandria_bytes::Bytes; +use alexandria_bytes::{Bytes, BytesTrait}; use contracts::client::router_component::{IRouterDispatcher, IRouterDispatcherTrait}; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; use mocks::test_erc721::{ITestERC721Dispatcher, ITestERC721DispatcherTrait}; use snforge_std::cheatcodes::contract_class::{ContractClass, ContractClassTrait}; use snforge_std::{ @@ -10,7 +11,8 @@ use starknet::ContractAddress; use super::common::{ setup, DESTINATION, INITIAL_SUPPLY, Setup, IHypErc721TestDispatcher, IHypErc721TestDispatcherTrait, ALICE, BOB, deploy_remote_token, perform_remote_transfer, - ZERO_ADDRESS, NAME, SYMBOL, URI, TRANSFER_ID, process_transfer + ZERO_ADDRESS, NAME, SYMBOL, URI, TRANSFER_ID, process_transfer, FEE_CAP, + test_transfer_with_hook_specified }; use token::components::token_router::{ITokenRouterDispatcher, ITokenRouterDispatcherTrait}; @@ -63,3 +65,21 @@ fn test_erc721_collateral_uri_storage_remote_transfer_revert_burned() { ); } +#[test] +fn test_erc721_collateral_uri_storage_remote_transfer_with_hook_specified( + mut fee: u256, metadata: u256 +) { + let fee = fee % FEE_CAP; + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + + let mut setup = setup_erc721_collateral_uri_storage(); + let setup = deploy_remote_token(setup, false); + setup.local_primary_token.approve(setup.local_token.contract_address, 0); + test_transfer_with_hook_specified(@setup, 0, fee, metadata_bytes); + assert_eq!( + setup.local_token.balance_of(starknet::get_contract_address()), INITIAL_SUPPLY * 2 - 2 + ); +} + diff --git a/cairo/crates/token/tests/hyp_erc721/hyp_erc721_test.cairo b/cairo/crates/token/tests/hyp_erc721/hyp_erc721_test.cairo index 7ca9dbc..83226d8 100644 --- a/cairo/crates/token/tests/hyp_erc721/hyp_erc721_test.cairo +++ b/cairo/crates/token/tests/hyp_erc721/hyp_erc721_test.cairo @@ -1,9 +1,12 @@ -use alexandria_bytes::Bytes; +use alexandria_bytes::{Bytes, BytesTrait}; use contracts::client::router_component::{IRouterDispatcher, IRouterDispatcherTrait}; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::ContractAddress; use super::common::{ setup, DESTINATION, INITIAL_SUPPLY, Setup, IHypErc721TestDispatcher, - IHypErc721TestDispatcherTrait, ALICE, BOB, deploy_remote_token, perform_remote_transfer + IHypErc721TestDispatcherTrait, ALICE, BOB, deploy_remote_token, perform_remote_transfer, + test_transfer_with_hook_specified, FEE_CAP }; use token::components::token_router::{ITokenRouterDispatcher, ITokenRouterDispatcherTrait}; @@ -66,6 +69,27 @@ fn test_erc721_remote_transfer(is_collateral: u8) { assert_eq!(setup.local_token.balance_of(starknet::get_contract_address()), INITIAL_SUPPLY - 1); } +#[test] +fn test_erc721_remote_transfer_with_hook_specified( + is_collateral: u8, mut fee: u256, metadata: u256 +) { + let is_collateral = if is_collateral % 2 == 0 { + true + } else { + false + }; + + let fee = fee % FEE_CAP; + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + + let mut setup = hyp_erc721_setup(); + let setup = deploy_remote_token(setup, is_collateral); + test_transfer_with_hook_specified(@setup, 0, fee, metadata_bytes); + assert_eq!(setup.local_token.balance_of(starknet::get_contract_address()), INITIAL_SUPPLY - 1); +} + #[test] #[should_panic] fn test_erc721_remote_transfer_revert_unowned() { diff --git a/cairo/crates/token/tests/hyp_erc721/hyp_erc721_uri_storage_test.cairo b/cairo/crates/token/tests/hyp_erc721/hyp_erc721_uri_storage_test.cairo index b3d4f4e..1b8d43a 100644 --- a/cairo/crates/token/tests/hyp_erc721/hyp_erc721_uri_storage_test.cairo +++ b/cairo/crates/token/tests/hyp_erc721/hyp_erc721_uri_storage_test.cairo @@ -1,5 +1,6 @@ -use alexandria_bytes::Bytes; +use alexandria_bytes::{Bytes, BytesTrait}; use contracts::client::router_component::{IRouterDispatcher, IRouterDispatcherTrait}; +use contracts::hooks::libs::standard_hook_metadata::standard_hook_metadata::VARIANT; use mocks::test_erc721::{ITestERC721Dispatcher, ITestERC721DispatcherTrait}; use snforge_std::cheatcodes::contract_class::{ContractClass, ContractClassTrait}; use snforge_std::{ @@ -10,7 +11,7 @@ use starknet::ContractAddress; use super::common::{ setup, DESTINATION, INITIAL_SUPPLY, Setup, IHypErc721TestDispatcher, IHypErc721TestDispatcherTrait, ALICE, BOB, deploy_remote_token, perform_remote_transfer, - ZERO_ADDRESS, NAME, SYMBOL, URI + ZERO_ADDRESS, NAME, SYMBOL, URI, test_transfer_with_hook_specified, FEE_CAP }; use token::components::token_router::{ITokenRouterDispatcher, ITokenRouterDispatcherTrait}; @@ -55,3 +56,27 @@ fn test_erc721_uri_storage_remote_transfer_revert_burned() { assert_eq!(uri, URI()); } +#[test] +fn test_erc721_uri_storage_remote_transfer() { + let setup = setup_erc721_uri_storage(); + + let setup = deploy_remote_token(setup, false); + perform_remote_transfer(@setup, 2500, 0); + + let balance = setup.local_token.balance_of(starknet::get_contract_address()); + assert_eq!(balance, INITIAL_SUPPLY - 1); +} + +#[test] +fn test_erc721_uri_storage_remote_transfer_with_hook_specified(mut fee: u256, metadata: u256) { + let fee = fee % FEE_CAP; + let mut metadata_bytes = BytesTrait::new_empty(); + metadata_bytes.append_u16(VARIANT); + metadata_bytes.append_u256(metadata); + + let mut setup = setup_erc721_uri_storage(); + let setup = deploy_remote_token(setup, false); + test_transfer_with_hook_specified(@setup, 0, fee, metadata_bytes); + assert_eq!(setup.local_token.balance_of(starknet::get_contract_address()), INITIAL_SUPPLY - 1); +} +