diff --git a/dex/farm/src/exit_penalty.rs b/dex/farm/src/exit_penalty.rs index 388a8b1f2..e8905fade 100644 --- a/dex/farm/src/exit_penalty.rs +++ b/dex/farm/src/exit_penalty.rs @@ -2,6 +2,7 @@ multiversx_sc::imports!(); use common_errors::ERROR_PARAMETERS; use common_structs::Epoch; +use pair::pair_actions::remove_liq::ProxyTrait as _; pub const MAX_PERCENT: u64 = 10_000; pub const DEFAULT_PENALTY_PERCENT: u64 = 100; diff --git a/dex/fuzz/src/fuzz_pair.rs b/dex/fuzz/src/fuzz_pair.rs index 3ccc5031a..cd22614cb 100644 --- a/dex/fuzz/src/fuzz_pair.rs +++ b/dex/fuzz/src/fuzz_pair.rs @@ -12,7 +12,9 @@ pub mod fuzz_pair_test { use rand::prelude::*; use crate::fuzz_data::fuzz_data_tests::*; - use pair::*; + use pair::pair_actions::{ + add_liq::AddLiquidityModule, remove_liq::RemoveLiquidityModule, swap::SwapModule, + }; pub fn add_liquidity( fuzzer_data: &mut FuzzerData< diff --git a/dex/pair/src/lib.rs b/dex/pair/src/lib.rs index 9b800e67a..9da3d5067 100644 --- a/dex/pair/src/lib.rs +++ b/dex/pair/src/lib.rs @@ -11,30 +11,20 @@ mod events; pub mod fee; mod liquidity_pool; pub mod locking_wrapper; +pub mod pair_actions; pub mod safe_price; pub mod safe_price_view; -use crate::contexts::add_liquidity::AddLiquidityContext; -use crate::contexts::remove_liquidity::RemoveLiquidityContext; use crate::errors::*; -use common_errors::ERROR_PERMISSION_DENIED; use contexts::base::*; -use contexts::swap::SwapContext; +use pair_actions::common_result_types::{ + AddLiquidityResultType, RemoveLiquidityResultType, SwapTokensFixedInputResultType, + SwapTokensFixedOutputResultType, +}; use pausable::State; use permissions_module::Permissions; -pub type AddLiquidityResultType = - MultiValue3, EsdtTokenPayment, EsdtTokenPayment>; - -pub type RemoveLiquidityResultType = - MultiValue2, EsdtTokenPayment>; - -pub type SwapTokensFixedInputResultType = EsdtTokenPayment; - -pub type SwapTokensFixedOutputResultType = - MultiValue2, EsdtTokenPayment>; - #[multiversx_sc::contract] pub trait Pair: amm::AmmModule @@ -49,6 +39,12 @@ pub trait Pair: + locking_wrapper::LockingWrapperModule + permissions_module::PermissionsModule + pausable::PausableModule + + pair_actions::initial_liq::InitialLiquidityModule + + pair_actions::add_liq::AddLiquidityModule + + pair_actions::remove_liq::RemoveLiquidityModule + + pair_actions::swap::SwapModule + + pair_actions::views::ViewsModule + + pair_actions::common_methods::CommonMethodsModule { #[init] fn init( @@ -105,462 +101,6 @@ pub trait Pair: #[endpoint] fn upgrade(&self) {} - #[payable("*")] - #[endpoint(addInitialLiquidity)] - fn add_initial_liquidity(&self) -> AddLiquidityResultType { - let mut storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - - let opt_initial_liq_adder = self.initial_liquidity_adder().get(); - if let Some(initial_liq_adder) = opt_initial_liq_adder { - require!(caller == initial_liq_adder, ERROR_PERMISSION_DENIED); - } - - let [first_payment, second_payment] = self.call_value().multi_esdt(); - require!( - first_payment.token_identifier == storage_cache.first_token_id - && first_payment.amount > 0, - ERROR_BAD_PAYMENT_TOKENS - ); - require!( - second_payment.token_identifier == storage_cache.second_token_id - && second_payment.amount > 0, - ERROR_BAD_PAYMENT_TOKENS - ); - require!( - !self.is_state_active(storage_cache.contract_state), - ERROR_ACTIVE - ); - require!( - storage_cache.lp_token_supply == 0, - ERROR_INITIAL_LIQUIDITY_ALREADY_ADDED - ); - - let first_token_optimal_amount = &first_payment.amount; - let second_token_optimal_amount = &second_payment.amount; - let liq_added = self.pool_add_initial_liquidity( - first_token_optimal_amount, - second_token_optimal_amount, - &mut storage_cache, - ); - - self.send() - .esdt_local_mint(&storage_cache.lp_token_id, 0, &liq_added); - self.send() - .direct_esdt(&caller, &storage_cache.lp_token_id, 0, &liq_added); - - self.state().set(State::PartialActive); - - let add_liq_context = AddLiquidityContext { - first_payment: first_payment.clone(), - second_payment: second_payment.clone(), - first_token_amount_min: BigUint::from(1u32), - second_token_amount_min: BigUint::from(1u32), - first_token_optimal_amount: first_token_optimal_amount.clone(), - second_token_optimal_amount: second_token_optimal_amount.clone(), - liq_added, - }; - let output = self.build_add_initial_liq_results(&storage_cache, &add_liq_context); - - self.emit_add_liquidity_event(&storage_cache, add_liq_context); - - output - } - - #[payable("*")] - #[endpoint(addLiquidity)] - fn add_liquidity( - &self, - first_token_amount_min: BigUint, - second_token_amount_min: BigUint, - ) -> AddLiquidityResultType { - require!( - first_token_amount_min > 0 && second_token_amount_min > 0, - ERROR_INVALID_ARGS - ); - - let mut storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - - let [first_payment, second_payment] = self.call_value().multi_esdt(); - require!( - first_payment.token_identifier == storage_cache.first_token_id - && first_payment.amount > 0, - ERROR_BAD_PAYMENT_TOKENS - ); - require!( - second_payment.token_identifier == storage_cache.second_token_id - && second_payment.amount > 0, - ERROR_BAD_PAYMENT_TOKENS - ); - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - storage_cache.lp_token_id.is_valid_esdt_identifier(), - ERROR_LP_TOKEN_NOT_ISSUED - ); - require!( - self.initial_liquidity_adder().get().is_none() || storage_cache.lp_token_supply != 0, - ERROR_INITIAL_LIQUIDITY_NOT_ADDED - ); - - self.update_safe_price( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let initial_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let mut add_liq_context = AddLiquidityContext::new( - first_payment, - second_payment, - first_token_amount_min, - second_token_amount_min, - ); - self.set_optimal_amounts(&mut add_liq_context, &storage_cache); - - add_liq_context.liq_added = if storage_cache.lp_token_supply == 0u64 { - self.pool_add_initial_liquidity( - &add_liq_context.first_token_optimal_amount, - &add_liq_context.second_token_optimal_amount, - &mut storage_cache, - ) - } else { - self.pool_add_liquidity( - &add_liq_context.first_token_optimal_amount, - &add_liq_context.second_token_optimal_amount, - &mut storage_cache, - ) - }; - - let new_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - require!(initial_k <= new_k, ERROR_K_INVARIANT_FAILED); - - self.send() - .esdt_local_mint(&storage_cache.lp_token_id, 0, &add_liq_context.liq_added); - - let output_payments = self.build_add_liq_output_payments(&storage_cache, &add_liq_context); - self.send_multiple_tokens_if_not_zero(&caller, &output_payments); - - let output = self.build_add_liq_results(&storage_cache, &add_liq_context); - - self.emit_add_liquidity_event(&storage_cache, add_liq_context); - - output - } - - #[payable("*")] - #[endpoint(removeLiquidity)] - fn remove_liquidity( - &self, - first_token_amount_min: BigUint, - second_token_amount_min: BigUint, - ) -> RemoveLiquidityResultType { - require!( - first_token_amount_min > 0 && second_token_amount_min > 0, - ERROR_INVALID_ARGS - ); - - let mut storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - let payment = self.call_value().single_esdt(); - - require!( - self.is_state_active(storage_cache.contract_state), - ERROR_NOT_ACTIVE - ); - require!( - storage_cache.lp_token_id.is_valid_esdt_identifier(), - ERROR_LP_TOKEN_NOT_ISSUED - ); - require!( - payment.token_identifier == storage_cache.lp_token_id && payment.amount > 0, - ERROR_BAD_PAYMENT_TOKENS - ); - - self.update_safe_price( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let initial_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let mut remove_liq_context = RemoveLiquidityContext::new( - payment.amount, - first_token_amount_min, - second_token_amount_min, - ); - self.pool_remove_liquidity(&mut remove_liq_context, &mut storage_cache); - - let new_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - require!(new_k <= initial_k, ERROR_K_INVARIANT_FAILED); - - self.burn( - &storage_cache.lp_token_id, - &remove_liq_context.lp_token_payment_amount, - ); - - let output_payments = - self.build_remove_liq_output_payments(&storage_cache, &remove_liq_context); - self.send_multiple_tokens_if_not_zero(&caller, &output_payments); - - self.emit_remove_liquidity_event(&storage_cache, remove_liq_context); - - self.build_remove_liq_results(output_payments) - } - - #[payable("*")] - #[endpoint(removeLiquidityAndBuyBackAndBurnToken)] - fn remove_liquidity_and_burn_token(&self, token_to_buyback_and_burn: TokenIdentifier) { - let mut storage_cache = StorageCache::new(self); - let caller = self.blockchain().get_caller(); - let payment = self.call_value().single_esdt(); - - require!(self.whitelist().contains(&caller), ERROR_NOT_WHITELISTED); - require!( - storage_cache.lp_token_id.is_valid_esdt_identifier(), - ERROR_LP_TOKEN_NOT_ISSUED - ); - require!( - payment.token_identifier == storage_cache.lp_token_id && payment.amount > 0, - ERROR_BAD_PAYMENT_TOKENS - ); - - self.update_safe_price( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let mut remove_liq_context = - RemoveLiquidityContext::new(payment.amount, BigUint::from(1u64), BigUint::from(1u64)); - self.pool_remove_liquidity(&mut remove_liq_context, &mut storage_cache); - - self.burn( - &storage_cache.lp_token_id, - &remove_liq_context.lp_token_payment_amount, - ); - - let dest_address = ManagedAddress::zero(); - let first_token_id = storage_cache.first_token_id.clone(); - self.send_fee_slice( - &mut storage_cache, - SwapTokensOrder::PoolOrder, - &first_token_id, - &remove_liq_context.first_token_amount_removed, - &dest_address, - &token_to_buyback_and_burn, - ); - - let second_token_id = storage_cache.second_token_id.clone(); - self.send_fee_slice( - &mut storage_cache, - SwapTokensOrder::ReverseOrder, - &second_token_id, - &remove_liq_context.second_token_amount_removed, - &dest_address, - &token_to_buyback_and_burn, - ); - } - - #[payable("*")] - #[endpoint(swapNoFeeAndForward)] - fn swap_no_fee(&self, token_out: TokenIdentifier, destination_address: ManagedAddress) { - let caller = self.blockchain().get_caller(); - require!(self.whitelist().contains(&caller), ERROR_NOT_WHITELISTED); - - let mut storage_cache = StorageCache::new(self); - let (token_in, _, amount_in) = self.call_value().single_esdt().into_tuple(); - let swap_tokens_order = storage_cache.get_swap_tokens_order(&token_in, &token_out); - - require!( - self.can_swap(storage_cache.contract_state), - ERROR_SWAP_NOT_ENABLED - ); - - self.update_safe_price( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let initial_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let mut swap_context = SwapContext::new( - token_in, - amount_in.clone(), - token_out, - BigUint::from(1u32), - swap_tokens_order, - ); - swap_context.final_input_amount = amount_in; - - let amount_out = self.swap_safe_no_fee( - &mut storage_cache, - swap_context.swap_tokens_order, - &swap_context.final_input_amount, - ); - require!(amount_out > 0u64, ERROR_ZERO_AMOUNT); - - swap_context.final_output_amount = amount_out; - - let new_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - require!(initial_k <= new_k, ERROR_K_INVARIANT_FAILED); - - self.burn( - &swap_context.output_token_id, - &swap_context.final_output_amount, - ); - - self.emit_swap_no_fee_and_forward_event(swap_context, destination_address); - } - - #[payable("*")] - #[endpoint(swapTokensFixedInput)] - fn swap_tokens_fixed_input( - &self, - token_out: TokenIdentifier, - amount_out_min: BigUint, - ) -> SwapTokensFixedInputResultType { - require!(amount_out_min > 0, ERROR_INVALID_ARGS); - - let mut storage_cache = StorageCache::new(self); - let (token_in, _, amount_in) = self.call_value().single_esdt().into_tuple(); - let swap_tokens_order = storage_cache.get_swap_tokens_order(&token_in, &token_out); - - require!( - self.can_swap(storage_cache.contract_state), - ERROR_SWAP_NOT_ENABLED - ); - - let reserve_out = storage_cache.get_mut_reserve_out(swap_tokens_order); - require!(*reserve_out > amount_out_min, ERROR_NOT_ENOUGH_RESERVE); - - self.update_safe_price( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let initial_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let mut swap_context = SwapContext::new( - token_in, - amount_in, - token_out, - amount_out_min, - swap_tokens_order, - ); - self.perform_swap_fixed_input(&mut swap_context, &mut storage_cache); - - let new_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - require!(initial_k <= new_k, ERROR_K_INVARIANT_FAILED); - - if swap_context.fee_amount > 0 { - self.send_fee( - &mut storage_cache, - swap_context.swap_tokens_order, - &swap_context.input_token_id, - &swap_context.fee_amount, - ); - } - - let caller = self.blockchain().get_caller(); - let output_payments = self.build_swap_output_payments(&swap_context); - self.send_multiple_tokens_if_not_zero(&caller, &output_payments); - - self.emit_swap_event(&storage_cache, swap_context); - - self.build_swap_fixed_input_results(output_payments) - } - - #[payable("*")] - #[endpoint(swapTokensFixedOutput)] - fn swap_tokens_fixed_output( - &self, - token_out: TokenIdentifier, - amount_out: BigUint, - ) -> SwapTokensFixedOutputResultType { - require!(amount_out > 0, ERROR_INVALID_ARGS); - - let mut storage_cache = StorageCache::new(self); - let (token_in, _, amount_in_max) = self.call_value().single_esdt().into_tuple(); - let swap_tokens_order = storage_cache.get_swap_tokens_order(&token_in, &token_out); - - require!( - self.can_swap(storage_cache.contract_state), - ERROR_SWAP_NOT_ENABLED - ); - - let reserve_out = storage_cache.get_mut_reserve_out(swap_tokens_order); - require!(*reserve_out > amount_out, ERROR_NOT_ENOUGH_RESERVE); - - self.update_safe_price( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let initial_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - - let mut swap_context = SwapContext::new( - token_in, - amount_in_max, - token_out, - amount_out, - swap_tokens_order, - ); - self.perform_swap_fixed_output(&mut swap_context, &mut storage_cache); - - let new_k = self.calculate_k_constant( - &storage_cache.first_token_reserve, - &storage_cache.second_token_reserve, - ); - require!(initial_k <= new_k, ERROR_K_INVARIANT_FAILED); - - if swap_context.fee_amount > 0 { - self.send_fee( - &mut storage_cache, - swap_context.swap_tokens_order, - &swap_context.input_token_id, - &swap_context.fee_amount, - ); - } - - let caller = self.blockchain().get_caller(); - let output_payments = self.build_swap_output_payments(&swap_context); - self.send_multiple_tokens_if_not_zero(&caller, &output_payments); - - self.emit_swap_event(&storage_cache, swap_context); - - self.build_swap_fixed_output_results(output_payments) - } - #[endpoint(setLpTokenIdentifier)] fn set_lp_token_identifier(&self, token_identifier: TokenIdentifier) { self.require_caller_has_owner_permissions(); @@ -580,175 +120,4 @@ pub trait Pair: ); self.lp_token_identifier().set(&token_identifier); } - - #[view(getTokensForGivenPosition)] - fn get_tokens_for_given_position( - &self, - liquidity: BigUint, - ) -> MultiValue2, EsdtTokenPayment> { - self.get_both_tokens_for_given_position(liquidity) - } - - #[view(getReservesAndTotalSupply)] - fn get_reserves_and_total_supply(&self) -> MultiValue3 { - let first_token_id = self.first_token_id().get(); - let second_token_id = self.second_token_id().get(); - let first_token_reserve = self.pair_reserve(&first_token_id).get(); - let second_token_reserve = self.pair_reserve(&second_token_id).get(); - let total_supply = self.lp_token_supply().get(); - (first_token_reserve, second_token_reserve, total_supply).into() - } - - #[view(getAmountOut)] - fn get_amount_out_view(&self, token_in: TokenIdentifier, amount_in: BigUint) -> BigUint { - require!(amount_in > 0u64, ERROR_ZERO_AMOUNT); - - let first_token_id = self.first_token_id().get(); - let second_token_id = self.second_token_id().get(); - let first_token_reserve = self.pair_reserve(&first_token_id).get(); - let second_token_reserve = self.pair_reserve(&second_token_id).get(); - - if token_in == first_token_id { - require!(second_token_reserve > 0u64, ERROR_NOT_ENOUGH_RESERVE); - let amount_out = - self.get_amount_out(&amount_in, &first_token_reserve, &second_token_reserve); - require!(second_token_reserve > amount_out, ERROR_NOT_ENOUGH_RESERVE); - amount_out - } else if token_in == second_token_id { - require!(first_token_reserve > 0u64, ERROR_NOT_ENOUGH_RESERVE); - let amount_out = - self.get_amount_out(&amount_in, &second_token_reserve, &first_token_reserve); - require!(first_token_reserve > amount_out, ERROR_NOT_ENOUGH_RESERVE); - amount_out - } else { - sc_panic!(ERROR_UNKNOWN_TOKEN); - } - } - - #[view(getAmountIn)] - fn get_amount_in_view(&self, token_wanted: TokenIdentifier, amount_wanted: BigUint) -> BigUint { - require!(amount_wanted > 0u64, ERROR_ZERO_AMOUNT); - - let first_token_id = self.first_token_id().get(); - let second_token_id = self.second_token_id().get(); - let first_token_reserve = self.pair_reserve(&first_token_id).get(); - let second_token_reserve = self.pair_reserve(&second_token_id).get(); - - if token_wanted == first_token_id { - require!( - first_token_reserve > amount_wanted, - ERROR_NOT_ENOUGH_RESERVE - ); - - self.get_amount_in(&amount_wanted, &second_token_reserve, &first_token_reserve) - } else if token_wanted == second_token_id { - require!( - second_token_reserve > amount_wanted, - ERROR_NOT_ENOUGH_RESERVE - ); - - self.get_amount_in(&amount_wanted, &first_token_reserve, &second_token_reserve) - } else { - sc_panic!(ERROR_UNKNOWN_TOKEN); - } - } - - #[view(getEquivalent)] - fn get_equivalent(&self, token_in: TokenIdentifier, amount_in: BigUint) -> BigUint { - require!(amount_in > 0u64, ERROR_ZERO_AMOUNT); - let zero = BigUint::zero(); - - let first_token_id = self.first_token_id().get(); - let second_token_id = self.second_token_id().get(); - let first_token_reserve = self.pair_reserve(&first_token_id).get(); - let second_token_reserve = self.pair_reserve(&second_token_id).get(); - if first_token_reserve == 0u64 || second_token_reserve == 0u64 { - return zero; - } - - if token_in == first_token_id { - self.quote(&amount_in, &first_token_reserve, &second_token_reserve) - } else if token_in == second_token_id { - self.quote(&amount_in, &second_token_reserve, &first_token_reserve) - } else { - sc_panic!(ERROR_UNKNOWN_TOKEN); - } - } - - #[inline] - fn is_state_active(&self, state: State) -> bool { - state == State::Active || state == State::PartialActive - } - - #[inline] - fn can_swap(&self, state: State) -> bool { - state == State::Active - } - - fn perform_swap_fixed_input( - &self, - context: &mut SwapContext, - storage_cache: &mut StorageCache, - ) { - context.final_input_amount = context.input_token_amount.clone(); - - let reserve_in = storage_cache.get_reserve_in(context.swap_tokens_order); - let reserve_out = storage_cache.get_reserve_out(context.swap_tokens_order); - - let amount_out_optimal = - self.get_amount_out(&context.input_token_amount, reserve_in, reserve_out); - require!( - amount_out_optimal >= context.output_token_amount, - ERROR_SLIPPAGE_EXCEEDED - ); - require!(*reserve_out > amount_out_optimal, ERROR_NOT_ENOUGH_RESERVE); - require!(amount_out_optimal != 0u64, ERROR_ZERO_AMOUNT); - - context.final_output_amount = amount_out_optimal; - - let mut amount_in_after_fee = context.input_token_amount.clone(); - if self.is_fee_enabled() { - let fee_amount = self.get_special_fee_from_input(&amount_in_after_fee); - amount_in_after_fee -= &fee_amount; - - context.fee_amount = fee_amount; - } - - *storage_cache.get_mut_reserve_in(context.swap_tokens_order) += amount_in_after_fee; - *storage_cache.get_mut_reserve_out(context.swap_tokens_order) -= - &context.final_output_amount; - } - - fn perform_swap_fixed_output( - &self, - context: &mut SwapContext, - storage_cache: &mut StorageCache, - ) { - context.final_output_amount = context.output_token_amount.clone(); - - let reserve_in = storage_cache.get_reserve_in(context.swap_tokens_order); - let reserve_out = storage_cache.get_reserve_out(context.swap_tokens_order); - - let amount_in_optimal = - self.get_amount_in(&context.output_token_amount, reserve_in, reserve_out); - require!( - amount_in_optimal <= context.input_token_amount, - ERROR_SLIPPAGE_EXCEEDED - ); - require!(amount_in_optimal != 0, ERROR_ZERO_AMOUNT); - - context.final_input_amount = amount_in_optimal.clone(); - - let mut amount_in_optimal_after_fee = amount_in_optimal; - if self.is_fee_enabled() { - let fee_amount = self.get_special_fee_from_input(&amount_in_optimal_after_fee); - amount_in_optimal_after_fee -= &fee_amount; - - context.fee_amount = fee_amount; - } - - *storage_cache.get_mut_reserve_in(context.swap_tokens_order) += amount_in_optimal_after_fee; - *storage_cache.get_mut_reserve_out(context.swap_tokens_order) -= - &context.final_output_amount; - } } diff --git a/dex/pair/src/liquidity_pool.rs b/dex/pair/src/liquidity_pool.rs index 5e292e199..4797772e3 100644 --- a/dex/pair/src/liquidity_pool.rs +++ b/dex/pair/src/liquidity_pool.rs @@ -154,14 +154,14 @@ pub trait LiquidityPoolModule: context.first_token_optimal_amount = first_token_amount_optimal; context.second_token_optimal_amount = second_token_amount_desired.clone(); } - require!( - context.first_token_optimal_amount >= context.first_token_amount_min, - ERROR_INSUFFICIENT_FIRST_TOKEN - ); - require!( - context.second_token_optimal_amount >= context.second_token_amount_min, - ERROR_INSUFFICIENT_SECOND_TOKEN - ); + require!( + context.first_token_optimal_amount >= context.first_token_amount_min, + ERROR_INSUFFICIENT_FIRST_TOKEN + ); + require!( + context.second_token_optimal_amount >= context.second_token_amount_min, + ERROR_INSUFFICIENT_SECOND_TOKEN + ); } fn get_token_for_given_position( diff --git a/dex/pair/src/pair_actions/add_liq.rs b/dex/pair/src/pair_actions/add_liq.rs new file mode 100644 index 000000000..0878219a7 --- /dev/null +++ b/dex/pair/src/pair_actions/add_liq.rs @@ -0,0 +1,114 @@ +use crate::{ + contexts::add_liquidity::AddLiquidityContext, StorageCache, ERROR_BAD_PAYMENT_TOKENS, + ERROR_INITIAL_LIQUIDITY_NOT_ADDED, ERROR_INVALID_ARGS, ERROR_K_INVARIANT_FAILED, + ERROR_LP_TOKEN_NOT_ISSUED, ERROR_NOT_ACTIVE, +}; + +use super::common_result_types::AddLiquidityResultType; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait AddLiquidityModule: + crate::liquidity_pool::LiquidityPoolModule + + crate::amm::AmmModule + + crate::contexts::output_builder::OutputBuilderModule + + crate::locking_wrapper::LockingWrapperModule + + crate::events::EventsModule + + crate::safe_price::SafePriceModule + + crate::config::ConfigModule + + token_send::TokenSendModule + + permissions_module::PermissionsModule + + pausable::PausableModule + + super::common_methods::CommonMethodsModule +{ + #[payable("*")] + #[endpoint(addLiquidity)] + fn add_liquidity( + &self, + first_token_amount_min: BigUint, + second_token_amount_min: BigUint, + ) -> AddLiquidityResultType { + require!( + first_token_amount_min > 0 && second_token_amount_min > 0, + ERROR_INVALID_ARGS + ); + + let mut storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); + + let [first_payment, second_payment] = self.call_value().multi_esdt(); + require!( + first_payment.token_identifier == storage_cache.first_token_id + && first_payment.amount > 0, + ERROR_BAD_PAYMENT_TOKENS + ); + require!( + second_payment.token_identifier == storage_cache.second_token_id + && second_payment.amount > 0, + ERROR_BAD_PAYMENT_TOKENS + ); + require!( + self.is_state_active(storage_cache.contract_state), + ERROR_NOT_ACTIVE + ); + require!( + storage_cache.lp_token_id.is_valid_esdt_identifier(), + ERROR_LP_TOKEN_NOT_ISSUED + ); + require!( + self.initial_liquidity_adder().get().is_none() || storage_cache.lp_token_supply != 0, + ERROR_INITIAL_LIQUIDITY_NOT_ADDED + ); + + self.update_safe_price( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let initial_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let mut add_liq_context = AddLiquidityContext::new( + first_payment, + second_payment, + first_token_amount_min, + second_token_amount_min, + ); + self.set_optimal_amounts(&mut add_liq_context, &storage_cache); + + add_liq_context.liq_added = if storage_cache.lp_token_supply == 0u64 { + self.pool_add_initial_liquidity( + &add_liq_context.first_token_optimal_amount, + &add_liq_context.second_token_optimal_amount, + &mut storage_cache, + ) + } else { + self.pool_add_liquidity( + &add_liq_context.first_token_optimal_amount, + &add_liq_context.second_token_optimal_amount, + &mut storage_cache, + ) + }; + + let new_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + require!(initial_k <= new_k, ERROR_K_INVARIANT_FAILED); + + self.send() + .esdt_local_mint(&storage_cache.lp_token_id, 0, &add_liq_context.liq_added); + + let output_payments = self.build_add_liq_output_payments(&storage_cache, &add_liq_context); + self.send_multiple_tokens_if_not_zero(&caller, &output_payments); + + let output = self.build_add_liq_results(&storage_cache, &add_liq_context); + + self.emit_add_liquidity_event(&storage_cache, add_liq_context); + + output + } +} diff --git a/dex/pair/src/pair_actions/common_methods.rs b/dex/pair/src/pair_actions/common_methods.rs new file mode 100644 index 000000000..8839a85d5 --- /dev/null +++ b/dex/pair/src/pair_actions/common_methods.rs @@ -0,0 +1,16 @@ +use pausable::State; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait CommonMethodsModule { + #[inline] + fn is_state_active(&self, state: State) -> bool { + state == State::Active || state == State::PartialActive + } + + #[inline] + fn can_swap(&self, state: State) -> bool { + state == State::Active + } +} diff --git a/dex/pair/src/pair_actions/common_result_types.rs b/dex/pair/src/pair_actions/common_result_types.rs new file mode 100644 index 000000000..7fb6407b0 --- /dev/null +++ b/dex/pair/src/pair_actions/common_result_types.rs @@ -0,0 +1,10 @@ +multiversx_sc::imports!(); + +pub type AddLiquidityResultType = + MultiValue3, EsdtTokenPayment, EsdtTokenPayment>; + +pub type RemoveLiquidityResultType = MultiValue2, EsdtTokenPayment>; + +pub type SwapTokensFixedInputResultType = EsdtTokenPayment; + +pub type SwapTokensFixedOutputResultType = MultiValue2, EsdtTokenPayment>; diff --git a/dex/pair/src/pair_actions/initial_liq.rs b/dex/pair/src/pair_actions/initial_liq.rs new file mode 100644 index 000000000..e98cbb61e --- /dev/null +++ b/dex/pair/src/pair_actions/initial_liq.rs @@ -0,0 +1,87 @@ +use common_errors::ERROR_PERMISSION_DENIED; +use pausable::State; + +use crate::{ + contexts::add_liquidity::AddLiquidityContext, StorageCache, ERROR_ACTIVE, + ERROR_BAD_PAYMENT_TOKENS, ERROR_INITIAL_LIQUIDITY_ALREADY_ADDED, +}; + +use super::common_result_types::AddLiquidityResultType; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait InitialLiquidityModule: + crate::liquidity_pool::LiquidityPoolModule + + crate::amm::AmmModule + + crate::contexts::output_builder::OutputBuilderModule + + crate::locking_wrapper::LockingWrapperModule + + crate::events::EventsModule + + crate::config::ConfigModule + + token_send::TokenSendModule + + permissions_module::PermissionsModule + + pausable::PausableModule + + super::common_methods::CommonMethodsModule +{ + #[payable("*")] + #[endpoint(addInitialLiquidity)] + fn add_initial_liquidity(&self) -> AddLiquidityResultType { + let mut storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); + + let opt_initial_liq_adder = self.initial_liquidity_adder().get(); + if let Some(initial_liq_adder) = opt_initial_liq_adder { + require!(caller == initial_liq_adder, ERROR_PERMISSION_DENIED); + } + + let [first_payment, second_payment] = self.call_value().multi_esdt(); + require!( + first_payment.token_identifier == storage_cache.first_token_id + && first_payment.amount > 0, + ERROR_BAD_PAYMENT_TOKENS + ); + require!( + second_payment.token_identifier == storage_cache.second_token_id + && second_payment.amount > 0, + ERROR_BAD_PAYMENT_TOKENS + ); + require!( + !self.is_state_active(storage_cache.contract_state), + ERROR_ACTIVE + ); + require!( + storage_cache.lp_token_supply == 0, + ERROR_INITIAL_LIQUIDITY_ALREADY_ADDED + ); + + let first_token_optimal_amount = &first_payment.amount; + let second_token_optimal_amount = &second_payment.amount; + let liq_added = self.pool_add_initial_liquidity( + first_token_optimal_amount, + second_token_optimal_amount, + &mut storage_cache, + ); + + self.send() + .esdt_local_mint(&storage_cache.lp_token_id, 0, &liq_added); + self.send() + .direct_esdt(&caller, &storage_cache.lp_token_id, 0, &liq_added); + + self.state().set(State::PartialActive); + + let add_liq_context = AddLiquidityContext { + first_payment: first_payment.clone(), + second_payment: second_payment.clone(), + first_token_amount_min: BigUint::from(1u32), + second_token_amount_min: BigUint::from(1u32), + first_token_optimal_amount: first_token_optimal_amount.clone(), + second_token_optimal_amount: second_token_optimal_amount.clone(), + liq_added, + }; + let output = self.build_add_initial_liq_results(&storage_cache, &add_liq_context); + + self.emit_add_liquidity_event(&storage_cache, add_liq_context); + + output + } +} diff --git a/dex/pair/src/pair_actions/mod.rs b/dex/pair/src/pair_actions/mod.rs new file mode 100644 index 000000000..277650055 --- /dev/null +++ b/dex/pair/src/pair_actions/mod.rs @@ -0,0 +1,7 @@ +pub mod add_liq; +pub mod common_methods; +pub mod common_result_types; +pub mod initial_liq; +pub mod remove_liq; +pub mod swap; +pub mod views; diff --git a/dex/pair/src/pair_actions/remove_liq.rs b/dex/pair/src/pair_actions/remove_liq.rs new file mode 100644 index 000000000..898d99ccf --- /dev/null +++ b/dex/pair/src/pair_actions/remove_liq.rs @@ -0,0 +1,144 @@ +use crate::{ + contexts::remove_liquidity::RemoveLiquidityContext, StorageCache, SwapTokensOrder, + ERROR_BAD_PAYMENT_TOKENS, ERROR_INVALID_ARGS, ERROR_K_INVARIANT_FAILED, + ERROR_LP_TOKEN_NOT_ISSUED, ERROR_NOT_ACTIVE, ERROR_NOT_WHITELISTED, +}; + +use super::common_result_types::RemoveLiquidityResultType; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait RemoveLiquidityModule: + crate::liquidity_pool::LiquidityPoolModule + + crate::amm::AmmModule + + crate::contexts::output_builder::OutputBuilderModule + + crate::locking_wrapper::LockingWrapperModule + + crate::events::EventsModule + + crate::safe_price::SafePriceModule + + crate::fee::FeeModule + + crate::config::ConfigModule + + token_send::TokenSendModule + + permissions_module::PermissionsModule + + pausable::PausableModule + + super::common_methods::CommonMethodsModule +{ + #[payable("*")] + #[endpoint(removeLiquidity)] + fn remove_liquidity( + &self, + first_token_amount_min: BigUint, + second_token_amount_min: BigUint, + ) -> RemoveLiquidityResultType { + require!( + first_token_amount_min > 0 && second_token_amount_min > 0, + ERROR_INVALID_ARGS + ); + + let mut storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); + let payment = self.call_value().single_esdt(); + + require!( + self.is_state_active(storage_cache.contract_state), + ERROR_NOT_ACTIVE + ); + require!( + storage_cache.lp_token_id.is_valid_esdt_identifier(), + ERROR_LP_TOKEN_NOT_ISSUED + ); + require!( + payment.token_identifier == storage_cache.lp_token_id && payment.amount > 0, + ERROR_BAD_PAYMENT_TOKENS + ); + + self.update_safe_price( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let initial_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let mut remove_liq_context = RemoveLiquidityContext::new( + payment.amount, + first_token_amount_min, + second_token_amount_min, + ); + self.pool_remove_liquidity(&mut remove_liq_context, &mut storage_cache); + + let new_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + require!(new_k <= initial_k, ERROR_K_INVARIANT_FAILED); + + self.burn( + &storage_cache.lp_token_id, + &remove_liq_context.lp_token_payment_amount, + ); + + let output_payments = + self.build_remove_liq_output_payments(&storage_cache, &remove_liq_context); + self.send_multiple_tokens_if_not_zero(&caller, &output_payments); + + self.emit_remove_liquidity_event(&storage_cache, remove_liq_context); + + self.build_remove_liq_results(output_payments) + } + + #[payable("*")] + #[endpoint(removeLiquidityAndBuyBackAndBurnToken)] + fn remove_liquidity_and_burn_token(&self, token_to_buyback_and_burn: TokenIdentifier) { + let mut storage_cache = StorageCache::new(self); + let caller = self.blockchain().get_caller(); + let payment = self.call_value().single_esdt(); + + require!(self.whitelist().contains(&caller), ERROR_NOT_WHITELISTED); + require!( + storage_cache.lp_token_id.is_valid_esdt_identifier(), + ERROR_LP_TOKEN_NOT_ISSUED + ); + require!( + payment.token_identifier == storage_cache.lp_token_id && payment.amount > 0, + ERROR_BAD_PAYMENT_TOKENS + ); + + self.update_safe_price( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let mut remove_liq_context = + RemoveLiquidityContext::new(payment.amount, BigUint::from(1u64), BigUint::from(1u64)); + self.pool_remove_liquidity(&mut remove_liq_context, &mut storage_cache); + + self.burn( + &storage_cache.lp_token_id, + &remove_liq_context.lp_token_payment_amount, + ); + + let dest_address = ManagedAddress::zero(); + let first_token_id = storage_cache.first_token_id.clone(); + self.send_fee_slice( + &mut storage_cache, + SwapTokensOrder::PoolOrder, + &first_token_id, + &remove_liq_context.first_token_amount_removed, + &dest_address, + &token_to_buyback_and_burn, + ); + + let second_token_id = storage_cache.second_token_id.clone(); + self.send_fee_slice( + &mut storage_cache, + SwapTokensOrder::ReverseOrder, + &second_token_id, + &remove_liq_context.second_token_amount_removed, + &dest_address, + &token_to_buyback_and_burn, + ); + } +} diff --git a/dex/pair/src/pair_actions/swap.rs b/dex/pair/src/pair_actions/swap.rs new file mode 100644 index 000000000..6e028bad0 --- /dev/null +++ b/dex/pair/src/pair_actions/swap.rs @@ -0,0 +1,277 @@ +use crate::{ + contexts::swap::SwapContext, StorageCache, ERROR_INVALID_ARGS, ERROR_K_INVARIANT_FAILED, + ERROR_NOT_ENOUGH_RESERVE, ERROR_NOT_WHITELISTED, ERROR_SLIPPAGE_EXCEEDED, + ERROR_SWAP_NOT_ENABLED, ERROR_ZERO_AMOUNT, +}; + +use super::common_result_types::{SwapTokensFixedInputResultType, SwapTokensFixedOutputResultType}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait SwapModule: + crate::liquidity_pool::LiquidityPoolModule + + crate::amm::AmmModule + + crate::contexts::output_builder::OutputBuilderModule + + crate::locking_wrapper::LockingWrapperModule + + crate::events::EventsModule + + crate::safe_price::SafePriceModule + + crate::fee::FeeModule + + crate::config::ConfigModule + + token_send::TokenSendModule + + permissions_module::PermissionsModule + + pausable::PausableModule + + super::common_methods::CommonMethodsModule +{ + #[payable("*")] + #[endpoint(swapNoFeeAndForward)] + fn swap_no_fee(&self, token_out: TokenIdentifier, destination_address: ManagedAddress) { + let caller = self.blockchain().get_caller(); + require!(self.whitelist().contains(&caller), ERROR_NOT_WHITELISTED); + + let mut storage_cache = StorageCache::new(self); + let (token_in, _, amount_in) = self.call_value().single_esdt().into_tuple(); + let swap_tokens_order = storage_cache.get_swap_tokens_order(&token_in, &token_out); + + require!( + self.can_swap(storage_cache.contract_state), + ERROR_SWAP_NOT_ENABLED + ); + + self.update_safe_price( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let initial_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let mut swap_context = SwapContext::new( + token_in, + amount_in.clone(), + token_out, + BigUint::from(1u32), + swap_tokens_order, + ); + swap_context.final_input_amount = amount_in; + + let amount_out = self.swap_safe_no_fee( + &mut storage_cache, + swap_context.swap_tokens_order, + &swap_context.final_input_amount, + ); + require!(amount_out > 0u64, ERROR_ZERO_AMOUNT); + + swap_context.final_output_amount = amount_out; + + let new_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + require!(initial_k <= new_k, ERROR_K_INVARIANT_FAILED); + + self.burn( + &swap_context.output_token_id, + &swap_context.final_output_amount, + ); + + self.emit_swap_no_fee_and_forward_event(swap_context, destination_address); + } + + #[payable("*")] + #[endpoint(swapTokensFixedInput)] + fn swap_tokens_fixed_input( + &self, + token_out: TokenIdentifier, + amount_out_min: BigUint, + ) -> SwapTokensFixedInputResultType { + require!(amount_out_min > 0, ERROR_INVALID_ARGS); + + let mut storage_cache = StorageCache::new(self); + let (token_in, _, amount_in) = self.call_value().single_esdt().into_tuple(); + let swap_tokens_order = storage_cache.get_swap_tokens_order(&token_in, &token_out); + + require!( + self.can_swap(storage_cache.contract_state), + ERROR_SWAP_NOT_ENABLED + ); + + let reserve_out = storage_cache.get_mut_reserve_out(swap_tokens_order); + require!(*reserve_out > amount_out_min, ERROR_NOT_ENOUGH_RESERVE); + + self.update_safe_price( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let initial_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let mut swap_context = SwapContext::new( + token_in, + amount_in, + token_out, + amount_out_min, + swap_tokens_order, + ); + self.perform_swap_fixed_input(&mut swap_context, &mut storage_cache); + + let new_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + require!(initial_k <= new_k, ERROR_K_INVARIANT_FAILED); + + if swap_context.fee_amount > 0 { + self.send_fee( + &mut storage_cache, + swap_context.swap_tokens_order, + &swap_context.input_token_id, + &swap_context.fee_amount, + ); + } + + let caller = self.blockchain().get_caller(); + let output_payments = self.build_swap_output_payments(&swap_context); + self.send_multiple_tokens_if_not_zero(&caller, &output_payments); + + self.emit_swap_event(&storage_cache, swap_context); + + self.build_swap_fixed_input_results(output_payments) + } + + #[payable("*")] + #[endpoint(swapTokensFixedOutput)] + fn swap_tokens_fixed_output( + &self, + token_out: TokenIdentifier, + amount_out: BigUint, + ) -> SwapTokensFixedOutputResultType { + require!(amount_out > 0, ERROR_INVALID_ARGS); + + let mut storage_cache = StorageCache::new(self); + let (token_in, _, amount_in_max) = self.call_value().single_esdt().into_tuple(); + let swap_tokens_order = storage_cache.get_swap_tokens_order(&token_in, &token_out); + + require!( + self.can_swap(storage_cache.contract_state), + ERROR_SWAP_NOT_ENABLED + ); + + let reserve_out = storage_cache.get_mut_reserve_out(swap_tokens_order); + require!(*reserve_out > amount_out, ERROR_NOT_ENOUGH_RESERVE); + + self.update_safe_price( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let initial_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + + let mut swap_context = SwapContext::new( + token_in, + amount_in_max, + token_out, + amount_out, + swap_tokens_order, + ); + self.perform_swap_fixed_output(&mut swap_context, &mut storage_cache); + + let new_k = self.calculate_k_constant( + &storage_cache.first_token_reserve, + &storage_cache.second_token_reserve, + ); + require!(initial_k <= new_k, ERROR_K_INVARIANT_FAILED); + + if swap_context.fee_amount > 0 { + self.send_fee( + &mut storage_cache, + swap_context.swap_tokens_order, + &swap_context.input_token_id, + &swap_context.fee_amount, + ); + } + + let caller = self.blockchain().get_caller(); + let output_payments = self.build_swap_output_payments(&swap_context); + self.send_multiple_tokens_if_not_zero(&caller, &output_payments); + + self.emit_swap_event(&storage_cache, swap_context); + + self.build_swap_fixed_output_results(output_payments) + } + + fn perform_swap_fixed_input( + &self, + context: &mut SwapContext, + storage_cache: &mut StorageCache, + ) { + context.final_input_amount = context.input_token_amount.clone(); + + let reserve_in = storage_cache.get_reserve_in(context.swap_tokens_order); + let reserve_out = storage_cache.get_reserve_out(context.swap_tokens_order); + + let amount_out_optimal = + self.get_amount_out(&context.input_token_amount, reserve_in, reserve_out); + require!( + amount_out_optimal >= context.output_token_amount, + ERROR_SLIPPAGE_EXCEEDED + ); + require!(*reserve_out > amount_out_optimal, ERROR_NOT_ENOUGH_RESERVE); + require!(amount_out_optimal != 0u64, ERROR_ZERO_AMOUNT); + + context.final_output_amount = amount_out_optimal; + + let mut amount_in_after_fee = context.input_token_amount.clone(); + if self.is_fee_enabled() { + let fee_amount = self.get_special_fee_from_input(&amount_in_after_fee); + amount_in_after_fee -= &fee_amount; + + context.fee_amount = fee_amount; + } + + *storage_cache.get_mut_reserve_in(context.swap_tokens_order) += amount_in_after_fee; + *storage_cache.get_mut_reserve_out(context.swap_tokens_order) -= + &context.final_output_amount; + } + + fn perform_swap_fixed_output( + &self, + context: &mut SwapContext, + storage_cache: &mut StorageCache, + ) { + context.final_output_amount = context.output_token_amount.clone(); + + let reserve_in = storage_cache.get_reserve_in(context.swap_tokens_order); + let reserve_out = storage_cache.get_reserve_out(context.swap_tokens_order); + + let amount_in_optimal = + self.get_amount_in(&context.output_token_amount, reserve_in, reserve_out); + require!( + amount_in_optimal <= context.input_token_amount, + ERROR_SLIPPAGE_EXCEEDED + ); + require!(amount_in_optimal != 0, ERROR_ZERO_AMOUNT); + + context.final_input_amount = amount_in_optimal.clone(); + + let mut amount_in_optimal_after_fee = amount_in_optimal; + if self.is_fee_enabled() { + let fee_amount = self.get_special_fee_from_input(&amount_in_optimal_after_fee); + amount_in_optimal_after_fee -= &fee_amount; + + context.fee_amount = fee_amount; + } + + *storage_cache.get_mut_reserve_in(context.swap_tokens_order) += amount_in_optimal_after_fee; + *storage_cache.get_mut_reserve_out(context.swap_tokens_order) -= + &context.final_output_amount; + } +} diff --git a/dex/pair/src/pair_actions/views.rs b/dex/pair/src/pair_actions/views.rs new file mode 100644 index 000000000..878b3bd5e --- /dev/null +++ b/dex/pair/src/pair_actions/views.rs @@ -0,0 +1,113 @@ +use crate::{ERROR_NOT_ENOUGH_RESERVE, ERROR_UNKNOWN_TOKEN, ERROR_ZERO_AMOUNT}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait ViewsModule: + crate::liquidity_pool::LiquidityPoolModule + + crate::amm::AmmModule + + crate::contexts::output_builder::OutputBuilderModule + + crate::locking_wrapper::LockingWrapperModule + + crate::events::EventsModule + + crate::safe_price::SafePriceModule + + crate::fee::FeeModule + + crate::config::ConfigModule + + token_send::TokenSendModule + + permissions_module::PermissionsModule + + pausable::PausableModule + + super::common_methods::CommonMethodsModule +{ + #[view(getTokensForGivenPosition)] + fn get_tokens_for_given_position( + &self, + liquidity: BigUint, + ) -> MultiValue2, EsdtTokenPayment> { + self.get_both_tokens_for_given_position(liquidity) + } + + #[view(getReservesAndTotalSupply)] + fn get_reserves_and_total_supply(&self) -> MultiValue3 { + let first_token_id = self.first_token_id().get(); + let second_token_id = self.second_token_id().get(); + let first_token_reserve = self.pair_reserve(&first_token_id).get(); + let second_token_reserve = self.pair_reserve(&second_token_id).get(); + let total_supply = self.lp_token_supply().get(); + (first_token_reserve, second_token_reserve, total_supply).into() + } + + #[view(getAmountOut)] + fn get_amount_out_view(&self, token_in: TokenIdentifier, amount_in: BigUint) -> BigUint { + require!(amount_in > 0u64, ERROR_ZERO_AMOUNT); + + let first_token_id = self.first_token_id().get(); + let second_token_id = self.second_token_id().get(); + let first_token_reserve = self.pair_reserve(&first_token_id).get(); + let second_token_reserve = self.pair_reserve(&second_token_id).get(); + + if token_in == first_token_id { + require!(second_token_reserve > 0u64, ERROR_NOT_ENOUGH_RESERVE); + let amount_out = + self.get_amount_out(&amount_in, &first_token_reserve, &second_token_reserve); + require!(second_token_reserve > amount_out, ERROR_NOT_ENOUGH_RESERVE); + amount_out + } else if token_in == second_token_id { + require!(first_token_reserve > 0u64, ERROR_NOT_ENOUGH_RESERVE); + let amount_out = + self.get_amount_out(&amount_in, &second_token_reserve, &first_token_reserve); + require!(first_token_reserve > amount_out, ERROR_NOT_ENOUGH_RESERVE); + amount_out + } else { + sc_panic!(ERROR_UNKNOWN_TOKEN); + } + } + + #[view(getAmountIn)] + fn get_amount_in_view(&self, token_wanted: TokenIdentifier, amount_wanted: BigUint) -> BigUint { + require!(amount_wanted > 0u64, ERROR_ZERO_AMOUNT); + + let first_token_id = self.first_token_id().get(); + let second_token_id = self.second_token_id().get(); + let first_token_reserve = self.pair_reserve(&first_token_id).get(); + let second_token_reserve = self.pair_reserve(&second_token_id).get(); + + if token_wanted == first_token_id { + require!( + first_token_reserve > amount_wanted, + ERROR_NOT_ENOUGH_RESERVE + ); + + self.get_amount_in(&amount_wanted, &second_token_reserve, &first_token_reserve) + } else if token_wanted == second_token_id { + require!( + second_token_reserve > amount_wanted, + ERROR_NOT_ENOUGH_RESERVE + ); + + self.get_amount_in(&amount_wanted, &first_token_reserve, &second_token_reserve) + } else { + sc_panic!(ERROR_UNKNOWN_TOKEN); + } + } + + #[view(getEquivalent)] + fn get_equivalent(&self, token_in: TokenIdentifier, amount_in: BigUint) -> BigUint { + require!(amount_in > 0u64, ERROR_ZERO_AMOUNT); + let zero = BigUint::zero(); + + let first_token_id = self.first_token_id().get(); + let second_token_id = self.second_token_id().get(); + let first_token_reserve = self.pair_reserve(&first_token_id).get(); + let second_token_reserve = self.pair_reserve(&second_token_id).get(); + if first_token_reserve == 0u64 || second_token_reserve == 0u64 { + return zero; + } + + if token_in == first_token_id { + self.quote(&amount_in, &first_token_reserve, &second_token_reserve) + } else if token_in == second_token_id { + self.quote(&amount_in, &second_token_reserve, &first_token_reserve) + } else { + sc_panic!(ERROR_UNKNOWN_TOKEN); + } + } +} diff --git a/dex/pair/tests/pair_rs_test.rs b/dex/pair/tests/pair_rs_test.rs index eb90a88f6..5ff6fbce9 100644 --- a/dex/pair/tests/pair_rs_test.rs +++ b/dex/pair/tests/pair_rs_test.rs @@ -13,8 +13,10 @@ use multiversx_sc_scenario::{ managed_address, managed_biguint, managed_token_id, managed_token_id_wrapped, rust_biguint, whitebox_legacy::TxTokenTransfer, DebugApi, }; -// use pair::safe_price::MAX_OBSERVATIONS; -use pair::{config::MAX_PERCENTAGE, fee::FeeModule, locking_wrapper::LockingWrapperModule, Pair}; +use pair::{ + config::MAX_PERCENTAGE, fee::FeeModule, locking_wrapper::LockingWrapperModule, + pair_actions::swap::SwapModule, +}; use pair_setup::*; use simple_lock::{ locked_token::{LockedTokenAttributes, LockedTokenModule}, diff --git a/dex/pair/tests/pair_setup/mod.rs b/dex/pair/tests/pair_setup/mod.rs index e7311eca5..5ca0fcd84 100644 --- a/dex/pair/tests/pair_setup/mod.rs +++ b/dex/pair/tests/pair_setup/mod.rs @@ -20,6 +20,8 @@ pub const USER_TOTAL_MEX_TOKENS: u64 = 5_000_000_000; 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_view::*; use pair::*; use pausable::{PausableModule, State}; diff --git a/dex/pair/wasm-pair-full/src/lib.rs b/dex/pair/wasm-pair-full/src/lib.rs index 17b626ecc..149c4a91b 100644 --- a/dex/pair/wasm-pair-full/src/lib.rs +++ b/dex/pair/wasm-pair-full/src/lib.rs @@ -21,19 +21,7 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade - addInitialLiquidity => add_initial_liquidity - addLiquidity => add_liquidity - removeLiquidity => remove_liquidity - removeLiquidityAndBuyBackAndBurnToken => remove_liquidity_and_burn_token - swapNoFeeAndForward => swap_no_fee - swapTokensFixedInput => swap_tokens_fixed_input - swapTokensFixedOutput => swap_tokens_fixed_output setLpTokenIdentifier => set_lp_token_identifier - getTokensForGivenPosition => get_tokens_for_given_position - getReservesAndTotalSupply => get_reserves_and_total_supply - getAmountOut => get_amount_out_view - getAmountIn => get_amount_in_view - getEquivalent => get_equivalent getFeeState => is_fee_enabled whitelist => whitelist_endpoint removeWhitelist => remove_whitelist @@ -75,6 +63,18 @@ multiversx_sc_wasm_adapter::endpoints! { pause => pause resume => resume getState => state + addInitialLiquidity => add_initial_liquidity + addLiquidity => add_liquidity + removeLiquidity => remove_liquidity + removeLiquidityAndBuyBackAndBurnToken => remove_liquidity_and_burn_token + swapNoFeeAndForward => swap_no_fee + swapTokensFixedInput => swap_tokens_fixed_input + swapTokensFixedOutput => swap_tokens_fixed_output + getTokensForGivenPosition => get_tokens_for_given_position + getReservesAndTotalSupply => get_reserves_and_total_supply + getAmountOut => get_amount_out_view + getAmountIn => get_amount_in_view + getEquivalent => get_equivalent getLpTokensSafePriceByDefaultOffset => get_lp_tokens_safe_price_by_default_offset getLpTokensSafePriceByRoundOffset => get_lp_tokens_safe_price_by_round_offset getLpTokensSafePriceByTimestampOffset => get_lp_tokens_safe_price_by_timestamp_offset diff --git a/dex/pair/wasm/src/lib.rs b/dex/pair/wasm/src/lib.rs index 57339355b..82a357b8a 100644 --- a/dex/pair/wasm/src/lib.rs +++ b/dex/pair/wasm/src/lib.rs @@ -21,19 +21,7 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade - addInitialLiquidity => add_initial_liquidity - addLiquidity => add_liquidity - removeLiquidity => remove_liquidity - removeLiquidityAndBuyBackAndBurnToken => remove_liquidity_and_burn_token - swapNoFeeAndForward => swap_no_fee - swapTokensFixedInput => swap_tokens_fixed_input - swapTokensFixedOutput => swap_tokens_fixed_output setLpTokenIdentifier => set_lp_token_identifier - getTokensForGivenPosition => get_tokens_for_given_position - getReservesAndTotalSupply => get_reserves_and_total_supply - getAmountOut => get_amount_out_view - getAmountIn => get_amount_in_view - getEquivalent => get_equivalent getFeeState => is_fee_enabled whitelist => whitelist_endpoint removeWhitelist => remove_whitelist @@ -75,6 +63,18 @@ multiversx_sc_wasm_adapter::endpoints! { pause => pause resume => resume getState => state + addInitialLiquidity => add_initial_liquidity + addLiquidity => add_liquidity + removeLiquidity => remove_liquidity + removeLiquidityAndBuyBackAndBurnToken => remove_liquidity_and_burn_token + swapNoFeeAndForward => swap_no_fee + swapTokensFixedInput => swap_tokens_fixed_input + swapTokensFixedOutput => swap_tokens_fixed_output + getTokensForGivenPosition => get_tokens_for_given_position + getReservesAndTotalSupply => get_reserves_and_total_supply + getAmountOut => get_amount_out_view + getAmountIn => get_amount_in_view + getEquivalent => get_equivalent ) } diff --git a/dex/router/src/enable_swap_by_user.rs b/dex/router/src/enable_swap_by_user.rs index eec3f4d02..f1857403e 100644 --- a/dex/router/src/enable_swap_by_user.rs +++ b/dex/router/src/enable_swap_by_user.rs @@ -1,7 +1,7 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -use pair::config::ProxyTrait as _; +use pair::{config::ProxyTrait as _, pair_actions::views::ProxyTrait as _}; use pausable::{ProxyTrait as _, State}; use simple_lock::locked_token::LockedTokenAttributes; diff --git a/dex/router/src/multi_pair_swap.rs b/dex/router/src/multi_pair_swap.rs index e537beed9..3b0f73620 100644 --- a/dex/router/src/multi_pair_swap.rs +++ b/dex/router/src/multi_pair_swap.rs @@ -1,9 +1,9 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -use super::factory; +use pair::pair_actions::swap::ProxyTrait as _; -use pair::ProxyTrait as _; +use super::factory; type SwapOperationType = MultiValue4, ManagedBuffer, TokenIdentifier, BigUint>; diff --git a/dex/router/tests/router_setup/mod.rs b/dex/router/tests/router_setup/mod.rs index 54f6330a3..7b2969361 100644 --- a/dex/router/tests/router_setup/mod.rs +++ b/dex/router/tests/router_setup/mod.rs @@ -28,6 +28,7 @@ pub const USER_CUSTOM_TOKEN_BALANCE: u64 = 1_000_000_000; pub const USER_USDC_BALANCE: u64 = 1_000_000; use pair::config::*; +use pair::pair_actions::add_liq::AddLiquidityModule; use pair::*; use pausable::{PausableModule, State}; use router::factory::*; diff --git a/dex/router/tests/router_test.rs b/dex/router/tests/router_test.rs index b4b7ad010..cb5f1a78f 100644 --- a/dex/router/tests/router_test.rs +++ b/dex/router/tests/router_test.rs @@ -9,7 +9,7 @@ use multiversx_sc::{ MultiValueEncoded, }, }; -use pair::{config::ConfigModule, Pair}; +use pair::{config::ConfigModule, pair_actions::initial_liq::InitialLiquidityModule, Pair}; use pausable::{PausableModule, State}; use router::{ enable_swap_by_user::EnableSwapByUserModule, 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 163ebb922..f8048d66b 100644 --- a/farm-staking/farm-staking-proxy/src/external_contracts_interactions.rs +++ b/farm-staking/farm-staking-proxy/src/external_contracts_interactions.rs @@ -9,10 +9,12 @@ use farm_staking::{ unstake_farm::ProxyTrait as _, }; use farm_with_locked_rewards::ProxyTrait as _; -use pair::safe_price_view::ProxyTrait as _; +use pair::{ + pair_actions::{common_result_types::RemoveLiquidityResultType, remove_liq::ProxyTrait as _}, + safe_price_view::ProxyTrait as _, +}; use crate::result_types::*; -use pair::RemoveLiquidityResultType; pub type SafePriceResult = MultiValue2, EsdtTokenPayment>; diff --git a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_external_contracts/mod.rs b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_external_contracts/mod.rs index 95c734663..327897bfb 100644 --- a/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_external_contracts/mod.rs +++ b/farm-staking/farm-staking-proxy/tests/staking_farm_with_lp_external_contracts/mod.rs @@ -12,6 +12,8 @@ use multiversx_sc_scenario::whitebox_legacy::TxTokenTransfer; use multiversx_sc_scenario::{ managed_address, managed_biguint, managed_token_id, rust_biguint, whitebox_legacy::*, DebugApi, }; +use pair::pair_actions::add_liq::AddLiquidityModule; +use pair::pair_actions::remove_liq::RemoveLiquidityModule; use simple_lock::locked_token::LockedTokenModule; use farm::exit_penalty::ExitPenaltyModule; diff --git a/locked-asset/proxy_dex/src/pair_interactions.rs b/locked-asset/proxy_dex/src/pair_interactions.rs index 40b6b46a9..7e959a4e1 100644 --- a/locked-asset/proxy_dex/src/pair_interactions.rs +++ b/locked-asset/proxy_dex/src/pair_interactions.rs @@ -1,6 +1,10 @@ multiversx_sc::imports!(); -use pair::{AddLiquidityResultType, ProxyTrait as _, RemoveLiquidityResultType}; +use pair::pair_actions::{ + add_liq::ProxyTrait as _, + common_result_types::{AddLiquidityResultType, RemoveLiquidityResultType}, + remove_liq::ProxyTrait as _, +}; pub struct AddLiquidityResultWrapper { pub lp_tokens_received: EsdtTokenPayment,