1use alloc::vec;
21use frame_benchmarking::v2::*;
22use frame_support::{
23 ensure,
24 traits::{schedule::Priority, BoundedInline},
25 weights::WeightMeter,
26};
27use frame_system::{EventRecord, RawOrigin};
28
29use crate::*;
30
31type SystemCall<T> = frame_system::Call<T>;
32type SystemOrigin<T> = <T as frame_system::Config>::RuntimeOrigin;
33
34const SEED: u32 = 0;
35const BLOCK_NUMBER: u32 = 2;
36
37fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
38 let events = frame_system::Pallet::<T>::events();
39 let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
40 let EventRecord { event, .. } = &events[events.len() - 1];
42 assert_eq!(event, &system_event);
43}
44
45fn fill_schedule<T: Config>(when: BlockNumberFor<T>, n: u32) -> Result<(), &'static str> {
53 let t = DispatchTime::At(when);
54 let origin: <T as Config>::PalletsOrigin = frame_system::RawOrigin::Root.into();
55 for i in 0..n {
56 let call = make_call::<T>(None);
57 let period = Some(((i + 100).into(), 100));
58 let name = u32_to_name(i);
59 Pallet::<T>::do_schedule_named(name, t, period, 0, origin.clone(), call)?;
60 }
61 ensure!(Agenda::<T>::get(when).len() == n as usize, "didn't fill schedule");
62 Ok(())
63}
64
65fn fill_schedule_last_unnamed<T: Config>(
69 when: BlockNumberFor<T>,
70 n: u32,
71) -> Result<TaskAddress<BlockNumberFor<T>>, &'static str> {
72 ensure!(n > 0, "need at least one task");
73 fill_schedule::<T>(when, n - 1)?;
74 let origin: <T as Config>::PalletsOrigin = frame_system::RawOrigin::Root.into();
75 let call = make_call::<T>(None);
76 let period = Some(((n - 1 + 100).into(), 100));
77 let address = Pallet::<T>::do_schedule(DispatchTime::At(when), period, 0, origin, call)?;
78 ensure!(Agenda::<T>::get(when).len() == n as usize, "didn't fill schedule");
79 Ok(address)
80}
81
82fn u32_to_name(i: u32) -> TaskName {
83 i.using_encoded(blake2_256)
84}
85
86fn make_task<T: Config>(
87 periodic: bool,
88 named: bool,
89 signed: bool,
90 maybe_lookup_len: Option<u32>,
91 priority: Priority,
92) -> ScheduledOf<T> {
93 let call = make_call::<T>(maybe_lookup_len);
94 let maybe_periodic = match periodic {
95 true => Some((100u32.into(), 100)),
96 false => None,
97 };
98 let maybe_id = match named {
99 true => Some(u32_to_name(0)),
100 false => None,
101 };
102 let origin = make_origin::<T>(signed);
103 Scheduled { maybe_id, priority, call, maybe_periodic, origin, _phantom: PhantomData }
104}
105
106fn bounded<T: Config>(len: u32) -> Option<BoundedCallOf<T>> {
107 let call =
108 <<T as Config>::RuntimeCall>::from(SystemCall::remark { remark: vec![0; len as usize] });
109 T::Preimages::bound(call).ok()
110}
111
112fn make_call<T: Config>(maybe_lookup_len: Option<u32>) -> BoundedCallOf<T> {
113 let bound = BoundedInline::bound() as u32;
114 let mut len = match maybe_lookup_len {
115 Some(len) => len.min(T::Preimages::MAX_LENGTH as u32 - 2).max(bound) - 3,
116 None => bound.saturating_sub(4),
117 };
118
119 loop {
120 let c = match bounded::<T>(len) {
121 Some(x) => x,
122 None => {
123 len -= 1;
124 continue;
125 },
126 };
127 if c.lookup_needed() == maybe_lookup_len.is_some() {
128 break c;
129 }
130 if maybe_lookup_len.is_some() {
131 len += 1;
132 } else {
133 if len > 0 {
134 len -= 1;
135 } else {
136 break c;
137 }
138 }
139 }
140}
141
142fn make_origin<T: Config>(signed: bool) -> <T as Config>::PalletsOrigin {
143 match signed {
144 true => frame_system::RawOrigin::Signed(account("origin", 0, SEED)).into(),
145 false => frame_system::RawOrigin::Root.into(),
146 }
147}
148
149#[benchmarks]
150mod benchmarks {
151 use super::*;
152
153 #[benchmark]
155 fn service_agendas_base() {
156 let now = BLOCK_NUMBER.into();
157 IncompleteSince::<T>::put(now - One::one());
158
159 #[block]
160 {
161 Pallet::<T>::service_agendas(&mut WeightMeter::new(), now, 0);
162 }
163
164 assert_eq!(IncompleteSince::<T>::get(), Some(now - One::one()));
165 }
166
167 #[benchmark]
169 fn service_agenda_base(
170 s: Linear<0, { T::MaxScheduledPerBlock::get() }>,
171 ) -> Result<(), BenchmarkError> {
172 let now = BLOCK_NUMBER.into();
173 fill_schedule::<T>(now, s)?;
174 assert_eq!(Agenda::<T>::get(now).len() as u32, s);
175
176 #[block]
177 {
178 Pallet::<T>::service_agenda(&mut WeightMeter::new(), true, now, now, 0);
179 }
180
181 assert_eq!(Agenda::<T>::get(now).len() as u32, s);
182
183 Ok(())
184 }
185
186 #[benchmark]
189 fn service_task_base() {
190 let now = BLOCK_NUMBER.into();
191 let task = make_task::<T>(false, false, false, None, 0);
192 let mut counter = WeightMeter::with_limit(Weight::zero());
194 let _result;
195
196 #[block]
197 {
198 _result = Pallet::<T>::service_task(&mut counter, now, now, 0, true, task);
199 }
200
201 }
203
204 #[benchmark(pov_mode = MaxEncodedLen {
207 Preimage::PreimageFor: Measured
209 })]
210 fn service_task_fetched(
211 s: Linear<{ BoundedInline::bound() as u32 }, { T::Preimages::MAX_LENGTH as u32 }>,
212 ) {
213 let now = BLOCK_NUMBER.into();
214 let task = make_task::<T>(false, false, false, Some(s), 0);
215 let mut counter = WeightMeter::with_limit(Weight::zero());
217 let _result;
218
219 #[block]
220 {
221 _result = Pallet::<T>::service_task(&mut counter, now, now, 0, true, task);
222 }
223
224 }
226
227 #[benchmark]
230 fn service_task_named() {
231 let now = BLOCK_NUMBER.into();
232 let task = make_task::<T>(false, true, false, None, 0);
233 let mut counter = WeightMeter::with_limit(Weight::zero());
235 let _result;
236
237 #[block]
238 {
239 _result = Pallet::<T>::service_task(&mut counter, now, now, 0, true, task);
240 }
241
242 }
244
245 #[benchmark]
248 fn service_task_periodic() {
249 let now = BLOCK_NUMBER.into();
250 let task = make_task::<T>(true, false, false, None, 0);
251 let mut counter = WeightMeter::with_limit(Weight::zero());
253 let _result;
254
255 #[block]
256 {
257 _result = Pallet::<T>::service_task(&mut counter, now, now, 0, true, task);
258 }
259
260 }
262
263 #[benchmark]
265 fn execute_dispatch_signed() -> Result<(), BenchmarkError> {
266 let mut counter = WeightMeter::new();
267 let origin = make_origin::<T>(true);
268 let call = T::Preimages::realize(&make_call::<T>(None))?.0;
269 let result;
270
271 #[block]
272 {
273 result = Pallet::<T>::execute_dispatch(&mut counter, origin, call);
274 }
275
276 assert!(result.is_ok());
277
278 Ok(())
279 }
280
281 #[benchmark]
283 fn execute_dispatch_unsigned() -> Result<(), BenchmarkError> {
284 let mut counter = WeightMeter::new();
285 let origin = make_origin::<T>(false);
286 let call = T::Preimages::realize(&make_call::<T>(None))?.0;
287 let result;
288
289 #[block]
290 {
291 result = Pallet::<T>::execute_dispatch(&mut counter, origin, call);
292 }
293
294 assert!(result.is_ok());
295
296 Ok(())
297 }
298
299 #[benchmark]
300 fn schedule(
301 s: Linear<0, { T::MaxScheduledPerBlock::get() - 1 }>,
302 ) -> Result<(), BenchmarkError> {
303 let when = BLOCK_NUMBER.into();
304 let periodic = Some((BlockNumberFor::<T>::one(), 100));
305 let priority = 0;
306 let call = Box::new(SystemCall::set_storage { items: vec![] }.into());
308
309 fill_schedule::<T>(when, s)?;
310
311 #[extrinsic_call]
312 _(RawOrigin::Root, when, periodic, priority, call);
313
314 ensure!(Agenda::<T>::get(when).len() == s as usize + 1, "didn't add to schedule");
315
316 Ok(())
317 }
318
319 #[benchmark]
320 fn cancel(s: Linear<1, { T::MaxScheduledPerBlock::get() }>) -> Result<(), BenchmarkError> {
321 let when = BLOCK_NUMBER.into();
322
323 fill_schedule::<T>(when, s)?;
324 assert_eq!(Agenda::<T>::get(when).len(), s as usize);
325 let schedule_origin =
326 T::ScheduleOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
327
328 #[extrinsic_call]
329 _(schedule_origin as SystemOrigin<T>, when, 0);
330
331 ensure!(
332 s == 1 || Lookup::<T>::get(u32_to_name(0)).is_none(),
333 "didn't remove from lookup if more than 1 task scheduled for `when`"
334 );
335 ensure!(
337 s == 1 || Agenda::<T>::get(when)[0].is_none(),
338 "didn't remove from schedule if more than 1 task scheduled for `when`"
339 );
340 ensure!(
341 s > 1 || Agenda::<T>::get(when).len() == 0,
342 "remove from schedule if only 1 task scheduled for `when`"
343 );
344
345 Ok(())
346 }
347
348 #[benchmark]
349 fn schedule_named(
350 s: Linear<0, { T::MaxScheduledPerBlock::get() - 1 }>,
351 ) -> Result<(), BenchmarkError> {
352 let id = u32_to_name(s);
353 let when = BLOCK_NUMBER.into();
354 let periodic = Some((BlockNumberFor::<T>::one(), 100));
355 let priority = 0;
356 let call = Box::new(SystemCall::set_storage { items: vec![] }.into());
358
359 fill_schedule::<T>(when, s)?;
360
361 #[extrinsic_call]
362 _(RawOrigin::Root, id, when, periodic, priority, call);
363
364 ensure!(Agenda::<T>::get(when).len() == s as usize + 1, "didn't add to schedule");
365
366 Ok(())
367 }
368
369 #[benchmark]
370 fn cancel_named(
371 s: Linear<1, { T::MaxScheduledPerBlock::get() }>,
372 ) -> Result<(), BenchmarkError> {
373 let when = BLOCK_NUMBER.into();
374
375 fill_schedule::<T>(when, s)?;
376
377 #[extrinsic_call]
378 _(RawOrigin::Root, u32_to_name(0));
379
380 ensure!(
381 s == 1 || Lookup::<T>::get(u32_to_name(0)).is_none(),
382 "didn't remove from lookup if more than 1 task scheduled for `when`"
383 );
384 ensure!(
386 s == 1 || Agenda::<T>::get(when)[0].is_none(),
387 "didn't remove from schedule if more than 1 task scheduled for `when`"
388 );
389 ensure!(
390 s > 1 || Agenda::<T>::get(when).len() == 0,
391 "remove from schedule if only 1 task scheduled for `when`"
392 );
393
394 Ok(())
395 }
396
397 #[benchmark]
398 fn schedule_retry(
399 s: Linear<1, { T::MaxScheduledPerBlock::get() }>,
400 ) -> Result<(), BenchmarkError> {
401 let when = BLOCK_NUMBER.into();
402
403 fill_schedule::<T>(when, s)?;
404 let name = u32_to_name(s - 1);
405 let address = Lookup::<T>::get(name).unwrap();
406 let period: BlockNumberFor<T> = 1_u32.into();
407 let retry_config = RetryConfig { total_retries: 10, remaining: 10, period };
408 Retries::<T>::insert(address, retry_config);
409 let (mut when, index) = address;
410 let task = Agenda::<T>::get(when)[index as usize].clone().unwrap();
411 let mut weight_counter = WeightMeter::with_limit(T::MaximumWeight::get());
412
413 #[block]
414 {
415 Pallet::<T>::schedule_retry(
416 &mut weight_counter,
417 when,
418 when,
419 index,
420 &task,
421 retry_config,
422 );
423 }
424
425 when = when + BlockNumberFor::<T>::one();
426 assert_eq!(
427 Retries::<T>::get((when, 0)),
428 Some(RetryConfig { total_retries: 10, remaining: 9, period })
429 );
430
431 Ok(())
432 }
433
434 #[benchmark]
435 fn set_retry() -> Result<(), BenchmarkError> {
436 let s = T::MaxScheduledPerBlock::get();
437 let when = BLOCK_NUMBER.into();
438
439 let address = fill_schedule_last_unnamed::<T>(when, s)?;
441 let (when, index) = address;
442 let period = BlockNumberFor::<T>::one();
443
444 #[extrinsic_call]
445 _(RawOrigin::Root, (when, index), 10, period);
446
447 assert_eq!(
448 Retries::<T>::get((when, index)),
449 Some(RetryConfig { total_retries: 10, remaining: 10, period })
450 );
451 assert_last_event::<T>(
452 Event::RetrySet { task: address, id: None, period, retries: 10 }.into(),
453 );
454
455 Ok(())
456 }
457
458 #[benchmark]
459 fn set_retry_named() -> Result<(), BenchmarkError> {
460 let s = T::MaxScheduledPerBlock::get();
461 let when = BLOCK_NUMBER.into();
462
463 fill_schedule::<T>(when, s)?;
464 let name = u32_to_name(s - 1);
465 let address = Lookup::<T>::get(name).unwrap();
466 let (when, index) = address;
467 let period = BlockNumberFor::<T>::one();
468
469 #[extrinsic_call]
470 _(RawOrigin::Root, name, 10, period);
471
472 assert_eq!(
473 Retries::<T>::get((when, index)),
474 Some(RetryConfig { total_retries: 10, remaining: 10, period })
475 );
476 assert_last_event::<T>(
477 Event::RetrySet { task: address, id: Some(name), period, retries: 10 }.into(),
478 );
479
480 Ok(())
481 }
482
483 #[benchmark]
484 fn cancel_retry() -> Result<(), BenchmarkError> {
485 let s = T::MaxScheduledPerBlock::get();
486 let when = BLOCK_NUMBER.into();
487
488 fill_schedule::<T>(when, s)?;
489 let name = u32_to_name(s - 1);
490 let address = Lookup::<T>::get(name).unwrap();
491 let (when, index) = address;
492 let period = BlockNumberFor::<T>::one();
493 assert!(Pallet::<T>::set_retry_named(RawOrigin::Root.into(), name, 10, period).is_ok());
495
496 #[extrinsic_call]
497 _(RawOrigin::Root, (when, index));
498
499 assert!(!Retries::<T>::contains_key((when, index)));
500 assert_last_event::<T>(Event::RetryCancelled { task: address, id: None }.into());
501
502 Ok(())
503 }
504
505 #[benchmark]
506 fn cancel_retry_named() -> Result<(), BenchmarkError> {
507 let s = T::MaxScheduledPerBlock::get();
508 let when = BLOCK_NUMBER.into();
509
510 fill_schedule::<T>(when, s)?;
511 let name = u32_to_name(s - 1);
512 let address = Lookup::<T>::get(name).unwrap();
513 let (when, index) = address;
514 let period = BlockNumberFor::<T>::one();
515 assert!(Pallet::<T>::set_retry_named(RawOrigin::Root.into(), name, 10, period).is_ok());
516
517 #[extrinsic_call]
518 _(RawOrigin::Root, name);
519
520 assert!(!Retries::<T>::contains_key((when, index)));
521 assert_last_event::<T>(Event::RetryCancelled { task: address, id: Some(name) }.into());
522
523 Ok(())
524 }
525
526 impl_benchmark_test_suite! {
527 Pallet,
528 mock::new_test_ext(),
529 mock::Test
530 }
531}