@@ -20,6 +20,104 @@ use super::*;
2020use frame_support:: traits:: OnRuntimeUpgrade ;
2121use log;
2222
23+ /// Migrate to v2. Should have been part of <https://github.com/paritytech/polkadot-sdk/pull/1340>.
24+ pub mod v2 {
25+ use super :: * ;
26+ use frame_support:: {
27+ pallet_prelude:: * ,
28+ storage_alias,
29+ traits:: { Currency , ReservableCurrency } ,
30+ } ;
31+ use sp_runtime:: traits:: { Saturating , Zero } ;
32+ #[ cfg( feature = "try-runtime" ) ]
33+ use sp_std:: vec:: Vec ;
34+
35+ #[ storage_alias]
36+ pub type Candidates < T : Config > = StorageValue <
37+ Pallet < T > ,
38+ BoundedVec < CandidateInfo < <T as frame_system:: Config >:: AccountId , <<T as Config >:: Currency as Currency < <T as frame_system:: Config >:: AccountId > >:: Balance > , <T as Config >:: MaxCandidates > ,
39+ ValueQuery ,
40+ > ;
41+
42+ /// Migrate to V2.
43+ pub struct MigrationToV2 < T > ( sp_std:: marker:: PhantomData < T > ) ;
44+ impl < T : Config + pallet_balances:: Config > OnRuntimeUpgrade for MigrationToV2 < T > {
45+ fn on_runtime_upgrade ( ) -> Weight {
46+ let on_chain_version = Pallet :: < T > :: on_chain_storage_version ( ) ;
47+ if on_chain_version == 1 {
48+ let mut weight = Weight :: zero ( ) ;
49+ let mut count: u64 = 0 ;
50+ // candidates who exist under the old `Candidates` key
51+ let candidates = Candidates :: < T > :: take ( ) ;
52+
53+ // New candidates who have registered since the upgrade. Under normal circumstances,
54+ // this should not exist because the migration should be applied when the upgrade
55+ // happens. But in Polkadot/Kusama we messed this up, and people registered under
56+ // `CandidateList` while their funds were locked in `Candidates`.
57+ let new_candidate_list = CandidateList :: < T > :: get ( ) ;
58+ if new_candidate_list. len ( ) . is_zero ( ) {
59+ // The new list is empty, so this is essentially being applied correctly. We
60+ // just put the candidates into the new storage item.
61+ CandidateList :: < T > :: put ( & candidates) ;
62+ // 1 write for the new list
63+ weight. saturating_accrue ( T :: DbWeight :: get ( ) . reads_writes ( 0 , 1 ) ) ;
64+ } else {
65+ // Oops, the runtime upgraded without the migration. There are new candidates in
66+ // `CandidateList`. So, let's just refund the old ones and assume they have
67+ // already started participating in the new system.
68+ for candidate in candidates {
69+ let err = T :: Currency :: unreserve ( & candidate. who , candidate. deposit ) ;
70+ if err > Zero :: zero ( ) {
71+ log:: error!(
72+ target: LOG_TARGET ,
73+ "{:?} balance was unable to be unreserved from {:?}" ,
74+ err, & candidate. who,
75+ ) ;
76+ }
77+ count. saturating_inc ( ) ;
78+ }
79+ weight. saturating_accrue (
80+ <<T as pallet_balances:: Config >:: WeightInfo as pallet_balances:: WeightInfo >:: force_unreserve ( ) . saturating_mul ( count. into ( ) ) ,
81+ ) ;
82+ }
83+
84+ StorageVersion :: new ( 2 ) . put :: < Pallet < T > > ( ) ;
85+
86+ log:: info!(
87+ target: LOG_TARGET ,
88+ "Unreserved locked bond of {} candidates, upgraded storage to version 2" ,
89+ count,
90+ ) ;
91+
92+ weight. saturating_accrue ( T :: DbWeight :: get ( ) . reads_writes ( 3 , 2 ) ) ;
93+ weight
94+ } else {
95+ log:: info!(
96+ target: LOG_TARGET ,
97+ "Migration did not execute. This probably should be removed"
98+ ) ;
99+ T :: DbWeight :: get ( ) . reads ( 1 )
100+ }
101+ }
102+
103+ #[ cfg( feature = "try-runtime" ) ]
104+ fn pre_upgrade ( ) -> Result < Vec < u8 > , sp_runtime:: DispatchError > {
105+ let number_of_candidates = Candidates :: < T > :: get ( ) . to_vec ( ) . len ( ) ;
106+ Ok ( ( number_of_candidates as u32 ) . encode ( ) )
107+ }
108+
109+ #[ cfg( feature = "try-runtime" ) ]
110+ fn post_upgrade ( _number_of_candidates : Vec < u8 > ) -> Result < ( ) , sp_runtime:: DispatchError > {
111+ let new_number_of_candidates = Candidates :: < T > :: get ( ) . to_vec ( ) . len ( ) ;
112+ assert_eq ! (
113+ new_number_of_candidates, 0 as usize ,
114+ "after migration, the candidates map should be empty"
115+ ) ;
116+ Ok ( ( ) )
117+ }
118+ }
119+ }
120+
23121/// Version 1 Migration
24122/// This migration ensures that any existing `Invulnerables` storage lists are sorted.
25123pub mod v1 {
@@ -90,3 +188,136 @@ pub mod v1 {
90188 }
91189 }
92190}
191+
192+ #[ cfg( all( feature = "try-runtime" , test) ) ]
193+ mod tests {
194+ use super :: * ;
195+ use crate :: {
196+ migration:: v2:: Candidates ,
197+ mock:: { new_test_ext, Balances , Test } ,
198+ } ;
199+ use frame_support:: {
200+ traits:: { Currency , ReservableCurrency , StorageVersion } ,
201+ BoundedVec ,
202+ } ;
203+ use sp_runtime:: traits:: ConstU32 ;
204+
205+ #[ test]
206+ fn migrate_to_v2_with_new_candidates ( ) {
207+ new_test_ext ( ) . execute_with ( || {
208+ let storage_version = StorageVersion :: new ( 1 ) ;
209+ storage_version. put :: < Pallet < Test > > ( ) ;
210+
211+ let one = 1u64 ;
212+ let two = 2u64 ;
213+ let three = 3u64 ;
214+ let deposit = 10u64 ;
215+
216+ // Set balance to 100
217+ Balances :: make_free_balance_be ( & one, 100u64 ) ;
218+ Balances :: make_free_balance_be ( & two, 100u64 ) ;
219+ Balances :: make_free_balance_be ( & three, 100u64 ) ;
220+
221+ // Reservations: 10 for the "old" candidacy and 10 for the "new"
222+ Balances :: reserve ( & one, 10u64 ) . unwrap ( ) ; // old
223+ Balances :: reserve ( & two, 20u64 ) . unwrap ( ) ; // old + new
224+ Balances :: reserve ( & three, 10u64 ) . unwrap ( ) ; // new
225+
226+ // Candidate info
227+ let candidate_one = CandidateInfo { who : one, deposit } ;
228+ let candidate_two = CandidateInfo { who : two, deposit } ;
229+ let candidate_three = CandidateInfo { who : three, deposit } ;
230+
231+ // Storage lists
232+ let bounded_candidates =
233+ BoundedVec :: < CandidateInfo < u64 , u64 > , ConstU32 < 20 > > :: try_from ( vec ! [
234+ candidate_one. clone( ) ,
235+ candidate_two. clone( ) ,
236+ ] )
237+ . expect ( "it works" ) ;
238+ let bounded_candidate_list =
239+ BoundedVec :: < CandidateInfo < u64 , u64 > , ConstU32 < 20 > > :: try_from ( vec ! [
240+ candidate_two. clone( ) ,
241+ candidate_three. clone( ) ,
242+ ] )
243+ . expect ( "it works" ) ;
244+
245+ // Set storage
246+ Candidates :: < Test > :: put ( bounded_candidates) ;
247+ CandidateList :: < Test > :: put ( bounded_candidate_list. clone ( ) ) ;
248+
249+ // Sanity check
250+ assert_eq ! ( Balances :: free_balance( one) , 90 ) ;
251+ assert_eq ! ( Balances :: free_balance( two) , 80 ) ;
252+ assert_eq ! ( Balances :: free_balance( three) , 90 ) ;
253+
254+ // Run migration
255+ v2:: MigrationToV2 :: < Test > :: on_runtime_upgrade ( ) ;
256+
257+ let new_storage_version = StorageVersion :: get :: < Pallet < Test > > ( ) ;
258+ assert_eq ! ( new_storage_version, 2 ) ;
259+
260+ // 10 should have been unreserved from the old candidacy
261+ assert_eq ! ( Balances :: free_balance( one) , 100 ) ;
262+ assert_eq ! ( Balances :: free_balance( two) , 90 ) ;
263+ assert_eq ! ( Balances :: free_balance( three) , 90 ) ;
264+ // The storage item should be gone
265+ assert ! ( Candidates :: <Test >:: get( ) . is_empty( ) ) ;
266+ // The new storage item should be preserved
267+ assert_eq ! ( CandidateList :: <Test >:: get( ) , bounded_candidate_list) ;
268+ } ) ;
269+ }
270+
271+ #[ test]
272+ fn migrate_to_v2_without_new_candidates ( ) {
273+ new_test_ext ( ) . execute_with ( || {
274+ let storage_version = StorageVersion :: new ( 1 ) ;
275+ storage_version. put :: < Pallet < Test > > ( ) ;
276+
277+ let one = 1u64 ;
278+ let two = 2u64 ;
279+ let deposit = 10u64 ;
280+
281+ // Set balance to 100
282+ Balances :: make_free_balance_be ( & one, 100u64 ) ;
283+ Balances :: make_free_balance_be ( & two, 100u64 ) ;
284+
285+ // Reservations
286+ Balances :: reserve ( & one, 10u64 ) . unwrap ( ) ; // old
287+ Balances :: reserve ( & two, 10u64 ) . unwrap ( ) ; // old
288+
289+ // Candidate info
290+ let candidate_one = CandidateInfo { who : one, deposit } ;
291+ let candidate_two = CandidateInfo { who : two, deposit } ;
292+
293+ // Storage lists
294+ let bounded_candidates =
295+ BoundedVec :: < CandidateInfo < u64 , u64 > , ConstU32 < 20 > > :: try_from ( vec ! [
296+ candidate_one. clone( ) ,
297+ candidate_two. clone( ) ,
298+ ] )
299+ . expect ( "it works" ) ;
300+
301+ // Set storage
302+ Candidates :: < Test > :: put ( bounded_candidates. clone ( ) ) ;
303+
304+ // Sanity check
305+ assert_eq ! ( Balances :: free_balance( one) , 90 ) ;
306+ assert_eq ! ( Balances :: free_balance( two) , 90 ) ;
307+
308+ // Run migration
309+ v2:: MigrationToV2 :: < Test > :: on_runtime_upgrade ( ) ;
310+
311+ let new_storage_version = StorageVersion :: get :: < Pallet < Test > > ( ) ;
312+ assert_eq ! ( new_storage_version, 2 ) ;
313+
314+ // Nothing changes deposit-wise
315+ assert_eq ! ( Balances :: free_balance( one) , 90 ) ;
316+ assert_eq ! ( Balances :: free_balance( two) , 90 ) ;
317+ // The storage item should be gone
318+ assert ! ( Candidates :: <Test >:: get( ) . is_empty( ) ) ;
319+ // The new storage item should have the info now
320+ assert_eq ! ( CandidateList :: <Test >:: get( ) , bounded_candidates) ;
321+ } ) ;
322+ }
323+ }
0 commit comments