use super::PALLET_MIGRATIONS_ID;
use crate::{AddressMapper, Config, HoldReason, LOG_TARGET, weights::WeightInfo};
use frame_support::{
migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
pallet_prelude::PhantomData,
traits::{fungible::MutateHold, tokens::Precision},
weights::WeightMeter,
};
#[cfg(feature = "try-runtime")]
extern crate alloc;
#[cfg(feature = "try-runtime")]
use alloc::vec::Vec;
pub struct Migration<T: Config>(PhantomData<T>);
impl<T: Config> SteppedMigration for Migration<T> {
type Cursor = T::AccountId;
type Identifier = MigrationId<17>;
fn id() -> Self::Identifier {
MigrationId { pallet_id: *PALLET_MIGRATIONS_ID, version_from: 2, version_to: 3 }
}
fn step(
mut cursor: Option<Self::Cursor>,
meter: &mut WeightMeter,
) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
let required = <T as Config>::WeightInfo::v3_migration_step();
if meter.remaining().any_lt(required) {
return Err(SteppedMigrationError::InsufficientWeight { required });
}
loop {
if meter.try_consume(required).is_err() {
break;
}
let mut iter = if let Some(ref last_key) = cursor {
frame_system::Account::<T>::iter_from(frame_system::Account::<T>::hashed_key_for(
last_key,
))
} else {
frame_system::Account::<T>::iter()
};
if let Some((account_id, _)) = iter.next() {
if T::AddressMapper::is_eth_derived(&account_id) {
} else {
let _ = T::AddressMapper::map_no_deposit(&account_id).inspect_err(|err| {
log::debug!(
target: LOG_TARGET,
"Failed to map account {account_id:?}: {err:?}",
);
});
let _ = T::Currency::release_all(
&HoldReason::AddressMapping.into(),
&account_id,
Precision::BestEffort,
)
.inspect_err(|err| {
log::debug!(
target: LOG_TARGET,
"Failed to release mapping deposit for {account_id:?}: {err:?}",
);
});
}
cursor = Some(account_id);
} else {
cursor = None;
break;
}
}
Ok(cursor)
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
use sp_core::Get;
assert!(T::AutoMap::get(), "v3 migration requires AutoMap to be enabled");
use codec::Encode;
let unmapped: u32 = frame_system::Account::<T>::iter_keys()
.filter(|id| !T::AddressMapper::is_mapped(id))
.count() as u32;
log::info!(target: LOG_TARGET, "v3: {unmapped} accounts to map");
Ok(unmapped.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(prev: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
use codec::Decode;
use frame_support::traits::fungible::InspectHold;
use sp_runtime::traits::Zero;
let prev_unmapped =
u32::decode(&mut &prev[..]).expect("Failed to decode pre_upgrade state");
let still_unmapped: u32 = frame_system::Account::<T>::iter_keys()
.filter(|id| !T::AddressMapper::is_mapped(id))
.count() as u32;
assert_eq!(
still_unmapped, 0,
"v3: {still_unmapped} accounts still unmapped (was {prev_unmapped})",
);
for (account_id, _) in frame_system::Account::<T>::iter() {
assert!(
T::Currency::balance_on_hold(
&crate::HoldReason::AddressMapping.into(),
&account_id,
)
.is_zero(),
"v3: account {account_id:?} still has address mapping deposit held",
);
}
Ok(())
}
}
#[test]
fn migrate_to_v3() {
use crate::{
Config, OriginalAccount,
tests::{ExtBuilder, Test},
};
use frame_support::{traits::fungible::Mutate, weights::WeightMeter};
use sp_core::H160;
use sp_runtime::AccountId32;
ExtBuilder::default().genesis_config(None).build().execute_with(|| {
use crate::address::AccountId32Mapper;
use frame_support::traits::fungible::InspectHold;
let unmapped_accounts: Vec<AccountId32> =
(10..15u8).map(|i| AccountId32::new([i; 32])).collect();
let mapped_accounts: Vec<AccountId32> =
(15..20u8).map(|i| AccountId32::new([i; 32])).collect();
let eth_account = {
let mut bytes = [0xEE; 32];
bytes[..20].copy_from_slice(&[0xAA; 20]);
AccountId32::new(bytes)
};
for acc in unmapped_accounts.iter().chain(&mapped_accounts) {
<Test as Config>::Currency::set_balance(acc, 1_000_000);
}
<Test as Config>::Currency::set_balance(ð_account, 1_000_000);
for acc in &mapped_accounts {
AccountId32Mapper::<Test>::map(acc).unwrap();
assert!(
<Test as Config>::Currency::balance_on_hold(
&crate::HoldReason::AddressMapping.into(),
acc
) > 0,
);
}
let mut cursor = None;
let mut weight_meter = WeightMeter::new();
while let Some(new_cursor) = Migration::<Test>::step(cursor, &mut weight_meter).unwrap() {
cursor = Some(new_cursor);
}
for acc in unmapped_accounts.iter().chain(&mapped_accounts) {
assert!(AccountId32Mapper::<Test>::is_mapped(acc));
let addr = AccountId32Mapper::<Test>::to_address(acc);
assert_eq!(OriginalAccount::<Test>::get(addr).as_ref(), Some(acc));
}
for acc in &mapped_accounts {
assert_eq!(
<Test as Config>::Currency::balance_on_hold(
&crate::HoldReason::AddressMapping.into(),
acc
),
0,
);
}
let eth_addr = H160::from_slice(&[0xAA; 20]);
assert!(OriginalAccount::<Test>::get(eth_addr).is_none());
});
}
#[test]
fn migrate_to_v3_maps_all_accounts() {
use crate::{
Config,
address::AccountId32Mapper,
tests::{ExtBuilder, Test},
};
use frame_support::{traits::fungible::Mutate, weights::WeightMeter};
use sp_runtime::AccountId32;
ExtBuilder::default().genesis_config(None).build().execute_with(|| {
let accounts: Vec<AccountId32> = (10..15u8).map(|i| AccountId32::new([i; 32])).collect();
for acc in &accounts {
<Test as Config>::Currency::set_balance(acc, 1_000_000);
AccountId32Mapper::<Test>::map(acc).unwrap();
}
for acc in &accounts {
AccountId32Mapper::<Test>::unmap(acc).unwrap();
assert!(!AccountId32Mapper::<Test>::is_mapped(acc));
}
let mut cursor = None;
let mut meter = WeightMeter::new();
while let Some(new_cursor) = Migration::<Test>::step(cursor, &mut meter).unwrap() {
cursor = Some(new_cursor);
}
for acc in &accounts {
assert!(
AccountId32Mapper::<Test>::is_mapped(acc),
"account should be mapped after migration"
);
}
});
}