Skip to content

Commit 88bb9eb

Browse files
authored
[CU-2vxvfbd] Remove max_rewards from Rewards and add 'pause' logic for accumulation (#2126)
2 parents 1e61a84 + 0f27207 commit 88bb9eb

File tree

10 files changed

+381
-306
lines changed

10 files changed

+381
-306
lines changed

code/parachain/frame/composable-traits/src/dex.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,6 @@ pub trait Amm {
145145
) -> Result<SwapResult<Self::AssetId, Self::Balance>, DispatchError>;
146146
}
147147

148-
// TODO: Perhaps we need a way to not have a max reward for a pool.
149-
pub const MAX_REWARDS: u128 = 100_000_000_000_000_000_000_000_u128;
150148
pub const REWARD_PERCENTAGE: u32 = 10;
151149

152150
/// Pool Fees

code/parachain/frame/composable-traits/src/staking/mod.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@ pub struct Reward<Balance> {
3333
/// of all of the stakes in the pool.
3434
pub total_dilution_adjustment: Balance,
3535

36-
/// Upper bound on the `total_rewards - total_dilution_adjustment`.
37-
pub max_rewards: Balance,
38-
3936
/// The rewarding rate that increases the pool `total_reward`
4037
/// at a given time.
4138
pub reward_rate: RewardRate<Balance>,
@@ -87,7 +84,6 @@ impl<Balance: Zero> Reward<Balance> {
8784
total_rewards: Zero::zero(),
8885
claimed_rewards: Zero::zero(),
8986
total_dilution_adjustment: Zero::zero(),
90-
max_rewards: reward_config.max_rewards,
9187
reward_rate: reward_config.reward_rate,
9288
last_updated_timestamp: now_seconds,
9389
}
@@ -136,15 +132,9 @@ pub struct RewardPool<
136132
pub minimum_staking_amount: Balance,
137133
}
138134

