#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::string_lit_as_bytes)]
#![allow(clippy::unused_unit)]
use frame_support::{
ensure,
pallet_prelude::*,
storage,
traits::{EnsureOrigin, Get},
};
use frame_system::pallet_prelude::*;
use sp_runtime::{traits::SaturatedConversion, DispatchResult, RuntimeDebug};
use sp_std::prelude::Vec;
mod default_weight;
mod mock;
mod tests;
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
pub struct GraduallyUpdate {
pub key: StorageKeyBytes,
pub target_value: StorageValueBytes,
pub per_block: StorageValueBytes,
}
pub use module::*;
#[frame_support::pallet]
pub mod module {
use super::*;
pub trait WeightInfo {
fn gradually_update() -> Weight;
fn cancel_gradually_update() -> Weight;
fn on_finalize(u: u32) -> Weight;
}
pub(crate) type StorageKeyBytes = Vec<u8>;
pub(crate) type StorageValueBytes = Vec<u8>;
#[pallet::config]
pub trait Config: frame_system::Config {
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
#[pallet::constant]
type UpdateFrequency: Get<Self::BlockNumber>;
type DispatchOrigin: EnsureOrigin<Self::Origin>;
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T> {
InvalidPerBlockOrTargetValue,
InvalidTargetValue,
GraduallyUpdateHasExisted,
GraduallyUpdateNotFound,
}
#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
GraduallyUpdateAdded(StorageKeyBytes, StorageValueBytes, StorageValueBytes),
GraduallyUpdateCancelled(StorageKeyBytes),
Updated(T::BlockNumber, StorageKeyBytes, StorageValueBytes),
}
#[pallet::storage]
#[pallet::getter(fn gradually_updates)]
pub(crate) type GraduallyUpdates<T: Config> = StorageValue<_, Vec<GraduallyUpdate>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn last_updated_at)]
pub(crate) type LastUpdatedAt<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
fn on_initialize(now: T::BlockNumber) -> Weight {
if Self::_need_update(now) {
T::WeightInfo::on_finalize(GraduallyUpdates::<T>::get().len() as u32)
} else {
0
}
}
fn on_finalize(now: T::BlockNumber) {
Self::_on_finalize(now);
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::WeightInfo::gradually_update())]
pub fn gradually_update(origin: OriginFor<T>, update: GraduallyUpdate) -> DispatchResultWithPostInfo {
T::DispatchOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;
ensure!(
update.per_block.len() == update.target_value.len() && update.per_block.len() <= 16,
Error::<T>::InvalidPerBlockOrTargetValue
);
if storage::unhashed::exists(&update.key) {
let current_value = storage::unhashed::get::<StorageValueBytes>(&update.key).unwrap();
ensure!(
current_value.len() == update.target_value.len(),
Error::<T>::InvalidTargetValue
);
}
GraduallyUpdates::<T>::try_mutate(|gradually_updates| -> DispatchResult {
ensure!(
!gradually_updates.contains(&update),
Error::<T>::GraduallyUpdateHasExisted
);
gradually_updates.push(update.clone());
Ok(())
})?;
Self::deposit_event(Event::GraduallyUpdateAdded(
update.key,
update.per_block,
update.target_value,
));
Ok(().into())
}
#[pallet::weight(T::WeightInfo::cancel_gradually_update())]
pub fn cancel_gradually_update(origin: OriginFor<T>, key: StorageKeyBytes) -> DispatchResultWithPostInfo {
T::DispatchOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;
GraduallyUpdates::<T>::try_mutate(|gradually_updates| -> DispatchResult {
let old_len = gradually_updates.len();
gradually_updates.retain(|item| item.key != key);
ensure!(gradually_updates.len() != old_len, Error::<T>::GraduallyUpdateNotFound);
Ok(())
})?;
Self::deposit_event(Event::GraduallyUpdateCancelled(key));
Ok(().into())
}
}
}
impl<T: Config> Pallet<T> {
fn _need_update(now: T::BlockNumber) -> bool {
now >= Self::last_updated_at() + T::UpdateFrequency::get()
}
fn _on_finalize(now: T::BlockNumber) {
if !Self::_need_update(now) {
return;
}
let mut gradually_updates = GraduallyUpdates::<T>::get();
let initial_count = gradually_updates.len();
gradually_updates.retain(|update| {
let mut keep = true;
let current_value = storage::unhashed::get::<StorageValueBytes>(&update.key).unwrap_or_default();
let current_value_u128 = u128::from_le_bytes(Self::convert_vec_to_u8(¤t_value));
let frequency_u128: u128 = T::UpdateFrequency::get().saturated_into();
let step = u128::from_le_bytes(Self::convert_vec_to_u8(&update.per_block));
let step_u128 = step.checked_mul(frequency_u128).unwrap();
let target_u128 = u128::from_le_bytes(Self::convert_vec_to_u8(&update.target_value));
let new_value_u128 = if current_value_u128 > target_u128 {
(current_value_u128.checked_sub(step_u128).unwrap()).max(target_u128)
} else {
(current_value_u128.checked_add(step_u128).unwrap()).min(target_u128)
};
if new_value_u128 == target_u128 {
keep = false;
}
let mut value = new_value_u128.encode();
value.truncate(update.target_value.len());
storage::unhashed::put(&update.key, &value);
Self::deposit_event(Event::Updated(now, update.key.clone(), value));
keep
});
if gradually_updates.len() < initial_count {
GraduallyUpdates::<T>::put(gradually_updates);
}
LastUpdatedAt::<T>::put(now);
}
#[allow(clippy::ptr_arg)]
fn convert_vec_to_u8(input: &StorageValueBytes) -> [u8; 16] {
let mut array: [u8; 16] = [0; 16];
for (i, v) in input.iter().enumerate() {
array[i] = *v;
}
array
}
}