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/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..105765bca 100644 --- a/dex/router/src/config.rs +++ b/dex/router/src/config.rs @@ -1,13 +1,21 @@ 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; #[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, enabled: bool) { + self.pair_creation_enabled().set(enabled); } fn check_is_pair_sc(&self, pair_address: &ManagedAddress) { @@ -40,26 +48,10 @@ pub trait ConfigModule: read_pair_storage::ReadPairStorageModule { #[storage_mapper("pair_creation_enabled")] fn pair_creation_enabled(&self) -> SingleValueMapper; - #[view(getState)] - #[storage_mapper("state")] - fn state(&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 +59,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..a06899e6b 100644 --- a/dex/router/src/events.rs +++ b/dex/router/src/events.rs @@ -1,3 +1,7 @@ +use common_structs::{Epoch, Percent}; + +use crate::Blocks; + multiversx_sc::imports!(); multiversx_sc::derive_imports!(); @@ -6,11 +10,11 @@ pub struct CreatePairEvent { caller: ManagedAddress, first_token_id: TokenIdentifier, second_token_id: TokenIdentifier, - total_fee_percent: u64, - special_fee_percent: u64, + total_fee_percent: Percent, + special_fee_percent: Percent, pair_address: ManagedAddress, - block: u64, - epoch: u64, + block: Blocks, + epoch: Epoch, timestamp: u64, } @@ -30,8 +34,8 @@ pub struct MultiPairSwapEvent { token_out: TokenIdentifier, amount_out: BigUint, payments_out: ManagedVec>, - block: u64, - epoch: u64, + block: Blocks, + epoch: Epoch, timestamp: u64, } @@ -42,8 +46,8 @@ pub trait EventsModule { caller: ManagedAddress, first_token_id: TokenIdentifier, second_token_id: TokenIdentifier, - total_fee_percent: u64, - special_fee_percent: u64, + total_fee_percent: Percent, + special_fee_percent: Percent, pair_address: ManagedAddress, ) { let epoch = self.blockchain().get_block_epoch(); @@ -131,7 +135,7 @@ pub trait EventsModule { #[indexed] first_token_id: TokenIdentifier, #[indexed] second_token_id: TokenIdentifier, #[indexed] caller: ManagedAddress, - #[indexed] epoch: u64, + #[indexed] epoch: Epoch, swap_event: CreatePairEvent, ); @@ -141,7 +145,7 @@ pub trait EventsModule { #[indexed] first_token_id: TokenIdentifier, #[indexed] second_token_id: TokenIdentifier, #[indexed] caller: ManagedAddress, - #[indexed] epoch: u64, + #[indexed] epoch: Epoch, swap_enabled_event: UserPairSwapEnabledEvent, ); @@ -153,7 +157,7 @@ pub trait EventsModule { #[indexed] amount_in: BigUint, #[indexed] token_out: TokenIdentifier, #[indexed] amount_out: BigUint, - #[indexed] epoch: u64, + #[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..aeba7cc14 --- /dev/null +++ b/dex/router/src/lib.rs @@ -0,0 +1,56 @@ +#![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 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::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, pair_template_address_opt: OptionalValue) { + self.state().set(ACTIVE); + self.pair_creation_enabled().set(false); + + 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); + } + + self.owner().set(&self.blockchain().get_caller()); + } + + #[upgrade] + fn upgrade(&self) { + self.state().set(INACTIVE); + } +} diff --git a/dex/router/src/pair_actions/create.rs b/dex/router/src/pair_actions/create.rs new file mode 100644 index 000000000..61893f003 --- /dev/null +++ b/dex/router/src/pair_actions/create.rs @@ -0,0 +1,159 @@ +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; + +#[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: OptionalValue>, + mut admins: MultiValueEncoded, + ) -> ManagedAddress { + self.require_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 { + match opt_fee_percents { + OptionalValue::Some(fee_percents_multi_arg) => { + 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" + ); + } + OptionalValue::None => sc_panic!("Bad percents length"), + } + } + + 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.clone(), + ); + + address + } + + 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/enable_swap_by_user.rs b/dex/router/src/pair_actions/enable_swap_by_user.rs similarity index 94% rename from dex/router/src/enable_swap_by_user.rs rename to dex/router/src/pair_actions/enable_swap_by_user.rs index 1a8c40770..b9c1ed713 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,10 @@ 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 { #[only_owner] #[endpoint(configEnableByUserParameters)] @@ -39,11 +40,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 +71,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); } } @@ -86,7 +88,7 @@ pub trait EnableSwapByUserModule: #[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); @@ -254,4 +256,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..d301b80c9 --- /dev/null +++ b/dex/router/src/pair_actions/fees.rs @@ -0,0 +1,48 @@ +use pair::fee::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); + + let _: IgnoreValue = self + .pair_contract_proxy_fees(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, + ) { + self.require_active(); + self.check_is_pair_sc(&pair_address); + + let _: IgnoreValue = self + .pair_contract_proxy_fees(pair_address) + .set_fee_on(false, 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..2ef1ff893 --- /dev/null +++ b/dex/router/src/pair_actions/mod.rs @@ -0,0 +1,7 @@ +pub mod create; +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/multi_pair_swap.rs b/dex/router/src/pair_actions/multi_pair_swap.rs similarity index 51% rename from dex/router/src/multi_pair_swap.rs rename to dex/router/src/pair_actions/multi_pair_swap.rs index 8a09bfb9f..2d7368217 100644 --- a/dex/router/src/multi_pair_swap.rs +++ b/dex/router/src/pair_actions/multi_pair_swap.rs @@ -1,23 +1,37 @@ 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"; +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: - config::ConfigModule + crate::config::ConfigModule + read_pair_storage::ReadPairStorageModule - + factory::FactoryModule + token_send::TokenSendModule - + events::EventsModule + + crate::events::EventsModule + + crate::state::StateModule { #[payable("*")] #[endpoint(multiPairSwap)] @@ -25,10 +39,9 @@ pub trait MultiPairSwap: &self, swap_operations: MultiValueEncoded>, ) -> ManagedVec { - require!(self.is_active(), "Not active"); + self.require_active(); - let (token_id, nonce, amount) = self.call_value().single_esdt().into_tuple(); - require!(nonce == 0, "Invalid nonce. Should be zero"); + let (token_id, amount) = self.call_value().single_fungible_esdt(); require!(amount > 0u64, "Invalid amount. Should not be zero"); require!( !swap_operations.is_empty(), @@ -40,29 +53,28 @@ pub trait MultiPairSwap: let caller = self.blockchain().get_caller(); let mut payments = ManagedVec::new(); - let mut last_payment = EsdtTokenPayment::new(token_id.clone(), nonce, amount.clone()); + 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( + last_payment = self.actual_swap_fixed_input(SwapFixedInputArgs { pair_address, - last_payment.token_identifier, - last_payment.amount, - token_wanted, - amount_wanted, - ); + 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( + let (payment, residuum) = self.actual_swap_fixed_output(SwapFixedOutputArgs { pair_address, - last_payment.token_identifier, - last_payment.amount, - token_wanted, - amount_wanted, - ); - + 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 { @@ -81,33 +93,22 @@ pub trait MultiPairSwap: 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)) + 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, - 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(); + 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() } diff --git a/dex/router/src/pair_actions/remove.rs b/dex/router/src/pair_actions/remove.rs new file mode 100644 index 000000000..ecd58bd76 --- /dev/null +++ b/dex/router/src/pair_actions/remove.rs @@ -0,0 +1,53 @@ +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..8e0f79c00 --- /dev/null +++ b/dex/router/src/pair_actions/tokens.rs @@ -0,0 +1,123 @@ +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() { + 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 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..96ac67fa4 --- /dev/null +++ b/dex/router/src/pair_actions/upgrade.rs @@ -0,0 +1,46 @@ +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..f8d9ca2b5 --- /dev/null +++ b/dex/router/src/temp_owner.rs @@ -0,0 +1,45 @@ +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) -> usize { + let size = self.pair_temporary_owner().len(); + self.pair_temporary_owner().clear(); + + size + } + + 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, + } + } + + #[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..02b372f71 100644 --- a/dex/router/tests/router_setup/mod.rs +++ b/dex/router/tests/router_setup/mod.rs @@ -32,8 +32,8 @@ 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)] diff --git a/dex/router/tests/router_test.rs b/dex/router/tests/router_test.rs index 6564a508e..1008b007b 100644 --- a/dex/router/tests/router_test.rs +++ b/dex/router/tests/router_test.rs @@ -15,8 +15,14 @@ use 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::*; 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..9f5f7ed7f 100644 --- a/dex/router/wasm/src/lib.rs +++ b/dex/router/wasm/src/lib.rs @@ -20,35 +20,35 @@ multiversx_sc_wasm_adapter::endpoints! { ( init => init upgrade => upgrade - pause => pause - resume => resume + setPairTemplateAddress => set_pair_template_address + setPairCreationEnabled => set_pair_creation_enabled + 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 + 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 ) }