Skip to content

Commit 1207e4e

Browse files
committed
Linear relaase pallet migration
1 parent 4d70109 commit 1207e4e

File tree

8 files changed

+273
-6
lines changed

8 files changed

+273
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pallets/linear-release/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ workspace = true
1717
targets = ["x86_64-unknown-linux-gnu"]
1818

1919
[dependencies]
20+
log.workspace = true
2021
parity-scale-codec = { workspace = true, features = ["derive"] }
2122
scale-info = { workspace = true, features = ["derive"] }
2223
frame-support.workspace = true
@@ -35,6 +36,7 @@ std = [
3536
"frame-benchmarking?/std",
3637
"frame-support/std",
3738
"frame-system/std",
39+
"log/std",
3840
"pallet-balances/std",
3941
"parity-scale-codec/std",
4042
"polimec-common/std",

pallets/linear-release/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
extern crate alloc;
2323

2424
mod benchmarking;
25+
pub mod migrations;
2526
pub mod weights;
2627

2728
use alloc::{vec, vec::Vec};
@@ -33,7 +34,7 @@ use frame_support::{
3334
traits::{
3435
fungible::{BalancedHold, Inspect, InspectHold, Mutate, MutateHold},
3536
tokens::{Balance, Precision},
36-
Get, WithdrawReasons,
37+
Get, StorageVersion, WithdrawReasons,
3738
},
3839
};
3940
use frame_system::pallet_prelude::*;
@@ -110,6 +111,9 @@ impl<T: Config> Get<u32> for MaxVestingSchedulesGet<T> {
110111
}
111112
}
112113

