diff --git a/.all-contributorsrc b/.all-contributorsrc index dd7dae92..59270959 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -315,8 +315,35 @@ "contributions": [ "code" ] + }, + { + "login": "JeanWoked", + "name": "JeanWoked", + "avatar_url": "https://avatars.githubusercontent.com/u/149107619?v=4", + "profile": "https://github.com/JeanWoked", + "contributions": [ + "code" + ] + }, + { + "login": "vuittont60", + "name": "vuittont60", + "avatar_url": "https://avatars.githubusercontent.com/u/81072379?v=4", + "profile": "https://github.com/vuittont60", + "contributions": [ + "code" + ] + }, + { + "login": "MavericksFive", + "name": "Arnaud Berger", + "avatar_url": "https://avatars.githubusercontent.com/u/95299145?v=4", + "profile": "https://github.com/MavericksFive", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, "linkToUsage": false -} \ No newline at end of file +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bca716e1..4485dcfb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,6 @@ on: [push, pull_request] env: SCARB_VERSION: 0.7.0 - STARKNET_FOUNDRY_VERSION: 0.8.3 jobs: check: @@ -14,6 +13,11 @@ jobs: - uses: software-mansion/setup-scarb@v1 with: scarb-version: "0.7.0" + - uses: foundry-rs/setup-snfoundry@v1 + with: + starknet-foundry-version: 0.8.3 + - name: Run cairo tests + run: snforge test # - name: Set up Scarb #ses: software-mansion/setup-scarb@v1 # Install Scarb from a nightly release @@ -22,7 +26,3 @@ jobs: # wget https://github.com/software-mansion/scarb-nightlies/releases/download/${NIGHTLY_DATE}/scarb-${NIGHTLY_DATE}-x86_64-unknown-linux-gnu.tar.gz # tar -xvf scarb-${NIGHTLY_DATE}-x86_64-unknown-linux-gnu.tar.gz # sudo mv scarb-v${SCARB_VERSION}-x86_64-unknown-linux-gnu/bin/scarb /usr/local/bin - - name: Install starknet foundry - run: curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh -s -- -v ${STARKNET_FOUNDRY_VERSION} - - name: Run cairo tests - run: snforge diff --git a/README.md b/README.md index 2f820537..eabb5abd 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,11 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d VictorONN
VictorONN

💻 kasteph
kasteph

💻 Khaeljy
Khaeljy

💻 + JeanWoked
JeanWoked

💻 + + + vuittont60
vuittont60

💻 + Arnaud Berger
Arnaud Berger

