Skip to content

Commit

Permalink
Merge pull request #734 from multiversx/router-enable-swaps-by-user-map
Browse files Browse the repository at this point in the history
enable swaps token map functionality
  • Loading branch information
claudiulataretu authored Jun 19, 2023
2 parents 30817f6 + 831b869 commit c664ab6
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 22 deletions.
55 changes: 35 additions & 20 deletions dex/router/src/enable_swap_by_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct EnableSwapByUserConfig<M: ManagedTypeApi> {
pub struct SafePriceResult<M: ManagedTypeApi> {
pub first_token_id: TokenIdentifier<M>,
pub second_token_id: TokenIdentifier<M>,
pub common_token_id: TokenIdentifier<M>,
pub safe_price_in_common_token: BigUint<M>,
}

Expand All @@ -32,24 +33,32 @@ pub trait EnableSwapByUserModule:
#[endpoint(configEnableByUserParameters)]
fn config_enable_by_user_parameters(
&self,
common_token_id: TokenIdentifier,
locked_token_id: TokenIdentifier,
min_locked_token_value: BigUint,
min_lock_period_epochs: u64,
common_tokens_for_user_pairs: MultiValueEncoded<TokenIdentifier>,
) {
require!(
common_token_id.is_valid_esdt_identifier(),
"Invalid locked token ID"
);
require!(
locked_token_id.is_valid_esdt_identifier(),
"Invalid locked token ID"
);

self.enable_swap_by_user_config()
let whitelist = self.common_tokens_for_user_pairs();
require!(
whitelist.contains(&common_token_id),
"Common token not whitelisted"
);

self.enable_swap_by_user_config(&common_token_id)
.set(&EnableSwapByUserConfig {
locked_token_id,
min_locked_token_value,
min_lock_period_epochs,
});

self.add_common_tokens_for_user_pairs(common_tokens_for_user_pairs);
}

#[only_owner]
Expand Down Expand Up @@ -78,11 +87,6 @@ pub trait EnableSwapByUserModule:
self.require_state_active_no_swaps(&pair_address);

let payment = self.call_value().single_esdt();
let config = self.try_get_config();
require!(
payment.token_identifier == config.locked_token_id,
"Invalid payment token"
);

let own_sc_address = self.blockchain().get_sc_address();
let locked_token_data = self.blockchain().get_esdt_token_data(
Expand All @@ -102,6 +106,11 @@ pub trait EnableSwapByUserModule:
let locked_lp_token_amount = payment.amount.clone();
let lp_token_safe_price_result =
self.get_lp_token_value(pair_address.clone(), locked_lp_token_amount);
let config = self.try_get_config(&lp_token_safe_price_result.common_token_id);
require!(
payment.token_identifier == config.locked_token_id,
"Invalid locked token"
);
require!(
lp_token_safe_price_result.safe_price_in_common_token >= config.min_locked_token_value,
"Not enough value locked"
Expand Down Expand Up @@ -140,8 +149,8 @@ pub trait EnableSwapByUserModule:
}

#[view(getEnableSwapByUserConfig)]
fn try_get_config(&self) -> EnableSwapByUserConfig<Self::Api> {
let mapper = self.enable_swap_by_user_config();
fn try_get_config(&self, token_id: &TokenIdentifier) -> EnableSwapByUserConfig<Self::Api> {
let mapper = self.enable_swap_by_user_config(token_id);
require!(!mapper.is_empty(), "No config set");

mapper.get()
Expand Down Expand Up @@ -169,20 +178,23 @@ pub trait EnableSwapByUserModule:
.execute_on_dest_context();

let (first_result, second_result) = multi_value.into_tuple();
let mut safe_price_result = SafePriceResult {
first_token_id: first_result.token_identifier.clone(),
second_token_id: second_result.token_identifier.clone(),
common_token_id: first_result.token_identifier,
safe_price_in_common_token: BigUint::zero(),
};
let whitelist = self.common_tokens_for_user_pairs();
let safe_price_in_common_token = if whitelist.contains(&first_result.token_identifier) {
first_result.amount
if whitelist.contains(&safe_price_result.first_token_id) {
safe_price_result.safe_price_in_common_token = first_result.amount;
} else if whitelist.contains(&second_result.token_identifier) {
second_result.amount
safe_price_result.common_token_id = second_result.token_identifier;
safe_price_result.safe_price_in_common_token = second_result.amount;
} else {
sc_panic!("Invalid tokens in Pair contract");
};

SafePriceResult {
first_token_id: first_result.token_identifier,
second_token_id: second_result.token_identifier,
safe_price_in_common_token,
}
safe_price_result
}

fn require_state_active_no_swaps(&self, pair_address: &ManagedAddress) {
Expand Down Expand Up @@ -240,7 +252,10 @@ pub trait EnableSwapByUserModule:
fn user_pair_proxy(&self, to: ManagedAddress) -> pair::Proxy<Self::Api>;

#[storage_mapper("enableSwapByUserConfig")]
fn enable_swap_by_user_config(&self) -> SingleValueMapper<EnableSwapByUserConfig<Self::Api>>;
fn enable_swap_by_user_config(
&self,
token_id: &TokenIdentifier,
) -> SingleValueMapper<EnableSwapByUserConfig<Self::Api>>;

#[view(getCommonTokensForUserPairs)]
#[storage_mapper("commonTokensForUserPairs")]
Expand Down
179 changes: 177 additions & 2 deletions dex/router/tests/router_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ mod router_setup;
use multiversx_sc::{
codec::multi_types::OptionalValue,
storage::mappers::StorageTokenWrapper,
types::{EsdtLocalRole, ManagedAddress, ManagedVec, MultiValueEncoded},
types::{
EgldOrEsdtTokenIdentifier, EsdtLocalRole, ManagedAddress, ManagedVec, MultiValueEncoded,
},
};
use pair::{config::ConfigModule, Pair};
use pausable::{PausableModule, State};
Expand Down Expand Up @@ -151,11 +153,15 @@ fn user_enable_pair_swaps_through_router_test() {
managed_address!(pair_wrapper.address_ref()),
);

sc.add_common_tokens_for_user_pairs(MultiValueEncoded::from(ManagedVec::from(vec![
managed_token_id!(USDC_TOKEN_ID),
])));

sc.config_enable_by_user_parameters(
managed_token_id!(USDC_TOKEN_ID),
managed_token_id!(LOCKED_TOKEN_ID),
managed_biguint!(MIN_LOCKED_TOKEN_VALUE),
MIN_LOCKED_PERIOD_EPOCHS,
ManagedVec::from_single_item(managed_token_id!(USDC_TOKEN_ID)).into(),
)
})
.assert_ok();
Expand Down Expand Up @@ -288,3 +294,172 @@ fn user_enable_pair_swaps_through_router_test() {
}),
);
}

