referrerpolicy=no-referrer-when-downgrade

pallet_example_single_block_migrations/migrations/
v1.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: MIT-0
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy of
7// this software and associated documentation files (the "Software"), to deal in
8// the Software without restriction, including without limitation the rights to
9// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10// of the Software, and to permit persons to whom the Software is furnished to do
11// so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23
24use frame_support::{
25	storage_alias,
26	traits::{Get, UncheckedOnRuntimeUpgrade},
27};
28
29#[cfg(feature = "try-runtime")]
30use alloc::vec::Vec;
31
32/// Collection of storage item formats from the previous storage version.
33///
34/// Required so we can read values in the v0 storage format during the migration.
35mod v0 {
36	use super::*;
37
38	/// V0 type for [`crate::Value`].
39	#[storage_alias]
40	pub type Value<T: crate::Config> = StorageValue<crate::Pallet<T>, u32>;
41}
42
43/// Implements [`UncheckedOnRuntimeUpgrade`], migrating the state of this pallet from V0 to V1.
44///
45/// In V0 of the template [`crate::Value`] is just a `u32`. In V1, it has been upgraded to
46/// contain the struct [`crate::CurrentAndPreviousValue`].
47///
48/// In this migration, update the on-chain storage for the pallet to reflect the new storage
49/// layout.
50pub struct InnerMigrateV0ToV1<T: crate::Config>(core::marker::PhantomData<T>);
51
52impl<T: crate::Config> UncheckedOnRuntimeUpgrade for InnerMigrateV0ToV1<T> {
53	/// Return the existing [`crate::Value`] so we can check that it was correctly set in
54	/// `InnerMigrateV0ToV1::post_upgrade`.
55	#[cfg(feature = "try-runtime")]
56	fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
57		use codec::Encode;
58
59		// Access the old value using the `storage_alias` type
60		let old_value = v0::Value::<T>::get();
61		// Return it as an encoded `Vec<u8>`
62		Ok(old_value.encode())
63	}
64
65	/// Migrate the storage from V0 to V1.
66	///
67	/// - If the value doesn't exist, there is nothing to do.
68	/// - If the value exists, it is read and then written back to storage inside a
69	///   [`crate::CurrentAndPreviousValue`].
70	fn on_runtime_upgrade() -> frame_support::weights::Weight {
71		// Read the old value from storage
72		if let Some(old_value) = v0::Value::<T>::take() {
73			// Write the new value to storage
74			let new = crate::CurrentAndPreviousValue { current: old_value, previous: None };
75			crate::Value::<T>::put(new);
76			// One read + write for taking the old value, and one write for setting the new value
77			T::DbWeight::get().reads_writes(1, 2)
78		} else {
79			// No writes since there was no old value, just one read for checking
80			T::DbWeight::get().reads(1)
81		}
82	}
83
84	/// Verifies the storage was migrated correctly.
85	///
86	/// - If there was no old value, the new value should not be set.
87	/// - If there was an old value, the new value should be a [`crate::CurrentAndPreviousValue`].
88	#[cfg(feature = "try-runtime")]
89	fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
90		use codec::Decode;
91		use frame_support::ensure;
92
93		let maybe_old_value = Option::<u32>::decode(&mut &state[..]).map_err(|_| {
94			sp_runtime::TryRuntimeError::Other("Failed to decode old value from storage")
95		})?;
96
97		match maybe_old_value {
98			Some(old_value) => {
99				let expected_new_value =
100					crate::CurrentAndPreviousValue { current: old_value, previous: None };
101				let actual_new_value = crate::Value::<T>::get();
102
103				ensure!(actual_new_value.is_some(), "New value not set");
104				ensure!(
105					actual_new_value == Some(expected_new_value),
106					"New value not set correctly"
107				);
108			},
109			None => {
110				ensure!(crate::Value::<T>::get().is_none(), "New value unexpectedly set");
111			},
112		};
113		Ok(())
114	}
115}
116
117/// [`UncheckedOnRuntimeUpgrade`] implementation [`InnerMigrateV0ToV1`] wrapped in a
118/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), which ensures that:
119/// - The migration only runs once when the on-chain storage version is 0
120/// - The on-chain storage version is updated to `1` after the migration executes
121/// - Reads/Writes from checking/settings the on-chain storage version are accounted for
122pub type MigrateV0ToV1<T> = frame_support::migrations::VersionedMigration<
123	0, // The migration will only execute when the on-chain storage version is 0
124	1, // The on-chain storage version will be set to 1 after the migration is complete
125	InnerMigrateV0ToV1<T>,
126	crate::pallet::Pallet<T>,
127	<T as frame_system::Config>::DbWeight,
128>;
129
130/// Tests for our migration.
131///
132/// When writing migration tests, it is important to check:
133/// 1. `on_runtime_upgrade` returns the expected weight
134/// 2. `post_upgrade` succeeds when given the bytes returned by `pre_upgrade`
135/// 3. The storage is in the expected state after the migration
136#[cfg(any(all(feature = "try-runtime", test), doc))]
137mod test {
138	use self::InnerMigrateV0ToV1;
139	use super::*;
140	use crate::mock::{new_test_ext, MockRuntime};
141	use frame_support::assert_ok;
142
143	#[test]
144	fn handles_no_existing_value() {
145		new_test_ext().execute_with(|| {
146			// By default, no value should be set. Verify this assumption.
147			assert!(crate::Value::<MockRuntime>::get().is_none());
148			assert!(v0::Value::<MockRuntime>::get().is_none());
149
150			// Get the pre_upgrade bytes
151			let bytes = match InnerMigrateV0ToV1::<MockRuntime>::pre_upgrade() {
152				Ok(bytes) => bytes,
153				Err(e) => panic!("pre_upgrade failed: {e:?}"),
154			};
155
156			// Execute the migration
157			let weight = InnerMigrateV0ToV1::<MockRuntime>::on_runtime_upgrade();
158
159			// Verify post_upgrade succeeds
160			assert_ok!(InnerMigrateV0ToV1::<MockRuntime>::post_upgrade(bytes));
161
162			// The weight should be just 1 read for trying to access the old value.
163			assert_eq!(weight, <MockRuntime as frame_system::Config>::DbWeight::get().reads(1));
164
165			// After the migration, no value should have been set.
166			assert!(crate::Value::<MockRuntime>::get().is_none());
167		})
168	}
169
170	#[test]
171	fn handles_existing_value() {
172		new_test_ext().execute_with(|| {
173			// Set up an initial value
174			let initial_value = 42;
175			v0::Value::<MockRuntime>::put(initial_value);
176
177			// Get the pre_upgrade bytes
178			let bytes = match InnerMigrateV0ToV1::<MockRuntime>::pre_upgrade() {
179				Ok(bytes) => bytes,
180				Err(e) => panic!("pre_upgrade failed: {e:?}"),
181			};
182
183			// Execute the migration
184			let weight = InnerMigrateV0ToV1::<MockRuntime>::on_runtime_upgrade();
185
186			// Verify post_upgrade succeeds
187			assert_ok!(InnerMigrateV0ToV1::<MockRuntime>::post_upgrade(bytes));
188
189			// The weight used should be 1 read for the old value, and 1 write for the new
190			// value.
191			assert_eq!(
192				weight,
193				<MockRuntime as frame_system::Config>::DbWeight::get().reads_writes(1, 2)
194			);
195
196			// After the migration, the new value should be set as the `current` value.
197			let expected_new_value =
198				crate::CurrentAndPreviousValue { current: initial_value, previous: None };
199			assert_eq!(crate::Value::<MockRuntime>::get(), Some(expected_new_value));
200		})
201	}
202}