Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add improved homa lite redeem match #1626

Merged
merged 25 commits into from
Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b9e9e57
Added a new mock that costs no fees. This can be used to test economi…
Nov 16, 2021
6b5746c
Fixed a bug where total_staking is not changed when redeeming from av…
Nov 17, 2021
3ea7064
Added one more test
Nov 17, 2021
e02b9cf
Updated comments
Nov 17, 2021
83d7593
Added one more test
Nov 17, 2021
23c2000
Updated homalite redeem logic so if redeemer doesn't enough reserve b…
Nov 18, 2021
9ee460c
Fixed a clippy errot
Nov 18, 2021
0e59973
Added function that iterates redeem request from a starting element. …
Nov 19, 2021
e8c4b9e
Replaced redeem logic to iterate from the "Next" item
Nov 20, 2021
1c4c68d
Merge remote-tracking branch 'origin/master' into feature/homa-lite-r…
Nov 20, 2021
785658b
Improved the way iterator from next element
Nov 21, 2021
2bb50e2
Changed the storage of NextRedeemRequestToMatch to store the last red…
Nov 22, 2021
84f0042
Improved how "first element" is handled
Nov 22, 2021
47851d6
Improved coding structure for homalite redeem
Nov 23, 2021
a1613ed
Refactored the way functions are structured in HomaLite
Nov 23, 2021
e3acbff
Fixed a benchmarking test
Nov 23, 2021
f494599
refactor
xlc Nov 24, 2021
43318a9
Fixed unit tests
Nov 24, 2021
b8ca01d
Tidied up how events are asserted in HomaLite unit tests
Nov 24, 2021
4b7ee6a
Tidied up how events are asserted
Nov 24, 2021
62083c8
minor comments update
Nov 24, 2021
308d6e2
Added System::reset_events to make event assert clearer
Nov 24, 2021
37d0827
Fixed clippy
Nov 24, 2021
4cd40e4
Update modules/homa-lite/src/lib.rs
xlc Nov 24, 2021
64ca4fb
Merge remote-tracking branch 'origin/master' into feature/homa-lite-r…
xlc Nov 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 140 additions & 58 deletions modules/homa-lite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ pub mod module {
pub type XcmDestWeight<T: Config> = StorageValue<_, Weight, ValueQuery>;

/// Requests to redeem staked currencies.
/// RedeemRequests: Map: AccountId => Option<(liquid_amount: Balance, addtional_fee: Permill)>
/// RedeemRequests: Map: AccountId => Option<(liquid_amount: Balance, additional_fee: Permill)>
#[pallet::storage]
#[pallet::getter(fn redeem_requests)]
pub type RedeemRequests<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, (Balance, Permill), OptionQuery>;
Expand All @@ -260,6 +260,12 @@ pub mod module {
#[pallet::getter(fn staking_interest_rate_per_update)]
pub type StakingInterestRatePerUpdate<T: Config> = StorageValue<_, Permill, ValueQuery>;

/// Next redeem request to iterate from when matching redeem requests.
/// NextRedeemRequestToMatch: Value: T::AccountId
#[pallet::storage]
#[pallet::getter(fn next_redeem_request_to_match)]
pub type NextRedeemRequestToMatch<T: Config> = StorageValue<_, T::AccountId, ValueQuery>;
syan095 marked this conversation as resolved.
Show resolved Hide resolved

#[pallet::pallet]
pub struct Pallet<T>(_);

Expand Down Expand Up @@ -333,7 +339,7 @@ pub mod module {
/// liquid currency.
///
/// If any amount is minted through XCM, a portion of that amount (T::MintFee and
/// T::MaxRewardPerEra) is reducted as fee.
/// T::MaxRewardPerEra) is deducted as fee.
///
/// Parameters:
/// - `amount`: The amount of Staking currency to be exchanged.
Expand Down Expand Up @@ -565,7 +571,7 @@ pub mod module {
/// Requires `T::GovernanceOrigin`
///
/// Parameters:
/// - `new_unbonds`: The new ScheduledUnbond storage to replace the currrent storage.
/// - `new_unbonds`: The new ScheduledUnbond storage to replace the current storage.
#[pallet::weight(< T as Config >::WeightInfo::replace_schedule_unbond())]
#[transactional]
pub fn replace_schedule_unbond(
Expand Down Expand Up @@ -634,7 +640,7 @@ pub mod module {
}

/// Set the interest rate for TotalStakingCurrency.
/// TotakStakingCurrency is incremented every `T::StakingUpdateFrequency` blocks
/// TotalStakingCurrency is incremented every `T::StakingUpdateFrequency` blocks
///
/// Requires `T::GovernanceOrigin`
///
Expand Down Expand Up @@ -768,10 +774,10 @@ pub mod module {
// Attempt to match redeem requests if there are any.
let total_liquid_to_mint = Self::convert_staking_to_liquid(amount)?;

// The amount of liquid currency to be redeemed for the mint reuqest.
// The amount of liquid currency to be redeemed for the mint request.
let mut liquid_remaining = total_liquid_to_mint;

// New balances after redeem requests are fullfilled.
// New balances after redeem requests are fulfilled.
let mut new_balances: Vec<(T::AccountId, Balance, Permill)> = vec![];

// Iterate through the prioritized requests first
Expand Down Expand Up @@ -801,21 +807,32 @@ pub mod module {
new_balances.clear();

let mut redeem_requests_limit_remaining = T::MaximumRedeemRequestMatchesForMint::get();
// Iterate all remaining redeem requests now.
for (redeemer, (request_amount, extra_fee)) in RedeemRequests::<T>::iter() {
if !liquid_remaining.is_zero() {
// Define the function to be executed for each of the redeem request:
// Redeem the given requests with the current minting request.
//
// Params: redeemer: T::AccountId, request_amount: Balance, extra_fee: Permill
// Returns: Result<should_break, Error>
//
// Capturing: &mut liquid_remaining
// &mut redeem_requests_limit_remaining
// &mut new_balances
// If all the currencies are minted, return.
if liquid_remaining.is_zero() || redeem_requests_limit_remaining.is_zero() {
break;
}
Self::match_mint_with_redeem_request(
minter,
&redeemer,
request_amount,
extra_fee,
&mut liquid_remaining,
&mut new_balances,
)?;
redeem_requests_limit_remaining -= 1;
let mut f = |redeemer, request_amount, extra_fee| -> Result<bool, DispatchError> {
Self::match_mint_with_redeem_request(
minter,
&redeemer,
request_amount,
extra_fee,
&mut liquid_remaining,
&mut new_balances,
)?;
redeem_requests_limit_remaining -= 1;

// Should break when all currencies are minted
Ok(liquid_remaining.is_zero() || redeem_requests_limit_remaining.is_zero())
};
Self::iterate_from_next_redeem_request(&mut f)?;
}

// Update storage to the new balances. Remove Redeem requests that have been filled.
Expand Down Expand Up @@ -895,7 +912,7 @@ pub mod module {
/// - `max_num_matches`: Maximum number of redeem requests to be matched.
///
/// return:
/// Result<u32, DispatchError>: The number of redeem reqeusts actually matched.
/// Result<u32, DispatchError>: The number of redeem requests actually matched.
#[transactional]
pub fn process_redeem_requests_with_available_staking_balance(
max_num_matches: u32,
Expand All @@ -910,46 +927,63 @@ pub mod module {

let mut new_balances: Vec<(T::AccountId, Balance, Permill)> = vec![];
let mut num_matched = 0u32;
for (redeemer, (request_amount, extra_fee)) in RedeemRequests::<T>::iter() {
let actual_liquid_amount = min(
request_amount,
Self::convert_staking_to_liquid(available_staking_balance)?,
);

// Ensure the redeemer have enough liquid currency in their account.
if T::Currency::reserved_balance(T::LiquidCurrencyId::get(), &redeemer) >= actual_liquid_amount {
let actual_staking_amount = Self::convert_liquid_to_staking(actual_liquid_amount)?;

Self::update_total_staking_currency_storage(|total| {
Ok(total.saturating_sub(actual_staking_amount))
})?;

//Actual deposit amount has `T::XcmUnbondFee` deducted.
let actual_staking_amount_deposited = actual_staking_amount.saturating_sub(T::XcmUnbondFee::get());
T::Currency::deposit(T::StakingCurrencyId::get(), &redeemer, actual_staking_amount_deposited)?;

// Burn the corresponding amount of Liquid currency from the user.
// The redeemer is guaranteed to have enough fund
T::Currency::unreserve(T::LiquidCurrencyId::get(), &redeemer, actual_liquid_amount);
T::Currency::slash(T::LiquidCurrencyId::get(), &redeemer, actual_liquid_amount);

available_staking_balance = available_staking_balance.saturating_sub(actual_staking_amount);
let request_amount_remaining = request_amount.saturating_sub(actual_liquid_amount);
new_balances.push((redeemer.clone(), request_amount_remaining, extra_fee));

Self::deposit_event(Event::<T>::Redeemed(
redeemer,
actual_staking_amount_deposited,
actual_liquid_amount,
));
num_matched += 1u32;
}
{
// Define the function to be executed for each of the redeem request:
// Redeem the given requests using available_staking_balance.
//
// Params: redeemer: T::AccountId, request_amount: Balance, extra_fee: Permill
// Returns: Result< should_break, Error>
//
// Capturing: &mut available_staking_balance
// &mut num_matched
// &mut new_balances
let mut f = |redeemer, request_amount, extra_fee| -> Result<bool, DispatchError> {
let actual_liquid_amount = min(
request_amount,
Self::convert_staking_to_liquid(available_staking_balance)?,
);

// If all the currencies are minted, return.
if available_staking_balance < T::MinimumMintThreshold::get() || num_matched >= max_num_matches {
break;
}
// Ensure the redeemer have enough liquid currency in their account.
if T::Currency::reserved_balance(T::LiquidCurrencyId::get(), &redeemer) >= actual_liquid_amount {
let actual_staking_amount = Self::convert_liquid_to_staking(actual_liquid_amount)?;

Self::update_total_staking_currency_storage(|total| {
Ok(total.saturating_sub(actual_staking_amount))
})?;

//Actual deposit amount has `T::XcmUnbondFee` deducted.
let actual_staking_amount_deposited =
actual_staking_amount.saturating_sub(T::XcmUnbondFee::get());
T::Currency::deposit(T::StakingCurrencyId::get(), &redeemer, actual_staking_amount_deposited)?;

// Burn the corresponding amount of Liquid currency from the user.
// The redeemer is guaranteed to have enough fund
T::Currency::unreserve(T::LiquidCurrencyId::get(), &redeemer, actual_liquid_amount);
T::Currency::slash(T::LiquidCurrencyId::get(), &redeemer, actual_liquid_amount);

available_staking_balance = available_staking_balance.saturating_sub(actual_staking_amount);
let request_amount_remaining = request_amount.saturating_sub(actual_liquid_amount);
new_balances.push((redeemer.clone(), request_amount_remaining, extra_fee));

Self::deposit_event(Event::<T>::Redeemed(
redeemer,
actual_staking_amount_deposited,
actual_liquid_amount,
));
num_matched += 1u32;
}

// If all the currencies are minted, return `should_break` as true
Ok(available_staking_balance <= T::MinimumMintThreshold::get() || num_matched >= max_num_matches)
};
Self::iterate_from_next_redeem_request(&mut f)?;
}
// for (redeemer, (request_amount, extra_fee)) in RedeemRequests::<T>::iter() {
// if f(redeemer, request_amount, extra_fee)? {
// break;
// }
// }

// Update storage to the new balances. Remove Redeem requests that have been filled.
Self::update_redeem_requests(&new_balances);
Expand Down Expand Up @@ -1008,6 +1042,54 @@ pub mod module {
Ok(())
})
}

/// Helper function that iterates `RedeemRequests` storage from `NextRedeemRequestToMatch`.
/// If `NextRedeemRequestToMatch` is not found in storage, iterate from the start.
pub fn iterate_from_next_redeem_request(
f: &mut impl FnMut(T::AccountId, Balance, Permill) -> Result<bool, DispatchError>,
) -> DispatchResult {
let first_element = match RedeemRequests::<T>::iter().next() {
// Current storage is empty. Do nothing and return
None => return Ok(()),
Some(element) => element,
};

let starting_element = match Self::redeem_requests(Self::next_redeem_request_to_match()) {
Some(request) => (Self::next_redeem_request_to_match(), request),
None => first_element,
syan095 marked this conversation as resolved.
Show resolved Hide resolved
};
let mut iterator = RedeemRequests::<T>::iter();
let mut current = iterator.next();

// Skip elements until `starting_element` is found.
while current.is_some() && current.clone().unwrap_or_default() != starting_element {
syan095 marked this conversation as resolved.
Show resolved Hide resolved
current = iterator.next();
}

// Iterate until the end of the storage, calling f() for each element
while current.is_some() {
let (redeemer, (request_amount, extra_fee)) = current.unwrap_or_default();
if f(redeemer, request_amount, extra_fee)? {
// Store the `next` element as `NextRedeemRequestToMatch`
NextRedeemRequestToMatch::<T>::put(iterator.next().unwrap_or_default().0);
return Ok(());
}
current = iterator.next();
}

// Iterate from the start of the storage until `starting_element`, calling f() for each element.
iterator = RedeemRequests::<T>::iter();
current = iterator.next();
while current.is_some() && current.clone().unwrap_or_default() != starting_element {
let (redeemer, (request_amount, extra_fee)) = current.unwrap_or_default();
if f(redeemer, request_amount, extra_fee)? {
NextRedeemRequestToMatch::<T>::put(iterator.next().unwrap_or_default().0);
return Ok(());
}
current = iterator.next();
}
Ok(())
}
}

impl<T: Config> ExchangeRateProvider for Pallet<T> {
Expand Down
Loading