diff --git a/src/exchange/liquidation_handler.cairo b/src/exchange/liquidation_handler.cairo index cfd9a826..68fabc1c 100644 --- a/src/exchange/liquidation_handler.cairo +++ b/src/exchange/liquidation_handler.cairo @@ -154,15 +154,15 @@ mod LiquidationHandler { BaseOrderHandler::unsafe_new_contract_state(); //retrieve BaseOrderHandler state global_reentrancy_guard::non_reentrant_before(state_base.data_store.read()); - let mut role_state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); - IRoleModule::only_liquidation_keeper(@role_state); + // let mut role_state: RoleModule::ContractState = RoleModule::unsafe_new_contract_state(); TODO uncomment role + // IRoleModule::only_liquidation_keeper(@role_state); - // with_oracle_prices_before( - // state_base.oracle.read(), - // state_base.data_store.read(), - // state_base.event_emitter.read(), - // @oracle_params - // ); + with_oracle_prices_before( + state_base.oracle.read(), + state_base.data_store.read(), + state_base.event_emitter.read(), + @oracle_params + ); // let starting_gas: u128 = starknet_utils::sn_gasleft(array![100]); TODO GAS let starting_gas: u256 = 0; @@ -190,7 +190,7 @@ mod LiquidationHandler { execute_order_feature_disabled_key(get_contract_address(), params.order.order_type) ); state_base.order_utils_lib.read().execute_order_utils(params); - // with_oracle_prices_after(state_base.oracle.read()); + with_oracle_prices_after(state_base.oracle.read()); global_reentrancy_guard::non_reentrant_after(state_base.data_store.read()); } diff --git a/tests/integration/test_long_integration.cairo b/tests/integration/test_long_integration.cairo index 63650b0e..3d8ed11f 100644 --- a/tests/integration/test_long_integration.cairo +++ b/tests/integration/test_long_integration.cairo @@ -693,10 +693,10 @@ fn test_long_increase_decrease_close() { let first_position_close = data_store.get_position(position_key_1); - assert(first_position_close.size_in_tokens == 0, 'Size token should be 62574.99'); - assert(first_position_close.size_in_usd == 0, 'Size should be 62574.99'); - assert(first_position_close.borrowing_factor == 0, 'Borrow should be 62574.99'); - assert(first_position_close.collateral_amount == 0, 'Collat should be 62574.99'); + assert(first_position_close.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_close.size_in_usd == 0, 'Size should be 0'); + assert(first_position_close.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_close.collateral_amount == 0, 'Collat should be 0'); let balance_USDC_af_close = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() @@ -2672,7 +2672,7 @@ fn test_takeprofit_long_decrease_fails() { } .balance_of(caller_address); - assert(balance_USDC_bef_close == 53124999999999999996350, 'balance USDC bef close 53124.99$'); + assert(balance_USDC_bef_close == 53124999999999999996350, 'balance USDC bef clse 53124.99$'); assert(balance_USDC_af_close == 62724999999999999996350, 'balance USDC af close 62724.99$'); assert(balance_ETH_af_close == 7000000000000000000, 'balance ETH af close 7'); assert(balance_ETH_bef_close == 7000000000000000000, 'balance ETH bef close 7'); @@ -3337,3 +3337,460 @@ fn test_takeprofit_long_close_fails() { // ********************************************************************************************* teardown(data_store, market_factory); } + +#[test] +fn test_long_liquidation() { + // ********************************************************************************************* + // * SETUP * + // ********************************************************************************************* + let ( + caller_address, + market_factory_address, + role_store_address, + data_store_address, + market_token_class_hash, + market_factory, + role_store, + data_store, + event_emitter, + exchange_router, + deposit_handler, + deposit_vault, + oracle, + order_handler, + order_vault, + reader, + referal_storage, + withdrawal_handler, + withdrawal_vault, + liquidation_handler, + ) = + setup(); + + // ********************************************************************************************* + // * TEST LOGIC * + // ********************************************************************************************* + + // Create a market. + let market = data_store.get_market(create_market(market_factory)); + + // Set params in data_store + data_store.set_address(keys::fee_token(), market.index_token); + data_store.set_u256(keys::max_swap_path_length(), 5); + + // Set max pool amount. + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, market.long_token), + 5000000000000000000000000000000000000000000 //500 000 ETH + ); + data_store + .set_u256( + keys::max_pool_amount_key(market.market_token, market.short_token), + 2500000000000000000000000000000000000000000000 //250 000 000 USDC + ); + + let factor_for_deposits: felt252 = keys::max_pnl_factor_for_deposits(); + data_store + .set_u256( + keys::max_pnl_factor_key(factor_for_deposits, market.market_token, true), + 50000000000000000000000000000000000000000000000 + ); + let factor_for_withdrawal: felt252 = keys::max_pnl_factor_for_withdrawals(); + data_store + .set_u256( + keys::max_pnl_factor_key(factor_for_withdrawal, market.market_token, true), + 50000000000000000000000000000000000000000000000 + ); + data_store.set_u256(keys::reserve_factor_key(market.market_token, true), 1000000000000000000); + data_store + .set_u256( + keys::open_interest_reserve_factor_key(market.market_token, true), 1000000000000000000 + ); + + data_store.set_bool('REENTRANCY_GUARD_STATUS', false); + + 'fill the pool'.print(); + // Fill the pool. + IERC20Dispatcher { contract_address: market.long_token } + .mint(market.market_token, 50000000000000000000000000000000000000); // 5 ETH + IERC20Dispatcher { contract_address: market.short_token } + .mint(market.market_token, 25000000000000000000000000000000000000000); // 25000 USDC + 'filled pool 1'.print(); + + IERC20Dispatcher { contract_address: market.long_token } + .mint(caller_address, 9999999999999000000); // 9.999 ETH + IERC20Dispatcher { contract_address: market.short_token } + .mint(caller_address, 49999999999999999000000); // 49.999 UDC + 'filled account'.print(); + + // INITIAL LONG TOKEN IN POOL : 5 ETH + // INITIAL SHORT TOKEN IN POOL : 25000 USDC + + let balance_deposit_vault_before = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(deposit_vault.contract_address); + let balance_caller_ETH = IERC20Dispatcher { contract_address: market.long_token } + .balance_of(caller_address); + let balance_caller_USDC = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(caller_address); + + assert(balance_deposit_vault_before == 0, 'balance deposit should be 0'); + assert(balance_caller_ETH == 10000000000000000000, 'balanc ETH should be 10 ETH'); + assert(balance_caller_USDC == 50000000000000000000000, 'USDC be 50 000 USDC'); + + // Send token to deposit in the deposit vault (this should be in a multi call with create_deposit) + start_prank(market.long_token, caller_address); + start_prank(market.short_token, caller_address); + IERC20Dispatcher { contract_address: market.long_token } + .approve(caller_address, 50000000000000000000000000000); + IERC20Dispatcher { contract_address: market.short_token } + .approve(caller_address, 50000000000000000000000000000); + + IERC20Dispatcher { contract_address: market.long_token } + .mint(caller_address, 50000000000000000000000000000); // 20 ETH + IERC20Dispatcher { contract_address: market.short_token } + .mint(caller_address, 50000000000000000000000000000); // 100 000 USDC + + // role_store.grant_role(exchange_router.contract_address, role::ROUTER_PLUGIN); + // role_store.grant_role(caller_address, role::ROUTER_PLUGIN); + + exchange_router + .send_tokens( + market.long_token, deposit_vault.contract_address, 50000000000000000000000000000 + ); + exchange_router + .send_tokens( + market.short_token, deposit_vault.contract_address, 50000000000000000000000000000 + ); + + stop_prank(market.long_token); + stop_prank(market.short_token); + + // Create Deposit + + let addresss_zero: ContractAddress = 0.try_into().unwrap(); + + let params = CreateDepositParams { + receiver: caller_address, + callback_contract: addresss_zero, + ui_fee_receiver: addresss_zero, + market: market.market_token, + initial_long_token: market.long_token, + initial_short_token: market.short_token, + long_token_swap_path: Array32Trait::::span32(@array![]), + short_token_swap_path: Array32Trait::::span32(@array![]), + min_market_tokens: 0, + execution_fee: 0, + callback_gas_limit: 0, + }; + 'create deposit'.print(); + + start_roll(deposit_handler.contract_address, 1910); + let key = deposit_handler.create_deposit(caller_address, params); + let first_deposit = data_store.get_deposit(key); + + 'created deposit'.print(); + + assert(first_deposit.account == caller_address, 'Wrong account depositer'); + assert(first_deposit.receiver == caller_address, 'Wrong account receiver'); + assert(first_deposit.initial_long_token == market.long_token, 'Wrong initial long token'); + assert( + first_deposit.initial_long_token_amount == 50000000000000000000000000000, + 'Wrong initial long token amount' + ); + assert( + first_deposit.initial_short_token_amount == 50000000000000000000000000000, + 'Wrong init short token amount' + ); + + let price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![4000, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + start_prank(role_store.contract_address, caller_address); + + role_store.grant_role(caller_address, role::ORDER_KEEPER); + role_store.grant_role(caller_address, role::ROLE_ADMIN); + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + role_store.grant_role(caller_address, role::MARKET_KEEPER); + + 'execute deposit'.print(); + + // Execute Deposit + start_roll(deposit_handler.contract_address, 1915); + deposit_handler.execute_deposit(key, price_params); + + 'executed deposit'.print(); + + let not_deposit = data_store.get_deposit(key); + let default_deposit: Deposit = Default::default(); + assert(not_deposit == default_deposit, 'Still existing deposit'); + + let market_token_dispatcher = IMarketTokenDispatcher { contract_address: market.market_token }; + let balance_market_token = market_token_dispatcher.balance_of(caller_address); + + assert(balance_market_token != 0, 'should receive market token'); + + let balance_deposit_vault_after = IERC20Dispatcher { contract_address: market.short_token } + .balance_of(deposit_vault.contract_address); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000000000000000000000000000, 'wrong pool value 1' + ); + assert( + pool_value_info.long_token_amount == 50000000000000000000000000000, + 'wrong long token amount 1' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 1' + ); + + // ************************************* TEST LONG ********************************************* + + 'Begining of LONG TEST'.print(); + + let key_open_interest = keys::open_interest_key( + market.market_token, contract_address_const::<'ETH'>(), true + ); + data_store.set_u256(key_open_interest, 1); + let max_key_open_interest = keys::max_open_interest_key(market.market_token, true); + data_store + .set_u256( + max_key_open_interest, 1000000000000000000000000000000000000000000000000000 + ); // 1 000 000 + + // Send token to order_vault in multicall with create_order + start_prank(contract_address_const::<'ETH'>(), caller_address); + IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .transfer(order_vault.contract_address, 1000000000000000000); // 1ETH + + 'transfer made'.print(); + // Create order_params Struct + let contract_address = contract_address_const::<0>(); + start_prank(market.market_token, caller_address); + start_prank(market.long_token, caller_address); + let order_params_long = CreateOrderParams { + receiver: caller_address, + callback_contract: contract_address, + ui_fee_receiver: contract_address, + market: market.market_token, + initial_collateral_token: market.long_token, + swap_path: Array32Trait::::span32(@array![]), + size_delta_usd: 3500000000000000000000, + initial_collateral_delta_amount: 1000000000000000000, // 10^18 + trigger_price: 0, + acceptable_price: 3501, + execution_fee: 0, + callback_gas_limit: 0, + min_output_amount: 0, + order_type: OrderType::MarketIncrease(()), + decrease_position_swap_type: DecreasePositionSwapType::NoSwap(()), + is_long: true, + referral_code: 0 + }; + // Create the swap order. + role_store.grant_role(exchange_router.contract_address, role::CONTROLLER); + start_roll(exchange_router.contract_address, 1930); + 'try to create prder'.print(); + start_prank(exchange_router.contract_address, caller_address); + let key_long = exchange_router.create_order(order_params_long); + 'long created'.print(); + let got_order_long = data_store.get_order(key_long); + + // Execute the swap order. + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![3500, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + let keeper_address = contract_address_const::<'keeper'>(); + role_store.grant_role(keeper_address, role::ORDER_KEEPER); + + stop_prank(order_handler.contract_address); + start_prank(order_handler.contract_address, keeper_address); + start_roll(order_handler.contract_address, 1935); + // TODO add real signatures check on Oracle Account + order_handler.execute_order(key_long, set_price_params); + 'long position SUCCEEDED'.print(); + + let position_key = data_store.get_account_position_keys(caller_address, 0, 10); + let position_key_1: felt252 = *position_key.at(0); + let first_position = data_store.get_position(position_key_1); + + assert(first_position.size_in_tokens == 1000000000000000000, 'Size token should be 1 ETH'); + assert(first_position.size_in_usd == 3500000000000000000000, 'Size should be 3500$'); + assert(first_position.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position.collateral_amount == 1000000000000000000, 'Collat should be 1 ETH'); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 3850, max: 3850, }, + long_token_price: Price { min: 3850, max: 3850, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let position_info = reader + .get_position_info( + data_store, referal_storage, position_key_1, market_prices, 0, contract_address, true + ); + assert(position_info.base_pnl_usd.mag == 350000000000000000000, 'PnL should be 350$'); + + let balance_USDC = IERC20Dispatcher { contract_address: contract_address_const::<'USDC'>() } + .balance_of(caller_address); + + let balance_ETH = IERC20Dispatcher { contract_address: contract_address_const::<'ETH'>() } + .balance_of(caller_address); + + assert(balance_USDC == 50000000000000000000000, 'balance USDC 50 000$'); + assert(balance_ETH == 9000000000000000000, 'balance ETH 9'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000000000000000000000000001, 'wrong pool value 2' + ); + assert( + pool_value_info.long_token_amount == 50000000000000000000000000000, + 'wrong long token amount 2' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 2' + ); + + /////////////////////////////////////// LIQUIDATION LONG /////////////////////////////////////// + + 'Check if liquidable'.print(); + + let market_prices = market_utils::MarketPrices { + index_token_price: Price { min: 1000, max: 1000, }, + long_token_price: Price { min: 1000, max: 1000, }, + short_token_price: Price { min: 1, max: 1, }, + }; + + let (is_liquiditable, reason) = position_utils::is_position_liquiditable( + data_store, referal_storage, first_position, market, market_prices, false + ); + + assert(is_liquiditable == true, 'Position is liquidable'); + + let signatures: Span = array![0].span(); + let set_price_params = SetPricesParams { + signer_info: 0, + tokens: array![contract_address_const::<'ETH'>(), contract_address_const::<'USDC'>()], + compacted_min_oracle_block_numbers: array![1910, 1910], + compacted_max_oracle_block_numbers: array![1920, 1920], + compacted_oracle_timestamps: array![9999, 9999], + compacted_decimals: array![1, 1], + compacted_min_prices: array![2147483648010000], // 500000, 10000 compacted + compacted_min_prices_indexes: array![0], + compacted_max_prices: array![1000, 1], // 500000, 10000 compacted + compacted_max_prices_indexes: array![0], + signatures: array![ + array!['signatures1', 'signatures2'].span(), array!['signatures1', 'signatures2'].span() + ], + price_feed_tokens: array![] + }; + + // Execute Liquidation + liquidation_handler + .execute_liquidation( + first_position.account, + first_position.market, + first_position.collateral_token, + first_position.is_long, + set_price_params + ); + + let first_position_liq = data_store.get_position(position_key_1); + + assert(first_position_liq.size_in_tokens == 0, 'Size token should be 0'); + assert(first_position_liq.size_in_usd == 0, 'Size should be 0'); + assert(first_position_liq.borrowing_factor == 0, 'Borrow should be 0'); + assert(first_position_liq.collateral_amount == 0, 'Collat should be 0'); + + let balance_USDC_af_liq = IERC20Dispatcher { + contract_address: contract_address_const::<'USDC'>() + } + .balance_of(caller_address); + + let balance_ETH_af_liq = IERC20Dispatcher { + contract_address: contract_address_const::<'ETH'>() + } + .balance_of(caller_address); + + assert(balance_USDC_af_liq == 50000000000000000000000, 'balance USDC 50 000$'); + assert(balance_ETH_af_liq == 9000000000000000000, 'balance ETH 9'); + + let pool_value_info = market_utils::get_pool_value_info( + data_store, + market, + Price { min: 3500, max: 3500, }, + Price { min: 3500, max: 3500, }, + Price { min: 1, max: 1, }, + keys::max_pnl_factor_for_deposits(), + true, + ); + + assert( + pool_value_info.pool_value.mag == 175050000003500000000000000000000, 'wrong pool value 3' + ); + assert( + pool_value_info.long_token_amount == 50000000001000000000000000000, + 'wrong long token amount 3' + ); + assert( + pool_value_info.short_token_amount == 50000000000000000000000000000, + 'wrong short token amount 3' + ); + + // ********************************************************************************************* + // * TEARDOWN * + // ********************************************************************************************* + teardown(data_store, market_factory); +}