diff --git a/modules/incentives/src/lib.rs b/modules/incentives/src/lib.rs index aa6b7d08c..856709104 100644 --- a/modules/incentives/src/lib.rs +++ b/modules/incentives/src/lib.rs @@ -49,7 +49,7 @@ use primitives::{Amount, Balance, CurrencyId}; use scale_info::TypeInfo; use sp_runtime::{ traits::{AccountIdConversion, One, UniqueSaturatedInto, Zero}, - DispatchResult, FixedPointNumber, RuntimeDebug, + DispatchResult, FixedPointNumber, Permill, RuntimeDebug, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use support::{CDPTreasury, DEXIncentives, DEXManager, EmergencyShutdown, Rate}; @@ -86,6 +86,10 @@ pub mod module { #[pallet::constant] type AccumulatePeriod: Get; + /// The native currency for earning staking + #[pallet::constant] + type NativeCurrencyId: Get; + /// The reward type for dex saving. #[pallet::constant] type StableCurrencyId: Get; @@ -94,6 +98,10 @@ pub mod module { #[pallet::constant] type RewardsSource: Get; + /// Additional share amount from earning + #[pallet::constant] + type EarnShareBooster: Get; + /// The origin which may update incentive related params type UpdateOrigin: EnsureOrigin; @@ -576,16 +584,14 @@ impl DEXIncentives for Pallet { pub struct OnUpdateLoan(sp_std::marker::PhantomData); impl Happened<(T::AccountId, CurrencyId, Amount, Balance)> for OnUpdateLoan { fn happened(info: &(T::AccountId, CurrencyId, Amount, Balance)) { - let (who, currency_id, adjustment, previous_amount) = info; + let (who, currency_id, adjustment, _previous_amount) = info; let adjustment_abs = TryInto::::try_into(adjustment.saturating_abs()).unwrap_or_default(); - let new_share_amount = if adjustment.is_positive() { - previous_amount.saturating_add(adjustment_abs) + if adjustment.is_positive() { + >::add_share(who, &PoolId::Loans(*currency_id), adjustment_abs); } else { - previous_amount.saturating_sub(adjustment_abs) + >::remove_share(who, &PoolId::Loans(*currency_id), adjustment_abs); }; - - >::set_share(who, &PoolId::Loans(*currency_id), new_share_amount); } } @@ -605,3 +611,19 @@ impl RewardHandler for Pallet { }); } } + +pub struct OnEarningBonded(sp_std::marker::PhantomData); +impl Happened<(T::AccountId, Balance)> for OnEarningBonded { + fn happened((who, amount): &(T::AccountId, Balance)) { + let share = amount.saturating_add(T::EarnShareBooster::get() * *amount); + >::add_share(who, &PoolId::Loans(T::NativeCurrencyId::get()), share); + } +} + +pub struct OnEarningUnbonded(sp_std::marker::PhantomData); +impl Happened<(T::AccountId, Balance)> for OnEarningUnbonded { + fn happened((who, amount): &(T::AccountId, Balance)) { + let share = amount.saturating_add(T::EarnShareBooster::get() * *amount); + >::remove_share(who, &PoolId::Loans(T::NativeCurrencyId::get()), share); + } +} diff --git a/modules/incentives/src/mock.rs b/modules/incentives/src/mock.rs index f0324faa2..01946f051 100644 --- a/modules/incentives/src/mock.rs +++ b/modules/incentives/src/mock.rs @@ -248,11 +248,13 @@ impl orml_rewards::Config for Runtime { parameter_types! { pub const AccumulatePeriod: BlockNumber = 10; pub const StableCurrencyId: CurrencyId = AUSD; + pub const GetNativeCurrencyId: CurrencyId = ACA; pub const IncentivesPalletId: PalletId = PalletId(*b"aca/inct"); } ord_parameter_types! { pub const Root: AccountId = ROOT::get(); + pub const EarnShareBooster: Permill = Permill::from_percent(50); } impl Config for Runtime { @@ -260,6 +262,8 @@ impl Config for Runtime { type RewardsSource = RewardsSource; type AccumulatePeriod = AccumulatePeriod; type StableCurrencyId = StableCurrencyId; + type NativeCurrencyId = GetNativeCurrencyId; + type EarnShareBooster = EarnShareBooster; type UpdateOrigin = EnsureSignedBy; type CDPTreasury = MockCDPTreasury; type Currency = TokensModule; diff --git a/modules/incentives/src/tests.rs b/modules/incentives/src/tests.rs index 00f46ae0a..ad0b7e2ef 100644 --- a/modules/incentives/src/tests.rs +++ b/modules/incentives/src/tests.rs @@ -436,8 +436,7 @@ fn on_update_loan_works() { (100, Default::default()) ); - // share will be updated even if the adjustment is zero - OnUpdateLoan::::happened(&(ALICE::get(), BTC, 0, 200)); + OnUpdateLoan::::happened(&(ALICE::get(), BTC, 100, 100)); assert_eq!( RewardsModule::pool_infos(PoolId::Loans(BTC)), PoolInfo { @@ -450,7 +449,7 @@ fn on_update_loan_works() { (200, Default::default()) ); - OnUpdateLoan::::happened(&(BOB::get(), BTC, 100, 500)); + OnUpdateLoan::::happened(&(BOB::get(), BTC, 600, 0)); assert_eq!( RewardsModule::pool_infos(PoolId::Loans(BTC)), PoolInfo { @@ -476,7 +475,7 @@ fn on_update_loan_works() { (150, Default::default()) ); - OnUpdateLoan::::happened(&(BOB::get(), BTC, -650, 600)); + OnUpdateLoan::::happened(&(BOB::get(), BTC, -600, 600)); assert_eq!( RewardsModule::pool_infos(PoolId::Loans(BTC)), PoolInfo { @@ -938,3 +937,70 @@ fn on_initialize_should_work() { ); }); } + +#[test] +fn earning_booster_should_work() { + ExtBuilder::default().build().execute_with(|| { + OnUpdateLoan::::happened(&(ALICE::get(), ACA, 100, 0)); + assert_eq!( + RewardsModule::pool_infos(PoolId::Loans(ACA)), + PoolInfo { + total_shares: 100, + ..Default::default() + } + ); + assert_eq!( + RewardsModule::shares_and_withdrawn_rewards(PoolId::Loans(ACA), ALICE::get()), + (100, Default::default()) + ); + + OnEarningBonded::::happened(&(ALICE::get(), 80)); + assert_eq!( + RewardsModule::pool_infos(PoolId::Loans(ACA)), + PoolInfo { + total_shares: 100 + 80 + 40, + ..Default::default() + } + ); + assert_eq!( + RewardsModule::shares_and_withdrawn_rewards(PoolId::Loans(ACA), ALICE::get()), + (100 + 80 + 40, Default::default()) + ); + + OnEarningUnbonded::::happened(&(ALICE::get(), 20)); + assert_eq!( + RewardsModule::pool_infos(PoolId::Loans(ACA)), + PoolInfo { + total_shares: 100 + 60 + 30, + ..Default::default() + } + ); + assert_eq!( + RewardsModule::shares_and_withdrawn_rewards(PoolId::Loans(ACA), ALICE::get()), + (100 + 60 + 30, Default::default()) + ); + + OnUpdateLoan::::happened(&(ALICE::get(), ACA, -100, 100)); + assert_eq!( + RewardsModule::pool_infos(PoolId::Loans(ACA)), + PoolInfo { + total_shares: 60 + 30, + ..Default::default() + } + ); + assert_eq!( + RewardsModule::shares_and_withdrawn_rewards(PoolId::Loans(ACA), ALICE::get()), + (60 + 30, Default::default()) + ); + + OnEarningUnbonded::::happened(&(ALICE::get(), 60)); + assert_eq!( + RewardsModule::pool_infos(PoolId::Loans(ACA)), + PoolInfo { ..Default::default() } + ); + assert_eq!( + RewardsModule::shares_and_withdrawn_rewards(PoolId::Loans(ACA), ALICE::get()), + (0, Default::default()) + ); + }); +} diff --git a/runtime/acala/src/lib.rs b/runtime/acala/src/lib.rs index 5fd832513..2049db443 100644 --- a/runtime/acala/src/lib.rs +++ b/runtime/acala/src/lib.rs @@ -1229,12 +1229,15 @@ impl orml_rewards::Config for Runtime { parameter_types! { pub const AccumulatePeriod: BlockNumber = MINUTES; + pub const EarnShareBooster: Permill = Permill::from_percent(30); } impl module_incentives::Config for Runtime { type Event = Event; type RewardsSource = UnreleasedNativeVaultAccountId; type StableCurrencyId = GetStableCurrencyId; + type NativeCurrencyId = GetNativeCurrencyId; + type EarnShareBooster = EarnShareBooster; type AccumulatePeriod = AccumulatePeriod; type UpdateOrigin = EnsureRootOrThreeFourthsGeneralCouncil; type CDPTreasury = CdpTreasury; diff --git a/runtime/karura/src/lib.rs b/runtime/karura/src/lib.rs index 907ea0394..12e375450 100644 --- a/runtime/karura/src/lib.rs +++ b/runtime/karura/src/lib.rs @@ -1245,12 +1245,15 @@ impl orml_rewards::Config for Runtime { parameter_types! { pub const AccumulatePeriod: BlockNumber = MINUTES; + pub const EarnShareBooster: Permill = Permill::from_percent(30); } impl module_incentives::Config for Runtime { type Event = Event; type RewardsSource = UnreleasedNativeVaultAccountId; type StableCurrencyId = GetStableCurrencyId; + type NativeCurrencyId = GetNativeCurrencyId; + type EarnShareBooster = EarnShareBooster; type AccumulatePeriod = AccumulatePeriod; type UpdateOrigin = EnsureRootOrThreeFourthsGeneralCouncil; type CDPTreasury = CdpTreasury; diff --git a/runtime/mandala/src/lib.rs b/runtime/mandala/src/lib.rs index e64fb89d3..ea0952a3e 100644 --- a/runtime/mandala/src/lib.rs +++ b/runtime/mandala/src/lib.rs @@ -1226,9 +1226,9 @@ parameter_types! { impl module_earning::Config for Runtime { type Event = Event; type Currency = Balances; - type OnBonded = (); - type OnUnbonded = (); - type OnUnstakeFee = (); + type OnBonded = module_incentives::OnEarningBonded; + type OnUnbonded = module_incentives::OnEarningUnbonded; + type OnUnstakeFee = Treasury; // fee goes to treasury type MinBond = ConstU128<100>; type UnbondingPeriod = ConstU32<3>; type InstantUnstakeFee = InstantUnstakeFee; @@ -1265,12 +1265,15 @@ impl orml_rewards::Config for Runtime { parameter_types! { pub const AccumulatePeriod: BlockNumber = MINUTES; + pub const EarnShareBooster: Permill = Permill::from_percent(30); } impl module_incentives::Config for Runtime { type Event = Event; type RewardsSource = UnreleasedNativeVaultAccountId; type StableCurrencyId = GetStableCurrencyId; + type NativeCurrencyId = GetNativeCurrencyId; + type EarnShareBooster = EarnShareBooster; type AccumulatePeriod = AccumulatePeriod; type UpdateOrigin = EnsureRootOrThreeFourthsGeneralCouncil; type CDPTreasury = CdpTreasury;