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
74 changes: 43 additions & 31 deletions modules/homa-lite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,17 +419,26 @@ pub mod module {

Self::deposit_event(Event::<T>::RedeemRequestCancelled(who, request_amount));
}
} else {
// Redeem amount must be above a certain limit.
ensure!(
Self::liquid_amount_is_above_minimum_threshold(liquid_amount),
Error::<T>::AmountBelowMinimumThreshold
);
return Ok(());
}

// Withdraw fee is burned.
let base_withdraw_fee = T::BaseWithdrawFee::get().mul(liquid_amount);
let slash_amount = T::Currency::slash(T::LiquidCurrencyId::get(), &who, base_withdraw_fee);
ensure!(slash_amount.is_zero(), Error::<T>::InsufficientLiquidBalance);
// Redeem amount must be above a certain limit.
ensure!(
Self::liquid_amount_is_above_minimum_threshold(liquid_amount),
Error::<T>::AmountBelowMinimumThreshold
);

RedeemRequests::<T>::try_mutate(&who, |request| -> DispatchResult {
let old_amount = request.take().map(|(amount, _)| amount).unwrap_or_default();

let diff_amount = liquid_amount.saturating_sub(old_amount);

let base_withdraw_fee = T::BaseWithdrawFee::get().mul(diff_amount);
if !base_withdraw_fee.is_zero() {
// Burn withdraw fee for increased amount
let slash_amount = T::Currency::slash(T::LiquidCurrencyId::get(), &who, base_withdraw_fee);
ensure!(slash_amount.is_zero(), Error::<T>::InsufficientLiquidBalance);
}

// Deduct BaseWithdrawFee from the liquid amount.
let liquid_amount = liquid_amount.saturating_sub(base_withdraw_fee);
Expand Down Expand Up @@ -491,12 +500,17 @@ pub mod module {
}?;

// Insert/replace the new redeem request into storage.
RedeemRequests::<T>::insert(&who, (liquid_remaining, additional_fee));
*request = Some((liquid_remaining, additional_fee));

Self::deposit_event(Event::<T>::RedeemRequested(who, liquid_remaining, additional_fee));
Self::deposit_event(Event::<T>::RedeemRequested(
who.clone(),
liquid_remaining,
additional_fee,
));
}
}
Ok(())

Ok(())
})
}

/// Request staking currencies to be unbonded from the RelayChain.
Expand Down Expand Up @@ -556,19 +570,6 @@ pub mod module {
}

