1use frame_support::{defensive, ensure, traits::Defensive};
35use sp_runtime::DispatchResult;
36use sp_staking::{StakingAccount, StakingInterface};
37
38use crate::{
39 asset, BalanceOf, Bonded, Config, Error, Ledger, Pallet, Payee, RewardDestination,
40 StakingLedger, VirtualStakers,
41};
42
43#[cfg(any(feature = "runtime-benchmarks", test))]
44use sp_runtime::traits::Zero;
45
46impl<T: Config> StakingLedger<T> {
47 #[cfg(any(feature = "runtime-benchmarks", test))]
48 pub fn default_from(stash: T::AccountId) -> Self {
49 Self {
50 stash: stash.clone(),
51 total: Zero::zero(),
52 active: Zero::zero(),
53 unlocking: Default::default(),
54 legacy_claimed_rewards: Default::default(),
55 controller: Some(stash),
56 }
57 }
58
59 pub fn new(stash: T::AccountId, stake: BalanceOf<T>) -> Self {
67 Self {
68 stash: stash.clone(),
69 active: stake,
70 total: stake,
71 unlocking: Default::default(),
72 legacy_claimed_rewards: Default::default(),
73 controller: Some(stash),
75 }
76 }
77
78 pub(crate) fn paired_account(account: StakingAccount<T::AccountId>) -> Option<T::AccountId> {
87 match account {
88 StakingAccount::Stash(stash) => <Bonded<T>>::get(stash),
89 StakingAccount::Controller(controller) => {
90 <Ledger<T>>::get(&controller).map(|ledger| ledger.stash)
91 },
92 }
93 }
94
95 pub(crate) fn is_bonded(account: StakingAccount<T::AccountId>) -> bool {
97 match account {
98 StakingAccount::Stash(stash) => <Bonded<T>>::contains_key(stash),
99 StakingAccount::Controller(controller) => <Ledger<T>>::contains_key(controller),
100 }
101 }
102
103 pub(crate) fn get(account: StakingAccount<T::AccountId>) -> Result<StakingLedger<T>, Error<T>> {
112 let (stash, controller) = match account.clone() {
113 StakingAccount::Stash(stash) => {
114 (stash.clone(), <Bonded<T>>::get(&stash).ok_or(Error::<T>::NotStash)?)
115 },
116 StakingAccount::Controller(controller) => (
117 Ledger::<T>::get(&controller)
118 .map(|l| l.stash)
119 .ok_or(Error::<T>::NotController)?,
120 controller,
121 ),
122 };
123
124 let ledger = <Ledger<T>>::get(&controller)
125 .map(|mut ledger| {
126 ledger.controller = Some(controller.clone());
127 ledger
128 })
129 .ok_or(Error::<T>::NotController)?;
130
131 ensure!(
137 Bonded::<T>::get(&stash) == Some(controller) && ledger.stash == stash,
138 Error::<T>::BadState
139 );
140
141 Ok(ledger)
142 }
143
144 pub(crate) fn reward_destination(
149 account: StakingAccount<T::AccountId>,
150 ) -> Option<RewardDestination<T::AccountId>> {
151 let stash = match account {
152 StakingAccount::Stash(stash) => Some(stash),
153 StakingAccount::Controller(controller) => {
154 Self::paired_account(StakingAccount::Controller(controller))
155 },
156 };
157
158 if let Some(stash) = stash {
159 <Payee<T>>::get(stash)
160 } else {
161 defensive!("fetched reward destination from unbonded stash {}", stash);
162 None
163 }
164 }
165
166 pub fn controller(&self) -> Option<T::AccountId> {
173 self.controller.clone().or_else(|| {
174 defensive!("fetched a controller on a ledger instance without it.");
175 Self::paired_account(StakingAccount::Stash(self.stash.clone()))
176 })
177 }
178
179 pub(crate) fn update(self) -> Result<(), Error<T>> {
187 if !<Bonded<T>>::contains_key(&self.stash) {
188 return Err(Error::<T>::NotStash);
189 }
190
191 if !Pallet::<T>::is_virtual_staker(&self.stash) {
193 asset::update_stake::<T>(&self.stash, self.total)
195 .map_err(|_| Error::<T>::NotEnoughFunds)?;
196 }
197
198 Ledger::<T>::insert(
199 &self.controller().ok_or_else(|| {
200 defensive!("update called on a ledger that is not bonded.");
201 Error::<T>::NotController
202 })?,
203 &self,
204 );
205
206 Ok(())
207 }
208
209 pub(crate) fn bond(self, payee: RewardDestination<T::AccountId>) -> Result<(), Error<T>> {
213 if <Bonded<T>>::contains_key(&self.stash) {
214 return Err(Error::<T>::AlreadyBonded);
215 }
216
217 <Payee<T>>::insert(&self.stash, payee);
218 <Bonded<T>>::insert(&self.stash, &self.stash);
219 self.update()
220 }
221
222 pub(crate) fn set_payee(self, payee: RewardDestination<T::AccountId>) -> Result<(), Error<T>> {
224 if !<Bonded<T>>::contains_key(&self.stash) {
225 return Err(Error::<T>::NotStash);
226 }
227
228 <Payee<T>>::insert(&self.stash, payee);
229 Ok(())
230 }
231
232 pub(crate) fn set_controller_to_stash(self) -> Result<(), Error<T>> {
234 let controller = self.controller.as_ref()
235 .defensive_proof("Ledger's controller field didn't exist. The controller should have been fetched using StakingLedger.")
236 .ok_or(Error::<T>::NotController)?;
237
238 ensure!(self.stash != *controller, Error::<T>::AlreadyPaired);
239
240 if let Some(bonded_ledger) = Ledger::<T>::get(&self.stash) {
242 ensure!(bonded_ledger.stash == self.stash, Error::<T>::BadState);
247 }
248
249 <Ledger<T>>::remove(&controller);
250 <Ledger<T>>::insert(&self.stash, &self);
251 <Bonded<T>>::insert(&self.stash, &self.stash);
252
253 Ok(())
254 }
255
256 pub(crate) fn kill(stash: &T::AccountId) -> DispatchResult {
259 let controller = <Bonded<T>>::get(stash).ok_or(Error::<T>::NotStash)?;
260
261 <Ledger<T>>::get(&controller).ok_or(Error::<T>::NotController).map(|ledger| {
262 Ledger::<T>::remove(controller);
263 <Bonded<T>>::remove(&stash);
264 <Payee<T>>::remove(&stash);
265
266 if <VirtualStakers<T>>::take(&ledger.stash).is_none() {
268 asset::kill_stake::<T>(&ledger.stash)?;
270 }
271
272 Ok(())
273 })?
274 }
275}
276
277#[cfg(test)]
278use {
279 crate::UnlockChunk,
280 codec::{Decode, Encode, MaxEncodedLen},
281 scale_info::TypeInfo,
282};
283
284#[cfg(test)]
287#[derive(frame_support::DebugNoBound, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
288pub struct StakingLedgerInspect<T: Config> {
289 pub stash: T::AccountId,
290 #[codec(compact)]
291 pub total: BalanceOf<T>,
292 #[codec(compact)]
293 pub active: BalanceOf<T>,
294 pub unlocking: frame_support::BoundedVec<UnlockChunk<BalanceOf<T>>, T::MaxUnlockingChunks>,
295 pub legacy_claimed_rewards: frame_support::BoundedVec<sp_staking::EraIndex, T::HistoryDepth>,
296}
297
298#[cfg(test)]
299impl<T: Config> PartialEq<StakingLedgerInspect<T>> for StakingLedger<T> {
300 fn eq(&self, other: &StakingLedgerInspect<T>) -> bool {
301 self.stash == other.stash &&
302 self.total == other.total &&
303 self.active == other.active &&
304 self.unlocking == other.unlocking &&
305 self.legacy_claimed_rewards == other.legacy_claimed_rewards
306 }
307}
308
309#[cfg(test)]
310impl<T: Config> codec::EncodeLike<StakingLedger<T>> for StakingLedgerInspect<T> {}