diff --git a/src/exchange/withdrawal_handler.cairo b/src/exchange/withdrawal_handler.cairo index e6924a84..7b22fc42 100644 --- a/src/exchange/withdrawal_handler.cairo +++ b/src/exchange/withdrawal_handler.cairo @@ -174,7 +174,7 @@ mod WithdrawalHandler { global_reentrancy_guard::non_reentrant_before(data_store); // Initiates re-entrancy - let starting_gas = starknet_utils::sn_gasleft(array![100]); // Returns 100 for now, + let starting_gas = starknet_utils::sn_gasleft(array![200]); // Returns 200 for now, let withdrawal = data_store.get_withdrawal(key); feature_utils::validate_feature( diff --git a/src/market/market_utils.cairo b/src/market/market_utils.cairo index a14d7718..bd3ee309 100644 --- a/src/market/market_utils.cairo +++ b/src/market/market_utils.cairo @@ -2570,9 +2570,11 @@ fn validate_enabled_market_check( fn validate_enabled_market(data_store: IDataStoreDispatcher, market: Market) { assert(market.market_token != 0.try_into().unwrap(), MarketError::EMPTY_MARKET); - let is_market_disabled: bool = data_store - .get_bool(keys::is_market_disabled_key(market.market_token)) - .unwrap(); + let is_market_disabled: bool = + match data_store.get_bool(keys::is_market_disabled_key(market.market_token)) { + Option::Some(value) => value, + Option::None => false + }; if (is_market_disabled) { MarketError::DISABLED_MARKET(is_market_disabled); diff --git a/src/withdrawal/withdrawal_utils.cairo b/src/withdrawal/withdrawal_utils.cairo index daa278a9..ac3c4c4e 100644 --- a/src/withdrawal/withdrawal_utils.cairo +++ b/src/withdrawal/withdrawal_utils.cairo @@ -134,11 +134,7 @@ fn create_withdrawal( account_utils::validate_receiver(params.receiver); let market_token_amount = withdrawal_vault.record_transfer_in(params.market); - - if market_token_amount.is_zero() { - WithdrawalError::EMPTY_WITHDRAWAL_AMOUNT; - } - + assert(market_token_amount.is_non_zero(), WithdrawalError::EMPTY_WITHDRAWAL_AMOUNT); params.execution_fee = fee_token_amount.into(); market_utils::validate_enabled_market_check(data_store, params.market); @@ -187,7 +183,9 @@ fn create_withdrawal( gas_utils::validate_execution_fee(data_store, estimated_gas_limit, params.execution_fee); let key = nonce_utils::get_next_key(data_store); - + // assign generated key to withdrawal + withdrawal.key = key; + // store withdrawal data_store.set_withdrawal(key, withdrawal); event_emitter.emit_withdrawal_created(key, withdrawal); diff --git a/tests/exchange/test_withdrawal_handler.cairo b/tests/exchange/test_withdrawal_handler.cairo index 733193bb..e05d528d 100644 --- a/tests/exchange/test_withdrawal_handler.cairo +++ b/tests/exchange/test_withdrawal_handler.cairo @@ -1,8 +1,10 @@ use starknet::{ ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const }; -use snforge_std::{declare, start_prank, stop_prank, ContractClassTrait}; - +use snforge_std::{ + declare, start_prank, stop_prank, start_mock_call, stop_mock_call, ContractClassTrait +}; +use satoru::utils::span32::{Span32, Span32Trait}; use satoru::event::event_emitter::{IEventEmitterDispatcher, IEventEmitterDispatcherTrait}; use satoru::exchange::withdrawal_handler::{ IWithdrawalHandlerDispatcher, IWithdrawalHandlerDispatcherTrait @@ -22,35 +24,80 @@ use satoru::withdrawal::withdrawal::Withdrawal; use satoru::market::market::Market; use traits::Default; -// TODO: Add more tests after withdraw_utils implementation done. +// This tests check withdrawal creation under normal condition +// It calls withdrawal_handler.create_withdrawal +// The test expects the call to succeed without error #[test] fn given_normal_conditions_when_create_withdrawal_then_works() { - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); start_prank(withdrawal_handler.contract_address, caller_address); let account = contract_address_const::<'account'>(); + let market_token = contract_address_const::<'market_token'>(); + let params = create_withrawal_params(market_token); let address_zero = contract_address_const::<0>(); - let key = contract_address_const::<123456789>(); let mut market = Market { - market_token: key, + market_token: market_token, index_token: address_zero, long_token: address_zero, short_token: address_zero, }; - data_store.set_market(key, 0, market); + data_store.set_market(market_token, 0, market); + start_mock_call(withdrawal_vault_address, 'record_transfer_in', 1); + let key = withdrawal_handler.create_withdrawal(account, params); - let params = create_withrawal_params(key); -//withdrawal_handler.create_withdrawal(account, params); TODO fix create_withdrawal + //check withdrawal datas created + let withdrawal = data_store.get_withdrawal(key); + assert(withdrawal.key == key, 'Invalid withdrawal key'); + assert(withdrawal.account == account, 'Invalid withdrawal account'); } +// This tests check withdrawal creation when market_token_amount is 0 +// It calls withdrawal_handler.create_withdrawal +// The test expects the call to panic with error empty withdrawal amount +#[test] +#[should_panic(expected: ('empty withdrawal amount',))] +fn given_market_token_amount_equal_zero_when_create_withdrawal_then_fails() { + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let market_token = contract_address_const::<'market_token'>(); + let params = create_withrawal_params(market_token); + + withdrawal_handler.create_withdrawal(account, params); +} + +// This tests check withdrawal creation when fee token amount is lower than execution fee +// It calls withdrawal_handler.create_withdrawal +// The test expects the call to panic with the error 'insufficient fee token amout' +#[test] +#[should_panic(expected: ('insufficient fee token amout', 0, 1))] +fn given_fee_token_lower_than_execution_fee_conditions_when_create_withdrawal_then_fails() { + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let market_token = contract_address_const::<'market_token'>(); + let mut params = create_withrawal_params(market_token); + params.execution_fee = 1; + + withdrawal_handler.create_withdrawal(account, params); +} + +// This tests check withdrawal creation when caller address doesn't meet controller role +// It calls withdrawal_handler.create_withdrawal +// The test expects the call to panic with the error 'unauthorized_access'. #[test] #[should_panic(expected: ('unauthorized_access',))] fn given_caller_not_controller_when_create_withdrawal_then_fails() { // Should revert, call from anyone else then controller. - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); let caller: ContractAddress = 0x847.try_into().unwrap(); start_prank(withdrawal_handler.contract_address, caller); @@ -61,9 +108,13 @@ fn given_caller_not_controller_when_create_withdrawal_then_fails() { withdrawal_handler.create_withdrawal(caller, params); } +// This test checks withdrawal cancellation under normal condition +// It calls withdrawal_handler.cancel_withdrawal +// The test expects the call to succeed without error #[test] fn given_normal_conditions_when_cancel_withdrawal_then_works() { - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); start_prank(withdrawal_handler.contract_address, caller_address); let account = contract_address_const::<'account'>(); @@ -79,16 +130,44 @@ fn given_normal_conditions_when_cancel_withdrawal_then_works() { data_store.set_market(key, 0, market); let params = create_withrawal_params(key); -//let withdrawal_key = withdrawal_handler.create_withdrawal(account, params); TODO fix create_withdrawal + start_mock_call(withdrawal_vault_address, 'record_transfer_in', 1); -// Key cleaning should be done in withdrawal_utils. We only check call here. -//withdrawal_handler.cancel_withdrawal(withdrawal_key); + let withdrawal_key = withdrawal_handler.create_withdrawal(account, params); + + start_mock_call( + withdrawal_vault_address, + 'transfer_out', + array![contract_address_const::<'market_token'>().into(), account.into(), '1'] + ); + // Key cleaning should be done in withdrawal_utils. We only check call here. + withdrawal_handler.cancel_withdrawal(withdrawal_key); + + //check withdrawal correctly removed + let address_zero = contract_address_const::<0>(); + let withdrawal = data_store.get_withdrawal(withdrawal_key); + + assert(withdrawal.key == 0, 'Invalid key'); + assert(withdrawal.account == address_zero, 'Invalid account'); + assert(withdrawal.receiver == address_zero, 'Invalid receiver'); + assert(withdrawal.callback_contract == address_zero, 'Invalid callback after'); + assert(withdrawal.ui_fee_receiver == address_zero, 'Invalid ui_fee_receiver'); + assert(withdrawal.long_token_swap_path.len() == 0, 'Invalid long_swap_path'); + assert(withdrawal.short_token_swap_path.len() == 0, 'Invalid short_swap_path'); + assert(withdrawal.market_token_amount == 0, 'Invalid market_token_amount'); + assert(withdrawal.min_long_token_amount == 0, 'Invalid long_token_amount'); + assert(withdrawal.min_short_token_amount == 0, 'Invalid short_token_amount'); + assert(withdrawal.updated_at_block == 0, 'Invalid block'); + assert(withdrawal.execution_fee == 0, 'Invalid execution_fee'); + assert(withdrawal.callback_gas_limit == 0, 'Invalid callback_gas_limit'); } +// This tests check withdrawal cancellation when key doesn't exist in store +// It calls withdrawal_handler.cancel_withdrawal +// The test expects the call to panic with the error 'get_withdrawal failed'. #[test] #[should_panic(expected: ('empty withdrawal',))] fn given_unexisting_key_when_cancel_withdrawal_then_fails() { - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); start_prank(withdrawal_handler.contract_address, caller_address); let withdrawal_key = 'SAMPLE_WITHDRAW'; @@ -97,9 +176,114 @@ fn given_unexisting_key_when_cancel_withdrawal_then_fails() { withdrawal_handler.cancel_withdrawal(withdrawal_key); } +// This tests check withdrawal cancellation when account address is 0 +// It calls withdrawal_handler.cancel_withdrawal +// The test expects the call to panic with the error 'empty withdrawal'. +#[test] +#[should_panic(expected: ('empty withdrawal',))] +fn given_account_address_zero_when_cancel_withdrawal_then_fails() { + let mut withdrawal = Withdrawal { + key: 0, + account: contract_address_const::<0>(), + receiver: contract_address_const::<0>(), + callback_contract: contract_address_const::<0>(), + ui_fee_receiver: contract_address_const::<0>(), + market: contract_address_const::<0>(), + long_token_swap_path: Default::default(), + short_token_swap_path: Default::default(), + market_token_amount: 0, + min_long_token_amount: 0, + min_short_token_amount: 0, + updated_at_block: 0, + execution_fee: 0, + callback_gas_limit: 0, + }; + + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let key = contract_address_const::<'market'>(); + let mut params = create_withrawal_params(key); + + let market = Market { + market_token: key, + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + data_store.set_market(key, 0, market); + + start_mock_call(withdrawal_vault_address, 'record_transfer_in', 1); + + let withdrawal_key = withdrawal_handler.create_withdrawal(account, params); + + start_mock_call(data_store.contract_address, 'get_withdrawal', withdrawal); + + // Key cleaning should be done in withdrawal_utils. We only check call here. + withdrawal_handler.cancel_withdrawal(withdrawal_key); +} + + +// This tests check withdrawal cancellation when market token amount is 0 +// It calls withdrawal_handler.cancel_withdrawal +// The test expects the call to panic with the error 'empty withdrawal'. +#[test] +#[should_panic(expected: ('empty withdrawal amount',))] +fn given_market_token_equals_zero_when_cancel_withdrawal_then_fails() { + let mut withdrawal = Withdrawal { + key: 0, + account: contract_address_const::<'account'>(), + receiver: contract_address_const::<0>(), + callback_contract: contract_address_const::<0>(), + ui_fee_receiver: contract_address_const::<0>(), + market: contract_address_const::<0>(), + long_token_swap_path: Default::default(), + short_token_swap_path: Default::default(), + market_token_amount: 0, + min_long_token_amount: 0, + min_short_token_amount: 0, + updated_at_block: 0, + execution_fee: 0, + callback_gas_limit: 0, + }; + + let (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) = + setup(); + start_prank(withdrawal_handler.contract_address, caller_address); + + let account = contract_address_const::<'account'>(); + let key = contract_address_const::<'market'>(); + let mut params = create_withrawal_params(key); + + let market = Market { + market_token: key, + index_token: contract_address_const::<'index_token'>(), + long_token: contract_address_const::<'long_token'>(), + short_token: contract_address_const::<'short_token'>(), + }; + + data_store.set_market(key, 0, market); + + start_mock_call(withdrawal_vault_address, 'record_transfer_in', 1); + + let withdrawal_key = withdrawal_handler.create_withdrawal(account, params); + + stop_mock_call(withdrawal_vault_address, 'record_transfer_in'); + start_mock_call(data_store.contract_address, 'get_withdrawal', withdrawal); + + // Key cleaning should be done in withdrawal_utils. We only check call here. + withdrawal_handler.cancel_withdrawal(withdrawal_key); +} + +// This tests check withdrawal execution when caller address doesn't meet controller role +// It calls withdrawal_handler.execute_withdrawal +// The test expects the call to panic with the error 'unauthorized_access'. #[test] #[should_panic(expected: ('unauthorized_access',))] -fn given_caller_not_controller_when_execute_withdrawal_then_fails() { +fn given_caller_not_keeper_when_execute_withdrawal_then_fails() { let oracle_params = SetPricesParams { signer_info: Default::default(), tokens: Default::default(), @@ -115,7 +299,7 @@ fn given_caller_not_controller_when_execute_withdrawal_then_fails() { price_feed_tokens: Default::default(), }; - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); let withdrawal_key = 'SAMPLE_WITHDRAW'; @@ -141,7 +325,7 @@ fn given_caller_not_controller_when_execute_withdrawal_then_fails() { // price_feed_tokens: Default::default(), // }; -// let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); +// let (caller_address, data_store, event_emitter, withdrawal_handler,_) = setup(); // let order_keeper = contract_address_const::<0x2233>(); // start_prank(withdrawal_handler.contract_address, order_keeper); @@ -150,10 +334,13 @@ fn given_caller_not_controller_when_execute_withdrawal_then_fails() { // withdrawal_handler.execute_withdrawal(withdrawal_key, oracle_params); // } +// This tests check withdrawal simulation when when caller address doesn't meet controller role +// It calls withdrawal_handler.simulate_execute_withdrawal +// The test expects the call to panic with the error 'unauthorized_access'. #[test] #[should_panic(expected: ('unauthorized_access',))] fn given_caller_not_controller_when_simulate_execute_withdrawal_then_fails() { - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); let caller: ContractAddress = contract_address_const::<0x847>(); start_prank(withdrawal_handler.contract_address, caller); @@ -166,11 +353,13 @@ fn given_caller_not_controller_when_simulate_execute_withdrawal_then_fails() { withdrawal_handler.simulate_execute_withdrawal(withdrawal_key, oracle_params); } -// Panics due to the absence of a mocked withdrawal, resulting in 'withdrawal not found'. +// This tests check withdrawal simulation when key is unknown +// It calls withdrawal_handler.simulate_execute_withdrawal +// The test expects the call to panic with the error 'invalid withdrawal key','SAMPLE_WITHDRAW'. #[test] #[should_panic(expected: ('withdrawal not found',))] fn given_invalid_withdrawal_key_when_simulate_execute_withdrawal_then_fails() { - let (caller_address, data_store, event_emitter, withdrawal_handler) = setup(); + let (caller_address, data_store, event_emitter, withdrawal_handler, _) = setup(); let oracle_params = SimulatePricesParams { primary_tokens: Default::default(), primary_prices: Default::default(), }; @@ -302,7 +491,11 @@ fn deploy_event_emitter() -> ContractAddress { } fn setup() -> ( - ContractAddress, IDataStoreDispatcher, IEventEmitterDispatcher, IWithdrawalHandlerDispatcher + ContractAddress, + IDataStoreDispatcher, + IEventEmitterDispatcher, + IWithdrawalHandlerDispatcher, + ContractAddress ) { let caller_address: ContractAddress = contract_address_const::<'caller'>(); let order_keeper: ContractAddress = 0x2233.try_into().unwrap(); @@ -330,13 +523,14 @@ fn setup() -> ( contract_address: withdrawal_handler_address }; start_prank(role_store_address, caller_address); - role_store.grant_role(caller_address, role::MARKET_KEEPER); role_store.grant_role(caller_address, role::CONTROLLER); role_store.grant_role(order_keeper, role::ORDER_KEEPER); + role_store.grant_role(caller_address, role::MARKET_KEEPER); role_store.grant_role(withdrawal_handler_address, role::CONTROLLER); start_prank(data_store_address, caller_address); data_store.set_address(keys::fee_token(), fee_token_address); + //let market_token = IMarketTokenDispatcher { contract_address: market_token_address }; - (caller_address, data_store, event_emitter, withdrawal_handler) + (caller_address, data_store, event_emitter, withdrawal_handler, withdrawal_vault_address) }