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