#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::string_lit_as_bytes)]
use codec::{Decode, Encode};
use frame_support::{
decl_error, decl_event, decl_module, decl_storage, ensure, storage,
traits::{EnsureOrigin, Get},
weights::Weight,
};
use frame_system::ensure_root;
use sp_runtime::{traits::SaturatedConversion, DispatchResult, RuntimeDebug};
use sp_std::prelude::Vec;
mod default_weight;
mod mock;
mod tests;
pub trait WeightInfo {
fn gradually_update() -> Weight;
fn cancel_gradually_update() -> Weight;
fn on_finalize(u: u32) -> Weight;
}
type StorageKey = Vec<u8>;
type StorageValue = Vec<u8>;
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
pub struct GraduallyUpdate {
pub key: StorageKey,
pub target_value: StorageValue,
pub per_block: StorageValue,
}
pub trait Trait: frame_system::Trait {
type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
type UpdateFrequency: Get<Self::BlockNumber>;
type DispatchOrigin: EnsureOrigin<Self::Origin>;
type WeightInfo: WeightInfo;
}
decl_storage! {
trait Store for Module<T: Trait> as GraduallyUpdate {
pub GraduallyUpdates get(fn gradually_updates): Vec<GraduallyUpdate>;
pub LastUpdatedAt get(fn last_updated_at): T::BlockNumber;
}
}
decl_event!(
pub enum Event<T> where
<T as frame_system::Trait>::BlockNumber,
{
GraduallyUpdateAdded(StorageKey, StorageValue, StorageValue),
GraduallyUpdateCancelled(StorageKey),
Updated(BlockNumber, StorageKey, StorageValue),
}
);
decl_error! {
pub enum Error for Module<T: Trait> {
InvalidPerBlockOrTargetValue,
InvalidTargetValue,
GraduallyUpdateHasExisted,
GraduallyUpdateNotFound,
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
const UpdateFrequency: T::BlockNumber = T::UpdateFrequency::get();
#[weight = T::WeightInfo::gradually_update()]
pub fn gradually_update(origin, update: GraduallyUpdate) {
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::<StorageValue>(&update.key).unwrap();
ensure!(current_value.len() == update.target_value.len(), Error::<T>::InvalidTargetValue);
}
GraduallyUpdates::try_mutate(|gradually_updates| -> DispatchResult {
ensure!(!gradually_updates.contains(&update), Error::<T>::GraduallyUpdateHasExisted);
gradually_updates.push(update.clone());
Ok(())
})?;
Self::deposit_event(RawEvent::GraduallyUpdateAdded(update.key, update.per_block, update.target_value));
}
/// Cancel gradually_update to adjust numeric parameter.
#[weight = T::WeightInfo::cancel_gradually_update()]
pub fn cancel_gradually_update(origin, key: StorageKey) {
T::DispatchOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?;
GraduallyUpdates::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(RawEvent::GraduallyUpdateCancelled(key));
}
/// `on_initialize` to return the weight used in `on_finalize`.
fn on_initialize() -> Weight {
let now = <frame_system::Module<T>>::block_number();
if Self::_need_update(now) {
T::WeightInfo::on_finalize(GraduallyUpdates::get().len() as u32)
} else {
0
}
}
/// Update gradually_update to adjust numeric parameter.
fn on_finalize(now: T::BlockNumber) {
Self::_on_finalize(now);
}
}
}
impl<T: Trait> Module<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::get();
let initial_count = gradually_updates.len();
gradually_updates.retain(|update| {
let mut keep = true;
let current_value = storage::unhashed::get::<StorageValue>(&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)
};
// current_value equal target_value, remove gradually_update
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(RawEvent::Updated(now, update.key.clone(), value));
keep
});
// gradually_update has finished. Remove it from GraduallyUpdates.
if gradually_updates.len() < initial_count {
GraduallyUpdates::put(gradually_updates);
}
LastUpdatedAt::<T>::put(now);
}
#[allow(clippy::ptr_arg)]
fn convert_vec_to_u8(input: &StorageValue) -> [u8; 16] {
let mut array: [u8; 16] = [0; 16];
for (i, v) in input.iter().enumerate() {
array[i] = *v;
}
array
}
}