Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safe Price accumulated LP supply migration fix #863

Merged
merged 2 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions dex/pair/src/safe_price_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);

Expand Down Expand Up @@ -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,
Expand Down
194 changes: 194 additions & 0 deletions dex/pair/tests/pair_rs_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
65 changes: 64 additions & 1 deletion dex/pair/tests/pair_setup/mod.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down