From 7d2b10ebf2fbfc2ffe49f5771414fa31c986e310 Mon Sep 17 00:00:00 2001
From: zjb0807 <zjb0807@qq.com>
Date: Thu, 5 Mar 2020 15:48:39 +0800
Subject: [PATCH] Feature/schedule update (#108)

* add schedule-update

* add unit test

* fix schedule-update/Cargo.toml

* add try_for_each

* add next_block_number
---
 Cargo.dev.toml               |   3 +-
 auction/Cargo.toml           |   3 +
 schedule-update/Cargo.toml   |  31 ++++
 schedule-update/src/lib.rs   | 224 +++++++++++++++++++++++
 schedule-update/src/mock.rs  | 125 +++++++++++++
 schedule-update/src/tests.rs | 344 +++++++++++++++++++++++++++++++++++
 6 files changed, 729 insertions(+), 1 deletion(-)
 create mode 100644 schedule-update/Cargo.toml
 create mode 100644 schedule-update/src/lib.rs
 create mode 100644 schedule-update/src/mock.rs
 create mode 100644 schedule-update/src/tests.rs

diff --git a/Cargo.dev.toml b/Cargo.dev.toml
index 39e5d76..e21c9c9 100644
--- a/Cargo.dev.toml
+++ b/Cargo.dev.toml
@@ -9,4 +9,5 @@ members = [
 	"auction",
 	"vesting",
 	"gradually-update",
-]
+	"schedule-update",
+]
\ No newline at end of file
diff --git a/auction/Cargo.toml b/auction/Cargo.toml
index cfb695a..5b4bd1c 100644
--- a/auction/Cargo.toml
+++ b/auction/Cargo.toml
@@ -23,6 +23,9 @@ primitives = { package = "sp-core",  version = "2.0.0-alpha.3", default-features
 clear_on_drop = { version = "0.2.3", features = ["no_cc"] }	# https://github.com/paritytech/substrate/issues/4179
 quote = "=1.0.2" # https://github.com/rust-lang-nursery/failure/issues/342
 
+# CI failed. Caused by issue: https://github.com/withoutboats/failure_derive/issues/13
+quote = "=1.0.2"
+
 [features]
 default = ["std"]
 std = [
diff --git a/schedule-update/Cargo.toml b/schedule-update/Cargo.toml
new file mode 100644
index 0000000..b52227f
--- /dev/null
+++ b/schedule-update/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "orml-schedule-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-std = { git = "https://github.com/paritytech/substrate.git", default-features = false }
+sp-runtime = { git = "https://github.com/paritytech/substrate.git", default-features = false }
+
+[dev-dependencies]
+sp-io = { git = "https://github.com/paritytech/substrate.git", default-features = false }
+sp-core = { git = "https://github.com/paritytech/substrate.git", default-features = false }
+pallet-balances = { git = "https://github.com/paritytech/substrate.git", default-features = false }
+
+
+[features]
+default = ["std"]
+std = [
+	"codec/std",
+	"frame-support/std",
+	"frame-system/std",
+	"sp-std/std",
+	"sp-runtime/std",
+	"pallet-balances/std",
+]
diff --git a/schedule-update/src/lib.rs b/schedule-update/src/lib.rs
new file mode 100644
index 0000000..606ed0c
--- /dev/null
+++ b/schedule-update/src/lib.rs
@@ -0,0 +1,224 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use codec::{Decode, Encode};
+use frame_support::{
+	decl_error, decl_event, decl_module, decl_storage,
+	dispatch::Weight,
+	ensure,
+	traits::Get,
+	weights::{DispatchClass, GetDispatchInfo},
+	Parameter,
+};
+use frame_system::{self as system, ensure_root, ensure_signed};
+use sp_runtime::{
+	traits::{CheckedAdd, Dispatchable, One},
+	DispatchError, RuntimeDebug,
+};
+use sp_std::{prelude::*, result};
+
+mod mock;
+mod tests;
+
+#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)]
+pub enum DelayedDispatchTime<BlockNumber> {
+	At(BlockNumber),
+	After(BlockNumber),
+}
+
+type DispatchId = u32;
+type CallOf<T> = <T as Trait>::Call;
+
+pub trait Trait: frame_system::Trait {
+	type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
+	type Call: Parameter + Default + Dispatchable<Origin = <Self as frame_system::Trait>::Origin> + GetDispatchInfo;
+	type MaxScheduleDispatchWeight: Get<Weight>;
+}
+
+decl_event!(
+	/// Event for schedule-update module.
+	pub enum Event<T> where
+		<T as frame_system::Trait>::BlockNumber,
+	{
+		/// Add schedule dispatch success (BlockNumber, DispatchId)
+		ScheduleDispatch(BlockNumber, DispatchId),
+		/// Cancel deplayed dispatch success (DispatchId)
+		CancelDeplayedDispatch(DispatchId),
+		/// Schedule dispatch success (BlockNumber, DispatchId)
+		ScheduleDispatchSuccess(BlockNumber, DispatchId),
+		/// Schedule dispatch failed (DispatchId, DispatchError)
+		ScheduleDispatchFail(DispatchId, DispatchError),
+	}
+);
+
+decl_error! {
+	/// Error for schedule-update module.
+	pub enum Error for Module<T: Trait> {
+		BadOrigin,
+		InvalidDelayedDispatchTime,
+		CannotGetNextId,
+		NoPermission,
+		DispatchNotExisted,
+		BlockNumberOverflow,
+		ExceedMaxScheduleDispatchWeight,
+	}
+}
+
+decl_storage! {
+	trait Store for Module<T: Trait> as ScheduleUpdate {
+		pub NextId get(fn next_id): DispatchId;
+		pub DelayedNormalDispatches get(fn delayed_normal_dispatches): double_map hasher(twox_64_concat) T::BlockNumber, hasher(twox_64_concat) DispatchId => (Option<T::AccountId>, CallOf<T>, DispatchId);
+		pub DelayedOperationalDispatches get(fn delayed_operational_dispatches): double_map hasher(twox_64_concat) T::BlockNumber, hasher(twox_64_concat) DispatchId => (Option<T::AccountId>, CallOf<T>, DispatchId);
+	}
+}
+
+decl_module! {
+	pub struct Module<T: Trait> for enum Call where origin: T::Origin {
+		type Error = Error<T>;
+
+		fn deposit_event() = default;
+
+		const MaxScheduleDispatchWeight: Weight = T::MaxScheduleDispatchWeight::get();
+
+		/// Add schedule_update at block_number
+		pub fn schedule_dispatch(origin, call: CallOf<T>, when: DelayedDispatchTime<T::BlockNumber>) {
+			let who = match origin.into() {
+				Ok(frame_system::RawOrigin::Root) => None,
+				Ok(frame_system::RawOrigin::Signed(t)) => Some(t),
+				_ => return Err(Error::<T>::BadOrigin.into())
+			};
+
+			let now = <frame_system::Module<T>>::block_number();
+			let block_number = match when {
+				DelayedDispatchTime::At(block_number) => {
+					ensure!(block_number > now, Error::<T>::InvalidDelayedDispatchTime);
+					block_number
+				},
+				DelayedDispatchTime::After(block_count) => {
+					now.checked_add(&block_count).ok_or(Error::<T>::BlockNumberOverflow)?
+				},
+			};
+
+			let id = Self::_get_next_id()?;
+
+			match call.get_dispatch_info().class {
+				DispatchClass::Normal => {
+					<DelayedNormalDispatches<T>>::insert(block_number, id, (who, call, id));
+				},
+				DispatchClass::Operational => {
+					<DelayedOperationalDispatches<T>>::insert(block_number, id, (who, call, id));
+				},
+			}
+			Self::deposit_event(RawEvent::ScheduleDispatch(block_number, id));
+		}
+
+		/// Cancel schedule_update
+		pub fn cancel_deplayed_dispatch(origin, at: T::BlockNumber, id: DispatchId) {
+			let is_root = ensure_root(origin.clone()).is_ok();
+
+			if <DelayedNormalDispatches<T>>::contains_key(at, id) {
+				if !is_root {
+					let w = ensure_signed(origin)?;
+					let (who, _, _) = <DelayedNormalDispatches<T>>::get(at, id);
+					if Some(w) != who {
+						return Err(Error::<T>::NoPermission.into());
+					}
+				}
+				<DelayedNormalDispatches<T>>::remove(at, id);
+			} else if <DelayedOperationalDispatches<T>>::contains_key(at, id) {
+				if !is_root {
+					let w = ensure_signed(origin)?;
+					let (who, _, _) = <DelayedOperationalDispatches<T>>::get(at, id);
+					if Some(w) != who {
+						return Err(Error::<T>::NoPermission.into());
+					}
+				}
+				<DelayedOperationalDispatches<T>>::remove(at, id);
+			} else {
+				return Err(Error::<T>::DispatchNotExisted.into());
+			}
+			Self::deposit_event(RawEvent::CancelDeplayedDispatch(id));
+		}
+
+		fn on_initialize(now: T::BlockNumber) {
+			let mut weight: Weight = 0;
+			let total_weight = T::MaxScheduleDispatchWeight::get();
+			let next_block_number = match now.checked_add(&One::one()) {
+				Some(block_number) => block_number,
+				_ => return
+			};
+
+			// Operational calls are dispatched first and then normal calls
+			// TODO: dispatches should be sorted
+			let mut operational_dispatches = <DelayedOperationalDispatches<T>>::iter_prefix(now);
+			let _ = operational_dispatches.try_for_each(|(who, call, id)| {
+				weight += call.get_dispatch_info().weight;
+				if weight > total_weight {
+					return Err(Error::<T>::ExceedMaxScheduleDispatchWeight);
+				}
+
+				let origin: T::Origin;
+				if let Some(w) = who {
+					origin = frame_system::RawOrigin::Signed(w).into();
+				} else {
+					origin = frame_system::RawOrigin::Root.into();
+				}
+
+				let result = call.dispatch(origin.clone());
+				if let Err(e) = result {
+					 Self::deposit_event(RawEvent::ScheduleDispatchFail(id, e));
+				} else {
+					 Self::deposit_event(RawEvent::ScheduleDispatchSuccess(now, id));
+				}
+				<DelayedOperationalDispatches<T>>::remove(now, id);
+				Ok(())
+			});
+
+			let mut normal_dispatches = <DelayedNormalDispatches<T>>::iter_prefix(now);
+			let _ = normal_dispatches.try_for_each(|(who, call, id)| {
+				weight += call.get_dispatch_info().weight;
+				if weight > total_weight {
+					return Err(Error::<T>::ExceedMaxScheduleDispatchWeight);
+				}
+
+				let origin: T::Origin;
+				if let Some(w) = who {
+					origin = frame_system::RawOrigin::Signed(w).into();
+				} else {
+					origin = frame_system::RawOrigin::Root.into();
+				}
+
+				let result = call.dispatch(origin.clone());
+				if let Err(e) = result {
+					Self::deposit_event(RawEvent::ScheduleDispatchFail(id, e));
+				} else {
+					Self::deposit_event(RawEvent::ScheduleDispatchSuccess(now, id));
+				}
+				<DelayedNormalDispatches<T>>::remove(now, id);
+				Ok(())
+			});
+
+			// Check Call dispatch weight and ensure they don't exceed MaxScheduleDispatchWeight
+			// Extra ones are moved to next block
+			let operational_dispatches = <DelayedOperationalDispatches<T>>::iter_prefix(now);
+			operational_dispatches.for_each(|(who, call, id)| {
+				<DelayedOperationalDispatches<T>>::insert(next_block_number, id, (who, call, id));
+				<DelayedOperationalDispatches<T>>::remove(now, id);
+			});
+
+			let normal_dispatches = <DelayedNormalDispatches<T>>::iter_prefix(now);
+			normal_dispatches.for_each(|(who, call, id)| {
+				<DelayedNormalDispatches<T>>::insert(next_block_number, id, (who, call, id));
+				<DelayedNormalDispatches<T>>::remove(now, id);
+			});
+		}
+	}
+}
+
+impl<T: Trait> Module<T> {
+	fn _get_next_id() -> result::Result<DispatchId, Error<T>> {
+		let id = Self::next_id();
+		let next_id = id.checked_add(One::one()).ok_or(Error::<T>::CannotGetNextId)?;
+		NextId::put(next_id);
+		Ok(id)
+	}
+}
diff --git a/schedule-update/src/mock.rs b/schedule-update/src/mock.rs
new file mode 100644
index 0000000..81831d6
--- /dev/null
+++ b/schedule-update/src/mock.rs
@@ -0,0 +1,125 @@
+//! Mocks for the schedule-update module.
+
+#![cfg(test)]
+
+use frame_support::{impl_outer_dispatch, 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 schedule_update {
+	pub use crate::Event;
+}
+
+impl_outer_event! {
+	pub enum TestEvent for Runtime {
+		frame_system<T>,
+		schedule_update<T>,
+		pallet_balances<T>,
+	}
+}
+
+impl_outer_dispatch! {
+	pub enum Call for Runtime where origin: Origin {
+		pallet_balances::Balances,
+	}
+}
+
+impl Default for Call {
+	fn default() -> Call {
+		Default::default()
+	}
+}
+
+// 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 = 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 = pallet_balances::AccountData<u128>;
+	type OnNewAccount = ();
+	type OnKilledAccount = ();
+}
+pub type System = frame_system::Module<Runtime>;
+
+parameter_types! {
+	pub const ExistentialDeposit: u64 = 1;
+}
+
+impl pallet_balances::Trait for Runtime {
+	type Balance = u128;
+	type DustRemoval = ();
+	type Event = TestEvent;
+	type ExistentialDeposit = ExistentialDeposit;
+	type AccountStore = System;
+}
+
+parameter_types! {
+	pub const MaxScheduleDispatchWeight: Weight = 2_000_000;
+}
+
+impl Trait for Runtime {
+	type Event = TestEvent;
+	type Call = Call;
+	type MaxScheduleDispatchWeight = MaxScheduleDispatchWeight;
+}
+pub type ScheduleUpdateModule = Module<Runtime>;
+
+pub type Balances = pallet_balances::Module<Runtime>;
+
+pub type BalancesCall = pallet_balances::Call<Runtime>;
+
+pub struct ExtBuilder;
+
+impl Default for ExtBuilder {
+	fn default() -> Self {
+		ExtBuilder
+	}
+}
+
+impl ExtBuilder {
+	pub fn build(self) -> sp_io::TestExternalities {
+		let mut t = frame_system::GenesisConfig::default()
+			.build_storage::<Runtime>()
+			.unwrap();
+
+		pallet_balances::GenesisConfig::<Runtime> {
+			balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)],
+		}
+		.assimilate_storage(&mut t)
+		.unwrap();
+
+		t.into()
+	}
+}
diff --git a/schedule-update/src/tests.rs b/schedule-update/src/tests.rs
new file mode 100644
index 0000000..0eee9dc
--- /dev/null
+++ b/schedule-update/src/tests.rs
@@ -0,0 +1,344 @@
+//! Unit tests for the gradually-update module.
+
+#![cfg(test)]
+
+use super::*;
+use frame_support::{assert_noop, assert_ok};
+use mock::{BalancesCall, Call, ExtBuilder, Origin, Runtime, ScheduleUpdateModule, System, TestEvent};
+use sp_runtime::traits::OnInitialize;
+
+#[test]
+fn schedule_dispatch_should_work() {
+	ExtBuilder::default().build().execute_with(|| {
+		// NormalDispatches
+		let call = Call::Balances(BalancesCall::transfer(2, 11));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(2)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(2, 0));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		// OperationalDispatches
+		let call = Call::Balances(BalancesCall::set_balance(1, 10, 11));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::ROOT,
+			call,
+			DelayedDispatchTime::After(3)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(4, 1));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+	});
+}
+
+#[test]
+fn schedule_dispatch_should_fail() {
+	ExtBuilder::default().build().execute_with(|| {
+		let call = Call::Balances(BalancesCall::transfer(2, 11));
+		assert_noop!(
+			ScheduleUpdateModule::schedule_dispatch(Origin::signed(1), call, DelayedDispatchTime::At(0)),
+			Error::<Runtime>::InvalidDelayedDispatchTime
+		);
+	});
+}
+
+#[test]
+fn cancel_deplayed_dispatch_should_work() {
+	ExtBuilder::default().build().execute_with(|| {
+		// NormalDispatches
+		let call = Call::Balances(BalancesCall::transfer(2, 11));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(2)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(2, 0));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		assert_ok!(ScheduleUpdateModule::cancel_deplayed_dispatch(Origin::signed(1), 2, 0));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::CancelDeplayedDispatch(0));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		// root cancel NormalDispatches
+		let call = Call::Balances(BalancesCall::transfer(2, 12));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::After(3)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(4, 1));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		assert_ok!(ScheduleUpdateModule::cancel_deplayed_dispatch(Origin::ROOT, 4, 1));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::CancelDeplayedDispatch(1));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		// OperationalDispatches
+		let call = Call::Balances(BalancesCall::set_balance(2, 10, 13));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::ROOT,
+			call,
+			DelayedDispatchTime::At(5)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(5, 2));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		assert_ok!(ScheduleUpdateModule::cancel_deplayed_dispatch(Origin::ROOT, 5, 2));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::CancelDeplayedDispatch(2));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+	});
+}
+
+#[test]
+fn cancel_deplayed_dispatch_should_fail() {
+	ExtBuilder::default().build().execute_with(|| {
+		assert_noop!(
+			ScheduleUpdateModule::cancel_deplayed_dispatch(Origin::signed(1), 2, 0),
+			Error::<Runtime>::DispatchNotExisted
+		);
+
+		// NormalDispatches
+		let call = Call::Balances(BalancesCall::transfer(2, 11));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(2)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(2, 0));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		assert_noop!(
+			ScheduleUpdateModule::cancel_deplayed_dispatch(Origin::signed(2), 2, 0),
+			Error::<Runtime>::NoPermission
+		);
+
+		// OperationalDispatches
+		let call = Call::Balances(BalancesCall::set_balance(2, 10, 13));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::ROOT,
+			call,
+			DelayedDispatchTime::At(5)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(5, 1));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		assert_noop!(
+			ScheduleUpdateModule::cancel_deplayed_dispatch(Origin::signed(2), 5, 1),
+			Error::<Runtime>::NoPermission
+		);
+	});
+}
+
+#[test]
+fn on_initialize_should_work() {
+	ExtBuilder::default().build().execute_with(|| {
+		// NormalDispatches
+		let call = Call::Balances(BalancesCall::transfer(2, 11));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(2)
+		));
+
+		let call = Call::Balances(BalancesCall::transfer(2, 12));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(3)
+		));
+
+		assert_eq!(System::events().len(), 7);
+		ScheduleUpdateModule::on_initialize(1);
+		assert_eq!(System::events().len(), 7);
+
+		ScheduleUpdateModule::on_initialize(2);
+		println!("{:?}", System::events());
+		assert_eq!(System::events().len(), 9);
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatchSuccess(2, 0));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		ScheduleUpdateModule::on_initialize(3);
+		assert_eq!(System::events().len(), 11);
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatchSuccess(3, 1));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		// OperationalDispatches
+		let call = Call::Balances(BalancesCall::set_balance(3, 10, 11));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::ROOT,
+			call,
+			DelayedDispatchTime::After(10)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(11, 2));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		let call = Call::Balances(BalancesCall::set_balance(3, 20, 21));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::ROOT,
+			call,
+			DelayedDispatchTime::After(12)
+		));
+
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatch(13, 3));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		assert_eq!(System::events().len(), 13);
+		ScheduleUpdateModule::on_initialize(10);
+		assert_eq!(System::events().len(), 13);
+
+		ScheduleUpdateModule::on_initialize(11);
+		println!("{:?}", System::events());
+		assert_eq!(System::events().len(), 15);
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatchSuccess(11, 2));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		ScheduleUpdateModule::on_initialize(13);
+		assert_eq!(System::events().len(), 17);
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatchSuccess(13, 3));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+	});
+}
+
+#[test]
+fn on_initialize_should_fail() {
+	ExtBuilder::default().build().execute_with(|| {
+		// NormalDispatches balance not enough
+		let call = Call::Balances(BalancesCall::transfer(2, 110));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(2)
+		));
+
+		assert_eq!(System::events().len(), 6);
+		ScheduleUpdateModule::on_initialize(1);
+		assert_eq!(System::events().len(), 6);
+
+		ScheduleUpdateModule::on_initialize(2);
+		println!("{:?}", System::events());
+		assert_eq!(System::events().len(), 7);
+		//TODO hold the error
+		let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatchFail(
+			0,
+			DispatchError::Module {
+				index: 0,
+				error: 3,
+				message: None,
+			},
+		));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+
+		// OperationalDispatches not root
+		let call = Call::Balances(BalancesCall::set_balance(3, 10, 11));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::After(10)
+		));
+
+		assert_eq!(System::events().len(), 8);
+		ScheduleUpdateModule::on_initialize(10);
+		assert_eq!(System::events().len(), 8);
+
+		ScheduleUpdateModule::on_initialize(11);
+		println!("{:?}", System::events());
+		assert_eq!(System::events().len(), 9);
+		let schedule_dispatch_event =
+			TestEvent::schedule_update(RawEvent::ScheduleDispatchFail(1, DispatchError::BadOrigin));
+		assert!(System::events()
+			.iter()
+			.any(|record| record.event == schedule_dispatch_event));
+	});
+}
+
+#[test]
+fn on_initialize_weight_exceed() {
+	ExtBuilder::default().build().execute_with(|| {
+		// NormalDispatches
+		let call = Call::Balances(BalancesCall::transfer(2, 11));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(2)
+		));
+
+		let call = Call::Balances(BalancesCall::transfer(2, 12));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(2)
+		));
+
+		let call = Call::Balances(BalancesCall::transfer(2, 13));
+		assert_ok!(ScheduleUpdateModule::schedule_dispatch(
+			Origin::signed(1),
+			call,
+			DelayedDispatchTime::At(2)
+		));
+
+		assert_eq!(System::events().len(), 8);
+		ScheduleUpdateModule::on_initialize(1);
+		assert_eq!(System::events().len(), 8);
+
+		ScheduleUpdateModule::on_initialize(2);
+		println!("{:?}", System::events());
+		assert_eq!(System::events().len(), 12);
+		// TODO on_initialize should be sorted
+		//let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatchSuccess(0, 2));
+		//assert!(System::events().iter().any(|record| record.event == schedule_dispatch_event));
+
+		//let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatchSuccess(2, 2));
+		//assert!(System::events().iter().any(|record| record.event == schedule_dispatch_event));
+
+		ScheduleUpdateModule::on_initialize(3);
+		assert_eq!(System::events().len(), 14);
+		//let schedule_dispatch_event = TestEvent::schedule_update(RawEvent::ScheduleDispatchSuccess(1, 3));
+		//assert!(System::events().iter().any(|record| record.event == schedule_dispatch_event));
+	});
+}
-- 
GitLab