diff --git a/parachain/Cargo.lock b/parachain/Cargo.lock index e48e3b8e1c..a2f6d9a140 100644 --- a/parachain/Cargo.lock +++ b/parachain/Cargo.lock @@ -4542,7 +4542,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "heima-node" -version = "0.9.26" +version = "0.9.27" dependencies = [ "async-trait", "clap", diff --git a/parachain/node/Cargo.toml b/parachain/node/Cargo.toml index a22565b9ef..faf63d7d4d 100644 --- a/parachain/node/Cargo.toml +++ b/parachain/node/Cargo.toml @@ -7,7 +7,7 @@ homepage = 'https://www.heima.network' license = 'GPL-3.0' name = 'heima-node' repository = 'https://github.com/litentry/heima' -version = '0.9.26' +version = '0.9.27' [[bin]] name = 'heima-node' diff --git a/parachain/runtime/heima/src/lib.rs b/parachain/runtime/heima/src/lib.rs index ff93a479d2..cad7467403 100644 --- a/parachain/runtime/heima/src/lib.rs +++ b/parachain/runtime/heima/src/lib.rs @@ -153,10 +153,6 @@ pub type SignedPayload = generic::SignedPayload; /// Migrations to apply on runtime upgrade. pub type Migrations = ( - // one-shot: rescale bounded block-number/per-block state for the 12s -> 6s block-time change - // (spec_version 9262). Remove in the release after this one. The large `pallet_vesting` map is - // migrated separately as a multi-block migration, see `pallet_migrations::Config::Migrations`. - migration::block_time_6s::OnePassRescale, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); @@ -249,7 +245,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { impl_name: alloc::borrow::Cow::Borrowed("heima"), authoring_version: 1, // same versioning-mechanism as polkadot: use last digit for minor updates - spec_version: 9262, + spec_version: 9270, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -473,12 +469,7 @@ parameter_types! { impl pallet_migrations::Config for Runtime { type RuntimeEvent = RuntimeEvent; #[cfg(not(feature = "runtime-benchmarks"))] - type Migrations = ( - pallet_identity::migration::v2::LazyMigrationV1ToV2, - // one-shot: rescale vesting schedules for the 12s -> 6s block-time change (spec 9262). - // Remove in the release after this one. - migration::block_time_6s::VestingRescaleMigration, - ); + type Migrations = pallet_identity::migration::v2::LazyMigrationV1ToV2; // Benchmarks need mocked migrations to guarantee that they succeed. #[cfg(feature = "runtime-benchmarks")] type Migrations = pallet_migrations::mock_helpers::MockedMigrations; diff --git a/parachain/runtime/heima/src/migration/block_time_6s/mod.rs b/parachain/runtime/heima/src/migration/block_time_6s/mod.rs deleted file mode 100644 index e4b5f2768c..0000000000 --- a/parachain/runtime/heima/src/migration/block_time_6s/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2020-2025 Trust Computing GmbH. -// This file is part of Litentry. -// -// Litentry is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Litentry is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Litentry. If not, see . - -//! Migrations for the 12s -> 6s block-time change. -//! -//! Two pieces, wired into the runtime in different places: -//! * [`onepass::OnePassRescale`] — a single-pass `OnRuntimeUpgrade` for the bounded state -//! (parachain-staking round, score-staking round config, scheduler agenda). Register it in the -//! executive `Migrations` tuple in `lib.rs`. -//! * [`vesting::VestingRescaleMigration`] — a `SteppedMigration` (multi-block) for the large -//! `pallet_vesting` map. Register it in `pallet_migrations::Config::Migrations` in `lib.rs`. -//! -//! Both are one-shot: remove them in the release *after* the one that ships spec_version 9262, once -//! the upgrade has been enacted and finalized on every network. - -pub mod onepass; -pub mod vesting; - -pub use onepass::OnePassRescale; -pub use vesting::VestingRescaleMigration; diff --git a/parachain/runtime/heima/src/migration/block_time_6s/onepass.rs b/parachain/runtime/heima/src/migration/block_time_6s/onepass.rs deleted file mode 100644 index 474c3fa1bf..0000000000 --- a/parachain/runtime/heima/src/migration/block_time_6s/onepass.rs +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2020-2025 Trust Computing GmbH. -// This file is part of Litentry. -// -// Litentry is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Litentry is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Litentry. If not, see . - -//! Single-pass `OnRuntimeUpgrade` for the bounded pieces of on-chain state that encode an absolute -//! block number or a per-block rate, run once when block time is halved from 12s to 6s. -//! -//! All time-denominated *constants* (governance/treasury periods, `DefaultBlocksPerRound`, the -//! score-staking default interval, ...) are derived from `MINUTES/HOURS/DAYS` in `heima_primitives` -//! and therefore auto-double when `MILLISECS_PER_BLOCK` is halved — they need no migration. This -//! pass handles only the stored values that do **not** auto-rescale: -//! -//! * `parachain_staking::Round.length` — a stored block count (set at genesis / via -//! `set_blocks_per_round`); reset it to the new (doubled) `DefaultBlocksPerRound` and rebase -//! `first` to `now` so the current round neither ends early nor late on the transition. -//! * `score_staking::RoundConfig.interval` — a stored block count; reset to the new default -//! (`7 * DAYS`) while preserving the on-chain stake coefficients, and rebase `Round.start_block`. -//! * `pallet_scheduler::Agenda` — absolute future firing blocks. We rebase **only anonymous, -//! non-periodic** tasks (the only kind present on-chain: 1 on mainnet, 0 on Paseo), which lets us -//! avoid touching the pallet-private `Lookup`/`Retries` maps. Named/periodic tasks (none exist) -//! are asserted-against in try-runtime so this can never silently corrupt scheduler state. -//! -//! `vesting` is handled separately as a multi-block migration (see `vesting.rs`) because its map is -//! large. - -extern crate alloc; -use alloc::vec::Vec; -use core::marker::PhantomData; -use frame_support::{ - storage::unhashed, - traits::{Get, OnRuntimeUpgrade}, - weights::Weight, -}; -use frame_system::pallet_prelude::BlockNumberFor; -#[cfg(feature = "try-runtime")] -use parity_scale_codec::Encode; -use sp_runtime::Saturating; - -/// Storage key for the once-only guard. The rebases below are **not** naturally idempotent -/// (re-rebasing a scheduler task would push it out again), so we record completion under this key -/// and short-circuit on any subsequent execution. Derived as -/// `twox_128("BlockTime6s") ++ twox_128("OnePassRescaleDone")` — a regular pallet-style storage -/// prefix that cannot collide with any real pallet (no pallet is named `BlockTime6s`). -fn done_key() -> [u8; 32] { - let mut key = [0u8; 32]; - key[..16].copy_from_slice(&sp_core::hashing::twox_128(b"BlockTime6s")); - key[16..].copy_from_slice(&sp_core::hashing::twox_128(b"OnePassRescaleDone")); - key -} - -/// Rebase an absolute future block `b` so the same number of *remaining* blocks, at 2x speed, take -/// the same wall-clock time: `b' = now + 2 * (b - now)`. Past blocks are left untouched. -fn rebase_future(now: B, b: B) -> B -where - B: Copy + PartialOrd + Saturating + From, -{ - if b > now { - now.saturating_add(b.saturating_sub(now).saturating_mul(2u32.into())) - } else { - b - } -} - -pub struct OnePassRescale(PhantomData); - -impl OnRuntimeUpgrade for OnePassRescale -where - T: frame_system::Config - + pallet_parachain_staking::Config - + pallet_score_staking::Config - + pallet_scheduler::Config, -{ - fn on_runtime_upgrade() -> Weight { - let key = done_key(); - // Idempotency guard: the rebases below are not safe to apply twice, so run at most once. - if unhashed::get_raw(&key).is_some() { - log::info!("OnePassRescale: already applied, skipping"); - return T::DbWeight::get().reads(1); - } - - let now = frame_system::Pallet::::block_number(); - let mut reads: u64 = 1; - let mut writes: u64 = 1; - - // --- parachain-staking Round --- - pallet_parachain_staking::Round::::mutate(|r| { - r.length = ::DefaultBlocksPerRound::get(); - r.first = now; - }); - reads += 1; - writes += 1; - - // --- score-staking RoundConfig.interval + Round.start_block --- - // Take the (now-doubled) default interval but keep the chain's existing stake coefficients - // (mainnet uses m=3, Paseo m=2 — both differ from the default, so only `interval` is reset). - let default_interval = pallet_score_staking::DefaultRoundSetting::::get().interval; - pallet_score_staking::RoundConfig::::mutate(|c| { - c.interval = default_interval; - }); - pallet_score_staking::Round::::mutate(|r| { - r.start_block = now; - }); - reads += 2; - writes += 2; - - // --- scheduler Agenda: rebase anonymous, non-periodic future tasks only --- - let agenda_keys: Vec> = - pallet_scheduler::Agenda::::iter_keys().collect(); - for when in agenda_keys { - reads += 1; - if when <= now { - continue; - } - let new_when = rebase_future(now, when); - if new_when == when { - continue; - } - let agenda = pallet_scheduler::Agenda::::take(when); - writes += 1; - // Merge into the destination block (it is virtually always empty). - pallet_scheduler::Agenda::::mutate(new_when, |dest| { - for slot in agenda.into_iter() { - // Push best-effort; if the destination block is somehow full the task is - // dropped rather than panicking — try-runtime asserts the agenda is tiny. - let _ = dest.try_push(slot); - } - }); - writes += 1; - } - - // Mark complete so a re-execution is a no-op (idempotency). - unhashed::put_raw(&key, &[1u8]); - writes += 1; - - T::DbWeight::get().reads_writes(reads, writes) - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - // Snapshot the total number of scheduled slots so we can assert the rebase neither drops nor - // duplicates any task. (The `Scheduled` struct's fields are pallet-private, so we cannot - // inspect named/periodic-ness here; the rebase relocates opaque slots wholesale, which is - // correct for anonymous non-periodic tasks — the only kind on these chains. Named/periodic - // tasks would also need their private `Lookup`/period rebased, which this migration does not - // do; that pre-condition is verified out-of-band via RPC before deployment.) - let slot_count: u32 = pallet_scheduler::Agenda::::iter() - .map(|(_, a)| a.into_iter().flatten().count() as u32) - .sum(); - Ok(slot_count.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - use parity_scale_codec::Decode; - let before = u32::decode(&mut &state[..]).map_err(|_| "bad pre_upgrade state")?; - let after: u32 = pallet_scheduler::Agenda::::iter() - .map(|(_, a)| a.into_iter().flatten().count() as u32) - .sum(); - frame_support::ensure!(before == after, "scheduler slot count changed during rebase"); - - let round = pallet_parachain_staking::Round::::get(); - frame_support::ensure!( - round.length == ::DefaultBlocksPerRound::get(), - "parachain-staking Round.length not reset" - ); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{Runtime, RuntimeCall, RuntimeOrigin}; - use frame_support::traits::OnRuntimeUpgrade; - use sp_runtime::BuildStorage; - - fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| frame_system::Pallet::::set_block_number(1)); - ext - } - - // Mirrors the real heima on-chain scheduled burn: an anonymous, non-periodic Root-origin call - // (there it is a `utility.batchAll([utility.dispatchAs(Signed(_), balances.burn{..})])`; the - // rebase treats the call opaquely, so a plain `balances.burn` exercises the same code path). - #[test] - fn scheduled_burn_keeps_wall_clock_expiry_after_block_time_halving() { - new_test_ext().execute_with(|| { - let now: crate::BlockNumber = 1_000; - frame_system::Pallet::::set_block_number(now); - - // Schedule a burn 100_000 blocks out — at the OLD 12s block time that is ~13.9 days. - let blocks_until_fire: crate::BlockNumber = 100_000; - let when = now + blocks_until_fire; - let burn = RuntimeCall::Balances(pallet_balances::Call::burn { - value: 1_000_000_000_000_000_000_000, - keep_alive: false, - }); - pallet_scheduler::Pallet::::schedule( - RuntimeOrigin::root(), - when, - None, // non-periodic - 0, - Box::new(burn), - ) - .expect("schedule should succeed"); - - // Sanity: exactly one slot, sitting at `when`, none at the rebased target yet. - assert_eq!(pallet_scheduler::Agenda::::iter().count(), 1); - let live = |b: crate::BlockNumber| { - pallet_scheduler::Agenda::::get(b) - .iter() - .filter(|s| s.is_some()) - .count() - }; - assert_eq!(live(when), 1); - - let rebased = now + 2 * blocks_until_fire; - assert_eq!(live(rebased), 0); - - // Run the migration (block time is now halved 12s -> 6s). - let _ = OnePassRescale::::on_runtime_upgrade(); - - // The task moved from `when` to `now + 2*(when-now)`: same number of slots, none left - // behind at the old block. - assert_eq!(live(when), 0, "old agenda slot must be cleared"); - assert_eq!(live(rebased), 1, "task must be rebased to now + 2*(when-now)"); - assert_eq!( - pallet_scheduler::Agenda::::iter() - .map(|(_, a)| a.iter().filter(|s| s.is_some()).count()) - .sum::(), - 1, - "no task dropped or duplicated" - ); - - // Wall-clock invariant: old (12s) and new (6s) fire at the same real-world time. - let old_secs = (when - now) as u64 * 12; - let new_secs = (rebased - now) as u64 * 6; - assert_eq!(old_secs, new_secs, "real-world expiry must be unchanged"); - }); - } - - #[test] - fn migration_is_idempotent() { - new_test_ext().execute_with(|| { - let now: crate::BlockNumber = 1_000; - frame_system::Pallet::::set_block_number(now); - let when = now + 50_000; - let burn = RuntimeCall::Balances(pallet_balances::Call::burn { - value: 1_000, - keep_alive: false, - }); - pallet_scheduler::Pallet::::schedule( - RuntimeOrigin::root(), - when, - None, - 0, - Box::new(burn), - ) - .unwrap(); - - OnePassRescale::::on_runtime_upgrade(); - let after_first: Vec<_> = pallet_scheduler::Agenda::::iter_keys().collect(); - - // Second run must be a no-op (guarded), leaving the rebased agenda untouched. - OnePassRescale::::on_runtime_upgrade(); - let after_second: Vec<_> = pallet_scheduler::Agenda::::iter_keys().collect(); - - assert_eq!(after_first, after_second, "second run must not rebase again"); - }); - } -} diff --git a/parachain/runtime/heima/src/migration/block_time_6s/vesting.rs b/parachain/runtime/heima/src/migration/block_time_6s/vesting.rs deleted file mode 100644 index 379ccb3fdb..0000000000 --- a/parachain/runtime/heima/src/migration/block_time_6s/vesting.rs +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright 2020-2025 Trust Computing GmbH. -// This file is part of Litentry. -// -// Litentry is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Litentry is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Litentry. If not, see . - -//! Multi-block migration that rescales every `pallet_vesting` schedule when the chain block time -//! is halved from 12s to 6s. -//! -//! `VestingInfo` stores `{ locked, per_block, starting_block }`. `per_block` is a *per-block* drip -//! rate and `starting_block` is an *absolute* block. Once blocks arrive twice as fast in wall-clock -//! terms, an unmigrated schedule would finish vesting in **half** the intended real time. We rescale -//! so the *remaining* lock drains over the same wall-clock duration as before: -//! -//! Anchored at the upgrade block `now`: -//! * For a schedule that has **already started** (`starting_block <= now`): -//! - `still_locked = locked_at(now)` (funds already vested stay vested), -//! - new schedule `{ locked: still_locked, per_block: old_per_block / 2, starting_block: now }`. -//! At half the drip rate, `still_locked` now drains over ~2x the blocks => same wall-clock. -//! * For a schedule that has **not started yet** (`starting_block > now`): -//! - keep `locked`, halve `per_block`, and push the start out proportionally: -//! `starting_block' = now + 2 * (starting_block - now)`. -//! -//! `per_block` is clamped to `>= 1` so a schedule can never become non-terminating, and any -//! schedule that is already fully vested at `now` is dropped (its tokens are unlocked anyway). -//! -//! This is a `SteppedMigration` (registered via `pallet_migrations::Config::Migrations`) because the -//! `Vesting` map is large on mainnet (~2770 accounts) and a single-block pass could exceed block -//! weight. - -extern crate alloc; -use alloc::vec::Vec; -use core::marker::PhantomData; -use frame_support::{ - migrations::{SteppedMigration, SteppedMigrationError}, - pallet_prelude::*, - weights::WeightMeter, -}; -use frame_system::pallet_prelude::BlockNumberFor; -use pallet_vesting::{Vesting, VestingInfo}; -use sp_runtime::{traits::Zero, Saturating}; - -/// Unique identifier for this migration. Bump the version byte if it ever needs to re-run. -const PALLET_MIGRATION_ID: &[u8; 18] = b"vesting-rescale-6s"; - -type BalanceOf = <::Currency as frame_support::traits::Currency< - ::AccountId, ->>::Balance; - -/// An account's full vesting-schedule list, as stored by `pallet_vesting`. -type ScheduleList = BoundedVec< - VestingInfo, BlockNumberFor>, - pallet_vesting::MaxVestingSchedulesGet, ->; - -pub struct VestingRescaleMigration(PhantomData); - -impl VestingRescaleMigration -where - T: pallet_vesting::Config, - BalanceOf: - Saturating + Copy + Zero + PartialOrd + core::ops::Div> + From, -{ - /// Rescale a single account's schedule list. Returns `None` when, after rescaling, the account - /// no longer has any live schedule (everything was fully vested). - fn rescale_account( - now: BlockNumberFor, - schedules: ScheduleList, - ) -> Option> { - let mut out: Vec, BlockNumberFor>> = Vec::new(); - - for s in schedules.into_iter() { - // `locked_at` needs the runtime's `BlockNumberToBalance` converter; compute it here and - // hand the result to the pure rescale helper so the arithmetic is independently testable. - let still_locked = - s.locked_at::<::BlockNumberToBalance>(now); - if let Some(info) = rescale_schedule::, BlockNumberFor>( - now, - s.locked(), - s.per_block(), - s.starting_block(), - still_locked, - ) { - out.push(info); - } - } - - if out.is_empty() { - None - } else { - // The list came from a `BoundedVec` of the same bound and we never grow it, so this - // `try_from` cannot fail. - Some(BoundedVec::try_from(out).expect("rescaled list never exceeds the original bound")) - } - } -} - -impl SteppedMigration for VestingRescaleMigration -where - T: pallet_vesting::Config, - BalanceOf: - Saturating + Copy + Zero + PartialOrd + core::ops::Div> + From, -{ - type Cursor = T::AccountId; - type Identifier = MigrationId<18>; - - fn id() -> Self::Identifier { - MigrationId { pallet_id: *PALLET_MIGRATION_ID, version_from: 0, version_to: 1 } - } - - fn step( - mut cursor: Option, - meter: &mut WeightMeter, - ) -> Result, SteppedMigrationError> { - // One read + one write per account; require enough headroom for at least one account so the - // migration can always make progress. - let required = T::DbWeight::get().reads_writes(1, 1); - if meter.remaining().any_lt(required) { - return Err(SteppedMigrationError::InsufficientWeight { required }); - } - - let now = frame_system::Pallet::::block_number(); - - loop { - if meter.try_consume(required).is_err() { - // Out of weight for this block; resume from `cursor` next block. - return Ok(cursor); - } - - // Resume just past the last processed account, else start at the top of the map. - let mut iter = match &cursor { - Some(last) => Vesting::::iter_from(Vesting::::hashed_key_for(last)), - None => Vesting::::iter(), - }; - - match iter.next() { - Some((account, schedules)) => { - match Self::rescale_account(now, schedules) { - Some(new_schedules) => Vesting::::insert(&account, new_schedules), - None => Vesting::::remove(&account), - } - cursor = Some(account); - }, - // Reached the end of the map: migration complete. - None => return Ok(None), - } - } - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { - let count = Vesting::::iter().count() as u32; - Ok(count.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { - // Every remaining schedule must be valid (non-zero locked & per_block). - for (_who, schedules) in Vesting::::iter() { - for s in schedules.into_iter() { - frame_support::ensure!(s.is_valid(), "vesting schedule invalid after rescale"); - } - } - Ok(()) - } -} - -/// A small, self-describing identifier so two block-time migrations can never collide. -#[derive(MaxEncodedLen, Encode, Decode)] -pub struct MigrationId { - pub pallet_id: [u8; N], - pub version_from: u8, - pub version_to: u8, -} - -/// Pure rescale of a single vesting schedule for the 12s -> 6s block-time change. -/// -/// `still_locked` must be the schedule's `locked_at(now)` (computed by the caller using the -/// runtime's `BlockNumberToBalance`). Returns the new schedule, or `None` if it is already fully -/// vested at `now` (in which case the schedule is dropped — its funds are unlocked anyway). -/// -/// * Already started (`starting_block <= now`): keep already-vested funds vested; the remaining -/// `still_locked` drains from `now` at half the old rate, i.e. over ~2x the blocks => same -/// wall-clock. -/// * Not started yet (`starting_block > now`): keep `locked`, halve the rate, and push the start -/// out proportionally so it still begins at the same wall-clock moment. -/// -/// `per_block` is clamped to `>= 1` so the schedule can never become non-terminating. -fn rescale_schedule( - now: BlockNumber, - locked: Balance, - per_block: Balance, - starting_block: BlockNumber, - still_locked: Balance, -) -> Option> -where - Balance: sp_runtime::traits::AtLeast32BitUnsigned + Copy, - BlockNumber: sp_runtime::traits::AtLeast32BitUnsigned + Copy + sp_runtime::traits::Bounded, -{ - let halved_rate = per_block / Balance::from(2u32); - let new_per_block = if halved_rate.is_zero() { Balance::from(1u32) } else { halved_rate }; - - let info = if starting_block > now { - // Not started yet: delay the start proportionally, keep the full locked amount. - let delay = starting_block.saturating_sub(now); - let new_start = now.saturating_add(delay.saturating_mul(2u32.into())); - VestingInfo::new(locked, new_per_block, new_start) - } else { - // Already vesting: rebase the still-locked remainder to start draining from `now`. - if still_locked.is_zero() { - return None; - } - VestingInfo::new(still_locked, new_per_block, now) - }; - - if info.is_valid() { - Some(info) - } else { - None - } -} - -#[cfg(test)] -mod tests { - use super::rescale_schedule; - - // Balance = u128, BlockNumber = u32 (matches the heima runtime). - type Info = pallet_vesting::VestingInfo; - - fn rescale( - now: u32, - locked: u128, - per_block: u128, - starting_block: u32, - still_locked: u128, - ) -> Option { - rescale_schedule::(now, locked, per_block, starting_block, still_locked) - } - - #[test] - fn already_vesting_halves_rate_and_rebases_to_now() { - // 1000 locked, 10/block from block 0; at now=40, 400 vested, 600 still locked. - let out = rescale(40, 1000, 10, 0, 600).expect("still vesting"); - assert_eq!(out.locked(), 600, "only the still-locked remainder carries over"); - assert_eq!(out.per_block(), 5, "drip rate halved"); - assert_eq!(out.starting_block(), 40, "rebased to the upgrade block"); - // Same wall-clock: old remaining 600/10 = 60 blocks @12s; new 600/5 = 120 blocks @6s. - } - - #[test] - fn not_started_delays_start_proportionally() { - // Starts at block 100, now is 40 => 60 blocks away. After: 40 + 2*60 = 160. - let out = rescale(40, 1000, 10, 100, 1000).expect("not fully vested"); - assert_eq!(out.locked(), 1000, "full amount preserved before start"); - assert_eq!(out.per_block(), 5, "drip rate halved"); - assert_eq!(out.starting_block(), 160, "start pushed out so wall-clock start is unchanged"); - } - - #[test] - fn fully_vested_is_dropped() { - // now past the end; nothing still locked. - assert!(rescale(1000, 1000, 10, 0, 0).is_none()); - } - - #[test] - fn per_block_clamped_to_at_least_one() { - // per_block = 1 halves to 0 -> clamped to 1 so the schedule still terminates. - let out = rescale(0, 100, 1, 0, 100).expect("still vesting"); - assert_eq!(out.per_block(), 1, "never drops below 1"); - assert!(out.is_valid()); - } -} diff --git a/parachain/runtime/heima/src/migration/mod.rs b/parachain/runtime/heima/src/migration/mod.rs index 9bfaf1361d..20182fa371 100644 --- a/parachain/runtime/heima/src/migration/mod.rs +++ b/parachain/runtime/heima/src/migration/mod.rs @@ -13,5 +13,3 @@ // // You should have received a copy of the GNU General Public License // along with Litentry. If not, see . - -pub mod block_time_6s;