--- a/pallets/foreign-assets/src/lib.rs +++ b/pallets/foreign-assets/src/lib.rs @@ -25,7 +25,9 @@ use core::ops::Deref; -use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId}; +use frame_support::{ + dispatch::DispatchResult, pallet_prelude::*, storage_alias, traits::EnsureOrigin, PalletId, +}; use frame_system::pallet_prelude::*; use pallet_common::{ dispatch::CollectionDispatch, erc::CrossAccountId, XcmExtensions, NATIVE_FUNGIBLE_COLLECTION_ID, @@ -50,6 +52,41 @@ pub use module::*; pub use weights::WeightInfo; +/// Status of storage migration from an old XCM version to a new one. +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum MigrationStatus { + V3ToV4(MigrationStatusV3ToV4), +} + +/// Status of storage migration from XCMv3 to XCMv4. +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum MigrationStatusV3ToV4 { + /// The migration is completed. + Done, + + /// An asset is skipped during the migration + /// due to its inconsistent state. + SkippedInconsistentAssetData(staging_xcm::v3::AssetId), + + /// An asset instance is skipped during the migration + /// due to its inconsistent state. + SkippedInconsistentAssetInstanceData { + asset_id: staging_xcm::v3::AssetId, + asset_instance: staging_xcm::v3::AssetInstance, + }, + + /// An asset is skipped during the migration + /// because it couldn't be converted to the new XCM version. + SkippedNotConvertibleAssetId(staging_xcm::v3::AssetId), + + /// An asset instance is skipped during the migration + /// because it couldn't be converted to the new XCM version. + SkippedNotConvertibleAssetInstance { + asset_id: staging_xcm::v3::AssetId, + asset_instance: staging_xcm::v3::AssetInstance, + }, +} + #[frame_support::pallet] pub mod module { use pallet_common::CollectionIssuer; @@ -93,35 +130,37 @@ } #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { /// The foreign asset registered. ForeignAssetRegistered { collection_id: CollectionId, asset_id: Box, }, + + /// The migration status. + MigrationStatus(MigrationStatus), } /// The corresponding collections of foreign assets. #[pallet::storage] #[pallet::getter(fn foreign_asset_to_collection)] pub type ForeignAssetToCollection = - StorageMap<_, Twox64Concat, staging_xcm::v4::AssetId, CollectionId, OptionQuery>; + StorageMap<_, Blake2_128Concat, staging_xcm::v4::AssetId, CollectionId, OptionQuery>; /// The corresponding foreign assets of collections. #[pallet::storage] #[pallet::getter(fn collection_to_foreign_asset)] pub type CollectionToForeignAsset = - StorageMap<_, Twox64Concat, CollectionId, staging_xcm::v4::AssetId, OptionQuery>; + StorageMap<_, Blake2_128Concat, CollectionId, staging_xcm::v4::AssetId, OptionQuery>; /// The correponding NFT token id of reserve NFTs #[pallet::storage] #[pallet::getter(fn foreign_reserve_asset_instance_to_token_id)] pub type ForeignReserveAssetInstanceToTokenId = StorageDoubleMap< - Hasher1 = Twox64Concat, + Hasher1 = Blake2_128Concat, Key1 = CollectionId, Hasher2 = Blake2_128Concat, - // Key2 = staging_xcm::v3::AssetInstance, Key2 = staging_xcm::v4::AssetInstance, Value = TokenId, QueryKind = OptionQuery, @@ -131,16 +170,18 @@ #[pallet::storage] #[pallet::getter(fn token_id_to_foreign_reserve_asset_instance)] pub type TokenIdToForeignReserveAssetInstance = StorageDoubleMap< - Hasher1 = Twox64Concat, + Hasher1 = Blake2_128Concat, Key1 = CollectionId, Hasher2 = Blake2_128Concat, Key2 = TokenId, - // Value = staging_xcm::v3::AssetInstance, Value = staging_xcm::v4::AssetInstance, QueryKind = OptionQuery, >; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(staging_xcm::v4::VERSION as u16); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::call] @@ -198,9 +239,186 @@ Ok(()) } } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + Self::migrate_v3_to_v4() + } + } +} + +mod v3_storage { + use super::*; + + #[storage_alias] + pub type ForeignAssetToCollection = + StorageMap, Twox64Concat, staging_xcm::v3::AssetId, CollectionId, OptionQuery>; + + #[storage_alias] + pub type CollectionToForeignAsset = + StorageMap, Twox64Concat, CollectionId, staging_xcm::v3::AssetId, OptionQuery>; + + #[storage_alias] + pub type ForeignReserveAssetInstanceToTokenId = StorageDoubleMap< + Pallet, + Twox64Concat, + CollectionId, + Blake2_128Concat, + staging_xcm::v3::AssetInstance, + TokenId, + OptionQuery, + >; + + #[storage_alias] + pub type TokenIdToForeignReserveAssetInstance = StorageDoubleMap< + Pallet, + Twox64Concat, + CollectionId, + Blake2_128Concat, + TokenId, + staging_xcm::v3::AssetInstance, + OptionQuery, + >; } impl Pallet { + fn migrate_v3_to_v4() -> Weight { + if Self::on_chain_storage_version() < staging_xcm::v4::VERSION as u16 { + let put_version_weight = T::DbWeight::get().writes(1); + let event_weight = T::DbWeight::get().writes(1); + let collection_migration_weight = Self::migrate_collections(); + + StorageVersion::new(staging_xcm::v4::VERSION as u16).put::(); + + Self::deposit_event(Event::::MigrationStatus(MigrationStatus::V3ToV4( + MigrationStatusV3ToV4::Done, + ))); + + put_version_weight + .saturating_add(collection_migration_weight) + .saturating_add(event_weight) + } else { + Weight::zero() + } + } + + fn migrate_collections() -> Weight { + use MigrationStatus::*; + use MigrationStatusV3ToV4::*; + + let mut weight = Weight::zero(); + + for (fwd_asset_id, collection_id) in v3_storage::ForeignAssetToCollection::::drain() { + let bwd_asset_id = v3_storage::CollectionToForeignAsset::::take(collection_id); + weight = weight.saturating_add(T::DbWeight::get().reads(2)); + + let Some(bwd_asset_id) = bwd_asset_id else { + Self::deposit_event(Event::::MigrationStatus(V3ToV4( + SkippedInconsistentAssetData(fwd_asset_id), + ))); + + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + continue; + }; + + if fwd_asset_id != bwd_asset_id { + Self::deposit_event(Event::::MigrationStatus(V3ToV4( + SkippedInconsistentAssetData(fwd_asset_id), + ))); + + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + continue; + } + + let Ok(asset_id) = staging_xcm::v4::AssetId::try_from(fwd_asset_id) else { + Self::deposit_event(Event::::MigrationStatus(V3ToV4( + SkippedNotConvertibleAssetId(fwd_asset_id), + ))); + + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + continue; + }; + + >::insert(&asset_id, collection_id); + >::insert(collection_id, asset_id); + weight = weight.saturating_add(T::DbWeight::get().writes(2)); + + let migrate_tokens_weight = Self::migrate_tokens(&fwd_asset_id, collection_id); + weight = weight.saturating_add(migrate_tokens_weight); + } + + weight + } + + fn migrate_tokens(asset_id: &staging_xcm::v3::AssetId, collection_id: CollectionId) -> Weight { + use MigrationStatus::*; + use MigrationStatusV3ToV4::*; + + let mut weight = Weight::zero(); + + for (fwd_asset_instance, token_id) in + v3_storage::ForeignReserveAssetInstanceToTokenId::::drain_prefix(collection_id) + { + let bwd_asset_instance = v3_storage::TokenIdToForeignReserveAssetInstance::::take( + collection_id, + token_id, + ); + weight = weight.saturating_add(T::DbWeight::get().reads(2)); + + let Some(bwd_asset_instance) = bwd_asset_instance else { + Self::deposit_event(Event::::MigrationStatus(V3ToV4( + SkippedInconsistentAssetInstanceData { + asset_id: *asset_id, + asset_instance: fwd_asset_instance, + }, + ))); + + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + continue; + }; + + if fwd_asset_instance != bwd_asset_instance { + Self::deposit_event(Event::::MigrationStatus(V3ToV4( + SkippedInconsistentAssetInstanceData { + asset_id: *asset_id, + asset_instance: fwd_asset_instance, + }, + ))); + + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + continue; + } + + let Ok(asset_instance) = staging_xcm::v4::AssetInstance::try_from(fwd_asset_instance) + else { + Self::deposit_event(Event::::MigrationStatus(V3ToV4( + SkippedNotConvertibleAssetInstance { + asset_id: *asset_id, + asset_instance: fwd_asset_instance, + }, + ))); + + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + continue; + }; + + >::insert( + collection_id, + &asset_instance, + token_id, + ); + >::insert( + collection_id, + token_id, + asset_instance, + ); + weight = weight.saturating_add(T::DbWeight::get().writes(2)); + } + + weight + } + fn pallet_account() -> T::CrossAccountId { let owner: T::AccountId = T::PalletId::get().into_account_truncating(); T::CrossAccountId::from_sub(owner)