@@ -62,10 +62,10 @@ use frame_support::{
6262 transactional, BoundedBTreeMap ,
6363} ;
6464pub use pallet:: * ;
65- use sp_runtime:: { traits :: Saturating , SaturatedConversion } ;
65+ use sp_runtime:: SaturatedConversion ;
6666use 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.
16711668pub ( 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
17831749pub ( 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
17961759pub ( crate ) fn claim_of_stake < T : Config > (
0 commit comments