1use crate::{
35	asset, log, BalanceOf, Bonded, Config, DecodeWithMemTracking, Error, Ledger, Pallet, Payee,
36	RewardDestination, Vec, VirtualStakers,
37};
38use alloc::{collections::BTreeMap, fmt::Debug};
39use codec::{Decode, Encode, HasCompact, MaxEncodedLen};
40use frame_support::{
41	defensive, ensure,
42	traits::{Defensive, DefensiveSaturating, Get},
43	BoundedVec, CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound,
44};
45use scale_info::TypeInfo;
46use sp_runtime::{traits::Zero, DispatchResult, Perquintill, Rounding, Saturating};
47use sp_staking::{EraIndex, OnStakingUpdate, StakingAccount, StakingInterface};
48
49#[derive(
51	PartialEq, Eq, Clone, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo, MaxEncodedLen,
52)]
53pub struct UnlockChunk<Balance: HasCompact + MaxEncodedLen> {
54	#[codec(compact)]
56	pub value: Balance,
57	#[codec(compact)]
59	pub era: EraIndex,
60}
61
62#[derive(
72	PartialEqNoBound,
73	EqNoBound,
74	CloneNoBound,
75	Encode,
76	Decode,
77	DebugNoBound,
78	TypeInfo,
79	MaxEncodedLen,
80	DecodeWithMemTracking,
81)]
82#[scale_info(skip_type_params(T))]
83pub struct StakingLedger<T: Config> {
84	pub stash: T::AccountId,
86
87	#[codec(compact)]
90	pub total: BalanceOf<T>,
91
92	#[codec(compact)]
95	pub active: BalanceOf<T>,
96
97	pub unlocking: BoundedVec<UnlockChunk<BalanceOf<T>>, T::MaxUnlockingChunks>,
101
102	#[codec(skip)]
107	pub controller: Option<T::AccountId>,
108}
109
110impl<T: Config> StakingLedger<T> {
111	#[cfg(any(feature = "runtime-benchmarks", test))]
112	pub fn default_from(stash: T::AccountId) -> Self {
113		Self {
114			stash: stash.clone(),
115			total: Zero::zero(),
116			active: Zero::zero(),
117			unlocking: Default::default(),
118			controller: Some(stash),
119		}
120	}
121
122	pub fn new(stash: T::AccountId, stake: BalanceOf<T>) -> Self {
130		Self {
131			stash: stash.clone(),
132			active: stake,
133			total: stake,
134			unlocking: Default::default(),
135			controller: Some(stash),
137		}
138	}
139
140	pub(crate) fn paired_account(account: StakingAccount<T::AccountId>) -> Option<T::AccountId> {
149		match account {
150			StakingAccount::Stash(stash) => <Bonded<T>>::get(stash),
151			StakingAccount::Controller(controller) =>
152				<Ledger<T>>::get(&controller).map(|ledger| ledger.stash),
153		}
154	}
155
156	pub(crate) fn is_bonded(account: StakingAccount<T::AccountId>) -> bool {
158		match account {
159			StakingAccount::Stash(stash) => <Bonded<T>>::contains_key(stash),
160			StakingAccount::Controller(controller) => <Ledger<T>>::contains_key(controller),
161		}
162	}
163
164	pub(crate) fn get(account: StakingAccount<T::AccountId>) -> Result<StakingLedger<T>, Error<T>> {
173		let (stash, controller) = match account {
174			StakingAccount::Stash(stash) =>
175				(stash.clone(), <Bonded<T>>::get(&stash).ok_or(Error::<T>::NotStash)?),
176			StakingAccount::Controller(controller) => (
177				Ledger::<T>::get(&controller)
178					.map(|l| l.stash)
179					.ok_or(Error::<T>::NotController)?,
180				controller,
181			),
182		};
183
184		let ledger = <Ledger<T>>::get(&controller)
185			.map(|mut ledger| {
186				ledger.controller = Some(controller.clone());
187				ledger
188			})
189			.ok_or(Error::<T>::NotController)?;
190
191		ensure!(
197			Bonded::<T>::get(&stash) == Some(controller) && ledger.stash == stash,
198			Error::<T>::BadState
199		);
200
201		Ok(ledger)
202	}
203
204	pub(crate) fn reward_destination(
209		account: StakingAccount<T::AccountId>,
210	) -> Option<RewardDestination<T::AccountId>> {
211		let stash = match account {
212			StakingAccount::Stash(stash) => Some(stash),
213			StakingAccount::Controller(controller) =>
214				Self::paired_account(StakingAccount::Controller(controller)),
215		};
216
217		if let Some(stash) = stash {
218			<Payee<T>>::get(stash)
219		} else {
220			defensive!("fetched reward destination from unbonded stash {}", stash);
221			None
222		}
223	}
224
225	pub fn controller(&self) -> Option<T::AccountId> {
232		self.controller.clone().or_else(|| {
233			defensive!("fetched a controller on a ledger instance without it.");
234			Self::paired_account(StakingAccount::Stash(self.stash.clone()))
235		})
236	}
237
238	pub(crate) fn update(self) -> Result<(), Error<T>> {
246		if !<Bonded<T>>::contains_key(&self.stash) {
247			return Err(Error::<T>::NotStash)
248		}
249
250		if !Pallet::<T>::is_virtual_staker(&self.stash) {
252			asset::update_stake::<T>(&self.stash, self.total)
254				.map_err(|_| Error::<T>::NotEnoughFunds)?;
255		}
256
257		Ledger::<T>::insert(
258			&self.controller().ok_or_else(|| {
259				defensive!("update called on a ledger that is not bonded.");
260				Error::<T>::NotController
261			})?,
262			&self,
263		);
264
265		Ok(())
266	}
267
268	pub(crate) fn bond(self, payee: RewardDestination<T::AccountId>) -> Result<(), Error<T>> {
272		if <Bonded<T>>::contains_key(&self.stash) {
273			return Err(Error::<T>::AlreadyBonded)
274		}
275
276		<Payee<T>>::insert(&self.stash, payee);
277		<Bonded<T>>::insert(&self.stash, &self.stash);
278		self.update()
279	}
280
281	pub(crate) fn set_payee(self, payee: RewardDestination<T::AccountId>) -> Result<(), Error<T>> {
283		if !<Bonded<T>>::contains_key(&self.stash) {
284			return Err(Error::<T>::NotStash)
285		}
286
287		<Payee<T>>::insert(&self.stash, payee);
288		Ok(())
289	}
290
291	pub(crate) fn set_controller_to_stash(self) -> Result<(), Error<T>> {
293		let controller = self.controller.as_ref()
294            .defensive_proof("Ledger's controller field didn't exist. The controller should have been fetched using StakingLedger.")
295            .ok_or(Error::<T>::NotController)?;
296
297		ensure!(self.stash != *controller, Error::<T>::AlreadyPaired);
298
299		if let Some(bonded_ledger) = Ledger::<T>::get(&self.stash) {
301			ensure!(bonded_ledger.stash == self.stash, Error::<T>::BadState);
306		}
307
308		<Ledger<T>>::remove(&controller);
309		<Ledger<T>>::insert(&self.stash, &self);
310		<Bonded<T>>::insert(&self.stash, &self.stash);
311
312		Ok(())
313	}
314
315	pub(crate) fn kill(stash: &T::AccountId) -> DispatchResult {
318		let controller = <Bonded<T>>::get(stash).ok_or(Error::<T>::NotStash)?;
319
320		<Ledger<T>>::get(&controller).ok_or(Error::<T>::NotController).map(|ledger| {
321			Ledger::<T>::remove(controller);
322			<Bonded<T>>::remove(&stash);
323			<Payee<T>>::remove(&stash);
324
325			if <VirtualStakers<T>>::take(&ledger.stash).is_none() {
327				asset::kill_stake::<T>(&ledger.stash)?;
329			}
330			Pallet::<T>::deposit_event(crate::Event::<T>::StakerRemoved {
331				stash: ledger.stash.clone(),
332			});
333			Ok(())
334		})?
335	}
336
337	#[cfg(test)]
338	pub(crate) fn assert_stash_killed(stash: T::AccountId) {
339		assert!(!Ledger::<T>::contains_key(&stash));
340		assert!(!Bonded::<T>::contains_key(&stash));
341		assert!(!Payee::<T>::contains_key(&stash));
342		assert!(!VirtualStakers::<T>::contains_key(&stash));
343	}
344
345	pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self {
348		let mut total = self.total;
349		let unlocking: BoundedVec<_, _> = self
350			.unlocking
351			.into_iter()
352			.filter(|chunk| {
353				if chunk.era > current_era {
354					true
355				} else {
356					total = total.saturating_sub(chunk.value);
357					false
358				}
359			})
360			.collect::<Vec<_>>()
361			.try_into()
362			.expect(
363				"filtering items from a bounded vec always leaves length less than bounds. qed",
364			);
365
366		Self {
367			stash: self.stash,
368			total,
369			active: self.active,
370			unlocking,
371			controller: self.controller,
372		}
373	}
374
375	pub(crate) fn rebond(mut self, value: BalanceOf<T>) -> (Self, BalanceOf<T>) {
379		let mut unlocking_balance = BalanceOf::<T>::zero();
380
381		while let Some(last) = self.unlocking.last_mut() {
382			if unlocking_balance.defensive_saturating_add(last.value) <= value {
383				unlocking_balance += last.value;
384				self.active += last.value;
385				self.unlocking.pop();
386			} else {
387				let diff = value.defensive_saturating_sub(unlocking_balance);
388
389				unlocking_balance += diff;
390				self.active += diff;
391				last.value -= diff;
392			}
393
394			if unlocking_balance >= value {
395				break
396			}
397		}
398
399		(self, unlocking_balance)
400	}
401
402	pub fn slash(
426		&mut self,
427		slash_amount: BalanceOf<T>,
428		minimum_balance: BalanceOf<T>,
429		slash_era: EraIndex,
430	) -> BalanceOf<T> {
431		if slash_amount.is_zero() {
432			return Zero::zero()
433		}
434
435		use sp_runtime::PerThing as _;
436		let mut remaining_slash = slash_amount;
437		let pre_slash_total = self.total;
438
439		let slashable_chunks_start = slash_era.saturating_add(T::BondingDuration::get());
442
443		let (maybe_proportional, slash_chunks_priority) = {
446			if let Some(first_slashable_index) =
447				self.unlocking.iter().position(|c| c.era >= slashable_chunks_start)
448			{
449				let affected_indices = first_slashable_index..self.unlocking.len();
456				let unbonding_affected_balance =
457					affected_indices.clone().fold(BalanceOf::<T>::zero(), |sum, i| {
458						if let Some(chunk) = self.unlocking.get(i).defensive() {
459							sum.saturating_add(chunk.value)
460						} else {
461							sum
462						}
463					});
464				let affected_balance = self.active.saturating_add(unbonding_affected_balance);
465				let ratio = Perquintill::from_rational_with_rounding(
466					slash_amount,
467					affected_balance,
468					Rounding::Up,
469				)
470				.unwrap_or_else(|_| Perquintill::one());
471				(
472					Some(ratio),
473					affected_indices.chain((0..first_slashable_index).rev()).collect::<Vec<_>>(),
474				)
475			} else {
476				(None, (0..self.unlocking.len()).rev().collect::<Vec<_>>())
478			}
479		};
480
481		log!(
483			trace,
484			"slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}",
485			slash_amount,
486			slash_era,
487			self,
488			slash_chunks_priority,
489			maybe_proportional,
490		);
491
492		let mut slash_out_of = |target: &mut BalanceOf<T>, slash_remaining: &mut BalanceOf<T>| {
493			let mut slash_from_target = if let Some(ratio) = maybe_proportional {
494				ratio.mul_ceil(*target)
495			} else {
496				*slash_remaining
497			}
498			.min(*target)
501			.min(*slash_remaining);
504
505			*target = *target - slash_from_target;
507			if *target < minimum_balance {
508				slash_from_target =
512					core::mem::replace(target, Zero::zero()).saturating_add(slash_from_target)
513			}
514
515			self.total = self.total.saturating_sub(slash_from_target);
516			*slash_remaining = slash_remaining.saturating_sub(slash_from_target);
517		};
518
519		slash_out_of(&mut self.active, &mut remaining_slash);
521
522		let mut slashed_unlocking = BTreeMap::<_, _>::new();
523		for i in slash_chunks_priority {
524			if remaining_slash.is_zero() {
525				break
526			}
527
528			if let Some(chunk) = self.unlocking.get_mut(i).defensive() {
529				slash_out_of(&mut chunk.value, &mut remaining_slash);
530				slashed_unlocking.insert(chunk.era, chunk.value);
532			} else {
533				break
534			}
535		}
536
537		self.unlocking.retain(|c| !c.value.is_zero());
539
540		let final_slashed_amount = pre_slash_total.saturating_sub(self.total);
541		T::EventListeners::on_slash(
542			&self.stash,
543			self.active,
544			&slashed_unlocking,
545			final_slashed_amount,
546		);
547		final_slashed_amount
548	}
549}
550
551#[derive(PartialEq, Debug)]
553pub(crate) enum LedgerIntegrityState {
554	Ok,
556	Corrupted,
559	CorruptedKilled,
561	LockCorrupted,
563}
564
565#[cfg(test)]
568#[derive(frame_support::DebugNoBound, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
569pub struct StakingLedgerInspect<T: Config> {
570	pub stash: T::AccountId,
571	#[codec(compact)]
572	pub total: BalanceOf<T>,
573	#[codec(compact)]
574	pub active: BalanceOf<T>,
575	pub unlocking:
576		frame_support::BoundedVec<crate::UnlockChunk<BalanceOf<T>>, T::MaxUnlockingChunks>,
577}
578
579#[cfg(test)]
580impl<T: Config> PartialEq<StakingLedgerInspect<T>> for StakingLedger<T> {
581	fn eq(&self, other: &StakingLedgerInspect<T>) -> bool {
582		self.stash == other.stash &&
583			self.total == other.total &&
584			self.active == other.active &&
585			self.unlocking == other.unlocking
586	}
587}
588
589#[cfg(test)]
590impl<T: Config> codec::EncodeLike<StakingLedger<T>> for StakingLedgerInspect<T> {}