#[test]
fn user_enable_pair_swaps_fail_test() {
let rust_zero = rust_biguint!(0u64);
let mut b_mock = BlockchainStateWrapper::new();
let owner = b_mock.create_user_account(&rust_zero);
let user = b_mock.create_user_account(&rust_zero);

let current_epoch = 5;
b_mock.set_block_epoch(current_epoch);

b_mock.set_esdt_balance(
&user,
CUSTOM_TOKEN_ID,
&rust_biguint!(USER_CUSTOM_TOKEN_BALANCE),
);
b_mock.set_esdt_balance(&user, USDC_TOKEN_ID, &rust_biguint!(USER_USDC_BALANCE));

let router_wrapper = b_mock.create_sc_account(
&rust_zero,
Some(&owner),
router::contract_obj,
ROUTER_WASM_PATH,
);
let pair_wrapper = b_mock.create_sc_account(
&rust_zero,
Some(router_wrapper.address_ref()),
pair::contract_obj,
PAIR_WASM_PATH,
);

// setup router
b_mock
.execute_tx(&owner, &router_wrapper, &rust_zero, |sc| {
sc.init(OptionalValue::None);

sc.pair_map().insert(
PairTokens {
first_token_id: managed_token_id!(CUSTOM_TOKEN_ID),
second_token_id: managed_token_id!(USDC_TOKEN_ID),
},
managed_address!(pair_wrapper.address_ref()),
);

sc.add_common_tokens_for_user_pairs(MultiValueEncoded::from(ManagedVec::from(vec![
managed_token_id!(USDC_TOKEN_ID),
])));

sc.config_enable_by_user_parameters(
managed_token_id!(USDC_TOKEN_ID),
managed_token_id!(LOCKED_TOKEN_ID),
managed_biguint!(MIN_LOCKED_TOKEN_VALUE),
MIN_LOCKED_PERIOD_EPOCHS,
)
})
.assert_ok();

// setup pair
b_mock
.execute_tx(&owner, &pair_wrapper, &rust_zero, |sc| {
let first_token_id = managed_token_id!(CUSTOM_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);

sc.init(
first_token_id,
second_token_id,
router_address,
router_owner_address,
0,
0,
managed_address!(&user),
MultiValueEncoded::<DebugApi, ManagedAddress<DebugApi>>::new(),
);

assert_eq!(sc.state().get(), State::Inactive);

sc.lp_token_identifier()
.set(&managed_token_id!(LPUSDC_TOKEN_ID));
})
.assert_ok();

b_mock.set_esdt_local_roles(
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: USDC_TOKEN_ID.to_vec(),
nonce: 0,
value: rust_biguint!(USER_USDC_BALANCE),
},
];

let user_lp_tokens_balance = 999_000u64;
b_mock
.execute_esdt_multi_transfer(&user, &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();

let custom_locked_token = b"LTOK2-123456";
let _ = DebugApi::dummy();
b_mock.set_nft_balance(
&user,
custom_locked_token,
1,
&rust_biguint!(user_lp_tokens_balance),
&LockedTokenAttributes::<DebugApi> {
original_token_id: EgldOrEsdtTokenIdentifier::esdt(managed_token_id!(LPUSDC_TOKEN_ID)),
original_token_nonce: 0,
unlock_epoch: current_epoch + MIN_LOCKED_PERIOD_EPOCHS,
},
);

// pass blocks time to update safe price
b_mock.set_block_nonce(1_000_000);

// activate swaps through router
b_mock
.execute_esdt_transfer(
&user,
&router_wrapper,
custom_locked_token,
1,
&rust_biguint!(user_lp_tokens_balance),
|sc| {
sc.set_swap_enabled_by_user(managed_address!(pair_wrapper.address_ref()));
},
)
.assert_user_error("Invalid locked token");

// check pair state is active
b_mock
.execute_query(&pair_wrapper, |sc| {
assert_eq!(sc.state().get(), State::PartialActive);
})
.assert_ok();

// check user received the locked tokens back
b_mock.check_nft_balance(
&user,
custom_locked_token,
1,
&rust_biguint!(user_lp_tokens_balance),
Some(&LockedTokenAttributes::<DebugApi> {
original_token_id: managed_token_id_wrapped!(LPUSDC_TOKEN_ID),
original_token_nonce: 0,
unlock_epoch: current_epoch + MIN_LOCKED_PERIOD_EPOCHS,
}),
);
}

0 comments on commit c664ab6

Please sign in to comment.