114+
/// Current storage version
115+
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
116+
113117
/// Enable `dev_mode` for this pallet.
114118
#[frame_support::pallet(dev_mode)]
115119
pub mod pallet {
@@ -172,6 +176,7 @@ pub mod pallet {
172176
// Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and
173177
// method.
174178
#[pallet::pallet]
179+
#[pallet::storage_version(STORAGE_VERSION)]
175180
pub struct Pallet<T>(_);
176181

177182
#[pallet::event]
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
use super::*;
2+
use crate::{AccountIdOf, ReasonOf};
3+
use core::marker::PhantomData;
4+
use frame_support::{migrations::VersionedMigration, traits::UncheckedOnRuntimeUpgrade};
5+
use sp_runtime::{traits::BlockNumberProvider, Saturating, Weight};
6+
7+
pub type Values<T> = BoundedVec<VestingInfoOf<T>, MaxVestingSchedulesGet<T>>;
8+
9+
const LOG: &str = "linear_release::migration::v1";
10+
pub struct LinearReleaseVestingInfoMigration;
11+
12+
pub struct UncheckedMigrationToV1<T: Config>(PhantomData<T>);
13+
14+
impl<T: crate::Config> UncheckedOnRuntimeUpgrade for UncheckedMigrationToV1<T> {
15+
#[cfg(feature = "try-runtime")]
16+
fn pre_upgrade() -> Result<Vec<u8>, DispatchError> {
17+
let migration_count = crate::Vesting::<T>::iter().count() as u32;
18+
log::info!(target: LOG, "Pre-upgrade: {} UserMigrations entries", migration_count);
19+
20+
let vestings = crate::Vesting::<T>::iter().collect::<Vec<_>>();
21+
22+
Ok((migration_count, vestings).encode())
23+
}
24+
25+
fn on_runtime_upgrade() -> Weight {
26+
let mut items = 0u64;
27+
let translate_vesting_info =
28+
|_: AccountIdOf<T>, _: ReasonOf<T>, vesting_info: Values<T>| -> Option<Values<T>> {
29+
let migrated: Vec<_> = vesting_info
30+
.iter()
31+
.map(|vesting| {
32+
items = items.saturating_add(1);
33+
34+
// adjust starting block to relay chain block number
35+
let relay_chain_now = T::BlockNumberProvider::current_block_number();
36+
37+
let polimec_now = frame_system::Pallet::<T>::current_block_number();
38+
let two = 2_u32.into();
39+
40+
let relay_chain_starting_block = if polimec_now < vesting.starting_block() {
41+
let blocks_diff = vesting.starting_block().saturating_sub(polimec_now);
42+
relay_chain_now.saturating_add(blocks_diff.saturating_mul(two))
43+
} else {
44+
let blocks_passed = polimec_now.saturating_sub(vesting.starting_block());
45+
relay_chain_now.saturating_sub(blocks_passed.saturating_mul(two))
46+
};
47+
48+
let adjusted_per_block = vesting.per_block.saturating_mul(2_u32.into());
49+
50+
VestingInfo {
51+
locked: vesting.locked,
52+
per_block: adjusted_per_block,
53+
starting_block: relay_chain_starting_block,
54+
}
55+
})
56+
.collect();
57+
58+
Values::<T>::try_from(migrated).ok()
59+
};
60+
61+
log::info!(target: LOG, "Starting linear release vesting time migration to V1");
62+
63+
crate::Vesting::<T>::translate(translate_vesting_info);
64+
65+
log::info!(target: LOG, "Migrated {} linear release vesting entries", items);
66+
67+
T::DbWeight::get().reads_writes(items, items)
68+
}
69+
70+
#[cfg(feature = "try-runtime")]
71+
fn post_upgrade(pre_state: Vec<u8>) -> Result<(), DispatchError> {
72+
let (pre_migration_count, pre_vestings): (u32, Vec<((AccountIdOf<T>, ReasonOf<T>), Values<T>)>) =
73+
Decode::decode(&mut &pre_state[..]).expect("Failed to decode pre-migration state");
74+
75+
let post_migration_count = crate::Vesting::<T>::iter().count() as u32;
76+
77+
if pre_migration_count != post_migration_count {
78+
return Err("Migration count mismatch".into());
79+
}
80+
81+
for ((account, reason), pre_vesting) in pre_vestings {
82+
let post_vesting = crate::Vesting::<T>::get(&account, &reason).unwrap_or_default();
83+
84+
// check that the starting block has been adjusted
85+
let relay_chain_now = T::BlockNumberProvider::current_block_number();
86+
87+
for (pre_vesting_info, post_vesting_info) in pre_vesting.iter().zip(post_vesting.iter()) {
88+
assert_ne!(
89+
pre_vesting_info.starting_block, post_vesting_info.starting_block,
90+
"Starting block not adjusted"
91+
);
92+
assert!(
93+
post_vesting_info.starting_block <= relay_chain_now.try_into().ok().expect("safe to convert; qed"),
94+
"Starting block not adjusted correctly"
95+
);
96+
97+
assert!(
98+
post_vesting_info.per_block ==
99+
pre_vesting_info
100+
.per_block
101+
.saturating_mul(2_u32.try_into().ok().expect("safe to convert; qed")),
102+
"Per block not adjusted"
103+
);
104+
}
105+
}
106+
107+
Ok(())
108+
}
109+
}
110+
111+
pub type LinearReleaseVestingMigrationV1<T> =
112+
VersionedMigration<0, 1, UncheckedMigrationToV1<T>, crate::Pallet<T>, <T as frame_system::Config>::DbWeight>;
113+
114+
#[cfg(test)]
115+
mod test {
116+
use super::*;
117+
use crate::{
118+
mock::{ExtBuilder, MockRuntimeHoldReason, System, Test, MOCK_BLOCK_NUMBER},
119+
pallet::Vesting,
120+
AccountIdOf, BalanceOf, BlockNumberFor,
121+
};
122+
use frame_support::weights::RuntimeDbWeight;
123+
use sp_runtime::bounded_vec;
124+
125+
// Helper to calculate expected results concisely
126+
// Now takes the *actual* polimec_now and the *fixed* relay_chain_now from the mock provider
127+
fn calculate_expected(
128+
vesting: &VestingInfo<BalanceOf<Test>, BlockNumberFor<Test>>,
129+
polimec_now: BlockNumberFor<Test>,
130+
relay_chain_now: BlockNumberFor<Test>, // This will be MOCK_BLOCK_NUMBER
131+
) -> VestingInfo<BalanceOf<Test>, BlockNumberFor<Test>> {
132+
let two: BlockNumberFor<Test> = 2u32.into();
133+
let expected_relay_start = if polimec_now < vesting.starting_block {
134+
let blocks_diff = vesting.starting_block.saturating_sub(polimec_now);
135+
relay_chain_now.saturating_add(blocks_diff.saturating_mul(two))
136+
} else {
137+
let blocks_passed = polimec_now.saturating_sub(vesting.starting_block);
138+
relay_chain_now.saturating_sub(blocks_passed.saturating_mul(two))
139+
};
140+
let expected_per_block = vesting.per_block.saturating_mul(two);
141+
142+
VestingInfo {
143+
locked: vesting.locked, // Stays the same
144+
per_block: expected_per_block,
145+
starting_block: expected_relay_start,
146+
}
147+
}
148+
149+
#[test]
150+
fn migration_v1_adjusts_schedules_correctly() {
151+
ExtBuilder::default().existential_deposit(256).build().execute_with(|| {
152+
let polimec_now: BlockNumberFor<Test> = 100;
153+
System::set_block_number(polimec_now);
154+
155+
// The relay chain block number is now fixed by the mock provider
156+
let relay_chain_now: BlockNumberFor<Test> = MOCK_BLOCK_NUMBER;
157+
158+
let account1: AccountIdOf<Test> = 3;
159+
let account2: AccountIdOf<Test> = 4;
160+
161+
let reason1 = MockRuntimeHoldReason::Reason;
162+
let reason2 = MockRuntimeHoldReason::Reason2;
163+
164+
// Schedule starting in the past relative to polimec_now
165+
let v_past = VestingInfo { locked: 1000, per_block: 10, starting_block: 50 };
166+
// Schedule starting in the future relative to polimec_now
167+
let v_future = VestingInfo { locked: 2000, per_block: 20, starting_block: 150 };
168+
// Schedule starting exactly now relative to polimec_now
169+
let v_now = VestingInfo { locked: 500, per_block: 5, starting_block: polimec_now };
170+
// Schedule starting at block 0 (edge case)
171+
let v_zero = VestingInfo { locked: 100, per_block: 1, starting_block: 0 };
172+
173+
// Entry 1: Acc1, Reason1 -> Multiple schedules, covering past and future
174+
let schedules1: Values<Test> = bounded_vec![v_past.clone(), v_future.clone()];
175+
Vesting::<Test>::insert(account1, reason1.clone(), schedules1.clone());
176+
177+
// Entry 2: Acc2, Reason1 -> Single schedule, covering 'now' case
178+
let schedules2: Values<Test> = bounded_vec![v_now.clone()];
179+
Vesting::<Test>::insert(account2, reason1.clone(), schedules2.clone());
180+
181+
// Entry 3: Acc1, Reason2 -> Single schedule, edge case start block
182+
let schedules3: Values<Test> = bounded_vec![v_zero.clone()];
183+
Vesting::<Test>::insert(account1, reason2.clone(), schedules3.clone());
184+
185+
// Verify initial counts
186+
let initial_storage_entries = Vesting::<Test>::iter_keys().count(); // Counts distinct (Acc, Reason) pairs
187+
let initial_schedules_count: u64 = Vesting::<Test>::iter_values().map(|v| v.len() as u64).sum();
188+
assert_eq!(initial_storage_entries, 6); // default already adds 3 entries
189+
assert_eq!(initial_schedules_count, 7); // 3 from default, 4 from our setup
190+
191+
let weight = UncheckedMigrationToV1::<Test>::on_runtime_upgrade();
192+
193+
assert_eq!(
194+
Vesting::<Test>::iter_keys().count(),
195+
initial_storage_entries,
196+
"Number of storage entries should not change"
197+
);
198+
199+
let migrated1 = Vesting::<Test>::get(account1, reason1.clone()).unwrap();
200+
assert_eq!(migrated1.len(), 2);
201+
202+
assert_eq!(migrated1[0], calculate_expected(&v_past, polimec_now, relay_chain_now));
203+
assert_eq!(migrated1[1], calculate_expected(&v_future, polimec_now, relay_chain_now));
204+
205+
let migrated2 = Vesting::<Test>::get(account2, reason1.clone()).unwrap();
206+
assert_eq!(migrated2.len(), 1);
207+
assert_eq!(migrated2[0], calculate_expected(&v_now, polimec_now, relay_chain_now));
208+
209+
let migrated3 = Vesting::<Test>::get(account1, reason2.clone()).unwrap();
210+
assert_eq!(migrated3.len(), 1);
211+
assert_eq!(migrated3[0], calculate_expected(&v_zero, polimec_now, relay_chain_now));
212+
213+
let db_weight: RuntimeDbWeight = <Test as frame_system::Config>::DbWeight::get();
214+
let expected_weight = db_weight.reads_writes(initial_schedules_count, initial_schedules_count);
215+
216+
assert_eq!(weight, expected_weight, "Weight should match items processed");
217+
218+
// also verify default vesting entries that are migrated
219+
let default_vesting = Vesting::<Test>::get(1, MockRuntimeHoldReason::Reason).unwrap();
220+
assert_eq!(
221+
default_vesting[0],
222+
calculate_expected(
223+
&VestingInfo { starting_block: 0, per_block: 128, locked: 5 * 256 },
224+
polimec_now,
225+
relay_chain_now
226+
),
227+
);
228+
229+
let default_vesting_2 = Vesting::<Test>::get(2, MockRuntimeHoldReason::Reason).unwrap();
230+
assert_eq!(
231+
default_vesting_2[0],
232+
calculate_expected(
233+
&VestingInfo { starting_block: 10, per_block: 256, locked: 20 * 256 },
234+
polimec_now,
235+
relay_chain_now
236+
),
237+
);
238+
});
239+
}
240+
}

pallets/linear-release/src/mock.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,30 @@ impl pallet_balances::Config for Test {
6161

6262
parameter_types! {
6363
pub BenchmarkReason: MockRuntimeHoldReason = MockRuntimeHoldReason::Reason;
64+
65+
}
66+
67+
pub const MOCK_BLOCK_NUMBER: u64 = 1000;
68+
pub struct MockBlockNumberProvider;
69+
70+
impl BlockNumberProvider for MockBlockNumberProvider {
71+
type BlockNumber = u64;
72+
73+
fn current_block_number() -> Self::BlockNumber {
74+
// This is a mock implementation, so we return a fixed value.
75+
MOCK_BLOCK_NUMBER
76+
}
77+
78+
fn set_block_number(_block: Self::BlockNumber) {
79+
// This is a mock implementation, so we do nothing.
80+
}
6481
}
6582

6683
impl Config for Test {
6784
type Balance = u64;
6885
#[cfg(feature = "runtime-benchmarks")]
6986
type BenchmarkReason = BenchmarkReason;
70-
type BlockNumberProvider = System;
87+
type BlockNumberProvider = MockBlockNumberProvider;
7188
type BlockNumberToBalance = Identity;
7289
type Currency = Balances;
7390
// TODO: Use the type from Balances.

runtimes/polimec/src/custom_migrations/linear_release.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub type Values = BoundedVec<VestingInfo<Balance, BlockNumber>, MaxVestingSchedu
1515
pub struct LinearReleaseVestingMigration;
1616
impl OnRuntimeUpgrade for LinearReleaseVestingMigration {
1717
#[cfg(feature = "try-runtime")]
18-
fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
18+
fn pre_upgrade() -> Result<sp_std::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
1919
use crate::LinearRelease;
2020

2121
let funding_on_chain_version = LinearRelease::on_chain_storage_version();
@@ -94,7 +94,9 @@ impl OnRuntimeUpgrade for LinearReleaseVestingMigration {
9494
}
9595

9696
#[cfg(feature = "try-runtime")]
97-
fn post_upgrade(versioned_post_upgrade_data_bytes: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
97+
fn post_upgrade(
98+
versioned_post_upgrade_data_bytes: sp_std::vec::Vec<u8>,
99+
) -> Result<(), sp_runtime::TryRuntimeError> {
98100
let storage = pallet_linear_release::Vesting::<Runtime>::iter().collect_vec();
99101
ensure!(storage.len() == 15, "LinearReleaseVestingMigration: Invalid storage length in post_upgrade");
100102

runtimes/polimec/src/custom_migrations/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,3 @@
1818
#![allow(clippy::all)]
1919

2020
pub mod asset_id_migration;
21-
pub mod linear_release;

runtimes/polimec/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,10 +188,11 @@ pub mod migrations {
188188
#[allow(unused_parens)]
189189
pub type Unreleased = (
190190
super::custom_migrations::asset_id_migration::FromOldAssetIdMigration,
191-
super::custom_migrations::linear_release::LinearReleaseVestingMigration,
191+
// super::custom_migrations::linear_release::unversioned::LinearReleaseVestingMigration,
192192
pallet_funding::migrations::storage_migrations::v6::MigrationToV6<Runtime>,
193193
RemovePallet<IdentityPalletName, RuntimeDbWeight>,
194194
pallet_funding::migrations::vesting_info::v7::MigrationToV8<Runtime>,
195+
pallet_linear_release::migrations::LinearReleaseVestingMigrationV1<Runtime>,
195196
);
196197
}
197198

0 commit comments

Comments
 (0)