orml_gradually_update/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
20#![allow(clippy::string_lit_as_bytes)]
22#![allow(clippy::unused_unit)]
23
24use frame_support::{
25 ensure,
26 pallet_prelude::*,
27 storage,
28 traits::{EnsureOrigin, Get},
29 BoundedVec,
30};
31use frame_system::pallet_prelude::*;
32use parity_scale_codec::MaxEncodedLen;
33use scale_info::TypeInfo;
34use sp_runtime::{
35 traits::{SaturatedConversion, Saturating},
36 DispatchResult, RuntimeDebug,
37};
38
39mod default_weight;
40mod mock;
41mod tests;
42
43#[derive(Encode, Decode, Clone, Eq, PartialEq, MaxEncodedLen, RuntimeDebug, TypeInfo, DecodeWithMemTracking)]
47pub struct GraduallyUpdate<Key, Value> {
48 pub key: Key,
49 pub target_value: Value,
50 pub per_block: Value,
51}
52
53pub use module::*;
54
55#[frame_support::pallet]
56pub mod module {
57 use super::*;
58
59 pub trait WeightInfo {
60 fn gradually_update() -> Weight;
61 fn cancel_gradually_update() -> Weight;
62 fn on_finalize(u: u32) -> Weight;
63 }
64
65 pub(crate) type StorageKeyBytes<T> = BoundedVec<u8, <T as Config>::MaxStorageKeyBytes>;
66 pub(crate) type StorageValueBytes<T> = BoundedVec<u8, <T as Config>::MaxStorageValueBytes>;
67
68 type GraduallyUpdateOf<T> = GraduallyUpdate<StorageKeyBytes<T>, StorageValueBytes<T>>;
69
70 #[pallet::config]
71 pub trait Config: frame_system::Config {
72 #[pallet::constant]
74 type UpdateFrequency: Get<BlockNumberFor<Self>>;
75
76 type DispatchOrigin: EnsureOrigin<Self::RuntimeOrigin>;
78
79 type WeightInfo: WeightInfo;
81
82 type MaxGraduallyUpdate: Get<u32>;
84
85 type MaxStorageKeyBytes: Get<u32>;
87
88 type MaxStorageValueBytes: Get<u32>;
90 }
91
92 #[pallet::error]
93 pub enum Error<T> {
94 InvalidPerBlockOrTargetValue,
96 InvalidTargetValue,
98 GraduallyUpdateHasExisted,
100 GraduallyUpdateNotFound,
102 MaxGraduallyUpdateExceeded,
104 MaxStorageKeyBytesExceeded,
106 MaxStorageValueBytesExceeded,
108 }
109
110 #[pallet::event]
111 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
112 pub enum Event<T: Config> {
113 GraduallyUpdateAdded {
115 key: StorageKeyBytes<T>,
116 per_block: StorageValueBytes<T>,
117 target_value: StorageValueBytes<T>,
118 },
119 GraduallyUpdateCancelled { key: StorageKeyBytes<T> },
121 Updated {
123 block_number: BlockNumberFor<T>,
124 key: StorageKeyBytes<T>,
125 target_value: StorageValueBytes<T>,
126 },
127 }
128
129 #[pallet::storage]
131 #[pallet::getter(fn gradually_updates)]
132 pub(crate) type GraduallyUpdates<T: Config> =
133 StorageValue<_, BoundedVec<GraduallyUpdateOf<T>, T::MaxGraduallyUpdate>, ValueQuery>;
134
135 #[pallet::storage]
137 #[pallet::getter(fn last_updated_at)]
138 pub(crate) type LastUpdatedAt<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
139
140 #[pallet::pallet]
141 pub struct Pallet<T>(_);
142
143 #[pallet::hooks]
144 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
145 fn on_initialize(now: BlockNumberFor<T>) -> Weight {
147 if Self::_need_update(now) {
148 T::WeightInfo::on_finalize(GraduallyUpdates::<T>::get().len() as u32)
149 } else {
150 Weight::zero()
151 }
152 }
153
154 fn on_finalize(now: BlockNumberFor<T>) {
156 Self::_on_finalize(now);
157 }
158 }
159
160 #[pallet::call]
161 impl<T: Config> Pallet<T> {
162 #[pallet::call_index(0)]
164 #[pallet::weight(T::WeightInfo::gradually_update())]
165 pub fn gradually_update(origin: OriginFor<T>, update: GraduallyUpdateOf<T>) -> DispatchResult {
166 T::DispatchOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;
167
168 ensure!(
170 update.per_block.len() == update.target_value.len() && update.per_block.len() <= 16,
171 Error::<T>::InvalidPerBlockOrTargetValue
172 );
173
174 if storage::unhashed::exists(&update.key) {
175 let current_value = storage::unhashed::get::<StorageValueBytes<T>>(&update.key).unwrap();
176 ensure!(
177 current_value.len() == update.target_value.len(),
178 Error::<T>::InvalidTargetValue
179 );
180 }
181
182 GraduallyUpdates::<T>::try_mutate(|gradually_updates| -> DispatchResult {
183 ensure!(
184 !gradually_updates.contains(&update),
185 Error::<T>::GraduallyUpdateHasExisted
186 );
187
188 gradually_updates
189 .try_push(update.clone())
190 .map_err(|_| Error::<T>::MaxGraduallyUpdateExceeded)?;
191
192 Ok(())
193 })?;
194
195 Self::deposit_event(Event::GraduallyUpdateAdded {
196 key: update.key,
197 per_block: update.per_block,
198 target_value: update.target_value,
199 });
200 Ok(())
201 }
202
203 #[pallet::call_index(1)]
205 #[pallet::weight(T::WeightInfo::cancel_gradually_update())]
206 pub fn cancel_gradually_update(origin: OriginFor<T>, key: StorageKeyBytes<T>) -> DispatchResult {
207 T::DispatchOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;
208
209 GraduallyUpdates::<T>::try_mutate(|gradually_updates| -> DispatchResult {
210 let old_len = gradually_updates.len();
211 gradually_updates.retain(|item| item.key != key);
212
213 ensure!(gradually_updates.len() != old_len, Error::<T>::GraduallyUpdateNotFound);
214
215 Ok(())
216 })?;
217
218 Self::deposit_event(Event::GraduallyUpdateCancelled { key });
219 Ok(())
220 }
221 }
222}
223
224impl<T: Config> Pallet<T> {
225 fn _need_update(now: BlockNumberFor<T>) -> bool {
226 now >= Self::last_updated_at().saturating_add(T::UpdateFrequency::get())
227 }
228
229 fn _on_finalize(now: BlockNumberFor<T>) {
230 if !Self::_need_update(now) {
231 return;
232 }
233
234 let mut gradually_updates = GraduallyUpdates::<T>::get();
235 let initial_count = gradually_updates.len();
236
237 gradually_updates.retain(|update| {
238 let mut keep = true;
239 let current_value = storage::unhashed::get::<StorageValueBytes<T>>(&update.key).unwrap_or_default();
240 let current_value_u128 = u128::from_le_bytes(Self::convert_vec_to_u8(¤t_value));
241
242 let frequency_u128: u128 = T::UpdateFrequency::get().saturated_into();
243
244 let step = u128::from_le_bytes(Self::convert_vec_to_u8(&update.per_block));
245 let step_u128 = step.checked_mul(frequency_u128).unwrap();
246
247 let target_u128 = u128::from_le_bytes(Self::convert_vec_to_u8(&update.target_value));
248
249 let new_value_u128 = if current_value_u128 > target_u128 {
250 (current_value_u128.checked_sub(step_u128).unwrap()).max(target_u128)
251 } else {
252 (current_value_u128.checked_add(step_u128).unwrap()).min(target_u128)
253 };
254
255 if new_value_u128 == target_u128 {
257 keep = false;
258 }
259
260 let mut value = new_value_u128.encode();
261 value.truncate(update.target_value.len());
262
263 storage::unhashed::put(&update.key, &value);
264
265 let bounded_value: StorageValueBytes<T> = value.to_vec().try_into().unwrap();
266
267 Self::deposit_event(Event::Updated {
268 block_number: now,
269 key: update.key.clone(),
270 target_value: bounded_value,
271 });
272
273 keep
274 });
275
276 if gradually_updates.len() < initial_count {
278 GraduallyUpdates::<T>::put(gradually_updates);
279 }
280
281 LastUpdatedAt::<T>::put(now);
282 }
283
284 #[allow(clippy::ptr_arg)]
285 fn convert_vec_to_u8(input: &StorageValueBytes<T>) -> [u8; 16] {
286 let mut array: [u8; 16] = [0; 16];
287 for (i, v) in input.iter().enumerate() {
288 array[i] = *v;
289 }
290 array
291 }
292}