pallet_elections_phragmen/migrations/
unlock_and_unreserve_all_funds.rs1use alloc::{collections::btree_map::BTreeMap, vec::Vec};
22use core::iter::Sum;
23use frame_support::{
24 pallet_prelude::ValueQuery,
25 storage_alias,
26 traits::{Currency, LockIdentifier, LockableCurrency, OnRuntimeUpgrade, ReservableCurrency},
27 weights::RuntimeDbWeight,
28 Parameter, Twox64Concat,
29};
30use sp_core::Get;
31use sp_runtime::traits::Zero;
32
33const LOG_TARGET: &str = "elections_phragmen::migrations::unlock_and_unreserve_all_funds";
34
35type BalanceOf<T> =
36 <<T as UnlockConfig>::Currency as Currency<<T as UnlockConfig>::AccountId>>::Balance;
37
38pub trait UnlockConfig: 'static {
40 type AccountId: Parameter + Ord;
42 type Currency: LockableCurrency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
46 type PalletName: Get<&'static str>;
49 type MaxVotesPerVoter: Get<u32>;
51 type PalletId: Get<LockIdentifier>;
54 type DbWeight: Get<RuntimeDbWeight>;
56}
57
58#[storage_alias(dynamic)]
59type Members<T: UnlockConfig> = StorageValue<
60 <T as UnlockConfig>::PalletName,
61 Vec<crate::SeatHolder<<T as UnlockConfig>::AccountId, BalanceOf<T>>>,
62 ValueQuery,
63>;
64
65#[storage_alias(dynamic)]
66type RunnersUp<T: UnlockConfig> = StorageValue<
67 <T as UnlockConfig>::PalletName,
68 Vec<crate::SeatHolder<<T as UnlockConfig>::AccountId, BalanceOf<T>>>,
69 ValueQuery,
70>;
71
72#[storage_alias(dynamic)]
73type Candidates<T: UnlockConfig> = StorageValue<
74 <T as UnlockConfig>::PalletName,
75 Vec<(<T as UnlockConfig>::AccountId, BalanceOf<T>)>,
76 ValueQuery,
77>;
78
79#[storage_alias(dynamic)]
80type Voting<T: UnlockConfig> = StorageMap<
81 <T as UnlockConfig>::PalletName,
82 Twox64Concat,
83 <T as UnlockConfig>::AccountId,
84 crate::Voter<<T as UnlockConfig>::AccountId, BalanceOf<T>>,
85 ValueQuery,
86>;
87
88pub struct UnlockAndUnreserveAllFunds<T: UnlockConfig>(core::marker::PhantomData<T>);
97
98impl<T: UnlockConfig> UnlockAndUnreserveAllFunds<T> {
99 fn get_account_deposited_and_staked_sums() -> (
120 BTreeMap<T::AccountId, BalanceOf<T>>,
121 BTreeMap<T::AccountId, BalanceOf<T>>,
122 frame_support::weights::Weight,
123 ) {
124 use sp_runtime::Saturating;
125
126 let members = Members::<T>::get();
127 let runner_ups = RunnersUp::<T>::get();
128 let candidates = Candidates::<T>::get();
129
130 let account_deposited_sums: BTreeMap<T::AccountId, BalanceOf<T>> = members
133 .iter()
135 .chain(runner_ups.iter())
136 .map(|member| (member.who.clone(), member.deposit))
137 .chain(candidates.iter().map(|(candidate, amount)| (candidate.clone(), *amount)))
138 .chain(
139 Voting::<T>::iter().map(|(account_id, voter)| (account_id.clone(), voter.deposit)),
140 )
141 .fold(BTreeMap::new(), |mut acc, (id, deposit)| {
143 acc.entry(id.clone()).or_insert(Zero::zero()).saturating_accrue(deposit);
144 acc
145 });
146
147 let mut voters_len = 0;
149 let account_staked_sums: BTreeMap<T::AccountId, BalanceOf<T>> = Voting::<T>::iter()
150 .map(|(account_id, voter)| (account_id.clone(), voter.stake))
151 .fold(BTreeMap::new(), |mut acc, (id, stake)| {
152 voters_len.saturating_accrue(1);
153 acc.entry(id.clone()).or_insert(Zero::zero()).saturating_accrue(stake);
154 acc
155 });
156
157 (
158 account_deposited_sums,
159 account_staked_sums,
160 T::DbWeight::get().reads(
161 members
162 .len()
163 .saturating_add(runner_ups.len())
164 .saturating_add(candidates.len())
165 .saturating_add(voters_len.saturating_mul(T::MaxVotesPerVoter::get() as usize))
166 as u64,
167 ),
168 )
169 }
170}
171
172impl<T: UnlockConfig> OnRuntimeUpgrade for UnlockAndUnreserveAllFunds<T>
173where
174 BalanceOf<T>: Sum,
175{
176 #[cfg(feature = "try-runtime")]
189 fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
190 use alloc::collections::btree_set::BTreeSet;
191 use codec::Encode;
192
193 let (account_deposited_sums, account_staked_sums, _) =
195 Self::get_account_deposited_and_staked_sums();
196
197 let all_accounts: BTreeSet<T::AccountId> = account_staked_sums
198 .keys()
199 .chain(account_deposited_sums.keys())
200 .cloned()
201 .collect();
202
203 let account_reserved_before: BTreeMap<T::AccountId, BalanceOf<T>> = all_accounts
204 .iter()
205 .map(|account| (account.clone(), T::Currency::reserved_balance(&account)))
206 .collect();
207
208 let bugged_deposits = all_accounts
211 .iter()
212 .filter(|account| {
213 account_deposited_sums.get(&account).unwrap_or(&Zero::zero()) >
214 account_reserved_before.get(&account).unwrap_or(&Zero::zero())
215 })
216 .count();
217
218 let total_stake_to_unlock = account_staked_sums.clone().into_values().sum::<BalanceOf<T>>();
220 let total_deposits_to_unreserve =
221 account_deposited_sums.clone().into_values().sum::<BalanceOf<T>>();
222 log::info!(target: LOG_TARGET, "Total accounts: {:?}", all_accounts.len());
223 log::info!(target: LOG_TARGET, "Total stake to unlock: {:?}", total_stake_to_unlock);
224 log::info!(
225 target: LOG_TARGET,
226 "Total deposit to unreserve: {:?}",
227 total_deposits_to_unreserve
228 );
229 if bugged_deposits > 0 {
230 log::warn!(
231 target: LOG_TARGET,
232 "Bugged deposits: {}/{}",
233 bugged_deposits,
234 all_accounts.len()
235 );
236 }
237
238 Ok(account_reserved_before.encode())
239 }
240
241 fn on_runtime_upgrade() -> frame_support::weights::Weight {
248 let (account_deposited_sums, account_staked_sums, initial_reads) =
250 Self::get_account_deposited_and_staked_sums();
251
252 for (account, unreserve_amount) in account_deposited_sums.iter() {
254 if unreserve_amount.is_zero() {
255 log::warn!(target: LOG_TARGET, "Unexpected zero amount to unreserve");
256 continue
257 }
258 T::Currency::unreserve(&account, *unreserve_amount);
259 }
260
261 for (account, amount) in account_staked_sums.iter() {
263 if amount.is_zero() {
264 log::warn!(target: LOG_TARGET, "Unexpected zero amount to unlock");
265 continue
266 }
267 T::Currency::remove_lock(T::PalletId::get(), account);
268 }
269
270 T::DbWeight::get()
271 .reads_writes(
272 (account_deposited_sums.len().saturating_add(account_staked_sums.len())) as u64,
273 (account_deposited_sums.len().saturating_add(account_staked_sums.len())) as u64,
274 )
275 .saturating_add(initial_reads)
276 }
277
278 #[cfg(feature = "try-runtime")]
283 fn post_upgrade(
284 account_reserved_before_bytes: Vec<u8>,
285 ) -> Result<(), sp_runtime::TryRuntimeError> {
286 use codec::Decode;
287 use sp_runtime::Saturating;
288
289 let account_reserved_before =
290 BTreeMap::<T::AccountId, BalanceOf<T>>::decode(&mut &account_reserved_before_bytes[..])
291 .map_err(|_| "Failed to decode account_reserved_before_bytes")?;
292
293 let (account_deposited_sums, _, _) = Self::get_account_deposited_and_staked_sums();
295
296 for (account, actual_reserved_before) in account_reserved_before {
298 let actual_reserved_after = T::Currency::reserved_balance(&account);
299 let expected_amount_deducted = *account_deposited_sums
300 .get(&account)
301 .unwrap_or(&Zero::zero())
302 .min(&actual_reserved_before);
305 let expected_reserved_after =
306 actual_reserved_before.saturating_sub(expected_amount_deducted);
307 assert!(
308 actual_reserved_after == expected_reserved_after,
309 "Reserved balance for {:?} is incorrect. actual before: {:?}, actual after, {:?}, expected deducted: {:?}",
310 account,
311 actual_reserved_before,
312 actual_reserved_after,
313 expected_amount_deducted,
314 );
315 }
316
317 Ok(())
318 }
319}
320
321#[cfg(all(feature = "try-runtime", test))]
322mod test {
323 use super::*;
324 use crate::{
325 tests::{Balances, ElectionsPhragmenPalletId, ExtBuilder, PhragmenMaxVoters, Test},
326 Candidates, Members, RunnersUp, SeatHolder, Voter, Voting,
327 };
328 use frame_support::{
329 assert_ok, parameter_types,
330 traits::{Currency, OnRuntimeUpgrade, ReservableCurrency, WithdrawReasons},
331 };
332
333 parameter_types! {
334 const PalletName: &'static str = "Elections";
335 }
336
337 struct UnlockConfigImpl;
338 impl super::UnlockConfig for UnlockConfigImpl {
339 type Currency = Balances;
340 type AccountId = u64;
341 type DbWeight = ();
342 type PalletName = PalletName;
343 type MaxVotesPerVoter = PhragmenMaxVoters;
344 type PalletId = ElectionsPhragmenPalletId;
345 }
346
347 #[test]
348 fn unreserve_works_for_candidate() {
349 let candidate = 10;
350 let deposit = 100;
351 let initial_reserved = 15;
352 let initial_balance = 100_000;
353 ExtBuilder::default().build_and_execute(|| {
354 <Test as crate::Config>::Currency::make_free_balance_be(&candidate, initial_balance);
356 assert_ok!(<Test as crate::Config>::Currency::reserve(&candidate, initial_reserved));
357 Candidates::<Test>::set(vec![(candidate, deposit)]);
358 assert_ok!(<Test as crate::Config>::Currency::reserve(&candidate, deposit));
359
360 assert_eq!(
362 <Test as crate::Config>::Currency::reserved_balance(&candidate),
363 deposit + initial_reserved
364 );
365
366 let bytes = UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::pre_upgrade()
368 .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e));
369 UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::on_runtime_upgrade();
370 assert_ok!(UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::post_upgrade(bytes));
371
372 assert_eq!(
374 <Test as crate::Config>::Currency::reserved_balance(&candidate),
375 initial_reserved
376 );
377 });
378 }
379
380 #[test]
381 fn unreserve_works_for_runner_up() {
382 let runner_up = 10;
383 let deposit = 100;
384 let initial_reserved = 15;
385 let initial_balance = 100_000;
386 ExtBuilder::default().build_and_execute(|| {
387 <Test as crate::Config>::Currency::make_free_balance_be(&runner_up, initial_balance);
389 assert_ok!(<Test as crate::Config>::Currency::reserve(&runner_up, initial_reserved));
390 RunnersUp::<Test>::set(vec![SeatHolder { who: runner_up, deposit, stake: 10 }]);
391 assert_ok!(<Test as crate::Config>::Currency::reserve(&runner_up, deposit));
392
393 assert_eq!(
395 <Test as crate::Config>::Currency::reserved_balance(&runner_up),
396 deposit + initial_reserved
397 );
398
399 let bytes = UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::pre_upgrade()
401 .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e));
402 UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::on_runtime_upgrade();
403 assert_ok!(UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::post_upgrade(bytes));
404
405 assert_eq!(
407 <Test as crate::Config>::Currency::reserved_balance(&runner_up),
408 initial_reserved
409 );
410 });
411 }
412
413 #[test]
414 fn unreserve_works_for_member() {
415 let member = 10;
416 let deposit = 100;
417 let initial_reserved = 15;
418 let initial_balance = 100_000;
419 ExtBuilder::default().build_and_execute(|| {
420 <Test as crate::Config>::Currency::make_free_balance_be(&member, initial_balance);
422 assert_ok!(<Test as crate::Config>::Currency::reserve(&member, initial_reserved));
423 Members::<Test>::set(vec![SeatHolder { who: member, deposit, stake: 10 }]);
424 assert_ok!(<Test as crate::Config>::Currency::reserve(&member, deposit));
425
426 assert_eq!(
428 <Test as crate::Config>::Currency::reserved_balance(&member),
429 deposit + initial_reserved
430 );
431
432 let bytes = UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::pre_upgrade()
434 .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e));
435 UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::on_runtime_upgrade();
436 assert_ok!(UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::post_upgrade(bytes));
437
438 assert_eq!(
440 <Test as crate::Config>::Currency::reserved_balance(&member),
441 initial_reserved
442 );
443 });
444 }
445
446 #[test]
447 fn unlock_and_unreserve_works_for_voter() {
448 let voter = 10;
449 let deposit = 100;
450 let initial_reserved = 15;
451 let initial_locks = vec![(b"somethin", 10)];
452 let stake = 25;
453 let initial_balance = 100_000;
454 ExtBuilder::default().build_and_execute(|| {
455 let pallet_id = <Test as crate::Config>::PalletId::get();
456
457 <Test as crate::Config>::Currency::make_free_balance_be(&voter, initial_balance);
459 assert_ok!(<Test as crate::Config>::Currency::reserve(&voter, initial_reserved));
460 for lock in initial_locks.clone() {
461 <Test as crate::Config>::Currency::set_lock(
462 *lock.0,
463 &voter,
464 lock.1,
465 WithdrawReasons::all(),
466 );
467 }
468 Voting::<Test>::insert(voter, Voter { votes: vec![], deposit, stake });
469 assert_ok!(<Test as crate::Config>::Currency::reserve(&voter, deposit));
470 <Test as crate::Config>::Currency::set_lock(
471 <Test as crate::Config>::PalletId::get(),
472 &voter,
473 stake,
474 WithdrawReasons::all(),
475 );
476
477 assert_eq!(
479 <Test as crate::Config>::Currency::reserved_balance(&voter),
480 deposit + initial_reserved
481 );
482 let mut voter_all_locks = initial_locks.clone();
483 voter_all_locks.push((&pallet_id, stake));
484 assert_eq!(
485 <Test as crate::Config>::Currency::locks(&voter)
486 .iter()
487 .map(|lock| (&lock.id, lock.amount))
488 .collect::<Vec<_>>(),
489 voter_all_locks
490 );
491
492 let bytes = UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::pre_upgrade()
494 .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e));
495 UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::on_runtime_upgrade();
496 assert_ok!(UnlockAndUnreserveAllFunds::<UnlockConfigImpl>::post_upgrade(bytes));
497
498 assert_eq!(
501 <Test as crate::Config>::Currency::reserved_balance(&voter),
502 initial_reserved
503 );
504 assert_eq!(
505 <Test as crate::Config>::Currency::locks(&voter)
506 .iter()
507 .map(|lock| (&lock.id, lock.amount))
508 .collect::<Vec<_>>(),
509 initial_locks
510 );
511 });
512 }
513}