--- a/Cargo.lock +++ b/Cargo.lock @@ -7279,6 +7279,7 @@ name = "pallet-foreign-assets" version = "0.1.0" dependencies = [ + "derivative", "frame-benchmarking", "frame-support", "frame-system", --- a/pallets/foreign-assets/Cargo.toml +++ b/pallets/foreign-assets/Cargo.toml @@ -21,6 +21,7 @@ staging-xcm = { workspace = true } staging-xcm-executor = { workspace = true } up-data-structs = { workspace = true } +derivative = { workspace = true } [features] default = ["std"] --- a/pallets/foreign-assets/src/lib.rs +++ b/pallets/foreign-assets/src/lib.rs @@ -25,7 +25,10 @@ use core::ops::Deref; -use frame_support::{dispatch::DispatchResult, pallet_prelude::*, traits::EnsureOrigin, PalletId}; +use derivative::Derivative; +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, @@ -38,8 +41,8 @@ AssetsInHolding, }; use up_data_structs::{ - budget::ZeroBudget, CollectionId, CollectionMode, CollectionName, CollectionTokenPrefix, - CreateCollectionData, CreateFungibleData, CreateItemData, TokenId, + budget::ZeroBudget, CollectionFlags, CollectionId, CollectionMode, CollectionName, + CollectionTokenPrefix, CreateCollectionData, CreateFungibleData, CreateItemData, TokenId, }; pub mod weights; @@ -50,8 +53,44 @@ 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 frame_support::traits::BuildGenesisConfig; use pallet_common::CollectionIssuer; use up_data_structs::CollectionDescription; @@ -93,35 +132,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 +172,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] @@ -183,6 +226,10 @@ token_prefix, description, mode: mode.into(), + flags: CollectionFlags { + foreign: true, + ..Default::default() + }, ..Default::default() }, )?; @@ -198,9 +245,217 @@ Ok(()) } } + + #[pallet::genesis_config] + #[derive(Derivative)] + #[derivative(Default(bound = ""))] + pub struct GenesisConfig(PhantomData); + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + >::in_code_storage_version().put::>(); + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + if Self::on_chain_storage_version() < staging_xcm::v4::VERSION as u16 { + let put_version_weight = T::DbWeight::get().writes(1); + let fix_foreign_flag_weight = Self::fix_foreign_flag(); + let weight_v3_to_v4 = Self::migrate_v3_to_v4(); + + Self::in_code_storage_version().put::(); + + put_version_weight + .saturating_add(fix_foreign_flag_weight) + .saturating_add(weight_v3_to_v4) + } else { + Weight::zero() + } + } + } } +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 fix_foreign_flag() -> Weight { + let mut weight = Weight::zero(); + + for (_, collection_id) in v3_storage::ForeignAssetToCollection::::iter() { + pallet_common::CollectionById::::mutate(collection_id, |collection| { + if let Some(collection) = collection { + collection.flags.foreign = true; + } + }); + + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); + } + + weight + } + + fn migrate_v3_to_v4() -> Weight { + let event_weight = T::DbWeight::get().writes(1); + let collection_migration_weight = Self::migrate_collections(); + + Self::deposit_event(Event::::MigrationStatus(MigrationStatus::V3ToV4( + MigrationStatusV3ToV4::Done, + ))); + + collection_migration_weight.saturating_add(event_weight) + } + + 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)