referrerpolicy=no-referrer-when-downgrade

pallet_revive/migrations/
v3.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 v3
19//!
20//! Iterates `frame_system::Account` and for each account:
21//! - If unmapped: calls `map_no_deposit_unchecked` to create a deposit-free mapping.
22//! - If already mapped: releases the held address mapping deposit.
23
24use super::PALLET_MIGRATIONS_ID;
25use crate::{AddressMapper, Config, HoldReason, LOG_TARGET, weights::WeightInfo};
26use frame_support::{
27	migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
28	pallet_prelude::PhantomData,
29	traits::{fungible::MutateHold, tokens::Precision},
30	weights::WeightMeter,
31};
32
33#[cfg(feature = "try-runtime")]
34extern crate alloc;
35
36#[cfg(feature = "try-runtime")]
37use alloc::vec::Vec;
38
39/// Maps all existing accounts that are not yet address-mapped.
40pub struct Migration<T: Config>(PhantomData<T>);
41
42impl<T: Config> SteppedMigration for Migration<T> {
43	type Cursor = T::AccountId;
44	type Identifier = MigrationId<17>;
45
46	fn id() -> Self::Identifier {
47		MigrationId { pallet_id: *PALLET_MIGRATIONS_ID, version_from: 2, version_to: 3 }
48	}
49
50	fn step(
51		mut cursor: Option<Self::Cursor>,
52		meter: &mut WeightMeter,
53	) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
54		let required = <T as Config>::WeightInfo::v3_migration_step();
55		if meter.remaining().any_lt(required) {
56			return Err(SteppedMigrationError::InsufficientWeight { required });
57		}
58
59		loop {
60			if meter.try_consume(required).is_err() {
61				break;
62			}
63
64			let mut iter = if let Some(ref last_key) = cursor {
65				frame_system::Account::<T>::iter_from(frame_system::Account::<T>::hashed_key_for(
66					last_key,
67				))
68			} else {
69				frame_system::Account::<T>::iter()
70			};
71
72			if let Some((account_id, _)) = iter.next() {
73				if T::AddressMapper::is_eth_derived(&account_id) {
74					// Eth-derived accounts are stateless mapped, nothing to do.
75				} else {
76					let _ = T::AddressMapper::map_no_deposit_unchecked(&account_id).inspect_err(
77						|err| {
78							log::debug!(
79								target: LOG_TARGET,
80								"Failed to map account {account_id:?}: {err:?}",
81							);
82						},
83					);
84
85					let _ = T::Currency::release_all(
86						&HoldReason::AddressMapping.into(),
87						&account_id,
88						Precision::BestEffort,
89					)
90					.inspect_err(|err| {
91						log::debug!(
92							target: LOG_TARGET,
93							"Failed to release mapping deposit for {account_id:?}: {err:?}",
94						);
95					});
96				}
97				cursor = Some(account_id);
98			} else {
99				cursor = None;
100				break;
101			}
102		}
103		Ok(cursor)
104	}
105
106	#[cfg(feature = "try-runtime")]
107	fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
108		use sp_core::Get;
109		assert!(T::AutoMap::get(), "v3 migration requires AutoMap to be enabled");
110
111		use codec::Encode;
112		let unmapped: u32 = frame_system::Account::<T>::iter_keys()
113			.filter(|id| !T::AddressMapper::is_mapped(id))
114			.count() as u32;
115		log::info!(target: LOG_TARGET, "v3: {unmapped} accounts to map");
116		Ok(unmapped.encode())
117	}
118
119	#[cfg(feature = "try-runtime")]
120	fn post_upgrade(prev: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
121		use codec::Decode;
122		use frame_support::traits::fungible::InspectHold;
123		use sp_runtime::traits::Zero;
124
125		let prev_unmapped =
126			u32::decode(&mut &prev[..]).expect("Failed to decode pre_upgrade state");
127		let still_unmapped: u32 = frame_system::Account::<T>::iter_keys()
128			.filter(|id| !T::AddressMapper::is_mapped(id))
129			.count() as u32;
130		assert_eq!(
131			still_unmapped, 0,
132			"v3: {still_unmapped} accounts still unmapped (was {prev_unmapped})",
133		);
134
135		// Verify no accounts have address mapping deposits held
136		for (account_id, _) in frame_system::Account::<T>::iter() {
137			assert!(
138				T::Currency::balance_on_hold(
139					&crate::HoldReason::AddressMapping.into(),
140					&account_id,
141				)
142				.is_zero(),
143				"v3: account {account_id:?} still has address mapping deposit held",
144			);
145		}
146
147		Ok(())
148	}
149}
150
151#[test]
152fn migrate_to_v3() {
153	use crate::{
154		Config, OriginalAccount,
155		tests::{ExtBuilder, Test},
156	};
157	use frame_support::{traits::fungible::Mutate, weights::WeightMeter};
158	use sp_core::H160;
159	use sp_runtime::AccountId32;
160
161	ExtBuilder::default().genesis_config(None).build().execute_with(|| {
162		use crate::address::AccountId32Mapper;
163		use frame_support::traits::fungible::InspectHold;
164
165		let unmapped_accounts: Vec<AccountId32> =
166			(10..15u8).map(|i| AccountId32::new([i; 32])).collect();
167		let mapped_accounts: Vec<AccountId32> =
168			(15..20u8).map(|i| AccountId32::new([i; 32])).collect();
169		let eth_account = {
170			let mut bytes = [0xEE; 32];
171			bytes[..20].copy_from_slice(&[0xAA; 20]);
172			AccountId32::new(bytes)
173		};
174
175		// Fund all accounts
176		for acc in unmapped_accounts.iter().chain(&mapped_accounts) {
177			<Test as Config>::Currency::set_balance(acc, 1_000_000);
178		}
179		<Test as Config>::Currency::set_balance(&eth_account, 1_000_000);
180
181		// Map some accounts with a deposit (simulating pre-migration state)
182		for acc in &mapped_accounts {
183			AccountId32Mapper::<Test>::map(acc).unwrap();
184			assert!(
185				<Test as Config>::Currency::balance_on_hold(
186					&crate::HoldReason::AddressMapping.into(),
187					acc
188				) > 0,
189			);
190		}
191
192		// Run migration to completion
193		let mut cursor = None;
194		let mut weight_meter = WeightMeter::new();
195		while let Some(new_cursor) = Migration::<Test>::step(cursor, &mut weight_meter).unwrap() {
196			cursor = Some(new_cursor);
197		}
198
199		// Verify all non-eth accounts are mapped
200		for acc in unmapped_accounts.iter().chain(&mapped_accounts) {
201			assert!(AccountId32Mapper::<Test>::is_mapped(acc));
202			let addr = AccountId32Mapper::<Test>::to_address(acc);
203			assert_eq!(OriginalAccount::<Test>::get(addr).as_ref(), Some(acc));
204		}
205
206		// Verify deposits were released for previously-mapped accounts
207		for acc in &mapped_accounts {
208			assert_eq!(
209				<Test as Config>::Currency::balance_on_hold(
210					&crate::HoldReason::AddressMapping.into(),
211					acc
212				),
213				0,
214			);
215		}
216
217		// Eth-derived accounts should not have entries in OriginalAccount
218		let eth_addr = H160::from_slice(&[0xAA; 20]);
219		assert!(OriginalAccount::<Test>::get(eth_addr).is_none());
220	});
221}
222
223#[test]
224fn migrate_to_v3_maps_all_accounts() {
225	use crate::{
226		Config,
227		address::AccountId32Mapper,
228		tests::{ExtBuilder, Test},
229	};
230	use frame_support::{traits::fungible::Mutate, weights::WeightMeter};
231	use sp_runtime::AccountId32;
232
233	ExtBuilder::default().genesis_config(None).build().execute_with(|| {
234		let accounts: Vec<AccountId32> = (10..15u8).map(|i| AccountId32::new([i; 32])).collect();
235		for acc in &accounts {
236			<Test as Config>::Currency::set_balance(acc, 1_000_000);
237			AccountId32Mapper::<Test>::map(acc).unwrap();
238		}
239
240		// Clear all mappings to simulate pre-migration state
241		for acc in &accounts {
242			AccountId32Mapper::<Test>::unmap(acc).unwrap();
243			assert!(!AccountId32Mapper::<Test>::is_mapped(acc));
244		}
245
246		// Run migration to completion
247		let mut cursor = None;
248		let mut meter = WeightMeter::new();
249		while let Some(new_cursor) = Migration::<Test>::step(cursor, &mut meter).unwrap() {
250			cursor = Some(new_cursor);
251		}
252
253		for acc in &accounts {
254			assert!(
255				AccountId32Mapper::<Test>::is_mapped(acc),
256				"account should be mapped after migration"
257			);
258		}
259	});
260}