--- /dev/null
+++ b/pallets/scheduler-v2/src/benchmarking.rs
@@ -0,0 +1,315 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see .
+
+// Original license:
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Scheduler pallet benchmarking.
+
+use super::*;
+use frame_benchmarking::{account, benchmarks};
+use frame_support::{
+ ensure,
+ traits::{schedule::Priority, PreimageRecipient},
+};
+use frame_system::RawOrigin;
+use sp_std::{prelude::*, vec};
+
+use crate::{Pallet as Scheduler, ScheduledCall, EncodedCall};
+use frame_system::Call as SystemCall;
+
+const SEED: u32 = 0;
+
+const BLOCK_NUMBER: u32 = 2;
+
+type SystemOrigin = ::Origin;
+
+/// Add `n` items to the schedule.
+///
+/// For `resolved`:
+/// - `
+/// - `None`: aborted (hash without preimage)
+/// - `Some(true)`: hash resolves into call if possible, plain call otherwise
+/// - `Some(false)`: plain call
+fn fill_schedule(when: T::BlockNumber, n: u32) -> Result<(), &'static str> {
+ let t = DispatchTime::At(when);
+ let origin: ::PalletsOrigin = frame_system::RawOrigin::Root.into();
+ for i in 0..n {
+ let call = make_call::(None);
+ let period = Some(((i + 100).into(), 100));
+ let name = u32_to_name(i);
+ Scheduler::::do_schedule_named(name, t, period, 0, origin.clone(), call)?;
+ }
+ ensure!(Agenda::::get(when).len() == n as usize, "didn't fill schedule");
+ Ok(())
+}
+
+fn u32_to_name(i: u32) -> TaskName {
+ i.using_encoded(blake2_256)
+}
+
+fn make_task(
+ periodic: bool,
+ named: bool,
+ signed: bool,
+ maybe_lookup_len: Option,
+ priority: Priority,
+) -> ScheduledOf {
+ let call = make_call::(maybe_lookup_len);
+ let maybe_periodic = match periodic {
+ true => Some((100u32.into(), 100)),
+ false => None,
+ };
+ let maybe_id = match named {
+ true => Some(u32_to_name(0)),
+ false => None,
+ };
+ let origin = make_origin::(signed);
+ Scheduled { maybe_id, priority, call, maybe_periodic, origin, _phantom: PhantomData }
+}
+
+fn bounded(len: u32) -> Option> {
+ let call =
+ <::Call>::from(SystemCall::remark { remark: vec![0; len as usize] });
+ ScheduledCall::new(call).ok()
+}
+
+fn make_call(maybe_lookup_len: Option) -> ScheduledCall {
+ let bound = EncodedCall::bound() as u32;
+ let mut len = match maybe_lookup_len {
+ Some(len) => len.min(>::MaxSize::get() - 2).max(bound) - 3,
+ None => bound.saturating_sub(4),
+ };
+
+ loop {
+ let c = match bounded::(len) {
+ Some(x) => x,
+ None => {
+ len -= 1;
+ continue
+ },
+ };
+ if c.lookup_needed() == maybe_lookup_len.is_some() {
+ break c
+ }
+ if maybe_lookup_len.is_some() {
+ len += 1;
+ } else {
+ if len > 0 {
+ len -= 1;
+ } else {
+ break c
+ }
+ }
+ }
+}
+
+fn make_origin(signed: bool) -> ::PalletsOrigin {
+ match signed {
+ true => frame_system::RawOrigin::Signed(account("origin", 0, SEED)).into(),
+ false => frame_system::RawOrigin::Root.into(),
+ }
+}
+
+fn dummy_counter() -> WeightCounter {
+ WeightCounter { used: Weight::zero(), limit: Weight::MAX }
+}
+
+benchmarks! {
+ // `service_agendas` when no work is done.
+ service_agendas_base {
+ let now = T::BlockNumber::from(BLOCK_NUMBER);
+ IncompleteSince::::put(now - One::one());
+ }: {
+ Scheduler::::service_agendas(&mut dummy_counter(), now, 0);
+ } verify {
+ assert_eq!(IncompleteSince::::get(), Some(now - One::one()));
+ }
+
+ // `service_agenda` when no work is done.
+ service_agenda_base {
+ let now = BLOCK_NUMBER.into();
+ let s in 0 .. T::MaxScheduledPerBlock::get();
+ fill_schedule::(now, s)?;
+ let mut executed = 0;
+ }: {
+ Scheduler::::service_agenda(&mut dummy_counter(), &mut executed, now, now, 0);
+ } verify {
+ assert_eq!(executed, 0);
+ }
+
+ // `service_task` when the task is a non-periodic, non-named, non-fetched call which is not
+ // dispatched (e.g. due to being overweight).
+ service_task_base {
+ let now = BLOCK_NUMBER.into();
+ let task = make_task::(false, false, false, None, 0);
+ // prevent any tasks from actually being executed as we only want the surrounding weight.
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() };
+ }: {
+ let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task);
+ } verify {
+ //assert_eq!(result, Ok(()));
+ }
+
+ // `service_task` when the task is a non-periodic, non-named, fetched call (with a known
+ // preimage length) and which is not dispatched (e.g. due to being overweight).
+ service_task_fetched {
+ let s in (EncodedCall::bound() as u32) .. (>::MaxSize::get());
+ let now = BLOCK_NUMBER.into();
+ let task = make_task::(false, false, false, Some(s), 0);
+ // prevent any tasks from actually being executed as we only want the surrounding weight.
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() };
+ }: {
+ let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task);
+ } verify {
+ }
+
+ // `service_task` when the task is a non-periodic, named, non-fetched call which is not
+ // dispatched (e.g. due to being overweight).
+ service_task_named {
+ let now = BLOCK_NUMBER.into();
+ let task = make_task::(false, true, false, None, 0);
+ // prevent any tasks from actually being executed as we only want the surrounding weight.
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() };
+ }: {
+ let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task);
+ } verify {
+ }
+
+ // `service_task` when the task is a periodic, non-named, non-fetched call which is not
+ // dispatched (e.g. due to being overweight).
+ service_task_periodic {
+ let now = BLOCK_NUMBER.into();
+ let task = make_task::(true, false, false, None, 0);
+ // prevent any tasks from actually being executed as we only want the surrounding weight.
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() };
+ }: {
+ let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task);
+ } verify {
+ }
+
+ // `execute_dispatch` when the origin is `Signed`, not counting the dispatable's weight.
+ execute_dispatch_signed {
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX };
+ let origin = make_origin::(true);
+ let call = T::Preimages::realize(&make_call::(None)).unwrap().0;
+ }: {
+ assert!(Scheduler::::execute_dispatch(&mut counter, origin, call).is_ok());
+ }
+ verify {
+ }
+
+ // `execute_dispatch` when the origin is not `Signed`, not counting the dispatable's weight.
+ execute_dispatch_unsigned {
+ let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX };
+ let origin = make_origin::(false);
+ let call = T::Preimages::realize(&make_call::(None)).unwrap().0;
+ }: {
+ assert!(Scheduler::::execute_dispatch(&mut counter, origin, call).is_ok());
+ }
+ verify {
+ }
+
+ schedule {
+ let s in 0 .. (T::MaxScheduledPerBlock::get() - 1);
+ let when = BLOCK_NUMBER.into();
+ let periodic = Some((T::BlockNumber::one(), 100));
+ let priority = 0;
+ // Essentially a no-op call.
+ let call = Box::new(SystemCall::set_storage { items: vec![] }.into());
+
+ fill_schedule::(when, s)?;
+ }: _(RawOrigin::Root, when, periodic, priority, call)
+ verify {
+ ensure!(
+ Agenda::::get(when).len() == (s + 1) as usize,
+ "didn't add to schedule"
+ );
+ }
+
+ cancel {
+ let s in 1 .. T::MaxScheduledPerBlock::get();
+ let when = BLOCK_NUMBER.into();
+
+ fill_schedule::(when, s)?;
+ assert_eq!(Agenda::::get(when).len(), s as usize);
+ let schedule_origin = T::ScheduleOrigin::successful_origin();
+ }: _>(schedule_origin, when, 0)
+ verify {
+ ensure!(
+ Lookup::::get(u32_to_name(0)).is_none(),
+ "didn't remove from lookup"
+ );
+ // Removed schedule is NONE
+ ensure!(
+ Agenda::::get(when)[0].is_none(),
+ "didn't remove from schedule"
+ );
+ }
+
+ schedule_named {
+ let s in 0 .. (T::MaxScheduledPerBlock::get() - 1);
+ let id = u32_to_name(s);
+ let when = BLOCK_NUMBER.into();
+ let periodic = Some((T::BlockNumber::one(), 100));
+ let priority = 0;
+ // Essentially a no-op call.
+ let call = Box::new(SystemCall::set_storage { items: vec![] }.into());
+
+ fill_schedule::(when, s)?;
+ }: _(RawOrigin::Root, id, when, periodic, priority, call)
+ verify {
+ ensure!(
+ Agenda::::get(when).len() == (s + 1) as usize,
+ "didn't add to schedule"
+ );
+ }
+
+ cancel_named {
+ let s in 1 .. T::MaxScheduledPerBlock::get();
+ let when = BLOCK_NUMBER.into();
+
+ fill_schedule::(when, s)?;
+ }: _(RawOrigin::Root, u32_to_name(0))
+ verify {
+ ensure!(
+ Lookup::::get(u32_to_name(0)).is_none(),
+ "didn't remove from lookup"
+ );
+ // Removed schedule is NONE
+ ensure!(
+ Agenda::::get(when)[0].is_none(),
+ "didn't remove from schedule"
+ );
+ }
+
+ // impl_benchmark_test_suite!(Scheduler, crate::mock::new_test_ext(), crate::mock::Test);
+}
--- a/pallets/scheduler-v2/src/lib.rs
+++ b/pallets/scheduler-v2/src/lib.rs
@@ -143,6 +143,14 @@
}
}
+ /// Returns whether the image will require a lookup to be peeked.
+ pub fn lookup_needed(&self) -> bool {
+ match self {
+ Self::Inline(_) => false,
+ Self::PreimageLookup { .. } => true,
+ }
+ }
+
fn decode(mut data: &[u8]) -> Result<::Call, DispatchError> {
::Call::decode(&mut data)
.map_err(|_| >::ScheduledCallCorrupted.into())
@@ -153,6 +161,11 @@
fn drop(call: &ScheduledCall);
fn peek(call: &ScheduledCall) -> Result<(::Call, Option), DispatchError>;
+
+ /// Convert the given scheduled `call` value back into its original instance. If successful,
+ /// `drop` any data backing it. This will not break the realisability of independently
+ /// created instances of `ScheduledCall` which happen to have identical data.
+ fn realize(call: &ScheduledCall) -> Result<(::Call, Option), DispatchError>;
}
impl> SchedulerPreimages for PP {
@@ -175,6 +188,12 @@
},
}
}
+
+ fn realize(call: &ScheduledCall) -> Result<(::Call, Option), DispatchError> {
+ let r = Self::peek(call)?;
+ Self::drop(call);
+ Ok(r)
+ }
}
pub type TaskName = [u8; 32];
--- a/runtime/opal/Cargo.toml
+++ b/runtime/opal/Cargo.toml
@@ -42,6 +42,7 @@
'pallet-inflation/runtime-benchmarks',
'pallet-app-promotion/runtime-benchmarks',
'pallet-unique-scheduler/runtime-benchmarks',
+ 'pallet-unique-scheduler-v2/runtime-benchmarks',
'pallet-xcm/runtime-benchmarks',
'sp-runtime/runtime-benchmarks',
'xcm-builder/runtime-benchmarks',