pallet_parachain_staking/
auto_compound.rs1use crate::pallet::{
20 AddGet, AutoCompoundingDelegations as AutoCompoundingDelegationsStorage, BalanceOf,
21 CandidateInfo, Config, DelegatorState, Error, Event, Pallet, Total,
22};
23use crate::types::{Bond, BondAdjust, Delegator};
24use frame_support::dispatch::DispatchResultWithPostInfo;
25use frame_support::ensure;
26use frame_support::traits::Get;
27use parity_scale_codec::{Decode, Encode};
28use scale_info::TypeInfo;
29use sp_runtime::traits::Saturating;
30use sp_runtime::{BoundedVec, Percent, RuntimeDebug};
31use sp_std::prelude::*;
32
33#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, PartialOrd, Ord)]
35pub struct AutoCompoundConfig<AccountId> {
36 pub delegator: AccountId,
37 pub value: Percent,
38}
39
40#[derive(Clone, Eq, PartialEq, RuntimeDebug)]
42pub struct AutoCompoundDelegations<T: Config>(
43 BoundedVec<
44 AutoCompoundConfig<T::AccountId>,
45 AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
46 >,
47);
48
49impl<T> AutoCompoundDelegations<T>
50where
51 T: Config,
52{
53 #[cfg(test)]
56 pub fn new(
57 sorted_delegations: BoundedVec<
58 AutoCompoundConfig<T::AccountId>,
59 AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
60 >,
61 ) -> Self {
62 Self(sorted_delegations)
63 }
64
65 pub fn get_auto_compounding_delegation_count(candidate: &T::AccountId) -> usize {
66 <AutoCompoundingDelegationsStorage<T>>::decode_len(candidate).unwrap_or_default()
67 }
68
69 pub fn get_storage(candidate: &T::AccountId) -> Self {
71 Self(<AutoCompoundingDelegationsStorage<T>>::get(candidate))
72 }
73
74 pub fn set_storage(self, candidate: &T::AccountId) {
76 <AutoCompoundingDelegationsStorage<T>>::insert(candidate, self.0)
77 }
78
79 pub fn get_for_delegator(&self, delegator: &T::AccountId) -> Option<Percent> {
82 match self.0.binary_search_by(|d| d.delegator.cmp(&delegator)) {
83 Ok(index) => Some(self.0[index].value),
84 Err(_) => None,
85 }
86 }
87
88 pub fn set_for_delegator(
91 &mut self,
92 delegator: T::AccountId,
93 value: Percent,
94 ) -> Result<bool, Error<T>> {
95 match self.0.binary_search_by(|d| d.delegator.cmp(&delegator)) {
96 Ok(index) => {
97 if self.0[index].value == value {
98 Ok(false)
99 } else {
100 self.0[index].value = value;
101 Ok(true)
102 }
103 }
104 Err(index) => {
105 self.0
106 .try_insert(index, AutoCompoundConfig { delegator, value })
107 .map_err(|_| Error::<T>::ExceedMaxDelegationsPerDelegator)?;
108 Ok(true)
109 }
110 }
111 }
112
113 pub fn remove_for_delegator(&mut self, delegator: &T::AccountId) -> bool {
117 match self.0.binary_search_by(|d| d.delegator.cmp(&delegator)) {
118 Ok(index) => {
119 self.0.remove(index);
120 true
121 }
122 Err(_) => false,
123 }
124 }
125
126 pub fn len(&self) -> u32 {
128 self.0.len() as u32
129 }
130
131 #[cfg(test)]
133 pub fn inner(
134 &self,
135 ) -> &BoundedVec<
136 AutoCompoundConfig<T::AccountId>,
137 AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
138 > {
139 &self.0
140 }
141
142 #[cfg(test)]
144 pub fn into_inner(
145 self,
146 ) -> BoundedVec<
147 AutoCompoundConfig<T::AccountId>,
148 AddGet<T::MaxTopDelegationsPerCandidate, T::MaxBottomDelegationsPerCandidate>,
149 > {
150 self.0
151 }
152
153 pub(crate) fn delegate_with_auto_compound(
158 candidate: T::AccountId,
159 delegator: T::AccountId,
160 amount: BalanceOf<T>,
161 auto_compound: Percent,
162 candidate_delegation_count_hint: u32,
163 candidate_auto_compounding_delegation_count_hint: u32,
164 delegation_count_hint: u32,
165 ) -> DispatchResultWithPostInfo {
166 ensure!(
168 <Pallet<T>>::get_delegator_stakable_balance(&delegator) >= amount,
169 Error::<T>::InsufficientBalance
170 );
171 ensure!(
172 amount >= T::MinDelegation::get(),
173 Error::<T>::DelegationBelowMin
174 );
175
176 let mut delegator_state = if let Some(mut state) = <DelegatorState<T>>::get(&delegator) {
177 ensure!(
179 delegation_count_hint >= state.delegations.0.len() as u32,
180 Error::<T>::TooLowDelegationCountToDelegate
181 );
182 ensure!(
183 (state.delegations.0.len() as u32) < T::MaxDelegationsPerDelegator::get(),
184 Error::<T>::ExceedMaxDelegationsPerDelegator
185 );
186 ensure!(
187 state.add_delegation(Bond {
188 owner: candidate.clone(),
189 amount
190 }),
191 Error::<T>::AlreadyDelegatedCandidate
192 );
193 state
194 } else {
195 ensure!(
197 !<Pallet<T>>::is_candidate(&delegator),
198 Error::<T>::CandidateExists
199 );
200 Delegator::new(delegator.clone(), candidate.clone(), amount)
201 };
202 let mut candidate_state =
203 <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
204 ensure!(
205 candidate_delegation_count_hint >= candidate_state.delegation_count,
206 Error::<T>::TooLowCandidateDelegationCountToDelegate
207 );
208
209 if !auto_compound.is_zero() {
210 ensure!(
211 Self::get_auto_compounding_delegation_count(&candidate) as u32
212 <= candidate_auto_compounding_delegation_count_hint,
213 <Error<T>>::TooLowCandidateAutoCompoundingDelegationCountToDelegate,
214 );
215 }
216
217 let (delegator_position, less_total_staked) = candidate_state.add_delegation::<T>(
219 &candidate,
220 Bond {
221 owner: delegator.clone(),
222 amount,
223 },
224 )?;
225
226 delegator_state.adjust_bond_lock::<T>(BondAdjust::Increase(amount))?;
228
229 let net_total_increase = if let Some(less) = less_total_staked {
232 amount.saturating_sub(less)
233 } else {
234 amount
235 };
236 let new_total_locked = <Total<T>>::get().saturating_add(net_total_increase);
237
238 if !auto_compound.is_zero() {
240 let mut auto_compounding_state = Self::get_storage(&candidate);
241 auto_compounding_state.set_for_delegator(delegator.clone(), auto_compound.clone())?;
242 auto_compounding_state.set_storage(&candidate);
243 }
244
245 <Total<T>>::put(new_total_locked);
246 <CandidateInfo<T>>::insert(&candidate, candidate_state);
247 <DelegatorState<T>>::insert(&delegator, delegator_state);
248 <Pallet<T>>::deposit_event(Event::Delegation {
249 delegator: delegator,
250 locked_amount: amount,
251 candidate: candidate,
252 delegator_position: delegator_position,
253 auto_compound,
254 });
255
256 Ok(().into())
257 }
258
259 pub(crate) fn set_auto_compound(
261 candidate: T::AccountId,
262 delegator: T::AccountId,
263 value: Percent,
264 candidate_auto_compounding_delegation_count_hint: u32,
265 delegation_count_hint: u32,
266 ) -> DispatchResultWithPostInfo {
267 let delegator_state =
268 <DelegatorState<T>>::get(&delegator).ok_or(<Error<T>>::DelegatorDNE)?;
269 ensure!(
270 delegator_state.delegations.0.len() <= delegation_count_hint as usize,
271 <Error<T>>::TooLowDelegationCountToAutoCompound,
272 );
273 ensure!(
274 delegator_state
275 .delegations
276 .0
277 .iter()
278 .any(|b| b.owner == candidate),
279 <Error<T>>::DelegationDNE,
280 );
281
282 let mut auto_compounding_state = Self::get_storage(&candidate);
283 ensure!(
284 auto_compounding_state.len() <= candidate_auto_compounding_delegation_count_hint,
285 <Error<T>>::TooLowCandidateAutoCompoundingDelegationCountToAutoCompound,
286 );
287 let state_updated = if value.is_zero() {
288 auto_compounding_state.remove_for_delegator(&delegator)
289 } else {
290 auto_compounding_state.set_for_delegator(delegator.clone(), value)?
291 };
292 if state_updated {
293 auto_compounding_state.set_storage(&candidate);
294 }
295
296 <Pallet<T>>::deposit_event(Event::AutoCompoundSet {
297 candidate,
298 delegator,
299 value,
300 });
301
302 Ok(().into())
303 }
304
305 pub(crate) fn remove_auto_compound(candidate: &T::AccountId, delegator: &T::AccountId) {
308 let mut auto_compounding_state = Self::get_storage(candidate);
309 if auto_compounding_state.remove_for_delegator(delegator) {
310 auto_compounding_state.set_storage(&candidate);
311 }
312 }
313
314 pub(crate) fn auto_compound(candidate: &T::AccountId, delegator: &T::AccountId) -> Percent {
316 let delegations_config = Self::get_storage(candidate);
317 delegations_config
318 .get_for_delegator(&delegator)
319 .unwrap_or_else(|| Percent::zero())
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use crate::mock::Test;
327
328 #[test]
329 fn test_set_for_delegator_inserts_config_and_returns_true_if_entry_missing() {
330 let mut delegations_config =
331 AutoCompoundDelegations::<Test>::new(vec![].try_into().expect("must succeed"));
332 assert_eq!(
333 true,
334 delegations_config
335 .set_for_delegator(1, Percent::from_percent(50))
336 .expect("must succeed")
337 );
338 assert_eq!(
339 vec![AutoCompoundConfig {
340 delegator: 1,
341 value: Percent::from_percent(50),
342 }],
343 delegations_config.into_inner().into_inner(),
344 );
345 }
346
347 #[test]
348 fn test_set_for_delegator_updates_config_and_returns_true_if_entry_changed() {
349 let mut delegations_config = AutoCompoundDelegations::<Test>::new(
350 vec![AutoCompoundConfig {
351 delegator: 1,
352 value: Percent::from_percent(10),
353 }]
354 .try_into()
355 .expect("must succeed"),
356 );
357 assert_eq!(
358 true,
359 delegations_config
360 .set_for_delegator(1, Percent::from_percent(50))
361 .expect("must succeed")
362 );
363 assert_eq!(
364 vec![AutoCompoundConfig {
365 delegator: 1,
366 value: Percent::from_percent(50),
367 }],
368 delegations_config.into_inner().into_inner(),
369 );
370 }
371
372 #[test]
373 fn test_set_for_delegator_updates_config_and_returns_false_if_entry_unchanged() {
374 let mut delegations_config = AutoCompoundDelegations::<Test>::new(
375 vec![AutoCompoundConfig {
376 delegator: 1,
377 value: Percent::from_percent(10),
378 }]
379 .try_into()
380 .expect("must succeed"),
381 );
382 assert_eq!(
383 false,
384 delegations_config
385 .set_for_delegator(1, Percent::from_percent(10))
386 .expect("must succeed")
387 );
388 assert_eq!(
389 vec![AutoCompoundConfig {
390 delegator: 1,
391 value: Percent::from_percent(10),
392 }],
393 delegations_config.into_inner().into_inner(),
394 );
395 }
396
397 #[test]
398 fn test_remove_for_delegator_returns_false_if_entry_was_missing() {
399 let mut delegations_config =
400 AutoCompoundDelegations::<Test>::new(vec![].try_into().expect("must succeed"));
401 assert_eq!(false, delegations_config.remove_for_delegator(&1),);
402 }
403
404 #[test]
405 fn test_remove_delegation_config_returns_true_if_entry_existed() {
406 let mut delegations_config = AutoCompoundDelegations::<Test>::new(
407 vec![AutoCompoundConfig {
408 delegator: 1,
409 value: Percent::from_percent(10),
410 }]
411 .try_into()
412 .expect("must succeed"),
413 );
414 assert_eq!(true, delegations_config.remove_for_delegator(&1));
415 }
416}