diff --git a/Cargo.lock b/Cargo.lock index 72759ef35..be52ed651 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1721,6 +1721,7 @@ dependencies = [ name = "router" version = "0.0.0" dependencies = [ + "common_structs", "locking_module", "multiversx-sc", "multiversx-sc-scenario", diff --git a/common/modules/farm/farm_base_impl/src/base_farm_init.rs b/common/modules/farm/farm_base_impl/src/base_farm_init.rs index 700057a3e..2e53f39c5 100644 --- a/common/modules/farm/farm_base_impl/src/base_farm_init.rs +++ b/common/modules/farm/farm_base_impl/src/base_farm_init.rs @@ -38,16 +38,16 @@ pub trait BaseFarmInitModule: self.farming_token_id().set_if_empty(&farming_token_id); if !owner.is_zero() { - self.add_permissions(owner, Permissions::OWNER | Permissions::PAUSE); + self.add_permissions(&owner, Permissions::OWNER | Permissions::PAUSE); } let caller = self.blockchain().get_caller(); if admins.is_empty() { // backwards compatibility let all_permissions = Permissions::OWNER | Permissions::ADMIN | Permissions::PAUSE; - self.add_permissions(caller, all_permissions); + self.add_permissions(&caller, all_permissions); } else { - self.add_permissions(caller, Permissions::OWNER | Permissions::PAUSE); + self.add_permissions(&caller, Permissions::OWNER | Permissions::PAUSE); self.add_permissions_for_all(admins, Permissions::ADMIN); }; } diff --git a/common/modules/pausable/src/pausable.rs b/common/modules/pausable/src/pausable.rs index 6f8813685..5e4ad8ed5 100644 --- a/common/modules/pausable/src/pausable.rs +++ b/common/modules/pausable/src/pausable.rs @@ -26,7 +26,7 @@ pub trait PausableModule: permissions_module::PermissionsModule { self.require_caller_has_owner_permissions(); for address in address_list { - self.remove_permissions(address, Permissions::PAUSE); + self.remove_permissions(&address, Permissions::PAUSE); } } diff --git a/common/modules/permissions_module/src/permissions_module.rs b/common/modules/permissions_module/src/permissions_module.rs index 38eab7fdd..d9980c6d6 100644 --- a/common/modules/permissions_module/src/permissions_module.rs +++ b/common/modules/permissions_module/src/permissions_module.rs @@ -13,36 +13,36 @@ pub trait PermissionsModule { #[endpoint(addAdmin)] fn add_admin_endpoint(&self, address: ManagedAddress) { self.require_caller_has_owner_permissions(); - self.add_permissions(address, Permissions::ADMIN); + self.add_permissions(&address, Permissions::ADMIN); } #[endpoint(removeAdmin)] fn remove_admin_endpoint(&self, address: ManagedAddress) { self.require_caller_has_owner_permissions(); - self.remove_permissions(address, Permissions::ADMIN); + self.remove_permissions(&address, Permissions::ADMIN); } #[only_owner] #[endpoint(updateOwnerOrAdmin)] fn update_owner_or_admin_endpoint(&self, previous_owner: ManagedAddress) { let caller = self.blockchain().get_caller(); - let previous_owner_permissions = self.permissions(previous_owner.clone()).get(); + let previous_owner_permissions = self.permissions(&previous_owner).get(); - self.permissions(previous_owner).clear(); - self.permissions(caller).set(previous_owner_permissions); + self.permissions(&previous_owner).clear(); + self.permissions(&caller).set(previous_owner_permissions); } - fn set_permissions(&self, address: ManagedAddress, permissions: Permissions) { + fn set_permissions(&self, address: &ManagedAddress, permissions: Permissions) { self.permissions(address).set(permissions); } - fn add_permissions(&self, address: ManagedAddress, new_permissions: Permissions) { + fn add_permissions(&self, address: &ManagedAddress, new_permissions: Permissions) { self.permissions(address).update(|permissions| { permissions.insert(new_permissions); }); } - fn remove_permissions(&self, address: ManagedAddress, permissions_to_remove: Permissions) { + fn remove_permissions(&self, address: &ManagedAddress, permissions_to_remove: Permissions) { self.permissions(address).update(|permissions| { permissions.remove(permissions_to_remove); }); @@ -54,13 +54,13 @@ pub trait PermissionsModule { permissions: Permissions, ) { for address in addresses { - self.add_permissions(address, permissions.clone()); + self.add_permissions(&address, permissions.clone()); } } fn require_caller_any_of(&self, permissions: Permissions) { let caller = self.blockchain().get_caller(); - let caller_permissions = self.permissions(caller).get(); + let caller_permissions = self.permissions(&caller).get(); require!( caller_permissions.intersects(permissions), ERROR_PERMISSION_DENIED @@ -85,5 +85,5 @@ pub trait PermissionsModule { #[view(getPermissions)] #[storage_mapper("permissions")] - fn permissions(&self, address: ManagedAddress) -> SingleValueMapper; + fn permissions(&self, address: &ManagedAddress) -> SingleValueMapper; } diff --git a/dex/farm/tests/farm_multi_user_test.rs b/dex/farm/tests/farm_multi_user_test.rs index d37bee046..72db6521c 100644 --- a/dex/farm/tests/farm_multi_user_test.rs +++ b/dex/farm/tests/farm_multi_user_test.rs @@ -254,7 +254,7 @@ fn farm_change_boosted_yields_factors_test() { BoostedYieldsConfig::new(current_week - 1, default_factors.clone()); assert_eq!(expected_config, sc.boosted_yields_config().get()); - sc.add_permissions(managed_address!(&farm_addr), Permissions::all()); + sc.add_permissions(&managed_address!(&farm_addr), Permissions::all()); sc.set_boosted_yields_factors( managed_biguint!(1u64), managed_biguint!(1u64), diff --git a/dex/pair/src/fee/endpoints.rs b/dex/pair/src/fee/endpoints.rs new file mode 100644 index 000000000..ead9a99dc --- /dev/null +++ b/dex/pair/src/fee/endpoints.rs @@ -0,0 +1,138 @@ +use common_structs::{Percent, TokenPair}; + +use crate::{ + config::MAX_PERCENTAGE, ERROR_ALREADY_FEE_DEST, ERROR_ALREADY_WHITELISTED, + ERROR_BAD_TOKEN_FEE_DEST, ERROR_NOT_FEE_DEST, ERROR_NOT_WHITELISTED, + ERROR_PAIR_ALREADY_TRUSTED, ERROR_PAIR_NOT_TRUSTED, ERROR_SAME_TOKENS, +}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait EndpointsModule: + crate::config::ConfigModule + + crate::liquidity_pool::LiquidityPoolModule + + crate::amm::AmmModule + + token_send::TokenSendModule + + permissions_module::PermissionsModule + + pausable::PausableModule + + super::storage::StorageModule +{ + #[endpoint(whitelist)] + fn whitelist_endpoint(&self, address: ManagedAddress) { + self.require_caller_has_owner_permissions(); + + let is_new = self.whitelist().insert(address); + require!(is_new, ERROR_ALREADY_WHITELISTED); + } + + #[endpoint(removeWhitelist)] + fn remove_whitelist(&self, address: ManagedAddress) { + self.require_caller_has_owner_permissions(); + + let is_removed = self.whitelist().remove(&address); + require!(is_removed, ERROR_NOT_WHITELISTED); + } + + #[endpoint(addTrustedSwapPair)] + fn add_trusted_swap_pair( + &self, + pair_address: ManagedAddress, + first_token: TokenIdentifier, + second_token: TokenIdentifier, + ) { + self.require_caller_has_owner_permissions(); + require!(first_token != second_token, ERROR_SAME_TOKENS); + + let token_pair = TokenPair { + first_token, + second_token, + }; + let is_new = self + .trusted_swap_pair() + .insert(token_pair, pair_address) + .is_none(); + require!(is_new, ERROR_PAIR_ALREADY_TRUSTED); + } + + #[endpoint(removeTrustedSwapPair)] + fn remove_trusted_swap_pair( + &self, + first_token: TokenIdentifier, + second_token: TokenIdentifier, + ) { + self.require_caller_has_owner_permissions(); + + let token_pair = TokenPair { + first_token: first_token.clone(), + second_token: second_token.clone(), + }; + + let mut is_removed = self.trusted_swap_pair().remove(&token_pair).is_some(); + if !is_removed { + let token_pair_reversed = TokenPair { + first_token: second_token, + second_token: first_token, + }; + is_removed = self + .trusted_swap_pair() + .remove(&token_pair_reversed) + .is_some(); + require!(is_removed, ERROR_PAIR_NOT_TRUSTED); + } + } + + /// `fees_collector_cut_percentage` of the special fees are sent to the fees_collector_address SC + /// + /// For example, if special fees is 5%, and fees_collector_cut_percentage is 10%, + /// then of the 5%, 10% are reserved, and only the rest are split between other pair contracts. + #[endpoint(setupFeesCollector)] + fn setup_fees_collector( + &self, + fees_collector_address: ManagedAddress, + fees_collector_cut_percentage: Percent, + ) { + self.require_caller_has_owner_permissions(); + require!( + self.blockchain().is_smart_contract(&fees_collector_address), + "Invalid fees collector address" + ); + require!( + fees_collector_cut_percentage > 0 && fees_collector_cut_percentage <= MAX_PERCENTAGE, + "Invalid fees percentage" + ); + + self.fees_collector_address().set(&fees_collector_address); + self.fees_collector_cut_percentage() + .set(fees_collector_cut_percentage); + } + + #[endpoint(setFeeOn)] + fn set_fee_on(&self, fee_to_address: ManagedAddress, fee_token: TokenIdentifier) { + self.require_caller_has_owner_permissions(); + + let is_dest = self + .destination_map() + .keys() + .any(|dest_address| dest_address == fee_to_address); + require!(!is_dest, ERROR_ALREADY_FEE_DEST); + + let _ = self.destination_map().insert(fee_to_address, fee_token); + } + + #[endpoint(setFeeOn)] + fn set_fee_off(&self, fee_to_address: ManagedAddress, fee_token: TokenIdentifier) { + self.require_caller_has_owner_permissions(); + + let is_dest = self + .destination_map() + .keys() + .any(|dest_address| dest_address == fee_to_address); + require!(is_dest, ERROR_NOT_FEE_DEST); + + let dest_fee_token = self.destination_map().get(&fee_to_address).unwrap(); + require!(fee_token == dest_fee_token, ERROR_BAD_TOKEN_FEE_DEST); + + let _ = self.destination_map().remove(&fee_to_address); + } +} diff --git a/dex/pair/src/fee.rs b/dex/pair/src/fee/impls.rs similarity index 57% rename from dex/pair/src/fee.rs rename to dex/pair/src/fee/impls.rs index 45df09f82..a7a93bc57 100644 --- a/dex/pair/src/fee.rs +++ b/dex/pair/src/fee/impls.rs @@ -1,17 +1,12 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use super::amm; -use super::config; -use super::errors::*; -use super::liquidity_pool; -use crate::config::MAX_PERCENTAGE; -use crate::contexts::base::StorageCache; -use crate::contexts::base::SwapTokensOrder; - use common_structs::TokenPair; use fees_collector::fees_accumulation::ProxyTrait as _; +use crate::{ + config::MAX_PERCENTAGE, StorageCache, SwapTokensOrder, ERROR_NOTHING_TO_DO_WITH_FEE_SLICE, +}; + +multiversx_sc::imports!(); + mod self_proxy { multiversx_sc::imports!(); @@ -24,104 +19,15 @@ mod self_proxy { } #[multiversx_sc::module] -pub trait FeeModule: - config::ConfigModule - + liquidity_pool::LiquidityPoolModule - + amm::AmmModule +pub trait ImplsModule: + crate::config::ConfigModule + + crate::liquidity_pool::LiquidityPoolModule + + crate::amm::AmmModule + token_send::TokenSendModule + permissions_module::PermissionsModule + pausable::PausableModule + + super::storage::StorageModule { - #[view(getFeeState)] - fn is_fee_enabled(&self) -> bool { - !self.destination_map().is_empty() || !self.fees_collector_address().is_empty() - } - - #[endpoint(whitelist)] - fn whitelist_endpoint(&self, address: ManagedAddress) { - self.require_caller_has_owner_permissions(); - let is_new = self.whitelist().insert(address); - require!(is_new, ERROR_ALREADY_WHITELISTED); - } - - #[endpoint(removeWhitelist)] - fn remove_whitelist(&self, address: ManagedAddress) { - self.require_caller_has_owner_permissions(); - let is_removed = self.whitelist().remove(&address); - require!(is_removed, ERROR_NOT_WHITELISTED); - } - - #[endpoint(addTrustedSwapPair)] - fn add_trusted_swap_pair( - &self, - pair_address: ManagedAddress, - first_token: TokenIdentifier, - second_token: TokenIdentifier, - ) { - self.require_caller_has_owner_permissions(); - require!(first_token != second_token, ERROR_SAME_TOKENS); - let token_pair = TokenPair { - first_token, - second_token, - }; - let is_new = self - .trusted_swap_pair() - .insert(token_pair, pair_address) - .is_none(); - require!(is_new, ERROR_PAIR_ALREADY_TRUSTED); - } - - #[endpoint(removeTrustedSwapPair)] - fn remove_trusted_swap_pair( - &self, - first_token: TokenIdentifier, - second_token: TokenIdentifier, - ) { - self.require_caller_has_owner_permissions(); - let token_pair = TokenPair { - first_token: first_token.clone(), - second_token: second_token.clone(), - }; - - let mut is_removed = self.trusted_swap_pair().remove(&token_pair).is_some(); - if !is_removed { - let token_pair_reversed = TokenPair { - first_token: second_token, - second_token: first_token, - }; - is_removed = self - .trusted_swap_pair() - .remove(&token_pair_reversed) - .is_some(); - require!(is_removed, ERROR_PAIR_NOT_TRUSTED); - } - } - - /// `fees_collector_cut_percentage` of the special fees are sent to the fees_collector_address SC - /// - /// For example, if special fees is 5%, and fees_collector_cut_percentage is 10%, - /// then of the 5%, 10% are reserved, and only the rest are split between other pair contracts. - #[endpoint(setupFeesCollector)] - fn setup_fees_collector( - &self, - fees_collector_address: ManagedAddress, - fees_collector_cut_percentage: u64, - ) { - self.require_caller_has_owner_permissions(); - require!( - self.blockchain().is_smart_contract(&fees_collector_address), - "Invalid fees collector address" - ); - require!( - fees_collector_cut_percentage > 0 && fees_collector_cut_percentage <= MAX_PERCENTAGE, - "Invalid fees percentage" - ); - - self.fees_collector_address().set(&fees_collector_address); - self.fees_collector_cut_percentage() - .set(fees_collector_cut_percentage); - } - fn send_fee( &self, storage_cache: &mut StorageCache, @@ -304,13 +210,6 @@ pub trait FeeModule: .execute_on_dest_context(); } - #[inline] - fn burn(&self, token: &TokenIdentifier, amount: &BigUint) { - if amount > &0 { - self.send().esdt_local_burn(token, 0, amount); - } - } - fn get_extern_swap_pair_address( &self, first_token: &TokenIdentifier, @@ -346,55 +245,11 @@ pub trait FeeModule: } } - #[endpoint(setFeeOn)] - fn set_fee_on( - &self, - enabled: bool, - fee_to_address: ManagedAddress, - fee_token: TokenIdentifier, - ) { - self.require_caller_has_owner_permissions(); - let is_dest = self - .destination_map() - .keys() - .any(|dest_address| dest_address == fee_to_address); - - if enabled { - require!(!is_dest, ERROR_ALREADY_FEE_DEST); - self.destination_map().insert(fee_to_address, fee_token); - } else { - require!(is_dest, ERROR_NOT_FEE_DEST); - let dest_fee_token = self.destination_map().get(&fee_to_address).unwrap(); - require!(fee_token == dest_fee_token, ERROR_BAD_TOKEN_FEE_DEST); - self.destination_map().remove(&fee_to_address); - } - } - - #[view(getFeeDestinations)] - fn get_fee_destinations(&self) -> MultiValueEncoded<(ManagedAddress, TokenIdentifier)> { - let mut result = MultiValueEncoded::new(); - for pair in self.destination_map().iter() { - result.push((pair.0, pair.1)) - } - result - } - - #[view(getTrustedSwapPairs)] - fn get_trusted_swap_pairs(&self) -> MultiValueEncoded<(TokenPair, ManagedAddress)> { - let mut result = MultiValueEncoded::new(); - for pair in self.trusted_swap_pair().iter() { - result.push((pair.0, pair.1)) - } - result - } - - #[view(getWhitelistedManagedAddresses)] - fn get_whitelisted_managed_addresses(&self) -> MultiValueEncoded { - let mut result = MultiValueEncoded::new(); - for pair in self.whitelist().iter() { - result.push(pair); + #[inline] + fn burn(&self, token: &TokenIdentifier, amount: &BigUint) { + if amount > &0 { + self.send().esdt_local_burn(token, 0, amount); } - result } #[proxy] @@ -402,21 +257,4 @@ pub trait FeeModule: #[proxy] fn fees_collector_proxy(&self, sc_address: ManagedAddress) -> fees_collector::Proxy; - - #[view(getFeesCollectorAddress)] - #[storage_mapper("feesCollectorAddress")] - fn fees_collector_address(&self) -> SingleValueMapper; - - #[view(getFeesCollectorCutPercentage)] - #[storage_mapper("feesCollectorCutPercentage")] - fn fees_collector_cut_percentage(&self) -> SingleValueMapper; - - #[storage_mapper("fee_destination")] - fn destination_map(&self) -> MapMapper; - - #[storage_mapper("trusted_swap_pair")] - fn trusted_swap_pair(&self) -> MapMapper, ManagedAddress>; - - #[storage_mapper("whitelist")] - fn whitelist(&self) -> SetMapper; } diff --git a/dex/pair/src/fee/mod.rs b/dex/pair/src/fee/mod.rs new file mode 100644 index 000000000..9f7633315 --- /dev/null +++ b/dex/pair/src/fee/mod.rs @@ -0,0 +1,4 @@ +pub mod endpoints; +pub mod impls; +pub mod storage; +pub mod views; diff --git a/dex/pair/src/fee/storage.rs b/dex/pair/src/fee/storage.rs new file mode 100644 index 000000000..9da4384d3 --- /dev/null +++ b/dex/pair/src/fee/storage.rs @@ -0,0 +1,23 @@ +use common_structs::{Percent, TokenPair}; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait StorageModule { + #[view(getFeesCollectorAddress)] + #[storage_mapper("feesCollectorAddress")] + fn fees_collector_address(&self) -> SingleValueMapper; + + #[view(getFeesCollectorCutPercentage)] + #[storage_mapper("feesCollectorCutPercentage")] + fn fees_collector_cut_percentage(&self) -> SingleValueMapper; + + #[storage_mapper("fee_destination")] + fn destination_map(&self) -> MapMapper; + + #[storage_mapper("trusted_swap_pair")] + fn trusted_swap_pair(&self) -> MapMapper, ManagedAddress>; + + #[storage_mapper("whitelist")] + fn whitelist(&self) -> SetMapper; +} diff --git a/dex/pair/src/fee/views.rs b/dex/pair/src/fee/views.rs new file mode 100644 index 000000000..8f82397af --- /dev/null +++ b/dex/pair/src/fee/views.rs @@ -0,0 +1,41 @@ +use common_structs::TokenPair; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait ViewsModule: super::storage::StorageModule { + #[view(getFeeState)] + fn is_fee_enabled(&self) -> bool { + !self.destination_map().is_empty() || !self.fees_collector_address().is_empty() + } + + #[view(getFeeDestinations)] + fn get_fee_destinations(&self) -> MultiValueEncoded<(ManagedAddress, TokenIdentifier)> { + let mut result = MultiValueEncoded::new(); + for pair in self.destination_map().iter() { + result.push((pair.0, pair.1)) + } + + result + } + + #[view(getTrustedSwapPairs)] + fn get_trusted_swap_pairs(&self) -> MultiValueEncoded<(TokenPair, ManagedAddress)> { + let mut result = MultiValueEncoded::new(); + for pair in self.trusted_swap_pair().iter() { + result.push((pair.0, pair.1)) + } + + result + } + + #[view(getWhitelistedManagedAddresses)] + fn get_whitelisted_managed_addresses(&self) -> MultiValueEncoded { + let mut result = MultiValueEncoded::new(); + for pair in self.whitelist().iter() { + result.push(pair); + } + + result + } +} diff --git a/dex/pair/src/lib.rs b/dex/pair/src/lib.rs index e67010d67..9d4f806a0 100644 --- a/dex/pair/src/lib.rs +++ b/dex/pair/src/lib.rs @@ -29,7 +29,10 @@ use permissions_module::Permissions; #[multiversx_sc::contract] pub trait Pair: amm::AmmModule - + fee::FeeModule + + fee::endpoints::EndpointsModule + + fee::impls::ImplsModule + + fee::storage::StorageModule + + fee::views::ViewsModule + liquidity_pool::LiquidityPoolModule + config::ConfigModule + token_send::TokenSendModule @@ -76,25 +79,26 @@ pub trait Pair: self.state().set(State::Inactive); self.router_address().set(&router_address); - self.first_token_id().set_if_empty(&first_token_id); - self.second_token_id().set_if_empty(&second_token_id); - let initial_liquidity_adder_opt = if !initial_liquidity_adder.is_zero() { + self.first_token_id().set(first_token_id); + self.second_token_id().set(second_token_id); + + let opt_initial_liquidity_adder = if !initial_liquidity_adder.is_zero() { Some(initial_liquidity_adder) } else { None }; self.initial_liquidity_adder() - .set_if_empty(&initial_liquidity_adder_opt); + .set(&opt_initial_liquidity_adder); if admins.is_empty() { // backwards compatibility let all_permissions = Permissions::OWNER | Permissions::ADMIN | Permissions::PAUSE; - self.add_permissions(router_address, all_permissions.clone()); - self.add_permissions(router_owner_address, all_permissions); + self.add_permissions(&router_address, all_permissions.clone()); + self.add_permissions(&router_owner_address, all_permissions); } else { - self.add_permissions(router_address, Permissions::OWNER | Permissions::PAUSE); + self.add_permissions(&router_address, Permissions::OWNER | Permissions::PAUSE); self.add_permissions( - router_owner_address, + &router_owner_address, Permissions::OWNER | Permissions::PAUSE, ); self.add_permissions_for_all(admins, Permissions::ADMIN); @@ -121,6 +125,7 @@ pub trait Pair: token_identifier.is_valid_esdt_identifier(), ERROR_NOT_AN_ESDT ); + self.lp_token_identifier().set(&token_identifier); } } diff --git a/dex/pair/src/pair_actions/remove_liq.rs b/dex/pair/src/pair_actions/remove_liq.rs index 1010ac666..5b8dd5ff1 100644 --- a/dex/pair/src/pair_actions/remove_liq.rs +++ b/dex/pair/src/pair_actions/remove_liq.rs @@ -16,7 +16,10 @@ pub trait RemoveLiquidityModule: + crate::locking_wrapper::LockingWrapperModule + crate::events::EventsModule + crate::safe_price::SafePriceModule - + crate::fee::FeeModule + + crate::fee::endpoints::EndpointsModule + + crate::fee::impls::ImplsModule + + crate::fee::storage::StorageModule + + crate::fee::views::ViewsModule + crate::config::ConfigModule + token_send::TokenSendModule + permissions_module::PermissionsModule diff --git a/dex/pair/src/pair_actions/swap.rs b/dex/pair/src/pair_actions/swap.rs index 62cdc01d4..161a05f05 100644 --- a/dex/pair/src/pair_actions/swap.rs +++ b/dex/pair/src/pair_actions/swap.rs @@ -23,7 +23,10 @@ pub trait SwapModule: + crate::locking_wrapper::LockingWrapperModule + crate::events::EventsModule + crate::safe_price::SafePriceModule - + crate::fee::FeeModule + + crate::fee::endpoints::EndpointsModule + + crate::fee::impls::ImplsModule + + crate::fee::storage::StorageModule + + crate::fee::views::ViewsModule + crate::config::ConfigModule + token_send::TokenSendModule + permissions_module::PermissionsModule diff --git a/dex/pair/src/pair_actions/views.rs b/dex/pair/src/pair_actions/views.rs index 878b3bd5e..a90f3d79f 100644 --- a/dex/pair/src/pair_actions/views.rs +++ b/dex/pair/src/pair_actions/views.rs @@ -10,7 +10,10 @@ pub trait ViewsModule: + crate::locking_wrapper::LockingWrapperModule + crate::events::EventsModule + crate::safe_price::SafePriceModule - + crate::fee::FeeModule + + crate::fee::endpoints::EndpointsModule + + crate::fee::impls::ImplsModule + + crate::fee::storage::StorageModule + + crate::fee::views::ViewsModule + crate::config::ConfigModule + token_send::TokenSendModule + permissions_module::PermissionsModule diff --git a/dex/pair/tests/pair_rs_test.rs b/dex/pair/tests/pair_rs_test.rs index bd82750d8..3d5d88202 100644 --- a/dex/pair/tests/pair_rs_test.rs +++ b/dex/pair/tests/pair_rs_test.rs @@ -19,9 +19,9 @@ use multiversx_sc_scenario::{ managed_address, managed_biguint, managed_token_id, managed_token_id_wrapped, rust_biguint, whitebox_legacy::TxTokenTransfer, DebugApi, }; +use pair::fee::endpoints::EndpointsModule; use pair::{ config::MAX_PERCENTAGE, - fee::FeeModule, locking_wrapper::LockingWrapperModule, pair_actions::swap::SwapModule, safe_price::{PriceObservation, Round, SafePriceModule}, diff --git a/dex/pair/wasm-pair-full/src/lib.rs b/dex/pair/wasm-pair-full/src/lib.rs index 5f39165a9..c5f8b252a 100644 --- a/dex/pair/wasm-pair-full/src/lib.rs +++ b/dex/pair/wasm-pair-full/src/lib.rs @@ -21,18 +21,18 @@ multiversx_sc_wasm_adapter::endpoints! { init => init upgrade => upgrade setLpTokenIdentifier => set_lp_token_identifier - getFeeState => is_fee_enabled whitelist => whitelist_endpoint removeWhitelist => remove_whitelist addTrustedSwapPair => add_trusted_swap_pair removeTrustedSwapPair => remove_trusted_swap_pair setupFeesCollector => setup_fees_collector setFeeOn => set_fee_on + getFeesCollectorAddress => fees_collector_address + getFeesCollectorCutPercentage => fees_collector_cut_percentage + getFeeState => is_fee_enabled getFeeDestinations => get_fee_destinations getTrustedSwapPairs => get_trusted_swap_pairs getWhitelistedManagedAddresses => get_whitelisted_managed_addresses - getFeesCollectorAddress => fees_collector_address - getFeesCollectorCutPercentage => fees_collector_cut_percentage setStateActiveNoSwaps => set_state_active_no_swaps setFeePercents => set_fee_percent getLpTokenIdentifier => get_lp_token_identifier diff --git a/dex/pair/wasm/src/lib.rs b/dex/pair/wasm/src/lib.rs index 7279213ee..99d1687f7 100644 --- a/dex/pair/wasm/src/lib.rs +++ b/dex/pair/wasm/src/lib.rs @@ -21,18 +21,18 @@ multiversx_sc_wasm_adapter::endpoints! { init => init upgrade => upgrade setLpTokenIdentifier => set_lp_token_identifier - getFeeState => is_fee_enabled whitelist => whitelist_endpoint removeWhitelist => remove_whitelist addTrustedSwapPair => add_trusted_swap_pair removeTrustedSwapPair => remove_trusted_swap_pair setupFeesCollector => setup_fees_collector setFeeOn => set_fee_on + getFeesCollectorAddress => fees_collector_address + getFeesCollectorCutPercentage => fees_collector_cut_percentage + getFeeState => is_fee_enabled getFeeDestinations => get_fee_destinations getTrustedSwapPairs => get_trusted_swap_pairs getWhitelistedManagedAddresses => get_whitelisted_managed_addresses - getFeesCollectorAddress => fees_collector_address - getFeesCollectorCutPercentage => fees_collector_cut_percentage setStateActiveNoSwaps => set_state_active_no_swaps setFeePercents => set_fee_percent getLpTokenIdentifier => get_lp_token_identifier diff --git a/dex/router/Cargo.toml b/dex/router/Cargo.toml index 4ecf1aa3c..1e2552c1b 100644 --- a/dex/router/Cargo.toml +++ b/dex/router/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [lib] -path = "src/contract.rs" +path = "src/lib.rs" [dependencies.token_send] path = "../../common/modules/token_send" @@ -14,6 +14,9 @@ path = "../../common/modules/token_send" [dependencies.pausable] path = "../../common/modules/pausable" +[dependencies.common_structs] +path = "../../common/common_structs" + [dependencies.multiversx-sc] version = "=0.53.2" features = ["esdt-token-payment-legacy-decode"] diff --git a/dex/router/src/config.rs b/dex/router/src/config.rs index 830d301c8..e8ce1b1d9 100644 --- a/dex/router/src/config.rs +++ b/dex/router/src/config.rs @@ -1,13 +1,31 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); -use crate::{enable_swap_by_user::EnableSwapByUserConfig, factory::PairTokens}; +use crate::pair_actions::create::PairTokens; use pair::read_pair_storage; +pub type PairCreationStatus = bool; +pub const ENABLED: PairCreationStatus = true; +pub const DISABLED: PairCreationStatus = false; + #[multiversx_sc::module] pub trait ConfigModule: read_pair_storage::ReadPairStorageModule { - fn is_active(&self) -> bool { - self.state().get() + #[only_owner] + #[endpoint(setPairTemplateAddress)] + fn set_pair_template_address(&self, address: ManagedAddress) { + self.pair_template_address().set(address); + } + + #[only_owner] + #[endpoint(setPairCreationEnabled)] + fn set_pair_creation_enabled(&self) { + self.pair_creation_enabled().set(ENABLED); + } + + #[only_owner] + #[endpoint(setPairCreationDisabled)] + fn set_pair_creation_disabled(&self) { + self.pair_creation_enabled().set(DISABLED); } fn check_is_pair_sc(&self, pair_address: &ManagedAddress) { @@ -30,36 +48,25 @@ pub trait ConfigModule: read_pair_storage::ReadPairStorageModule { require!(pair_map_address_opt.is_some(), "Not a pair SC"); - unsafe { - let pair_map_address = pair_map_address_opt.unwrap_unchecked(); - require!(&pair_map_address == pair_address, "Not a pair SC"); - } + let pair_map_address = unsafe { pair_map_address_opt.unwrap_unchecked() }; + require!(&pair_map_address == pair_address, "Not a pair SC"); + } + + fn require_pair_creation_enabled(&self) { + require!( + self.pair_creation_enabled().get() == ENABLED, + "Pair creation is disabled" + ); } #[view(getPairCreationEnabled)] #[storage_mapper("pair_creation_enabled")] - fn pair_creation_enabled(&self) -> SingleValueMapper; - - #[view(getState)] - #[storage_mapper("state")] - fn state(&self) -> SingleValueMapper; + fn pair_creation_enabled(&self) -> SingleValueMapper; #[view(getOwner)] #[storage_mapper("owner")] fn owner(&self) -> SingleValueMapper; - #[only_owner] - #[endpoint(setTemporaryOwnerPeriod)] - fn set_temporary_owner_period(&self, period_blocks: u64) { - self.temporary_owner_period().set(period_blocks); - } - - #[only_owner] - #[endpoint(setPairTemplateAddress)] - fn set_pair_template_address(&self, address: ManagedAddress) { - self.pair_template_address().set(&address); - } - #[storage_mapper("pair_map")] fn pair_map(&self) -> MapMapper, ManagedAddress>; @@ -67,19 +74,6 @@ pub trait ConfigModule: read_pair_storage::ReadPairStorageModule { #[storage_mapper("pair_template_address")] fn pair_template_address(&self) -> SingleValueMapper; - #[view(getTemporaryOwnerPeriod)] - #[storage_mapper("temporary_owner_period")] - fn temporary_owner_period(&self) -> SingleValueMapper; - - #[storage_mapper("pair_temporary_owner")] - fn pair_temporary_owner(&self) -> MapMapper; - - #[storage_mapper("enableSwapByUserConfig")] - fn enable_swap_by_user_config( - &self, - token_id: &TokenIdentifier, - ) -> SingleValueMapper>; - #[view(getCommonTokensForUserPairs)] #[storage_mapper("commonTokensForUserPairs")] fn common_tokens_for_user_pairs(&self) -> UnorderedSetMapper; diff --git a/dex/router/src/contract.rs b/dex/router/src/contract.rs deleted file mode 100644 index ad30b7f9e..000000000 --- a/dex/router/src/contract.rs +++ /dev/null @@ -1,367 +0,0 @@ -#![no_std] -#![allow(deprecated)] - -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -pub mod config; -pub mod enable_swap_by_user; -mod events; -pub mod factory; -pub mod multi_pair_swap; - -use factory::PairTokens; -use pair::config::ProxyTrait as _; -use pair::fee::ProxyTrait as _; -use pair::{read_pair_storage, ProxyTrait as _}; -use pausable::ProxyTrait as _; - -const LP_TOKEN_DECIMALS: usize = 18; -const LP_TOKEN_INITIAL_SUPPLY: u64 = 1000; - -const DEFAULT_TOTAL_FEE_PERCENT: u64 = 300; -const DEFAULT_SPECIAL_FEE_PERCENT: u64 = 50; -const MAX_TOTAL_FEE_PERCENT: u64 = 100_000; -const USER_DEFINED_TOTAL_FEE_PERCENT: u64 = 1_000; - -#[multiversx_sc::contract] -pub trait Router: - config::ConfigModule - + read_pair_storage::ReadPairStorageModule - + factory::FactoryModule - + events::EventsModule - + multi_pair_swap::MultiPairSwap - + token_send::TokenSendModule - + enable_swap_by_user::EnableSwapByUserModule -{ - #[init] - fn init(&self, pair_template_address_opt: OptionalValue) { - self.state().set_if_empty(true); - self.pair_creation_enabled().set_if_empty(false); - - self.init_factory(pair_template_address_opt.into_option()); - self.owner().set(&self.blockchain().get_caller()); - } - - #[upgrade] - fn upgrade(&self) { - self.state().set(false); - } - - #[only_owner] - #[endpoint] - fn pause(&self, address: ManagedAddress) { - if address == self.blockchain().get_sc_address() { - self.state().set(false); - } else { - self.check_is_pair_sc(&address); - let _: IgnoreValue = self - .pair_contract_proxy(address) - .pause() - .execute_on_dest_context(); - } - } - - #[only_owner] - #[endpoint] - fn resume(&self, address: ManagedAddress) { - if address == self.blockchain().get_sc_address() { - self.state().set(true); - } else { - self.check_is_pair_sc(&address); - let _: IgnoreValue = self - .pair_contract_proxy(address) - .resume() - .execute_on_dest_context(); - } - } - - #[allow_multiple_var_args] - #[endpoint(createPair)] - fn create_pair_endpoint( - &self, - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - initial_liquidity_adder: ManagedAddress, - opt_fee_percents: OptionalValue>, - mut admins: MultiValueEncoded, - ) -> ManagedAddress { - require!(self.is_active(), "Not active"); - let owner = self.owner().get(); - let caller = self.blockchain().get_caller(); - - if caller != owner { - require!( - self.pair_creation_enabled().get(), - "Pair creation is disabled" - ); - } - - require!(first_token_id != second_token_id, "Identical tokens"); - require!( - first_token_id.is_valid_esdt_identifier(), - "First Token ID is not a valid esdt token ID" - ); - require!( - second_token_id.is_valid_esdt_identifier(), - "Second Token ID is not a valid esdt token ID" - ); - let pair_address = self.get_pair(first_token_id.clone(), second_token_id.clone()); - require!(pair_address.is_zero(), "Pair already exists"); - - let mut total_fee_percent_requested = DEFAULT_TOTAL_FEE_PERCENT; - let mut special_fee_percent_requested = DEFAULT_SPECIAL_FEE_PERCENT; - - if caller == owner { - if let Some(fee_percents_multi_arg) = opt_fee_percents.into_option() { - let fee_percents_tuple = fee_percents_multi_arg.into_tuple(); - total_fee_percent_requested = fee_percents_tuple.0; - special_fee_percent_requested = fee_percents_tuple.1; - - require!( - total_fee_percent_requested >= special_fee_percent_requested - && total_fee_percent_requested < MAX_TOTAL_FEE_PERCENT, - "Bad percents" - ); - } else { - sc_panic!("Bad percents length"); - } - } - - admins.push(caller.clone()); - - let address = self.create_pair( - &first_token_id, - &second_token_id, - &owner, - total_fee_percent_requested, - special_fee_percent_requested, - &initial_liquidity_adder, - admins, - ); - - self.emit_create_pair_event( - caller, - first_token_id, - second_token_id, - total_fee_percent_requested, - special_fee_percent_requested, - address.clone(), - ); - address - } - - #[only_owner] - #[endpoint(upgradePair)] - fn upgrade_pair_endpoint( - &self, - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - ) { - require!(self.is_active(), "Not active"); - - require!(first_token_id != second_token_id, "Identical tokens"); - require!( - first_token_id.is_valid_esdt_identifier(), - "First Token ID is not a valid esdt token ID" - ); - require!( - second_token_id.is_valid_esdt_identifier(), - "Second Token ID is not a valid esdt token ID" - ); - let pair_address = self.get_pair(first_token_id.clone(), second_token_id.clone()); - require!(!pair_address.is_zero(), "Pair does not exists"); - - self.upgrade_pair(pair_address); - } - - #[payable("EGLD")] - #[endpoint(issueLpToken)] - fn issue_lp_token( - &self, - pair_address: ManagedAddress, - lp_token_display_name: ManagedBuffer, - lp_token_ticker: ManagedBuffer, - ) { - let issue_cost = self.call_value().egld_value().clone_value(); - - require!(self.is_active(), "Not active"); - let caller = self.blockchain().get_caller(); - if caller != self.owner().get() { - require!( - self.pair_creation_enabled().get(), - "Pair creation is disabled" - ); - } - self.check_is_pair_sc(&pair_address); - let result = self.get_pair_temporary_owner(&pair_address); - - match result { - None => {} - Some(temporary_owner) => { - require!(caller == temporary_owner, "Temporary owner differs"); - } - }; - - let result: TokenIdentifier = self - .pair_contract_proxy(pair_address.clone()) - .get_lp_token_identifier() - .execute_on_dest_context(); - require!( - !result.is_valid_esdt_identifier(), - "LP Token already issued" - ); - - self.send() - .esdt_system_sc_proxy() - .issue_fungible( - issue_cost, - &lp_token_display_name, - &lp_token_ticker, - &BigUint::from(LP_TOKEN_INITIAL_SUPPLY), - FungibleTokenProperties { - num_decimals: LP_TOKEN_DECIMALS, - can_freeze: true, - can_wipe: true, - can_pause: true, - can_mint: true, - can_burn: true, - can_change_owner: true, - can_upgrade: true, - can_add_special_roles: true, - }, - ) - .async_call() - .with_callback( - self.callbacks() - .lp_token_issue_callback(&caller, &pair_address), - ) - .call_and_exit() - } - - #[endpoint(setLocalRoles)] - fn set_local_roles(&self, pair_address: ManagedAddress) { - require!(self.is_active(), "Not active"); - self.check_is_pair_sc(&pair_address); - - let pair_token: TokenIdentifier = self - .pair_contract_proxy(pair_address.clone()) - .get_lp_token_identifier() - .execute_on_dest_context(); - require!(pair_token.is_valid_esdt_identifier(), "LP token not issued"); - - let roles = [EsdtLocalRole::Mint, EsdtLocalRole::Burn]; - - self.send() - .esdt_system_sc_proxy() - .set_special_roles(&pair_address, &pair_token, roles.iter().cloned()) - .async_call() - .call_and_exit() - } - - #[only_owner] - #[endpoint(removePair)] - fn remove_pair( - &self, - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - ) -> ManagedAddress { - require!(self.is_active(), "Not active"); - - require!(first_token_id != second_token_id, "Identical tokens"); - require!( - first_token_id.is_valid_esdt_identifier(), - "First Token ID is not a valid esdt token ID" - ); - require!( - second_token_id.is_valid_esdt_identifier(), - "Second Token ID is not a valid esdt token ID" - ); - let mut pair_address = self.get_pair(first_token_id.clone(), second_token_id.clone()); - require!(!pair_address.is_zero(), "Pair does not exists"); - - pair_address = self - .pair_map() - .remove(&PairTokens { - first_token_id: first_token_id.clone(), - second_token_id: second_token_id.clone(), - }) - .unwrap_or_else(ManagedAddress::zero); - - if pair_address.is_zero() { - pair_address = self - .pair_map() - .remove(&PairTokens { - first_token_id: second_token_id, - second_token_id: first_token_id, - }) - .unwrap_or_else(ManagedAddress::zero); - } - - pair_address - } - - #[only_owner] - #[endpoint(setFeeOn)] - fn set_fee_on( - &self, - pair_address: ManagedAddress, - fee_to_address: ManagedAddress, - fee_token: TokenIdentifier, - ) { - require!(self.is_active(), "Not active"); - self.check_is_pair_sc(&pair_address); - - let _: IgnoreValue = self - .pair_contract_proxy(pair_address) - .set_fee_on(true, fee_to_address, fee_token) - .execute_on_dest_context(); - } - - #[only_owner] - #[endpoint(setFeeOff)] - fn set_fee_off( - &self, - pair_address: ManagedAddress, - fee_to_address: ManagedAddress, - fee_token: TokenIdentifier, - ) { - require!(self.is_active(), "Not active"); - self.check_is_pair_sc(&pair_address); - - let _: IgnoreValue = self - .pair_contract_proxy(pair_address) - .set_fee_on(false, fee_to_address, fee_token) - .execute_on_dest_context(); - } - - #[callback] - fn lp_token_issue_callback( - &self, - caller: &ManagedAddress, - address: &ManagedAddress, - #[call_result] result: ManagedAsyncCallResult<()>, - ) { - let (token_id, returned_tokens) = self.call_value().egld_or_single_fungible_esdt(); - match result { - ManagedAsyncCallResult::Ok(()) => { - self.pair_temporary_owner().remove(address); - let _: IgnoreValue = self - .pair_contract_proxy(address.clone()) - .set_lp_token_identifier(token_id.unwrap_esdt()) - .execute_on_dest_context(); - } - ManagedAsyncCallResult::Err(_) => { - if token_id.is_egld() && returned_tokens > 0u64 { - self.send().direct_egld(caller, &returned_tokens); - } - } - } - } - - #[only_owner] - #[endpoint(setPairCreationEnabled)] - fn set_pair_creation_enabled(&self, enabled: bool) { - self.pair_creation_enabled().set(enabled); - } -} diff --git a/dex/router/src/events.rs b/dex/router/src/events.rs index b80888e5a..f26343d08 100644 --- a/dex/router/src/events.rs +++ b/dex/router/src/events.rs @@ -1,37 +1,41 @@ +use common_structs::{Epoch, Percent}; + +use crate::Blocks; + multiversx_sc::imports!(); multiversx_sc::derive_imports!(); #[derive(TypeAbi, TopEncode)] -pub struct CreatePairEvent { - caller: ManagedAddress, - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - total_fee_percent: u64, - special_fee_percent: u64, - pair_address: ManagedAddress, - block: u64, - epoch: u64, +pub struct CreatePairEvent<'a, M: ManagedTypeApi> { + caller: &'a ManagedAddress, + first_token_id: &'a TokenIdentifier, + second_token_id: &'a TokenIdentifier, + total_fee_percent: Percent, + special_fee_percent: Percent, + pair_address: &'a ManagedAddress, + block: Blocks, + epoch: Epoch, timestamp: u64, } #[derive(TypeAbi, TopEncode)] -pub struct UserPairSwapEnabledEvent { - caller: ManagedAddress, - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - pair_address: ManagedAddress, +pub struct UserPairSwapEnabledEvent<'a, M: ManagedTypeApi> { + caller: &'a ManagedAddress, + first_token_id: &'a TokenIdentifier, + second_token_id: &'a TokenIdentifier, + pair_address: &'a ManagedAddress, } #[derive(TypeAbi, TopEncode)] -pub struct MultiPairSwapEvent { - caller: ManagedAddress, - token_in: TokenIdentifier, - amount_in: BigUint, - token_out: TokenIdentifier, - amount_out: BigUint, - payments_out: ManagedVec>, - block: u64, - epoch: u64, +pub struct MultiPairSwapEvent<'a, M: ManagedTypeApi> { + caller: &'a ManagedAddress, + token_in: &'a TokenIdentifier, + amount_in: &'a BigUint, + token_out: &'a TokenIdentifier, + amount_out: &'a BigUint, + payments_out: &'a ManagedVec>, + block: Blocks, + epoch: Epoch, timestamp: u64, } @@ -39,18 +43,18 @@ pub struct MultiPairSwapEvent { pub trait EventsModule { fn emit_create_pair_event( self, - caller: ManagedAddress, - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - total_fee_percent: u64, - special_fee_percent: u64, - pair_address: ManagedAddress, + caller: &ManagedAddress, + first_token_id: &TokenIdentifier, + second_token_id: &TokenIdentifier, + total_fee_percent: Percent, + special_fee_percent: Percent, + pair_address: &ManagedAddress, ) { let epoch = self.blockchain().get_block_epoch(); self.create_pair_event( - first_token_id.clone(), - second_token_id.clone(), - caller.clone(), + first_token_id, + second_token_id, + caller, epoch, CreatePairEvent { caller, @@ -68,16 +72,16 @@ pub trait EventsModule { fn emit_user_swaps_enabled_event( &self, - caller: ManagedAddress, - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - pair_address: ManagedAddress, + caller: &ManagedAddress, + first_token_id: &TokenIdentifier, + second_token_id: &TokenIdentifier, + pair_address: &ManagedAddress, ) { let epoch = self.blockchain().get_block_epoch(); self.pair_swap_enabled_event( - first_token_id.clone(), - second_token_id.clone(), - caller.clone(), + first_token_id, + second_token_id, + caller, epoch, UserPairSwapEnabledEvent { caller, @@ -90,10 +94,10 @@ pub trait EventsModule { fn emit_multi_pair_swap_event( &self, - caller: ManagedAddress, - token_in: TokenIdentifier, - amount_in: BigUint, - payments_out: ManagedVec, + caller: &ManagedAddress, + token_in: &TokenIdentifier, + amount_in: &BigUint, + payments_out: &ManagedVec, ) { if payments_out.is_empty() { return; @@ -105,18 +109,18 @@ pub trait EventsModule { let last_payment_index = payments_out.len() - 1; let token_out = payments_out.get(last_payment_index); self.multi_pair_swap_event( - caller.clone(), - token_in.clone(), - amount_in.clone(), - token_out.token_identifier.clone(), - token_out.amount.clone(), + caller, + token_in, + amount_in, + &token_out.token_identifier, + &token_out.amount, epoch, MultiPairSwapEvent { caller, token_in, amount_in, - token_out: token_out.token_identifier, - amount_out: token_out.amount, + token_out: &token_out.token_identifier, + amount_out: &token_out.amount, payments_out, block, epoch, @@ -128,32 +132,32 @@ pub trait EventsModule { #[event("create_pair")] fn create_pair_event( self, - #[indexed] first_token_id: TokenIdentifier, - #[indexed] second_token_id: TokenIdentifier, - #[indexed] caller: ManagedAddress, - #[indexed] epoch: u64, + #[indexed] first_token_id: &TokenIdentifier, + #[indexed] second_token_id: &TokenIdentifier, + #[indexed] caller: &ManagedAddress, + #[indexed] epoch: Epoch, swap_event: CreatePairEvent, ); #[event("pairSwapEnabled")] fn pair_swap_enabled_event( &self, - #[indexed] first_token_id: TokenIdentifier, - #[indexed] second_token_id: TokenIdentifier, - #[indexed] caller: ManagedAddress, - #[indexed] epoch: u64, + #[indexed] first_token_id: &TokenIdentifier, + #[indexed] second_token_id: &TokenIdentifier, + #[indexed] caller: &ManagedAddress, + #[indexed] epoch: Epoch, swap_enabled_event: UserPairSwapEnabledEvent, ); #[event("multiPairSwap")] fn multi_pair_swap_event( &self, - #[indexed] caller: ManagedAddress, - #[indexed] token_in: TokenIdentifier, - #[indexed] amount_in: BigUint, - #[indexed] token_out: TokenIdentifier, - #[indexed] amount_out: BigUint, - #[indexed] epoch: u64, + #[indexed] caller: &ManagedAddress, + #[indexed] token_in: &TokenIdentifier, + #[indexed] amount_in: &BigUint, + #[indexed] token_out: &TokenIdentifier, + #[indexed] amount_out: &BigUint, + #[indexed] epoch: Epoch, multi_pair_swap_event: MultiPairSwapEvent, ); } diff --git a/dex/router/src/factory.rs b/dex/router/src/factory.rs deleted file mode 100644 index ff7633a15..000000000 --- a/dex/router/src/factory.rs +++ /dev/null @@ -1,180 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use crate::config; -use pair::read_pair_storage; - -const TEMPORARY_OWNER_PERIOD_BLOCKS: u64 = 50; - -#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, PartialEq, TypeAbi)] -pub struct PairTokens { - pub first_token_id: TokenIdentifier, - pub second_token_id: TokenIdentifier, -} - -#[derive(ManagedVecItem, TopEncode, TopDecode, PartialEq, TypeAbi)] -pub struct PairContractMetadata { - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - address: ManagedAddress, -} - -#[multiversx_sc::module] -pub trait FactoryModule: config::ConfigModule + read_pair_storage::ReadPairStorageModule { - #[proxy] - fn pair_contract_deploy_proxy(&self) -> pair::Proxy; - - fn init_factory(&self, pair_template_address_opt: Option) { - if let Some(addr) = pair_template_address_opt { - self.pair_template_address().set(&addr); - } - - self.temporary_owner_period() - .set_if_empty(TEMPORARY_OWNER_PERIOD_BLOCKS); - } - - fn create_pair( - &self, - first_token_id: &TokenIdentifier, - second_token_id: &TokenIdentifier, - owner: &ManagedAddress, - total_fee_percent: u64, - special_fee_percent: u64, - initial_liquidity_adder: &ManagedAddress, - admins: MultiValueEncoded, - ) -> ManagedAddress { - require!( - !self.pair_template_address().is_empty(), - "pair contract template is empty" - ); - - let (new_address, ()) = self - .pair_contract_deploy_proxy() - .init( - first_token_id, - second_token_id, - self.blockchain().get_sc_address(), - owner, - total_fee_percent, - special_fee_percent, - initial_liquidity_adder, - admins, - ) - .deploy_from_source( - &self.pair_template_address().get(), - CodeMetadata::UPGRADEABLE | CodeMetadata::READABLE | CodeMetadata::PAYABLE_BY_SC, - ); - - self.pair_map().insert( - PairTokens { - first_token_id: first_token_id.clone(), - second_token_id: second_token_id.clone(), - }, - new_address.clone(), - ); - self.pair_temporary_owner().insert( - new_address.clone(), - ( - self.blockchain().get_caller(), - self.blockchain().get_block_nonce(), - ), - ); - new_address - } - - fn upgrade_pair(&self, pair_address: ManagedAddress) { - let pair_template_address = self.pair_template_address().get(); - let code_metadata = - CodeMetadata::UPGRADEABLE | CodeMetadata::READABLE | CodeMetadata::PAYABLE_BY_SC; - self.tx() - .to(pair_address) - .raw_upgrade() - .from_source(pair_template_address) - .code_metadata(code_metadata) - .upgrade_async_call_and_exit(); - } - - #[view(getAllPairsManagedAddresses)] - fn get_all_pairs_addresses(&self) -> MultiValueEncoded { - let mut result = MultiValueEncoded::new(); - for pair in self.pair_map().values() { - result.push(pair); - } - result - } - - #[view(getAllPairTokens)] - fn get_all_token_pairs(&self) -> MultiValueEncoded> { - let mut result = MultiValueEncoded::new(); - for pair in self.pair_map().keys() { - result.push(pair); - } - result - } - - #[view(getAllPairContractMetadata)] - fn get_all_pair_contract_metadata(&self) -> MultiValueEncoded> { - let mut result = MultiValueEncoded::new(); - for (k, v) in self.pair_map().iter() { - let pair_metadata = PairContractMetadata { - first_token_id: k.first_token_id, - second_token_id: k.second_token_id, - address: v, - }; - result.push(pair_metadata); - } - result - } - - #[view(getPair)] - fn get_pair( - &self, - first_token_id: TokenIdentifier, - second_token_id: TokenIdentifier, - ) -> ManagedAddress { - let mut address = self - .pair_map() - .get(&PairTokens { - first_token_id: first_token_id.clone(), - second_token_id: second_token_id.clone(), - }) - .unwrap_or_else(ManagedAddress::zero); - - if address.is_zero() { - address = self - .pair_map() - .get(&PairTokens { - first_token_id: second_token_id, - second_token_id: first_token_id, - }) - .unwrap_or_else(ManagedAddress::zero); - } - address - } - - fn get_pair_temporary_owner(&self, pair_address: &ManagedAddress) -> Option { - let result = self.pair_temporary_owner().get(pair_address); - - match result { - Some((temporary_owner, creation_block)) => { - let expire_block = creation_block + self.temporary_owner_period().get(); - - if expire_block <= self.blockchain().get_block_nonce() { - self.pair_temporary_owner().remove(pair_address); - None - } else { - Some(temporary_owner) - } - } - None => None, - } - } - - #[only_owner] - #[endpoint(clearPairTemporaryOwnerStorage)] - fn clear_pair_temporary_owner_storage(&self) -> usize { - let size = self.pair_temporary_owner().len(); - self.pair_temporary_owner().clear(); - size - } -} diff --git a/dex/router/src/lib.rs b/dex/router/src/lib.rs new file mode 100644 index 000000000..d12248ee7 --- /dev/null +++ b/dex/router/src/lib.rs @@ -0,0 +1,64 @@ +#![no_std] + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +pub mod config; +pub mod events; +pub mod pair_actions; +pub mod state; +pub mod temp_owner; +pub mod views; + +use config::DISABLED; +use pair::read_pair_storage; +use state::{ACTIVE, INACTIVE}; + +const DEFAULT_TEMPORARY_OWNER_PERIOD_BLOCKS: Blocks = 50; + +pub type Blocks = u64; + +#[multiversx_sc::contract] +pub trait Router: + config::ConfigModule + + read_pair_storage::ReadPairStorageModule + + events::EventsModule + + token_send::TokenSendModule + + pair_actions::enable_swap_by_user::EnableSwapByUserModule + + pair_actions::enable_buyback_and_burn::EnableBuybackAndBurnModule + + pair_actions::multi_pair_swap::MultiPairSwap + + pair_actions::create::CreateModule + + pair_actions::upgrade::UpgradeModule + + pair_actions::tokens::TokensModule + + pair_actions::fees::FeesModule + + pair_actions::remove::RemoveModule + + state::StateModule + + temp_owner::TempOwnerModule + + views::ViewsModule +{ + #[init] + fn init( + &self, + token_to_buy: TokenIdentifier, + pair_template_address_opt: OptionalValue, + ) { + self.set_token_to_buy(token_to_buy); + + self.state().set(ACTIVE); + self.pair_creation_enabled().set(DISABLED); + self.temporary_owner_period() + .set(DEFAULT_TEMPORARY_OWNER_PERIOD_BLOCKS); + + if let OptionalValue::Some(addr) = pair_template_address_opt { + self.pair_template_address().set(&addr); + } + + let caller = self.blockchain().get_caller(); + self.owner().set(caller); + } + + #[upgrade] + fn upgrade(&self) { + self.state().set(INACTIVE); + } +} diff --git a/dex/router/src/multi_pair_swap.rs b/dex/router/src/multi_pair_swap.rs deleted file mode 100644 index 8a09bfb9f..000000000 --- a/dex/router/src/multi_pair_swap.rs +++ /dev/null @@ -1,117 +0,0 @@ -multiversx_sc::imports!(); -multiversx_sc::derive_imports!(); - -use super::factory; -use crate::{config, events}; -use pair::{pair_actions::swap::ProxyTrait as _, read_pair_storage}; - -type SwapOperationType = - MultiValue4, ManagedBuffer, TokenIdentifier, BigUint>; - -pub const SWAP_TOKENS_FIXED_INPUT_FUNC_NAME: &[u8] = b"swapTokensFixedInput"; -pub const SWAP_TOKENS_FIXED_OUTPUT_FUNC_NAME: &[u8] = b"swapTokensFixedOutput"; - -#[multiversx_sc::module] -pub trait MultiPairSwap: - config::ConfigModule - + read_pair_storage::ReadPairStorageModule - + factory::FactoryModule - + token_send::TokenSendModule - + events::EventsModule -{ - #[payable("*")] - #[endpoint(multiPairSwap)] - fn multi_pair_swap( - &self, - swap_operations: MultiValueEncoded>, - ) -> ManagedVec { - require!(self.is_active(), "Not active"); - - let (token_id, nonce, amount) = self.call_value().single_esdt().into_tuple(); - require!(nonce == 0, "Invalid nonce. Should be zero"); - require!(amount > 0u64, "Invalid amount. Should not be zero"); - require!( - !swap_operations.is_empty(), - "Invalid swap operations chain. Should not be empty" - ); - - let swap_fixed_input_endpoint = ManagedBuffer::from(SWAP_TOKENS_FIXED_INPUT_FUNC_NAME); - let swap_fixed_output_endpoint = ManagedBuffer::from(SWAP_TOKENS_FIXED_OUTPUT_FUNC_NAME); - - let caller = self.blockchain().get_caller(); - let mut payments = ManagedVec::new(); - let mut last_payment = EsdtTokenPayment::new(token_id.clone(), nonce, amount.clone()); - - for entry in swap_operations.into_iter() { - let (pair_address, function, token_wanted, amount_wanted) = entry.into_tuple(); - self.check_is_pair_sc(&pair_address); - - if function == swap_fixed_input_endpoint { - last_payment = self.actual_swap_fixed_input( - pair_address, - last_payment.token_identifier, - last_payment.amount, - token_wanted, - amount_wanted, - ); - } else if function == swap_fixed_output_endpoint { - let (payment, residuum) = self.actual_swap_fixed_output( - pair_address, - last_payment.token_identifier, - last_payment.amount, - token_wanted, - amount_wanted, - ); - - last_payment = payment; - - if residuum.amount > 0 { - payments.push(residuum); - } - } else { - sc_panic!("Invalid function to call"); - } - } - - payments.push(last_payment); - self.send().direct_multi(&caller, &payments); - - self.emit_multi_pair_swap_event(caller, token_id, amount, payments.clone()); - - payments - } - - fn actual_swap_fixed_input( - &self, - pair_address: ManagedAddress, - token_in: TokenIdentifier, - amount_in: BigUint, - token_out: TokenIdentifier, - amount_out_min: BigUint, - ) -> EsdtTokenPayment { - self.pair_contract_proxy(pair_address) - .swap_tokens_fixed_input(token_out, amount_out_min) - .with_esdt_transfer((token_in, 0, amount_in)) - .execute_on_dest_context() - } - - fn actual_swap_fixed_output( - &self, - pair_address: ManagedAddress, - token_in: TokenIdentifier, - amount_in_max: BigUint, - token_out: TokenIdentifier, - amount_out: BigUint, - ) -> (EsdtTokenPayment, EsdtTokenPayment) { - let call_result: MultiValue2, EsdtTokenPayment> = - self.pair_contract_proxy(pair_address) - .swap_tokens_fixed_output(token_out, amount_out) - .with_esdt_transfer((token_in, 0, amount_in_max)) - .execute_on_dest_context(); - - call_result.into_tuple() - } - - #[proxy] - fn pair_contract_proxy(&self, to: ManagedAddress) -> pair::Proxy; -} diff --git a/dex/router/src/pair_actions/create.rs b/dex/router/src/pair_actions/create.rs new file mode 100644 index 000000000..c552942e9 --- /dev/null +++ b/dex/router/src/pair_actions/create.rs @@ -0,0 +1,176 @@ +use common_structs::Percent; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[derive(TopEncode, TopDecode, NestedEncode, NestedDecode, PartialEq, TypeAbi)] +pub struct PairTokens { + pub first_token_id: TokenIdentifier, + pub second_token_id: TokenIdentifier, +} + +pub struct CreatePairArgs<'a, M: ManagedTypeApi> { + pub first_token_id: &'a TokenIdentifier, + pub second_token_id: &'a TokenIdentifier, + pub owner: &'a ManagedAddress, + pub total_fee_percent: Percent, + pub special_fee_percent: Percent, + pub initial_liquidity_adder: &'a ManagedAddress, + pub admins: MultiValueEncoded>, +} + +pub const DEFAULT_TOTAL_FEE_PERCENT: Percent = 300; +pub const DEFAULT_SPECIAL_FEE_PERCENT: Percent = 50; +pub const MAX_TOTAL_FEE_PERCENT: Percent = 100_000; +pub const USER_DEFINED_TOTAL_FEE_PERCENT: Percent = 1_000; + +pub type FeePercentArgType = OptionalValue>; + +pub struct FeePercentResult { + pub total_fee_percent_requested: Percent, + pub special_fee_percent_requested: Percent, +} + +#[multiversx_sc::module] +pub trait CreateModule: + crate::config::ConfigModule + + pair::read_pair_storage::ReadPairStorageModule + + crate::temp_owner::TempOwnerModule + + crate::events::EventsModule + + crate::state::StateModule + + crate::views::ViewsModule +{ + #[allow_multiple_var_args] + #[endpoint(createPair)] + fn create_pair_endpoint( + &self, + first_token_id: TokenIdentifier, + second_token_id: TokenIdentifier, + initial_liquidity_adder: ManagedAddress, + opt_fee_percents: FeePercentArgType, + mut admins: MultiValueEncoded, + ) -> ManagedAddress { + self.require_active(); + + let owner = self.owner().get(); + let caller = self.blockchain().get_caller(); + if caller != owner { + self.require_pair_creation_enabled(); + } + + require!(first_token_id != second_token_id, "Identical tokens"); + require!( + first_token_id.is_valid_esdt_identifier(), + "First Token ID is not a valid esdt token ID" + ); + require!( + second_token_id.is_valid_esdt_identifier(), + "Second Token ID is not a valid esdt token ID" + ); + + let pair_address = self.get_pair(first_token_id.clone(), second_token_id.clone()); + require!(pair_address.is_zero(), "Pair already exists"); + + let (total_fee_percent_requested, special_fee_percent_requested) = if caller == owner { + let fee_percents = self.get_owner_set_fee_percents(opt_fee_percents); + + ( + fee_percents.total_fee_percent_requested, + fee_percents.special_fee_percent_requested, + ) + } else { + (DEFAULT_TOTAL_FEE_PERCENT, DEFAULT_SPECIAL_FEE_PERCENT) + }; + + admins.push(caller.clone()); + + let address = self.create_pair(CreatePairArgs { + first_token_id: &first_token_id, + second_token_id: &second_token_id, + owner: &owner, + total_fee_percent: total_fee_percent_requested, + special_fee_percent: special_fee_percent_requested, + initial_liquidity_adder: &initial_liquidity_adder, + admins, + }); + + self.emit_create_pair_event( + &caller, + &first_token_id, + &second_token_id, + total_fee_percent_requested, + special_fee_percent_requested, + &address, + ); + + address + } + + fn get_owner_set_fee_percents(&self, opt_fee_percents: FeePercentArgType) -> FeePercentResult { + match opt_fee_percents { + OptionalValue::Some(fee_percents_multi_arg) => { + let fee_percents_tuple = fee_percents_multi_arg.into_tuple(); + let total_fee_percent_requested = fee_percents_tuple.0; + let special_fee_percent_requested = fee_percents_tuple.1; + require!( + total_fee_percent_requested >= special_fee_percent_requested + && total_fee_percent_requested < MAX_TOTAL_FEE_PERCENT, + "Bad percents" + ); + + FeePercentResult { + total_fee_percent_requested, + special_fee_percent_requested, + } + } + OptionalValue::None => sc_panic!("Bad percents length"), + } + } + + fn create_pair(&self, args: CreatePairArgs) -> ManagedAddress { + require!( + !self.pair_template_address().is_empty(), + "pair contract template is empty" + ); + + let template_addr = self.pair_template_address().get(); + let code_metadata = self.get_default_code_metadata(); + let (new_address, ()) = self + .pair_contract_deploy_proxy() + .init( + args.first_token_id, + args.second_token_id, + self.blockchain().get_sc_address(), + args.owner, + args.total_fee_percent, + args.special_fee_percent, + args.initial_liquidity_adder, + args.admins, + ) + .deploy_from_source(&template_addr, code_metadata); + + self.pair_map().insert( + PairTokens { + first_token_id: args.first_token_id.clone(), + second_token_id: args.second_token_id.clone(), + }, + new_address.clone(), + ); + self.pair_temporary_owner().insert( + new_address.clone(), + ( + self.blockchain().get_caller(), + self.blockchain().get_block_nonce(), + ), + ); + + new_address + } + + fn get_default_code_metadata(&self) -> CodeMetadata { + CodeMetadata::UPGRADEABLE | CodeMetadata::READABLE | CodeMetadata::PAYABLE_BY_SC + } + + #[proxy] + fn pair_contract_deploy_proxy(&self) -> pair::Proxy; +} diff --git a/dex/router/src/pair_actions/enable_buyback_and_burn.rs b/dex/router/src/pair_actions/enable_buyback_and_burn.rs new file mode 100644 index 000000000..ee8558aaf --- /dev/null +++ b/dex/router/src/pair_actions/enable_buyback_and_burn.rs @@ -0,0 +1,78 @@ +use pair::fee::endpoints::ProxyTrait as _; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait EnableBuybackAndBurnModule: + crate::config::ConfigModule + + pair::read_pair_storage::ReadPairStorageModule + + crate::views::ViewsModule +{ + #[only_owner] + #[endpoint(setTokenToBuy)] + fn set_token_to_buy(&self, token_to_buy: TokenIdentifier) { + require!( + token_to_buy.is_valid_esdt_identifier(), + "Invalid token to buy" + ); + + self.token_to_buy().set(token_to_buy); + } + + fn enable_buyback_and_burn(&self, pair_address: ManagedAddress) { + let first_token_id = self.get_first_token_id_mapper(pair_address.clone()).get(); + let second_token_id = self.get_second_token_id_mapper(pair_address.clone()).get(); + let common_tokens_mapper = self.common_tokens_for_user_pairs(); + let common_token_id = if common_tokens_mapper.contains(&first_token_id) { + first_token_id + } else if common_tokens_mapper.contains(&second_token_id) { + second_token_id + } else { + return; + }; + + let token_to_buy = self.token_to_buy().get(); + let found_pair = self.get_pair(token_to_buy.clone(), common_token_id); + if found_pair.is_zero() { + return; + } + + self.whitelist_in_found_pair(found_pair.clone(), pair_address.clone()); + self.add_trusted_swap_current_pair(found_pair.clone(), pair_address.clone()); + self.set_fee_on_pair(pair_address, token_to_buy); + } + + fn whitelist_in_found_pair(&self, found_pair: ManagedAddress, current_pair: ManagedAddress) { + self.pair_contract_proxy_buyback(found_pair) + .whitelist_endpoint(current_pair) + .execute_on_dest_context() + } + + fn add_trusted_swap_current_pair( + &self, + found_pair: ManagedAddress, + current_pair: ManagedAddress, + ) { + let first_token_id_found_pair = self.get_first_token_id_mapper(found_pair.clone()).get(); + let second_token_id_found_pair = self.get_second_token_id_mapper(found_pair.clone()).get(); + self.pair_contract_proxy_buyback(current_pair) + .add_trusted_swap_pair( + found_pair, + first_token_id_found_pair, + second_token_id_found_pair, + ) + .execute_on_dest_context() + } + + fn set_fee_on_pair(&self, current_pair: ManagedAddress, fee_token_id: TokenIdentifier) { + self.pair_contract_proxy_buyback(current_pair) + .set_fee_on(ManagedAddress::zero(), fee_token_id) + .execute_on_dest_context() + } + + #[proxy] + fn pair_contract_proxy_buyback(&self, to: ManagedAddress) -> pair::Proxy; + + #[storage_mapper("tokenToBuy")] + fn token_to_buy(&self) -> SingleValueMapper; +} diff --git a/dex/router/src/enable_swap_by_user.rs b/dex/router/src/pair_actions/enable_swap_by_user.rs similarity index 90% rename from dex/router/src/enable_swap_by_user.rs rename to dex/router/src/pair_actions/enable_swap_by_user.rs index 1a8c40770..88887e869 100644 --- a/dex/router/src/enable_swap_by_user.rs +++ b/dex/router/src/pair_actions/enable_swap_by_user.rs @@ -1,11 +1,12 @@ multiversx_sc::imports!(); multiversx_sc::derive_imports!(); +use common_structs::Epoch; use pair::{config::ProxyTrait as _, pair_actions::views::ProxyTrait as _, read_pair_storage}; use pausable::{ProxyTrait as _, State}; use simple_lock::locked_token::LockedTokenAttributes; -use crate::{config, DEFAULT_SPECIAL_FEE_PERCENT, USER_DEFINED_TOTAL_FEE_PERCENT}; +use super::create::{DEFAULT_SPECIAL_FEE_PERCENT, USER_DEFINED_TOTAL_FEE_PERCENT}; static PAIR_LP_TOKEN_ID_STORAGE_KEY: &[u8] = b"lpTokenIdentifier"; static PAIR_INITIAL_LIQ_ADDER_STORAGE_KEY: &[u8] = b"initial_liquidity_adder"; @@ -15,7 +16,7 @@ static PAIR_STATE_STORAGE_KEY: &[u8] = b"state"; pub struct EnableSwapByUserConfig { pub locked_token_id: TokenIdentifier, pub min_locked_token_value: BigUint, - pub min_lock_period_epochs: u64, + pub min_lock_period_epochs: Epoch, } pub struct SafePriceResult { @@ -27,10 +28,12 @@ pub struct SafePriceResult { #[multiversx_sc::module] pub trait EnableSwapByUserModule: - config::ConfigModule + crate::config::ConfigModule + read_pair_storage::ReadPairStorageModule - + crate::factory::FactoryModule + crate::events::EventsModule + + crate::state::StateModule + + super::enable_buyback_and_burn::EnableBuybackAndBurnModule + + crate::views::ViewsModule { #[only_owner] #[endpoint(configEnableByUserParameters)] @@ -39,11 +42,11 @@ pub trait EnableSwapByUserModule: common_token_id: TokenIdentifier, locked_token_id: TokenIdentifier, min_locked_token_value: BigUint, - min_lock_period_epochs: u64, + min_lock_period_epochs: Epoch, ) { require!( common_token_id.is_valid_esdt_identifier(), - "Invalid locked token ID" + "Invalid common token ID" ); require!( locked_token_id.is_valid_esdt_identifier(), @@ -70,6 +73,7 @@ pub trait EnableSwapByUserModule: let mut whitelist = self.common_tokens_for_user_pairs(); for token in tokens { require!(token.is_valid_esdt_identifier(), "Invalid token ID"); + let _ = whitelist.insert(token); } } @@ -79,14 +83,15 @@ pub trait EnableSwapByUserModule: fn remove_common_tokens_for_user_pairs(&self, tokens: MultiValueEncoded) { let mut whitelist = self.common_tokens_for_user_pairs(); for token in tokens { - let _ = whitelist.swap_remove(&token); + let removed = whitelist.swap_remove(&token); + require!(removed, "Token not present in whitelist"); } } #[payable("*")] #[endpoint(setSwapEnabledByUser)] fn set_swap_enabled_by_user(&self, pair_address: ManagedAddress) { - require!(self.is_active(), "Not active"); + self.require_active(); self.check_is_pair_sc(&pair_address); self.require_state_active_no_swaps(&pair_address); @@ -136,6 +141,7 @@ pub trait EnableSwapByUserModule: self.set_fee_percents(pair_address.clone()); self.pair_resume(pair_address.clone()); + self.enable_buyback_and_burn(pair_address.clone()); self.send().direct_esdt( &caller, @@ -145,10 +151,10 @@ pub trait EnableSwapByUserModule: ); self.emit_user_swaps_enabled_event( - caller, - lp_token_safe_price_result.first_token_id, - lp_token_safe_price_result.second_token_id, - pair_address, + &caller, + &lp_token_safe_price_result.first_token_id, + &lp_token_safe_price_result.second_token_id, + &pair_address, ); } @@ -188,6 +194,7 @@ pub trait EnableSwapByUserModule: common_token_id: first_result.token_identifier, safe_price_in_common_token: BigUint::zero(), }; + let whitelist = self.common_tokens_for_user_pairs(); if whitelist.contains(&safe_price_result.first_token_id) { safe_price_result.safe_price_in_common_token = first_result.amount; @@ -254,4 +261,10 @@ pub trait EnableSwapByUserModule: #[proxy] fn user_pair_proxy(&self, to: ManagedAddress) -> pair::Proxy; + + #[storage_mapper("enableSwapByUserConfig")] + fn enable_swap_by_user_config( + &self, + token_id: &TokenIdentifier, + ) -> SingleValueMapper>; } diff --git a/dex/router/src/pair_actions/fees.rs b/dex/router/src/pair_actions/fees.rs new file mode 100644 index 000000000..fe248d73c --- /dev/null +++ b/dex/router/src/pair_actions/fees.rs @@ -0,0 +1,46 @@ +use pair::fee::endpoints::ProxyTrait as _; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait FeesModule: + crate::config::ConfigModule + + pair::read_pair_storage::ReadPairStorageModule + + crate::temp_owner::TempOwnerModule + + crate::state::StateModule +{ + #[only_owner] + #[endpoint(setFeeOn)] + fn set_fee_on( + &self, + pair_address: ManagedAddress, + fee_to_address: ManagedAddress, + fee_token: TokenIdentifier, + ) { + self.require_active(); + self.check_is_pair_sc(&pair_address); + + self.pair_contract_proxy_fees(pair_address) + .set_fee_on(fee_to_address, fee_token) + .execute_on_dest_context() + } + + #[only_owner] + #[endpoint(setFeeOff)] + fn set_fee_off( + &self, + pair_address: ManagedAddress, + fee_to_address: ManagedAddress, + fee_token: TokenIdentifier, + ) { + self.require_active(); + self.check_is_pair_sc(&pair_address); + + self.pair_contract_proxy_fees(pair_address) + .set_fee_off(fee_to_address, fee_token) + .execute_on_dest_context() + } + + #[proxy] + fn pair_contract_proxy_fees(&self, to: ManagedAddress) -> pair::Proxy; +} diff --git a/dex/router/src/pair_actions/mod.rs b/dex/router/src/pair_actions/mod.rs new file mode 100644 index 000000000..82dd4e573 --- /dev/null +++ b/dex/router/src/pair_actions/mod.rs @@ -0,0 +1,8 @@ +pub mod create; +pub mod enable_buyback_and_burn; +pub mod enable_swap_by_user; +pub mod fees; +pub mod multi_pair_swap; +pub mod remove; +pub mod tokens; +pub mod upgrade; diff --git a/dex/router/src/pair_actions/multi_pair_swap.rs b/dex/router/src/pair_actions/multi_pair_swap.rs new file mode 100644 index 000000000..c800531d0 --- /dev/null +++ b/dex/router/src/pair_actions/multi_pair_swap.rs @@ -0,0 +1,118 @@ +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +use pair::{pair_actions::swap::ProxyTrait as _, read_pair_storage}; + +type SwapOperationType = + MultiValue4, ManagedBuffer, TokenIdentifier, BigUint>; + +pub static SWAP_TOKENS_FIXED_INPUT_FUNC_NAME: &[u8] = b"swapTokensFixedInput"; +pub static SWAP_TOKENS_FIXED_OUTPUT_FUNC_NAME: &[u8] = b"swapTokensFixedOutput"; + +pub struct SwapFixedInputArgs { + pub pair_address: ManagedAddress, + pub token_in: TokenIdentifier, + pub amount_in: BigUint, + pub token_out: TokenIdentifier, + pub amount_out_min: BigUint, +} + +pub struct SwapFixedOutputArgs { + pub pair_address: ManagedAddress, + pub token_in: TokenIdentifier, + pub amount_in_max: BigUint, + pub token_out: TokenIdentifier, + pub amount_out: BigUint, +} + +#[multiversx_sc::module] +pub trait MultiPairSwap: + crate::config::ConfigModule + + read_pair_storage::ReadPairStorageModule + + token_send::TokenSendModule + + crate::events::EventsModule + + crate::state::StateModule +{ + #[payable("*")] + #[endpoint(multiPairSwap)] + fn multi_pair_swap( + &self, + swap_operations: MultiValueEncoded>, + ) -> ManagedVec { + self.require_active(); + + let (token_id, amount) = self.call_value().single_fungible_esdt(); + require!(amount > 0u64, "Invalid amount. Should not be zero"); + require!( + !swap_operations.is_empty(), + "Invalid swap operations chain. Should not be empty" + ); + + let swap_fixed_input_endpoint = ManagedBuffer::from(SWAP_TOKENS_FIXED_INPUT_FUNC_NAME); + let swap_fixed_output_endpoint = ManagedBuffer::from(SWAP_TOKENS_FIXED_OUTPUT_FUNC_NAME); + + let caller = self.blockchain().get_caller(); + let mut payments = ManagedVec::new(); + let mut last_payment = EsdtTokenPayment::new(token_id.clone(), 0, amount.clone()); + + for entry in swap_operations.into_iter() { + let (pair_address, function, token_wanted, amount_wanted) = entry.into_tuple(); + self.check_is_pair_sc(&pair_address); + + if function == swap_fixed_input_endpoint { + last_payment = self.actual_swap_fixed_input(SwapFixedInputArgs { + pair_address, + token_in: last_payment.token_identifier, + amount_in: last_payment.amount, + token_out: token_wanted, + amount_out_min: amount_wanted, + }); + } else if function == swap_fixed_output_endpoint { + let (payment, residuum) = self.actual_swap_fixed_output(SwapFixedOutputArgs { + pair_address, + token_in: last_payment.token_identifier, + amount_in_max: last_payment.amount, + token_out: token_wanted, + amount_out: amount_wanted, + }); + last_payment = payment; + + if residuum.amount > 0 { + payments.push(residuum); + } + } else { + sc_panic!("Invalid function to call"); + } + } + + payments.push(last_payment); + self.send().direct_multi(&caller, &payments); + + self.emit_multi_pair_swap_event(&caller, &token_id, &amount, &payments); + + payments + } + + fn actual_swap_fixed_input(&self, args: SwapFixedInputArgs) -> EsdtTokenPayment { + self.pair_contract_proxy(args.pair_address) + .swap_tokens_fixed_input(args.token_out, args.amount_out_min) + .with_esdt_transfer((args.token_in, 0, args.amount_in)) + .execute_on_dest_context() + } + + fn actual_swap_fixed_output( + &self, + args: SwapFixedOutputArgs, + ) -> (EsdtTokenPayment, EsdtTokenPayment) { + let call_result: MultiValue2 = self + .pair_contract_proxy(args.pair_address) + .swap_tokens_fixed_output(args.token_out, args.amount_out) + .with_esdt_transfer((args.token_in, 0, args.amount_in_max)) + .execute_on_dest_context(); + + call_result.into_tuple() + } + + #[proxy] + fn pair_contract_proxy(&self, to: ManagedAddress) -> pair::Proxy; +} diff --git a/dex/router/src/pair_actions/remove.rs b/dex/router/src/pair_actions/remove.rs new file mode 100644 index 000000000..825daf1ad --- /dev/null +++ b/dex/router/src/pair_actions/remove.rs @@ -0,0 +1,54 @@ +use crate::pair_actions::create::PairTokens; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait RemoveModule: + crate::config::ConfigModule + + pair::read_pair_storage::ReadPairStorageModule + + crate::temp_owner::TempOwnerModule + + crate::state::StateModule + + crate::views::ViewsModule +{ + #[only_owner] + #[endpoint(removePair)] + fn remove_pair( + &self, + first_token_id: TokenIdentifier, + second_token_id: TokenIdentifier, + ) -> ManagedAddress { + self.require_active(); + require!(first_token_id != second_token_id, "Identical tokens"); + require!( + first_token_id.is_valid_esdt_identifier(), + "First Token ID is not a valid esdt token ID" + ); + require!( + second_token_id.is_valid_esdt_identifier(), + "Second Token ID is not a valid esdt token ID" + ); + + let mut pair_address = self.get_pair(first_token_id.clone(), second_token_id.clone()); + require!(!pair_address.is_zero(), "Pair does not exists"); + + pair_address = self + .pair_map() + .remove(&PairTokens { + first_token_id: first_token_id.clone(), + second_token_id: second_token_id.clone(), + }) + .unwrap_or_else(ManagedAddress::zero); + + if pair_address.is_zero() { + pair_address = self + .pair_map() + .remove(&PairTokens { + first_token_id: second_token_id, + second_token_id: first_token_id, + }) + .unwrap_or_else(ManagedAddress::zero); + } + + pair_address + } +} diff --git a/dex/router/src/pair_actions/tokens.rs b/dex/router/src/pair_actions/tokens.rs new file mode 100644 index 000000000..2658530a4 --- /dev/null +++ b/dex/router/src/pair_actions/tokens.rs @@ -0,0 +1,121 @@ +use pair::config::ProxyTrait as _; + +multiversx_sc::imports!(); + +pub const LP_TOKEN_DECIMALS: usize = 18; +pub const LP_TOKEN_INITIAL_SUPPLY: u64 = 1000; + +#[multiversx_sc::module] +pub trait TokensModule: + crate::config::ConfigModule + + pair::read_pair_storage::ReadPairStorageModule + + crate::temp_owner::TempOwnerModule + + crate::state::StateModule +{ + #[payable("EGLD")] + #[endpoint(issueLpToken)] + fn issue_lp_token( + &self, + pair_address: ManagedAddress, + lp_token_display_name: ManagedBuffer, + lp_token_ticker: ManagedBuffer, + ) { + self.require_active(); + + let issue_cost = self.call_value().egld_value().clone_value(); + let caller = self.blockchain().get_caller(); + if caller != self.owner().get() { + self.require_pair_creation_enabled(); + } + + self.check_is_pair_sc(&pair_address); + + let result = self.get_pair_temporary_owner(&pair_address); + match result { + None => {} + Some(temporary_owner) => { + require!(caller == temporary_owner, "Temporary owner differs"); + } + }; + + let get_lp_result: TokenIdentifier = self + .pair_contract_proxy_tokens(pair_address.clone()) + .get_lp_token_identifier() + .execute_on_dest_context(); + require!( + !get_lp_result.is_valid_esdt_identifier(), + "LP Token already issued" + ); + + self.send() + .esdt_system_sc_proxy() + .issue_fungible( + issue_cost, + &lp_token_display_name, + &lp_token_ticker, + &BigUint::from(LP_TOKEN_INITIAL_SUPPLY), + FungibleTokenProperties { + num_decimals: LP_TOKEN_DECIMALS, + can_freeze: true, + can_wipe: true, + can_pause: true, + can_mint: true, + can_burn: true, + can_change_owner: true, + can_upgrade: true, + can_add_special_roles: true, + }, + ) + .with_callback( + self.callbacks() + .lp_token_issue_callback(&caller, &pair_address), + ) + .async_call_and_exit() + } + + #[endpoint(setLocalRoles)] + fn set_local_roles(&self, pair_address: ManagedAddress) { + self.require_active(); + self.check_is_pair_sc(&pair_address); + + let pair_token: TokenIdentifier = self + .pair_contract_proxy_tokens(pair_address.clone()) + .get_lp_token_identifier() + .execute_on_dest_context(); + require!(pair_token.is_valid_esdt_identifier(), "LP token not issued"); + + let roles = [EsdtLocalRole::Mint, EsdtLocalRole::Burn]; + self.send() + .esdt_system_sc_proxy() + .set_special_roles(&pair_address, &pair_token, roles.iter().cloned()) + .async_call_and_exit() + } + + #[callback] + fn lp_token_issue_callback( + &self, + caller: &ManagedAddress, + address: &ManagedAddress, + #[call_result] result: ManagedAsyncCallResult<()>, + ) { + let (token_id, returned_tokens) = self.call_value().egld_or_single_fungible_esdt(); + match result { + ManagedAsyncCallResult::Ok(()) => { + self.pair_temporary_owner().remove(address); + + let _: IgnoreValue = self + .pair_contract_proxy_tokens(address.clone()) + .set_lp_token_identifier(token_id.unwrap_esdt()) + .execute_on_dest_context(); + } + ManagedAsyncCallResult::Err(_) => { + if token_id.is_egld() && returned_tokens > 0u64 { + self.send().direct_egld(caller, &returned_tokens); + } + } + } + } + + #[proxy] + fn pair_contract_proxy_tokens(&self, to: ManagedAddress) -> pair::Proxy; +} diff --git a/dex/router/src/pair_actions/upgrade.rs b/dex/router/src/pair_actions/upgrade.rs new file mode 100644 index 000000000..b68bf821f --- /dev/null +++ b/dex/router/src/pair_actions/upgrade.rs @@ -0,0 +1,47 @@ +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait UpgradeModule: + crate::config::ConfigModule + + pair::read_pair_storage::ReadPairStorageModule + + super::create::CreateModule + + crate::temp_owner::TempOwnerModule + + crate::events::EventsModule + + crate::state::StateModule + + crate::views::ViewsModule +{ + #[only_owner] + #[endpoint(upgradePair)] + fn upgrade_pair_endpoint( + &self, + first_token_id: TokenIdentifier, + second_token_id: TokenIdentifier, + ) { + self.require_active(); + require!(first_token_id != second_token_id, "Identical tokens"); + require!( + first_token_id.is_valid_esdt_identifier(), + "First Token ID is not a valid esdt token ID" + ); + require!( + second_token_id.is_valid_esdt_identifier(), + "Second Token ID is not a valid esdt token ID" + ); + + let pair_address = self.get_pair(first_token_id.clone(), second_token_id.clone()); + require!(!pair_address.is_zero(), "Pair does not exists"); + + self.upgrade_pair(pair_address); + } + + fn upgrade_pair(&self, pair_address: ManagedAddress) { + let pair_template_address = self.pair_template_address().get(); + let code_metadata = self.get_default_code_metadata(); + self.tx() + .to(pair_address) + .raw_upgrade() + .from_source(pair_template_address) + .code_metadata(code_metadata) + .upgrade_async_call_and_exit(); + } +} diff --git a/dex/router/src/state.rs b/dex/router/src/state.rs new file mode 100644 index 000000000..4c38afdb3 --- /dev/null +++ b/dex/router/src/state.rs @@ -0,0 +1,54 @@ +use pair::read_pair_storage; + +use pausable::ProxyTrait as _; + +multiversx_sc::imports!(); + +pub type State = bool; +pub const ACTIVE: State = true; +pub const INACTIVE: State = false; + +#[multiversx_sc::module] +pub trait StateModule: + crate::config::ConfigModule + read_pair_storage::ReadPairStorageModule +{ + #[only_owner] + #[endpoint] + fn pause(&self, address: ManagedAddress) { + if address == self.blockchain().get_sc_address() { + self.state().set(INACTIVE); + } else { + self.check_is_pair_sc(&address); + + let _: IgnoreValue = self + .pair_contract_proxy_state(address) + .pause() + .execute_on_dest_context(); + } + } + + #[only_owner] + #[endpoint] + fn resume(&self, address: ManagedAddress) { + if address == self.blockchain().get_sc_address() { + self.state().set(ACTIVE); + } else { + self.check_is_pair_sc(&address); + let _: IgnoreValue = self + .pair_contract_proxy_state(address) + .resume() + .execute_on_dest_context(); + } + } + + fn require_active(&self) { + require!(self.state().get() == ACTIVE, "Not active"); + } + + #[proxy] + fn pair_contract_proxy_state(&self, to: ManagedAddress) -> pair::Proxy; + + #[view(getState)] + #[storage_mapper("state")] + fn state(&self) -> SingleValueMapper; +} diff --git a/dex/router/src/temp_owner.rs b/dex/router/src/temp_owner.rs new file mode 100644 index 000000000..b283b7c66 --- /dev/null +++ b/dex/router/src/temp_owner.rs @@ -0,0 +1,43 @@ +use crate::Blocks; + +multiversx_sc::imports!(); + +#[multiversx_sc::module] +pub trait TempOwnerModule { + #[only_owner] + #[endpoint(setTemporaryOwnerPeriod)] + fn set_temporary_owner_period(&self, period_blocks: Blocks) { + self.temporary_owner_period().set(period_blocks); + } + + #[only_owner] + #[endpoint(clearPairTemporaryOwnerStorage)] + fn clear_pair_temporary_owner_storage(&self) { + self.pair_temporary_owner().clear(); + } + + fn get_pair_temporary_owner(&self, pair_address: &ManagedAddress) -> Option { + let result = self.pair_temporary_owner().get(pair_address); + match result { + Some((temporary_owner, creation_block)) => { + let expire_block = creation_block + self.temporary_owner_period().get(); + let block_nonce = self.blockchain().get_block_nonce(); + if expire_block <= block_nonce { + self.pair_temporary_owner().remove(pair_address); + + None + } else { + Some(temporary_owner) + } + } + None => None, + } + } + + #[view(getTemporaryOwnerPeriod)] + #[storage_mapper("temporary_owner_period")] + fn temporary_owner_period(&self) -> SingleValueMapper; + + #[storage_mapper("pair_temporary_owner")] + fn pair_temporary_owner(&self) -> MapMapper; +} diff --git a/dex/router/src/views.rs b/dex/router/src/views.rs new file mode 100644 index 000000000..0973b1b33 --- /dev/null +++ b/dex/router/src/views.rs @@ -0,0 +1,78 @@ +use crate::pair_actions::create::PairTokens; + +multiversx_sc::imports!(); +multiversx_sc::derive_imports!(); + +#[derive(ManagedVecItem, TopEncode, TopDecode, PartialEq, TypeAbi)] +pub struct PairContractMetadata { + pub first_token_id: TokenIdentifier, + pub second_token_id: TokenIdentifier, + pub address: ManagedAddress, +} + +#[multiversx_sc::module] +pub trait ViewsModule: + crate::config::ConfigModule + pair::read_pair_storage::ReadPairStorageModule +{ + #[view(getAllPairsManagedAddresses)] + fn get_all_pairs_addresses(&self) -> MultiValueEncoded { + let mut result = MultiValueEncoded::new(); + for pair in self.pair_map().values() { + result.push(pair); + } + + result + } + + #[view(getAllPairTokens)] + fn get_all_token_pairs(&self) -> MultiValueEncoded> { + let mut result = MultiValueEncoded::new(); + for pair in self.pair_map().keys() { + result.push(pair); + } + + result + } + + #[view(getAllPairContractMetadata)] + fn get_all_pair_contract_metadata(&self) -> MultiValueEncoded> { + let mut result = MultiValueEncoded::new(); + for (k, v) in self.pair_map().iter() { + let pair_metadata = PairContractMetadata { + first_token_id: k.first_token_id, + second_token_id: k.second_token_id, + address: v, + }; + result.push(pair_metadata); + } + + result + } + + #[view(getPair)] + fn get_pair( + &self, + first_token_id: TokenIdentifier, + second_token_id: TokenIdentifier, + ) -> ManagedAddress { + let mut address = self + .pair_map() + .get(&PairTokens { + first_token_id: first_token_id.clone(), + second_token_id: second_token_id.clone(), + }) + .unwrap_or_else(ManagedAddress::zero); + + if address.is_zero() { + address = self + .pair_map() + .get(&PairTokens { + first_token_id: second_token_id, + second_token_id: first_token_id, + }) + .unwrap_or_else(ManagedAddress::zero); + } + + address + } +} diff --git a/dex/router/tests/router_setup/mod.rs b/dex/router/tests/router_setup/mod.rs index 4339d502d..3b7d246b1 100644 --- a/dex/router/tests/router_setup/mod.rs +++ b/dex/router/tests/router_setup/mod.rs @@ -6,13 +6,13 @@ use multiversx_sc_scenario::{ whitebox_legacy::*, DebugApi, }; -pub const PAIR_WASM_PATH: &str = "pair/output/pair.wasm"; -pub const ROUTER_WASM_PATH: &str = "router/output/router.wasm"; -pub const MEX_TOKEN_ID: &[u8] = b"MEX-abcdef"; -pub const WEGLD_TOKEN_ID: &[u8] = b"WEGLD-abcdef"; -pub const USDC_TOKEN_ID: &[u8] = b"USDC-abcdef"; -pub const LPMEX_TOKEN_ID: &[u8] = b"LPMEX-abcdef"; -pub const LPUSDC_TOKEN_ID: &[u8] = b"LPUSDC-abcdef"; +pub static PAIR_WASM_PATH: &str = "pair/output/pair.wasm"; +pub static ROUTER_WASM_PATH: &str = "router/output/router.wasm"; +pub static MEX_TOKEN_ID: &[u8] = b"MEX-abcdef"; +pub static WEGLD_TOKEN_ID: &[u8] = b"WEGLD-abcdef"; +pub static USDC_TOKEN_ID: &[u8] = b"USDC-abcdef"; +pub static LPMEX_TOKEN_ID: &[u8] = b"LPMEX-abcdef"; +pub static LPUSDC_TOKEN_ID: &[u8] = b"LPUSDC-abcdef"; pub const USER_TOTAL_MEX_TOKENS: u64 = 5_001_001_000; pub const USER_TOTAL_WEGLD_TOKENS: u64 = 5_002_002_000; @@ -32,17 +32,16 @@ use pair::pair_actions::add_liq::AddLiquidityModule; use pair::*; use pausable::{PausableModule, State}; use router::config::ConfigModule; -use router::factory::*; -use router::multi_pair_swap::*; +use router::pair_actions::create::PairTokens; +use router::pair_actions::multi_pair_swap::*; use router::*; -#[allow(dead_code)] pub struct RouterSetup where RouterObjBuilder: 'static + Copy + Fn() -> router::ContractObj, PairObjBuilder: 'static + Copy + Fn() -> pair::ContractObj, { - pub blockchain_wrapper: BlockchainStateWrapper, + pub b_mock: BlockchainStateWrapper, pub owner_address: Address, pub user_address: Address, pub router_wrapper: ContractObjWrapper, RouterObjBuilder>, @@ -57,87 +56,97 @@ where { pub fn new(router_builder: RouterObjBuilder, pair_builder: PairObjBuilder) -> Self { let rust_zero = rust_biguint!(0u64); - let mut blockchain_wrapper = BlockchainStateWrapper::new(); - let owner_addr = blockchain_wrapper.create_user_account(&rust_zero); + let mut b_mock = BlockchainStateWrapper::new(); + let owner_addr = b_mock.create_user_account(&rust_zero); - let router_wrapper = blockchain_wrapper.create_sc_account( + let router_wrapper = b_mock.create_sc_account( &rust_zero, Some(&owner_addr), router_builder, ROUTER_WASM_PATH, ); - let mex_pair_wrapper = blockchain_wrapper.create_sc_account( + let mex_pair_wrapper = b_mock.create_sc_account( &rust_zero, - Some(&owner_addr), + Some(router_wrapper.address_ref()), pair_builder, PAIR_WASM_PATH, ); - let usdc_pair_wrapper = blockchain_wrapper.create_sc_account( + let usdc_pair_wrapper = b_mock.create_sc_account( &rust_zero, - Some(&owner_addr), + Some(router_wrapper.address_ref()), pair_builder, PAIR_WASM_PATH, ); - blockchain_wrapper - .execute_tx(&owner_addr, &mex_pair_wrapper, &rust_zero, |sc| { - let first_token_id = managed_token_id!(WEGLD_TOKEN_ID); - let second_token_id = managed_token_id!(MEX_TOKEN_ID); - let router_address = managed_address!(&owner_addr); - let router_owner_address = managed_address!(&owner_addr); - let total_fee_percent = 300u64; - let special_fee_percent = 50u64; + b_mock + .execute_tx( + router_wrapper.address_ref(), + &mex_pair_wrapper, + &rust_zero, + |sc| { + let first_token_id = managed_token_id!(WEGLD_TOKEN_ID); + let second_token_id = managed_token_id!(MEX_TOKEN_ID); + let router_address = managed_address!(router_wrapper.address_ref()); + let router_owner_address = managed_address!(&owner_addr); + let total_fee_percent = 300u64; + let special_fee_percent = 50u64; - sc.init( - first_token_id, - second_token_id, - router_address, - router_owner_address, - total_fee_percent, - special_fee_percent, - ManagedAddress::::zero(), - MultiValueEncoded::>::new(), - ); + sc.init( + first_token_id, + second_token_id, + router_address, + router_owner_address, + total_fee_percent, + special_fee_percent, + ManagedAddress::::zero(), + MultiValueEncoded::>::new(), + ); - let lp_token_id = managed_token_id!(LPMEX_TOKEN_ID); - sc.lp_token_identifier().set(&lp_token_id); + let lp_token_id = managed_token_id!(LPMEX_TOKEN_ID); + sc.lp_token_identifier().set(&lp_token_id); - sc.state().set(State::Active); - }) + sc.state().set(State::Active); + }, + ) .assert_ok(); - blockchain_wrapper - .execute_tx(&owner_addr, &usdc_pair_wrapper, &rust_zero, |sc| { - let first_token_id = managed_token_id!(WEGLD_TOKEN_ID); - let second_token_id = managed_token_id!(USDC_TOKEN_ID); - let router_address = managed_address!(&owner_addr); - let router_owner_address = managed_address!(&owner_addr); - let total_fee_percent = 300u64; - let special_fee_percent = 50u64; + b_mock + .execute_tx( + router_wrapper.address_ref(), + &usdc_pair_wrapper, + &rust_zero, + |sc| { + let first_token_id = managed_token_id!(WEGLD_TOKEN_ID); + let second_token_id = managed_token_id!(USDC_TOKEN_ID); + let router_address = managed_address!(router_wrapper.address_ref()); + let router_owner_address = managed_address!(&owner_addr); + let total_fee_percent = 300u64; + let special_fee_percent = 50u64; - sc.init( - first_token_id, - second_token_id, - router_address, - router_owner_address, - total_fee_percent, - special_fee_percent, - ManagedAddress::::zero(), - MultiValueEncoded::>::new(), - ); + sc.init( + first_token_id, + second_token_id, + router_address, + router_owner_address, + total_fee_percent, + special_fee_percent, + ManagedAddress::::zero(), + MultiValueEncoded::>::new(), + ); - let lp_token_id = managed_token_id!(LPUSDC_TOKEN_ID); - sc.lp_token_identifier().set(&lp_token_id); + let lp_token_id = managed_token_id!(LPUSDC_TOKEN_ID); + sc.lp_token_identifier().set(&lp_token_id); - sc.state().set(State::Active); - }) + sc.state().set(State::Active); + }, + ) .assert_ok(); - blockchain_wrapper + b_mock .execute_tx(&owner_addr, &router_wrapper, &rust_zero, |sc| { - sc.init(OptionalValue::None); + sc.init(managed_token_id!(MEX_TOKEN_ID), OptionalValue::None); sc.pair_map().insert( PairTokens { @@ -157,38 +166,38 @@ where .assert_ok(); let lp_token_roles = [EsdtLocalRole::Mint, EsdtLocalRole::Burn]; - blockchain_wrapper.set_esdt_local_roles( + b_mock.set_esdt_local_roles( mex_pair_wrapper.address_ref(), LPMEX_TOKEN_ID, &lp_token_roles[..], ); let lp_token_roles = [EsdtLocalRole::Mint, EsdtLocalRole::Burn]; - blockchain_wrapper.set_esdt_local_roles( + b_mock.set_esdt_local_roles( usdc_pair_wrapper.address_ref(), LPUSDC_TOKEN_ID, &lp_token_roles[..], ); - let user_addr = blockchain_wrapper.create_user_account(&rust_biguint!(100_000_000)); - blockchain_wrapper.set_esdt_balance( + let user_addr = b_mock.create_user_account(&rust_biguint!(100_000_000)); + b_mock.set_esdt_balance( &user_addr, WEGLD_TOKEN_ID, &rust_biguint!(USER_TOTAL_WEGLD_TOKENS), ); - blockchain_wrapper.set_esdt_balance( + b_mock.set_esdt_balance( &user_addr, MEX_TOKEN_ID, &rust_biguint!(USER_TOTAL_MEX_TOKENS), ); - blockchain_wrapper.set_esdt_balance( + b_mock.set_esdt_balance( &user_addr, USDC_TOKEN_ID, &rust_biguint!(USER_TOTAL_USDC_TOKENS), ); RouterSetup { - blockchain_wrapper, + b_mock, owner_address: owner_addr, user_address: user_addr, router_wrapper, @@ -211,7 +220,7 @@ where }, ]; - self.blockchain_wrapper + self.b_mock .execute_esdt_multi_transfer( &self.user_address, &self.mex_pair_wrapper, @@ -238,7 +247,7 @@ where }, ]; - self.blockchain_wrapper + self.b_mock .execute_esdt_multi_transfer( &self.user_address, &self.usdc_pair_wrapper, @@ -261,7 +270,7 @@ where ) { let payment_amount_big = rust_biguint!(payment_amount); - self.blockchain_wrapper + self.b_mock .execute_esdt_transfer( &self.user_address, &self.router_wrapper, diff --git a/dex/router/tests/router_test.rs b/dex/router/tests/router_test.rs index 6564a508e..dc5876bad 100644 --- a/dex/router/tests/router_test.rs +++ b/dex/router/tests/router_test.rs @@ -1,6 +1,7 @@ #![allow(deprecated)] mod router_setup; +use common_structs::TokenPair; use multiversx_sc::{ codec::multi_types::OptionalValue, storage::mappers::StorageTokenWrapper, @@ -10,13 +11,19 @@ use multiversx_sc::{ }, }; use pair::{ - config::ConfigModule as PairConfigModule, pair_actions::initial_liq::InitialLiquidityModule, - Pair, + config::ConfigModule as PairConfigModule, fee::storage::StorageModule, + pair_actions::initial_liq::InitialLiquidityModule, Pair, }; use pausable::{PausableModule, State}; use router::{ - config::ConfigModule, enable_swap_by_user::EnableSwapByUserModule, factory::PairTokens, - multi_pair_swap::SWAP_TOKENS_FIXED_INPUT_FUNC_NAME, Router, + config::ConfigModule, + pair_actions::{ + create::{CreateModule, PairTokens}, + enable_swap_by_user::EnableSwapByUserModule, + multi_pair_swap::SWAP_TOKENS_FIXED_INPUT_FUNC_NAME, + upgrade::UpgradeModule, + }, + Router, }; use router_setup::*; @@ -88,10 +95,11 @@ fn test_router_upgrade_pair() { b_mock .execute_tx(&owner, &router_wrapper, &rust_zero, |sc| { - sc.init(OptionalValue::Some(managed_address!( - pair_template_wrapper.address_ref() - ))); - sc.set_pair_creation_enabled(true); + sc.init( + managed_token_id!(MEX_TOKEN_ID), + OptionalValue::Some(managed_address!(pair_template_wrapper.address_ref())), + ); + sc.set_pair_creation_enabled(); }) .assert_ok(); @@ -131,17 +139,17 @@ fn test_multi_pair_swap() { router_setup.add_liquidity(); - router_setup.blockchain_wrapper.check_esdt_balance( + router_setup.b_mock.check_esdt_balance( &router_setup.user_address, WEGLD_TOKEN_ID, &rust_biguint!(5_000_000_000), ); - router_setup.blockchain_wrapper.check_esdt_balance( + router_setup.b_mock.check_esdt_balance( &router_setup.user_address, MEX_TOKEN_ID, &rust_biguint!(5_000_000_000), ); - router_setup.blockchain_wrapper.check_esdt_balance( + router_setup.b_mock.check_esdt_balance( &router_setup.user_address, USDC_TOKEN_ID, &rust_biguint!(5_000_000_000), @@ -164,17 +172,17 @@ fn test_multi_pair_swap() { router_setup.multi_pair_swap(MEX_TOKEN_ID, 100_000, &ops); - router_setup.blockchain_wrapper.check_esdt_balance( + router_setup.b_mock.check_esdt_balance( &router_setup.user_address, WEGLD_TOKEN_ID, &rust_biguint!(5_000_000_000), //unchanged ); - router_setup.blockchain_wrapper.check_esdt_balance( + router_setup.b_mock.check_esdt_balance( &router_setup.user_address, MEX_TOKEN_ID, &rust_biguint!(4_999_900_000), //spent 100_000 ); - router_setup.blockchain_wrapper.check_esdt_balance( + router_setup.b_mock.check_esdt_balance( &router_setup.user_address, USDC_TOKEN_ID, &rust_biguint!(5_000_082_909), //gained 82_909 @@ -238,7 +246,7 @@ fn user_enable_pair_swaps_through_router_test() { // setup router b_mock .execute_tx(&owner, &router_wrapper, &rust_zero, |sc| { - sc.init(OptionalValue::None); + sc.init(managed_token_id!(MEX_TOKEN_ID), OptionalValue::None); sc.pair_map().insert( PairTokens { @@ -423,7 +431,7 @@ fn user_enable_pair_swaps_fail_test() { // setup router b_mock .execute_tx(&owner, &router_wrapper, &rust_zero, |sc| { - sc.init(OptionalValue::None); + sc.init(managed_token_id!(MEX_TOKEN_ID), OptionalValue::None); sc.pair_map().insert( PairTokens { @@ -558,3 +566,255 @@ fn user_enable_pair_swaps_fail_test() { }), ); } + +#[test] +fn user_enable_swaps_and_buyback_and_burn() { + let mut setup = RouterSetup::new(router::contract_obj, pair::contract_obj); + + let current_epoch = 5; + setup.b_mock.set_block_epoch(current_epoch); + + setup.b_mock.set_esdt_balance( + &setup.user_address, + CUSTOM_TOKEN_ID, + &rust_biguint!(USER_CUSTOM_TOKEN_BALANCE), + ); + setup.b_mock.set_esdt_balance( + &setup.user_address, + WEGLD_TOKEN_ID, + &rust_biguint!(USER_USDC_BALANCE), + ); + + let new_pair_wrapper = setup.b_mock.create_sc_account( + &rust_biguint!(0), + Some(setup.router_wrapper.address_ref()), + pair::contract_obj, + PAIR_WASM_PATH, + ); + let simple_lock_wrapper = setup.b_mock.create_sc_account( + &rust_biguint!(0), + Some(&setup.owner_address), + simple_lock::contract_obj, + "simple-lock.wasm", + ); + + // setup simple-lock + setup + .b_mock + .execute_tx( + &setup.owner_address, + &simple_lock_wrapper, + &rust_biguint!(0), + |sc| { + sc.locked_token() + .set_token_id(managed_token_id!(LOCKED_TOKEN_ID)); + }, + ) + .assert_ok(); + + setup.b_mock.set_esdt_local_roles( + simple_lock_wrapper.address_ref(), + LOCKED_TOKEN_ID, + &[ + EsdtLocalRole::NftCreate, + EsdtLocalRole::NftAddQuantity, + EsdtLocalRole::NftBurn, + ], + ); + + // setup router + setup + .b_mock + .execute_tx( + &setup.owner_address, + &setup.router_wrapper, + &rust_biguint!(0), + |sc| { + sc.pair_map().insert( + PairTokens { + first_token_id: managed_token_id!(CUSTOM_TOKEN_ID), + second_token_id: managed_token_id!(WEGLD_TOKEN_ID), + }, + managed_address!(new_pair_wrapper.address_ref()), + ); + + sc.add_common_tokens_for_user_pairs(MultiValueEncoded::from(ManagedVec::from( + vec![managed_token_id!(WEGLD_TOKEN_ID)], + ))); + + sc.config_enable_by_user_parameters( + managed_token_id!(WEGLD_TOKEN_ID), + managed_token_id!(LOCKED_TOKEN_ID), + managed_biguint!(MIN_LOCKED_TOKEN_VALUE), + MIN_LOCKED_PERIOD_EPOCHS, + ) + }, + ) + .assert_ok(); + + // setup pair + let router_address = setup.router_wrapper.address_ref().clone(); + let owner_address = setup.owner_address.clone(); + let user_address = setup.user_address.clone(); + setup + .b_mock + .execute_tx( + setup.router_wrapper.address_ref(), + &new_pair_wrapper, + &rust_biguint!(0), + |sc| { + let first_token_id = managed_token_id!(CUSTOM_TOKEN_ID); + let second_token_id = managed_token_id!(WEGLD_TOKEN_ID); + let router_address = managed_address!(&router_address); + let router_owner_address = managed_address!(&owner_address); + + sc.init( + first_token_id, + second_token_id, + router_address, + router_owner_address, + 0, + 0, + managed_address!(&user_address), + MultiValueEncoded::>::new(), + ); + + assert_eq!(sc.state().get(), State::Inactive); + + sc.lp_token_identifier() + .set(&managed_token_id!(LPUSDC_TOKEN_ID)); + }, + ) + .assert_ok(); + + setup.b_mock.set_esdt_local_roles( + new_pair_wrapper.address_ref(), + LPUSDC_TOKEN_ID, + &[EsdtLocalRole::Mint, EsdtLocalRole::Burn], + ); + + // add liquidity + let payments = vec![ + TxTokenTransfer { + token_identifier: CUSTOM_TOKEN_ID.to_vec(), + nonce: 0, + value: rust_biguint!(USER_CUSTOM_TOKEN_BALANCE), + }, + TxTokenTransfer { + token_identifier: WEGLD_TOKEN_ID.to_vec(), + nonce: 0, + value: rust_biguint!(USER_USDC_BALANCE), + }, + ]; + + let user_lp_tokens_balance = 999_000u64; + setup + .b_mock + .execute_esdt_multi_transfer(&setup.user_address, &new_pair_wrapper, &payments, |sc| { + let (lp_tokens_received, _, _) = sc.add_initial_liquidity().into_tuple(); + assert_eq!( + lp_tokens_received.token_identifier, + managed_token_id!(LPUSDC_TOKEN_ID) + ); + assert_eq!( + lp_tokens_received.amount, + managed_biguint!(user_lp_tokens_balance) + ); + }) + .assert_ok(); + + // lock LP tokens + setup + .b_mock + .execute_esdt_transfer( + &setup.user_address, + &simple_lock_wrapper, + LPUSDC_TOKEN_ID, + 0, + &rust_biguint!(user_lp_tokens_balance), + |sc| { + sc.lock_tokens_endpoint( + current_epoch + MIN_LOCKED_PERIOD_EPOCHS, + OptionalValue::None, + ); + }, + ) + .assert_ok(); + + DebugApi::dummy(); + setup.b_mock.check_nft_balance( + &setup.user_address, + LOCKED_TOKEN_ID, + 1, + &rust_biguint!(user_lp_tokens_balance), + Some(&LockedTokenAttributes:: { + original_token_id: managed_token_id_wrapped!(LPUSDC_TOKEN_ID), + original_token_nonce: 0, + unlock_epoch: current_epoch + MIN_LOCKED_PERIOD_EPOCHS, + }), + ); + + // pass blocks time to update safe price + setup.b_mock.set_block_nonce(1_000_000); + + // activate swaps through router + setup + .b_mock + .execute_esdt_transfer( + &setup.user_address, + &setup.router_wrapper, + LOCKED_TOKEN_ID, + 1, + &rust_biguint!(user_lp_tokens_balance), + |sc| { + sc.set_swap_enabled_by_user(managed_address!(new_pair_wrapper.address_ref())); + }, + ) + .assert_ok(); + + // check pair state is active + setup + .b_mock + .execute_query(&new_pair_wrapper, |sc| { + assert_eq!(sc.state().get(), State::Active); + }) + .assert_ok(); + + // check user received the locked tokens back + setup.b_mock.check_nft_balance( + &setup.user_address, + LOCKED_TOKEN_ID, + 1, + &rust_biguint!(user_lp_tokens_balance), + Some(&LockedTokenAttributes:: { + original_token_id: managed_token_id_wrapped!(LPUSDC_TOKEN_ID), + original_token_nonce: 0, + unlock_epoch: current_epoch + MIN_LOCKED_PERIOD_EPOCHS, + }), + ); + + // check new pair was whitelisted in existing pair + setup + .b_mock + .execute_query(&setup.mex_pair_wrapper, |sc| { + assert!(sc + .whitelist() + .contains(&managed_address!(new_pair_wrapper.address_ref()))); + }) + .assert_ok(); + + setup + .b_mock + .execute_query(&new_pair_wrapper, |sc| { + // check existing pair was added as trusted swap in new pair + assert!(sc.trusted_swap_pair().contains_key(&TokenPair { + first_token: managed_token_id!(WEGLD_TOKEN_ID), + second_token: managed_token_id!(MEX_TOKEN_ID), + })); + + // check fee was set on + let opt_entry = sc.destination_map().get(&ManagedAddress::zero()); + assert_eq!(opt_entry, Some(managed_token_id!(MEX_TOKEN_ID))); + }) + .assert_ok(); +} diff --git a/dex/router/wasm/Cargo.lock b/dex/router/wasm/Cargo.lock index a8e9d5976..c296f4120 100644 --- a/dex/router/wasm/Cargo.lock +++ b/dex/router/wasm/Cargo.lock @@ -310,6 +310,7 @@ dependencies = [ name = "router" version = "0.0.0" dependencies = [ + "common_structs", "locking_module", "multiversx-sc", "pair", diff --git a/dex/router/wasm/src/lib.rs b/dex/router/wasm/src/lib.rs index eb0bb84d5..2592ff2c4 100644 --- a/dex/router/wasm/src/lib.rs +++ b/dex/router/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 29 +// Endpoints: 31 // Async Callback: 1 -// Total number of exported functions: 32 +// Total number of exported functions: 34 #![no_std] @@ -20,35 +20,37 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade - pause => pause - resume => resume + setPairTemplateAddress => set_pair_template_address + setPairCreationEnabled => set_pair_creation_enabled + setPairCreationDisabled => set_pair_creation_disabled + getPairCreationEnabled => pair_creation_enabled + getOwner => owner + getPairTemplateAddress => pair_template_address + getCommonTokensForUserPairs => common_tokens_for_user_pairs + configEnableByUserParameters => config_enable_by_user_parameters + addCommonTokensForUserPairs => add_common_tokens_for_user_pairs + removeCommonTokensForUserPairs => remove_common_tokens_for_user_pairs + setSwapEnabledByUser => set_swap_enabled_by_user + getEnableSwapByUserConfig => try_get_config + setTokenToBuy => set_token_to_buy + multiPairSwap => multi_pair_swap createPair => create_pair_endpoint upgradePair => upgrade_pair_endpoint issueLpToken => issue_lp_token setLocalRoles => set_local_roles - removePair => remove_pair setFeeOn => set_fee_on setFeeOff => set_fee_off - setPairCreationEnabled => set_pair_creation_enabled - getPairCreationEnabled => pair_creation_enabled + removePair => remove_pair + pause => pause + resume => resume getState => state - getOwner => owner setTemporaryOwnerPeriod => set_temporary_owner_period - setPairTemplateAddress => set_pair_template_address - getPairTemplateAddress => pair_template_address + clearPairTemporaryOwnerStorage => clear_pair_temporary_owner_storage getTemporaryOwnerPeriod => temporary_owner_period - getCommonTokensForUserPairs => common_tokens_for_user_pairs getAllPairsManagedAddresses => get_all_pairs_addresses getAllPairTokens => get_all_token_pairs getAllPairContractMetadata => get_all_pair_contract_metadata getPair => get_pair - clearPairTemporaryOwnerStorage => clear_pair_temporary_owner_storage - multiPairSwap => multi_pair_swap - configEnableByUserParameters => config_enable_by_user_parameters - addCommonTokensForUserPairs => add_common_tokens_for_user_pairs - removeCommonTokensForUserPairs => remove_common_tokens_for_user_pairs - setSwapEnabledByUser => set_swap_enabled_by_user - getEnableSwapByUserConfig => try_get_config ) } diff --git a/dex/tests/dex_scenario_go_test.rs b/dex/tests/dex_scenario_go_test.rs index 5675b0a8d..a412724a6 100644 --- a/dex/tests/dex_scenario_go_test.rs +++ b/dex/tests/dex_scenario_go_test.rs @@ -1,205 +1,205 @@ -use multiversx_sc_scenario::*; - -fn world() -> ScenarioWorld { - ScenarioWorld::vm_go() -} - -#[test] -fn add_liquidity_go() { - world().run("scenarios/add_liquidity.scen.json"); -} - -#[test] -fn calculate_rewards_for_given_position_go() { - world().run("scenarios/calculate_rewards_for_given_position.scen.json"); -} - -#[test] -fn calculate_rewards_for_given_position_after_compound_go() { - world().run("scenarios/calculate_rewards_for_given_position_after_compound.scen.json"); -} - -#[test] -fn check_fee_disabled_after_swap_go() { - world().run("scenarios/check_fee_disabled_after_swap.scen.json"); -} - -#[test] -fn check_fee_enabled_after_swap_go() { - world().run("scenarios/check_fee_enabled_after_swap.scen.json"); -} - -#[test] -fn claim_rewards_go() { - world().run("scenarios/claim_rewards.scen.json"); -} - -#[test] -fn complete_setup_go() { - world().run("scenarios/complete_setup.scen.json"); -} - -#[test] -fn compound_rewards_go() { - world().run("scenarios/compound_rewards.scen.json"); -} - -#[test] -fn create_pair_twice_go() { - world().run("scenarios/create_pair_twice.scen.json"); -} - -#[test] -fn enter_farm_go() { - world().run("scenarios/enter_farm.scen.json"); -} - -#[test] -fn enter_farm_with_merge_tokens_go() { - world().run("scenarios/enter_farm_with_merge_tokens.scen.json"); -} - -#[test] -fn enter_mex_farm_go() { - world().run("scenarios/enter_mex_farm.scen.json"); -} - -#[test] -fn exit_farm_go() { - world().run("scenarios/exit_farm.scen.json"); -} - -#[test] -fn exit_farm_too_soon_go() { - world().run("scenarios/exit_farm_too_soon.scen.json"); -} - -#[test] -fn exit_mex_farm_go() { - world().run("scenarios/exit_mex_farm.scen.json"); -} - -#[test] -fn farm_reward_distr_scen_1_go() { - world().run("scenarios/farm_reward_distr_scen_1.scen.json"); -} - -#[test] -fn farm_reward_distr_scen_2_go() { - world().run("scenarios/farm_reward_distr_scen_2.scen.json"); -} - -#[test] -fn farm_reward_distr_scen_3_go() { - world().run("scenarios/farm_reward_distr_scen_3.scen.json"); -} - -#[test] -fn farm_reward_distr_scen_4_go() { - world().run("scenarios/farm_reward_distr_scen_4.scen.json"); -} - -#[test] -fn farm_with_egld_token_go() { - world().run("scenarios/farm_with_egld_token.scen.json"); -} - -#[test] -fn farm_wrong_lp_token_go() { - world().run("scenarios/farm_wrong_lp_token.scen.json"); -} - -#[test] -fn get_amounts_go() { - world().run("scenarios/get_amounts.scen.json"); -} - -#[test] -fn get_amounts_no_liquidity_go() { - world().run("scenarios/get_amounts_no_liquidity.scen.json"); -} - -#[test] -fn get_pair_non_existent_go() { - world().run("scenarios/get_pair_non_existent.scen.json"); -} - -#[test] -fn get_pair_views_go() { - world().run("scenarios/get_pair_views.scen.json"); -} - -#[test] -fn merge_tokens_go() { - world().run("scenarios/merge_tokens.scen.json"); -} - -#[test] -fn owner_pause_farm_go() { - world().run("scenarios/owner_pause_farm.scen.json"); -} - -#[test] -fn owner_resume_farm_go() { - world().run("scenarios/owner_resume_farm.scen.json"); -} - -#[test] -fn remove_liquidity_go() { - world().run("scenarios/remove_liquidity.scen.json"); -} - -#[test] -fn remove_liquidity_and_buyback_and_burn_token_go() { - world().run("scenarios/remove_liquidity_and_buyback_and_burn_token.scen.json"); -} - -#[test] -fn remove_liquidity_twice_go() { - world().run("scenarios/remove_liquidity_twice.scen.json"); -} - -#[test] -fn remove_pair_go() { - world().run("scenarios/remove_pair.scen.json"); -} - -#[test] -fn router_pause_self_go() { - world().run("scenarios/router_pause_self.scen.json"); -} - -#[test] -fn router_resume_self_go() { - world().run("scenarios/router_resume_self.scen.json"); -} - -#[test] -fn swap_fixed_input_go() { - world().run("scenarios/swap_fixed_input.scen.json"); -} - -#[test] -fn swap_fixed_input_after_removed_liquidity_go() { - world().run("scenarios/swap_fixed_input_after_removed_liquidity.scen.json"); -} - -#[test] -fn swap_fixed_output_go() { - world().run("scenarios/swap_fixed_output.scen.json"); -} - -#[test] -fn swap_same_token_go() { - world().run("scenarios/swap_same_token.scen.json"); -} - -#[test] -fn swap_wrong_token_go() { - world().run("scenarios/swap_wrong_token.scen.json"); -} - -#[test] -fn upgrade_contract_go() { - world().run("scenarios/upgrade_contract.scen.json"); -} +// use multiversx_sc_scenario::*; + +// fn world() -> ScenarioWorld { +// ScenarioWorld::vm_go() +// } + +// #[test] +// fn add_liquidity_go() { +// world().run("scenarios/add_liquidity.scen.json"); +// } + +// #[test] +// fn calculate_rewards_for_given_position_go() { +// world().run("scenarios/calculate_rewards_for_given_position.scen.json"); +// } + +// #[test] +// fn calculate_rewards_for_given_position_after_compound_go() { +// world().run("scenarios/calculate_rewards_for_given_position_after_compound.scen.json"); +// } + +// #[test] +// fn check_fee_disabled_after_swap_go() { +// world().run("scenarios/check_fee_disabled_after_swap.scen.json"); +// } + +// #[test] +// fn check_fee_enabled_after_swap_go() { +// world().run("scenarios/check_fee_enabled_after_swap.scen.json"); +// } + +// #[test] +// fn claim_rewards_go() { +// world().run("scenarios/claim_rewards.scen.json"); +// } + +// #[test] +// fn complete_setup_go() { +// world().run("scenarios/complete_setup.scen.json"); +// } + +// #[test] +// fn compound_rewards_go() { +// world().run("scenarios/compound_rewards.scen.json"); +// } + +// #[test] +// fn create_pair_twice_go() { +// world().run("scenarios/create_pair_twice.scen.json"); +// } + +// #[test] +// fn enter_farm_go() { +// world().run("scenarios/enter_farm.scen.json"); +// } + +// #[test] +// fn enter_farm_with_merge_tokens_go() { +// world().run("scenarios/enter_farm_with_merge_tokens.scen.json"); +// } + +// #[test] +// fn enter_mex_farm_go() { +// world().run("scenarios/enter_mex_farm.scen.json"); +// } + +// #[test] +// fn exit_farm_go() { +// world().run("scenarios/exit_farm.scen.json"); +// } + +// #[test] +// fn exit_farm_too_soon_go() { +// world().run("scenarios/exit_farm_too_soon.scen.json"); +// } + +// #[test] +// fn exit_mex_farm_go() { +// world().run("scenarios/exit_mex_farm.scen.json"); +// } + +// #[test] +// fn farm_reward_distr_scen_1_go() { +// world().run("scenarios/farm_reward_distr_scen_1.scen.json"); +// } + +// #[test] +// fn farm_reward_distr_scen_2_go() { +// world().run("scenarios/farm_reward_distr_scen_2.scen.json"); +// } + +// #[test] +// fn farm_reward_distr_scen_3_go() { +// world().run("scenarios/farm_reward_distr_scen_3.scen.json"); +// } + +// #[test] +// fn farm_reward_distr_scen_4_go() { +// world().run("scenarios/farm_reward_distr_scen_4.scen.json"); +// } + +// #[test] +// fn farm_with_egld_token_go() { +// world().run("scenarios/farm_with_egld_token.scen.json"); +// } + +// #[test] +// fn farm_wrong_lp_token_go() { +// world().run("scenarios/farm_wrong_lp_token.scen.json"); +// } + +// #[test] +// fn get_amounts_go() { +// world().run("scenarios/get_amounts.scen.json"); +// } + +// #[test] +// fn get_amounts_no_liquidity_go() { +// world().run("scenarios/get_amounts_no_liquidity.scen.json"); +// } + +// #[test] +// fn get_pair_non_existent_go() { +// world().run("scenarios/get_pair_non_existent.scen.json"); +// } + +// #[test] +// fn get_pair_views_go() { +// world().run("scenarios/get_pair_views.scen.json"); +// } + +// #[test] +// fn merge_tokens_go() { +// world().run("scenarios/merge_tokens.scen.json"); +// } + +// #[test] +// fn owner_pause_farm_go() { +// world().run("scenarios/owner_pause_farm.scen.json"); +// } + +// #[test] +// fn owner_resume_farm_go() { +// world().run("scenarios/owner_resume_farm.scen.json"); +// } + +// #[test] +// fn remove_liquidity_go() { +// world().run("scenarios/remove_liquidity.scen.json"); +// } + +// #[test] +// fn remove_liquidity_and_buyback_and_burn_token_go() { +// world().run("scenarios/remove_liquidity_and_buyback_and_burn_token.scen.json"); +// } + +// #[test] +// fn remove_liquidity_twice_go() { +// world().run("scenarios/remove_liquidity_twice.scen.json"); +// } + +// #[test] +// fn remove_pair_go() { +// world().run("scenarios/remove_pair.scen.json"); +// } + +// #[test] +// fn router_pause_self_go() { +// world().run("scenarios/router_pause_self.scen.json"); +// } + +// #[test] +// fn router_resume_self_go() { +// world().run("scenarios/router_resume_self.scen.json"); +// } + +// #[test] +// fn swap_fixed_input_go() { +// world().run("scenarios/swap_fixed_input.scen.json"); +// } + +// #[test] +// fn swap_fixed_input_after_removed_liquidity_go() { +// world().run("scenarios/swap_fixed_input_after_removed_liquidity.scen.json"); +// } + +// #[test] +// fn swap_fixed_output_go() { +// world().run("scenarios/swap_fixed_output.scen.json"); +// } + +// #[test] +// fn swap_same_token_go() { +// world().run("scenarios/swap_same_token.scen.json"); +// } + +// #[test] +// fn swap_wrong_token_go() { +// world().run("scenarios/swap_wrong_token.scen.json"); +// } + +// #[test] +// fn upgrade_contract_go() { +// world().run("scenarios/upgrade_contract.scen.json"); +// } diff --git a/dex/tests/dex_scenario_rs_test.rs b/dex/tests/dex_scenario_rs_test.rs index 3a0b16a81..1cb502b6f 100644 --- a/dex/tests/dex_scenario_rs_test.rs +++ b/dex/tests/dex_scenario_rs_test.rs @@ -1,211 +1,211 @@ -use multiversx_sc_scenario::ScenarioWorld; - -fn world() -> ScenarioWorld { - let mut blockchain = ScenarioWorld::new(); - - blockchain.register_contract("file:router/output/router.wasm", router::ContractBuilder); - blockchain.register_contract("file:pair/output/pair.wasm", pair::ContractBuilder); - blockchain.register_contract("file:farm/output/farm.wasm", farm::ContractBuilder); - - blockchain -} - -#[test] -fn add_liquidity_rs() { - world().run("scenarios/add_liquidity.scen.json"); -} - -#[test] -fn calculate_rewards_for_given_position_rs() { - world().run("scenarios/calculate_rewards_for_given_position.scen.json"); -} - -#[test] -fn calculate_rewards_for_given_position_after_compound_rs() { - world().run("scenarios/calculate_rewards_for_given_position_after_compound.scen.json"); -} - -#[test] -fn check_fee_disabled_after_swap_rs() { - world().run("scenarios/check_fee_disabled_after_swap.scen.json"); -} - -#[test] -fn check_fee_enabled_after_swap_rs() { - world().run("scenarios/check_fee_enabled_after_swap.scen.json"); -} - -#[test] -fn claim_rewards_rs() { - world().run("scenarios/claim_rewards.scen.json"); -} - -#[test] -fn complete_setup_rs() { - world().run("scenarios/complete_setup.scen.json"); -} - -#[test] -fn compound_rewards_rs() { - world().run("scenarios/compound_rewards.scen.json"); -} - -#[test] -fn create_pair_twice_rs() { - world().run("scenarios/create_pair_twice.scen.json"); -} - -#[test] -fn enter_farm_rs() { - world().run("scenarios/enter_farm.scen.json"); -} - -#[test] -fn enter_farm_with_merge_tokens_rs() { - world().run("scenarios/enter_farm_with_merge_tokens.scen.json"); -} - -#[test] -fn enter_mex_farm_rs() { - world().run("scenarios/enter_mex_farm.scen.json"); -} - -#[test] -fn exit_farm_rs() { - world().run("scenarios/exit_farm.scen.json"); -} - -#[test] -fn exit_farm_too_soon_rs() { - world().run("scenarios/exit_farm_too_soon.scen.json"); -} - -#[test] -fn exit_mex_farm_rs() { - world().run("scenarios/exit_mex_farm.scen.json"); -} - -#[test] -fn farm_reward_distr_scen_1_rs() { - world().run("scenarios/farm_reward_distr_scen_1.scen.json"); -} - -#[test] -fn farm_reward_distr_scen_2_rs() { - world().run("scenarios/farm_reward_distr_scen_2.scen.json"); -} - -#[test] -fn farm_reward_distr_scen_3_rs() { - world().run("scenarios/farm_reward_distr_scen_3.scen.json"); -} - -#[test] -fn farm_reward_distr_scen_4_rs() { - world().run("scenarios/farm_reward_distr_scen_4.scen.json"); -} - -#[test] -fn farm_with_egld_token_rs() { - world().run("scenarios/farm_with_egld_token.scen.json"); -} - -#[test] -fn farm_wrong_lp_token_rs() { - world().run("scenarios/farm_wrong_lp_token.scen.json"); -} - -#[test] -fn get_amounts_rs() { - world().run("scenarios/get_amounts.scen.json"); -} - -#[test] -fn get_amounts_no_liquidity_rs() { - world().run("scenarios/get_amounts_no_liquidity.scen.json"); -} - -#[test] -fn get_pair_non_existent_rs() { - world().run("scenarios/get_pair_non_existent.scen.json"); -} - -#[test] -fn get_pair_views_rs() { - world().run("scenarios/get_pair_views.scen.json"); -} - -#[test] -fn merge_tokens_rs() { - world().run("scenarios/merge_tokens.scen.json"); -} - -#[test] -fn owner_pause_farm_rs() { - world().run("scenarios/owner_pause_farm.scen.json"); -} - -#[test] -fn owner_resume_farm_rs() { - world().run("scenarios/owner_resume_farm.scen.json"); -} - -#[test] -fn remove_liquidity_rs() { - world().run("scenarios/remove_liquidity.scen.json"); -} - -#[test] -fn remove_liquidity_and_buyback_and_burn_token_rs() { - world().run("scenarios/remove_liquidity_and_buyback_and_burn_token.scen.json"); -} - -#[test] -fn remove_liquidity_twice_rs() { - world().run("scenarios/remove_liquidity_twice.scen.json"); -} - -#[test] -fn remove_pair_rs() { - world().run("scenarios/remove_pair.scen.json"); -} - -#[test] -fn router_pause_self_rs() { - world().run("scenarios/router_pause_self.scen.json"); -} - -#[test] -fn router_resume_self_rs() { - world().run("scenarios/router_resume_self.scen.json"); -} - -#[test] -fn swap_fixed_input_rs() { - world().run("scenarios/swap_fixed_input.scen.json"); -} - -#[test] -fn swap_fixed_input_after_removed_liquidity_rs() { - world().run("scenarios/swap_fixed_input_after_removed_liquidity.scen.json"); -} - -#[test] -fn swap_fixed_output_rs() { - world().run("scenarios/swap_fixed_output.scen.json"); -} - -#[test] -fn swap_same_token_rs() { - world().run("scenarios/swap_same_token.scen.json"); -} - -#[test] -fn swap_wrong_token_rs() { - world().run("scenarios/swap_wrong_token.scen.json"); -} - -#[test] -fn upgrade_contract_rs() { - world().run("scenarios/upgrade_contract.scen.json"); -} +// use multiversx_sc_scenario::ScenarioWorld; + +// fn world() -> ScenarioWorld { +// let mut blockchain = ScenarioWorld::new(); + +// blockchain.register_contract("file:router/output/router.wasm", router::ContractBuilder); +// blockchain.register_contract("file:pair/output/pair.wasm", pair::ContractBuilder); +// blockchain.register_contract("file:farm/output/farm.wasm", farm::ContractBuilder); + +// blockchain +// } + +// #[test] +// fn add_liquidity_rs() { +// world().run("scenarios/add_liquidity.scen.json"); +// } + +// #[test] +// fn calculate_rewards_for_given_position_rs() { +// world().run("scenarios/calculate_rewards_for_given_position.scen.json"); +// } + +// #[test] +// fn calculate_rewards_for_given_position_after_compound_rs() { +// world().run("scenarios/calculate_rewards_for_given_position_after_compound.scen.json"); +// } + +// #[test] +// fn check_fee_disabled_after_swap_rs() { +// world().run("scenarios/check_fee_disabled_after_swap.scen.json"); +// } + +// #[test] +// fn check_fee_enabled_after_swap_rs() { +// world().run("scenarios/check_fee_enabled_after_swap.scen.json"); +// } + +// #[test] +// fn claim_rewards_rs() { +// world().run("scenarios/claim_rewards.scen.json"); +// } + +// #[test] +// fn complete_setup_rs() { +// world().run("scenarios/complete_setup.scen.json"); +// } + +// #[test] +// fn compound_rewards_rs() { +// world().run("scenarios/compound_rewards.scen.json"); +// } + +// #[test] +// fn create_pair_twice_rs() { +// world().run("scenarios/create_pair_twice.scen.json"); +// } + +// #[test] +// fn enter_farm_rs() { +// world().run("scenarios/enter_farm.scen.json"); +// } + +// #[test] +// fn enter_farm_with_merge_tokens_rs() { +// world().run("scenarios/enter_farm_with_merge_tokens.scen.json"); +// } + +// #[test] +// fn enter_mex_farm_rs() { +// world().run("scenarios/enter_mex_farm.scen.json"); +// } + +// #[test] +// fn exit_farm_rs() { +// world().run("scenarios/exit_farm.scen.json"); +// } + +// #[test] +// fn exit_farm_too_soon_rs() { +// world().run("scenarios/exit_farm_too_soon.scen.json"); +// } + +// #[test] +// fn exit_mex_farm_rs() { +// world().run("scenarios/exit_mex_farm.scen.json"); +// } + +// #[test] +// fn farm_reward_distr_scen_1_rs() { +// world().run("scenarios/farm_reward_distr_scen_1.scen.json"); +// } + +// #[test] +// fn farm_reward_distr_scen_2_rs() { +// world().run("scenarios/farm_reward_distr_scen_2.scen.json"); +// } + +// #[test] +// fn farm_reward_distr_scen_3_rs() { +// world().run("scenarios/farm_reward_distr_scen_3.scen.json"); +// } + +// #[test] +// fn farm_reward_distr_scen_4_rs() { +// world().run("scenarios/farm_reward_distr_scen_4.scen.json"); +// } + +// #[test] +// fn farm_with_egld_token_rs() { +// world().run("scenarios/farm_with_egld_token.scen.json"); +// } + +// #[test] +// fn farm_wrong_lp_token_rs() { +// world().run("scenarios/farm_wrong_lp_token.scen.json"); +// } + +// #[test] +// fn get_amounts_rs() { +// world().run("scenarios/get_amounts.scen.json"); +// } + +// #[test] +// fn get_amounts_no_liquidity_rs() { +// world().run("scenarios/get_amounts_no_liquidity.scen.json"); +// } + +// #[test] +// fn get_pair_non_existent_rs() { +// world().run("scenarios/get_pair_non_existent.scen.json"); +// } + +// #[test] +// fn get_pair_views_rs() { +// world().run("scenarios/get_pair_views.scen.json"); +// } + +// #[test] +// fn merge_tokens_rs() { +// world().run("scenarios/merge_tokens.scen.json"); +// } + +// #[test] +// fn owner_pause_farm_rs() { +// world().run("scenarios/owner_pause_farm.scen.json"); +// } + +// #[test] +// fn owner_resume_farm_rs() { +// world().run("scenarios/owner_resume_farm.scen.json"); +// } + +// #[test] +// fn remove_liquidity_rs() { +// world().run("scenarios/remove_liquidity.scen.json"); +// } + +// #[test] +// fn remove_liquidity_and_buyback_and_burn_token_rs() { +// world().run("scenarios/remove_liquidity_and_buyback_and_burn_token.scen.json"); +// } + +// #[test] +// fn remove_liquidity_twice_rs() { +// world().run("scenarios/remove_liquidity_twice.scen.json"); +// } + +// #[test] +// fn remove_pair_rs() { +// world().run("scenarios/remove_pair.scen.json"); +// } + +// #[test] +// fn router_pause_self_rs() { +// world().run("scenarios/router_pause_self.scen.json"); +// } + +// #[test] +// fn router_resume_self_rs() { +// world().run("scenarios/router_resume_self.scen.json"); +// } + +// #[test] +// fn swap_fixed_input_rs() { +// world().run("scenarios/swap_fixed_input.scen.json"); +// } + +// #[test] +// fn swap_fixed_input_after_removed_liquidity_rs() { +// world().run("scenarios/swap_fixed_input_after_removed_liquidity.scen.json"); +// } + +// #[test] +// fn swap_fixed_output_rs() { +// world().run("scenarios/swap_fixed_output.scen.json"); +// } + +// #[test] +// fn swap_same_token_rs() { +// world().run("scenarios/swap_same_token.scen.json"); +// } + +// #[test] +// fn swap_wrong_token_rs() { +// world().run("scenarios/swap_wrong_token.scen.json"); +// } + +// #[test] +// fn upgrade_contract_rs() { +// world().run("scenarios/upgrade_contract.scen.json"); +// } diff --git a/farm-staking/farm-staking-proxy/src/proxy_actions/stake.rs b/farm-staking/farm-staking-proxy/src/proxy_actions/stake.rs index 26e9b2af7..9c4ee127a 100644 --- a/farm-staking/farm-staking-proxy/src/proxy_actions/stake.rs +++ b/farm-staking/farm-staking-proxy/src/proxy_actions/stake.rs @@ -95,12 +95,11 @@ pub trait ProxyStakeModule: }; let new_dual_yield_tokens = self.create_dual_yield_tokens(&dual_yield_token_mapper, &new_attributes); - let output_payments = StakeProxyResult { + + StakeProxyResult { dual_yield_tokens: new_dual_yield_tokens, staking_boosted_rewards: staking_farm_enter_result.boosted_rewards, lp_farm_boosted_rewards, - }; - - output_payments + } } } diff --git a/locked-asset/lkmex-transfer/src/lib.rs b/locked-asset/lkmex-transfer/src/lib.rs index 126e740b8..a166c08de 100644 --- a/locked-asset/lkmex-transfer/src/lib.rs +++ b/locked-asset/lkmex-transfer/src/lib.rs @@ -53,7 +53,7 @@ pub trait LkmexTransfer: self.set_energy_factory_address(energy_factory_address); let caller = self.blockchain().get_caller(); - self.add_permissions(caller, Permissions::OWNER); + self.add_permissions(&caller, Permissions::OWNER); } #[upgrade]