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					v0::ReferendumInfo::Rejected(e, s, d) =>
150						Some(ReferendumInfo::Rejected(e, Some(s), d)),
151					v0::ReferendumInfo::Cancelled(e, s, d) =>
152						Some(ReferendumInfo::Cancelled(e, Some(s), d)),
153					v0::ReferendumInfo::TimedOut(e, s, d) =>
154						Some(ReferendumInfo::TimedOut(e, Some(s), d)),
155				};
156				if let Some(new_value) = maybe_new_value {
157					weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
158					log::info!(target: TARGET, "migrating referendum #{:?}", &key);
159					v1::ReferendumInfoFor::<T, I>::insert(key, new_value);
160				} else {
161					weight.saturating_accrue(T::DbWeight::get().reads(1));
162				}
163			});
164			StorageVersion::new(1).put::<Pallet<T, I>>();
165			weight.saturating_accrue(T::DbWeight::get().writes(1));
166			weight
167		}
168
169		#[cfg(feature = "try-runtime")]
170		fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
171			let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
172			ensure!(on_chain_version == 1, "must upgrade from version 0 to 1.");
173			let pre_referendum_count: u32 = Decode::decode(&mut &state[..])
174				.expect("failed to decode the state from pre-upgrade.");
175			let post_referendum_count = ReferendumInfoFor::<T, I>::iter().count() as u32;
176			ensure!(post_referendum_count == pre_referendum_count, "must migrate all referendums.");
177			log::info!(target: TARGET, "migrated all referendums.");
178			Ok(())
179		}
180	}
181}
182
183/// Migration for when changing the block number provider.
184///
185/// This migration is not guarded
186pub mod switch_block_number_provider {
187	use super::*;
188
189	/// The log target.
190	const TARGET: &'static str = "runtime::referenda::migration::change_block_number_provider";
191	/// Convert from one to another block number provider/type.
192	pub trait BlockNumberConversion<Old, New> {
193		/// Convert the `old` block number type to the new block number type.
194		///
195		/// Any changes in the rate of blocks need to be taken into account.
196		fn convert_block_number(block_number: Old) -> New;
197	}
198
199	/// Transforms `SystemBlockNumberFor<T>` to `BlockNumberFor<T,I>`
200	pub struct MigrateBlockNumberProvider<BlockConverter, T, I = ()>(
201		PhantomData<(T, I)>,
202		PhantomData<BlockConverter>,
203	);
204	impl<BlockConverter: BlockNumberConversion<T, I>, T: Config<I>, I: 'static> OnRuntimeUpgrade
205		for MigrateBlockNumberProvider<BlockConverter, T, I>
206	where
207		BlockConverter: BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, I>>,
208		T: Config<I>,
209	{
210		#[cfg(feature = "try-runtime")]
211		fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
212			let referendum_count = v1::ReferendumInfoFor::<T, I>::iter().count();
213			log::info!(
214				target: TARGET,
215				"pre-upgrade state contains '{}' referendums.",
216				referendum_count
217			);
218			Ok((referendum_count as u32).encode())
219		}
220
221		fn on_runtime_upgrade() -> Weight {
222			let mut weight = Weight::zero();
223			weight.saturating_accrue(migrate_block_number_provider::<BlockConverter, T, I>());
224			weight
225		}
226
227		#[cfg(feature = "try-runtime")]
228		fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
229			let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
230			ensure!(on_chain_version == 1, "must upgrade from version 1 to 2.");
231			let pre_referendum_count: u32 = Decode::decode(&mut &state[..])
232				.expect("failed to decode the state from pre-upgrade.");
233			let post_referendum_count = ReferendumInfoFor::<T, I>::iter().count() as u32;
234			ensure!(post_referendum_count == pre_referendum_count, "must migrate all referendums.");
235			log::info!(target: TARGET, "migrated all referendums.");
236			Ok(())
237		}
238	}
239
240	pub fn migrate_block_number_provider<BlockConverter, T, I: 'static>() -> Weight
241	where
242		BlockConverter: BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, I>>,
243		T: Config<I>,
244	{
245		let in_code_version = Pallet::<T, I>::in_code_storage_version();
246		let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
247		let mut weight = T::DbWeight::get().reads(1);
248		log::info!(
249			target: "runtime::referenda::migration::change_block_number_provider",
250			"running migration with in-code storage version {:?} / onchain {:?}.",
251			in_code_version,
252			on_chain_version
253		);
254		if on_chain_version == 0 {
255			log::error!(target: TARGET, "skipping migration from v0 to switch_block_number_provider.");
256			return weight
257		}
258
259		// Migration logic here
260		v1::ReferendumInfoFor::<T, I>::iter().for_each(|(key, value)| {
261			let maybe_new_value = match value {
262				ReferendumInfo::Ongoing(_) | ReferendumInfo::Killed(_) => None,
263				ReferendumInfo::Approved(e, s, d) => {
264					let new_e = BlockConverter::convert_block_number(e);
265					Some(ReferendumInfo::Approved(new_e, s, d))
266				},
267				ReferendumInfo::Rejected(e, s, d) => {
268					let new_e = BlockConverter::convert_block_number(e);
269					Some(ReferendumInfo::Rejected(new_e, s, d))
270				},
271				ReferendumInfo::Cancelled(e, s, d) => {
272					let new_e = BlockConverter::convert_block_number(e);
273					Some(ReferendumInfo::Cancelled(new_e, s, d))
274				},
275				ReferendumInfo::TimedOut(e, s, d) => {
276					let new_e = BlockConverter::convert_block_number(e);
277					Some(ReferendumInfo::TimedOut(new_e, s, d))
278				},
279			};
280			if let Some(new_value) = maybe_new_value {
281				weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
282				log::info!(target: TARGET, "migrating referendum #{:?}", &key);
283				ReferendumInfoFor::<T, I>::insert(key, new_value);
284			} else {
285				weight.saturating_accrue(T::DbWeight::get().reads(1));
286			}
287		});
288
289		weight
290	}
291}
292
293#[cfg(test)]
294pub mod test {
295	use super::*;
296	use crate::{
297		migration::switch_block_number_provider::{
298			migrate_block_number_provider, BlockNumberConversion,
299		},
300		mock::{Test as T, *},
301	};
302	use core::str::FromStr;
303
304	// create referendum status v0.
305	fn create_status_v0() -> v0::ReferendumStatusOf<T, ()> {
306		let origin: OriginCaller = frame_system::RawOrigin::Root.into();
307		let track = <T as Config<()>>::Tracks::track_for(&origin).unwrap();
308		v0::ReferendumStatusOf::<T, ()> {
309			track,
310			in_queue: true,
311			origin,
312			proposal: set_balance_proposal_bounded(1),
313			enactment: DispatchTime::At(1),
314			tally: TallyOf::<T, ()>::new(track),
315			submission_deposit: Deposit { who: 1, amount: 10 },
316			submitted: 1,
317			decision_deposit: None,
318			alarm: None,
319			deciding: None,
320		}
321	}
322	#[test]
323	pub fn referendum_status_v0() {
324		// make sure the bytes of the encoded referendum v0 is decodable.
325		let ongoing_encoded = sp_core::Bytes::from_str("0x00000000012c01082a0000000000000004000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap();
326		let ongoing_dec = v0::ReferendumInfoOf::<T, ()>::decode(&mut &*ongoing_encoded).unwrap();
327		let ongoing = v0::ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0());
328		assert_eq!(ongoing, ongoing_dec);
329	}
330
331	#[test]
332	fn migration_v0_to_v1_works() {
333		ExtBuilder::default().build_and_execute(|| {
334			// create and insert into the storage an ongoing referendum v0.
335			let status_v0 = create_status_v0();
336			let ongoing_v0 = v0::ReferendumInfoOf::<T, ()>::Ongoing(status_v0.clone());
337			ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
338			v0::ReferendumInfoFor::<T, ()>::insert(2, ongoing_v0);
339			// create and insert into the storage an approved referendum v0.
340			let approved_v0 = v0::ReferendumInfoOf::<T, ()>::Approved(
341				123,
342				Deposit { who: 1, amount: 10 },
343				Some(Deposit { who: 2, amount: 20 }),
344			);
345			ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
346			v0::ReferendumInfoFor::<T, ()>::insert(5, approved_v0);
347			// run migration from v0 to v1.
348			v1::MigrateV0ToV1::<T, ()>::on_runtime_upgrade();
349			// fetch and assert migrated into v1 the ongoing referendum.
350			let ongoing_v1 = v1::ReferendumInfoFor::<T, ()>::get(2).unwrap();
351			// referendum status schema is the same for v0 and v1.
352			assert_eq!(ReferendumInfoOf::<T, ()>::Ongoing(status_v0), ongoing_v1);
353			// fetch and assert migrated into v1 the approved referendum.
354			let approved_v1 = v1::ReferendumInfoFor::<T, ()>::get(5).unwrap();
355			assert_eq!(
356				approved_v1,
357				ReferendumInfoOf::<T, ()>::Approved(
358					123,
359					Some(Deposit { who: 1, amount: 10 }),
360					Some(Deposit { who: 2, amount: 20 })
361				)
362			);
363		});
364	}
365
366	#[test]
367	fn migration_v1_to_switch_block_number_provider_works() {
368		ExtBuilder::default().build_and_execute(|| {
369			pub struct MockBlockConverter;
370
371			impl BlockNumberConversion<SystemBlockNumberFor<T>, BlockNumberFor<T, ()>> for MockBlockConverter {
372				fn convert_block_number(block_number: SystemBlockNumberFor<T>) -> BlockNumberFor<T, ()> {
373					block_number as u64 + 10u64
374				}
375			}
376
377			let referendum_ongoing = v1::ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0());
378			let referendum_approved = v1::ReferendumInfoOf::<T, ()>::Approved(
379				50, //old block number
380				Some(Deposit { who: 1, amount: 10 }),
381				Some(Deposit { who: 2, amount: 20 }),
382			);
383
384			ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
385			v1::ReferendumInfoFor::<T, ()>::insert(1, referendum_ongoing);
386
387			ReferendumCount::<T, ()>::mutate(|x| x.saturating_inc());
388			v1::ReferendumInfoFor::<T, ()>::insert(2, referendum_approved);
389
390			migrate_block_number_provider::<MockBlockConverter, T, ()>();
391
392			let ongoing_v2 = ReferendumInfoFor::<T, ()>::get(1).unwrap();
393			assert_eq!(
394				ongoing_v2,
395				ReferendumInfoOf::<T, ()>::Ongoing(create_status_v0())
396			);
397
398			let approved_v2 = ReferendumInfoFor::<T, ()>::get(2).unwrap();
399			assert_eq!(
400				approved_v2,
401				ReferendumInfoOf::<T, ()>::Approved(
402					50,
403					Some(Deposit { who: 1, amount: 10 }),
404					Some(Deposit { who: 2, amount: 20 })
405				)
406			);
407		});
408	}
409}