diff --git a/Cargo.lock b/Cargo.lock index 612d539f6..6851d9146 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9466,6 +9466,7 @@ dependencies = [ "sp-core", "sp-genesis-builder", "sp-inherents", + "sp-io", "sp-offchain", "sp-runtime", "sp-session", diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index 3367daf31..344696b15 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -118,5 +118,7 @@ pub mod shortcuts { pub type PolkadotSystem = ::System; pub type PolimecSystem = ::System; + + pub type PolimecParachainSystem = ::ParachainSystem; } pub use shortcuts::*; diff --git a/integration-tests/src/tests/vest.rs b/integration-tests/src/tests/vest.rs index dcb483d90..b36979d90 100644 --- a/integration-tests/src/tests/vest.rs +++ b/integration-tests/src/tests/vest.rs @@ -14,21 +14,35 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{polimec::ED, *}; /// Tests for the oracle pallet integration. /// Alice, Bob, Charlie are members of the OracleProvidersMembers. /// Only members should be able to feed data into the oracle. -use frame_support::traits::fungible::Inspect; -use frame_support::traits::fungible::Mutate; +use crate::{polimec::ED, *}; +use cumulus_pallet_parachain_system::ValidationData; +use cumulus_primitives_core::PersistedValidationData; +use frame_support::traits::fungible::{Inspect, Mutate}; use macros::generate_accounts; use pallet_funding::assert_close_enough; use pallet_vesting::VestingInfo; use polimec_runtime::{Balances, BlockchainOperationTreasury, ParachainStaking, RuntimeOrigin, Vesting, PLMC}; use sp_core::crypto::get_public_from_string_or_panic; -use sp_runtime::Perquintill; +use sp_runtime::{Perquintill, SaturatedConversion}; generate_accounts!(PEPE, CARLOS); +fn set_relay_chain_block_number(to: u32) { + let mut validation_data = ValidationData::::get().unwrap_or_else(|| + // PersistedValidationData does not impl default in non-std + PersistedValidationData { + parent_head: vec![].into(), + relay_parent_number: Default::default(), + max_pov_size: Default::default(), + relay_parent_storage_root: Default::default(), + }); + validation_data.relay_parent_number = to.saturated_into(); + ValidationData::::put(validation_data) +} + #[test] fn base_vested_can_stake() { PolimecNet::execute_with(|| { @@ -89,10 +103,7 @@ fn base_can_withdraw_when_free_is_below_frozen_with_hold() { // Vesting schedule for PEPE of 20k PLMC + ED, which should have start date before it is applied let vesting_schedule = VestingInfo::new(2_020 * PLMC, 10 * PLMC, 0); - assert_eq!(Balances::free_balance(&CARLOS.into()), 0); - // We need some free balance at the time of the vested transfer - // Otherwise the user will never have free balance to pay for the "vest" extrinsic - PolimecSystem::set_block_number(1u32); + set_relay_chain_block_number::(1); // The actual vested transfer assert_ok!(Vesting::vested_transfer( @@ -101,7 +112,7 @@ fn base_can_withdraw_when_free_is_below_frozen_with_hold() { vesting_schedule )); - // Vested transfer didnt start with the full amount locked, since start date was befire execution + // Vested transfer didnt start with the full amount locked, since start date was before execution assert_eq!(Balances::usable_balance(&CARLOS.into()), 10 * PLMC); let carlos_acc: AccountId = CARLOS.into(); @@ -123,7 +134,7 @@ fn base_can_withdraw_when_free_is_below_frozen_with_hold() { assert_eq!(Balances::usable_balance(&CARLOS.into()), 10 * PLMC); // Be able to vest 10 more PLMC for this example description - PolimecSystem::set_block_number(2u32); + set_relay_chain_block_number::(2); // This should pass if the fee is correctly deducted with the new fee struct assert_ok!(Vesting::vest(RuntimeOrigin::signed(CARLOS.into()))); diff --git a/pallets/funding/src/benchmarking.rs b/pallets/funding/src/benchmarking.rs index b67fcc3c4..6499b6dc2 100644 --- a/pallets/funding/src/benchmarking.rs +++ b/pallets/funding/src/benchmarking.rs @@ -97,7 +97,7 @@ pub fn string_account( #[benchmarks( where - T: Config + frame_system::Config::RuntimeEvent> + pallet_balances::Config + cumulus_pallet_parachain_system::Config + sp_std::fmt::Debug, + T: Config + frame_system::Config::RuntimeEvent> + pallet_balances::Config + cumulus_pallet_parachain_system::Config + core::fmt::Debug, ::RuntimeEvent: TryInto> + Parameter + Member, ::Price: From, T::Hash: From, diff --git a/runtimes/polimec/Cargo.toml b/runtimes/polimec/Cargo.toml index ee690e472..fa6842f4b 100644 --- a/runtimes/polimec/Cargo.toml +++ b/runtimes/polimec/Cargo.toml @@ -74,6 +74,7 @@ sp-api.workspace = true sp-block-builder.workspace = true sp-consensus-aura.workspace = true sp-core.workspace = true +sp-io.workspace = true sp-inherents.workspace = true sp-offchain.workspace = true sp-runtime.workspace = true @@ -177,6 +178,7 @@ std = [ "sp-core/std", "sp-genesis-builder/std", "sp-inherents/std", + "sp-io/std", "sp-offchain/std", "sp-runtime/std", "sp-session/std", diff --git a/runtimes/polimec/src/custom_migrations/linear_release.rs b/runtimes/polimec/src/custom_migrations/linear_release.rs deleted file mode 100644 index 5a4b794fd..000000000 --- a/runtimes/polimec/src/custom_migrations/linear_release.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::{Balance, BlockNumber, Runtime, RuntimeHoldReason}; -use alloc::vec::Vec; -use frame_support::{storage::storage_prefix, traits::OnRuntimeUpgrade, BoundedVec}; -use pallet_linear_release::{MaxVestingSchedulesGet, VestingInfo}; - -#[cfg(feature = "try-runtime")] -use frame_support::{ensure, migrations::VersionedPostUpgradeData, traits::GetStorageVersion}; -#[cfg(feature = "try-runtime")] -use itertools::Itertools; -#[cfg(feature = "try-runtime")] -use parity_scale_codec::{DecodeAll, Encode}; - -pub type Values = BoundedVec, MaxVestingSchedulesGet>; - -pub struct LinearReleaseVestingMigration; -impl OnRuntimeUpgrade for LinearReleaseVestingMigration { - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - use crate::LinearRelease; - - let funding_on_chain_version = LinearRelease::on_chain_storage_version(); - if funding_on_chain_version == 0 { - let storage = pallet_linear_release::Vesting::::iter().collect_vec(); - ensure!(storage.len() == 15, "LinearReleaseVestingMigration: Invalid storage length in pre_upgrade"); - Ok(VersionedPostUpgradeData::MigrationExecuted(Vec::new()).encode()) - } else { - Ok(VersionedPostUpgradeData::Noop.encode()) - } - } - - fn on_runtime_upgrade() -> frame_support::weights::Weight { - let db_weight = ::DbWeight::get(); - - // Step 1: Get account count for weight calculation - let account_count = pallet_linear_release::Vesting::::iter().count(); - - // Initial weight: account_count reads + 1 clear_prefix operation - let mut total_weight = db_weight.reads_writes(account_count as u64, 1); // For clear_prefix - - // Step 2: Collect all accounts and their hold amounts - let mut account_holds = Vec::with_capacity(account_count); - - for (account, reason, vesting_info) in pallet_linear_release::Vesting::::iter() { - if !vesting_info.is_empty() { - log::info!( - "Found vesting for account: {:?}, reason: {:?}, schedule count: {:?}", - account, - reason, - vesting_info.len() - ); - account_holds.push((account, reason, vesting_info[0])); - } else { - log::warn!("Empty vesting info found for account: {:?}, reason: {:?}", account, reason); - } - } - - // Step 3: Clear all corrupted vesting entries - let pallet_prefix = storage_prefix(b"LinearRelease", b"Vesting"); - let removed_keys = frame_support::storage::unhashed::clear_prefix(&pallet_prefix, None, None); - log::info!("Cleared {:#?} vesting storage keys", removed_keys.deconstruct()); - - // Step 4: Create fresh vesting entries for all accounts with holds - let mut success_count = 0u64; - let mut failure_count = 0u64; - - for (account, _corrupted_reason, vesting_info) in account_holds { - // Create a BoundedVec with this schedule - reuse original VestingInfo value, but set the k2 to the new pallet_funding HoldReason::Participation. - let mut schedules = BoundedVec::<_, MaxVestingSchedulesGet>::default(); - - match schedules.try_push(vesting_info) { - Ok(_) => { - pallet_linear_release::Vesting::::insert( - &account, - &RuntimeHoldReason::Funding(pallet_funding::HoldReason::Participation), - schedules, - ); - success_count += 1; - total_weight = total_weight.saturating_add(db_weight.writes(1)); - }, - Err(_) => { - log::error!("Failed to add vesting schedule to BoundedVec for account {:?}", account); - failure_count += 1; - }, - } - } - - log::info!( - "Migration complete. Successfully created {} new vesting entries. Failed: {}", - success_count, - failure_count - ); - - total_weight - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade( - versioned_post_upgrade_data_bytes: sp_std::vec::Vec, - ) -> Result<(), sp_runtime::TryRuntimeError> { - let storage = pallet_linear_release::Vesting::::iter().collect_vec(); - ensure!(storage.len() == 15, "LinearReleaseVestingMigration: Invalid storage length in post_upgrade"); - - 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 7091bf7c8..d866e6547 100644 --- a/runtimes/polimec/src/custom_migrations/mod.rs +++ b/runtimes/polimec/src/custom_migrations/mod.rs @@ -18,3 +18,5 @@ #![allow(clippy::all)] pub mod asset_id_migration; + +pub mod vesting; diff --git a/runtimes/polimec/src/custom_migrations/vesting.rs b/runtimes/polimec/src/custom_migrations/vesting.rs new file mode 100644 index 000000000..9e355bd25 --- /dev/null +++ b/runtimes/polimec/src/custom_migrations/vesting.rs @@ -0,0 +1,149 @@ +use alloc::vec::Vec; +use frame_support::{traits::Currency, BoundedVec}; +use frame_system::pallet_prelude::BlockNumberFor; +use pallet_vesting::{MaxVestingSchedulesGet, VestingInfo}; +use parity_scale_codec::Encode; + +#[cfg(feature = "try-runtime")] +use parity_scale_codec::Decode; +#[cfg(feature = "try-runtime")] +use sp_runtime::DispatchError; + +type VestingInfoOf = VestingInfo< + <::Currency as Currency<::AccountId>>::Balance, + BlockNumberFor, +>; +pub type Values = BoundedVec, MaxVestingSchedulesGet>; + +pub mod v1 { + use super::*; + use core::marker::PhantomData; + use frame_support::traits::OnRuntimeUpgrade; + use parachains_common::impls::AccountIdOf; + use sp_core::Get; + use sp_runtime::{traits::BlockNumberProvider, Saturating, Weight}; + + const LOG: &str = "pallet_vesting::migration::v1"; + + /// Stores the status of vesting pallet migration to async backing. If it is populated, the migration already happened. + pub const VESTING_ASYNC_BACKED_KEY: &[u8; 31] = b"vesting_async_backing_migration"; + + pub struct UncheckedMigrationToV1(PhantomData); + + impl OnRuntimeUpgrade for UncheckedMigrationToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, DispatchError> { + let vesting_async_backed = sp_io::storage::get(VESTING_ASYNC_BACKED_KEY); + + if vesting_async_backed.is_some() { + log::info!(target: LOG, "Skipping migration as vesting pallet is already migrated with async backing"); + return Ok(Vec::new()); + } + + let migration_count = pallet_vesting::Vesting::::iter().count() as u32; + log::info!(target: LOG, "Pre-upgrade: {} UserMigrations entries", migration_count); + + let vestings = pallet_vesting::Vesting::::iter().collect::>(); + + Ok((migration_count, vestings).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let vesting_async_backed = sp_io::storage::get(VESTING_ASYNC_BACKED_KEY); + + if vesting_async_backed.is_some() { + log::info!(target: LOG, "Skipping migration as vesting pallet is already migrated with async backing"); + return Weight::zero(); + } + + let mut items = 0u64; + let translate_vesting_info = |_: AccountIdOf, vesting_info: Values| -> Option> { + let migrated: Vec<_> = vesting_info + .iter() + .map(|vesting| { + items = items.saturating_add(1); + + // adjust starting block to relay chain block number + let relay_chain_now = T::BlockNumberProvider::current_block_number(); + + let polimec_now = frame_system::Pallet::::current_block_number(); + + let two = 2_u32.into(); + + let relay_chain_starting_block = if polimec_now < vesting.starting_block() { + let blocks_diff = vesting.starting_block().saturating_sub(polimec_now); + relay_chain_now.saturating_add(blocks_diff.saturating_mul(two)) + } else { + let blocks_passed = polimec_now.saturating_sub(vesting.starting_block()); + relay_chain_now.saturating_sub(blocks_passed.saturating_mul(two)) + }; + + let adjusted_per_block = vesting.per_block().saturating_mul(2_u32.into()); + + VestingInfo::new(vesting.locked(), adjusted_per_block, relay_chain_starting_block) + }) + .collect(); + + Values::::try_from(migrated).ok() + }; + + log::info!(target: LOG, "Starting vesting time migration to V1"); + + pallet_vesting::Vesting::::translate(translate_vesting_info); + + log::info!(target: LOG, "Migrated {} vesting entries", items); + + sp_io::storage::set(VESTING_ASYNC_BACKED_KEY, &().encode()[..]); + + T::DbWeight::get().reads_writes(items, items) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(pre_state: Vec) -> Result<(), DispatchError> { + let vesting_async_backed = sp_io::storage::get(VESTING_ASYNC_BACKED_KEY); + if vesting_async_backed.is_some() { + log::info!(target: LOG, "Skipping migration as vesting pallet is already migrated with async backing"); + return Ok(()); + } + + let (pre_migration_count, pre_vestings): (u32, Vec<(AccountIdOf, Values)>) = + Decode::decode(&mut &pre_state[..]).expect("Failed to decode pre-migration state"); + + let post_migration_count = pallet_vesting::Vesting::::iter().count() as u32; + + if pre_migration_count != post_migration_count { + return Err("Migration count mismatch".into()); + } + + for (account, pre_vesting) in pre_vestings { + let post_vesting = pallet_vesting::Vesting::::get(&account).unwrap_or_default(); + + // check that the starting block has been adjusted + let relay_chain_now = T::BlockNumberProvider::current_block_number(); + + for (pre_vesting_info, post_vesting_info) in pre_vesting.iter().zip(post_vesting.iter()) { + assert_ne!( + pre_vesting_info.starting_block(), + post_vesting_info.starting_block(), + "Starting block not adjusted" + ); + assert!( + post_vesting_info.starting_block() <= + relay_chain_now.try_into().ok().expect("safe to convert; qed"), + "Starting block not adjusted correctly" + ); + + assert!( + post_vesting_info.per_block() == + pre_vesting_info + .per_block() + .saturating_mul(2_u32.try_into().ok().expect("safe to convert; qed")), + "Per block not adjusted" + ); + } + } + + Ok(()) + } + } +} diff --git a/runtimes/polimec/src/lib.rs b/runtimes/polimec/src/lib.rs index d0017862c..e6ea62bb0 100644 --- a/runtimes/polimec/src/lib.rs +++ b/runtimes/polimec/src/lib.rs @@ -187,12 +187,10 @@ pub mod migrations { /// Unreleased migrations. Add new ones here: #[allow(unused_parens)] pub type Unreleased = ( - super::custom_migrations::asset_id_migration::FromOldAssetIdMigration, - // super::custom_migrations::linear_release::unversioned::LinearReleaseVestingMigration, - pallet_funding::migrations::storage_migrations::v6::MigrationToV6, RemovePallet, pallet_funding::migrations::vesting_info::v7::MigrationToV8, pallet_linear_release::migrations::LinearReleaseVestingMigrationV1, + super::custom_migrations::vesting::v1::UncheckedMigrationToV1, ); } @@ -935,7 +933,7 @@ where } impl pallet_vesting::Config for Runtime { - type BlockNumberProvider = System; + type BlockNumberProvider = RelaychainDataProvider; type BlockNumberToBalance = ConvertInto; type Currency = Balances; type MinVestedTransfer = shared_configuration::vesting::MinVestedTransfer;