vesting_mbm/lib.rs
1// This file is part of Astar.
2
3// Copyright (C) Stake Technologies Pte.Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later
5
6// Astar is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// Astar is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with Astar. If not, see <http://www.gnu.org/licenses/>.
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21use frame_support::{
22 migrations::{MigrationId, SteppedMigration, SteppedMigrationError},
23 weights::WeightMeter,
24};
25use pallet_vesting::{Vesting, VestingInfo};
26use sp_arithmetic::traits::{SaturatedConversion, Saturating};
27use sp_runtime::{traits::BlockNumberProvider, Percent};
28
29pub use pallet::*;
30
31#[cfg(feature = "runtime-benchmarks")]
32mod benchmarks;
33#[cfg(test)]
34mod mock;
35#[cfg(test)]
36mod tests;
37
38pub mod weights;
39
40const LOG_TARGET: &str = "mbm::vesting";
41const PALLET_MIGRATIONS_ID: &[u8; 18] = b"pallet-vesting-mbm";
42
43pub struct LazyMigration<T, W: weights::WeightInfo>(core::marker::PhantomData<(T, W)>);
44
45impl<T: pallet_vesting::Config, W: weights::WeightInfo> SteppedMigration for LazyMigration<T, W> {
46 type Cursor = <T as frame_system::Config>::AccountId;
47 // Without the explicit length here the construction of the ID would not be infallible.
48 type Identifier = MigrationId<18>;
49
50 /// The identifier of this migration. Which should be globally unique.
51 fn id() -> Self::Identifier {
52 MigrationId {
53 pallet_id: *PALLET_MIGRATIONS_ID,
54 version_from: 0,
55 version_to: 1,
56 }
57 }
58
59 fn step(
60 mut cursor: Option<Self::Cursor>,
61 meter: &mut WeightMeter,
62 ) -> Result<Option<Self::Cursor>, SteppedMigrationError> {
63 let required = W::step(T::MAX_VESTING_SCHEDULES);
64 // If there is not enough weight for a single step, return an error. This case can be
65 // problematic if it is the first migration that ran in this block. But there is nothing
66 // that we can do about it here.
67 if meter.remaining().any_lt(required) {
68 return Err(SteppedMigrationError::InsufficientWeight { required });
69 }
70
71 let mut count = 0u32;
72 let para_block_number = frame_system::Pallet::<T>::block_number();
73 let current_block_number = T::BlockNumberProvider::current_block_number();
74
75 // We loop here to do as much progress as possible per step.
76 loop {
77 // stop when remaining weight is lower than step max weight
78 if meter.remaining().any_lt(required) {
79 break;
80 }
81
82 let mut iter = if let Some(last_key) = cursor {
83 // If a cursor is provided, start iterating from the stored value
84 // corresponding to the last key processed in the previous step.
85 // Note that this only works if the old and the new map use the same way to hash
86 // storage keys.
87 Vesting::<T>::iter_from(Vesting::<T>::hashed_key_for(last_key))
88 } else {
89 // If no cursor is provided, start iterating from the beginning.
90 Vesting::<T>::iter()
91 };
92
93 // If there's a next item in the iterator, perform the migration.
94 if let Some((ref last_key, mut schedules)) = iter.next() {
95 for schedule in schedules.iter_mut() {
96 // remaining locked balance
97 let locked = schedule.locked_at::<T::BlockNumberToBalance>(para_block_number);
98 // reduce unlock `per_block` into half
99 let per_block = Percent::from_percent(50) * schedule.per_block();
100 // remaining blocks to start vesting if vesting hasn't started yet
101 // remaining blocks will be doubled
102 let remaining_blocks = schedule
103 .starting_block()
104 .saturating_sub(para_block_number)
105 .saturating_mul(2u32.into());
106 let start_block = current_block_number.saturating_add(remaining_blocks);
107
108 *schedule = VestingInfo::new(locked, per_block, start_block);
109 }
110
111 // consume the exact weight
112 meter.consume(W::step(schedules.len().saturated_into()));
113
114 // Override vesting schedules
115 Vesting::<T>::insert(last_key, schedules);
116
117 // inc counter
118 count.saturating_inc();
119
120 // Return the processed key as the new cursor.
121 cursor = Some(last_key.clone())
122 } else {
123 // Signal that the migration is complete (no more items to process).
124 cursor = None;
125 break;
126 }
127 }
128 log::debug!(target: LOG_TARGET, "migrated {count:?} entries");
129 Ok(cursor)
130 }
131}
132
133#[frame_support::pallet]
134pub mod pallet {
135 use super::*;
136
137 #[pallet::pallet]
138 #[pallet::without_storage_info]
139 pub struct Pallet<T>(_);
140
141 #[pallet::config]
142 pub trait Config: frame_system::Config + pallet_vesting::Config {}
143}