impl<T: Config> Pallet<T> {
/// Calculate the exchange rate between the Staking and Liquid currency.
/// returns Ratio(staking : liquid) = total_staking_amount / liquid_total_issuance
/// If the exchange rate cannot be calculated, T::DefaultExchangeRate is used
pub fn get_exchange_rate() -> Ratio {
let staking_total = Self::total_staking_currency();
let liquid_total = T::Currency::total_issuance(T::LiquidCurrencyId::get());
if staking_total.is_zero() {
T::DefaultExchangeRate::get()
} else {
Ratio::checked_from_rational(staking_total, liquid_total).unwrap_or_else(T::DefaultExchangeRate::get)
}
}

/// Calculate the amount of Staking currency converted from Liquid currency.
/// staking_amount = (total_staking_amount / liquid_total_issuance) * liquid_amount
/// If the exchange rate cannot be calculated, T::DefaultExchangeRate is used
Expand Down Expand Up @@ -799,6 +800,8 @@ pub mod module {
);
let actual_staking_amount = Self::convert_liquid_to_staking(actual_liquid_amount)?;

TotalStakingCurrency::<T>::mutate(|x| *x = x.saturating_sub(actual_staking_amount));

// Redeem from the available_staking_balances costs only the xcm unbond fee.
T::Currency::deposit(
T::StakingCurrencyId::get(),
Expand Down Expand Up @@ -866,10 +869,19 @@ pub mod module {
)
}
}
pub struct LiquidExchangeProvider<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> ExchangeRateProvider for LiquidExchangeProvider<T> {
fn get_exchange_rate() -> ExchangeRate {
Pallet::<T>::get_exchange_rate().reciprocal().unwrap_or_default()

impl<T: Config> ExchangeRateProvider for Pallet<T> {
/// Calculate the exchange rate between the Staking and Liquid currency.
/// returns Ratio(staking : liquid) = total_staking_amount / liquid_total_issuance
/// If the exchange rate cannot be calculated, T::DefaultExchangeRate is used
fn get_exchange_rate() -> Ratio {
let staking_total = Self::total_staking_currency();
let liquid_total = T::Currency::total_issuance(T::LiquidCurrencyId::get());
if staking_total.is_zero() {
T::DefaultExchangeRate::get()
} else {
Ratio::checked_from_rational(staking_total, liquid_total).unwrap_or_else(T::DefaultExchangeRate::get)
}
}
}
}
9 changes: 9 additions & 0 deletions modules/homa-lite/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,15 @@ pub struct ExtBuilder {
native_balances: Vec<(AccountId, Balance)>,
}

impl ExtBuilder {
pub fn empty() -> Self {
Self {
tokens_balances: vec![],
native_balances: vec![],
}
}
}

impl Default for ExtBuilder {
fn default() -> Self {
let initial = dollar(INITIAL_BALANCE);
Expand Down
138 changes: 128 additions & 10 deletions modules/homa-lite/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ fn mint_works() {
HomaLite::get_exchange_rate(),
ExchangeRate::saturating_from_rational(lksm_issuance / 5, lksm_issuance)
);
assert_eq!(
LiquidExchangeProvider::<Runtime>::get_exchange_rate(),
ExchangeRate::saturating_from_rational(lksm_issuance, lksm_issuance / 5)
);

// The exchange rate is now 1:5 ratio
// liquid = (1000 - 0.01) * 1_009_899_901_000_000_000 / 201_979_980_200_000_000 * 0.99
Expand Down Expand Up @@ -517,11 +513,11 @@ fn request_redeem_works() {
assert_eq!(AvailableStakingBalance::<Runtime>::get(), 0);
assert_eq!(Currencies::free_balance(KSM, &ROOT), dollar(49_998));
assert_eq!(Currencies::free_balance(LKSM, &ROOT), dollar(349_400));
assert_eq!(Currencies::reserved_balance(LKSM, &ROOT), 149_850_000_000_000_000);
assert_eq!(Currencies::reserved_balance(LKSM, &ROOT), 149949400000000000);
// request_redeem replaces existing item in the queue, not add to it.
assert_eq!(
RedeemRequests::<Runtime>::get(&ROOT),
Some((149_850_000_000_000_000, Permill::zero()))
Some((149949400000000000, Permill::zero()))
);
});
}
Expand Down Expand Up @@ -641,10 +637,10 @@ fn can_replace_requested_redeem() {
dollar(50_000),
Permill::from_percent(50)
));
assert_eq!(Currencies::reserved_balance(LKSM, &ROOT), dollar(49_950));
assert_eq!(Currencies::reserved_balance(LKSM, &ROOT), dollar(50_000));
assert_eq!(
RedeemRequests::<Runtime>::get(&ROOT),
Some((dollar(49_950), Permill::from_percent(50)))
Some((dollar(50_000), Permill::from_percent(50)))
);

// Increasing the amount locks additional liquid currency.
Expand All @@ -653,10 +649,10 @@ fn can_replace_requested_redeem() {
dollar(150_000),
Permill::from_percent(10)
));
assert_eq!(Currencies::reserved_balance(LKSM, &ROOT), dollar(149_850));
assert_eq!(Currencies::reserved_balance(LKSM, &ROOT), dollar(149_900));
assert_eq!(
RedeemRequests::<Runtime>::get(&ROOT),
Some((dollar(149_850), Permill::from_percent(10)))
Some((dollar(149_900), Permill::from_percent(10)))
);
});
}
Expand Down Expand Up @@ -824,3 +820,125 @@ fn redeem_can_handle_dust_available_staking_currency() {
)));
});
}

