diff --git a/pallets/funding/src/lib.rs b/pallets/funding/src/lib.rs index 50e87ca26..79c32ca4c 100644 --- a/pallets/funding/src/lib.rs +++ b/pallets/funding/src/lib.rs @@ -95,7 +95,7 @@ pub use types::*; use xcm::v4::prelude::*; pub mod functions; -pub mod storage_migrations; +pub mod migrations; pub mod traits; pub mod types; pub mod weights; @@ -166,7 +166,7 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::storage_version(storage_migrations::STORAGE_VERSION)] + #[pallet::storage_version(migrations::STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] diff --git a/pallets/funding/src/migrations/mod.rs b/pallets/funding/src/migrations/mod.rs new file mode 100644 index 000000000..e54c18e0a --- /dev/null +++ b/pallets/funding/src/migrations/mod.rs @@ -0,0 +1,9 @@ +//! Migrations + +use frame_support::traits::StorageVersion; + +pub mod storage_migrations; +pub mod vesting_info; + +/// Current storage version +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); diff --git a/pallets/funding/src/storage_migrations.rs b/pallets/funding/src/migrations/storage_migrations.rs similarity index 96% rename from pallets/funding/src/storage_migrations.rs rename to pallets/funding/src/migrations/storage_migrations.rs index 04d0eb0a1..1c64462c6 100644 --- a/pallets/funding/src/storage_migrations.rs +++ b/pallets/funding/src/migrations/storage_migrations.rs @@ -4,7 +4,7 @@ use crate::{ ProjectMetadataOf, StringLimitOf, }; use core::marker::PhantomData; -use frame_support::traits::{StorageVersion, UncheckedOnRuntimeUpgrade}; +use frame_support::traits::UncheckedOnRuntimeUpgrade; use polimec_common::{assets::AcceptedFundingAsset, credentials::Cid}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; @@ -15,10 +15,6 @@ 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(6); -pub const LOG: &str = "runtime::funding::migration"; - pub mod v5_storage_items { #[derive(Clone, Copy, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -200,11 +196,11 @@ pub mod v5_storage_items { } pub mod v6 { - use super::*; - use crate::{ - storage_migrations::v5_storage_items::{OldMigration, OldProjectStatus, MAX_PARTICIPATIONS_PER_USER}, - EvaluationRoundInfo, ProjectDetailsOf, ProjectStatus, TicketSize, + use super::{ + v5_storage_items::{OldMigration, OldProjectStatus, MAX_PARTICIPATIONS_PER_USER}, + *, }; + use crate::{EvaluationRoundInfo, ProjectDetailsOf, ProjectStatus, TicketSize}; use frame_system::pallet_prelude::BlockNumberFor; use polimec_common::{ credentials::Did, diff --git a/pallets/funding/src/migrations/vesting_info.rs b/pallets/funding/src/migrations/vesting_info.rs new file mode 100644 index 000000000..f90e3800a --- /dev/null +++ b/pallets/funding/src/migrations/vesting_info.rs @@ -0,0 +1,148 @@ +// storage_migrations.rs + +use crate::Config; +use alloc::vec::Vec; +use frame_support::{pallet_prelude::*, traits::UncheckedOnRuntimeUpgrade, weights::Weight}; +use polimec_common::migration_types::{Migration, MigrationInfo, MigrationStatus}; +use sp_runtime::{traits::ConstU32, WeakBoundedVec}; + +pub mod v7 { + use crate::{AccountIdOf, ProjectId, UserMigrations}; + + use super::*; + + const LOG: &str = "funding::migration::v7"; + pub const MAX_PARTICIPATIONS_PER_USER: u32 = 10_000; + + type UserMigrationsKey = (ProjectId, AccountIdOf); + type UserMigrationsValue = (MigrationStatus, WeakBoundedVec>); + + pub struct UncheckedMigrationToV7(PhantomData); + impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV7 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, DispatchError> { + let migration_count = UserMigrations::::iter().count() as u32; + log::info!(target: LOG, "Pre-upgrade: {} UserMigrations entries", migration_count); + + // Encode counts and sample pre-migration vesting times for validation + let user_migrations = UserMigrations::::iter().collect::>(); + + Ok((migration_count, user_migrations).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let mut items = 0u64; + log::info!(target: LOG, "Starting vesting time migration to V7"); + + let translate_migrations = + |_: UserMigrationsKey, migration_value: UserMigrationsValue| -> Option { + let (status, migrations) = migration_value; + let migrated: Vec<_> = migrations + .iter() + .map(|migration| { + items = items.saturating_add(1); + + Migration { + info: MigrationInfo { + vesting_time: migration.info.vesting_time.saturating_mul(2), + ..migration.info + }, + ..migration.clone() + } + }) + .collect(); + + let bounded = WeakBoundedVec::try_from(migrated) + .unwrap_or_else(|_| panic!("MaxParticipationsPerUser exceeded during migration")); + + Some((status, bounded)) + }; + + UserMigrations::::translate(translate_migrations); + + log::info!(target: LOG, "Migrated {} vesting entries", items); + T::DbWeight::get().reads_writes(items, items) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(pre_state: Vec) -> Result<(), DispatchError> { + let (pre_migration_count, pre_user_migrations): (u32, Vec<(UserMigrationsKey, UserMigrationsValue)>) = + Decode::decode(&mut &pre_state[..]).expect("Failed to decode pre-migration state"); + + let post_migration_count = UserMigrations::::iter().count() as u32; + + if pre_migration_count != post_migration_count { + return Err("Migration count mismatch".into()); + } + + for (key, pre_value) in pre_user_migrations { + let post_value = UserMigrations::::get(key.clone()) + .unwrap_or_else(|| panic!("Post-migration UserMigrations entry not found for {:?}", key)); + + for (pre_migration, post_migration) in pre_value.1.iter().zip(post_value.1.iter()) { + if pre_migration.info.vesting_time.saturating_mul(2) != post_migration.info.vesting_time { + return Err("Migration vesting time mismatch".into()); + } + } + } + + Ok(()) + } + } + + pub type MigrationToV8 = frame_support::migrations::VersionedMigration< + 6, + 7, + UncheckedMigrationToV7, + crate::Pallet, + ::DbWeight, + >; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + mock::{new_test_ext, TestRuntime as Test}, + UserMigrations, + }; + use cumulus_primitives_core::Junction; + use polimec_common::migration_types::MigrationOrigin; + use v7::{UncheckedMigrationToV7, MAX_PARTICIPATIONS_PER_USER}; + + #[test] + fn migration_to_v7() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + assert_eq!(UserMigrations::::iter().count(), 0); + + let mut migrations = Vec::new(); + for i in 0..MAX_PARTICIPATIONS_PER_USER { + migrations.push(Migration { + info: MigrationInfo { vesting_time: i as u64, contribution_token_amount: 1_u128 }, + origin: MigrationOrigin { + user: Junction::OnlyChild, + participation_type: polimec_common::migration_types::ParticipationType::Bid, + }, + }); + } + + let bounded_migrations = WeakBoundedVec::try_from(migrations).unwrap(); + + UserMigrations::::insert((1, 1), (MigrationStatus::Confirmed, bounded_migrations)); + + assert_eq!(UserMigrations::::iter().count(), 1); + + let weight = UncheckedMigrationToV7::::on_runtime_upgrade(); + assert_eq!(UserMigrations::::iter().count(), 1); + + for (_, (_, migrations)) in UserMigrations::::iter() { + for (i, migration) in migrations.iter().enumerate() { + assert_eq!(migration.info.vesting_time, i as u64 * 2); + } + } + + assert!(weight.is_zero()); + }); + } +} diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index 25c6cace0..e424bd883 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -189,8 +189,9 @@ pub mod migrations { pub type Unreleased = ( super::custom_migrations::asset_id_migration::FromOldAssetIdMigration, super::custom_migrations::linear_release::LinearReleaseVestingMigration, - pallet_funding::storage_migrations::v6::MigrationToV6, + pallet_funding::migrations::storage_migrations::v6::MigrationToV6, RemovePallet, + pallet_funding::migrations::vesting_info::v7::MigrationToV8, ); }