diff --git a/dex/pair/src/safe_price_view.rs b/dex/pair/src/safe_price_view.rs index 31704e58a..16d5d96c3 100644 --- a/dex/pair/src/safe_price_view.rs +++ b/dex/pair/src/safe_price_view.rs @@ -97,15 +97,6 @@ pub trait SafePriceViewModule: .get_from_address(&pair_address); let price_observations = self.price_observations(); - let last_price_observation = self.get_price_observation( - &pair_address, - &first_token_id, - &second_token_id, - safe_price_current_index, - &price_observations, - end_round, - ); - let oldest_price_observation = self.get_oldest_price_observation( &pair_address, safe_price_current_index, @@ -126,6 +117,15 @@ pub trait SafePriceViewModule: start_round, ); + let last_price_observation = self.get_price_observation( + &pair_address, + &first_token_id, + &second_token_id, + safe_price_current_index, + &price_observations, + end_round, + ); + let mut weighted_amounts = self.compute_weighted_amounts(&first_price_observation, &last_price_observation); @@ -512,12 +512,17 @@ pub trait SafePriceViewModule: - first_price_observation .second_token_reserve_accumulated .clone(); - let lp_supply_diff = last_price_observation.lp_supply_accumulated.clone() - - first_price_observation.lp_supply_accumulated.clone(); let weighted_first_token_reserve = first_token_reserve_diff / weight_diff; let weighted_second_token_reserve = second_token_reserve_diff / weight_diff; - let weighted_lp_supply = lp_supply_diff / weight_diff; + + let weighted_lp_supply = if first_price_observation.lp_supply_accumulated > 0 { + let lp_supply_diff = &last_price_observation.lp_supply_accumulated + - &first_price_observation.lp_supply_accumulated; + lp_supply_diff / weight_diff + } else { + BigUint::zero() + }; PriceObservationWeightedAmounts { weighted_first_token_reserve, diff --git a/dex/pair/tests/pair_rs_test.rs b/dex/pair/tests/pair_rs_test.rs index ec2c3f772..c67be08ec 100644 --- a/dex/pair/tests/pair_rs_test.rs +++ b/dex/pair/tests/pair_rs_test.rs @@ -111,6 +111,200 @@ fn test_safe_price_observation_decoding() { ); } +#[test] +fn test_safe_price_migration() { + let mut pair_setup = PairSetup::new(pair::contract_obj); + let pair_address = pair_setup.pair_wrapper.address_ref().clone(); + let starting_round = 1000; + let payment_amount = 1000; + let mut expected_amount = 996; + + let mut first_token_reserve = 1_002_000; + let mut second_token_reserve = 1_000_004; + let mut first_token_accumulated = 1_001_000; + let mut second_token_accumulated = 1_001_000; + + let weight = 10; + let mut block_round = starting_round + weight; + pair_setup.b_mock.set_block_round(block_round); + + let lp_increase = 1_000_000; + let min_lp_amount = 1_000; + let mut lp_amount = lp_increase + min_lp_amount; + pair_setup.add_liquidity( + lp_increase + min_lp_amount, + lp_increase, + lp_increase + min_lp_amount, + lp_increase, + lp_increase, + lp_increase + min_lp_amount, + lp_increase + min_lp_amount, + ); + pair_setup.swap_fixed_input( + WEGLD_TOKEN_ID, + payment_amount, + MEX_TOKEN_ID, + 900, + expected_amount, + ); + pair_setup.check_lp_amount(lp_amount); + + block_round += weight; + expected_amount -= 2; // slippage + pair_setup.b_mock.set_block_round(block_round); + pair_setup.swap_fixed_input( + WEGLD_TOKEN_ID, + payment_amount, + MEX_TOKEN_ID, + 900, + expected_amount, + ); + pair_setup.check_lp_amount(lp_amount); + + first_token_accumulated += weight * first_token_reserve; + second_token_accumulated += weight * second_token_reserve; + + block_round += weight; + first_token_reserve += payment_amount; + second_token_reserve -= expected_amount; + first_token_accumulated += weight * first_token_reserve; + second_token_accumulated += weight * second_token_reserve; + expected_amount -= 2; + pair_setup.b_mock.set_block_round(block_round); + + // Change LP amount starting block 1030 + let lp_amount_increase = 998_005; + lp_amount += lp_amount_increase; + pair_setup.add_liquidity( + lp_increase, + lp_increase, + 996_021, + 996_021, + lp_amount_increase, + lp_increase, + 996_021, + ); + pair_setup.swap_fixed_input( + WEGLD_TOKEN_ID, + payment_amount, + MEX_TOKEN_ID, + 900, + expected_amount, + ); + pair_setup.check_lp_amount(lp_amount); + + block_round += weight; + first_token_reserve += payment_amount; + second_token_reserve -= expected_amount; + first_token_accumulated += weight * first_token_reserve; + second_token_accumulated += weight * second_token_reserve; + expected_amount -= 1; + pair_setup.b_mock.set_block_round(block_round); + pair_setup.swap_fixed_input( + WEGLD_TOKEN_ID, + payment_amount, + MEX_TOKEN_ID, + 900, + expected_amount, + ); + pair_setup.check_lp_amount(lp_amount); + + block_round += weight; + first_token_reserve += payment_amount; + second_token_reserve -= expected_amount; + first_token_accumulated += weight * first_token_reserve; + second_token_accumulated += weight * second_token_reserve; + expected_amount -= 1; + pair_setup.b_mock.set_block_round(block_round); + pair_setup.swap_fixed_input( + WEGLD_TOKEN_ID, + payment_amount, + MEX_TOKEN_ID, + 900, + expected_amount, + ); + pair_setup.check_lp_amount(lp_amount); + + // Check the normal safe price + let lp_token_amount = 100_000; + pair_setup.check_lp_tokens_safe_price( + &pair_address, + 1011, + 1019, + lp_token_amount, + WEGLD_TOKEN_ID, + 100_099, + MEX_TOKEN_ID, + 99_900, + ); + + pair_setup.check_lp_tokens_safe_price( + &pair_address, + 1020, + 1030, + lp_token_amount, + WEGLD_TOKEN_ID, + 100_199, + MEX_TOKEN_ID, + 99_801, + ); + + pair_setup.check_lp_tokens_safe_price( + &pair_address, + 1030, + 1040, + lp_token_amount, + WEGLD_TOKEN_ID, + 100_249, + MEX_TOKEN_ID, + 99_751, + ); + + // Simulate old price observations + pair_setup.set_price_observation_as_old(&pair_address, 1); + pair_setup.set_price_observation_as_old(&pair_address, 2); + + // Check migration safe price + // Both observations are old + // Latest LP amount is used, so this should be the different than before + pair_setup.check_lp_tokens_safe_price( + &pair_address, + 1011, + 1019, + lp_token_amount, + WEGLD_TOKEN_ID, + 50124, + MEX_TOKEN_ID, + 50_025, + ); + + // First observation is old and the last observation is migrated + // Latest LP amount is used, so this should be the different than before + pair_setup.check_lp_tokens_safe_price( + &pair_address, + 1020, + 1030, + lp_token_amount, + WEGLD_TOKEN_ID, + 50_174, + MEX_TOKEN_ID, + 49_975, + ); + + // Both observations are migrated, + // Saved LP is used, so this should be the same as before + pair_setup.check_lp_tokens_safe_price( + &pair_address, + 1030, + 1040, + lp_token_amount, + WEGLD_TOKEN_ID, + 100_249, + MEX_TOKEN_ID, + 99_751, + ); +} + #[test] fn test_safe_price() { let mut pair_setup = PairSetup::new(pair::contract_obj); diff --git a/dex/pair/tests/pair_setup/mod.rs b/dex/pair/tests/pair_setup/mod.rs index 5ca0fcd84..a232daf5d 100644 --- a/dex/pair/tests/pair_setup/mod.rs +++ b/dex/pair/tests/pair_setup/mod.rs @@ -1,6 +1,6 @@ use multiversx_sc::codec::multi_types::MultiValue3; use multiversx_sc::types::{ - Address, EsdtLocalRole, EsdtTokenPayment, ManagedAddress, MultiValueEncoded, + Address, BigUint, EsdtLocalRole, EsdtTokenPayment, ManagedAddress, MultiValueEncoded, }; use multiversx_sc_scenario::whitebox_legacy::TxTokenTransfer; use multiversx_sc_scenario::{ @@ -22,6 +22,7 @@ pub const USER_TOTAL_WEGLD_TOKENS: u64 = 5_000_000_000; use pair::config::ConfigModule as PairConfigModule; use pair::pair_actions::add_liq::AddLiquidityModule; use pair::pair_actions::swap::SwapModule; +use pair::safe_price::SafePriceModule; use pair::safe_price_view::*; use pair::*; use pausable::{PausableModule, State}; @@ -274,6 +275,15 @@ where ); } + pub fn check_lp_amount(&mut self, expected_amount: u64) { + self.b_mock + .execute_query(&self.pair_wrapper, |sc| { + let lp_amount = sc.lp_token_supply().get(); + assert_eq!(lp_amount, managed_biguint!(expected_amount)); + }) + .assert_ok(); + } + pub fn check_price_observation( &mut self, pair_address: &Address, @@ -358,6 +368,59 @@ where }); } + #[allow(clippy::too_many_arguments)] + pub fn check_lp_tokens_safe_price( + &mut self, + pair_address: &Address, + start_round: u64, + end_round: u64, + lp_token_amount: u64, + expected_first_token_id: &[u8], + expected_first_token_amount: u64, + expected_second_token_id: &[u8], + expected_second_token_amount: u64, + ) { + let _ = self.b_mock.execute_query(&self.pair_wrapper, |sc| { + let lp_tokens_safe_price = sc.get_lp_tokens_safe_price( + managed_address!(pair_address), + start_round, + end_round, + managed_biguint!(lp_token_amount), + ); + let (first_payment, second_payment) = lp_tokens_safe_price.into_tuple(); + assert_eq!( + first_payment.token_identifier, + managed_token_id!(expected_first_token_id) + ); + assert_eq!( + first_payment.amount, + managed_biguint!(expected_first_token_amount) + ); + assert_eq!( + second_payment.token_identifier, + managed_token_id!(expected_second_token_id) + ); + assert_eq!( + second_payment.amount, + managed_biguint!(expected_second_token_amount) + ); + }); + } + + pub fn set_price_observation_as_old( + &mut self, + pair_address: &Address, + observation_index: usize, + ) { + let _ = self.b_mock.execute_query(&self.pair_wrapper, |sc| { + let price_observations = sc.price_observations(); + + let mut price_observation = price_observations.get(observation_index); + price_observation.lp_supply_accumulated = BigUint::zero(); + price_observations.set(observation_index, &price_observation); + }); + } + #[allow(clippy::too_many_arguments)] pub fn check_safe_price_from_second_pair( &mut self,