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 sale_index: 1u32,
290 }
291 .into(),
292 );
293
294 Ok(())
295 }
296
297 #[benchmark]
298 fn purchase() -> Result<(), BenchmarkError> {
299 let sale_data = setup_and_start_sale::<T>()?;
300
301 advance_to::<T>(2);
302
303 let caller: T::AccountId = whitelisted_caller();
304 T::Currency::set_balance(
305 &caller.clone(),
306 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
307 );
308
309 #[extrinsic_call]
310 _(RawOrigin::Signed(caller.clone()), sale_data.start_price);
311
312 assert_eq!(SaleInfo::<T>::get().unwrap().sellout_price.unwrap(), sale_data.end_price);
313 assert_last_event::<T>(
314 Event::Purchased {
315 who: caller,
316 region_id: RegionId {
317 begin: SaleInfo::<T>::get().unwrap().region_begin,
318 core: sale_data.first_core,
319 mask: CoreMask::complete(),
320 },
321 price: sale_data.end_price,
322 duration: 3u32.into(),
323 }
324 .into(),
325 );
326
327 Ok(())
328 }
329
330 #[benchmark]
331 fn renew() -> Result<(), BenchmarkError> {
332 let sale_data = setup_and_start_sale::<T>()?;
333 let region_len = Configuration::<T>::get().unwrap().region_length;
334
335 advance_to::<T>(2);
336
337 let caller: T::AccountId = whitelisted_caller();
338 T::Currency::set_balance(
339 &caller.clone(),
340 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
341 );
342
343 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
344 .expect("Offer not high enough for configuration.");
345
346 Broker::<T>::do_assign(region, None, 1001, Final)
347 .map_err(|_| BenchmarkError::Weightless)?;
348
349 advance_to::<T>((T::TimeslicePeriod::get() * region_len.into()).try_into().ok().unwrap());
350
351 #[extrinsic_call]
352 _(RawOrigin::Signed(caller), region.core);
353
354 let id = PotentialRenewalId { core: region.core, when: region.begin + region_len * 2 };
355 assert!(PotentialRenewals::<T>::get(id).is_some());
356
357 Ok(())
358 }
359
360 #[benchmark]
361 fn transfer() -> Result<(), BenchmarkError> {
362 let sale_data = setup_and_start_sale::<T>()?;
363
364 advance_to::<T>(2);
365
366 let caller: T::AccountId = whitelisted_caller();
367 T::Currency::set_balance(
368 &caller.clone(),
369 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
370 );
371
372 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
373 .expect("Offer not high enough for configuration.");
374
375 let recipient: T::AccountId = account("recipient", 0, SEED);
376
377 #[extrinsic_call]
378 _(RawOrigin::Signed(caller.clone()), region, recipient.clone());
379
380 assert_last_event::<T>(
381 Event::Transferred {
382 region_id: region,
383 old_owner: Some(caller),
384 owner: Some(recipient),
385 duration: 3u32.into(),
386 }
387 .into(),
388 );
389
390 Ok(())
391 }
392
393 #[benchmark]
394 fn partition() -> Result<(), BenchmarkError> {
395 let sale_data = setup_and_start_sale::<T>()?;
396
397 advance_to::<T>(2);
398
399 let caller: T::AccountId = whitelisted_caller();
400 T::Currency::set_balance(
401 &caller.clone(),
402 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
403 );
404
405 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
406 .expect("Offer not high enough for configuration.");
407
408 Broker::<T>::do_pool(region, None, caller.clone(), Finality::Provisional)
410 .map_err(|_| BenchmarkError::Weightless)?;
411
412 #[extrinsic_call]
413 _(RawOrigin::Signed(caller), region, 2);
414
415 assert_last_event::<T>(
416 Event::Partitioned {
417 old_region_id: RegionId {
418 begin: region.begin,
419 core: sale_data.first_core,
420 mask: CoreMask::complete(),
421 },
422 new_region_ids: (
423 RegionId {
424 begin: region.begin,
425 core: sale_data.first_core,
426 mask: CoreMask::complete(),
427 },
428 RegionId {
429 begin: region.begin + 2,
430 core: sale_data.first_core,
431 mask: CoreMask::complete(),
432 },
433 ),
434 }
435 .into(),
436 );
437
438 Ok(())
439 }
440
441 #[benchmark]
442 fn interlace() -> Result<(), BenchmarkError> {
443 let sale_data = setup_and_start_sale::<T>()?;
444 let core = sale_data.first_core;
445
446 advance_to::<T>(2);
447
448 let caller: T::AccountId = whitelisted_caller();
449 T::Currency::set_balance(
450 &caller.clone(),
451 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
452 );
453
454 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
455 .expect("Offer not high enough for configuration.");
456
457 Broker::<T>::do_pool(region, None, caller.clone(), Finality::Provisional)
459 .map_err(|_| BenchmarkError::Weightless)?;
460
461 #[extrinsic_call]
462 _(RawOrigin::Signed(caller), region, 0x00000_fffff_fffff_00000.into());
463
464 assert_last_event::<T>(
465 Event::Interlaced {
466 old_region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
467 new_region_ids: (
468 RegionId { begin: region.begin, core, mask: 0x00000_fffff_fffff_00000.into() },
469 RegionId {
470 begin: region.begin,
471 core,
472 mask: CoreMask::complete() ^ 0x00000_fffff_fffff_00000.into(),
473 },
474 ),
475 }
476 .into(),
477 );
478
479 Ok(())
480 }
481
482 #[benchmark]
483 fn assign() -> Result<(), BenchmarkError> {
484 let sale_data = setup_and_start_sale::<T>()?;
485 let core = sale_data.first_core;
486
487 advance_to::<T>(2);
488
489 let caller: T::AccountId = whitelisted_caller();
490 T::Currency::set_balance(
491 &caller.clone(),
492 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
493 );
494
495 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
496 .expect("Offer not high enough for configuration.");
497
498 Broker::<T>::do_pool(region, None, caller.clone(), Finality::Provisional)
500 .map_err(|_| BenchmarkError::Weightless)?;
501
502 #[extrinsic_call]
503 _(RawOrigin::Signed(caller), region, 1000, Provisional);
504
505 let workplan_key = (region.begin, region.core);
506 assert!(Workplan::<T>::get(workplan_key).is_some());
507
508 assert!(Regions::<T>::get(region).is_some());
509
510 assert_last_event::<T>(
511 Event::Assigned {
512 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
513 task: 1000,
514 duration: 3u32.into(),
515 }
516 .into(),
517 );
518
519 Ok(())
520 }
521
522 #[benchmark]
523 fn pool() -> Result<(), BenchmarkError> {
524 let sale_data = setup_and_start_sale::<T>()?;
525 let core = sale_data.first_core;
526
527 advance_to::<T>(2);
528
529 let caller: T::AccountId = whitelisted_caller();
530 T::Currency::set_balance(
531 &caller.clone(),
532 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
533 );
534
535 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
536 .expect("Offer not high enough for configuration.");
537
538 let recipient: T::AccountId = account("recipient", 0, SEED);
539
540 #[extrinsic_call]
541 _(RawOrigin::Signed(caller), region, recipient, Final);
542
543 let workplan_key = (region.begin, region.core);
544 assert!(Workplan::<T>::get(workplan_key).is_some());
545
546 assert_last_event::<T>(
547 Event::Pooled {
548 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
549 duration: 3u32.into(),
550 }
551 .into(),
552 );
553
554 Ok(())
555 }
556
557 #[benchmark]
558 fn claim_revenue(
559 m: Linear<1, { new_config_record::<T>().region_length }>,
560 ) -> Result<(), BenchmarkError> {
561 let sale_data = setup_and_start_sale::<T>()?;
562 let core = sale_data.first_core;
563
564 advance_to::<T>(2);
565
566 let caller: T::AccountId = whitelisted_caller();
567 T::Currency::set_balance(
568 &caller.clone(),
569 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
570 );
571 let payout_per_timeslice: BalanceOf<T> = 200_000_000u32.into();
572 let total_payout = payout_per_timeslice.saturating_mul(m.into());
573 T::Currency::set_balance(
574 &Broker::<T>::account_id(),
575 T::Currency::minimum_balance().saturating_add(total_payout),
576 );
577
578 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
579 .expect("Offer not high enough for configuration.");
580
581 let recipient: T::AccountId = account("recipient", 0, SEED);
582 T::Currency::set_balance(&recipient.clone(), T::Currency::minimum_balance());
583
584 Broker::<T>::do_pool(region, None, recipient.clone(), Final)
585 .map_err(|_| BenchmarkError::Weightless)?;
586
587 let revenue = 10_000_000u32.into();
588 let claimed = region.begin..region.begin.saturating_add(m);
589 for timeslice in claimed.clone() {
590 InstaPoolHistory::<T>::insert(
591 timeslice,
592 InstaPoolHistoryRecord {
593 private_contributions: 4u32.into(),
594 system_contributions: 3u32.into(),
595 maybe_payout: Some(revenue),
596 },
597 );
598 }
599
600 #[extrinsic_call]
601 _(RawOrigin::Signed(caller), region, m);
602
603 for timeslice in claimed {
604 assert!(InstaPoolHistory::<T>::get(timeslice).is_none());
605 }
606 assert_last_event::<T>(
607 Event::RevenueClaimPaid {
608 who: recipient,
609 amount: total_payout,
610 next: if m < new_config_record::<T>().region_length {
611 Some(RegionId {
612 begin: region.begin.saturating_add(m),
613 core,
614 mask: CoreMask::complete(),
615 })
616 } else {
617 None
618 },
619 }
620 .into(),
621 );
622
623 Ok(())
624 }
625
626 #[benchmark]
627 fn purchase_credit() -> Result<(), BenchmarkError> {
628 setup_and_start_sale::<T>()?;
629
630 advance_to::<T>(2);
631
632 let caller: T::AccountId = whitelisted_caller();
633 T::Currency::set_balance(
634 &caller.clone(),
635 T::Currency::minimum_balance().saturating_add(T::MinimumCreditPurchase::get()),
636 );
637 T::Currency::set_balance(&Broker::<T>::account_id(), T::Currency::minimum_balance());
638
639 let beneficiary: RelayAccountIdOf<T> = account("beneficiary", 0, SEED);
640
641 #[extrinsic_call]
642 _(RawOrigin::Signed(caller.clone()), T::MinimumCreditPurchase::get(), beneficiary.clone());
643
644 assert_last_event::<T>(
645 Event::CreditPurchased {
646 who: caller,
647 beneficiary,
648 amount: T::MinimumCreditPurchase::get(),
649 }
650 .into(),
651 );
652
653 Ok(())
654 }
655
656 #[benchmark]
657 fn drop_region() -> Result<(), BenchmarkError> {
658 let sale_data = setup_and_start_sale::<T>()?;
659 let core = sale_data.first_core;
660 let region_len = Configuration::<T>::get().unwrap().region_length;
661
662 advance_to::<T>(2);
663
664 let caller: T::AccountId = whitelisted_caller();
665 T::Currency::set_balance(
666 &caller.clone(),
667 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
668 );
669
670 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
671 .expect("Offer not high enough for configuration.");
672
673 advance_to::<T>(
674 (T::TimeslicePeriod::get() * (region_len * 4).into()).try_into().ok().unwrap(),
675 );
676
677 #[extrinsic_call]
678 _(RawOrigin::Signed(caller), region);
679
680 assert_last_event::<T>(
681 Event::RegionDropped {
682 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
683 duration: 3u32.into(),
684 }
685 .into(),
686 );
687
688 Ok(())
689 }
690
691 #[benchmark]
692 fn drop_contribution() -> Result<(), BenchmarkError> {
693 let sale_data = setup_and_start_sale::<T>()?;
694 let core = sale_data.first_core;
695 let region_len = Configuration::<T>::get().unwrap().region_length;
696
697 advance_to::<T>(2);
698
699 let caller: T::AccountId = whitelisted_caller();
700 T::Currency::set_balance(
701 &caller.clone(),
702 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
703 );
704
705 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
706 .expect("Offer not high enough for configuration.");
707
708 let recipient: T::AccountId = account("recipient", 0, SEED);
709
710 Broker::<T>::do_pool(region, None, recipient, Final)
711 .map_err(|_| BenchmarkError::Weightless)?;
712
713 advance_to::<T>(
714 (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(),
715 );
716
717 #[extrinsic_call]
718 _(RawOrigin::Signed(caller), region);
719
720 assert_last_event::<T>(
721 Event::ContributionDropped {
722 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
723 }
724 .into(),
725 );
726
727 Ok(())
728 }
729
730 #[benchmark]
731 fn drop_history() -> Result<(), BenchmarkError> {
732 setup_and_start_sale::<T>()?;
733 let when = 5u32.into();
734 let revenue = 10_000_000u32.into();
735 let region_len = Configuration::<T>::get().unwrap().region_length;
736
737 advance_to::<T>(
738 (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(),
739 );
740
741 let caller: T::AccountId = whitelisted_caller();
742 InstaPoolHistory::<T>::insert(
743 when,
744 InstaPoolHistoryRecord {
745 private_contributions: 4u32.into(),
746 system_contributions: 3u32.into(),
747 maybe_payout: Some(revenue),
748 },
749 );
750
751 #[extrinsic_call]
752 _(RawOrigin::Signed(caller), when);
753
754 assert!(InstaPoolHistory::<T>::get(when).is_none());
755 assert_last_event::<T>(Event::HistoryDropped { when, revenue }.into());
756
757 Ok(())
758 }
759
760 #[benchmark]
761 fn drop_renewal() -> Result<(), BenchmarkError> {
762 let sale_data = setup_and_start_sale::<T>()?;
763 let core = sale_data.first_core;
764 let when = 5u32.into();
765 let region_len = Configuration::<T>::get().unwrap().region_length;
766
767 advance_to::<T>(
768 (T::TimeslicePeriod::get() * (region_len * 3).into()).try_into().ok().unwrap(),
769 );
770
771 let id = PotentialRenewalId { core, when };
772 let record = PotentialRenewalRecord {
773 price: 1_000_000u32.into(),
774 completion: CompletionStatus::Complete(new_schedule()),
775 };
776 PotentialRenewals::<T>::insert(id, record);
777
778 let caller: T::AccountId = whitelisted_caller();
779
780 #[extrinsic_call]
781 _(RawOrigin::Signed(caller), core, when);
782
783 assert!(PotentialRenewals::<T>::get(id).is_none());
784 assert_last_event::<T>(Event::PotentialRenewalDropped { core, when }.into());
785
786 Ok(())
787 }
788
789 #[benchmark]
790 fn request_core_count(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
791 let admin_origin =
792 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
793
794 #[extrinsic_call]
795 _(admin_origin as T::RuntimeOrigin, n.try_into().unwrap());
796
797 assert_last_event::<T>(
798 Event::CoreCountRequested { core_count: n.try_into().unwrap() }.into(),
799 );
800
801 Ok(())
802 }
803
804 #[benchmark]
805 fn process_core_count(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
806 setup_and_start_sale::<T>()?;
807
808 let core_count = n.try_into().unwrap();
809
810 CoreCountInbox::<T>::put(core_count);
811
812 let mut status = Status::<T>::get().ok_or(BenchmarkError::Weightless)?;
813
814 #[block]
815 {
816 Broker::<T>::process_core_count(&mut status);
817 }
818
819 assert_last_event::<T>(Event::CoreCountChanged { core_count }.into());
820
821 Ok(())
822 }
823
824 #[benchmark]
825 fn process_revenue() -> Result<(), BenchmarkError> {
826 setup_and_start_sale::<T>()?;
827
828 advance_to::<T>(2);
829
830 let caller: T::AccountId = whitelisted_caller();
831 T::Currency::set_balance(
832 &caller.clone(),
833 T::Currency::minimum_balance().saturating_add(30_000_000u32.into()),
834 );
835 T::Currency::set_balance(
836 &Broker::<T>::account_id(),
837 T::Currency::minimum_balance().saturating_add(90_000_000u32.into()),
838 );
839
840 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
841 let multiplicator = 5;
842
843 RevenueInbox::<T>::put(OnDemandRevenueRecord {
844 until: (timeslice_period * multiplicator).into(),
845 amount: 10_000_000u32.into(),
846 });
847
848 let timeslice = multiplicator - 1;
849 InstaPoolHistory::<T>::insert(
850 timeslice,
851 InstaPoolHistoryRecord {
852 private_contributions: 4u32.into(),
853 system_contributions: 6u32.into(),
854 maybe_payout: None,
855 },
856 );
857
858 #[block]
859 {
860 let (when, revenue) =
861 Broker::<T>::take_revenue_report().expect("a revenue report was put in; qed");
862 Broker::<T>::process_revenue_amount(when, revenue);
863 }
864
865 assert_last_event::<T>(
866 Event::ClaimsReady {
867 when: timeslice.into(),
868 system_payout: 6_000_000u32.into(),
869 private_payout: 4_000_000u32.into(),
870 }
871 .into(),
872 );
873
874 Ok(())
875 }
876
877 #[benchmark]
878 fn rotate_sale(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
879 let config = new_config_record::<T>();
880 Configuration::<T>::put(config.clone());
881
882 let n_reservations = T::MaxReservedCores::get().min(n.saturating_sub(1));
887 setup_reservations::<T>(n_reservations);
888 let n_leases =
890 T::MaxLeasedCores::get().min(n.saturating_sub(1).saturating_sub(n_reservations));
891 setup_leases::<T>(n_leases, 1, 20);
892
893 let initial_price = 10_000_000u32.into();
895 let (start_price, _) = get_start_end_price::<T>(initial_price);
896 Broker::<T>::do_start_sales(
897 initial_price,
898 n.saturating_sub(n_reservations)
899 .saturating_sub(n_leases)
900 .try_into()
901 .expect("Upper limit of n is a u16."),
902 )
903 .expect("Configuration was initialized before; qed");
904
905 advance_to::<T>(2);
907
908 let n_renewable = T::MaxAutoRenewals::get()
911 .min(n.saturating_sub(n_leases).saturating_sub(n_reservations));
912
913 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
914 let sale = SaleInfo::<T>::get().expect("Sale has started.");
915
916 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
917 let price = Broker::<T>::sale_price(&sale, now);
918 (0..n_renewable.into()).try_for_each(|indx| -> Result<(), BenchmarkError> {
919 let task = 1000 + indx;
920 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
921 .expect("Failed to get sovereign account");
922 T::Currency::set_balance(
923 &caller.clone(),
924 T::Currency::minimum_balance()
925 .saturating_add(start_price)
926 .saturating_add(start_price),
927 );
928
929 let region = Broker::<T>::do_purchase(caller.clone(), start_price)
930 .expect("Offer not high enough for configuration.");
931
932 Broker::<T>::do_assign(region, None, task, Final)
933 .map_err(|_| BenchmarkError::Weightless)?;
934
935 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
936
937 Ok(())
938 })?;
939
940 let rotate_block = timeslice_period.saturating_mul(config.region_length) - 2;
942 advance_to::<T>(rotate_block - 1);
943
944 System::<T>::set_block_number(rotate_block.into());
946 RCBlockNumberProviderOf::<T::Coretime>::set_block_number(rotate_block.into());
947 let mut status = Status::<T>::get().expect("Sale has started.");
948 let sale = SaleInfo::<T>::get().expect("Sale has started.");
949 Broker::<T>::process_core_count(&mut status);
950 status.last_committed_timeslice = config.region_length;
951
952 #[block]
953 {
954 Broker::<T>::rotate_sale(sale.clone(), &config, &status);
955 }
956
957 let new_prices = T::PriceAdapter::adapt_price(SalePerformance::from_sale(&sale));
959 let new_sale = SaleInfo::<T>::get().expect("Sale has started.");
960 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
961 let sale_start = config.interlude_length.saturating_add(rotate_block.into());
962
963 assert_has_event::<T>(
964 Event::SaleInitialized {
965 sale_start,
966 leadin_length: 1u32.into(),
967 start_price: Broker::<T>::sale_price(&new_sale, now),
968 end_price: new_prices.end_price,
969 region_begin: sale.region_begin + config.region_length,
970 region_end: sale.region_end + config.region_length,
971 ideal_cores_sold: 0,
972 cores_offered: n
973 .saturating_sub(n_reservations)
974 .saturating_sub(n_leases)
975 .try_into()
976 .unwrap(),
977 sale_index: sale.sale_index.saturating_add(1),
978 }
979 .into(),
980 );
981
982 (0..n_renewable).for_each(|indx| {
984 let task = 1000 + indx;
985 let who = T::SovereignAccountOf::maybe_convert(task)
986 .expect("Failed to get sovereign account");
987 assert_has_event::<T>(
988 Event::Renewed {
989 who,
990 old_core: n_reservations as u16 + n_leases as u16 + indx as u16,
991 core: n_reservations as u16 + n_leases as u16 + indx as u16,
992 price,
993 begin: new_sale.region_begin,
994 duration: config.region_length,
995 workload: Schedule::truncate_from(vec![ScheduleItem {
996 assignment: Task(task),
997 mask: CoreMask::complete(),
998 }]),
999 }
1000 .into(),
1001 );
1002 });
1003
1004 Ok(())
1005 }
1006
1007 #[benchmark]
1008 fn process_pool() {
1009 let when = 10u32.into();
1010 let private_pool_size = 5u32.into();
1011 let system_pool_size = 4u32.into();
1012
1013 let config = new_config_record::<T>();
1014 let commit_timeslice = Broker::<T>::latest_timeslice_ready_to_commit(&config);
1015 let mut status = StatusRecord {
1016 core_count: 5u16.into(),
1017 private_pool_size,
1018 system_pool_size,
1019 last_committed_timeslice: commit_timeslice.saturating_sub(1),
1020 last_timeslice: Broker::<T>::current_timeslice(),
1021 };
1022
1023 #[block]
1024 {
1025 Broker::<T>::process_pool(when, &mut status);
1026 }
1027
1028 assert!(InstaPoolHistory::<T>::get(when).is_some());
1029 assert_last_event::<T>(
1030 Event::HistoryInitialized { when, private_pool_size, system_pool_size }.into(),
1031 );
1032 }
1033
1034 #[benchmark]
1035 fn process_core_schedule() {
1036 let timeslice = 10u32.into();
1037 let core = 5u16.into();
1038 let rc_begin = 1u32.into();
1039
1040 Workplan::<T>::insert((timeslice, core), new_schedule());
1041
1042 #[block]
1043 {
1044 Broker::<T>::process_core_schedule(timeslice, rc_begin, core);
1045 }
1046
1047 assert_eq!(Workload::<T>::get(core).len(), CORE_MASK_BITS);
1048
1049 let mut assignment: Vec<(CoreAssignment, PartsOf57600)> = vec![];
1050 for i in 0..CORE_MASK_BITS {
1051 assignment.push((CoreAssignment::Task(i.try_into().unwrap()), 57600));
1052 }
1053 assert_last_event::<T>(Event::CoreAssigned { core, when: rc_begin, assignment }.into());
1054 }
1055
1056 #[benchmark]
1057 fn request_revenue_info_at() {
1058 let current_timeslice = Broker::<T>::current_timeslice();
1059 let rc_block = T::TimeslicePeriod::get() * current_timeslice.into();
1060
1061 #[block]
1062 {
1063 T::Coretime::request_revenue_info_at(rc_block);
1064 }
1065 }
1066
1067 #[benchmark]
1068 fn notify_core_count() -> Result<(), BenchmarkError> {
1069 let admin_origin =
1070 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1071
1072 #[extrinsic_call]
1073 _(admin_origin as T::RuntimeOrigin, 100);
1074
1075 assert!(CoreCountInbox::<T>::take().is_some());
1076 Ok(())
1077 }
1078
1079 #[benchmark]
1080 fn notify_revenue() -> Result<(), BenchmarkError> {
1081 let admin_origin =
1082 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1083
1084 #[extrinsic_call]
1085 _(
1086 admin_origin as T::RuntimeOrigin,
1087 OnDemandRevenueRecord { until: 100u32.into(), amount: 100_000_000u32.into() },
1088 );
1089
1090 assert!(RevenueInbox::<T>::take().is_some());
1091 Ok(())
1092 }
1093
1094 #[benchmark]
1095 fn do_tick_base() -> Result<(), BenchmarkError> {
1096 setup_and_start_sale::<T>()?;
1097
1098 advance_to::<T>(5);
1099
1100 let mut status = Status::<T>::get().unwrap();
1101 status.last_committed_timeslice = 3;
1102 Status::<T>::put(&status);
1103
1104 #[block]
1105 {
1106 Broker::<T>::do_tick();
1107 }
1108
1109 let updated_status = Status::<T>::get().unwrap();
1110 assert_eq!(status, updated_status);
1111
1112 Ok(())
1113 }
1114
1115 #[benchmark]
1116 fn force_reserve() -> Result<(), BenchmarkError> {
1117 Configuration::<T>::put(new_config_record::<T>());
1118 let reservation_count = T::MaxReservedCores::get().saturating_sub(1);
1120 setup_reservations::<T>(reservation_count);
1121
1122 setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
1124
1125 let origin =
1126 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1127
1128 Broker::<T>::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap())
1130 .map_err(|_| BenchmarkError::Weightless)?;
1131
1132 let core_count = Status::<T>::get().unwrap().core_count;
1134 Broker::<T>::do_request_core_count(core_count + 1).unwrap();
1135
1136 advance_to::<T>(T::TimeslicePeriod::get().try_into().ok().unwrap());
1137
1138 let schedule = new_schedule();
1139
1140 #[extrinsic_call]
1141 _(origin as T::RuntimeOrigin, schedule.clone(), core_count);
1142
1143 assert_eq!(Reservations::<T>::decode_len().unwrap(), T::MaxReservedCores::get() as usize);
1144 assert_eq!(ForceReservations::<T>::get(), vec![schedule]);
1145
1146 Ok(())
1147 }
1148
1149 #[benchmark]
1150 fn swap_leases() -> Result<(), BenchmarkError> {
1151 let admin_origin =
1152 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1153
1154 let n = (T::MaxLeasedCores::get() / 2) as usize;
1156 let mut leases = vec![LeaseRecordItem { task: 1, until: 10u32.into() }; n];
1157 leases.extend(vec![LeaseRecordItem { task: 2, until: 20u32.into() }; n]);
1158 Leases::<T>::put(BoundedVec::try_from(leases).unwrap());
1159
1160 #[extrinsic_call]
1161 _(admin_origin as T::RuntimeOrigin, 1, 2);
1162
1163 Ok(())
1164 }
1165
1166 #[benchmark]
1167 fn enable_auto_renew() -> Result<(), BenchmarkError> {
1168 let sale_data = setup_and_start_sale::<T>()?;
1169
1170 advance_to::<T>(2);
1171
1172 let sale = SaleInfo::<T>::get().expect("Sale has already started.");
1173 (0..T::MaxAutoRenewals::get() - 1).try_for_each(|indx| -> Result<(), BenchmarkError> {
1175 let task = 1000 + indx;
1176 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
1177 .expect("Failed to get sovereign account");
1178 T::Currency::set_balance(
1180 &caller.clone(),
1181 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1182 );
1183
1184 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1185 .expect("Offer not high enough for configuration.");
1186
1187 Broker::<T>::do_assign(region, None, task, Final)
1188 .map_err(|_| BenchmarkError::Weightless)?;
1189
1190 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
1191
1192 Ok(())
1193 })?;
1194
1195 let caller: T::AccountId =
1196 T::SovereignAccountOf::maybe_convert(2001).expect("Failed to get sovereign account");
1197 T::Currency::set_balance(
1199 &caller.clone(),
1200 T::Currency::minimum_balance()
1201 .saturating_add(sale_data.start_price.saturating_add(sale_data.start_price)),
1202 );
1203
1204 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1206 .expect("Offer not high enough for configuration.");
1207 Broker::<T>::do_assign(region, None, 2001, Final)
1208 .map_err(|_| BenchmarkError::Weightless)?;
1209
1210 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
1213 let config = Configuration::<T>::get().expect("Already configured.");
1214 advance_to::<T>(config.region_length * timeslice_period);
1215
1216 #[extrinsic_call]
1217 _(RawOrigin::Signed(caller), region.core, 2001, None);
1218
1219 assert_last_event::<T>(Event::AutoRenewalEnabled { core: region.core, task: 2001 }.into());
1220 let sale = SaleInfo::<T>::get().expect("Sales have started.");
1222 assert!(PotentialRenewals::<T>::get(PotentialRenewalId {
1223 core: region.core,
1224 when: sale.region_end,
1225 })
1226 .is_some());
1227
1228 Ok(())
1229 }
1230
1231 #[benchmark]
1232 fn disable_auto_renew() -> Result<(), BenchmarkError> {
1233 let sale_data = setup_and_start_sale::<T>()?;
1234 let core = sale_data.first_core;
1235
1236 advance_to::<T>(2);
1237
1238 let sale = SaleInfo::<T>::get().expect("Sale has already started.");
1239 (0..T::MaxAutoRenewals::get()).try_for_each(|indx| -> Result<(), BenchmarkError> {
1241 let task = 1000 + indx;
1242 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
1243 .expect("Failed to get sovereign account");
1244 T::Currency::set_balance(
1245 &caller.clone(),
1246 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1247 );
1248
1249 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1250 .expect("Offer not high enough for configuration.");
1251
1252 Broker::<T>::do_assign(region, None, task, Final)
1253 .map_err(|_| BenchmarkError::Weightless)?;
1254
1255 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
1256
1257 Ok(())
1258 })?;
1259
1260 let task = 1000;
1261
1262 let caller: T::AccountId =
1263 T::SovereignAccountOf::maybe_convert(task).expect("Failed to get sovereign account");
1264
1265 #[extrinsic_call]
1266 _(RawOrigin::Signed(caller), core, task);
1267
1268 assert_last_event::<T>(Event::AutoRenewalDisabled { core, task }.into());
1269
1270 Ok(())
1271 }
1272
1273 #[benchmark]
1274 fn on_new_timeslice() -> Result<(), BenchmarkError> {
1275 let sale_data = setup_and_start_sale::<T>()?;
1276
1277 advance_to::<T>(2);
1278
1279 let caller: T::AccountId = whitelisted_caller();
1280 T::Currency::set_balance(
1281 &caller.clone(),
1282 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1283 );
1284
1285 let _region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1286 .expect("Offer not high enough for configuration.");
1287
1288 let timeslice = Broker::<T>::current_timeslice();
1289
1290 #[block]
1291 {
1292 T::Coretime::on_new_timeslice(timeslice);
1293 }
1294
1295 Ok(())
1296 }
1297
1298 #[benchmark]
1299 fn remove_assignment() -> Result<(), BenchmarkError> {
1300 let sale_data = setup_and_start_sale::<T>()?;
1301
1302 advance_to::<T>(2);
1303
1304 let caller: T::AccountId = whitelisted_caller();
1305 T::Currency::set_balance(
1306 &caller.clone(),
1307 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1308 );
1309
1310 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1311 .expect("Offer not high enough for configuration.");
1312
1313 Broker::<T>::do_assign(region, None, 1000, Provisional)
1314 .map_err(|_| BenchmarkError::Weightless)?;
1315
1316 let origin =
1317 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1318
1319 #[extrinsic_call]
1320 _(origin as T::RuntimeOrigin, region);
1321
1322 Ok(())
1323 }
1324
1325 #[benchmark]
1326 fn remove_potential_renewal() -> Result<(), BenchmarkError> {
1327 let sale_data = setup_and_start_sale::<T>()?;
1328 advance_to::<T>(2);
1329
1330 let caller: T::AccountId = whitelisted_caller();
1331 T::Currency::set_balance(
1332 &caller.clone(),
1333 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1334 );
1335
1336 let region_id = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1337 .expect("Offer not high enough for configuration.");
1338 let region = Regions::<T>::get(region_id).expect("Requested region not found");
1339
1340 Broker::<T>::do_assign(region_id, None, 1000, Final)
1341 .map_err(|_| BenchmarkError::Weightless)?;
1342
1343 let origin =
1344 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1345
1346 #[extrinsic_call]
1347 _(origin as T::RuntimeOrigin, region_id.core, region.end);
1348
1349 assert_last_event::<T>(
1350 Event::PotentialRenewalRemoved { core: region_id.core, timeslice: region.end }.into(),
1351 );
1352
1353 Ok(())
1354 }
1355
1356 #[benchmark]
1357 fn force_transfer() -> Result<(), BenchmarkError> {
1358 let sale_data = setup_and_start_sale::<T>()?;
1359 advance_to::<T>(2);
1360
1361 let caller: T::AccountId = whitelisted_caller();
1362 T::Currency::set_balance(
1363 &caller.clone(),
1364 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1365 );
1366
1367 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1368 .expect("Offer not high enough for configuration.");
1369
1370 let recipient: T::AccountId = account("recipient", 0, SEED);
1371
1372 let origin =
1373 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1374
1375 #[extrinsic_call]
1376 _(origin, region, recipient.clone());
1377
1378 assert_last_event::<T>(
1379 Event::Transferred {
1380 region_id: region,
1381 old_owner: Some(caller),
1382 owner: Some(recipient),
1383 duration: 3u32.into(),
1384 }
1385 .into(),
1386 );
1387
1388 Ok(())
1389 }
1390
1391 impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
1394}