Skip to content
Snippets Groups Projects
Unverified Commit f58749cc authored by zjb0807's avatar zjb0807 Committed by GitHub
Browse files

Feature/gradually-update (#104)

* add schedule-update

* schedule-update add convert_vec_to_u8

* fix ci build

* remove sp_state_machine

* change OnReapAccount to OnKilledAccount
parent 3ac2286c
No related branches found
No related tags found
No related merge requests found
......@@ -8,4 +8,5 @@ members = [
"currencies",
"auction",
"vesting",
"gradually-update",
]
[package]
name = "orml-gradually-update"
version = "0.0.1"
authors = ["Laminar Developers <hello@laminar.one>"]
edition = "2018"
[dependencies]
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
frame-support = { git = "https://github.com/paritytech/substrate.git", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate.git", default-features = false }
sp-io = { git = "https://github.com/paritytech/substrate.git", default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate.git", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate.git", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate.git", default-features = false }
[dev-dependencies]
orml-utilities = { path = "../utilities", default-features = false }
[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"sp-io/std",
"sp-std/std",
"sp-core/std",
"sp-runtime/std",
]
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode};
use frame_support::{decl_error, decl_event, decl_module, decl_storage, ensure, storage, traits::Get};
use frame_system::{self as system, ensure_root};
use sp_runtime::{traits::SaturatedConversion, RuntimeDebug};
use sp_std::prelude::Vec;
mod mock;
mod tests;
type StorageKey = Vec<u8>;
type StorageValue = Vec<u8>;
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug)]
pub struct GraduallyUpdate {
key: StorageKey,
target_value: StorageValue,
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>;
}
decl_storage! {
trait Store for Module<T: Trait> as GraduallyUpdate {
pub GraduallyUpdates get(fn gradually_updates): Vec<GraduallyUpdate>;
pub GraduallyUpdateBlockNumber get(fn gradually_update_block_number): T::BlockNumber;
}
}
decl_event!(
/// Event for gradually-update module.
pub enum Event<T> where
<T as frame_system::Trait>::BlockNumber,
{
/// Add gradually_update success (key, per_block, target_value)
GraduallyUpdate(StorageKey, StorageValue, StorageValue),
/// Cancel gradually_update success (key)
CancelGraduallyUpdate(StorageKey),
/// Update gradually_update success (blocknum, key, target_value)
GraduallyUpdateBlockNumber(BlockNumber, StorageKey, StorageValue),
}
);
decl_error! {
/// Error for gradually-update module.
pub enum Error for Module<T: Trait> {
InvalidPerBlockOrTargetValue,
InvalidTargetValue,
GraduallyUpdateHasExisted,
CancelGradullyUpdateNotExisted,
}
}
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();
/// Add gradually_update to adjust numeric parameter. This is a root call.
pub fn gradually_update(origin, update: GraduallyUpdate) {
ensure_root(origin)?;
// Support max value is u128, ensure per_block and target_value <= 16 bytes.
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);
}
let mut gradually_updates = GraduallyUpdates::get();
ensure!(!gradually_updates.contains(&update), Error::<T>::GraduallyUpdateHasExisted);
gradually_updates.push(update.clone());
GraduallyUpdates::put(gradually_updates);
Self::deposit_event(RawEvent::GraduallyUpdate(update.key, update.per_block, update.target_value));
}
/// Cancel gradually_update to adjust numeric parameter. This is a root call.
pub fn cancel_gradually_update(origin, key: StorageKey) {
ensure_root(origin)?;
let gradually_updates: Vec<GraduallyUpdate> = GraduallyUpdates::get()
.into_iter()
.filter(|item| item.key != key)
.collect();
ensure!(GraduallyUpdates::decode_len().unwrap_or_default() - gradually_updates.len() == 1, Error::<T>::CancelGradullyUpdateNotExisted);
GraduallyUpdates::put(gradually_updates);
Self::deposit_event(RawEvent::CancelGraduallyUpdate(key));
}
/// Update gradually_update to adjust numeric parameter.
fn on_finalize(now: T::BlockNumber) {
Self::_on_finalize(now);
}
}
}
impl<T: Trait> Module<T> {
fn _on_finalize(now: T::BlockNumber) {
if now < GraduallyUpdateBlockNumber::<T>::get() + T::UpdateFrequency::get() {
return;
}
let mut gradually_updates = GraduallyUpdates::get();
for (i, update) in gradually_updates.clone().iter().enumerate() {
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(&current_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: u128;
if current_value_u128 > target_u128 {
new_value_u128 = (current_value_u128.checked_sub(step_u128).unwrap()).max(target_u128);
} else {
new_value_u128 = (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 {
gradually_updates.remove(i);
}
let mut value = new_value_u128.encode();
value.truncate(update.target_value.len());
storage::unhashed::put(&update.key, &value);
Self::deposit_event(RawEvent::GraduallyUpdateBlockNumber(now, update.key.clone(), value));
}
// gradually_update has finished. Remove it from GraduallyUpdates.
if gradually_updates.len() < GraduallyUpdates::decode_len().unwrap_or_default() {
GraduallyUpdates::put(gradually_updates);
}
GraduallyUpdateBlockNumber::<T>::put(now);
}
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.clone();
}
array
}
}
//! Mocks for the gradually-update module.
#![cfg(test)]
use frame_support::{impl_outer_event, impl_outer_origin, parameter_types};
use frame_system as system;
use sp_core::H256;
use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill};
use super::*;
impl_outer_origin! {
pub enum Origin for Runtime {}
}
mod gradually_update {
pub use crate::Event;
}
impl_outer_event! {
pub enum TestEvent for Runtime {
frame_system<T>,
gradually_update<T>,
}
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Runtime;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: u32 = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
pub type AccountId = u64;
pub type BlockNumber = u64;
impl frame_system::Trait for Runtime {
type Origin = Origin;
type Index = u64;
type BlockNumber = BlockNumber;
type Call = ();
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = TestEvent;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type MaximumBlockLength = MaximumBlockLength;
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
type ModuleToIndex = ();
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
}
pub type System = system::Module<Runtime>;
parameter_types! {
pub const UpdateFrequency: BlockNumber = 10;
}
impl Trait for Runtime {
type Event = TestEvent;
type UpdateFrequency = UpdateFrequency;
}
pub type GraduallyUpdateModule = Module<Runtime>;
pub struct ExtBuilder;
impl Default for ExtBuilder {
fn default() -> Self {
ExtBuilder
}
}
impl ExtBuilder {
pub fn build(self) -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::default()
.build_storage::<Runtime>()
.unwrap();
t.into()
}
}
//! Unit tests for the gradually-update module.
#![cfg(test)]
use super::*;
use frame_support::{assert_noop, assert_ok};
use mock::{ExtBuilder, GraduallyUpdateModule, Origin, Runtime, System, TestEvent};
use orml_utilities::FixedU128;
use sp_runtime::{traits::OnFinalize, Permill};
fn storage_set(key: &Vec<u8>, value: &Vec<u8>) {
storage::unhashed::put(key, value);
}
fn storage_get(key: &Vec<u8>) -> Vec<u8> {
storage::unhashed::get::<StorageValue>(key).unwrap_or_default()
}
#[test]
fn gradually_update_should_work() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: vec![9],
per_block: vec![1],
};
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
let gradually_update_event = TestEvent::gradually_update(RawEvent::GraduallyUpdate(
update.key,
update.per_block,
update.target_value,
));
assert!(System::events()
.iter()
.any(|record| record.event == gradually_update_event));
});
}
#[test]
fn gradually_update_should_fail() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: 9u32.encode(),
per_block: 1u64.encode(),
};
assert_noop!(
GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()),
Error::<Runtime>::InvalidPerBlockOrTargetValue
);
let update = GraduallyUpdate {
key: vec![1],
target_value: 90u32.encode(),
per_block: 1u32.encode(),
};
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
GraduallyUpdateModule::on_finalize(20);
let new_update = GraduallyUpdate {
key: vec![1],
target_value: 9u64.encode(),
per_block: 1u64.encode(),
};
assert_noop!(
GraduallyUpdateModule::gradually_update(Origin::ROOT, new_update.clone()),
Error::<Runtime>::InvalidTargetValue
);
assert_noop!(
GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()),
Error::<Runtime>::GraduallyUpdateHasExisted
);
});
}
#[test]
fn cancel_gradually_update_should_work() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: vec![9],
per_block: vec![1],
};
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
let gradually_update_event = TestEvent::gradually_update(RawEvent::GraduallyUpdate(
update.key.clone(),
update.per_block,
update.target_value,
));
assert!(System::events()
.iter()
.any(|record| record.event == gradually_update_event));
assert_ok!(GraduallyUpdateModule::cancel_gradually_update(
Origin::ROOT,
update.key.clone()
));
let cancel_gradually_update_event = TestEvent::gradually_update(RawEvent::CancelGraduallyUpdate(update.key));
assert!(System::events()
.iter()
.any(|record| record.event == cancel_gradually_update_event));
});
}
#[test]
fn cancel_gradually_update_should_fail() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: 9u32.encode(),
per_block: 1u32.encode(),
};
assert_noop!(
GraduallyUpdateModule::cancel_gradually_update(Origin::ROOT, update.key.clone()),
Error::<Runtime>::CancelGradullyUpdateNotExisted
);
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
assert_ok!(GraduallyUpdateModule::cancel_gradually_update(
Origin::ROOT,
update.key.clone()
));
});
}
#[test]
fn add_on_finalize_should_work() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: vec![30],
per_block: vec![1],
};
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
assert_eq!(storage_get(&update.key), vec![]);
GraduallyUpdateModule::on_finalize(10);
assert_eq!(storage_get(&update.key), vec![10]);
let gradually_update_blocknumber_event =
TestEvent::gradually_update(RawEvent::GraduallyUpdateBlockNumber(10, update.key.clone(), vec![10]));
assert!(System::events()
.iter()
.any(|record| record.event == gradually_update_blocknumber_event));
assert_eq!(System::events().len(), 2);
GraduallyUpdateModule::on_finalize(15);
assert_eq!(storage_get(&update.key), vec![10]);
assert_eq!(System::events().len(), 2);
GraduallyUpdateModule::on_finalize(20);
assert_eq!(storage_get(&update.key), vec![20]);
let gradually_update_blocknumber_event =
TestEvent::gradually_update(RawEvent::GraduallyUpdateBlockNumber(20, update.key.clone(), vec![20]));
assert!(System::events()
.iter()
.any(|record| record.event == gradually_update_blocknumber_event));
assert_eq!(System::events().len(), 3);
GraduallyUpdateModule::on_finalize(40);
assert_eq!(storage_get(&update.key), vec![30]);
let gradually_update_blocknumber_event =
TestEvent::gradually_update(RawEvent::GraduallyUpdateBlockNumber(40, update.key.clone(), vec![30]));
assert!(System::events()
.iter()
.any(|record| record.event == gradually_update_blocknumber_event));
});
}
#[test]
fn sub_on_finalize_should_work() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: vec![5],
per_block: vec![1],
};
storage_set(&update.key, &vec![30]);
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
assert_eq!(storage_get(&update.key), vec![30]);
GraduallyUpdateModule::on_finalize(10);
assert_eq!(storage_get(&update.key), vec![20]);
let gradually_update_blocknumber_event =
TestEvent::gradually_update(RawEvent::GraduallyUpdateBlockNumber(10, update.key.clone(), vec![20]));
assert!(System::events()
.iter()
.any(|record| record.event == gradually_update_blocknumber_event));
assert_eq!(System::events().len(), 2);
GraduallyUpdateModule::on_finalize(15);
assert_eq!(storage_get(&update.key), vec![20]);
assert_eq!(System::events().len(), 2);
GraduallyUpdateModule::on_finalize(20);
assert_eq!(storage_get(&update.key), vec![10]);
let gradually_update_blocknumber_event =
TestEvent::gradually_update(RawEvent::GraduallyUpdateBlockNumber(20, update.key.clone(), vec![10]));
assert!(System::events()
.iter()
.any(|record| record.event == gradually_update_blocknumber_event));
assert_eq!(System::events().len(), 3);
GraduallyUpdateModule::on_finalize(40);
assert_eq!(storage_get(&update.key), vec![5]);
let gradually_update_blocknumber_event =
TestEvent::gradually_update(RawEvent::GraduallyUpdateBlockNumber(40, update.key.clone(), vec![5]));
assert!(System::events()
.iter()
.any(|record| record.event == gradually_update_blocknumber_event));
});
}
#[test]
fn u32_should_work() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: 30u32.encode(),
per_block: 1u32.encode(),
};
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
assert_eq!(storage_get(&update.key), vec![]);
GraduallyUpdateModule::on_finalize(10);
assert_eq!(storage_get(&update.key), vec![10, 0, 0, 0]);
GraduallyUpdateModule::on_finalize(15);
assert_eq!(storage_get(&update.key), vec![10, 0, 0, 0]);
GraduallyUpdateModule::on_finalize(20);
assert_eq!(storage_get(&update.key), vec![20, 0, 0, 0]);
GraduallyUpdateModule::on_finalize(40);
assert_eq!(storage_get(&update.key), vec![30, 0, 0, 0]);
});
}
#[test]
fn u128_should_work() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: 30u128.encode(),
per_block: 1u128.encode(),
};
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
assert_eq!(storage_get(&update.key), vec![]);
GraduallyUpdateModule::on_finalize(10);
assert_eq!(
storage_get(&update.key),
vec![10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
);
GraduallyUpdateModule::on_finalize(15);
assert_eq!(
storage_get(&update.key),
vec![10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
);
GraduallyUpdateModule::on_finalize(20);
assert_eq!(
storage_get(&update.key),
vec![20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
);
GraduallyUpdateModule::on_finalize(40);
assert_eq!(
storage_get(&update.key),
vec![30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
);
});
}
#[test]
fn permill_should_work() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: Permill::from_percent(30).encode(),
per_block: Permill::from_percent(1).encode(),
};
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
assert_eq!(storage_get(&update.key), vec![]);
GraduallyUpdateModule::on_finalize(10);
assert_eq!(storage_get(&update.key), vec![160, 134, 1, 0]);
GraduallyUpdateModule::on_finalize(15);
assert_eq!(storage_get(&update.key), vec![160, 134, 1, 0]);
GraduallyUpdateModule::on_finalize(20);
assert_eq!(storage_get(&update.key), vec![64, 13, 3, 0]);
GraduallyUpdateModule::on_finalize(40);
assert_eq!(storage_get(&update.key), vec![224, 147, 4, 0]);
});
}
#[test]
fn fixedu128_should_work() {
ExtBuilder::default().build().execute_with(|| {
let update = GraduallyUpdate {
key: vec![1],
target_value: FixedU128::from_rational(30, 1).encode(),
per_block: FixedU128::from_rational(1, 1).encode(),
};
assert_ok!(GraduallyUpdateModule::gradually_update(Origin::ROOT, update.clone()));
assert_eq!(storage_get(&update.key), vec![]);
GraduallyUpdateModule::on_finalize(10);
assert_eq!(
storage_get(&update.key),
vec![0, 0, 232, 137, 4, 35, 199, 138, 0, 0, 0, 0, 0, 0, 0, 0]
);
GraduallyUpdateModule::on_finalize(15);
assert_eq!(
storage_get(&update.key),
vec![0, 0, 232, 137, 4, 35, 199, 138, 0, 0, 0, 0, 0, 0, 0, 0]
);
GraduallyUpdateModule::on_finalize(20);
assert_eq!(
storage_get(&update.key),
vec![0, 0, 208, 19, 9, 70, 142, 21, 1, 0, 0, 0, 0, 0, 0, 0]
);
GraduallyUpdateModule::on_finalize(40);
assert_eq!(
storage_get(&update.key),
vec![0, 0, 184, 157, 13, 105, 85, 160, 1, 0, 0, 0, 0, 0, 0, 0]
);
});
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment