1#![cfg(feature = "runtime-benchmarks")]
19
20use super::*;
21
22use crate::{CoreAssignment::Task, Pallet as Broker};
23use alloc::{vec, vec::Vec};
24use frame_benchmarking::v2::*;
25use frame_support::{
26 storage::bounded_vec::BoundedVec,
27 traits::{
28 fungible::{Inspect, Mutate},
29 EnsureOrigin, Hooks,
30 },
31};
32use frame_system::{Pallet as System, RawOrigin};
33use sp_arithmetic::{FixedU64, Perbill};
34use sp_core::Get;
35use sp_runtime::{
36 traits::{BlockNumberProvider, MaybeConvert},
37 FixedPointNumber, Saturating,
38};
39
40const SEED: u32 = 0;
41const MAX_CORE_COUNT: u16 = 1_000;
42
43fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
44 frame_system::Pallet::<T>::assert_last_event(generic_event.into());
45}
46
47fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
48 frame_system::Pallet::<T>::assert_has_event(generic_event.into());
49}
50
51fn new_config_record<T: Config>() -> ConfigRecordOf<T> {
52 ConfigRecord {
53 advance_notice: 2u32.into(),
54 interlude_length: 1u32.into(),
55 leadin_length: 1u32.into(),
56 ideal_bulk_proportion: Default::default(),
57 limit_cores_offered: None,
58 region_length: 3,
59 renewal_bump: Perbill::from_percent(10),
60 contribution_timeout: 5,
61 }
62}
63
64fn new_schedule() -> Schedule {
65 let mut items = Vec::new();
67 for i in 0..CORE_MASK_BITS {
68 items.push(ScheduleItem {
69 assignment: Task(i.try_into().unwrap()),
70 mask: CoreMask::complete(),
71 });
72 }
73 Schedule::truncate_from(items)
74}
75
76fn setup_reservations<T: Config>(n: u32) {
77 let schedule = new_schedule();
78
79 Reservations::<T>::put(BoundedVec::try_from(vec![schedule.clone(); n as usize]).unwrap());
80}
81
82fn setup_leases<T: Config>(n: u32, task: u32, until: u32) {
83 Leases::<T>::put(
84 BoundedVec::try_from(vec![LeaseRecordItem { task, until: until.into() }; n as usize])
85 .unwrap(),
86 );
87}
88
89fn advance_to<T: Config>(b: u32) {
90 while System::<T>::block_number() < b.into() {
91 System::<T>::set_block_number(System::<T>::block_number().saturating_add(1u32.into()));
92
93 let block_number: u32 = System::<T>::block_number().try_into().ok().unwrap();
94
95 RCBlockNumberProviderOf::<T::Coretime>::set_block_number(block_number.into());
96 Broker::<T>::on_initialize(System::<T>::block_number());
97 }
98}
99
100struct StartedSale<Balance> {
101 start_price: Balance,
102 end_price: Balance,
103 first_core: CoreIndex,
104}
105
106fn setup_and_start_sale<T: Config>() -> Result<StartedSale<BalanceOf<T>>, BenchmarkError> {
107 Configuration::<T>::put(new_config_record::<T>());
108
109 setup_reservations::<T>(T::MaxReservedCores::get());
111
112 setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
114
115 let initial_price = 10_000_000u32.into();
116 let (start_price, end_price) = get_start_end_price::<T>(initial_price);
117 Broker::<T>::do_start_sales(initial_price, MAX_CORE_COUNT.into())
118 .map_err(|_| BenchmarkError::Weightless)?;
119
120 let sale_data = StartedSale {
121 start_price,
122 end_price,
123 first_core: T::MaxReservedCores::get()
124 .saturating_add(T::MaxLeasedCores::get())
125 .try_into()
126 .unwrap(),
127 };
128
129 Ok(sale_data)
130}
131
132fn get_start_end_price<T: Config>(initial_price: BalanceOf<T>) -> (BalanceOf<T>, BalanceOf<T>) {
133 let end_price = <T as Config>::PriceAdapter::adapt_price(SalePerformance {
134 sellout_price: None,
135 end_price: initial_price,
136 ideal_cores_sold: 0,
137 cores_offered: 0,
138 cores_sold: 0,
139 })
140 .end_price;
141 let start_price = <T as Config>::PriceAdapter::leadin_factor_at(FixedU64::from(0))
142 .saturating_mul_int(end_price);
143 (start_price, end_price)
144}
145
146#[benchmarks]
147mod benches {
148 use super::*;
149 use crate::Finality::*;
150
151 #[benchmark]
152 fn configure() -> Result<(), BenchmarkError> {
153 let config = new_config_record::<T>();
154
155 let origin =
156 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
157
158 #[extrinsic_call]
159 _(origin as T::RuntimeOrigin, config.clone());
160
161 assert_eq!(Configuration::<T>::get(), Some(config));
162
163 Ok(())
164 }
165
166 #[benchmark]
167 fn reserve() -> Result<(), BenchmarkError> {
168 let schedule = new_schedule();
169
170 setup_reservations::<T>(T::MaxReservedCores::get().saturating_sub(1));
172
173 let origin =
174 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
175
176 #[extrinsic_call]
177 _(origin as T::RuntimeOrigin, schedule);
178
179 assert_eq!(Reservations::<T>::get().len(), T::MaxReservedCores::get() as usize);
180
181 Ok(())
182 }
183
184 #[benchmark]
185 fn unreserve() -> Result<(), BenchmarkError> {
186 setup_reservations::<T>(T::MaxReservedCores::get());
188
189 let origin =
190 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
191
192 #[extrinsic_call]
193 _(origin as T::RuntimeOrigin, 0);
194
195 assert_eq!(
196 Reservations::<T>::get().len(),
197 T::MaxReservedCores::get().saturating_sub(1) as usize
198 );
199
200 Ok(())
201 }
202
203 #[benchmark]
204 fn set_lease() -> Result<(), BenchmarkError> {
205 let task = 1u32;
206 let until = 10u32.into();
207
208 setup_leases::<T>(T::MaxLeasedCores::get().saturating_sub(1), task, until);
210
211 let origin =
212 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
213
214 #[extrinsic_call]
215 _(origin as T::RuntimeOrigin, task, until);
216
217 assert_eq!(Leases::<T>::get().len(), T::MaxLeasedCores::get() as usize);
218
219 Ok(())
220 }
221
222 #[benchmark]
223 fn remove_lease() -> Result<(), BenchmarkError> {
224 let task = 1u32;
225 let until = 10u32;
226
227 let mut leases = vec![
229 LeaseRecordItem { task, until };
230 T::MaxLeasedCores::get().saturating_sub(1) as usize
231 ];
232 let task = 2u32;
233 leases.push(LeaseRecordItem { task, until });
234 Leases::<T>::put(BoundedVec::try_from(leases).unwrap());
235
236 let origin =
237 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
238
239 #[extrinsic_call]
240 _(origin as T::RuntimeOrigin, task);
241
242 assert_eq!(Leases::<T>::get().len(), T::MaxLeasedCores::get().saturating_sub(1) as usize);
243
244 Ok(())
245 }
246
247 #[benchmark]
248 fn start_sales(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
249 let config = new_config_record::<T>();
250 Configuration::<T>::put(config.clone());
251
252 let mut extra_cores = n;
253
254 setup_reservations::<T>(extra_cores.min(T::MaxReservedCores::get()));
256 extra_cores = extra_cores.saturating_sub(T::MaxReservedCores::get());
257
258 setup_leases::<T>(extra_cores.min(T::MaxLeasedCores::get()), 1, 10);
260 extra_cores = extra_cores.saturating_sub(T::MaxLeasedCores::get());
261
262 let latest_region_begin = Broker::<T>::latest_timeslice_ready_to_commit(&config);
263
264 let initial_price = 10_000_000u32.into();
265 let (start_price, end_price) = get_start_end_price::<T>(initial_price);
266 let origin =
267 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
268
269 #[extrinsic_call]
270 _(origin as T::RuntimeOrigin, initial_price, extra_cores.try_into().unwrap());
271
272 assert!(SaleInfo::<T>::get().is_some());
273 let sale_start = RCBlockNumberProviderOf::<T::Coretime>::current_block_number() +
274 config.interlude_length;
275 assert_last_event::<T>(
276 Event::SaleInitialized {
277 sale_start,
278 leadin_length: 1u32.into(),
279 start_price,
280 end_price,
281 region_begin: latest_region_begin + config.region_length,
282 region_end: latest_region_begin + config.region_length * 2,
283 ideal_cores_sold: 0,
284 cores_offered: n
285 .saturating_sub(T::MaxReservedCores::get())
286 .saturating_sub(T::MaxLeasedCores::get())
287 .try_into()
288 .unwrap(),
289 }
290 .into(),
291 );
292
293 Ok(())
294 }
295
296 #[benchmark]
297 fn purchase() -> Result<(), BenchmarkError> {
298 let sale_data = setup_and_start_sale::<T>()?;
299
300 advance_to::<T>(2);
301
302 let caller: T::AccountId = whitelisted_caller();
303 T::Currency::set_balance(
304 &caller.clone(),
305 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
306 );
307
308 #[extrinsic_call]
309 _(RawOrigin::Signed(caller.clone()), sale_data.start_price);
310
311 assert_eq!(SaleInfo::<T>::get().unwrap().sellout_price.unwrap(), sale_data.end_price);
312 assert_last_event::<T>(
313 Event::Purchased {
314 who: caller,
315 region_id: RegionId {
316 begin: SaleInfo::<T>::get().unwrap().region_begin,
317 core: sale_data.first_core,
318 mask: CoreMask::complete(),
319 },
320 price: sale_data.end_price,
321 duration: 3u32.into(),
322 }
323 .into(),
324 );
325
326 Ok(())
327 }
328
329 #[benchmark]
330 fn renew() -> Result<(), BenchmarkError> {
331 let sale_data = setup_and_start_sale::<T>()?;
332 let region_len = Configuration::<T>::get().unwrap().region_length;
333
334 advance_to::<T>(2);
335
336 let caller: T::AccountId = whitelisted_caller();
337 T::Currency::set_balance(
338 &caller.clone(),
339 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
340 );
341
342 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
343 .expect("Offer not high enough for configuration.");
344
345 Broker::<T>::do_assign(region, None, 1001, Final)
346 .map_err(|_| BenchmarkError::Weightless)?;
347
348 advance_to::<T>((T::TimeslicePeriod::get() * region_len.into()).try_into().ok().unwrap());
349
350 #[extrinsic_call]
351 _(RawOrigin::Signed(caller), region.core);
352
353 let id = PotentialRenewalId { core: region.core, when: region.begin + region_len * 2 };
354 assert!(PotentialRenewals::<T>::get(id).is_some());
355
356 Ok(())
357 }
358
359 #[benchmark]
360 fn transfer() -> Result<(), BenchmarkError> {
361 let sale_data = setup_and_start_sale::<T>()?;
362
363 advance_to::<T>(2);
364
365 let caller: T::AccountId = whitelisted_caller();
366 T::Currency::set_balance(
367 &caller.clone(),
368 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
369 );
370
371 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
372 .expect("Offer not high enough for configuration.");
373
374 let recipient: T::AccountId = account("recipient", 0, SEED);
375
376 #[extrinsic_call]
377 _(RawOrigin::Signed(caller.clone()), region, recipient.clone());
378
379 assert_last_event::<T>(
380 Event::Transferred {
381 region_id: region,
382 old_owner: Some(caller),
383 owner: Some(recipient),
384 duration: 3u32.into(),
385 }
386 .into(),
387 );
388
389 Ok(())
390 }
391
392 #[benchmark]
393 fn partition() -> Result<(), BenchmarkError> {
394 let sale_data = setup_and_start_sale::<T>()?;
395
396 advance_to::<T>(2);
397
398 let caller: T::AccountId = whitelisted_caller();
399 T::Currency::set_balance(
400 &caller.clone(),
401 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
402 );
403
404 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
405 .expect("Offer not high enough for configuration.");
406
407 Broker::<T>::do_pool(region, None, caller.clone(), Finality::Provisional)
409 .map_err(|_| BenchmarkError::Weightless)?;
410
411 #[extrinsic_call]
412 _(RawOrigin::Signed(caller), region, 2);
413
414 assert_last_event::<T>(
415 Event::Partitioned {
416 old_region_id: RegionId {
417 begin: region.begin,
418 core: sale_data.first_core,
419 mask: CoreMask::complete(),
420 },
421 new_region_ids: (
422 RegionId {
423 begin: region.begin,
424 core: sale_data.first_core,
425 mask: CoreMask::complete(),
426 },
427 RegionId {
428 begin: region.begin + 2,
429 core: sale_data.first_core,
430 mask: CoreMask::complete(),
431 },
432 ),
433 }
434 .into(),
435 );
436
437 Ok(())
438 }
439
440 #[benchmark]
441 fn interlace() -> Result<(), BenchmarkError> {
442 let sale_data = setup_and_start_sale::<T>()?;
443 let core = sale_data.first_core;
444
445 advance_to::<T>(2);
446
447 let caller: T::AccountId = whitelisted_caller();
448 T::Currency::set_balance(
449 &caller.clone(),
450 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
451 );
452
453 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
454 .expect("Offer not high enough for configuration.");
455
456 Broker::<T>::do_pool(region, None, caller.clone(), Finality::Provisional)
458 .map_err(|_| BenchmarkError::Weightless)?;
459
460 #[extrinsic_call]
461 _(RawOrigin::Signed(caller), region, 0x00000_fffff_fffff_00000.into());
462
463 assert_last_event::<T>(
464 Event::Interlaced {
465 old_region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
466 new_region_ids: (
467 RegionId { begin: region.begin, core, mask: 0x00000_fffff_fffff_00000.into() },
468 RegionId {
469 begin: region.begin,
470 core,
471 mask: CoreMask::complete() ^ 0x00000_fffff_fffff_00000.into(),
472 },
473 ),
474 }
475 .into(),
476 );
477
478 Ok(())
479 }
480
481 #[benchmark]
482 fn assign() -> Result<(), BenchmarkError> {
483 let sale_data = setup_and_start_sale::<T>()?;
484 let core = sale_data.first_core;
485
486 advance_to::<T>(2);
487
488 let caller: T::AccountId = whitelisted_caller();
489 T::Currency::set_balance(
490 &caller.clone(),
491 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
492 );
493
494 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
495 .expect("Offer not high enough for configuration.");
496
497 Broker::<T>::do_pool(region, None, caller.clone(), Finality::Provisional)
499 .map_err(|_| BenchmarkError::Weightless)?;
500
501 #[extrinsic_call]
502 _(RawOrigin::Signed(caller), region, 1000, Provisional);
503
504 let workplan_key = (region.begin, region.core);
505 assert!(Workplan::<T>::get(workplan_key).is_some());
506
507 assert!(Regions::<T>::get(region).is_some());
508
509 assert_last_event::<T>(
510 Event::Assigned {
511 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
512 task: 1000,
513 duration: 3u32.into(),
514 }
515 .into(),
516 );
517
518 Ok(())
519 }
520
521 #[benchmark]
522 fn pool() -> Result<(), BenchmarkError> {
523 let sale_data = setup_and_start_sale::<T>()?;
524 let core = sale_data.first_core;
525
526 advance_to::<T>(2);
527
528 let caller: T::AccountId = whitelisted_caller();
529 T::Currency::set_balance(
530 &caller.clone(),
531 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
532 );
533
534 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
535 .expect("Offer not high enough for configuration.");
536
537 let recipient: T::AccountId = account("recipient", 0, SEED);
538
539 #[extrinsic_call]
540 _(RawOrigin::Signed(caller), region, recipient, Final);
541
542 let workplan_key = (region.begin, region.core);
543 assert!(Workplan::<T>::get(workplan_key).is_some());
544
545 assert_last_event::<T>(
546 Event::Pooled {
547 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
548 duration: 3u32.into(),
549 }
550 .into(),
551 );
552
553 Ok(())
554 }
555
556 #[benchmark]
557 fn claim_revenue(
558 m: Linear<1, { new_config_record::<T>().region_length }>,
559 ) -> Result<(), BenchmarkError> {
560 let sale_data = setup_and_start_sale::<T>()?;
561 let core = sale_data.first_core;
562
563 advance_to::<T>(2);
564
565 let caller: T::AccountId = whitelisted_caller();
566 T::Currency::set_balance(
567 &caller.clone(),
568 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
569 );
570 T::Currency::set_balance(
571 &Broker::<T>::account_id(),
572 T::Currency::minimum_balance().saturating_add(200_000_000u32.into()),
573 );
574
575 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
576 .expect("Offer not high enough for configuration.");
577
578 let recipient: T::AccountId = account("recipient", 0, SEED);
579 T::Currency::set_balance(&recipient.clone(), T::Currency::minimum_balance());
580
581 Broker::<T>::do_pool(region, None, recipient.clone(), Final)
582 .map_err(|_| BenchmarkError::Weightless)?;
583
584 let revenue = 10_000_000u32.into();
585 InstaPoolHistory::<T>::insert(
586 region.begin,
587 InstaPoolHistoryRecord {
588 private_contributions: 4u32.into(),
589 system_contributions: 3u32.into(),
590 maybe_payout: Some(revenue),
591 },
592 );
593
594 #[extrinsic_call]
595 _(RawOrigin::Signed(caller), region, m);
596
597 assert!(InstaPoolHistory::<T>::get(region.begin).is_none());
598 assert_last_event::<T>(
599 Event::RevenueClaimPaid {
600 who: recipient,
601 amount: 200_000_000u32.into(),
602 next: if m < new_config_record::<T>().region_length {
603 Some(RegionId {
604 begin: region.begin.saturating_add(m),
605 core,
606 mask: CoreMask::complete(),
607 })
608 } else {
609 None
610 },
611 }
612 .into(),
613 );
614
615 Ok(())
616 }
617
618 #[benchmark]
619 fn purchase_credit() -> Result<(), BenchmarkError> {
620 setup_and_start_sale::<T>()?;
621
622 advance_to::<T>(2);
623
624 let caller: T::AccountId = whitelisted_caller();
625 T::Currency::set_balance(
626 &caller.clone(),
627 T::Currency::minimum_balance().saturating_add(T::MinimumCreditPurchase::get()),
628 );
629 T::Currency::set_balance(&Broker::<T>::account_id(), T::Currency::minimum_balance());
630
631 let beneficiary: RelayAccountIdOf<T> = account("beneficiary", 0, SEED);
632
633 #[extrinsic_call]
634 _(RawOrigin::Signed(caller.clone()), T::MinimumCreditPurchase::get(), beneficiary.clone());
635
636 assert_last_event::<T>(
637 Event::CreditPurchased {
638 who: caller,
639 beneficiary,
640 amount: T::MinimumCreditPurchase::get(),
641 }
642 .into(),
643 );
644
645 Ok(())
646 }
647
648 #[benchmark]
649 fn drop_region() -> Result<(), BenchmarkError> {
650 let sale_data = setup_and_start_sale::<T>()?;
651 let core = sale_data.first_core;
652 let region_len = Configuration::<T>::get().unwrap().region_length;
653
654 advance_to::<T>(2);
655
656 let caller: T::AccountId = whitelisted_caller();
657 T::Currency::set_balance(
658 &caller.clone(),
659 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
660 );
661
662 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
663 .expect("Offer not high enough for configuration.");
664
665 advance_to::<T>(
666 (T::TimeslicePeriod::get() * (region_len * 4).into()).try_into().ok().unwrap(),
667 );
668
669 #[extrinsic_call]
670 _(RawOrigin::Signed(caller), region);
671
672 assert_last_event::<T>(
673 Event::RegionDropped {
674 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
675 duration: 3u32.into(),
676 }
677 .into(),
678 );
679
680 Ok(())
681 }
682
683 #[benchmark]
684 fn drop_contribution() -> Result<(), BenchmarkError> {
685 let sale_data = setup_and_start_sale::<T>()?;
686 let core = sale_data.first_core;
687 let region_len = Configuration::<T>::get().unwrap().region_length;
688
689 advance_to::<T>(2);
690
691 let caller: T::AccountId = whitelisted_caller();
692 T::Currency::set_balance(
693 &caller.clone(),
694 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
695 );
696
697 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
698 .expect("Offer not high enough for configuration.");
699
700 let recipient: T::AccountId = account("recipient", 0, SEED);
701
702 Broker::<T>::do_pool(region, None, recipient, Final)
703 .map_err(|_| BenchmarkError::Weightless)?;
704
705 advance_to::<T>(
706 (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(),
707 );
708
709 #[extrinsic_call]
710 _(RawOrigin::Signed(caller), region);
711
712 assert_last_event::<T>(
713 Event::ContributionDropped {
714 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
715 }
716 .into(),
717 );
718
719 Ok(())
720 }
721
722 #[benchmark]
723 fn drop_history() -> Result<(), BenchmarkError> {
724 setup_and_start_sale::<T>()?;
725 let when = 5u32.into();
726 let revenue = 10_000_000u32.into();
727 let region_len = Configuration::<T>::get().unwrap().region_length;
728
729 advance_to::<T>(
730 (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(),
731 );
732
733 let caller: T::AccountId = whitelisted_caller();
734 InstaPoolHistory::<T>::insert(
735 when,
736 InstaPoolHistoryRecord {
737 private_contributions: 4u32.into(),
738 system_contributions: 3u32.into(),
739 maybe_payout: Some(revenue),
740 },
741 );
742
743 #[extrinsic_call]
744 _(RawOrigin::Signed(caller), when);
745
746 assert!(InstaPoolHistory::<T>::get(when).is_none());
747 assert_last_event::<T>(Event::HistoryDropped { when, revenue }.into());
748
749 Ok(())
750 }
751
752 #[benchmark]
753 fn drop_renewal() -> Result<(), BenchmarkError> {
754 let sale_data = setup_and_start_sale::<T>()?;
755 let core = sale_data.first_core;
756 let when = 5u32.into();
757 let region_len = Configuration::<T>::get().unwrap().region_length;
758
759 advance_to::<T>(
760 (T::TimeslicePeriod::get() * (region_len * 3).into()).try_into().ok().unwrap(),
761 );
762
763 let id = PotentialRenewalId { core, when };
764 let record = PotentialRenewalRecord {
765 price: 1_000_000u32.into(),
766 completion: CompletionStatus::Complete(new_schedule()),
767 };
768 PotentialRenewals::<T>::insert(id, record);
769
770 let caller: T::AccountId = whitelisted_caller();
771
772 #[extrinsic_call]
773 _(RawOrigin::Signed(caller), core, when);
774
775 assert!(PotentialRenewals::<T>::get(id).is_none());
776 assert_last_event::<T>(Event::PotentialRenewalDropped { core, when }.into());
777
778 Ok(())
779 }
780
781 #[benchmark]
782 fn request_core_count(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
783 let admin_origin =
784 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
785
786 #[extrinsic_call]
787 _(admin_origin as T::RuntimeOrigin, n.try_into().unwrap());
788
789 assert_last_event::<T>(
790 Event::CoreCountRequested { core_count: n.try_into().unwrap() }.into(),
791 );
792
793 Ok(())
794 }
795
796 #[benchmark]
797 fn process_core_count(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
798 setup_and_start_sale::<T>()?;
799
800 let core_count = n.try_into().unwrap();
801
802 CoreCountInbox::<T>::put(core_count);
803
804 let mut status = Status::<T>::get().ok_or(BenchmarkError::Weightless)?;
805
806 #[block]
807 {
808 Broker::<T>::process_core_count(&mut status);
809 }
810
811 assert_last_event::<T>(Event::CoreCountChanged { core_count }.into());
812
813 Ok(())
814 }
815
816 #[benchmark]
817 fn process_revenue() -> Result<(), BenchmarkError> {
818 setup_and_start_sale::<T>()?;
819
820 advance_to::<T>(2);
821
822 let caller: T::AccountId = whitelisted_caller();
823 T::Currency::set_balance(
824 &caller.clone(),
825 T::Currency::minimum_balance().saturating_add(30_000_000u32.into()),
826 );
827 T::Currency::set_balance(
828 &Broker::<T>::account_id(),
829 T::Currency::minimum_balance().saturating_add(90_000_000u32.into()),
830 );
831
832 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
833 let multiplicator = 5;
834
835 RevenueInbox::<T>::put(OnDemandRevenueRecord {
836 until: (timeslice_period * multiplicator).into(),
837 amount: 10_000_000u32.into(),
838 });
839
840 let timeslice = multiplicator - 1;
841 InstaPoolHistory::<T>::insert(
842 timeslice,
843 InstaPoolHistoryRecord {
844 private_contributions: 4u32.into(),
845 system_contributions: 6u32.into(),
846 maybe_payout: None,
847 },
848 );
849
850 #[block]
851 {
852 Broker::<T>::process_revenue();
853 }
854
855 assert_last_event::<T>(
856 Event::ClaimsReady {
857 when: timeslice.into(),
858 system_payout: 6_000_000u32.into(),
859 private_payout: 4_000_000u32.into(),
860 }
861 .into(),
862 );
863
864 Ok(())
865 }
866
867 #[benchmark]
868 fn rotate_sale(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
869 let config = new_config_record::<T>();
870 Configuration::<T>::put(config.clone());
871
872 let n_reservations = T::MaxReservedCores::get().min(n.saturating_sub(1));
877 setup_reservations::<T>(n_reservations);
878 let n_leases =
880 T::MaxLeasedCores::get().min(n.saturating_sub(1).saturating_sub(n_reservations));
881 setup_leases::<T>(n_leases, 1, 20);
882
883 let initial_price = 10_000_000u32.into();
885 let (start_price, _) = get_start_end_price::<T>(initial_price);
886 Broker::<T>::do_start_sales(
887 initial_price,
888 n.saturating_sub(n_reservations)
889 .saturating_sub(n_leases)
890 .try_into()
891 .expect("Upper limit of n is a u16."),
892 )
893 .expect("Configuration was initialized before; qed");
894
895 advance_to::<T>(2);
897
898 let n_renewable = T::MaxAutoRenewals::get()
901 .min(n.saturating_sub(n_leases).saturating_sub(n_reservations));
902
903 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
904 let sale = SaleInfo::<T>::get().expect("Sale has started.");
905
906 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
907 let price = Broker::<T>::sale_price(&sale, now);
908 (0..n_renewable.into()).try_for_each(|indx| -> Result<(), BenchmarkError> {
909 let task = 1000 + indx;
910 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
911 .expect("Failed to get sovereign account");
912 T::Currency::set_balance(
913 &caller.clone(),
914 T::Currency::minimum_balance()
915 .saturating_add(start_price)
916 .saturating_add(start_price),
917 );
918
919 let region = Broker::<T>::do_purchase(caller.clone(), start_price)
920 .expect("Offer not high enough for configuration.");
921
922 Broker::<T>::do_assign(region, None, task, Final)
923 .map_err(|_| BenchmarkError::Weightless)?;
924
925 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
926
927 Ok(())
928 })?;
929
930 let rotate_block = timeslice_period.saturating_mul(config.region_length) - 2;
932 advance_to::<T>(rotate_block - 1);
933
934 System::<T>::set_block_number(rotate_block.into());
936 RCBlockNumberProviderOf::<T::Coretime>::set_block_number(rotate_block.into());
937 let mut status = Status::<T>::get().expect("Sale has started.");
938 let sale = SaleInfo::<T>::get().expect("Sale has started.");
939 Broker::<T>::process_core_count(&mut status);
940 Broker::<T>::process_revenue();
941 status.last_committed_timeslice = config.region_length;
942
943 #[block]
944 {
945 Broker::<T>::rotate_sale(sale.clone(), &config, &status);
946 }
947
948 let new_prices = T::PriceAdapter::adapt_price(SalePerformance::from_sale(&sale));
950 let new_sale = SaleInfo::<T>::get().expect("Sale has started.");
951 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
952 let sale_start = config.interlude_length.saturating_add(rotate_block.into());
953
954 assert_has_event::<T>(
955 Event::SaleInitialized {
956 sale_start,
957 leadin_length: 1u32.into(),
958 start_price: Broker::<T>::sale_price(&new_sale, now),
959 end_price: new_prices.end_price,
960 region_begin: sale.region_begin + config.region_length,
961 region_end: sale.region_end + config.region_length,
962 ideal_cores_sold: 0,
963 cores_offered: n
964 .saturating_sub(n_reservations)
965 .saturating_sub(n_leases)
966 .try_into()
967 .unwrap(),
968 }
969 .into(),
970 );
971
972 (0..n_renewable).for_each(|indx| {
974 let task = 1000 + indx;
975 let who = T::SovereignAccountOf::maybe_convert(task)
976 .expect("Failed to get sovereign account");
977 assert_has_event::<T>(
978 Event::Renewed {
979 who,
980 old_core: n_reservations as u16 + n_leases as u16 + indx as u16,
981 core: n_reservations as u16 + n_leases as u16 + indx as u16,
982 price,
983 begin: new_sale.region_begin,
984 duration: config.region_length,
985 workload: Schedule::truncate_from(vec![ScheduleItem {
986 assignment: Task(task),
987 mask: CoreMask::complete(),
988 }]),
989 }
990 .into(),
991 );
992 });
993
994 Ok(())
995 }
996
997 #[benchmark]
998 fn process_pool() {
999 let when = 10u32.into();
1000 let private_pool_size = 5u32.into();
1001 let system_pool_size = 4u32.into();
1002
1003 let config = new_config_record::<T>();
1004 let commit_timeslice = Broker::<T>::latest_timeslice_ready_to_commit(&config);
1005 let mut status = StatusRecord {
1006 core_count: 5u16.into(),
1007 private_pool_size,
1008 system_pool_size,
1009 last_committed_timeslice: commit_timeslice.saturating_sub(1),
1010 last_timeslice: Broker::<T>::current_timeslice(),
1011 };
1012
1013 #[block]
1014 {
1015 Broker::<T>::process_pool(when, &mut status);
1016 }
1017
1018 assert!(InstaPoolHistory::<T>::get(when).is_some());
1019 assert_last_event::<T>(
1020 Event::HistoryInitialized { when, private_pool_size, system_pool_size }.into(),
1021 );
1022 }
1023
1024 #[benchmark]
1025 fn process_core_schedule() {
1026 let timeslice = 10u32.into();
1027 let core = 5u16.into();
1028 let rc_begin = 1u32.into();
1029
1030 Workplan::<T>::insert((timeslice, core), new_schedule());
1031
1032 #[block]
1033 {
1034 Broker::<T>::process_core_schedule(timeslice, rc_begin, core);
1035 }
1036
1037 assert_eq!(Workload::<T>::get(core).len(), CORE_MASK_BITS);
1038
1039 let mut assignment: Vec<(CoreAssignment, PartsOf57600)> = vec![];
1040 for i in 0..CORE_MASK_BITS {
1041 assignment.push((CoreAssignment::Task(i.try_into().unwrap()), 57600));
1042 }
1043 assert_last_event::<T>(Event::CoreAssigned { core, when: rc_begin, assignment }.into());
1044 }
1045
1046 #[benchmark]
1047 fn request_revenue_info_at() {
1048 let current_timeslice = Broker::<T>::current_timeslice();
1049 let rc_block = T::TimeslicePeriod::get() * current_timeslice.into();
1050
1051 #[block]
1052 {
1053 T::Coretime::request_revenue_info_at(rc_block);
1054 }
1055 }
1056
1057 #[benchmark]
1058 fn notify_core_count() -> Result<(), BenchmarkError> {
1059 let admin_origin =
1060 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1061
1062 #[extrinsic_call]
1063 _(admin_origin as T::RuntimeOrigin, 100);
1064
1065 assert!(CoreCountInbox::<T>::take().is_some());
1066 Ok(())
1067 }
1068
1069 #[benchmark]
1070 fn notify_revenue() -> Result<(), BenchmarkError> {
1071 let admin_origin =
1072 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1073
1074 #[extrinsic_call]
1075 _(
1076 admin_origin as T::RuntimeOrigin,
1077 OnDemandRevenueRecord { until: 100u32.into(), amount: 100_000_000u32.into() },
1078 );
1079
1080 assert!(RevenueInbox::<T>::take().is_some());
1081 Ok(())
1082 }
1083
1084 #[benchmark]
1085 fn do_tick_base() -> Result<(), BenchmarkError> {
1086 setup_and_start_sale::<T>()?;
1087
1088 advance_to::<T>(5);
1089
1090 let mut status = Status::<T>::get().unwrap();
1091 status.last_committed_timeslice = 3;
1092 Status::<T>::put(&status);
1093
1094 #[block]
1095 {
1096 Broker::<T>::do_tick();
1097 }
1098
1099 let updated_status = Status::<T>::get().unwrap();
1100 assert_eq!(status, updated_status);
1101
1102 Ok(())
1103 }
1104
1105 #[benchmark]
1106 fn force_reserve() -> Result<(), BenchmarkError> {
1107 Configuration::<T>::put(new_config_record::<T>());
1108 let reservation_count = T::MaxReservedCores::get().saturating_sub(1);
1110 setup_reservations::<T>(reservation_count);
1111
1112 setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
1114
1115 let origin =
1116 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1117
1118 Broker::<T>::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap())
1120 .map_err(|_| BenchmarkError::Weightless)?;
1121
1122 let status = Status::<T>::get().unwrap();
1124 Broker::<T>::do_request_core_count(status.core_count + 1).unwrap();
1125
1126 advance_to::<T>(T::TimeslicePeriod::get().try_into().ok().unwrap());
1127 let schedule = new_schedule();
1128
1129 #[extrinsic_call]
1130 _(origin as T::RuntimeOrigin, schedule.clone(), status.core_count);
1131
1132 assert_eq!(Reservations::<T>::decode_len().unwrap(), T::MaxReservedCores::get() as usize);
1133
1134 let sale_info = SaleInfo::<T>::get().unwrap();
1135 assert_eq!(
1136 Workplan::<T>::get((sale_info.region_begin, status.core_count)),
1137 Some(schedule.clone())
1138 );
1139 assert_eq!(Workplan::<T>::get((3, status.core_count)), Some(schedule));
1142
1143 Ok(())
1144 }
1145
1146 #[benchmark]
1147 fn swap_leases() -> Result<(), BenchmarkError> {
1148 let admin_origin =
1149 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1150
1151 let n = (T::MaxLeasedCores::get() / 2) as usize;
1153 let mut leases = vec![LeaseRecordItem { task: 1, until: 10u32.into() }; n];
1154 leases.extend(vec![LeaseRecordItem { task: 2, until: 20u32.into() }; n]);
1155 Leases::<T>::put(BoundedVec::try_from(leases).unwrap());
1156
1157 #[extrinsic_call]
1158 _(admin_origin as T::RuntimeOrigin, 1, 2);
1159
1160 Ok(())
1161 }
1162
1163 #[benchmark]
1164 fn enable_auto_renew() -> Result<(), BenchmarkError> {
1165 let sale_data = setup_and_start_sale::<T>()?;
1166
1167 advance_to::<T>(2);
1168
1169 let sale = SaleInfo::<T>::get().expect("Sale has already started.");
1170 (0..T::MaxAutoRenewals::get() - 1).try_for_each(|indx| -> Result<(), BenchmarkError> {
1172 let task = 1000 + indx;
1173 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
1174 .expect("Failed to get sovereign account");
1175 T::Currency::set_balance(
1177 &caller.clone(),
1178 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1179 );
1180
1181 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1182 .expect("Offer not high enough for configuration.");
1183
1184 Broker::<T>::do_assign(region, None, task, Final)
1185 .map_err(|_| BenchmarkError::Weightless)?;
1186
1187 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
1188
1189 Ok(())
1190 })?;
1191
1192 let caller: T::AccountId =
1193 T::SovereignAccountOf::maybe_convert(2001).expect("Failed to get sovereign account");
1194 T::Currency::set_balance(
1196 &caller.clone(),
1197 T::Currency::minimum_balance()
1198 .saturating_add(sale_data.start_price.saturating_add(sale_data.start_price)),
1199 );
1200
1201 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1203 .expect("Offer not high enough for configuration.");
1204 Broker::<T>::do_assign(region, None, 2001, Final)
1205 .map_err(|_| BenchmarkError::Weightless)?;
1206
1207 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
1210 let config = Configuration::<T>::get().expect("Already configured.");
1211 advance_to::<T>(config.region_length * timeslice_period);
1212
1213 #[extrinsic_call]
1214 _(RawOrigin::Signed(caller), region.core, 2001, None);
1215
1216 assert_last_event::<T>(Event::AutoRenewalEnabled { core: region.core, task: 2001 }.into());
1217 let sale = SaleInfo::<T>::get().expect("Sales have started.");
1219 assert!(PotentialRenewals::<T>::get(PotentialRenewalId {
1220 core: region.core,
1221 when: sale.region_end,
1222 })
1223 .is_some());
1224
1225 Ok(())
1226 }
1227
1228 #[benchmark]
1229 fn disable_auto_renew() -> Result<(), BenchmarkError> {
1230 let sale_data = setup_and_start_sale::<T>()?;
1231 let core = sale_data.first_core;
1232
1233 advance_to::<T>(2);
1234
1235 let sale = SaleInfo::<T>::get().expect("Sale has already started.");
1236 (0..T::MaxAutoRenewals::get()).try_for_each(|indx| -> Result<(), BenchmarkError> {
1238 let task = 1000 + indx;
1239 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
1240 .expect("Failed to get sovereign account");
1241 T::Currency::set_balance(
1242 &caller.clone(),
1243 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1244 );
1245
1246 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1247 .expect("Offer not high enough for configuration.");
1248
1249 Broker::<T>::do_assign(region, None, task, Final)
1250 .map_err(|_| BenchmarkError::Weightless)?;
1251
1252 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
1253
1254 Ok(())
1255 })?;
1256
1257 let task = 1000;
1258
1259 let caller: T::AccountId =
1260 T::SovereignAccountOf::maybe_convert(task).expect("Failed to get sovereign account");
1261
1262 #[extrinsic_call]
1263 _(RawOrigin::Signed(caller), core, task);
1264
1265 assert_last_event::<T>(Event::AutoRenewalDisabled { core, task }.into());
1266
1267 Ok(())
1268 }
1269
1270 #[benchmark]
1271 fn on_new_timeslice() -> Result<(), BenchmarkError> {
1272 let sale_data = setup_and_start_sale::<T>()?;
1273
1274 advance_to::<T>(2);
1275
1276 let caller: T::AccountId = whitelisted_caller();
1277 T::Currency::set_balance(
1278 &caller.clone(),
1279 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1280 );
1281
1282 let _region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1283 .expect("Offer not high enough for configuration.");
1284
1285 let timeslice = Broker::<T>::current_timeslice();
1286
1287 #[block]
1288 {
1289 T::Coretime::on_new_timeslice(timeslice);
1290 }
1291
1292 Ok(())
1293 }
1294
1295 #[benchmark]
1296 fn remove_assignment() -> Result<(), BenchmarkError> {
1297 let sale_data = setup_and_start_sale::<T>()?;
1298
1299 advance_to::<T>(2);
1300
1301 let caller: T::AccountId = whitelisted_caller();
1302 T::Currency::set_balance(
1303 &caller.clone(),
1304 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1305 );
1306
1307 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1308 .expect("Offer not high enough for configuration.");
1309
1310 Broker::<T>::do_assign(region, None, 1000, Provisional)
1311 .map_err(|_| BenchmarkError::Weightless)?;
1312
1313 let origin =
1314 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1315
1316 #[extrinsic_call]
1317 _(origin as T::RuntimeOrigin, region);
1318
1319 Ok(())
1320 }
1321
1322 impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
1325}