💻 diff --git a/book/src/continuous-integration/workflows.md b/book/src/continuous-integration/workflows.md index aaa66638..6cf0cfe5 100644 --- a/book/src/continuous-integration/workflows.md +++ b/book/src/continuous-integration/workflows.md @@ -198,18 +198,18 @@ on: - main env: SCARB_VERSION: 0.7.0 - STARKNET_FOUNDRY_VERSION: 0.8.3 jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: foundry-rs/setup-snfoundry@v1 + with: + starknet-foundry-version: 0.9.0 - uses: software-mansion/setup-scarb@v1 with: scarb-version: "0.7.0" - - name: Install starknet foundry - run: curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh -s -- -v ${STARKNET_FOUNDRY_VERSION} - name: Run cairo tests run: snforge ``` \ No newline at end of file diff --git a/src/adl/adl_utils.cairo b/src/adl/adl_utils.cairo index 27fecd61..d2d71dfa 100644 --- a/src/adl/adl_utils.cairo +++ b/src/adl/adl_utils.cairo @@ -250,15 +250,7 @@ fn set_latest_adl_block( fn get_adl_enabled( data_store: IDataStoreDispatcher, market: ContractAddress, is_long: bool ) -> bool { // TODO - let result = data_store.get_bool(keys::is_adl_enabled_key(market, is_long)); - match result { - Option::Some(data) => { - return data; - }, - Option::None => { - return false; - } - } + data_store.get_bool(keys::is_adl_enabled_key(market, is_long)) } /// Set whether ADL is enabled. diff --git a/src/adl/error.cairo b/src/adl/error.cairo index 78adb5a5..30ca0f29 100644 --- a/src/adl/error.cairo +++ b/src/adl/error.cairo @@ -3,5 +3,5 @@ mod AdlError { 'block_no_smaller_than_required'; const INVALID_SIZE_DELTA_FOR_ADL: felt252 = 'invalid_size_delta_for_adl'; const ADL_NOT_ENABLED: felt252 = 'adl_not_enabled'; - const POSTION_NOT_VALID: felt252 = 'position_not_valid'; + const POSITION_NOT_VALID: felt252 = 'position_not_valid'; } diff --git a/src/data/data_store.cairo b/src/data/data_store.cairo index 3da4e1a6..f2e2263e 100644 --- a/src/data/data_store.cairo +++ b/src/data/data_store.cairo @@ -161,7 +161,7 @@ trait IDataStore { /// * `key` - The key to get the value for. /// # Returns /// The value for the given key. - fn get_bool(self: @TContractState, key: felt252) -> Option; + fn get_bool(self: @TContractState, key: felt252) -> bool; /// Set a bool value for the given key. /// # Arguments @@ -511,7 +511,7 @@ mod DataStore { u128_values: LegacyMap::, i128_values: LegacyMap::, address_values: LegacyMap::, - bool_values: LegacyMap::>, + bool_values: LegacyMap::, /// Market storage market_values: LegacyMap::, markets: List, @@ -800,7 +800,7 @@ mod DataStore { // ************************************************************************* // Bool related functions. // ************************************************************************* - fn get_bool(self: @ContractState, key: felt252) -> Option { + fn get_bool(self: @ContractState, key: felt252) -> bool { self.bool_values.read(key) } @@ -808,14 +808,14 @@ mod DataStore { // Check that the caller has permission to set the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); // Set the value. - self.bool_values.write(key, Option::Some(value)); + self.bool_values.write(key, value); } fn remove_bool(ref self: ContractState, key: felt252) { // Check that the caller has permission to delete the value. self.role_store.read().assert_only_role(get_caller_address(), role::CONTROLLER); // Delete the value. - self.bool_values.write(key, Option::None); + self.bool_values.write(key, false); } // ************************************************************************* 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/feature/feature_utils.cairo b/src/feature/feature_utils.cairo index f7b52440..c2a1e84f 100644 --- a/src/feature/feature_utils.cairo +++ b/src/feature/feature_utils.cairo @@ -16,10 +16,7 @@ use satoru::feature::error::FeatureError; /// # Returns /// whether the feature is disabled. fn is_feature_disabled(data_store: IDataStoreDispatcher, key: felt252) -> bool { - match data_store.get_bool(key) { - Option::Some(value) => value, - Option::None => false - } + data_store.get_bool(key) } /// Validate whether a feature is enabled, reverts if the feature is disabled. diff --git a/src/market/error.cairo b/src/market/error.cairo index e5ae3fa0..9305eb77 100644 --- a/src/market/error.cairo +++ b/src/market/error.cairo @@ -11,7 +11,6 @@ mod MarketError { 'empty_addr_market_balance_val'; const EMPTY_ADDRESS_TOKEN_BALANCE_VAL: felt252 = 'empty_addr_token_balance_val'; const INVALID_MARKET_TOKEN_BALANCE: felt252 = 'invalid_market_token_balance'; - const EmptyAddressInMarketTokenBalanceValidation: felt252 = 'EmptyAddressMarketBalanceVal'; const INVALID_POSITION_MARKET: felt252 = 'invalid_position_market'; const INVALID_COLLATERAL_TOKEN_FOR_MARKET: felt252 = 'invalid_coll_token_for_market'; const UNABLE_TO_GET_OPPOSITE_TOKEN: felt252 = 'unable_to_get_opposite_token'; diff --git a/src/market/market_utils.cairo b/src/market/market_utils.cairo index bcf26d7f..a31f1d11 100644 --- a/src/market/market_utils.cairo +++ b/src/market/market_utils.cairo @@ -30,7 +30,7 @@ use satoru::utils::precision; use satoru::utils::calc::{roundup_division, to_signed, sum_return_int_128, to_unsigned}; use satoru::position::position::Position; use integer::u128_to_felt252; -use satoru::utils::{i128::i128, error_utils}; +use satoru::utils::{i128::{i128, i128_neg}, error_utils}; use satoru::utils::precision::{apply_exponent_factor, float_to_wei, mul_div}; use satoru::data::keys::{skip_borrowing_fee_for_smaller_side, max_swap_path_length}; @@ -923,7 +923,7 @@ fn apply_delta_to_open_interest( if is_long { apply_delta_to_virtual_inventory_for_positions( - data_store, event_emitter, *market.index_token, -delta + data_store, event_emitter, *market.index_token, i128_neg(delta) ); } else { apply_delta_to_virtual_inventory_for_positions( @@ -1455,7 +1455,9 @@ fn apply_swap_impact_with_cap( // if there is a positive impact, the impact pool amount should be reduced // if there is a negative impact, the impact pool amount should be increased - apply_delta_to_swap_impact_pool(data_store, event_emitter, market, token, -impact_amount); + apply_delta_to_swap_impact_pool( + data_store, event_emitter, market, token, i128_neg(impact_amount) + ); return impact_amount; } @@ -1799,6 +1801,7 @@ fn get_borrowing_fees(data_store: IDataStoreDispatcher, position: @Position) -> let cumulative_borrowing_factor: u128 = get_cumulative_borrowing_factor( @data_store, *position.market, *position.is_long ); + if (cumulative_borrowing_factor < *position.borrowing_factor) { MarketError::UNEXCEPTED_BORROWING_FACTOR( *position.borrowing_factor, cumulative_borrowing_factor @@ -2427,8 +2430,7 @@ fn get_borrowing_factor_per_second( // if skipBorrowingFeeForSmallerSide is true, and the longOpenInterest is exactly the same as the shortOpenInterest // then the borrowing fee would be charged for both sides, this should be very rare let skip_borrowing_fee_for_smaller_side: bool = data_store - .get_bool(keys::skip_borrowing_fee_for_smaller_side()) - .unwrap(); + .get_bool(keys::skip_borrowing_fee_for_smaller_side()); let market_snap = @market; if (skip_borrowing_fee_for_smaller_side) { @@ -2569,8 +2571,7 @@ 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(); + .get_bool(keys::is_market_disabled_key(market.market_token)); if (is_market_disabled) { MarketError::DISABLED_MARKET(is_market_disabled); diff --git a/src/order/base_order_utils.cairo b/src/order/base_order_utils.cairo index ebbe5528..f222495e 100644 --- a/src/order/base_order_utils.cairo +++ b/src/order/base_order_utils.cairo @@ -412,7 +412,7 @@ fn get_execution_price_for_decrease( let adjusted_price_impact_usd = if is_long { price_impact_usd } else { - -price_impact_usd + i128_neg(price_impact_usd) }; if adjusted_price_impact_usd < Zeroable::zero() diff --git a/src/order/error.cairo b/src/order/error.cairo index 4e459faf..a311f480 100644 --- a/src/order/error.cairo +++ b/src/order/error.cairo @@ -14,7 +14,7 @@ mod OrderError { const EMPTY_SIZE_DELTA_IN_TOKENS: felt252 = 'empty_size_delta_in_tokens'; const UNEXPECTED_MARKET: felt252 = 'unexpected market'; const INVALID_SIZE_DELTA_FOR_ADL: felt252 = 'invalid_size_delta_for_adl'; - const POSTION_NOT_VALID: felt252 = 'position_not_valid'; + const POSITION_NOT_VALID: felt252 = 'position_not_valid'; const ORDER_ALREADY_FROZEN: felt252 = 'order_already_frozen'; diff --git a/src/position/increase_position_utils.cairo b/src/position/increase_position_utils.cairo index 8c40a092..3d5680bb 100644 --- a/src/position/increase_position_utils.cairo +++ b/src/position/increase_position_utils.cairo @@ -144,7 +144,7 @@ fn increase_position(mut params: UpdatePositionParams, collateral_increment_amou params.contracts.data_store, params.contracts.event_emitter, params.market.market_token, - -cache.price_impact_amount + i128_neg(cache.price_impact_amount) ); cache.next_position_size_in_usd = params.position.size_in_usd + params.order.size_delta_usd; diff --git a/src/position/position_utils.cairo b/src/position/position_utils.cairo index 9213d4ac..e1b2215c 100644 --- a/src/position/position_utils.cairo +++ b/src/position/position_utils.cairo @@ -460,6 +460,7 @@ fn is_position_liquiditable( market_utils::get_cached_token_price(position.collateral_token, market, prices); cache.collateral_usd = position.collateral_amount * cache.collateral_token_price.min; + // calculate the usdDeltaForPriceImpact for fully closing the position cache.usd_delta_for_price_impact = calc::to_signed(position.size_in_usd, false); cache @@ -543,6 +544,7 @@ fn is_position_liquiditable( precision::apply_factor_u128(position.size_in_usd, cache.min_collateral_factor), true ); + if cache.remaining_collateral_usd <= cache.min_collateral_usd_for_leverage { return (true, 'min collateral for leverage'); } @@ -728,10 +730,10 @@ fn update_open_interest( size_delta_usd ); - market_utils::apply_delta_to_open_interest( + market_utils::apply_delta_to_open_interest_in_tokens( params.contracts.data_store, params.contracts.event_emitter, - @params.market, + params.market, params.position.collateral_token, params.position.is_long, size_delta_in_tokens diff --git a/src/reader/reader.cairo b/src/reader/reader.cairo index ebcb32b3..4d95f1f1 100644 --- a/src/reader/reader.cairo +++ b/src/reader/reader.cairo @@ -690,8 +690,7 @@ mod Reader { let virtual_inventory = self.get_virtual_inventory(data_store, market); let is_disabled = data_store - .get_bool(keys::is_market_disabled_key(market.market_token)) - .expect('get_bool failed'); + .get_bool(keys::is_market_disabled_key(market.market_token)); MarketInfo { market, borrowing_factor_per_second_for_longs, diff --git a/src/reader/reader_pricing_utils.cairo b/src/reader/reader_pricing_utils.cairo index 25124097..b78989cb 100644 --- a/src/reader/reader_pricing_utils.cairo +++ b/src/reader/reader_pricing_utils.cairo @@ -189,7 +189,7 @@ fn get_execution_price( let size_delta_usd_abs = if size_delta_usd > Zeroable::zero() { size_delta_usd } else { - -size_delta_usd + i128_neg(size_delta_usd) }; params.order.size_delta_usd = calc::to_unsigned(size_delta_usd_abs); params.order.is_long = is_long; diff --git a/src/swap/swap_utils.cairo b/src/swap/swap_utils.cairo index 4af3c035..54fea1d7 100644 --- a/src/swap/swap_utils.cairo +++ b/src/swap/swap_utils.cairo @@ -130,8 +130,7 @@ fn swap(params: @SwapParams) -> (ContractAddress, u128) { } let market: Market = *params.swap_path_markets[i]; let flag_exists = (*params.data_store) - .get_bool(keys::swap_path_market_flag_key(market.market_token)) - .expect('get_bool failed'); + .get_bool(keys::swap_path_market_flag_key(market.market_token)); if (flag_exists) { SwapError::DUPLICATED_MARKET_IN_SWAP_PATH(market.market_token); } diff --git a/src/utils/global_reentrancy_guard.cairo b/src/utils/global_reentrancy_guard.cairo index 5a5ca086..ad674418 100644 --- a/src/utils/global_reentrancy_guard.cairo +++ b/src/utils/global_reentrancy_guard.cairo @@ -14,10 +14,7 @@ const REENTRANCY_GUARD_STATUS: felt252 = 'REENTRANCY_GUARD_STATUS'; /// Modifier to avoid reentrancy. fn non_reentrant_before(data_store: IDataStoreDispatcher) { // Read key from Data Store. - let status = match data_store.get_bool(REENTRANCY_GUARD_STATUS) { - Option::Some(v) => v, - Option::None => false, - }; + let status = data_store.get_bool(REENTRANCY_GUARD_STATUS); // Check if reentrancy is happening. assert(!status, ReentrancyGuardError::REENTRANT_CALL); diff --git a/src/utils/precision.cairo b/src/utils/precision.cairo index da19ba88..2a2e6df4 100644 --- a/src/utils/precision.cairo +++ b/src/utils/precision.cairo @@ -6,7 +6,7 @@ use alexandria_math::pow; use integer::{ u128_to_felt252, u256_wide_mul, u512_safe_div_rem_by_u256, BoundedU256, u256_try_as_non_zero }; -use satoru::utils::i128::i128; +use satoru::utils::i128::{i128, i128_neg}; use core::traits::TryInto; use core::option::Option; use satoru::utils::calc::{roundup_division, roundup_magnitude_division}; @@ -82,7 +82,7 @@ fn mul_div_ival(value: i128, numerator: u128, denominator: u128) -> i128 { /// * `divisor` - The denominator that divides value. fn mul_div_inum(value: u128, numerator: i128, denominator: u128) -> i128 { let numerator_abs = if numerator < Zeroable::zero() { - -numerator + i128_neg(numerator) } else { numerator }; @@ -94,7 +94,7 @@ fn mul_div_inum(value: u128, numerator: i128, denominator: u128) -> i128 { if numerator > Zeroable::zero() { return i128_result; } else { - return -i128_result; + return i128_neg(i128_result); } } @@ -107,7 +107,7 @@ fn mul_div_inum_roundup( value: u128, numerator: i128, denominator: u128, roundup_magnitude: bool ) -> i128 { let numerator_abs = if numerator < Zeroable::zero() { - -numerator + i128_neg(numerator) } else { numerator }; @@ -119,7 +119,7 @@ fn mul_div_inum_roundup( if numerator > Zeroable::zero() { return i128_result; } else { - return -i128_result; + return i128_neg(i128_result); } } @@ -205,7 +205,7 @@ fn to_factor(value: u128, divisor: u128) -> u128 { /// The factor between value and divisor. fn to_factor_ival(value: i128, divisor: u128) -> i128 { let value_abs = if value < Zeroable::zero() { - -value + i128_neg(value) } else { value }; @@ -217,7 +217,7 @@ fn to_factor_ival(value: i128, divisor: u128) -> i128 { if value > Zeroable::zero() { i128_result } else { - -i128_result + i128_neg(i128_result) } } 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/data/test_data_store.cairo b/tests/data/test_data_store.cairo index e1983e01..ac541379 100644 --- a/tests/data/test_data_store.cairo +++ b/tests/data/test_data_store.cairo @@ -67,12 +67,12 @@ fn given_normal_conditions_when_bool_functions_then_expected_results() { // Safe to unwrap because we know that the key exists and if it doesn't the test should fail. let value = data_store.get_bool(1); // Check that the value read is true. - assert(value.unwrap() == true, 'Invalid value'); + assert(value == true, 'Invalid value'); // Remove key 1. data_store.remove_bool(1); // Check that the key was removed. - assert(data_store.get_bool(1) == Option::None, 'Key was not deleted'); + assert(data_store.get_bool(1) == false, 'Key was not deleted'); // ********************************************************************************************* // * TEARDOWN * 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) } diff --git a/tests/position/test_position_utils.cairo b/tests/position/test_position_utils.cairo index c259620f..28625c32 100644 --- a/tests/position/test_position_utils.cairo +++ b/tests/position/test_position_utils.cairo @@ -5,6 +5,7 @@ // Core lib imports. use result::ResultTrait; + use traits::{TryInto, Into}; use starknet::{ ContractAddress, get_caller_address, Felt252TryIntoContractAddress, contract_address_const, @@ -29,7 +30,7 @@ use satoru::price::price::{Price, PriceTrait}; use satoru::position::{position::Position, position_utils::UpdatePositionParams, position_utils}; use satoru::tests_lib::{setup, setup_event_emitter, teardown}; use satoru::mock::referral_storage::{IReferralStorageDispatcher, IReferralStorageDispatcherTrait}; -use satoru::pricing::{position_pricing_utils::PositionFees}; +use satoru::pricing::position_pricing_utils::{PositionFees, PositionReferralFees}; use satoru::order::{ order::{Order, SecondaryOrderType, OrderType, DecreasePositionSwapType}, order_vault::{IOrderVaultDispatcher, IOrderVaultDispatcherTrait} @@ -37,15 +38,15 @@ use satoru::order::{ use satoru::oracle::oracle::{IOracleDispatcher, IOracleDispatcherTrait}; use satoru::swap::swap_handler::{ISwapHandlerDispatcher, ISwapHandlerDispatcherTrait}; use satoru::order::base_order_utils::ExecuteOrderParamsContracts; - +use satoru::utils::i128::{i128}; #[test] fn given_normal_conditions_when_get_position_key_then_works() { // // Setup // - let account: ContractAddress = 'account'.try_into().unwrap(); - let market: ContractAddress = 'market'.try_into().unwrap(); - let token: ContractAddress = 'token'.try_into().unwrap(); + let account: ContractAddress = contract_address_const::<'account'>(); + let market: ContractAddress = contract_address_const::<'market'>(); + let token: ContractAddress = contract_address_const::<'token'>(); let mut data = array![account.into(), market.into(), token.into(), false.into()]; let mut data2 = array![account.into(), market.into(), token.into(), true.into()]; let key_1 = poseidon_hash_span(data.span()); @@ -99,7 +100,7 @@ fn given_invalid_position_size_when_validate_position_then_fails() { let (caller_address, role_store, data_store) = setup(); let referral_storage = IReferralStorageDispatcher { - contract_address: '12345'.try_into().unwrap() + contract_address: contract_address_const::<'12345'>() }; let position: Position = Default::default(); @@ -124,7 +125,7 @@ fn given_empty_market_when_validate_position_then_fails() { let (caller_address, role_store, data_store) = setup(); let referral_storage = IReferralStorageDispatcher { - contract_address: '12345'.try_into().unwrap() + contract_address: contract_address_const::<'12345'>() }; let mut position: Position = Default::default(); @@ -156,9 +157,9 @@ fn given_minimum_position_size_when_validate_position_then_fails() { let (caller_address, role_store, data_store) = setup(); let referral_storage = IReferralStorageDispatcher { - contract_address: '12345'.try_into().unwrap() + contract_address: contract_address_const::<'12345'>() }; - let token: ContractAddress = 'token'.try_into().unwrap(); + let token: ContractAddress = contract_address_const::<'token'>(); let mut position: Position = Default::default(); let mut market: Market = Default::default(); @@ -169,7 +170,7 @@ fn given_minimum_position_size_when_validate_position_then_fails() { // Set valid market colleteral tokens (positon.collateral_token == market.long_token || token == market.short_token;) position.collateral_token = token; market.long_token = token; - market.market_token = 'market_token'.try_into().unwrap(); + market.market_token = contract_address_const::<'market_token'>(); let price = Price { min: 0, max: 0 }; let prices: MarketPrices = MarketPrices { @@ -207,10 +208,10 @@ fn given_normal_conditions_when_increment_claimable_funding_amount_then_works() let (caller_address, role_store, data_store) = setup(); let (event_emitter_address, event_emitter) = setup_event_emitter(); - let market_token: ContractAddress = 'market_token'.try_into().unwrap(); - let long_token: ContractAddress = 'long_token'.try_into().unwrap(); - let short_token: ContractAddress = 'short_token'.try_into().unwrap(); - let account: ContractAddress = 'account'.try_into().unwrap(); + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + let account: ContractAddress = contract_address_const::<'account'>(); let long_token_amount: u128 = 10000; let short_token_amount: u128 = 20000; @@ -248,7 +249,7 @@ fn given_normal_conditions_when_increment_claimable_funding_amount_then_works() market_token, short_token, account ); - // Check funding amounts increased for long and short tokens + // Check funding amounts increased for long and short tokens let retrieved_claimable_long = data_store.get_u128(claimable_fund_long_key); let retrieved_claimable_short = data_store.get_u128(claimable_fund_short_key); assert(retrieved_claimable_long == long_token_amount, 'Invalid claimable for long'); @@ -267,34 +268,426 @@ fn given_normal_conditions_when_increment_claimable_funding_amount_then_works() teardown(data_store.contract_address); } -// TODO -// Missing libraries -//fn test_is_position_liquiditable() { -// - -// TODO -// Missing libraries -//fn test_will_position_collateral_be_sufficient() { -// - -// TODO -// Missing libraries -//fn test_update_funding_and_borrowing_state() { -// - -// TODO -// Missing libraries -//fn test_update_total_borrowing() { -// - -// TODO -// Missing libraries -//fn test_update_open_interest() { -// - -// TODO -// Missing libraries -//fn test_handle_referral() { -// + + +/// Utility function to deploy a `ReferralStorage` contract and return its dispatcher. +fn deploy_referral_storage(event_emitter_address: ContractAddress) -> ContractAddress { + let contract = declare('ReferralStorage'); + let constructor_calldata = array![event_emitter_address.into()]; + contract.deploy(@constructor_calldata).unwrap() +} + + +#[test] +fn given_negative_remaining_collateral_usd_when_checking_liquidatability_then_invalid_position() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let referral_storage_address: ContractAddress = deploy_referral_storage(event_emitter_address); + + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + //Create a long position + let mut position: Position = Default::default(); + position.size_in_usd = 10000; + position.collateral_amount = 10; + position.borrowing_factor = 2; + position.size_in_tokens = 50; + position.is_long = true; + position.collateral_token = long_token; + position.market = market_token; + + // Fill required data store keys. + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u128(open_interest_key, 15000); + + // setting cumulative borrowing factor greater than the borrowing factor... + let cumulative_borrowing_factor_key = keys::cumulative_borrowing_factor_key(market_token, true); + data_store.set_u128(cumulative_borrowing_factor_key, 1000); + + let market = Market { market_token, index_token: long_token, long_token, short_token, }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + // Test + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referral_storage, position, market, prices, false + ); + + assert(is_liquiditable, 'Invalid position liquidation'); + assert(reason == '0<', 'Invalid liquidation reason'); +} + + +#[test] +fn given_below_minimum_collateral_when_checking_liquidatability_then_invalid_position() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let referral_storage_address: ContractAddress = deploy_referral_storage(event_emitter_address); + + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + //Create a long position + let mut position: Position = Default::default(); + position.size_in_usd = 10000; + position.collateral_amount = 10; + position.borrowing_factor = 2; + position.size_in_tokens = 50; + position.is_long = true; + position.collateral_token = long_token; + position.market = market_token; + + // Fill required data store keys. + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u128(open_interest_key, 15000); + + // setting cumulative borrowing factor greater than the borrowing factor... + let cumulative_borrowing_factor_key = keys::cumulative_borrowing_factor_key(market_token, true); + data_store.set_u128(cumulative_borrowing_factor_key, 1000); + + let market = Market { market_token, index_token: long_token, long_token, short_token, }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + // Test + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referral_storage, position, market, prices, true + ); + + assert(is_liquiditable, 'Invalid position liquidation'); + assert(reason == 'min collateral', 'Invalid liquidation reason'); +} + +#[test] +fn given_valid_position_when_checking_liquidatability_then_valid_position() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let referral_storage_address: ContractAddress = deploy_referral_storage(event_emitter_address); + + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + //Create a long position + let mut position: Position = Default::default(); + position.size_in_usd = 10000; + position.collateral_amount = 1000; + position.borrowing_factor = 2; + position.size_in_tokens = 50; + position.is_long = true; + position.collateral_token = long_token; + position.market = market_token; + + // Fill required data store keys. + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u128(open_interest_key, 15000); + + // setting cumulative borrowing factor greater than the borrowing factor... + let cumulative_borrowing_factor_key = keys::cumulative_borrowing_factor_key(market_token, true); + data_store.set_u128(cumulative_borrowing_factor_key, 1000); + + let market = Market { market_token, index_token: long_token, long_token, short_token, }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + // Test + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referral_storage, position, market, prices, true + ); + + assert(!is_liquiditable, 'Invalid position liquidation'); + assert(reason == '', 'Invalid liquidation reason'); +} + +#[test] +fn given_below_min_collateral_leverage_when_checking_liquidatability_then_invalid_position() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let referral_storage_address: ContractAddress = deploy_referral_storage(event_emitter_address); + + let referral_storage = IReferralStorageDispatcher { + contract_address: referral_storage_address + }; + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + //Create a long position + let mut position: Position = Default::default(); + position.size_in_usd = 10000; + position.collateral_amount = 60; + position.borrowing_factor = 2; + position.size_in_tokens = 50; + position.is_long = true; + position.collateral_token = long_token; + position.market = market_token; + + // Fill required data store keys. + + // setting long interest greater than te position size in USD... + let open_interest_key = keys::open_interest_key(market_token, long_token, true); + data_store.set_u128(open_interest_key, 15000); + + // setting cumulative borrowing factor greater than the borrowing factor... + let cumulative_borrowing_factor_key = keys::cumulative_borrowing_factor_key(market_token, true); + data_store.set_u128(cumulative_borrowing_factor_key, 1000); + + // setting a min collateral factor for the market + let min_collateral_factor_key = keys::min_collateral_factor_key(market_token); + data_store.set_u128(min_collateral_factor_key, 10_000_000_000_000_000_000); + + let market = Market { market_token, index_token: long_token, long_token, short_token, }; + + let long_token_price = Price { min: 100, max: 110 }; + let index_token_price = Price { min: 100, max: 110 }; + let short_token_price = Price { min: 100, max: 110 }; + + let prices: MarketPrices = MarketPrices { + index_token_price: index_token_price, + long_token_price: long_token_price, + short_token_price: short_token_price + }; + + // Test + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referral_storage, position, market, prices, false + ); + + assert(is_liquiditable, 'Invalid position liquidation'); + assert(reason == 'min collateral for leverage', 'Invalid liquidation reason'); +} + + +#[test] +fn given_initial_total_borrowing_when_updating_then_correct_total_borrowing() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + // Fill required data store keys. + let total_borrowing_key = keys::total_borrowing_key(market_token, false); + data_store.set_u128(total_borrowing_key, 1000); + + let mut params: position_utils::UpdatePositionParams = UpdatePositionParams { + contracts: ExecuteOrderParamsContracts { + data_store, + event_emitter, + order_vault: IOrderVaultDispatcher { contract_address: Zeroable::zero() }, + oracle: IOracleDispatcher { contract_address: Zeroable::zero() }, + swap_handler: ISwapHandlerDispatcher { contract_address: Zeroable::zero() }, + referral_storage: IReferralStorageDispatcher { contract_address: Zeroable::zero() }, + }, + market: Market { market_token, index_token: long_token, long_token, short_token, }, + order: Default::default(), + order_key: 0, + position: Default::default(), + position_key: 0, + secondary_order_type: SecondaryOrderType::None, + }; + + //Test + + //Update total borrowing + let next_position_size_in_usd: u128 = 1000000000000000; + let next_position_borrowing_factor: u128 = 20000000; + + position_utils::update_total_borrowing( + params, next_position_size_in_usd, next_position_borrowing_factor + ); + + let total_borrowing_value: u128 = data_store.get_u128(total_borrowing_key); + assert(total_borrowing_value == 1200, 'Invalid total borrowing') +} + +#[test] +fn given_initial_open_interest_when_updating_then_correct_open_interest() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + // Fill required data store keys. + let key_open_interest = keys::open_interest_key( + market_token, contract_address_const::<0>(), false + ); + data_store.set_u128(key_open_interest, 1000); + + let key_open_interest_in_tokens = keys::open_interest_in_tokens_key( + market_token, contract_address_const::<0>(), false + ); + data_store.set_u128(key_open_interest_in_tokens, 2000); + + let mut params: position_utils::UpdatePositionParams = UpdatePositionParams { + contracts: ExecuteOrderParamsContracts { + data_store, + event_emitter, + order_vault: IOrderVaultDispatcher { contract_address: Zeroable::zero() }, + oracle: IOracleDispatcher { contract_address: Zeroable::zero() }, + swap_handler: ISwapHandlerDispatcher { contract_address: Zeroable::zero() }, + referral_storage: IReferralStorageDispatcher { contract_address: Zeroable::zero() }, + }, + market: Market { market_token, index_token: long_token, long_token, short_token, }, + order: Default::default(), + order_key: 0, + position: Default::default(), + position_key: 0, + secondary_order_type: SecondaryOrderType::None, + }; + + //Update open interest + let size_delta_usd: i128 = 10.try_into().unwrap(); + let size_delta_in_tokens: i128 = 20.try_into().unwrap(); + + //Test + + position_utils::update_open_interest(params, size_delta_usd, size_delta_in_tokens); + + let open_interest = data_store.get_u128(key_open_interest); + + let open_interest_in_tokens = data_store.get_u128(key_open_interest_in_tokens); + + assert(open_interest == 1010, 'Invalid open interest value'); + assert(open_interest_in_tokens == 2020, 'Invalid open interest value'); +} + +#[test] +fn given_valid_referral_when_handling_then_referral_successfully_processed() { + // + // Setup + // + let (caller_address, role_store, data_store) = setup(); + let (event_emitter_address, event_emitter) = setup_event_emitter(); + + let market_token: ContractAddress = contract_address_const::<'market_token'>(); + let long_token: ContractAddress = contract_address_const::<'long_token'>(); + let short_token: ContractAddress = contract_address_const::<'short_token'>(); + + let mut fees: PositionFees = Default::default(); + let mut referral: PositionReferralFees = Default::default(); + + referral.affiliate = contract_address_const::<'1'>(); + referral.affiliate_reward_amount = 20; + fees.referral = referral; + + // Fill required data store keys. + let affiliate_reward_for_account_key = keys::affiliate_reward_for_account_key( + market_token, contract_address_const::<0>(), referral.affiliate + ); + data_store.set_u128(affiliate_reward_for_account_key, 10); + + let mut params: position_utils::UpdatePositionParams = UpdatePositionParams { + contracts: ExecuteOrderParamsContracts { + data_store, + event_emitter, + order_vault: IOrderVaultDispatcher { contract_address: Zeroable::zero() }, + oracle: IOracleDispatcher { contract_address: Zeroable::zero() }, + swap_handler: ISwapHandlerDispatcher { contract_address: Zeroable::zero() }, + referral_storage: IReferralStorageDispatcher { contract_address: Zeroable::zero() }, + }, + market: Market { market_token, index_token: long_token, long_token, short_token, }, + order: Default::default(), + order_key: 0, + position: Default::default(), + position_key: 0, + secondary_order_type: SecondaryOrderType::None, + }; + + //Attribute position.market to the market instance define above + + params.position.market = params.market.market_token; + + //Test + + position_utils::handle_referral(params, fees); + let affiliate_reward_value = data_store.get_u128(affiliate_reward_for_account_key); + + assert(affiliate_reward_value == 30, 'Invalide affiliate reward value') +} +//TODO +// #[test] +// fn test_will_position_collateral_be_sufficient() { +//} + +//TODO +// #[test] +// fn test_update_funding_and_borrowing_state() { +// } diff --git a/tests/utils/test_reentrancy_guard.cairo b/tests/utils/test_reentrancy_guard.cairo index 4b839e51..2a710542 100644 --- a/tests/utils/test_reentrancy_guard.cairo +++ b/tests/utils/test_reentrancy_guard.cairo @@ -16,18 +16,18 @@ fn given_normal_conditions_when_non_reentrancy_before_and_after_then_works() { let initial_value = data_store.get_bool('REENTRANCY_GUARD_STATUS'); // Initial value should be false. - assert(initial_value.is_none(), 'Initial value wrong'); + assert(initial_value == false, 'Initial value wrong'); // Sets value to true non_reentrant_before(data_store); // Gets value after non_reentrant_before call - let entrant = data_store.get_bool('REENTRANCY_GUARD_STATUS').unwrap(); + let entrant = data_store.get_bool('REENTRANCY_GUARD_STATUS'); assert(entrant, 'Entered value wrong'); non_reentrant_after(data_store); // This should set value false. // Gets final value - let after: bool = data_store.get_bool('REENTRANCY_GUARD_STATUS').unwrap(); + let after: bool = data_store.get_bool('REENTRANCY_GUARD_STATUS'); assert(!after, 'Final value wrong'); } @@ -48,7 +48,7 @@ fn given_reentrant_call_when_reentrancy_before_and_after_then_fails() { non_reentrant_before(data_store); // Gets value after non_reentrant_before - let entraant: bool = data_store.get_bool('REENTRANCY_GUARD_STATUS').unwrap(); + let entraant: bool = data_store.get_bool('REENTRANCY_GUARD_STATUS'); assert(entraant, 'Entered value wrong'); // This should revert, means reentrant call happened.