referrerpolicy=no-referrer-when-downgrade

pallet_referenda/
migration.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Storage migrations for the referenda pallet.
19
20use super::*;
21use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
22use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade};
23use log;
24
25#[cfg(feature = "try-runtime")]
26use sp_runtime::TryRuntimeError;
27
28type SystemBlockNumberFor<T> = frame_system::pallet_prelude::BlockNumberFor<T>;
29
30/// Initial version of storage types.
31pub mod v0 {
32	use super::*;
33	// ReferendumStatus and its dependency types referenced from the latest version while staying
34	// unchanged. [`super::test::referendum_status_v0()`] checks its immutability between v0 and
35	// latest version.
36	#[cfg(test)]
37	pub(super) use super::{ReferendumStatus, ReferendumStatusOf};
38
39	pub type ReferendumInfoOf<T, I> = ReferendumInfo<
40		TrackIdOf<T, I>,
41		PalletsOriginOf<T>,
42		SystemBlockNumberFor<T>,
43		BoundedCallOf<T, I>,
44		BalanceOf<T, I>,
45		TallyOf<T, I>,
46		<T as frame_system::Config>::AccountId,
47		ScheduleAddressOf<T, I>,
48	>;
49
50	/// Info regarding a referendum, present or past.
51	#[derive(
52		Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen, DecodeWithMemTracking,
53	)]
54	pub enum ReferendumInfo<
55		TrackId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
56		RuntimeOrigin: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
57		Moment: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone + EncodeLike,
58		Call: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
59		Balance: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
60		Tally: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
61		AccountId: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
62		ScheduleAddress: Eq + PartialEq + Debug + Encode + Decode + TypeInfo + Clone,
63	> {
64		/// Referendum has been submitted and is being voted on.
65		Ongoing(
66			ReferendumStatus<
67				TrackId,
68				RuntimeOrigin,
69				Moment,
70				Call,
71				Balance,
72				Tally,
73				AccountId,
74				ScheduleAddress,
75			>,
76		),
77		/// Referendum finished with approval. Submission deposit is held.
78		Approved(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
79		/// Referendum finished with rejection. Submission deposit is held.
80		Rejected(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
81		/// Referendum finished with cancellation. Submission deposit is held.
82		Cancelled(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
83		/// Referendum finished and was never decided. Submission deposit is held.
84		TimedOut(Moment, Deposit<AccountId, Balance>, Option<Deposit<AccountId, Balance>>),
85		/// Referendum finished with a kill.
86		Killed(Moment),
87	}
88
89	#[storage_alias]
90	pub type ReferendumInfoFor<T: Config<I>, I: 'static> =
91		StorageMap<Pallet<T, I>, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf<T, I>>;
92}
93
94pub mod v1 {
95	use super::*;
96
97	/// The log target.
98	const TARGET: &'static str = "runtime::referenda::migration::v1";
99
100	pub(crate) type ReferendumInfoOf<T, I> = ReferendumInfo<
101		TrackIdOf<T, I>,
102		PalletsOriginOf<T>,
103		SystemBlockNumberFor<T>,
104		BoundedCallOf<T, I>,
105		BalanceOf<T, I>,
106		TallyOf<T, I>,
107		<T as frame_system::Config>::AccountId,
108		ScheduleAddressOf<T, I>,
109	>;
110
111	#[storage_alias]
112	pub type ReferendumInfoFor<T: Config<I>, I: 'static> =
113		StorageMap<Pallet<T, I>, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf<T, I>>;
114
115	/// Transforms a submission deposit of ReferendumInfo(Approved|Rejected|Cancelled|TimedOut) to
116	/// optional value, making it refundable.
117	pub struct MigrateV0ToV1<T, I = ()>(PhantomData<(T, I)>);
118	impl<T: Config<I>, I: 'static> OnRuntimeUpgrade for MigrateV0ToV1<T, I> {
119		#[cfg(feature = "try-runtime")]
120		fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
121			let referendum_count = v0::ReferendumInfoFor::<T, I>::iter().count();
122			log::info!(
123				target: TARGET,
124				"pre-upgrade state contains '{}' referendums.",
125				referendum_count
126			);
127			Ok((referendum_count as u32).encode())
128		}
129
130		fn on_runtime_upgrade() -> Weight {
131			let in_code_version = Pallet::<T, I>::in_code_storage_version();
132			let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
133			let mut weight = T::DbWeight::get().reads(1);
134			log::info!(
135				target: TARGET,
136				"running migration with in-code storage version {:?} / onchain {:?}.",
137				in_code_version,
138				on_chain_version
139			);
140			if on_chain_version != 0 {
141				log::warn!(target: TARGET, "skipping migration from v0 to v1.");
142				return weight;
143			}
144			v0::ReferendumInfoFor::<T, I>::iter().for_each(|(key, value)| {
145				let maybe_new_value = match value {
146					v0::ReferendumInfo::Ongoing(_) | v0::ReferendumInfo::Killed(_) => None,
147					v0::ReferendumInfo::Approved(e, s, d) => {
148						Some(ReferendumInfo::Approved(e, Some(s), d))
149					},
150					v0::ReferendumInfo::Rejected(e, s, d) => {
151						Some(ReferendumInfo::Rejected(e, Some(s), d))
152					},
153					v0::ReferendumInfo::Cancelled(e, s, d) => {
154						Some(ReferendumInfo::Cancelled(e, Some(s), d))
155					},
156					v0::ReferendumInfo::TimedOut(e, s, d) => {
157						Some(ReferendumInfo::TimedOut(e, Some(s), d))
158					},
159				};
160				if let Some(new_value) = maybe_new_value {
161					weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
162					log::info!(target: TARGET, "migrating referendum #{:?}", &key);
163					v1::ReferendumInfoFor::<T, I>::insert(key, new_value);
164				} else {
165					weight.saturating_accrue(T::DbWeight::get().reads(1));
166				}
167			});
168			StorageVersion::new(1).put::<Pallet<T, I>>();
169			weight.saturating_accrue(T::DbWeight::get().writes(1));
170			weight
171		}
172
173		#[cfg(feature = "try-runtime")]
174		fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
175			let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
176			ensure!(on_chain_version == 1, "must upgrade from version 0 to 1.");
177			let pre_referendum_count: u32 = Decode::decode(&mut &state[..])
178				.expect("failed to decode the state from pre-upgrade.");
179			let post_referendum_count = ReferendumInfoFor::<T, I>::iter().count() as u32;
180			ensure!(post_referendum_count == pre_referendum_count, "must migrate all referendums.");
181			log::info!(target: TARGET, "migrated all referendums.");
182			Ok(())
183		}
184	}
185}
186
187/// Migration for when changing the block number provider.
188///
189/// This migration is not guarded
190pub mod switch_block_number_provider {
191	use super::*;
192
193	/// The log target.
194	const TARGET: &'static str = "runtime::referenda::migration::change_block_number_provider";
195	/// Convert from one to another block number provider/type.
196	pub trait BlockNumberConversion<Old, New> {
197		/// Convert the `old` block number type to the new block number type.
198		///
199		/// Any changes in the rate of blocks need to be taken into account.
200		fn convert_block_number(block_number: Old) -> New;
201	}
202
203	/// Transforms `SystemBlockNumberFor<T>` to `BlockNumberFor<T,I>`
204	pub struct MigrateBlockNumberProvider<BlockConverter, T, I = ()>(
205		PhantomData<(T, I)>,
206		PhantomData<BlockConverter>,
207	);
208	impl<BlockConverter: BlockNumberConversion<T, I>, T: Config<I>, I: 'static> OnRuntimeUpgrade
209		for MigrateBlockNumberProvider<BlockConverter, T, I>
210	where
211		BlockConverter: BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, I>>,
212		T: Config<I>,
213	{
214		#[cfg(feature = "try-runtime")]
215		fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
216			let referendum_count = v1::ReferendumInfoFor::<T, I>::iter().count();
217			log::info!(
218				target: TARGET,
219				"pre-upgrade state contains '{}' referendums.",
220				referendum_count
221			);
222			Ok((referendum_count as u32).encode())
223		}
224
225		fn on_runtime_upgrade() -> Weight {
226			let mut weight = Weight::zero();
227			weight.saturating_accrue(migrate_block_number_provider::<BlockConverter, T, I>());
228			weight
229		}
230
231		#[cfg(feature = "try-runtime")]
232		fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
233			let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
234			ensure!(on_chain_version == 1, "must upgrade from version 1 to 2.");
235			let pre_referendum_count: u32 = Decode::decode(&mut &state[..])
236				.expect("failed to decode the state from pre-upgrade.");
237			let post_referendum_count = ReferendumInfoFor::<T, I>::iter().count() as u32;
238			ensure!(post_referendum_count == pre_referendum_count, "must migrate all referendums.");
239			log::info!(target: TARGET, "migrated all referendums.");
240			Ok(())
241		}
242	}
243
244	pub fn migrate_block_number_provider<BlockConverter, T, I: 'static>() -> Weight
245	where
246		BlockConverter: BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, I>>,
247		T: Config<I>,
248	{
249		let in_code_version = Pallet::<T, I>::in_code_storage_version();
250		let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
251		let mut weight = T::DbWeight::get().reads(1);
252		log::info!(
253			target: "runtime::referenda::migration::change_block_number_provider",
254			"running migration with in-code storage version {:?} / onchain {:?}.",
255			in_code_version,
256			on_chain_version
257		);
258		if on_chain_version == 0 {
259			log::error!(target: TARGET, "skipping migration from v0 to switch_block_number_provider.");
260			return weight;
261		}
262
263		// Migration logic here
264		v1::ReferendumInfoFor::<T, I>::iter().for_each(|(key, value)| {
265			let maybe_new_value = match value {
266				ReferendumInfo::Ongoing(_) | ReferendumInfo::Killed(_) => None,
267				ReferendumInfo::Approved(e, s, d) => {
268					let new_e = BlockConverter::convert_block_number(e);
269					Some(ReferendumInfo::Approved(new_e, s, d))
270				},
271				ReferendumInfo::Rejected(e, s, d) => {
272					let new_e = BlockConverter::convert_block_number(e);
273					Some(ReferendumInfo::Rejected(new_e, s, d))
274				},
275				ReferendumInfo::Cancelled(e, s, d) => {
276					let new_e = BlockConverter::convert_block_number(e);
277					Some(ReferendumInfo::Cancelled(new_e, s, d))
278				},
279				ReferendumInfo::TimedOut(e, s, d) => {
280					let new_e = BlockConverter::convert_block_number(e);
281					Some(ReferendumInfo::TimedOut(new_e, s, d))
282				},
283			};
284			if let Some(new_value) = maybe_new_value {
285				weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
286				log::info!(target: TARGET, "migrating referendum #{:?}", &key);
287				ReferendumInfoFor::<T, I>::insert(key, new_value);
288			} else {
289				weight.saturating_accrue(T::DbWeight::get().reads(1));
290			}
291		});
292
293		weight
294	}
295}
296
297#[cfg(test)]
298pub mod test {
299	use super::*;
300	use crate::{
301		migration::switch_block_number_provider::{
302			migrate_block_number_provider, BlockNumberConversion,
303		},
304		mock::{Test as T, *},
305	};
306	use core::str::FromStr;
307
308	// create referendum status v0.
309	fn create_status_v0() -> v0::ReferendumStatusOf<T, ()> {
310		let origin: OriginCaller = frame_system::RawOrigin::Root.into();
311		let track = <T as Config<()>>::Tracks::track_for(&origin).unwrap();
312		v0::ReferendumStatusOf::<T, ()> {
313			track,
314			in_queue: true,
315			origin,
316			proposal: set_balance_proposal_bounded(1),
317			enactment: DispatchTime::At(1),
318			tally: TallyOf::<T, ()>::new(track),
319			submission_deposit: Deposit { who: 1, amount: 10 },
320			submitted: 1,
321			decision_deposit: None,
322			alarm: None,
323			deciding: None,
324		}
325	}
326	#[test]
327	pub fn referendum_status_v0() {
328		// make sure the bytes of the encoded referendum v0 is decodable.
329		let ongoing_encoded = sp_core::Bytes::from_str("0x00000000012c01082a0000000000000004000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap();
330		let ongoing_dec = v0::ReferendumInfoOf::<T, ()>::decode(&mut &*ongoing_encoded).unwrap();
331		let ongoing = v0::ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0());
332		assert_eq!(ongoing, ongoing_dec);
333	}
334
335	#[test]
336	fn migration_v0_to_v1_works() {
337		ExtBuilder::default().build_and_execute(|| {
338			// create and insert into the storage an ongoing referendum v0.
339			let status_v0 = create_status_v0();
340			let ongoing_v0 = v0::ReferendumInfoOf::<T, ()>::Ongoing(status_v0.clone());
341			ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
342			v0::ReferendumInfoFor::<T, ()>::insert(2, ongoing_v0);
343			// create and insert into the storage an approved referendum v0.
344			let approved_v0 = v0::ReferendumInfoOf::<T, ()>::Approved(
345				123,
346				Deposit { who: 1, amount: 10 },
347				Some(Deposit { who: 2, amount: 20 }),
348			);
349			ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
350			v0::ReferendumInfoFor::<T, ()>::insert(5, approved_v0);
351			// run migration from v0 to v1.
352			v1::MigrateV0ToV1::<T, ()>::on_runtime_upgrade();
353			// fetch and assert migrated into v1 the ongoing referendum.
354			let ongoing_v1 = v1::ReferendumInfoFor::<T, ()>::get(2).unwrap();
355			// referendum status schema is the same for v0 and v1.
356			assert_eq!(ReferendumInfoOf::<T, ()>::Ongoing(status_v0), ongoing_v1);
357			// fetch and assert migrated into v1 the approved referendum.
358			let approved_v1 = v1::ReferendumInfoFor::<T, ()>::get(5).unwrap();
359			assert_eq!(
360				approved_v1,
361				ReferendumInfoOf::<T, ()>::Approved(
362					123,
363					Some(Deposit { who: 1, amount: 10 }),
364					Some(Deposit { who: 2, amount: 20 })
365				)
366			);
367		});
368	}
369
370	#[test]
371	fn migration_v1_to_switch_block_number_provider_works() {
372		ExtBuilder::default().build_and_execute(|| {
373			pub struct MockBlockConverter;
374
375			impl BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, ()>> for MockBlockConverter {
376				fn convert_block_number(block_number: SystemBlockNumberFor<T>) -> BlockNumberFor<T, ()> {
377					block_number as u64 + 10u64
378				}
379			}
380
381			let referendum_ongoing = v1::ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0());
382			let referendum_approved = v1::ReferendumInfoOf::<T, ()>::Approved(
383				50, //old block number
384				Some(Deposit { who: 1, amount: 10 }),
385				Some(Deposit { who: 2, amount: 20 }),
386			);
387
388			ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
389			v1::ReferendumInfoFor::<T, ()>::insert(1, referendum_ongoing);
390
391			ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
392			v1::ReferendumInfoFor::<T, ()>::insert(2, referendum_approved);
393
394			migrate_block_number_provider::<MockBlockConverter, T, ()>();
395
396			let ongoing_v2 = ReferendumInfoFor::<T, ()>::get(1).unwrap();
397			assert_eq!(
398				ongoing_v2,
399				ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0())
400			);
401
402			let approved_v2 = ReferendumInfoFor::<T, ()>::get(2).unwrap();
403			assert_eq!(
404				approved_v2,
405				ReferendumInfoOf::<T, ()>::Approved(
406					50,
407					Some(Deposit { who: 1, amount: 10 }),
408					Some(Deposit { who: 2, amount: 20 })
409				)
410			);
411		});
412	}
413}