diff --git a/Cargo.lock b/Cargo.lock index c93e8a84c..42952105c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8811,6 +8811,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "hex-literal", + "itertools 0.11.0", "lazy_static", "log", "orml-oracle", diff --git a/pallets/funding/src/mock.rs b/pallets/funding/src/mock.rs index 05b9c8098..72d9a0054 100644 --- a/pallets/funding/src/mock.rs +++ b/pallets/funding/src/mock.rs @@ -210,7 +210,9 @@ impl pallet_assets::Config for TestRuntime { type WeightInfo = (); } +#[cfg(feature = "runtime-benchmarks")] pub struct PalletAssetsBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] impl pallet_assets::BenchmarkHelper for PalletAssetsBenchmarkHelper { fn create_asset_id_parameter(id: u32) -> Location { (Parent, Parachain(id)).into() diff --git a/pallets/funding/src/runtime_api.rs b/pallets/funding/src/runtime_api.rs index 9bb64157f..1c7a12433 100644 --- a/pallets/funding/src/runtime_api.rs +++ b/pallets/funding/src/runtime_api.rs @@ -1,13 +1,11 @@ use crate::traits::BondingRequirementCalculation; #[allow(clippy::wildcard_imports)] - use crate::{traits::BondingRequirementCalculation, *}; use alloc::{collections::BTreeMap, string::String}; use frame_support::traits::fungibles::{Inspect, InspectEnumerable}; use itertools::Itertools; use parity_scale_codec::{Decode, Encode}; -use polimec_common::assets::AcceptedFundingAsset; -use polimec_common::{credentials::InvestorType, ProvideAssetPrice, USD_DECIMALS}; +use polimec_common::{assets::AcceptedFundingAsset, credentials::InvestorType, ProvideAssetPrice, USD_DECIMALS}; use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::traits::Zero; diff --git a/pallets/funding/src/storage_migrations.rs b/pallets/funding/src/storage_migrations.rs index 841f3f676..7690415cc 100644 --- a/pallets/funding/src/storage_migrations.rs +++ b/pallets/funding/src/storage_migrations.rs @@ -1,5 +1,153 @@ //! A module that is responsible for migration of storage. -use frame_support::traits::StorageVersion; +use crate::{ + AccountIdOf, BiddingTicketSizes, Config, ContributingTicketSizes, CurrencyMetadata, FixedPointNumber, + ParticipantsAccountType, PriceOf, ProjectMetadataOf, StringLimitOf, +}; +use core::marker::PhantomData; +use frame_support::traits::{StorageVersion, UncheckedOnRuntimeUpgrade}; +use polimec_common::{assets::AcceptedFundingAsset, credentials::Cid}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_core::{ConstU32, Decode, Encode, Get, MaxEncodedLen, RuntimeDebug}; +use sp_runtime::{BoundedVec, Percent}; +extern crate alloc; +use alloc::vec::Vec; +use polimec_common::migration_types::{MigrationInfo, ParticipationType}; +use xcm::v4::Location; + /// The current storage version -pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); pub const LOG: &str = "runtime::funding::migration"; + +pub mod v5_storage_items { + + use super::*; + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo, Serialize, Deserialize)] + pub struct OldProjectMetadata { + /// Token Metadata + pub token_information: CurrencyMetadata, + /// Mainnet Token Max Supply + pub mainnet_token_max_supply: Balance, + /// Total allocation of Contribution Tokens available for the Funding Round. + pub total_allocation_size: Balance, + /// Percentage of the total allocation of Contribution Tokens available for the Auction Round + pub auction_round_allocation_percentage: Percent, + /// The minimum price per token in USD, decimal-aware. See [`calculate_decimals_aware_price()`](crate::traits::ProvideAssetPrice::calculate_decimals_aware_price) for more information. + pub minimum_price: Price, + /// Maximum and minimum ticket sizes for auction round + pub bidding_ticket_sizes: BiddingTicketSizes, + /// Maximum and minimum ticket sizes for community/remainder rounds + pub contributing_ticket_sizes: ContributingTicketSizes, + /// Participation currencies (e.g stablecoin, DOT, KSM) + /// e.g. https://github.com/paritytech/substrate/blob/427fd09bcb193c1e79dec85b1e207c718b686c35/frame/uniques/src/types.rs#L110 + /// For now is easier to handle the case where only just one Currency is accepted + pub participation_currencies: + BoundedVec>, + pub funding_destination_account: AccountId, + /// Additional metadata + pub policy_ipfs_cid: Option, + } + + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub struct OldMigrationOrigin { + pub user: Location, + pub id: u32, + pub participation_type: ParticipationType, + } + impl PartialOrd for OldMigrationOrigin { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + impl Ord for OldMigrationOrigin { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + if self.participation_type == other.participation_type { + self.id.cmp(&other.id) + } else { + self.participation_type.cmp(&other.participation_type) + } + } + } + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub struct OldMigration { + pub origin: OldMigrationOrigin, + pub info: MigrationInfo, + } +} + +pub mod v6 { + use super::*; + use crate::{storage_migrations::v5_storage_items::OldMigration, MaxParticipationsPerUser}; + use polimec_common::migration_types::{Migration, MigrationOrigin, MigrationStatus}; + + type OldProjectMetadataOf = super::v5_storage_items::OldProjectMetadata< + BoundedVec>, + ::Balance, + PriceOf, + AccountIdOf, + Cid, + >; + + pub struct UncheckedMigrationToV6(PhantomData); + impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV6 { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let mut items = 0; + log::info!("Starting migration to V5"); + let translate_project_details = |_key, item: OldProjectMetadataOf| -> Option> { + items += 1; + log::info!("project_details item {:?}", items); + + // let old_participation_currencies = item.participation_currencies.to_vec(); + // let new_participation_currencies: BoundedVec> = old_participation_currencies.try_into().ok()?; + + Some(ProjectMetadataOf:: { + token_information: item.token_information, + mainnet_token_max_supply: item.mainnet_token_max_supply, + total_allocation_size: item.total_allocation_size, + auction_round_allocation_percentage: item.auction_round_allocation_percentage, + minimum_price: item.minimum_price, + bidding_ticket_sizes: item.bidding_ticket_sizes, + contributing_ticket_sizes: item.contributing_ticket_sizes, + participation_currencies: item.participation_currencies, + funding_destination_account: item.funding_destination_account, + policy_ipfs_cid: item.policy_ipfs_cid, + participants_account_type: ParticipantsAccountType::Polkadot, + }) + }; + crate::ProjectsMetadata::::translate(translate_project_details); + + let translate_migration = + |_keys, + (status, migrations): (MigrationStatus, BoundedVec>)| + -> Option<(MigrationStatus, BoundedVec>)> { + let old_migrations = migrations.to_vec(); + let mut new_migrations = Vec::new(); + + for mut old_migration in old_migrations { + items += 1; + log::info!("migration items {:?}", items); + let origin_junction = old_migration.origin.user.interior.take_first().unwrap(); + let new_origin = MigrationOrigin { + user: origin_junction, + id: old_migration.origin.id, + participation_type: old_migration.origin.participation_type, + }; + new_migrations.push(Migration { origin: new_origin, info: old_migration.info }); + } + let new_migrations = new_migrations.try_into().ok()?; + Some((status, new_migrations)) + }; + crate::UserMigrations::::translate(translate_migration); + + T::DbWeight::get().reads_writes(items, items) + } + } + + pub type MigrationToV6 = frame_support::migrations::VersionedMigration< + 5, + 6, + UncheckedMigrationToV6, + crate::Pallet, + ::DbWeight, + >; +} diff --git a/pallets/proxy-bonding/src/mock.rs b/pallets/proxy-bonding/src/mock.rs index 8e74c70f6..f858c6af7 100644 --- a/pallets/proxy-bonding/src/mock.rs +++ b/pallets/proxy-bonding/src/mock.rs @@ -78,7 +78,9 @@ impl pallet_balances::Config for TestRuntime { type RuntimeHoldReason = MockRuntimeHoldReason; } +#[cfg(feature = "runtime-benchmarks")] pub struct PalletAssetsBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] impl pallet_assets::BenchmarkHelper for PalletAssetsBenchmarkHelper { fn create_asset_id_parameter(id: u32) -> Location { (Parent, Parachain(id)).into() @@ -89,6 +91,7 @@ impl pallet_assets::Config for TestRuntime { type AssetId = Location; type AssetIdParameter = Location; type Balance = ::Balance; + #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper = PalletAssetsBenchmarkHelper; type CreateOrigin = AsEnsureOriginWithArg>; type Currency = Balances; diff --git a/runtimes/polimec/Cargo.toml b/runtimes/polimec/Cargo.toml index 7b431f7f1..6f4e21212 100644 --- a/runtimes/polimec/Cargo.toml +++ b/runtimes/polimec/Cargo.toml @@ -112,6 +112,7 @@ parachains-common.workspace = true # ORML orml-oracle.workspace = true +itertools.workspace = true [features] default = [ "std" ] fast-mode = [ "shared-configuration/fast-mode" ] diff --git a/runtimes/polimec/src/custom_migrations/asset_id_migration.rs b/runtimes/polimec/src/custom_migrations/asset_id_migration.rs new file mode 100644 index 000000000..9dab0c9c6 --- /dev/null +++ b/runtimes/polimec/src/custom_migrations/asset_id_migration.rs @@ -0,0 +1,208 @@ +use crate::{AccountId, Funding, Oracle, Runtime}; +use alloc::{collections::BTreeMap, vec::Vec}; +use frame_support::{ + migrations::VersionedPostUpgradeData, + pallet_prelude::{NMapKey, StorageDoubleMap, StorageMap, StorageNMap, StorageValue, ValueQuery}, + storage_alias, + traits::{GetStorageVersion, OnRuntimeUpgrade}, + Blake2_128Concat, +}; +use itertools::Itertools; +use pallet_assets::{Approval, AssetAccount, AssetDetails, AssetMetadata}; +use parity_scale_codec::Encode; +use polimec_common::assets::AcceptedFundingAsset; +use sp_api::runtime_decl_for_core::CoreV5; +use sp_runtime::BoundedVec; +use xcm::v4::Location; + +// Storage items of pallet-assets are set to private for some reason. So we have to redefine them to get the same storage +// encoding and call the `translate` methods. -_-' +pub mod pallet_assets_storage_items { + use super::*; + + type Balance = u128; + + pub type AssetAccountOf = AssetAccount; + + pub type AssetDetailsOf = AssetDetails; + + pub type AssetMetadataOf = AssetMetadata>; + + pub mod old_types { + use super::*; + + type OldAssetId = u32; + + #[storage_alias] + pub type Account = + StorageDoubleMap; + + #[storage_alias] + pub type Asset = StorageMap; + + #[storage_alias] + pub type Approvals = StorageNMap< + ForeignAssets, + ( + NMapKey, + NMapKey, + NMapKey, + ), + Approval, + >; + + #[storage_alias] + pub type Metadata = StorageMap; + } + + pub mod new_types { + use super::*; + + type NewAssetId = Location; + + #[storage_alias] + pub type Account = + StorageDoubleMap; + + #[storage_alias] + pub type Asset = StorageMap; + + #[storage_alias] + pub type Approvals = StorageNMap< + ForeignAssets, + ( + NMapKey, + NMapKey, + NMapKey, + ), + Approval, + >; + + #[storage_alias] + pub type Metadata = StorageMap; + } +} + +pub mod orml_oracle_storage_items { + use super::*; + + pub mod old_types { + use frame_support::Twox64Concat; + use orml_oracle::TimestampedValue; + use shared_configuration::Price; + use super::*; + + type TimeStampedValueOf = TimestampedValue; + + #[storage_alias] + pub type RawValues = + StorageDoubleMap; + + #[storage_alias] + pub type Values = + StorageMap; + } +} + +// This migration should be run right before the pallet_funding migration from v5 -> v6. +pub struct FromOldAssetIdMigration; +impl OnRuntimeUpgrade for FromOldAssetIdMigration { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let funding_on_chain_version = Funding::on_chain_storage_version(); + if funding_on_chain_version == 5 { + Ok(VersionedPostUpgradeData::MigrationExecuted(Vec::new()).encode()) + } else { + Ok(VersionedPostUpgradeData::Noop.encode()) + } + } + + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let version = Funding::on_chain_storage_version(); + log::info!("funding version: {:?}", version); + if version != 5 { + log::info!("funding version is not 5"); + return frame_support::weights::Weight::zero(); + } + let runtime_version = Runtime::version(); + let mut items = 0; + if runtime_version.spec_version == 1_000_000 { + let id_map = BTreeMap::from([ + (1984, AcceptedFundingAsset::USDT.id()), + (1337, AcceptedFundingAsset::USDC.id()), + (10, AcceptedFundingAsset::DOT.id()), + (3344, Location::here()) + ]); + + let old_account_iterator = pallet_assets_storage_items::old_types::Account::iter().collect_vec(); + for (old_asset_id, account, account_info) in old_account_iterator { + items += 1; + log::info!("old_account item {:?}", items); + pallet_assets_storage_items::new_types::Account::insert( + id_map.get(&old_asset_id).unwrap(), + account.clone(), + account_info, + ); + pallet_assets_storage_items::old_types::Account::remove(old_asset_id, account); + } + + let old_asset_iterator = pallet_assets_storage_items::old_types::Asset::iter().collect_vec(); + for (old_asset_id, asset_info) in old_asset_iterator { + items += 1; + log::info!("old_asset item {:?}", items); + pallet_assets_storage_items::new_types::Asset::insert(id_map.get(&old_asset_id).unwrap(), asset_info); + pallet_assets_storage_items::old_types::Asset::remove(old_asset_id); + } + + let old_approvals_iterator = pallet_assets_storage_items::old_types::Approvals::iter().collect_vec(); + for ((old_asset_id, owner, delegate), approval) in old_approvals_iterator { + items += 1; + log::info!("old_approvals item {:?}", items); + pallet_assets_storage_items::new_types::Approvals::insert( + (id_map.get(&old_asset_id).unwrap(), owner.clone(), delegate.clone()), + approval, + ); + pallet_assets_storage_items::old_types::Approvals::remove((old_asset_id, owner, delegate)); + } + + let old_metadata_iterator = pallet_assets_storage_items::old_types::Metadata::iter().collect_vec(); + for (old_asset_id, metadata) in old_metadata_iterator { + items += 1; + log::info!("old_metadata item {:?}", items); + pallet_assets_storage_items::new_types::Metadata::insert(id_map.get(&old_asset_id).unwrap(), metadata); + pallet_assets_storage_items::old_types::Metadata::remove(old_asset_id); + } + + let old_oracle_raw_values_iterator = orml_oracle_storage_items::old_types::RawValues::iter().collect_vec(); + for (account, old_asset_id, raw_values) in old_oracle_raw_values_iterator { + items += 1; + log::info!("old_oracle_raw_values item {:?}", items); + orml_oracle::RawValues::::insert(account.clone(), id_map.get(&old_asset_id).unwrap(), raw_values); + orml_oracle_storage_items::old_types::RawValues::remove(account, old_asset_id); + } + + let old_oracle_values_iterator = orml_oracle_storage_items::old_types::Values::iter().collect_vec(); + for (old_asset_id, value) in old_oracle_values_iterator { + items += 1; + log::info!("old_oracle_values item {:?}", items); + orml_oracle::Values::::insert(id_map.get(&old_asset_id).unwrap(), value); + orml_oracle_storage_items::old_types::Values::remove(old_asset_id); + } + } + + ::DbWeight::get().reads_writes(items, items) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade( + versioned_post_upgrade_data_bytes: sp_std::vec::Vec, + ) -> Result<(), sp_runtime::TryRuntimeError> { + use parity_scale_codec::DecodeAll; + match ::decode_all(&mut &versioned_post_upgrade_data_bytes[..]) + .map_err(|_| "VersionedMigration post_upgrade failed to decode PreUpgradeData")? + { + VersionedPostUpgradeData::MigrationExecuted(_inner_bytes) => Ok(()), + VersionedPostUpgradeData::Noop => Ok(()), + } + } +} diff --git a/runtimes/polimec/src/custom_migrations/mod.rs b/runtimes/polimec/src/custom_migrations/mod.rs index 79925e649..7091bf7c8 100644 --- a/runtimes/polimec/src/custom_migrations/mod.rs +++ b/runtimes/polimec/src/custom_migrations/mod.rs @@ -16,3 +16,5 @@ // The generated files do not pass clippy. #![allow(clippy::all)] + +pub mod asset_id_migration; diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 4e1e11c61..2291f28d1 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -21,6 +21,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); extern crate alloc; +use assets_common::fungible_conversion::{convert, convert_balance}; use core::ops::RangeInclusive; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; @@ -97,14 +98,11 @@ use alloc::string::String; use sp_core::crypto::Ss58Codec; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; -use xcm::{ - v4::{Location}, - VersionedAssetId, -}; +use xcm::{v4::Location, VersionedAssetId}; #[cfg(feature = "runtime-benchmarks")] mod benchmark_helpers; -mod custom_migrations; +pub mod custom_migrations; mod weights; pub mod xcm_config; @@ -178,8 +176,10 @@ pub mod migrations { /// Unreleased migrations. Add new ones here: #[allow(unused_parens)] - pub type Unreleased = - (frame_support::migrations::RemovePallet::DbWeight>,); + pub type Unreleased = ( + super::custom_migrations::asset_id_migration::FromOldAssetIdMigration, + pallet_funding::storage_migrations::v6::MigrationToV6, + ); } /// Executive: handles dispatch to the various modules. @@ -225,7 +225,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("polimec-mainnet"), impl_name: create_runtime_str!("polimec-mainnet"), authoring_version: 1, - spec_version: 0_009_000, + spec_version: 1_000_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -1315,30 +1315,29 @@ mod benches { impl_runtime_apis! { impl assets_common::runtime_api::FungiblesApi for Runtime{ fn query_account_balances(account: AccountId) -> Result { - // Ok([ - // // collect pallet_balance - // { - // let balance = Balances::balance(&account); - // if balance > 0 { - // vec![convert_balance::(balance)?] - // } else { - // vec![] - // } - // }, - // // collect pallet_assets (ContributionTokens) - // convert::<_, _, _, _, xcm_config::ContributionTokensConvertedConcreteId>( - // ContributionTokens::account_balances(account.clone()) - // .iter() - // .filter(|(_, balance)| balance > &0) - // )?, - // // collect pallet_assets (ForeignAssets) - // convert::<_, _, _, _, xcm_config::ForeignAssetsConvertedConcreteId>( - // ForeignAssets::account_balances(account) - // .iter() - // .filter(|(_, balance)| balance > &0) - // )?, - // ].concat().into()) - Ok(vec![].into()) + Ok([ + // collect pallet_balance + { + let balance = Balances::balance(&account); + if balance > 0 { + vec![convert_balance::(balance)?] + } else { + vec![] + } + }, + // collect pallet_assets (ContributionTokens) + convert::<_, _, _, _, xcm_config::ContributionTokensConvertedConcreteId>( + ContributionTokens::account_balances(account.clone()) + .iter() + .filter(|(_, balance)| balance > &0) + )?, + // collect pallet_assets (ForeignAssets) + convert::<_, _, _, _, xcm_config::ForeignAssetsConvertedConcreteId>( + ForeignAssets::account_balances(account) + .iter() + .filter(|(_, balance)| balance > &0) + )?, + ].concat().into()) } } diff --git a/runtimes/polimec/src/xcm_config.rs b/runtimes/polimec/src/xcm_config.rs index 10285bfc1..a6642df57 100644 --- a/runtimes/polimec/src/xcm_config.rs +++ b/runtimes/polimec/src/xcm_config.rs @@ -36,7 +36,15 @@ use polimec_common_test_utils::DummyXcmSender; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; use xcm::v4::prelude::*; -use xcm_builder::{AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CreateMatcher, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, MatchXcm, MatchedConvertedConcreteId, MintLocation, NoChecking, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin}; +use xcm_builder::{ + AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, + AllowTopLevelPaidExecutionFrom, CreateMatcher, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, + FixedRateOfFungible, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, + MatchXcm, MatchedConvertedConcreteId, MintLocation, NoChecking, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, +}; use xcm_executor::{ traits::{JustTry, Properties, ShouldExecute}, XcmExecutor, @@ -65,6 +73,7 @@ parameter_types! { pub UniversalLocationNetworkId: NetworkId = UniversalLocation::get().global_consensus().unwrap(); pub const HereLocation: Location = Location::here(); pub AssetHubLocation: Location = (Parent, Parachain(1000)).into(); + pub CheckAccount: AccountId = PolkadotXcm::check_account(); /// The check account that is allowed to mint assets locally. Used for PLMC teleport /// checking once enabled.