139-
/// Default transfer limit on new asset added as rewards.
140-
pub const DEFAULT_MAX_REWARDS: u128 = 1_000_000_000_000_000_000_u128;
141-
142135
/// Reward configurations for a given asset type.
143136
#[derive(RuntimeDebug, PartialEq, Eq, Clone, MaxEncodedLen, Encode, Decode, TypeInfo)]
144137
pub struct RewardConfig<Balance> {
145-
/// Upper bound on the `total_rewards - total_dilution_adjustment`.
146-
pub max_rewards: Balance,
147-
148138
/// The rewarding rate that increases the pool `total_reward`
149139
/// at a given time.
150140
pub reward_rate: RewardRate<Balance>,

code/parachain/frame/pablo/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub mod pallet {
7070
defi::{CurrencyPair, Rate},
7171
dex::{
7272
Amm, BasicPoolInfo, Fee, PriceAggregate, RedeemableAssets,
73-
RemoveLiquiditySimulationResult, MAX_REWARDS,
73+
RemoveLiquiditySimulationResult,
7474
},
7575
staking::{
7676
lock::LockConfig, ManageStaking, ProtocolStaking, RewardConfig,
@@ -569,12 +569,11 @@ pub mod pallet {
569569
>,
570570
DispatchError,
571571
> {
572-
let max_rewards: T::Balance = T::Convert::convert(MAX_REWARDS);
573572
// let reward_rate = Perbill::from_percent(REWARD_PERCENTAGE); not sure how this
574573
// translates to the new model
575574
let reward_rate = RewardRate::per_second(T::Convert::convert(0));
576575
let pblo_asset_id: T::AssetId = T::PbloAssetId::get();
577-
let reward_configs = [(pblo_asset_id, RewardConfig { max_rewards, reward_rate })]
576+
let reward_configs = [(pblo_asset_id, RewardConfig { reward_rate })]
578577
.into_iter()
579578
.try_collect()
580579
.map_err(|_| Error::<T>::StakingPoolConfigError)?;

code/parachain/frame/staking-rewards/src/benchmarking.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,7 @@ fn reward_config<T: Config>(
6767
(0..reward_count)
6868
.map(|asset_id| {
6969
let asset_id = (asset_id as u128) + BASE_ASSET_ID;
70-
(
71-
asset_id.into(),
72-
RewardConfig {
73-
max_rewards: 100_u128.into(),
74-
reward_rate: RewardRate::per_second(10_u128),
75-
},
76-
)
70+
(asset_id.into(), RewardConfig { reward_rate: RewardRate::per_second(10_u128) })
7771
})
7872
.try_collect()
7973
.unwrap()
@@ -234,7 +228,6 @@ benchmarks! {
234228
let reward_asset_id = 1_u128.into();
235229

236230
let reward_config = RewardConfig {
237-
max_rewards: 1_000_000.into(),
238231
reward_rate: RewardRate::per_second(10_000),
239232
};
240233

code/parachain/frame/staking-rewards/src/lib.rs

Lines changed: 66 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ use frame_support::{
6262
transactional, BoundedBTreeMap,
6363
};
6464
pub use pallet::*;
65-
use sp_runtime::{traits::Saturating, SaturatedConversion};
65+
use sp_runtime::SaturatedConversion;
6666
use sp_std::{
6767
cmp,
68-
ops::{Div, Sub},
68+
ops::{Div, Mul},
6969
};
7070

7171
#[frame_support::pallet]
@@ -81,7 +81,6 @@ pub mod pallet {
8181
fnft::{FinancialNft, FinancialNftProtocol},
8282
staking::{
8383
lock::LockConfig, RewardPoolConfiguration::RewardRateBasedIncentive, RewardRatePeriod,
84-
DEFAULT_MAX_REWARDS,
8584
},
8685
time::{DurationSeconds, ONE_MONTH, ONE_WEEK},
8786
};
@@ -114,7 +113,7 @@ pub mod pallet {
114113
traits::{AccountIdConversion, BlockNumberProvider, One},
115114
ArithmeticError, PerThing,
116115
};
117-
use sp_std::{cmp::max, fmt::Debug, ops::Mul, vec, vec::Vec};
116+
use sp_std::{fmt::Debug, ops::Mul, vec, vec::Vec};
118117

119118
use crate::{
120119
add_to_rewards_pot, claim_of_stake, do_reward_accumulation, prelude::*,
@@ -194,10 +193,6 @@ pub mod pallet {
194193
asset_id: T::AssetId,
195194
error: RewardAccumulationHookError,
196195
},
197-
MaxRewardsAccumulated {
198-
pool_id: T::AssetId,
199-
asset_id: T::AssetId,
200-
},
201196
RewardPoolUpdated {
202197
pool_id: T::AssetId,
203198
},
@@ -219,6 +214,7 @@ pub mod pallet {
219214
pub enum RewardAccumulationHookError {
220215
BackToTheFuture,
221216
RewardsPotEmpty,
217+
ArithmeticError,
222218
}
223219

224220
#[pallet::error]
@@ -271,6 +267,8 @@ pub mod pallet {
271267
StakedAmountTooLow,
272268
/// Staked amount after split is less than the minimum staking amount for the pool.
273269
StakedAmountTooLowAfterSplit,
270+
/// Some operation resulted in an arithmetic overflow.
271+
ArithmeticError,
274272
}
275273

276274
pub(crate) type AssetIdOf<T> = <T as Config>::AssetId;
@@ -1470,7 +1468,13 @@ pub mod pallet {
14701468

14711469
let pool_account = Self::pool_account_id(&pool_id);
14721470

1473-
match do_reward_accumulation::<T>(reward_asset_id, reward, &pool_account, now_seconds) {
1471+
match do_reward_accumulation::<T>(
1472+
pool_id,
1473+
reward_asset_id,
1474+
reward,
1475+
&pool_account,
1476+
now_seconds,
1477+
) {
14741478
Ok(()) => {},
14751479
Err(BackToTheFuture) => {
14761480
Self::deposit_event(Event::<T>::RewardAccumulationHookError {
@@ -1489,16 +1493,13 @@ pub mod pallet {
14891493
});
14901494
}
14911495
},
1492-
Err(MaxRewardsAccumulated) => {
1493-
Self::deposit_event(Event::<T>::MaxRewardsAccumulated {
1496+
Err(ArithmeticError) => {
1497+
Self::deposit_event(Event::<T>::RewardAccumulationHookError {
14941498
pool_id,
14951499
asset_id: reward_asset_id,
1500+
error: RewardAccumulationHookError::ArithmeticError,
14961501
});
14971502
},
1498-
Err(MaxRewardsAccumulatedPreviously) => {
1499-
// max rewards were accumulated previously, silently continue since the
1500-
// MaxRewardsAccumulated event has already been emitted for this pool
1501-
},
15021503
}
15031504
}
15041505

@@ -1569,7 +1570,6 @@ pub mod pallet {
15691570
total_rewards: amount,
15701571
claimed_rewards: Zero::zero(),
15711572
total_dilution_adjustment: T::Balance::zero(),
1572-
max_rewards: max(amount, DEFAULT_MAX_REWARDS.into()),
15731573
reward_rate: RewardRate {
15741574
amount: T::Balance::zero(),
15751575
period: RewardRatePeriod::PerSecond,
@@ -1605,22 +1605,25 @@ fn add_to_rewards_pot<T: Config>(
16051605
amount: T::Balance,
16061606
keep_alive: bool,
16071607
) -> DispatchResult {
1608-
RewardPools::<T>::get(pool_id)
1609-
.ok_or(Error::<T>::RewardsPoolNotFound)?
1610-
.rewards
1611-
.get(&asset_id)
1612-
.ok_or(Error::<T>::RewardAssetNotFound)?;
1608+
RewardPools::<T>::try_mutate(pool_id, |pool| {
1609+
let pool = pool.as_mut().ok_or(Error::<T>::RewardsPoolNotFound)?;
16131610

1614-
let pool_account = Pallet::<T>::pool_account_id(&pool_id);
1611+
let reward = pool.rewards.get_mut(&asset_id).ok_or(Error::<T>::RewardAssetNotFound)?;
16151612

1616-
T::Assets::transfer(asset_id, &who, &pool_account, amount, keep_alive)?;
1617-
T::Assets::hold(asset_id, &pool_account, amount)?;
1613+
if RewardsPotIsEmpty::<T>::contains_key(pool_id, asset_id) {
1614+
reward.last_updated_timestamp = T::UnixTime::now().as_secs();
1615+
RewardsPotIsEmpty::<T>::remove(pool_id, asset_id);
1616+
}
1617+
1618+
let pool_account = Pallet::<T>::pool_account_id(&pool_id);
16181619

1619-
RewardsPotIsEmpty::<T>::remove(pool_id, asset_id);
1620+
T::Assets::transfer(asset_id, &who, &pool_account, amount, keep_alive)?;
1621+
T::Assets::hold(asset_id, &pool_account, amount)?;
16201622

1621-
Pallet::<T>::deposit_event(Event::<T>::RewardsPotIncreased { pool_id, asset_id, amount });
1623+
Pallet::<T>::deposit_event(Event::<T>::RewardsPotIncreased { pool_id, asset_id, amount });
16221624

1623-
Ok(())
1625+
Ok(())
1626+
})
16241627
}
16251628

16261629
#[transactional]
@@ -1641,21 +1644,15 @@ fn update_rewards_pool<T: Config>(
16411644

16421645
for (asset_id, update) in reward_updates {
16431646
let reward = pool.rewards.get_mut(&asset_id).ok_or(Error::<T>::RewardAssetNotFound)?;
1644-
match do_reward_accumulation::<T>(asset_id, reward, &pool_account, now_seconds) {
1647+
match do_reward_accumulation::<T>(pool_id, asset_id, reward, &pool_account, now_seconds)
1648+
{
16451649
Ok(()) => {},
16461650
Err(RewardAccumulationCalculationError::BackToTheFuture) =>
16471651
return Err(Error::<T>::BackToTheFuture.into()),
1648-
Err(RewardAccumulationCalculationError::MaxRewardsAccumulated) => {
1649-
Pallet::<T>::deposit_event(Event::<T>::MaxRewardsAccumulated {
1650-
pool_id,
1651-
asset_id,
1652-
});
1653-
continue
1654-
},
1655-
Err(RewardAccumulationCalculationError::MaxRewardsAccumulatedPreviously) =>
1656-
continue,
16571652
Err(RewardAccumulationCalculationError::RewardsPotEmpty) =>
16581653
return Err(Error::<T>::RewardsPotEmpty.into()),
1654+
Err(RewardAccumulationCalculationError::ArithmeticError) =>
1655+
return Err(Error::<T>::ArithmeticError.into()),
16591656
}
16601657

16611658
reward.reward_rate = update.reward_rate;
@@ -1669,6 +1666,7 @@ fn update_rewards_pool<T: Config>(
16691666

16701667
/// Calculates the update to the reward and unlocks the accumulated rewards from the pool account.
16711668
pub(crate) fn do_reward_accumulation<T: Config>(
1669+
pool_id: T::AssetId,
16721670
asset_id: T::AssetId,
16731671
reward: &mut Reward<T::Balance>,
16741672
pool_account: &T::AccountId,
@@ -1704,12 +1702,17 @@ pub(crate) fn do_reward_accumulation<T: Config>(
17041702
let releasable_periods_surpassed =
17051703
cmp::min(maximum_releasable_periods, periods_surpassed.into());
17061704

1707-
// saturating is safe here since these values are checked against max_rewards anyways, which
1708-
// is <= u128::MAX
1705+
// SAFETY: Usage of mul is safe here because newly_accumulated_rewards =
1706+
// ( total_locked_rewards elapsed_time )
1707+
// min ( ------------------------- , -------------------------- )
1708+
// ( reward.reward_rate.amount reward_rate_period_seconds )
1709+
// If LHS is smaller, this will result in total_locked_rewards, which we know fits in a
1710+
// u128. If RHS is smaller, this has to be < total_locked_rewards, so it will also fit.
17091711
let newly_accumulated_rewards =
1710-
u128::saturating_mul(releasable_periods_surpassed, reward.reward_rate.amount.into());
1711-
let new_total_rewards =
1712-
newly_accumulated_rewards.saturating_add(reward.total_rewards.into());
1712+
releasable_periods_surpassed.mul(reward.reward_rate.amount.into());
1713+
let new_total_rewards = newly_accumulated_rewards
1714+
.safe_add(&reward.total_rewards.into())
1715+
.map_err(|_| RewardAccumulationCalculationError::ArithmeticError)?;
17131716

17141717
// u64::MAX is roughly 584.9 billion years in the future, so saturating at that should be ok
17151718
let last_updated_timestamp = reward.last_updated_timestamp.saturating_add(
@@ -1718,79 +1721,39 @@ pub(crate) fn do_reward_accumulation<T: Config>(
17181721
.saturating_mul(releasable_periods_surpassed.saturated_into::<u64>()),
17191722
);
17201723

1721-
// TODO(benluelo): This can probably be simplified, review the period calculations
1722-
if u128::saturating_add(new_total_rewards, reward.total_dilution_adjustment.into()) <=
1723-
reward.max_rewards.into()
1724-
{
1725-
let balance_on_hold = T::Assets::balance_on_hold(asset_id, pool_account);
1726-
if !balance_on_hold.is_zero() && balance_on_hold >= newly_accumulated_rewards.into() {
1727-
T::Assets::release(
1728-
asset_id,
1729-
pool_account,
1730-
newly_accumulated_rewards.into(),
1731-
false, // not best effort, entire amount must be released
1732-
)
1733-
.expect("funds should be available to release based on previous check; qed;");
1734-
1735-
reward.total_rewards = new_total_rewards.into();
1736-
reward.last_updated_timestamp = last_updated_timestamp;
1737-
Ok(())
1738-
} else {
1739-
Err(RewardAccumulationCalculationError::RewardsPotEmpty)
1740-
}
1741-
} else if reward.total_rewards.saturating_add(reward.total_dilution_adjustment) <
1742-
reward.max_rewards
1743-
{
1744-
// if the new total rewards are less than or equal to the max rewards AND the current
1745-
// total rewards are less than the max rewards (i.e. the newly accumulated rewards is
1746-
// less than the the amount that would be accumulated based on the periods surpassed),
1747-
// then release *up to* the max rewards
1748-
1749-
// REVIEW(benluelo): Should max_rewards be max_periods instead? Currently, the
1750-
// max_rewards isn't updatable, and once the max_rewards is hit, it's expected that no
1751-
// more rewards will be accumulated, so it's ok to not reward an entire period's worth
1752-
// of rewards. Review this if the max_rewards ever becomes updateable in the future.
1753-
1754-
// SAFETY(benluelo): Usage of Sub::sub: reward.total_rewards is known to be less than
1755-
// reward.max_rewards as per check above
1756-
let rewards_to_release = reward.max_rewards.sub(reward.total_rewards).into();
1757-
1758-
let balance_on_hold = T::Assets::balance_on_hold(asset_id, pool_account);
1759-
if !balance_on_hold.is_zero() && balance_on_hold >= newly_accumulated_rewards.into() {
1760-
T::Assets::release(
1761-
asset_id,
1762-
pool_account,
1763-
rewards_to_release.into(),
1764-
false, // not best effort, entire amount must be released
1765-
)
1766-
.expect("funds should be available to release based on previous check; qed;");
1767-
1768-
// return an error, but update the reward first
1769-
reward.total_rewards = reward.max_rewards;
1770-
reward.last_updated_timestamp = last_updated_timestamp;
1771-
Err(RewardAccumulationCalculationError::MaxRewardsAccumulated)
1772-
} else {
1773-
Err(RewardAccumulationCalculationError::RewardsPotEmpty)
1724+
if !newly_accumulated_rewards.is_zero() {
1725+
T::Assets::release(
1726+
asset_id,
1727+
pool_account,
1728+
newly_accumulated_rewards.into(),
1729+
false, // not best effort, entire amount must be released
1730+
)
1731+
.expect("funds should be available to release based on previous check; qed;");
1732+
1733+
reward.total_rewards = new_total_rewards.into();
1734+
reward.last_updated_timestamp = last_updated_timestamp;
1735+
1736+
let new_balance_on_hold = T::Assets::balance_on_hold(asset_id, pool_account);
1737+
1738+
if new_balance_on_hold.into().is_zero() {
1739+
RewardsPotIsEmpty::<T>::insert(pool_id, asset_id, ());
17741740
}
1741+
1742+
Ok(())
17751743
} else {
1776-
// at this point, reward.total_rewards is known to be equal to max_rewards which means
1777-
// that the max rewards was hit previously
1778-
Err(RewardAccumulationCalculationError::MaxRewardsAccumulatedPreviously)
1744+
Err(RewardAccumulationCalculationError::RewardsPotEmpty)
17791745
}
17801746
}
17811747
}
17821748

17831749
pub(crate) enum RewardAccumulationCalculationError {
17841750
/// T::UnixTime::now() returned a value in the past.
17851751
BackToTheFuture,
1786-
/// The `max_rewards` for this reward was hit during this calculation.
1787-
MaxRewardsAccumulated,
1788-
/// The `max_rewards` were hit previously; i.e. `total_rewards == max_rewards` at the start of
1789-
/// this calculation.
1790-
MaxRewardsAccumulatedPreviously,
17911752
/// The rewards pot (held balance) for this pool is empty or doesn't have enough held balance
17921753
/// to release for the rewards accumulated.
17931754
RewardsPotEmpty,
1755+
/// Some operation resulted in an arithmetic error.
1756+
ArithmeticError,
17941757
}
17951758

17961759
pub(crate) fn claim_of_stake<T: Config>(

0 commit comments

Comments
 (0)