From 13dbc753a6c15fbf73f430e667a2a09d167a7e32 Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Wed, 21 Feb 2024 12:42:06 +0200 Subject: [PATCH 1/8] partial impl --- .../src/base_functions.rs | 8 +- .../src/cocentrated_liq.rs | 107 ++++++++++++++++++ .../src/custom_token_attributes.rs | 0 dex/farm-concentrated-liq/src/lib.rs | 35 ++++-- 4 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 dex/farm-concentrated-liq/src/cocentrated_liq.rs create mode 100644 dex/farm-concentrated-liq/src/custom_token_attributes.rs diff --git a/dex/farm-concentrated-liq/src/base_functions.rs b/dex/farm-concentrated-liq/src/base_functions.rs index 3e6620120..5cbcd114e 100644 --- a/dex/farm-concentrated-liq/src/base_functions.rs +++ b/dex/farm-concentrated-liq/src/base_functions.rs @@ -193,7 +193,7 @@ pub trait BaseFunctionsModule: } fn claim_only_boosted_payment(&self, caller: &ManagedAddress) -> BigUint { - let reward = Wrapper::::calculate_boosted_rewards(self, caller); + let reward = FarmConcentratedLiqWrapper::::calculate_boosted_rewards(self, caller); if reward > 0 { self.reward_reserve().update(|reserve| *reserve -= &reward); } @@ -227,7 +227,7 @@ pub trait BaseFunctionsModule: } } -pub struct Wrapper< +pub struct FarmConcentratedLiqWrapper< T: BaseFunctionsModule + farm_boosted_yields::FarmBoostedYieldsModule + crate::exit_penalty::ExitPenaltyModule, @@ -235,7 +235,7 @@ pub struct Wrapper< _phantom: PhantomData, } -impl Wrapper +impl FarmConcentratedLiqWrapper where T: BaseFunctionsModule + farm_boosted_yields::FarmBoostedYieldsModule @@ -252,7 +252,7 @@ where } } -impl FarmContract for Wrapper +impl FarmContract for FarmConcentratedLiqWrapper where T: BaseFunctionsModule + farm_boosted_yields::FarmBoostedYieldsModule diff --git a/dex/farm-concentrated-liq/src/cocentrated_liq.rs b/dex/farm-concentrated-liq/src/cocentrated_liq.rs new file mode 100644 index 000000000..0a6c4edeb --- /dev/null +++ b/dex/farm-concentrated-liq/src/cocentrated_liq.rs @@ -0,0 +1,107 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub type Tick = i32; + +pub const PRICE_DECIMALS: u64 = 1_000_000_000_000_000_000; +pub const PRICE_INCREASE_PER_TICK: i32 = 10_001; +pub const PRICE_SCALING_FACTOR: i32 = 10_000; + +// TODO: Import from pair +#[derive(TypeAbi, NestedEncode, NestedDecode, TopEncode, TopDecode, Clone)] +pub struct LpTokenAttributes { + pub virtual_liquidity: BigUint, + pub tick_min: Tick, + pub tick_max: Tick, + pub first_token_accumulated_fee: BigUint, + pub second_token_accumulated_fee: BigUint, +} + +#[multiversx_sc::module] +pub trait ConcentratedLiqModule: + farm_token::FarmTokenModule + + permissions_module::PermissionsModule + + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule +{ + fn update_token_amounts_after_enter( + &self, + payment_amount: &BigUint, + attributes: &LpTokenAttributes, + ) { + let price_min = self.tick_to_price(attributes.tick_min); + let price_max = self.tick_to_price(attributes.tick_max); + let price_min_adjusted = self.price_to_closest_min_ticker_multiply(&price_min); + let price_max_adjusted = self.price_to_closest_min_ticker_multiply(&price_max); + + self.tokens_with_min(&price_min_adjusted) + .update(|value| *value += payment_amount); + self.tokens_with_max(&price_max_adjusted) + .update(|value| *value += payment_amount); + } + + fn get_price(&self, _lp_token: EsdtTokenPayment) -> BigUint { + let last_queried_price = self.last_queried_price().get(); + if last_queried_price == 0 { + todo!(); + } + + todo!() + } + + fn tick_to_price(&self, tick: Tick) -> BigUint { + let price_base = BigFloat::from(PRICE_INCREASE_PER_TICK) / PRICE_SCALING_FACTOR.into(); + let price = price_base.pow(tick); + let price_scaled_down = price + .ceil() + .into_big_uint() + .unwrap_or_else(|| sc_panic!("Could not convert to BigUint")); + + price_scaled_down * PRICE_DECIMALS + } + + fn price_to_closest_min_ticker_multiply(&self, price: &BigUint) -> BigUint { + let min_ticker = self.min_ticker().get(); + let lower_bound = price / &min_ticker * &min_ticker; + let upper_bound = &lower_bound + &min_ticker; + + let lower_diff = price - &lower_bound; + let upper_diff = &upper_bound - price; + if lower_diff < upper_diff { + lower_bound + } else { + upper_bound + } + } + + fn price_to_tick(&self, price: BigUint) -> Tick { + let log_numerator = BigFloat::from(price) / BigFloat::from(BigUint::from(PRICE_DECIMALS)); + let log_base = + BigFloat::from(PRICE_INCREASE_PER_TICK) / BigFloat::from(PRICE_SCALING_FACTOR); + + self.log_base_n(log_numerator, log_base) + } + + // TODO: Find a better solution + fn log_base_n(&self, numerator: BigFloat, base: BigFloat) -> i32 { + let mut result = 0; + let mut num = numerator; + while num >= base { + num /= &base; + result += 1; + } + + result + } + + #[storage_mapper("minTicker")] + fn min_ticker(&self) -> SingleValueMapper; + + #[storage_mapper("tokensWithMin")] + fn tokens_with_min(&self, price_min: &BigUint) -> SingleValueMapper; + + #[storage_mapper("tokensWithMax")] + fn tokens_with_max(&self, price_max: &BigUint) -> SingleValueMapper; + + #[storage_mapper("lastQueriedPrice")] + fn last_queried_price(&self) -> SingleValueMapper; +} diff --git a/dex/farm-concentrated-liq/src/custom_token_attributes.rs b/dex/farm-concentrated-liq/src/custom_token_attributes.rs new file mode 100644 index 000000000..e69de29bb diff --git a/dex/farm-concentrated-liq/src/lib.rs b/dex/farm-concentrated-liq/src/lib.rs index 3aedbdfdc..9c238682a 100644 --- a/dex/farm-concentrated-liq/src/lib.rs +++ b/dex/farm-concentrated-liq/src/lib.rs @@ -4,10 +4,13 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); pub mod base_functions; +pub mod cocentrated_liq; +pub mod custom_token_attributes; pub mod exit_penalty; use base_functions::{ - ClaimRewardsResultType, CompoundRewardsResultType, ExitFarmResultType, Wrapper, + ClaimRewardsResultType, CompoundRewardsResultType, ExitFarmResultType, + FarmConcentratedLiqWrapper, }; use common_structs::FarmTokenAttributes; use contexts::storage_cache::StorageCache; @@ -58,6 +61,7 @@ pub trait Farm: + weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule + energy_query::EnergyQueryModule + utils::UtilsModule + + cocentrated_liq::ConcentratedLiqModule { #[init] fn init( @@ -65,6 +69,7 @@ pub trait Farm: reward_token_id: TokenIdentifier, farming_token_id: TokenIdentifier, division_safety_constant: BigUint, + min_ticker: BigUint, pair_contract_address: ManagedAddress, owner: ManagedAddress, admins: MultiValueEncoded, @@ -77,11 +82,15 @@ pub trait Farm: admins, ); + require!(min_ticker > 0, "Invalid min ticker value"); + self.penalty_percent().set(DEFAULT_PENALTY_PERCENT); self.minimum_farming_epochs() .set(DEFAULT_MINUMUM_FARMING_EPOCHS); self.burn_gas_limit().set(DEFAULT_BURN_GAS_LIMIT); - self.pair_contract_address().set(&pair_contract_address); + + self.min_ticker().set(min_ticker); + self.pair_contract_address().set(pair_contract_address); let current_epoch = self.blockchain().get_block_epoch(); self.first_week_start_epoch().set(current_epoch); @@ -102,7 +111,8 @@ pub trait Farm: let boosted_rewards_payment = EsdtTokenPayment::new(self.reward_token_id().get(), 0, boosted_rewards); - let new_farm_token = self.enter_farm::>(orig_caller.clone()); + let new_farm_token = + self.enter_farm::>(orig_caller.clone()); self.send_payment_non_zero(&caller, &new_farm_token); self.send_payment_non_zero(&caller, &boosted_rewards_payment); @@ -122,7 +132,8 @@ pub trait Farm: ) -> ClaimRewardsResultType { let caller = self.blockchain().get_caller(); let orig_caller = self.get_orig_caller_from_opt(&caller, opt_orig_caller); - let claim_rewards_result = self.claim_rewards::>(orig_caller); + let claim_rewards_result = + self.claim_rewards::>(orig_caller); self.send_payment_non_zero(&caller, &claim_rewards_result.new_farm_token); self.send_payment_non_zero(&caller, &claim_rewards_result.rewards); @@ -138,7 +149,8 @@ pub trait Farm: ) -> CompoundRewardsResultType { let caller = self.blockchain().get_caller(); let orig_caller = self.get_orig_caller_from_opt(&caller, opt_orig_caller); - let compund_result = self.compound_rewards::>(orig_caller.clone()); + let compund_result = + self.compound_rewards::>(orig_caller.clone()); self.send_payment_non_zero(&caller, &compund_result.new_farm_token); @@ -157,7 +169,8 @@ pub trait Farm: let orig_caller = self.get_orig_caller_from_opt(&caller, opt_orig_caller); let payment = self.call_value().single_esdt(); - let exit_farm_result = self.exit_farm::>(orig_caller.clone(), payment); + let exit_farm_result = + self.exit_farm::>(orig_caller.clone(), payment); self.send_payment_non_zero(&caller, &exit_farm_result.farming_tokens); self.send_payment_non_zero(&caller, &exit_farm_result.rewards); @@ -179,7 +192,7 @@ pub trait Farm: let boosted_rewards_payment = EsdtTokenPayment::new(self.reward_token_id().get(), 0, boosted_rewards); - let merged_farm_token = self.merge_farm_tokens::>(); + let merged_farm_token = self.merge_farm_tokens::>(); self.send_payment_non_zero(&caller, &merged_farm_token); self.send_payment_non_zero(&caller, &boosted_rewards_payment); @@ -222,13 +235,13 @@ pub trait Farm: #[endpoint(endProduceRewards)] fn end_produce_rewards_endpoint(&self) { self.require_caller_has_admin_permissions(); - self.end_produce_rewards::>(); + self.end_produce_rewards::>(); } #[endpoint(setPerBlockRewardAmount)] fn set_per_block_rewards_endpoint(&self, per_block_amount: BigUint) { self.require_caller_has_admin_permissions(); - self.set_per_block_rewards::>(per_block_amount); + self.set_per_block_rewards::>(per_block_amount); } #[view(calculateRewardsForGivenPosition)] @@ -241,9 +254,9 @@ pub trait Farm: self.require_queried(); let mut storage_cache = StorageCache::new(self); - Wrapper::::generate_aggregated_rewards(self, &mut storage_cache); + FarmConcentratedLiqWrapper::::generate_aggregated_rewards(self, &mut storage_cache); - Wrapper::::calculate_rewards( + FarmConcentratedLiqWrapper::::calculate_rewards( self, &user, &farm_token_amount, From 05dbe8a68afae2009708a95a6175527d8fbf6b74 Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Wed, 21 Feb 2024 15:04:06 +0200 Subject: [PATCH 2/8] update farm token supply --- .../src/cocentrated_liq.rs | 111 +++++++++++++++--- 1 file changed, 97 insertions(+), 14 deletions(-) diff --git a/dex/farm-concentrated-liq/src/cocentrated_liq.rs b/dex/farm-concentrated-liq/src/cocentrated_liq.rs index 0a6c4edeb..00c90692e 100644 --- a/dex/farm-concentrated-liq/src/cocentrated_liq.rs +++ b/dex/farm-concentrated-liq/src/cocentrated_liq.rs @@ -3,6 +3,7 @@ multiversx_sc::derive_imports!(); pub type Tick = i32; +// TODO: Import from pair pub const PRICE_DECIMALS: u64 = 1_000_000_000_000_000_000; pub const PRICE_INCREASE_PER_TICK: i32 = 10_001; pub const PRICE_SCALING_FACTOR: i32 = 10_000; @@ -17,6 +18,11 @@ pub struct LpTokenAttributes { pub second_token_accumulated_fee: BigUint, } +pub struct PriceBounds { + pub lower: BigUint, + pub upper: BigUint, +} + #[multiversx_sc::module] pub trait ConcentratedLiqModule: farm_token::FarmTokenModule @@ -28,10 +34,8 @@ pub trait ConcentratedLiqModule: payment_amount: &BigUint, attributes: &LpTokenAttributes, ) { - let price_min = self.tick_to_price(attributes.tick_min); - let price_max = self.tick_to_price(attributes.tick_max); - let price_min_adjusted = self.price_to_closest_min_ticker_multiply(&price_min); - let price_max_adjusted = self.price_to_closest_min_ticker_multiply(&price_max); + let price_min_adjusted = self.tick_to_closest_min_ticker_multiply(attributes.tick_min); + let price_max_adjusted = self.tick_to_closest_min_ticker_multiply(attributes.tick_max); self.tokens_with_min(&price_min_adjusted) .update(|value| *value += payment_amount); @@ -39,13 +43,79 @@ pub trait ConcentratedLiqModule: .update(|value| *value += payment_amount); } - fn get_price(&self, _lp_token: EsdtTokenPayment) -> BigUint { + fn update_token_amounts_after_exit( + &self, + farming_token_amount: &BigUint, + attributes: &LpTokenAttributes, + ) { + let price_min_adjusted = self.tick_to_closest_min_ticker_multiply(attributes.tick_min); + let price_max_adjusted = self.tick_to_closest_min_ticker_multiply(attributes.tick_max); + + self.tokens_with_min(&price_min_adjusted) + .update(|value| *value -= farming_token_amount); + self.tokens_with_max(&price_max_adjusted) + .update(|value| *value -= farming_token_amount); + } + + #[inline(always)] + fn update_token_amounts_after_compound( + &self, + compounded_amount: &BigUint, + attributes: &LpTokenAttributes, + ) { + self.update_token_amounts_after_enter(compounded_amount, attributes); + } + + // TODO: Query price! + fn get_price_and_update_farm_token_supply(&self, _lp_token: EsdtTokenPayment) -> BigUint { + let queried_price = BigUint::zero(); + let last_queried_price = self.last_queried_price().get(); if last_queried_price == 0 { - todo!(); + self.last_queried_price().set(&queried_price); + + return queried_price; + } + + if queried_price == last_queried_price { + return queried_price; } - todo!() + let price_bounds = self.get_price_bounds(&last_queried_price); + let mut current_price = if queried_price > last_queried_price { + price_bounds.upper + } else { + price_bounds.lower + }; + + let min_ticker = self.min_ticker().get(); + self.farm_token_supply().update(|farm_token_supply| { + if queried_price > last_queried_price { + while current_price <= queried_price { + let tokens_with_min = self.tokens_with_min(¤t_price).get(); + let tokens_with_max = self.tokens_with_max(¤t_price).get(); + + *farm_token_supply += tokens_with_min; + *farm_token_supply -= tokens_with_max; + + current_price += &min_ticker; + } + } else { + while current_price >= queried_price { + let tokens_with_min = self.tokens_with_min(¤t_price).get(); + let tokens_with_max = self.tokens_with_max(¤t_price).get(); + + *farm_token_supply -= tokens_with_min; + *farm_token_supply += tokens_with_max; + + current_price -= &min_ticker; + } + } + }); + + self.last_queried_price().set(&queried_price); + + queried_price } fn tick_to_price(&self, tick: Tick) -> BigUint { @@ -60,19 +130,32 @@ pub trait ConcentratedLiqModule: } fn price_to_closest_min_ticker_multiply(&self, price: &BigUint) -> BigUint { + let price_bounds = self.get_price_bounds(price); + let lower_diff = price - &price_bounds.lower; + let upper_diff = &price_bounds.upper - price; + if lower_diff < upper_diff { + price_bounds.lower + } else { + price_bounds.upper + } + } + + fn get_price_bounds(&self, price: &BigUint) -> PriceBounds { let min_ticker = self.min_ticker().get(); - let lower_bound = price / &min_ticker * &min_ticker; + let lower_bound = price / &min_ticker * &min_ticker; let upper_bound = &lower_bound + &min_ticker; - let lower_diff = price - &lower_bound; - let upper_diff = &upper_bound - price; - if lower_diff < upper_diff { - lower_bound - } else { - upper_bound + PriceBounds { + lower: lower_bound, + upper: upper_bound, } } + fn tick_to_closest_min_ticker_multiply(&self, tick: Tick) -> BigUint { + let price = self.tick_to_price(tick); + self.price_to_closest_min_ticker_multiply(&price) + } + fn price_to_tick(&self, price: BigUint) -> Tick { let log_numerator = BigFloat::from(price) / BigFloat::from(BigUint::from(PRICE_DECIMALS)); let log_base = From 73441c09caebe3f5687a21d006adfe6a0544f3fa Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Wed, 21 Feb 2024 15:43:18 +0200 Subject: [PATCH 3/8] query price --- .../src/cocentrated_liq.rs | 35 ++++++++++++++++-- dex/farm-concentrated-liq/src/lib.rs | 25 +++++++++++++ dex/pair/src/safe_price_view.rs | 36 +++++++++++-------- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/dex/farm-concentrated-liq/src/cocentrated_liq.rs b/dex/farm-concentrated-liq/src/cocentrated_liq.rs index 00c90692e..6bb8c55bb 100644 --- a/dex/farm-concentrated-liq/src/cocentrated_liq.rs +++ b/dex/farm-concentrated-liq/src/cocentrated_liq.rs @@ -1,3 +1,5 @@ +use pair::safe_price_view::{ProxyTrait as _, SafePriceLpToken}; + multiversx_sc::imports!(); multiversx_sc::derive_imports!(); @@ -28,6 +30,7 @@ pub trait ConcentratedLiqModule: farm_token::FarmTokenModule + permissions_module::PermissionsModule + multiversx_sc_modules::default_issue_callbacks::DefaultIssueCallbacksModule + + crate::exit_penalty::ExitPenaltyModule { fn update_token_amounts_after_enter( &self, @@ -66,9 +69,8 @@ pub trait ConcentratedLiqModule: self.update_token_amounts_after_enter(compounded_amount, attributes); } - // TODO: Query price! - fn get_price_and_update_farm_token_supply(&self, _lp_token: EsdtTokenPayment) -> BigUint { - let queried_price = BigUint::zero(); + fn get_price_and_update_farm_token_supply(&self, lp_token_amount: BigUint) -> BigUint { + let queried_price = self.get_price(lp_token_amount); let last_queried_price = self.last_queried_price().get(); if last_queried_price == 0 { @@ -176,6 +178,27 @@ pub trait ConcentratedLiqModule: result } + fn get_price(&self, lp_token_amount: BigUint) -> BigUint { + let pair_for_query = self.pair_for_query().get(); + let pair_for_price = self.pair_contract_address().get(); + let safe_price_lp_token: SafePriceLpToken = self + .pair_proxy(pair_for_query) + .get_lp_tokens_safe_price_by_default_offset(pair_for_price, lp_token_amount) + .execute_on_dest_context(); + + let price_token = self.price_token().get(); + if safe_price_lp_token.first_token_payment.token_identifier == price_token { + safe_price_lp_token.first_token_payment.amount + } else if safe_price_lp_token.second_token_payment.token_identifier == price_token { + safe_price_lp_token.second_token_payment.amount + } else { + sc_panic!("Invalid tokens in pair") + } + } + + #[proxy] + fn pair_proxy(&self, sc_address: ManagedAddress) -> pair::Proxy; + #[storage_mapper("minTicker")] fn min_ticker(&self) -> SingleValueMapper; @@ -187,4 +210,10 @@ pub trait ConcentratedLiqModule: #[storage_mapper("lastQueriedPrice")] fn last_queried_price(&self) -> SingleValueMapper; + + #[storage_mapper("pairForQueery")] + fn pair_for_query(&self) -> SingleValueMapper; + + #[storage_mapper("priceToken")] + fn price_token(&self) -> SingleValueMapper; } diff --git a/dex/farm-concentrated-liq/src/lib.rs b/dex/farm-concentrated-liq/src/lib.rs index 9c238682a..5e1c5faf3 100644 --- a/dex/farm-concentrated-liq/src/lib.rs +++ b/dex/farm-concentrated-liq/src/lib.rs @@ -71,6 +71,8 @@ pub trait Farm: division_safety_constant: BigUint, min_ticker: BigUint, pair_contract_address: ManagedAddress, + pair_for_query: ManagedAddress, + price_token: TokenIdentifier, owner: ManagedAddress, admins: MultiValueEncoded, ) { @@ -82,8 +84,21 @@ pub trait Farm: admins, ); + self.require_sc_address(&pair_contract_address); + self.require_sc_address(&pair_for_query); require!(min_ticker > 0, "Invalid min ticker value"); + let first_token_id = self + .first_token_id() + .get_from_address(&pair_contract_address); + let second_token_id = self + .second_token_id() + .get_from_address(&pair_contract_address); + require!( + price_token == first_token_id || price_token == second_token_id, + "Invalid price token" + ); + self.penalty_percent().set(DEFAULT_PENALTY_PERCENT); self.minimum_farming_epochs() .set(DEFAULT_MINUMUM_FARMING_EPOCHS); @@ -91,6 +106,8 @@ pub trait Farm: self.min_ticker().set(min_ticker); self.pair_contract_address().set(pair_contract_address); + self.pair_for_query().set(pair_for_query); + self.price_token().set(price_token); let current_epoch = self.blockchain().get_block_epoch(); self.first_week_start_epoch().set(current_epoch); @@ -264,4 +281,12 @@ pub trait Farm: &storage_cache, ) } + + // Pair storage - used for validation + + #[storage_mapper("first_token_id")] + fn first_token_id(&self) -> SingleValueMapper; + + #[storage_mapper("second_token_id")] + fn second_token_id(&self) -> SingleValueMapper; } diff --git a/dex/pair/src/safe_price_view.rs b/dex/pair/src/safe_price_view.rs index 4a1042a56..5ec048571 100644 --- a/dex/pair/src/safe_price_view.rs +++ b/dex/pair/src/safe_price_view.rs @@ -1,4 +1,5 @@ multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); use common_errors::{ERROR_BAD_INPUT_TOKEN, ERROR_PARAMETERS}; use core::cmp::Ordering; @@ -12,6 +13,12 @@ use crate::{ pub const DEFAULT_SAFE_PRICE_ROUNDS_OFFSET: u64 = 10 * 60; pub const SECONDS_PER_ROUND: u64 = 6; +#[derive(TypeAbi, TopEncode, TopDecode, NestedEncode, NestedDecode)] +pub struct SafePriceLpToken { + pub first_token_payment: EsdtTokenPayment, + pub second_token_payment: EsdtTokenPayment, +} + #[multiversx_sc::module] pub trait SafePriceViewModule: safe_price::SafePriceModule @@ -27,7 +34,7 @@ pub trait SafePriceViewModule: &self, pair_address: ManagedAddress, liquidity: BigUint, - ) -> MultiValue2 { + ) -> SafePriceLpToken { let current_round = self.blockchain().get_block_round(); let default_offset_rounds = self.get_default_offset_rounds(&pair_address, current_round); let start_round = current_round - default_offset_rounds; @@ -42,7 +49,7 @@ pub trait SafePriceViewModule: pair_address: ManagedAddress, round_offset: Round, liquidity: BigUint, - ) -> MultiValue2 { + ) -> SafePriceLpToken { let current_round = self.blockchain().get_block_round(); require!( round_offset > 0 && round_offset < current_round, @@ -60,7 +67,7 @@ pub trait SafePriceViewModule: pair_address: ManagedAddress, timestamp_offset: u64, liquidity: BigUint, - ) -> MultiValue2 { + ) -> SafePriceLpToken { let current_round = self.blockchain().get_block_round(); let round_offset = timestamp_offset / SECONDS_PER_ROUND; require!( @@ -80,18 +87,17 @@ pub trait SafePriceViewModule: start_round: Round, end_round: Round, liquidity: BigUint, - ) -> MultiValue2 { + ) -> SafePriceLpToken { require!(end_round > start_round, ERROR_PARAMETERS); let lp_total_supply = self.lp_token_supply().get_from_address(&pair_address); let first_token_id = self.first_token_id().get_from_address(&pair_address); let second_token_id = self.second_token_id().get_from_address(&pair_address); if lp_total_supply == 0 { - return ( - EsdtTokenPayment::new(first_token_id, 0, BigUint::zero()), - EsdtTokenPayment::new(second_token_id, 0, BigUint::zero()), - ) - .into(); + return SafePriceLpToken { + first_token_payment: EsdtTokenPayment::new(first_token_id, 0, BigUint::zero()), + second_token_payment: EsdtTokenPayment::new(second_token_id, 0, BigUint::zero()), + }; } let safe_price_current_index = self @@ -136,7 +142,10 @@ pub trait SafePriceViewModule: let first_token_payment = EsdtTokenPayment::new(first_token_id, 0, first_token_worth); let second_token_payment = EsdtTokenPayment::new(second_token_id, 0, second_token_worth); - (first_token_payment, second_token_payment).into() + SafePriceLpToken { + first_token_payment, + second_token_payment, + } } #[label("safe-price-view")] @@ -522,16 +531,13 @@ pub trait SafePriceViewModule: fn update_and_get_tokens_for_given_position_with_safe_price( &self, liquidity: BigUint, - ) -> MultiValue2, EsdtTokenPayment> { + ) -> SafePriceLpToken { let pair_address = self.blockchain().get_sc_address(); self.get_lp_tokens_safe_price_by_default_offset(pair_address, liquidity) } #[endpoint(updateAndGetSafePrice)] - fn update_and_get_safe_price( - &self, - input: EsdtTokenPayment, - ) -> EsdtTokenPayment { + fn update_and_get_safe_price(&self, input: EsdtTokenPayment) -> EsdtTokenPayment { let pair_address = self.blockchain().get_sc_address(); self.get_safe_price_by_default_offset(pair_address, input) } From e9e382c0943b3250ada747349bb3e6be4f715947 Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Fri, 23 Feb 2024 09:28:12 +0200 Subject: [PATCH 4/8] rename --- .../src/{cocentrated_liq.rs => concentrated_liq.rs} | 0 dex/farm-concentrated-liq/src/lib.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename dex/farm-concentrated-liq/src/{cocentrated_liq.rs => concentrated_liq.rs} (100%) diff --git a/dex/farm-concentrated-liq/src/cocentrated_liq.rs b/dex/farm-concentrated-liq/src/concentrated_liq.rs similarity index 100% rename from dex/farm-concentrated-liq/src/cocentrated_liq.rs rename to dex/farm-concentrated-liq/src/concentrated_liq.rs diff --git a/dex/farm-concentrated-liq/src/lib.rs b/dex/farm-concentrated-liq/src/lib.rs index 5e1c5faf3..3feb19565 100644 --- a/dex/farm-concentrated-liq/src/lib.rs +++ b/dex/farm-concentrated-liq/src/lib.rs @@ -4,7 +4,7 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); pub mod base_functions; -pub mod cocentrated_liq; +pub mod concentrated_liq; pub mod custom_token_attributes; pub mod exit_penalty; @@ -61,7 +61,7 @@ pub trait Farm: + weekly_rewards_splitting::update_claim_progress_energy::UpdateClaimProgressEnergyModule + energy_query::EnergyQueryModule + utils::UtilsModule - + cocentrated_liq::ConcentratedLiqModule + + concentrated_liq::ConcentratedLiqModule { #[init] fn init( From c06b10670b31c08e6d41d97593591485c4da4de3 Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Fri, 23 Feb 2024 09:33:21 +0200 Subject: [PATCH 5/8] fix farm staking proxy --- .../src/external_contracts_interactions.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/farm-staking/farm-staking-proxy/src/external_contracts_interactions.rs b/farm-staking/farm-staking-proxy/src/external_contracts_interactions.rs index f8048d66b..471a5ca5e 100644 --- a/farm-staking/farm-staking-proxy/src/external_contracts_interactions.rs +++ b/farm-staking/farm-staking-proxy/src/external_contracts_interactions.rs @@ -11,13 +11,11 @@ use farm_staking::{ use farm_with_locked_rewards::ProxyTrait as _; use pair::{ pair_actions::{common_result_types::RemoveLiquidityResultType, remove_liq::ProxyTrait as _}, - safe_price_view::ProxyTrait as _, + safe_price_view::{ProxyTrait as _, SafePriceLpToken}, }; use crate::result_types::*; -pub type SafePriceResult = MultiValue2, EsdtTokenPayment>; - #[multiversx_sc::module] pub trait ExternalContractsInteractionsModule: crate::lp_farm_token::LpFarmTokenModule + utils::UtilsModule + energy_query::EnergyQueryModule @@ -199,17 +197,16 @@ pub trait ExternalContractsInteractionsModule: fn get_lp_tokens_safe_price(&self, lp_tokens_amount: BigUint) -> BigUint { let pair_address = self.pair_address().get(); - let result: SafePriceResult = self + let result: SafePriceLpToken = self .pair_proxy_obj(pair_address) .update_and_get_tokens_for_given_position_with_safe_price(lp_tokens_amount) .execute_on_dest_context(); - let (first_token_info, second_token_info) = result.into_tuple(); let staking_token_id = self.staking_token_id().get(); - if first_token_info.token_identifier == staking_token_id { - first_token_info.amount - } else if second_token_info.token_identifier == staking_token_id { - second_token_info.amount + if result.first_token_payment.token_identifier == staking_token_id { + result.first_token_payment.amount + } else if result.second_token_payment.token_identifier == staking_token_id { + result.second_token_payment.amount } else { sc_panic!("Invalid Pair contract called"); } From fd811f3607d31064e3ba0e65e42626ac3d6e62ed Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Fri, 23 Feb 2024 09:52:21 +0200 Subject: [PATCH 6/8] remove asignment --- dex/farm-concentrated-liq/src/concentrated_liq.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dex/farm-concentrated-liq/src/concentrated_liq.rs b/dex/farm-concentrated-liq/src/concentrated_liq.rs index 6bb8c55bb..f2b65618e 100644 --- a/dex/farm-concentrated-liq/src/concentrated_liq.rs +++ b/dex/farm-concentrated-liq/src/concentrated_liq.rs @@ -167,11 +167,10 @@ pub trait ConcentratedLiqModule: } // TODO: Find a better solution - fn log_base_n(&self, numerator: BigFloat, base: BigFloat) -> i32 { + fn log_base_n(&self, mut numerator: BigFloat, base: BigFloat) -> i32 { let mut result = 0; - let mut num = numerator; - while num >= base { - num /= &base; + while numerator >= base { + numerator /= &base; result += 1; } From d4ec3f276d32ab612b700f2a1ec49df2f713a6f9 Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Fri, 23 Feb 2024 11:27:50 +0200 Subject: [PATCH 7/8] new attributes type --- Cargo.lock | 1 + .../farm_base_impl/src/base_traits_impl.rs | 4 +- .../farm/farm_base_impl/src/enter_farm.rs | 2 +- dex/farm-concentrated-liq/Cargo.toml | 3 + .../src/base_functions.rs | 78 +++++++++++-- .../src/custom_token_attributes.rs | 106 ++++++++++++++++++ dex/farm-concentrated-liq/src/lib.rs | 4 +- dex/farm-concentrated-liq/wasm/Cargo.lock | 1 + .../farm-staking/src/base_impl_wrapper.rs | 4 +- 9 files changed, 185 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc1da0bf0..73c6e21ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,6 +793,7 @@ dependencies = [ "farm_base_impl", "farm_token", "fixed-supply-token", + "math", "mergeable", "multiversx-sc", "multiversx-sc-modules", diff --git a/common/modules/farm/farm_base_impl/src/base_traits_impl.rs b/common/modules/farm/farm_base_impl/src/base_traits_impl.rs index ecf0f6e66..a2078e0b0 100644 --- a/common/modules/farm/farm_base_impl/src/base_traits_impl.rs +++ b/common/modules/farm/farm_base_impl/src/base_traits_impl.rs @@ -116,7 +116,7 @@ pub trait FarmContract { fn create_enter_farm_initial_attributes( sc: &Self::FarmSc, caller: ManagedAddress<::Api>, - farming_token_amount: BigUint<::Api>, + farming_token_payment: EsdtTokenPayment<::Api>, current_reward_per_share: BigUint<::Api>, ) -> Self::AttributesType { let current_epoch = sc.blockchain().get_block_epoch(); @@ -124,7 +124,7 @@ pub trait FarmContract { reward_per_share: current_reward_per_share, entering_epoch: current_epoch, compounded_reward: BigUint::zero(), - current_farm_amount: farming_token_amount, + current_farm_amount: farming_token_payment.amount, original_owner: caller, }; diff --git a/common/modules/farm/farm_base_impl/src/enter_farm.rs b/common/modules/farm/farm_base_impl/src/enter_farm.rs index 02e20d42a..45e6ff49d 100644 --- a/common/modules/farm/farm_base_impl/src/enter_farm.rs +++ b/common/modules/farm/farm_base_impl/src/enter_farm.rs @@ -64,7 +64,7 @@ pub trait BaseEnterFarmModule: let base_attributes = FC::create_enter_farm_initial_attributes( self, caller, - enter_farm_context.farming_token_payment.amount.clone(), + enter_farm_context.farming_token_payment.clone(), storage_cache.reward_per_share.clone(), ); let new_farm_token = self.merge_and_create_token( diff --git a/dex/farm-concentrated-liq/Cargo.toml b/dex/farm-concentrated-liq/Cargo.toml index 8a73b96fa..a6358d3fe 100644 --- a/dex/farm-concentrated-liq/Cargo.toml +++ b/dex/farm-concentrated-liq/Cargo.toml @@ -68,6 +68,9 @@ path = "../../energy-integration/common-modules/weekly-rewards-splitting" [dependencies.energy-query] path = "../../energy-integration/common-modules/energy-query" +[dependencies.math] +path = "../../common/modules/math" + [dependencies.multiversx-sc] version = "=0.46.1" features = ["esdt-token-payment-legacy-decode"] diff --git a/dex/farm-concentrated-liq/src/base_functions.rs b/dex/farm-concentrated-liq/src/base_functions.rs index 5cbcd114e..eae5f94a8 100644 --- a/dex/farm-concentrated-liq/src/base_functions.rs +++ b/dex/farm-concentrated-liq/src/base_functions.rs @@ -7,13 +7,13 @@ multiversx_sc::derive_imports!(); use core::marker::PhantomData; use common_errors::ERROR_ZERO_AMOUNT; -use common_structs::FarmTokenAttributes; +use common_structs::FarmToken; use contexts::storage_cache::StorageCache; -use farm_base_impl::base_traits_impl::{DefaultFarmWrapper, FarmContract}; +use farm_base_impl::base_traits_impl::FarmContract; use fixed_supply_token::FixedSupplyToken; -use crate::exit_penalty; +use crate::{custom_token_attributes::FarmTokenConcentratedLiqAttributes, exit_penalty}; pub const DEFAULT_FARM_POSITION_MIGRATION_NONCE: u64 = 1; @@ -259,7 +259,7 @@ where + crate::exit_penalty::ExitPenaltyModule, { type FarmSc = T; - type AttributesType = FarmTokenAttributes<::Api>; + type AttributesType = FarmTokenConcentratedLiqAttributes<::Api>; fn generate_aggregated_rewards( sc: &Self::FarmSc, @@ -289,13 +289,14 @@ where token_attributes: &Self::AttributesType, storage_cache: &StorageCache, ) -> BigUint<::Api> { - let base_farm_reward = DefaultFarmWrapper::::calculate_rewards( - sc, - caller, - farm_token_amount, - token_attributes, - storage_cache, - ); + let token_rps = token_attributes.get_reward_per_share(); + let base_farm_reward = if storage_cache.reward_per_share <= token_rps { + BigUint::zero() + } else { + let rps_diff = &storage_cache.reward_per_share - &token_rps; + farm_token_amount * &rps_diff / &storage_cache.division_safety_constant + }; + let boosted_yield_rewards = Self::calculate_boosted_rewards(sc, caller); base_farm_reward + boosted_yield_rewards @@ -335,4 +336,59 @@ where &storage_cache.reward_token_id, ); } + + fn create_enter_farm_initial_attributes( + sc: &Self::FarmSc, + caller: ManagedAddress<::Api>, + farming_token_payment: EsdtTokenPayment<::Api>, + current_reward_per_share: BigUint<::Api>, + ) -> Self::AttributesType { + let current_epoch = sc.blockchain().get_block_epoch(); + FarmTokenConcentratedLiqAttributes { + reward_per_share: current_reward_per_share, + entering_epoch: current_epoch, + compounded_reward: BigUint::zero(), + current_farm_amount: farming_token_payment.amount, + original_owner: caller, + lp_token_nonce: farming_token_payment.token_nonce, + } + } + + fn create_claim_rewards_initial_attributes( + _sc: &Self::FarmSc, + caller: ManagedAddress<::Api>, + first_token_attributes: Self::AttributesType, + current_reward_per_share: BigUint<::Api>, + ) -> Self::AttributesType { + let current_farm_amount = first_token_attributes.get_total_supply(); + FarmTokenConcentratedLiqAttributes { + reward_per_share: current_reward_per_share, + entering_epoch: first_token_attributes.entering_epoch, + compounded_reward: first_token_attributes.compounded_reward, + current_farm_amount, + original_owner: caller, + lp_token_nonce: first_token_attributes.lp_token_nonce, + } + } + + fn create_compound_rewards_initial_attributes( + sc: &Self::FarmSc, + caller: ManagedAddress<::Api>, + first_token_attributes: Self::AttributesType, + current_reward_per_share: BigUint<::Api>, + reward: &BigUint<::Api>, + ) -> Self::AttributesType { + let current_epoch = sc.blockchain().get_block_epoch(); + let new_pos_compounded_reward = first_token_attributes.compounded_reward + reward; + let new_pos_current_farm_amount = first_token_attributes.current_farm_amount + reward; + + FarmTokenConcentratedLiqAttributes { + reward_per_share: current_reward_per_share, + entering_epoch: current_epoch, + compounded_reward: new_pos_compounded_reward, + current_farm_amount: new_pos_current_farm_amount, + original_owner: caller, + lp_token_nonce: first_token_attributes.lp_token_nonce, + } + } } diff --git a/dex/farm-concentrated-liq/src/custom_token_attributes.rs b/dex/farm-concentrated-liq/src/custom_token_attributes.rs index e69de29bb..bff387252 100644 --- a/dex/farm-concentrated-liq/src/custom_token_attributes.rs +++ b/dex/farm-concentrated-liq/src/custom_token_attributes.rs @@ -0,0 +1,106 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use common_structs::{Epoch, FarmToken, FarmTokenAttributes, Nonce}; +use fixed_supply_token::FixedSupplyToken; +use math::weighted_average_round_up; +use mergeable::Mergeable; + +static NOT_IMPLEMENTED_ERR_MSG: &[u8] = b"Not implemented"; + +#[derive( + ManagedVecItem, + TopEncode, + TopDecode, + NestedEncode, + NestedDecode, + TypeAbi, + Clone, + PartialEq, + Debug, +)] +pub struct FarmTokenConcentratedLiqAttributes { + pub reward_per_share: BigUint, + pub entering_epoch: Epoch, + pub compounded_reward: BigUint, + pub current_farm_amount: BigUint, + pub original_owner: ManagedAddress, + pub lp_token_nonce: Nonce, +} + +impl From> for FarmTokenConcentratedLiqAttributes { + fn from(_value: FarmTokenAttributes) -> Self { + M::error_api_impl().signal_error(NOT_IMPLEMENTED_ERR_MSG); + } +} + +impl Into> for FarmTokenConcentratedLiqAttributes { + fn into(self) -> FarmTokenAttributes { + M::error_api_impl().signal_error(NOT_IMPLEMENTED_ERR_MSG); + } +} + +impl FixedSupplyToken for FarmTokenConcentratedLiqAttributes { + fn get_total_supply(&self) -> BigUint { + self.current_farm_amount.clone() + } + + fn into_part(self, payment_amount: &BigUint) -> Self { + if payment_amount == &self.get_total_supply() { + return self; + } + + let new_compounded_reward = self.rule_of_three(payment_amount, &self.compounded_reward); + let new_current_farm_amount = payment_amount.clone(); + + FarmTokenConcentratedLiqAttributes { + reward_per_share: self.reward_per_share, + entering_epoch: self.entering_epoch, + compounded_reward: new_compounded_reward, + current_farm_amount: new_current_farm_amount, + original_owner: self.original_owner, + lp_token_nonce: self.lp_token_nonce, + } + } +} + +impl Mergeable for FarmTokenConcentratedLiqAttributes { + fn can_merge_with(&self, other: &Self) -> bool { + self.original_owner == other.original_owner && self.lp_token_nonce == other.lp_token_nonce + } + + fn merge_with(&mut self, other: Self) { + self.error_if_not_mergeable(&other); + + let first_supply = self.get_total_supply(); + let second_supply = other.get_total_supply(); + self.reward_per_share = weighted_average_round_up( + self.reward_per_share.clone(), + first_supply, + other.reward_per_share.clone(), + second_supply, + ); + + self.compounded_reward += other.compounded_reward; + self.current_farm_amount += other.current_farm_amount; + + self.entering_epoch = core::cmp::max(self.entering_epoch, other.entering_epoch); + } +} + +impl FarmToken for FarmTokenConcentratedLiqAttributes { + #[inline] + fn get_reward_per_share(&self) -> BigUint { + self.reward_per_share.clone() + } + + #[inline] + fn get_compounded_rewards(&self) -> BigUint { + self.compounded_reward.clone() + } + + #[inline] + fn get_initial_farming_tokens(&self) -> BigUint { + &self.current_farm_amount - &self.compounded_reward + } +} diff --git a/dex/farm-concentrated-liq/src/lib.rs b/dex/farm-concentrated-liq/src/lib.rs index 3feb19565..108a607c0 100644 --- a/dex/farm-concentrated-liq/src/lib.rs +++ b/dex/farm-concentrated-liq/src/lib.rs @@ -12,9 +12,9 @@ use base_functions::{ ClaimRewardsResultType, CompoundRewardsResultType, ExitFarmResultType, FarmConcentratedLiqWrapper, }; -use common_structs::FarmTokenAttributes; use contexts::storage_cache::StorageCache; +use custom_token_attributes::FarmTokenConcentratedLiqAttributes; use exit_penalty::{ DEFAULT_BURN_GAS_LIMIT, DEFAULT_MINUMUM_FARMING_EPOCHS, DEFAULT_PENALTY_PERCENT, }; @@ -266,7 +266,7 @@ pub trait Farm: &self, user: ManagedAddress, farm_token_amount: BigUint, - attributes: FarmTokenAttributes, + attributes: FarmTokenConcentratedLiqAttributes, ) -> BigUint { self.require_queried(); diff --git a/dex/farm-concentrated-liq/wasm/Cargo.lock b/dex/farm-concentrated-liq/wasm/Cargo.lock index 52aa29daa..a6e714fb5 100644 --- a/dex/farm-concentrated-liq/wasm/Cargo.lock +++ b/dex/farm-concentrated-liq/wasm/Cargo.lock @@ -147,6 +147,7 @@ dependencies = [ "farm_base_impl", "farm_token", "fixed-supply-token", + "math", "mergeable", "multiversx-sc", "multiversx-sc-modules", diff --git a/farm-staking/farm-staking/src/base_impl_wrapper.rs b/farm-staking/farm-staking/src/base_impl_wrapper.rs index a29a64549..74c257684 100644 --- a/farm-staking/farm-staking/src/base_impl_wrapper.rs +++ b/farm-staking/farm-staking/src/base_impl_wrapper.rs @@ -143,13 +143,13 @@ where fn create_enter_farm_initial_attributes( _sc: &Self::FarmSc, caller: ManagedAddress<::Api>, - farming_token_amount: BigUint<::Api>, + farming_token_payment: EsdtTokenPayment<::Api>, current_reward_per_share: BigUint<::Api>, ) -> Self::AttributesType { StakingFarmTokenAttributes { reward_per_share: current_reward_per_share, compounded_reward: BigUint::zero(), - current_farm_amount: farming_token_amount, + current_farm_amount: farming_token_payment.amount, original_owner: caller, } } From c1ed51083da7ee0d9ce8681f18e58d52a3906510 Mon Sep 17 00:00:00 2001 From: Dorin Marian Iancu Date: Fri, 23 Feb 2024 11:29:56 +0200 Subject: [PATCH 8/8] clippy --- dex/farm-concentrated-liq/src/custom_token_attributes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/dex/farm-concentrated-liq/src/custom_token_attributes.rs b/dex/farm-concentrated-liq/src/custom_token_attributes.rs index bff387252..354e8d405 100644 --- a/dex/farm-concentrated-liq/src/custom_token_attributes.rs +++ b/dex/farm-concentrated-liq/src/custom_token_attributes.rs @@ -34,6 +34,7 @@ impl From> for FarmTokenConcentratedLi } } +#[allow(clippy::from_over_into)] impl Into> for FarmTokenConcentratedLiqAttributes { fn into(self) -> FarmTokenAttributes { M::error_api_impl().signal_error(NOT_IMPLEMENTED_ERR_MSG);