#[test]
fn process_scheduled_unbond_with_multiple_requests() {
ExtBuilder::empty().build().execute_with(|| {
assert_ok!(Currencies::update_balance(
Origin::root(),
ALICE,
LKSM,
dollar(100) as i128
));
assert_ok!(Currencies::update_balance(
Origin::root(),
BOB,
LKSM,
dollar(100) as i128
));
assert_ok!(Currencies::update_balance(
Origin::root(),
CHARLIE,
LKSM,
dollar(200) as i128
));

assert_ok!(HomaLite::set_total_staking_currency(Origin::root(), dollar(40)));

let rate1 = HomaLite::get_exchange_rate();
assert_eq!(HomaLite::get_exchange_rate(), Ratio::saturating_from_rational(1, 10));

assert_ok!(HomaLite::request_redeem(
Origin::signed(ALICE),
dollar(100),
Permill::zero()
));

assert_ok!(HomaLite::request_redeem(
Origin::signed(BOB),
dollar(100),
Permill::zero()
));

assert_ok!(HomaLite::request_redeem(
Origin::signed(CHARLIE),
dollar(200),
Permill::zero()
));

assert_ok!(HomaLite::replace_schedule_unbond(Origin::root(), vec![(dollar(30), 1)],));
MockRelayBlockNumberProvider::set(1);
HomaLite::on_idle(MockRelayBlockNumberProvider::get(), 5_000_000_000);

let rate2 = HomaLite::get_exchange_rate();
assert!(rate1 < rate2);

// Some rounding error
assert_eq!(AvailableStakingBalance::<Runtime>::get(), 1);

// Some rounding error, 10 KSM - 1 KSM unbond fee
assert_eq!(Currencies::free_balance(KSM, &ALICE), 8999999999999);
assert_eq!(Currencies::free_balance(LKSM, &ALICE), 0);

// 10 KSM - 1 KSM unbond fee
assert_eq!(Currencies::free_balance(KSM, &BOB), 9000000000000);
assert_eq!(Currencies::free_balance(LKSM, &BOB), 0);

// 10 KSM - 1 KSM unbond fee
assert_eq!(Currencies::free_balance(KSM, &CHARLIE), 9000000000000);
// 100 LKSM minus fee
assert_eq!(Currencies::reserved_balance(LKSM, &CHARLIE), 99899999999996);
});
}

#[test]
fn not_overcharge_redeem_fee() {
ExtBuilder::empty().build().execute_with(|| {
assert_ok!(Currencies::update_balance(
Origin::root(),
ALICE,
LKSM,
dollar(100) as i128
));

assert_ok!(HomaLite::set_total_staking_currency(Origin::root(), dollar(10)));

assert_ok!(HomaLite::request_redeem(
Origin::signed(ALICE),
dollar(50),
Permill::zero()
));

let fee = dollar(50) / 1000;

assert_eq!(Currencies::free_balance(LKSM, &ALICE), dollar(50));
assert_eq!(Currencies::reserved_balance(LKSM, &ALICE), dollar(50) - fee);

assert_ok!(HomaLite::request_redeem(
Origin::signed(ALICE),
dollar(50) - fee,
Permill::zero()
));

assert_eq!(Currencies::free_balance(LKSM, &ALICE), dollar(50));
assert_eq!(Currencies::reserved_balance(LKSM, &ALICE), dollar(50) - fee);

assert_ok!(HomaLite::request_redeem(
Origin::signed(ALICE),
dollar(100) - fee,
Permill::zero()
));

assert_eq!(Currencies::free_balance(LKSM, &ALICE), 0);
assert_eq!(Currencies::reserved_balance(LKSM, &ALICE), dollar(100) - fee * 2);

assert_ok!(HomaLite::request_redeem(
Origin::signed(ALICE),
dollar(20) - fee * 2,
Permill::zero()
));

assert_eq!(Currencies::free_balance(LKSM, &ALICE), dollar(80));
assert_eq!(Currencies::reserved_balance(LKSM, &ALICE), dollar(20) - fee * 2);
});
}
2 changes: 1 addition & 1 deletion runtime/acala/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,7 +798,7 @@ impl module_prices::Config for Runtime {
type GetStakingCurrencyId = GetStakingCurrencyId;
type GetLiquidCurrencyId = GetLiquidCurrencyId;
type LockOrigin = EnsureRootOrTwoThirdsGeneralCouncil;
type LiquidStakingExchangeRateProvider = module_homa_lite::LiquidExchangeProvider<Runtime>;
type LiquidStakingExchangeRateProvider = HomaLite;
type DEX = Dex;
type Currency = Currencies;
type CurrencyIdMapping = EvmCurrencyIdMapping<Runtime>;
Expand Down
Loading