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