Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
63 changes: 38 additions & 25 deletions modules/homa-lite/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,6 @@ pub struct Module<T: Config>(crate::Pallet<T>);
const SEED: u32 = 0;

benchmarks! {
on_idle {
let amount = 1_000_000_000_000;
let caller: T::AccountId = account("caller", 0, SEED);
let caller1: T::AccountId = account("callera", 0, SEED);
let caller2: T::AccountId = account("callerb", 0, SEED);
let caller3: T::AccountId = account("callerc", 0, SEED);
<T as module::Config>::Currency::deposit(T::LiquidCurrencyId::get(), &caller1, amount)?;
<T as module::Config>::Currency::deposit(T::LiquidCurrencyId::get(), &caller2, amount)?;
<T as module::Config>::Currency::deposit(T::LiquidCurrencyId::get(), &caller3, amount)?;
let _ = crate::Pallet::<T>::request_redeem(RawOrigin::Signed(caller1).into(), amount, Permill::default());
let _ = crate::Pallet::<T>::request_redeem(RawOrigin::Signed(caller2.clone()).into(), amount, Permill::default());
let _ = crate::Pallet::<T>::request_redeem(RawOrigin::Signed(caller3.clone()).into(), amount, Permill::default());
let _ = crate::Pallet::<T>::schedule_unbond(RawOrigin::Root.into(), amount*2, <T as frame_system::Config>::BlockNumber::default());
}: {
let _ = crate::Pallet::<T>::on_idle(<T as frame_system::Config>::BlockNumber::default(), 1_000_000_000);
}

mint {
let amount = 1_000_000_000_000;
let caller: T::AccountId = account("caller", 0, SEED);
Expand All @@ -73,7 +56,11 @@ benchmarks! {

set_total_staking_currency {}: _(RawOrigin::Root, 1_000_000_000_000)

adjust_total_staking_currency {}: _(RawOrigin::Root, AmountOf::<T>::default())
adjust_total_staking_currency {}: _(RawOrigin::Root, AmountOf::<T>::max_value())

adjust_available_staking_balance_with_no_matches {}: {
let _ = crate::Pallet::<T>::adjust_available_staking_balance(RawOrigin::Root.into(), AmountOf::<T>::max_value(), 0);
}

set_minting_cap {
}: _(RawOrigin::Root, 1_000_000_000_000_000_000)
Expand All @@ -90,6 +77,20 @@ benchmarks! {
schedule_unbond {}: _(RawOrigin::Root, 1_000_000_000_000, <T as frame_system::Config>::BlockNumber::default())

replace_schedule_unbond {}: _(RawOrigin::Root, vec![(1_000_000, <T as frame_system::Config>::BlockNumber::default()), (1_000_000_000, <T as frame_system::Config>::BlockNumber::default())])

redeem_with_available_staking_balance {
let amount = 1_000_000_000_000_000;
let caller: T::AccountId = account("caller", 0, SEED);
<T as module::Config>::Currency::deposit(T::LiquidCurrencyId::get(), &caller, amount)?;
let _ = crate::Pallet::<T>::adjust_available_staking_balance(RawOrigin::Root.into(), AmountOf::<T>::max_value(), 1);
let _ = crate::Pallet::<T>::request_redeem(RawOrigin::Signed(caller).into(), amount, Permill::default());
}: {
let _ = crate::Pallet::<T>::process_redeem_requests_with_available_staking_balance(1);
}

xcm_unbond {}: {
let _ = crate::Pallet::<T>::process_scheduled_unbond(1_000_000_000_000_000);
}
}

#[cfg(test)]
Expand All @@ -98,13 +99,6 @@ mod tests {
use crate::mock::*;
use frame_support::assert_ok;

#[test]
fn test_on_idle() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(Pallet::<Runtime>::test_benchmark_on_idle());
});
}

#[test]
fn test_mint() {
ExtBuilder::default().build().execute_with(|| {
Expand All @@ -129,6 +123,13 @@ mod tests {
assert_ok!(Pallet::<Runtime>::test_benchmark_adjust_total_staking_currency());
});
}
#[test]
fn test_adjust_available_staking_balance_with_no_matches() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(Pallet::<Runtime>::test_benchmark_adjust_available_staking_balance_with_no_matches());
});
}

#[test]
fn test_set_minting_cap() {
ExtBuilder::default().build().execute_with(|| {
Expand Down Expand Up @@ -159,4 +160,16 @@ mod tests {
assert_ok!(Pallet::<Runtime>::test_benchmark_replace_schedule_unbond());
});
}
#[test]
fn test_redeem_with_available_staking_balance() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(Pallet::<Runtime>::test_benchmark_redeem_with_available_staking_balance());
});
}
#[test]
fn test_xcm_unbond() {
ExtBuilder::default().build().execute_with(|| {
assert_ok!(Pallet::<Runtime>::test_benchmark_xcm_unbond());
});
}
}
160 changes: 130 additions & 30 deletions modules/homa-lite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ pub mod module {
/// The scheduled Unbond has been withdrew from the RelayChain.
///\[staking_amount_added\]
ScheduledUnbondWithdrew(Balance),

/// The amount of the staking currency available to be redeemed is set.
/// \[total_available_staking_balance\]
AvailableStakingBalanceSet(Balance),
}

