1
1
#![ allow( clippy:: unused_unit) ]
2
2
#![ cfg_attr( not( feature = "std" ) , no_std) ]
3
3
4
+ pub mod migrations;
4
5
mod mock;
5
6
mod tests;
6
7
7
8
use codec:: { FullCodec , HasCompact , MaxEncodedLen } ;
8
- use frame_support:: pallet_prelude:: * ;
9
+ use frame_support:: { pallet_prelude:: * , weights:: Weight } ;
10
+ pub use migrations:: PoolInfoV0 ;
11
+ use orml_traits:: RewardHandler ;
12
+ use sp_core:: U256 ;
9
13
use sp_runtime:: {
10
- traits:: { AtLeast32BitUnsigned , Bounded , MaybeSerializeDeserialize , Member , Saturating , Zero } ,
11
- FixedPointNumber , FixedPointOperand , FixedU128 , RuntimeDebug ,
12
- } ;
13
- use sp_std:: {
14
- cmp:: { Eq , PartialEq } ,
15
- fmt:: Debug ,
14
+ traits:: { AtLeast32BitUnsigned , Convert , MaybeSerializeDeserialize , Member , Saturating , UniqueSaturatedInto , Zero } ,
15
+ FixedPointOperand , RuntimeDebug , SaturatedConversion ,
16
16
} ;
17
-
18
- use orml_traits:: RewardHandler ;
17
+ use sp_std:: { borrow:: ToOwned , collections:: btree_map:: BTreeMap , fmt:: Debug , prelude:: * } ;
19
18
20
19
/// The Reward Pool Info.
21
- #[ derive( Clone , Encode , Decode , PartialEq , Eq , RuntimeDebug , Default , MaxEncodedLen ) ]
22
- pub struct PoolInfo < Share : HasCompact , Balance : HasCompact > {
20
+ #[ derive( Clone , Encode , Decode , PartialEq , Eq , RuntimeDebug ) ]
21
+ pub struct PoolInfo < Share : HasCompact , Balance : HasCompact , CurrencyId : Ord > {
23
22
/// Total shares amount
24
- #[ codec( compact) ]
25
23
pub total_shares : Share ,
26
- /// Total rewards amount
27
- #[ codec( compact) ]
28
- pub total_rewards : Balance ,
29
- /// Total withdrawn rewards amount
30
- #[ codec( compact) ]
31
- pub total_withdrawn_rewards : Balance ,
24
+ /// Reward infos <reward_currency, (total_reward, total_withdrawn_reward)>
25
+ pub rewards : BTreeMap < CurrencyId , ( Balance , Balance ) > ,
26
+ }
27
+
28
+ impl < Share , Balance , CurrencyId > Default for PoolInfo < Share , Balance , CurrencyId >
29
+ where
30
+ Share : Default + HasCompact ,
31
+ Balance : HasCompact ,
32
+ CurrencyId : Ord ,
33
+ {
34
+ fn default ( ) -> Self {
35
+ Self {
36
+ total_shares : Default :: default ( ) ,
37
+ rewards : BTreeMap :: new ( ) ,
38
+ }
39
+ }
32
40
}
33
41
34
42
pub use module:: * ;
@@ -59,24 +67,64 @@ pub mod module {
59
67
+ Debug
60
68
+ FixedPointOperand ;
61
69
70
+ /// The old version of reward pool ID type.
71
+ /// NOTE: remove it after migration
72
+ type PoolIdV0 : Parameter + Member + Clone + FullCodec ;
73
+
74
+ /// The convertor to convert PoolIdV0 to PoolId
75
+ /// NOTE: remove it after migration
76
+ type PoolIdConvertor : Convert < Self :: PoolIdV0 , Option < Self :: PoolId > > ;
77
+
62
78
/// The reward pool ID type.
63
79
type PoolId : Parameter + Member + Clone + FullCodec ;
64
80
81
+ type CurrencyId : Parameter + Member + Copy + MaybeSerializeDeserialize + Ord ;
82
+
65
83
/// The `RewardHandler`
66
- type Handler : RewardHandler < Self :: AccountId , Balance = Self :: Balance , PoolId = Self :: PoolId > ;
84
+ type Handler : RewardHandler < Self :: AccountId , Self :: CurrencyId , Balance = Self :: Balance , PoolId = Self :: PoolId > ;
85
+ }
86
+
87
+ #[ pallet:: error]
88
+ pub enum Error < T > {
89
+ /// Pool does not exist
90
+ PoolDoesNotExist ,
67
91
}
68
92
69
93
/// Stores reward pool info.
94
+ /// NOTE: remove it after migration
70
95
#[ pallet:: storage]
71
96
#[ pallet:: getter( fn pools) ]
72
- pub type Pools < T : Config > = StorageMap < _ , Twox64Concat , T :: PoolId , PoolInfo < T :: Share , T :: Balance > , ValueQuery > ;
97
+ pub type Pools < T : Config > = StorageMap < _ , Twox64Concat , T :: PoolIdV0 , PoolInfoV0 < T :: Share , T :: Balance > , ValueQuery > ;
98
+
99
+ /// Record reward pool info.
100
+ #[ pallet:: storage]
101
+ #[ pallet:: getter( fn pool_infos) ]
102
+ pub type PoolInfos < T : Config > =
103
+ StorageMap < _ , Twox64Concat , T :: PoolId , PoolInfo < T :: Share , T :: Balance , T :: CurrencyId > , ValueQuery > ;
73
104
74
105
/// Record share amount and withdrawn reward amount for specific `AccountId`
75
106
/// under `PoolId`.
107
+ /// NOTE: remove it after migration
76
108
#[ pallet:: storage]
77
109
#[ pallet:: getter( fn share_and_withdrawn_reward) ]
78
110
pub type ShareAndWithdrawnReward < T : Config > =
79
- StorageDoubleMap < _ , Twox64Concat , T :: PoolId , Twox64Concat , T :: AccountId , ( T :: Share , T :: Balance ) , ValueQuery > ;
111
+ StorageDoubleMap < _ , Twox64Concat , T :: PoolIdV0 , Twox64Concat , T :: AccountId , ( T :: Share , T :: Balance ) , ValueQuery > ;
112
+
113
+ /// Record share amount, reward currency and withdrawn reward amount for
114
+ /// specific `AccountId` under `PoolId`.
115
+ ///
116
+ /// double_map (PoolId, AccountId) => (Share, BTreeMap<CurrencyId, Balance>)
117
+ #[ pallet:: storage]
118
+ #[ pallet:: getter( fn shares_and_withdrawn_rewards) ]
119
+ pub type SharesAndWithdrawnRewards < T : Config > = StorageDoubleMap <
120
+ _ ,
121
+ Twox64Concat ,
122
+ T :: PoolId ,
123
+ Twox64Concat ,
124
+ T :: AccountId ,
125
+ ( T :: Share , BTreeMap < T :: CurrencyId , T :: Balance > ) ,
126
+ ValueQuery ,
127
+ > ;
80
128
81
129
#[ pallet:: pallet]
82
130
pub struct Pallet < T > ( _ ) ;
@@ -89,35 +137,73 @@ pub mod module {
89
137
}
90
138
91
139
impl < T : Config > Pallet < T > {
92
- pub fn accumulate_reward ( pool : & T :: PoolId , reward_increment : T :: Balance ) {
93
- if !reward_increment. is_zero ( ) {
94
- Pools :: < T > :: mutate ( pool, |pool_info| {
95
- pool_info. total_rewards = pool_info. total_rewards . saturating_add ( reward_increment)
96
- } ) ;
140
+ pub fn accumulate_reward (
141
+ pool : & T :: PoolId ,
142
+ reward_currency : T :: CurrencyId ,
143
+ reward_increment : T :: Balance ,
144
+ ) -> DispatchResult {
145
+ if reward_increment. is_zero ( ) {
146
+ return Ok ( ( ) ) ;
97
147
}
148
+ PoolInfos :: < T > :: mutate_exists ( pool, |maybe_pool_info| -> DispatchResult {
149
+ let pool_info = maybe_pool_info. as_mut ( ) . ok_or ( Error :: < T > :: PoolDoesNotExist ) ?;
150
+
151
+ pool_info
152
+ . rewards
153
+ . entry ( reward_currency)
154
+ . and_modify ( |( total_reward, _) | {
155
+ * total_reward = total_reward. saturating_add ( reward_increment) ;
156
+ } )
157
+ . or_insert ( ( reward_increment, Zero :: zero ( ) ) ) ;
158
+
159
+ Ok ( ( ) )
160
+ } )
98
161
}
99
162
100
163
pub fn add_share ( who : & T :: AccountId , pool : & T :: PoolId , add_amount : T :: Share ) {
101
164
if add_amount. is_zero ( ) {
102
165
return ;
103
166
}
104
167
105
- Pools :: < T > :: mutate ( pool, |pool_info| {
106
- let reward_inflation = if pool_info. total_shares . is_zero ( ) {
107
- Zero :: zero ( )
108
- } else {
109
- let proportion = FixedU128 :: checked_from_rational ( add_amount, pool_info. total_shares )
110
- . unwrap_or_else ( FixedU128 :: max_value) ;
111
- proportion. saturating_mul_int ( pool_info. total_rewards )
112
- } ;
113
-
168
+ PoolInfos :: < T > :: mutate ( pool, |pool_info| {
169
+ let initial_total_shares = pool_info. total_shares ;
114
170
pool_info. total_shares = pool_info. total_shares . saturating_add ( add_amount) ;
115
- pool_info. total_rewards = pool_info. total_rewards . saturating_add ( reward_inflation) ;
116
- pool_info. total_withdrawn_rewards = pool_info. total_withdrawn_rewards . saturating_add ( reward_inflation) ;
117
171
118
- ShareAndWithdrawnReward :: < T > :: mutate ( pool, who, |( share, withdrawn_rewards) | {
172
+ let mut withdrawn_inflation = Vec :: < ( T :: CurrencyId , T :: Balance ) > :: new ( ) ;
173
+
174
+ pool_info
175
+ . rewards
176
+ . iter_mut ( )
177
+ . for_each ( |( reward_currency, ( total_reward, total_withdrawn_reward) ) | {
178
+ let reward_inflation = if initial_total_shares. is_zero ( ) {
179
+ Zero :: zero ( )
180
+ } else {
181
+ U256 :: from ( add_amount. to_owned ( ) . saturated_into :: < u128 > ( ) )
182
+ . saturating_mul ( total_reward. to_owned ( ) . saturated_into :: < u128 > ( ) . into ( ) )
183
+ . checked_div ( initial_total_shares. to_owned ( ) . saturated_into :: < u128 > ( ) . into ( ) )
184
+ . unwrap_or_default ( )
185
+ . as_u128 ( )
186
+ . saturated_into ( )
187
+ } ;
188
+ * total_reward = total_reward. saturating_add ( reward_inflation) ;
189
+ * total_withdrawn_reward = total_withdrawn_reward. saturating_add ( reward_inflation) ;
190
+
191
+ withdrawn_inflation. push ( ( * reward_currency, reward_inflation) ) ;
192
+ } ) ;
193
+
194
+ SharesAndWithdrawnRewards :: < T > :: mutate ( pool, who, |( share, withdrawn_rewards) | {
119
195
* share = share. saturating_add ( add_amount) ;
120
- * withdrawn_rewards = withdrawn_rewards. saturating_add ( reward_inflation) ;
196
+ // update withdrawn inflation for each reward currency
197
+ withdrawn_inflation
198
+ . into_iter ( )
199
+ . for_each ( |( reward_currency, reward_inflation) | {
200
+ withdrawn_rewards
201
+ . entry ( reward_currency)
202
+ . and_modify ( |withdrawn_reward| {
203
+ * withdrawn_reward = withdrawn_reward. saturating_add ( reward_inflation) ;
204
+ } )
205
+ . or_insert ( reward_inflation) ;
206
+ } ) ;
121
207
} ) ;
122
208
} ) ;
123
209
}
@@ -130,26 +216,50 @@ impl<T: Config> Pallet<T> {
130
216
// claim rewards firstly
131
217
Self :: claim_rewards ( who, pool) ;
132
218
133
- ShareAndWithdrawnReward :: < T > :: mutate_exists ( pool, who, |share_info| {
219
+ SharesAndWithdrawnRewards :: < T > :: mutate_exists ( pool, who, |share_info| {
134
220
if let Some ( ( mut share, mut withdrawn_rewards) ) = share_info. take ( ) {
135
221
let remove_amount = remove_amount. min ( share) ;
136
222
137
223
if remove_amount. is_zero ( ) {
138
224
return ;
139
225
}
140
226
141
- Pools :: < T > :: mutate ( pool, |pool_info| {
142
- let proportion = FixedU128 :: checked_from_rational ( remove_amount, share)
143
- . expect ( "share is gte remove_amount and not zero which checked before; qed" ) ;
144
- let withdrawn_rewards_to_remove = proportion. saturating_mul_int ( withdrawn_rewards) ;
145
-
146
- pool_info. total_shares = pool_info. total_shares . saturating_sub ( remove_amount) ;
147
- pool_info. total_rewards = pool_info. total_rewards . saturating_sub ( withdrawn_rewards_to_remove) ;
148
- pool_info. total_withdrawn_rewards = pool_info
149
- . total_withdrawn_rewards
150
- . saturating_sub ( withdrawn_rewards_to_remove) ;
151
-
152
- withdrawn_rewards = withdrawn_rewards. saturating_sub ( withdrawn_rewards_to_remove) ;
227
+ PoolInfos :: < T > :: mutate_exists ( pool, |maybe_pool_info| {
228
+ if let Some ( mut pool_info) = maybe_pool_info. take ( ) {
229
+ let removing_share = U256 :: from ( remove_amount. saturated_into :: < u128 > ( ) ) ;
230
+
231
+ pool_info. total_shares = pool_info. total_shares . saturating_sub ( remove_amount) ;
232
+
233
+ // update withdrawn rewards for each reward currency
234
+ withdrawn_rewards
235
+ . iter_mut ( )
236
+ . for_each ( |( reward_currency, withdrawn_reward) | {
237
+ let withdrawn_reward_to_remove: T :: Balance = removing_share
238
+ . saturating_mul ( withdrawn_reward. to_owned ( ) . saturated_into :: < u128 > ( ) . into ( ) )
239
+ . checked_div ( share. saturated_into :: < u128 > ( ) . into ( ) )
240
+ . unwrap_or_default ( )
241
+ . as_u128 ( )
242
+ . saturated_into ( ) ;
243
+
244
+ if let Some ( ( total_reward, total_withdrawn_reward) ) =
245
+ pool_info. rewards . get_mut ( reward_currency)
246
+ {
247
+ * total_reward = total_reward. saturating_sub ( withdrawn_reward_to_remove) ;
248
+ * total_withdrawn_reward =
249
+ total_withdrawn_reward. saturating_sub ( withdrawn_reward_to_remove) ;
250
+
251
+ // remove if all reward is withdrawn
252
+ if total_reward. is_zero ( ) {
253
+ pool_info. rewards . remove ( reward_currency) ;
254
+ }
255
+ }
256
+ * withdrawn_reward = withdrawn_reward. saturating_sub ( withdrawn_reward_to_remove) ;
257
+ } ) ;
258
+
259
+ if !pool_info. total_shares . is_zero ( ) {
260
+ * maybe_pool_info = Some ( pool_info) ;
261
+ }
262
+ }
153
263
} ) ;
154
264
155
265
share = share. saturating_sub ( remove_amount) ;
@@ -161,7 +271,7 @@ impl<T: Config> Pallet<T> {
161
271
}
162
272
163
273
pub fn set_share ( who : & T :: AccountId , pool : & T :: PoolId , new_share : T :: Share ) {
164
- let ( share, _) = Self :: share_and_withdrawn_reward ( pool, who) ;
274
+ let ( share, _) = Self :: shares_and_withdrawn_rewards ( pool, who) ;
165
275
166
276
if new_share > share {
167
277
Self :: add_share ( who, pool, new_share. saturating_sub ( share) ) ;
@@ -171,33 +281,44 @@ impl<T: Config> Pallet<T> {
171
281
}
172
282
173
283
pub fn claim_rewards ( who : & T :: AccountId , pool : & T :: PoolId ) {
174
- ShareAndWithdrawnReward :: < T > :: mutate ( pool, who, |( share, withdrawn_rewards) | {
175
- if share. is_zero ( ) {
176
- return ;
177
- }
178
-
179
- Pools :: < T > :: mutate ( pool, |pool_info| {
180
- let proportion = FixedU128 :: checked_from_rational ( * share, pool_info. total_shares ) . unwrap_or_default ( ) ;
181
- let reward_to_withdraw = proportion
182
- . saturating_mul_int ( pool_info. total_rewards )
183
- . saturating_sub ( * withdrawn_rewards)
184
- . min (
185
- pool_info
186
- . total_rewards
187
- . saturating_sub ( pool_info. total_withdrawn_rewards ) ,
188
- ) ;
189
-
190
- if reward_to_withdraw. is_zero ( ) {
284
+ SharesAndWithdrawnRewards :: < T > :: mutate_exists ( pool, who, |maybe_share_withdrawn| {
285
+ if let Some ( ( share, withdrawn_rewards) ) = maybe_share_withdrawn {
286
+ if share. is_zero ( ) {
191
287
return ;
192
288
}
193
289
194
- pool_info. total_withdrawn_rewards =
195
- pool_info. total_withdrawn_rewards . saturating_add ( reward_to_withdraw) ;
196
- * withdrawn_rewards = withdrawn_rewards. saturating_add ( reward_to_withdraw) ;
197
-
198
- // pay reward to `who`
199
- T :: Handler :: payout ( who, pool, reward_to_withdraw) ;
200
- } ) ;
290
+ PoolInfos :: < T > :: mutate ( pool, |pool_info| {
291
+ let total_shares = U256 :: from ( pool_info. total_shares . to_owned ( ) . saturated_into :: < u128 > ( ) ) ;
292
+ pool_info. rewards . iter_mut ( ) . for_each (
293
+ |( reward_currency, ( total_reward, total_withdrawn_reward) ) | {
294
+ let withdrawn_reward = withdrawn_rewards. get ( reward_currency) . copied ( ) . unwrap_or_default ( ) ;
295
+
296
+ let total_reward_proportion: T :: Balance =
297
+ U256 :: from ( share. to_owned ( ) . saturated_into :: < u128 > ( ) )
298
+ . saturating_mul ( U256 :: from ( total_reward. to_owned ( ) . saturated_into :: < u128 > ( ) ) )
299
+ . checked_div ( total_shares)
300
+ . unwrap_or_default ( )
301
+ . as_u128 ( )
302
+ . unique_saturated_into ( ) ;
303
+
304
+ let reward_to_withdraw = total_reward_proportion
305
+ . saturating_sub ( withdrawn_reward)
306
+ . min ( total_reward. saturating_sub ( * total_withdrawn_reward) ) ;
307
+
308
+ if reward_to_withdraw. is_zero ( ) {
309
+ return ;
310
+ }
311
+
312
+ * total_withdrawn_reward = total_withdrawn_reward. saturating_add ( reward_to_withdraw) ;
313
+ withdrawn_rewards
314
+ . insert ( * reward_currency, withdrawn_reward. saturating_add ( reward_to_withdraw) ) ;
315
+
316
+ // pay reward to `who`
317
+ T :: Handler :: payout ( who, pool, * reward_currency, reward_to_withdraw) ;
318
+ } ,
319
+ ) ;
320
+ } ) ;
321
+ }
201
322
} ) ;
202
323
}
203
324
}
0 commit comments