referrerpolicy=no-referrer-when-downgrade

pallet_revive/migrations/
v2.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//! # Multi-Block Migration v2
19//!
20//! - migrate the old `CodeInfoOf` storage to the new `CodeInfoOf` which add the new `code_type`
21//! field.
22//! - Unhold the deposit on the owner and transfer it to the pallet account.
23
24extern crate alloc;
25use super::PALLET_MIGRATIONS_ID;
26use crate::{vm::BytecodeType, weights::WeightInfo, Config, Pallet, H256, LOG_TARGET};
27use frame_support::{
28	migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
29	pallet_prelude::PhantomData,
30	traits::{
31		fungible::{Inspect, Mutate, MutateHold},
32		tokens::{Fortitude, Precision, Restriction},
33	},
34	weights::WeightMeter,
35};
36
37#[cfg(feature = "try-runtime")]
38use alloc::{collections::btree_map::BTreeMap, vec::Vec};
39
40#[cfg(feature = "try-runtime")]
41use frame_support::{sp_runtime::TryRuntimeError, traits::fungible::InspectHold};
42
43/// Module containing the old storage items.
44mod old {
45	use super::Config;
46	use crate::{pallet::Pallet, AccountIdOf, BalanceOf, H256};
47	use codec::{Decode, Encode};
48	use frame_support::{storage_alias, Identity};
49
50	#[derive(Clone, Encode, Decode)]
51	pub struct CodeInfo<T: Config> {
52		pub owner: AccountIdOf<T>,
53		#[codec(compact)]
54		pub deposit: BalanceOf<T>,
55		#[codec(compact)]
56		pub refcount: u64,
57		pub code_len: u32,
58		pub behaviour_version: u32,
59	}
60
61	#[storage_alias]
62	/// The storage item that is being migrated from.
63	pub type CodeInfoOf<T: Config> = StorageMap<Pallet<T>, Identity, H256, CodeInfo<T>>;
64}
65
66mod new {
67	use super::{BytecodeType, Config};
68	use crate::{pallet::Pallet, AccountIdOf, BalanceOf, H256};
69	use codec::{Decode, Encode};
70	use frame_support::{storage_alias, DebugNoBound, Identity};
71
72	#[derive(PartialEq, Eq, DebugNoBound, Encode, Decode)]
73	pub struct CodeInfo<T: Config> {
74		pub owner: AccountIdOf<T>,
75		#[codec(compact)]
76		pub deposit: BalanceOf<T>,
77		#[codec(compact)]
78		pub refcount: u64,
79		pub code_len: u32,
80		pub code_type: BytecodeType,
81		pub behaviour_version: u32,
82	}
83
84	#[storage_alias]
85	/// The storage item that is being migrated to.
86	pub type CodeInfoOf<T: Config> = StorageMap<Pallet<T>, Identity, H256, CodeInfo<T>>;
87}
88
89/// Migrates the items of the [`old::CodeInfoOf`] map into [`crate::CodeInfoOf`] by adding the
90/// `code_type` field.
91pub struct Migration<T: Config>(PhantomData<T>);
92
93impl<T: Config> SteppedMigration for Migration<T> {
94	type Cursor = H256;
95	type Identifier = MigrationId<17>;
96
97	fn id() -> Self::Identifier {
98		MigrationId { pallet_id: *PALLET_MIGRATIONS_ID, version_from: 1, version_to: 2 }
99	}
100
101	fn step(
102		mut cursor: Option<Self::Cursor>,
103		meter: &mut WeightMeter,
104	) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
105		let required = <T as Config>::WeightInfo::v2_migration_step();
106		if meter.remaining().any_lt(required) {
107			return Err(SteppedMigrationError::InsufficientWeight { required });
108		}
109
110		if !frame_system::Pallet::<T>::account_exists(&Pallet::<T>::account_id()) {
111			let _ =
112				T::Currency::mint_into(&Pallet::<T>::account_id(), T::Currency::minimum_balance());
113		}
114
115		loop {
116			if meter.try_consume(required).is_err() {
117				break;
118			}
119
120			let mut iter = if let Some(last_key) = cursor {
121				old::CodeInfoOf::<T>::iter_from(old::CodeInfoOf::<T>::hashed_key_for(last_key))
122			} else {
123				old::CodeInfoOf::<T>::iter()
124			};
125
126			if let Some((last_key, value)) = iter.next() {
127				if let Err(err) = T::Currency::transfer_on_hold(
128					&crate::HoldReason::CodeUploadDepositReserve.into(),
129					&value.owner,
130					&Pallet::<T>::account_id(),
131					value.deposit,
132					Precision::Exact,
133					Restriction::OnHold,
134					Fortitude::Polite,
135				) {
136					log::error!(
137						target: LOG_TARGET,
138						"Failed to unhold the deposit for code hash {last_key:?} and owner {:?}: {err:?}",
139						value.owner,
140					);
141				}
142
143				new::CodeInfoOf::<T>::insert(
144					last_key,
145					new::CodeInfo {
146						owner: value.owner,
147						deposit: value.deposit,
148						refcount: value.refcount,
149						code_len: value.code_len,
150						code_type: BytecodeType::Pvm,
151						behaviour_version: value.behaviour_version,
152					},
153				);
154				cursor = Some(last_key)
155			} else {
156				cursor = None;
157				break
158			}
159		}
160		Ok(cursor)
161	}
162
163	#[cfg(feature = "try-runtime")]
164	fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
165		use codec::Encode;
166
167		// Return the state of the storage before the migration.
168		Ok(old::CodeInfoOf::<T>::iter().collect::<BTreeMap<_, _>>().encode())
169	}
170
171	#[cfg(feature = "try-runtime")]
172	fn post_upgrade(prev: Vec<u8>) -> Result<(), TryRuntimeError> {
173		use codec::Decode;
174		use sp_runtime::{traits::Zero, Saturating};
175
176		// Check the state of the storage after the migration.
177		let prev_map = BTreeMap::<H256, old::CodeInfo<T>>::decode(&mut &prev[..])
178			.expect("Failed to decode the previous storage state");
179
180		// Check the len of prev and post are the same.
181		assert_eq!(
182			crate::CodeInfoOf::<T>::iter().count(),
183			prev_map.len(),
184			"Migration failed: the number of items in the storage after the migration is not the same as before"
185		);
186
187		let deposit_sum: crate::BalanceOf<T> = Zero::zero();
188
189		for (code_hash, old_code_info) in prev_map {
190			deposit_sum.saturating_add(old_code_info.deposit);
191			Self::assert_migrated_code_info(code_hash, &old_code_info);
192		}
193
194		assert_eq!(
195			<T as Config>::Currency::balance_on_hold(
196				&crate::HoldReason::CodeUploadDepositReserve.into(),
197				&Pallet::<T>::account_id(),
198			),
199			deposit_sum,
200		);
201
202		Ok(())
203	}
204}
205
206#[cfg(any(feature = "runtime-benchmarks", feature = "try-runtime", test))]
207impl<T: Config> Migration<T> {
208	/// Insert an old CodeInfo for benchmarking purposes.
209	pub fn insert_old_code_info(code_hash: H256, code_info: old::CodeInfo<T>) {
210		old::CodeInfoOf::<T>::insert(code_hash, code_info);
211	}
212
213	/// Create an old CodeInfo struct for benchmarking.
214	pub fn create_old_code_info(
215		owner: crate::AccountIdOf<T>,
216		deposit: crate::BalanceOf<T>,
217		refcount: u64,
218		code_len: u32,
219		behaviour_version: u32,
220	) -> old::CodeInfo<T> {
221		use frame_support::traits::fungible::Mutate;
222		T::Currency::mint_into(&owner, Pallet::<T>::min_balance() + deposit)
223			.expect("Failed to mint into owner account");
224		T::Currency::hold(&crate::HoldReason::CodeUploadDepositReserve.into(), &owner, deposit)
225			.expect("Failed to hold the deposit on the owner account");
226
227		old::CodeInfo { owner, deposit, refcount, code_len, behaviour_version }
228	}
229
230	/// Assert that the migrated CodeInfo matches the expected values from the old CodeInfo.
231	pub fn assert_migrated_code_info(code_hash: H256, old_code_info: &old::CodeInfo<T>) {
232		use frame_support::traits::fungible::InspectHold;
233		use sp_runtime::traits::Zero;
234		let migrated =
235			new::CodeInfoOf::<T>::get(code_hash).expect("Failed to get migrated CodeInfo");
236
237		assert!(<T as Config>::Currency::balance_on_hold(
238			&crate::HoldReason::CodeUploadDepositReserve.into(),
239			&old_code_info.owner
240		)
241		.is_zero());
242
243		assert_eq!(
244			migrated,
245			new::CodeInfo {
246				owner: old_code_info.owner.clone(),
247				deposit: old_code_info.deposit,
248				refcount: old_code_info.refcount,
249				code_len: old_code_info.code_len,
250				behaviour_version: old_code_info.behaviour_version,
251				code_type: BytecodeType::Pvm,
252			},
253			"Migration failed: deposit mismatch for key {code_hash:?}",
254		);
255	}
256}
257
258#[test]
259fn migrate_to_v2() {
260	use crate::{
261		tests::{ExtBuilder, Test},
262		AccountIdOf,
263	};
264	use alloc::collections::BTreeMap;
265
266	ExtBuilder::default().genesis_config(None).build().execute_with(|| {
267		// Store the original values to verify against later
268		let mut original_values = BTreeMap::new();
269
270		for i in 0..10u8 {
271			let code_hash = H256::from([i; 32]);
272			let old_info = Migration::<Test>::create_old_code_info(
273				AccountIdOf::<Test>::from([i; 32]),
274				(1000u32 + i as u32).into(),
275				1 + i as u64,
276				100 + i as u32,
277				i as u32,
278			);
279
280			Migration::<Test>::insert_old_code_info(code_hash, old_info.clone());
281			original_values.insert(code_hash, old_info);
282		}
283
284		let mut cursor = None;
285		let mut weight_meter = WeightMeter::new();
286		while let Some(new_cursor) = Migration::<Test>::step(cursor, &mut weight_meter).unwrap() {
287			cursor = Some(new_cursor);
288		}
289
290		assert_eq!(crate::CodeInfoOf::<Test>::iter().count(), 10);
291
292		// Verify all values match between old and new with code_type set to PVM
293		for (code_hash, old_value) in original_values {
294			Migration::<Test>::assert_migrated_code_info(code_hash, &old_value);
295		}
296	})
297}