/// The total amount of the staking currency on the relaychain.
Expand Down Expand Up @@ -245,9 +249,9 @@ pub mod module {
#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
fn on_idle(_n: T::BlockNumber, remaining_weight: Weight) -> Weight {
let required_weight = <T as Config>::WeightInfo::on_idle();
let mut current_weight = 0;
if remaining_weight > required_weight {
// If enough weight, process the next XCM unbond.
if remaining_weight > <T as Config>::WeightInfo::xcm_unbond() {
let mut scheduled_unbond = Self::scheduled_unbond();
if !scheduled_unbond.is_empty() {
let (staking_amount, block_number) = scheduled_unbond[0];
Expand All @@ -257,14 +261,30 @@ pub mod module {
debug_assert!(res.is_ok());

if res.is_ok() {
current_weight = required_weight;
current_weight = <T as Config>::WeightInfo::xcm_unbond();

scheduled_unbond.remove(0);
ScheduledUnbond::<T>::put(scheduled_unbond);
}
}
}
}

// With remaining weight, calculate max number of redeems that can be matched
let num_redeem_matches = remaining_weight
.saturating_sub(current_weight)
.checked_div(<T as Config>::WeightInfo::redeem_with_available_staking_balance())
.unwrap_or_default();

// Iterate through existing redeem_requests, and try to match them with `available_staking_balance`
let res = Self::process_redeem_requests_with_available_staking_balance(num_redeem_matches as u32);
debug_assert!(res.is_ok());
if let Ok(count) = res {
current_weight = current_weight.saturating_add(
<T as Config>::WeightInfo::redeem_with_available_staking_balance().saturating_mul(count as Weight),
);
}

current_weight
}
}
Expand Down Expand Up @@ -318,7 +338,6 @@ pub mod module {
#[transactional]
pub fn adjust_total_staking_currency(origin: OriginFor<T>, by_amount: AmountOf<T>) -> DispatchResult {
T::GovernanceOrigin::ensure_origin(origin)?;
let mut current_staking_total = Self::total_staking_currency();

// Convert AmountOf<T> into Balance safely.
let by_amount_abs = if by_amount == AmountOf::<T>::min_value() {
Expand All @@ -329,19 +348,21 @@ pub mod module {

let by_balance = TryInto::<Balance>::try_into(by_amount_abs).map_err(|_| ArithmeticError::Overflow)?;

// Adjust the current total.
if by_amount.is_positive() {
current_staking_total = current_staking_total
.checked_add(by_balance)
.ok_or(ArithmeticError::Overflow)?;
} else {
current_staking_total = current_staking_total
.checked_sub(by_balance)
.ok_or(ArithmeticError::Underflow)?;
}
// ensure TotalStakingCurrency doesn't become 0
ensure!(
by_amount.is_positive() || by_balance < Self::total_staking_currency(),
Error::<T>::InvalidTotalStakingCurrency
);

TotalStakingCurrency::<T>::put(current_staking_total);
Self::deposit_event(Event::<T>::TotalStakingCurrencySet(current_staking_total));
// Adjust the current total.
TotalStakingCurrency::<T>::mutate(|current| {
if by_amount.is_positive() {
*current = current.saturating_add(by_balance);
} else {
*current = current.saturating_sub(by_balance);
}
Self::deposit_event(Event::<T>::TotalStakingCurrencySet(*current));
});

Ok(())
}
Expand Down Expand Up @@ -567,6 +588,53 @@ pub mod module {

Ok(())
}

/// Adjusts the AvailableStakingBalance by the given difference.
/// Also attempt to process queued redeem request with the new Staking Balance.
/// Requires `T::GovernanceOrigin`
///
/// Parameters:
/// - `adjustment`: The difference in amount the AvailableStakingBalance should be adjusted
/// by.
///
/// Weight: Weight(xcm unbond) + n * Weight(match redeem requests), where n is number of
/// redeem requests matched.
#[pallet::weight(
< T as Config >::WeightInfo::adjust_available_staking_balance_with_no_matches().saturating_add(
(*max_num_matches as Weight).saturating_mul(< T as Config >::WeightInfo::redeem_with_available_staking_balance())
)
)]
#[transactional]
pub fn adjust_available_staking_balance(
origin: OriginFor<T>,
by_amount: AmountOf<T>,
max_num_matches: u32,
) -> DispatchResult {
T::GovernanceOrigin::ensure_origin(origin)?;

// Convert AmountOf<T> into Balance safely.
let by_amount_abs = if by_amount == AmountOf::<T>::min_value() {
AmountOf::<T>::max_value()
} else {
by_amount.abs()
};

let by_balance = TryInto::<Balance>::try_into(by_amount_abs).map_err(|_| ArithmeticError::Overflow)?;

// Adjust the current total.
AvailableStakingBalance::<T>::mutate(|current| {
if by_amount.is_positive() {
*current = current.saturating_add(by_balance);
} else {
*current = current.saturating_sub(by_balance);
}
Self::deposit_event(Event::<T>::AvailableStakingBalanceSet(*current));
});

// With new staking balance available, process pending redeem requests.
let _ = Self::process_redeem_requests_with_available_staking_balance(max_num_matches)?;
Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -775,25 +843,51 @@ pub mod module {
Ok(())
}

/// Construct XCM message and sent it to the relaychain to withdraw_unbonded Staking
/// currency. The staking currency withdrew becomes available to be redeemed.
///
/// params:
/// - `staking_amount_unbonded`: amount of staking currency to withdraw unbond via XCM
#[transactional]
fn process_scheduled_unbond(staking_amount: Balance) -> DispatchResult {
let msg = Self::construct_xcm_unreserve_message(T::ParachainAccount::get(), staking_amount);
pub fn process_scheduled_unbond(staking_amount_unbonded: Balance) -> DispatchResult {
let msg = Self::construct_xcm_unreserve_message(T::ParachainAccount::get(), staking_amount_unbonded);

let res = pallet_xcm::Pallet::<T>::send_xcm(Here, Parent, msg);
log::debug!("on_idle XCM result: {:?}", res);
ensure!(res.is_ok(), Error::<T>::XcmFailed);

// Now that there's available staking balance, automatically match existing
// redeem_requests.
// Update storage with the new available amount
AvailableStakingBalance::<T>::mutate(|current| {
*current = current.saturating_add(staking_amount_unbonded);
});

Self::deposit_event(Event::<T>::ScheduledUnbondWithdrew(staking_amount_unbonded));
Ok(())
}

/// Iterate through all redeem requests, then match them with available_staking_balance.
/// This should be called when new available_staking_balance becomes available.
///
/// params:
/// - `max_num_matches`: Maximum number of redeem requests to be matched.
///
/// return:
/// Result<u32, DispatchError>: The number of redeem reqeusts actually matched.
#[transactional]
pub fn process_redeem_requests_with_available_staking_balance(
max_num_matches: u32,
) -> Result<u32, sp_runtime::DispatchError> {
if max_num_matches.is_zero() {
return Ok(0);
}
let mut available_staking_balance = Self::available_staking_balance();
if available_staking_balance < T::MinimumMintThreshold::get() {
return Ok(0);
}

let mut new_balances: Vec<(T::AccountId, Balance, Permill)> = vec![];
let mut available_staking_balance = Self::available_staking_balance()
.checked_add(staking_amount)
.ok_or(ArithmeticError::Overflow)?;
let mut num_matched = 0u32;
for (redeemer, (request_amount, extra_fee)) in RedeemRequests::<T>::iter() {
// If all the currencies are minted, return.
if available_staking_balance.is_zero() {
break;
}
let actual_liquid_amount = min(
request_amount,
Self::convert_staking_to_liquid(available_staking_balance)?,
Expand Down Expand Up @@ -821,20 +915,26 @@ pub mod module {
actual_staking_amount,
actual_liquid_amount,
));
num_matched += 1u32;

// If all the currencies are minted, return.
if available_staking_balance < T::MinimumMintThreshold::get() || num_matched >= max_num_matches {
break;
}
}

// Update storage to the new balances. Remove Redeem requests that have been filled.
Self::update_redeem_requests(&new_balances);

AvailableStakingBalance::<T>::put(available_staking_balance);
Self::deposit_event(Event::<T>::ScheduledUnbondWithdrew(staking_amount));

Ok(())
Ok(num_matched)
}

/// Update the RedeemRequests storage with the new balances.
/// Remove Redeem requests that are dust, or have been filled.
#[allow(clippy::ptr_arg)]
fn update_redeem_requests(new_balances: &Vec<(T::AccountId, Balance, Permill)>) {
// Update storage with the new balances. Remove Redeem requests that have been filled.
for (redeemer, new_balance, extra_fee) in new_balances {
if Self::liquid_amount_is_above_minimum_threshold(*new_balance) {
RedeemRequests::<T>::insert(&redeemer, (*new_balance, *extra_fee));
Expand Down
4 changes: 2 additions & 2 deletions modules/homa-lite/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ impl module_currencies::Config for Runtime {
parameter_types! {
pub const StakingCurrencyId: CurrencyId = KSM;
pub const LiquidCurrencyId: CurrencyId = LKSM;
pub MinimumMintThreshold: Balance = millicent(1);
pub MinimumRedeemThreshold: Balance = millicent(1);
pub MinimumMintThreshold: Balance = millicent(1000);
pub MinimumRedeemThreshold: Balance = millicent(1000);
pub const MockXcmDestination: MultiLocation = MOCK_XCM_DESTINATION;
pub const MockXcmAccountId: AccountId = MOCK_XCM_ACCOUNTID;
pub DefaultExchangeRate: ExchangeRate = ExchangeRate::saturating_from_rational(1, 10);
Expand Down
Loading