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 T::Currency::set_balance(
572 &Broker::<T>::account_id(),
573 T::Currency::minimum_balance().saturating_add(200_000_000u32.into()),
574 );
575
576 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
577 .expect("Offer not high enough for configuration.");
578
579 let recipient: T::AccountId = account("recipient", 0, SEED);
580 T::Currency::set_balance(&recipient.clone(), T::Currency::minimum_balance());
581
582 Broker::<T>::do_pool(region, None, recipient.clone(), Final)
583 .map_err(|_| BenchmarkError::Weightless)?;
584
585 let revenue = 10_000_000u32.into();
586 InstaPoolHistory::<T>::insert(
587 region.begin,
588 InstaPoolHistoryRecord {
589 private_contributions: 4u32.into(),
590 system_contributions: 3u32.into(),
591 maybe_payout: Some(revenue),
592 },
593 );
594
595 #[extrinsic_call]
596 _(RawOrigin::Signed(caller), region, m);
597
598 assert!(InstaPoolHistory::<T>::get(region.begin).is_none());
599 assert_last_event::<T>(
600 Event::RevenueClaimPaid {
601 who: recipient,
602 amount: 200_000_000u32.into(),
603 next: if m < new_config_record::<T>().region_length {
604 Some(RegionId {
605 begin: region.begin.saturating_add(m),
606 core,
607 mask: CoreMask::complete(),
608 })
609 } else {
610 None
611 },
612 }
613 .into(),
614 );
615
616 Ok(())
617 }
618
619 #[benchmark]
620 fn purchase_credit() -> Result<(), BenchmarkError> {
621 setup_and_start_sale::<T>()?;
622
623 advance_to::<T>(2);
624
625 let caller: T::AccountId = whitelisted_caller();
626 T::Currency::set_balance(
627 &caller.clone(),
628 T::Currency::minimum_balance().saturating_add(T::MinimumCreditPurchase::get()),
629 );
630 T::Currency::set_balance(&Broker::<T>::account_id(), T::Currency::minimum_balance());
631
632 let beneficiary: RelayAccountIdOf<T> = account("beneficiary", 0, SEED);
633
634 #[extrinsic_call]
635 _(RawOrigin::Signed(caller.clone()), T::MinimumCreditPurchase::get(), beneficiary.clone());
636
637 assert_last_event::<T>(
638 Event::CreditPurchased {
639 who: caller,
640 beneficiary,
641 amount: T::MinimumCreditPurchase::get(),
642 }
643 .into(),
644 );
645
646 Ok(())
647 }
648
649 #[benchmark]
650 fn drop_region() -> Result<(), BenchmarkError> {
651 let sale_data = setup_and_start_sale::<T>()?;
652 let core = sale_data.first_core;
653 let region_len = Configuration::<T>::get().unwrap().region_length;
654
655 advance_to::<T>(2);
656
657 let caller: T::AccountId = whitelisted_caller();
658 T::Currency::set_balance(
659 &caller.clone(),
660 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
661 );
662
663 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
664 .expect("Offer not high enough for configuration.");
665
666 advance_to::<T>(
667 (T::TimeslicePeriod::get() * (region_len * 4).into()).try_into().ok().unwrap(),
668 );
669
670 #[extrinsic_call]
671 _(RawOrigin::Signed(caller), region);
672
673 assert_last_event::<T>(
674 Event::RegionDropped {
675 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
676 duration: 3u32.into(),
677 }
678 .into(),
679 );
680
681 Ok(())
682 }
683
684 #[benchmark]
685 fn drop_contribution() -> Result<(), BenchmarkError> {
686 let sale_data = setup_and_start_sale::<T>()?;
687 let core = sale_data.first_core;
688 let region_len = Configuration::<T>::get().unwrap().region_length;
689
690 advance_to::<T>(2);
691
692 let caller: T::AccountId = whitelisted_caller();
693 T::Currency::set_balance(
694 &caller.clone(),
695 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
696 );
697
698 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
699 .expect("Offer not high enough for configuration.");
700
701 let recipient: T::AccountId = account("recipient", 0, SEED);
702
703 Broker::<T>::do_pool(region, None, recipient, Final)
704 .map_err(|_| BenchmarkError::Weightless)?;
705
706 advance_to::<T>(
707 (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(),
708 );
709
710 #[extrinsic_call]
711 _(RawOrigin::Signed(caller), region);
712
713 assert_last_event::<T>(
714 Event::ContributionDropped {
715 region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() },
716 }
717 .into(),
718 );
719
720 Ok(())
721 }
722
723 #[benchmark]
724 fn drop_history() -> Result<(), BenchmarkError> {
725 setup_and_start_sale::<T>()?;
726 let when = 5u32.into();
727 let revenue = 10_000_000u32.into();
728 let region_len = Configuration::<T>::get().unwrap().region_length;
729
730 advance_to::<T>(
731 (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(),
732 );
733
734 let caller: T::AccountId = whitelisted_caller();
735 InstaPoolHistory::<T>::insert(
736 when,
737 InstaPoolHistoryRecord {
738 private_contributions: 4u32.into(),
739 system_contributions: 3u32.into(),
740 maybe_payout: Some(revenue),
741 },
742 );
743
744 #[extrinsic_call]
745 _(RawOrigin::Signed(caller), when);
746
747 assert!(InstaPoolHistory::<T>::get(when).is_none());
748 assert_last_event::<T>(Event::HistoryDropped { when, revenue }.into());
749
750 Ok(())
751 }
752
753 #[benchmark]
754 fn drop_renewal() -> Result<(), BenchmarkError> {
755 let sale_data = setup_and_start_sale::<T>()?;
756 let core = sale_data.first_core;
757 let when = 5u32.into();
758 let region_len = Configuration::<T>::get().unwrap().region_length;
759
760 advance_to::<T>(
761 (T::TimeslicePeriod::get() * (region_len * 3).into()).try_into().ok().unwrap(),
762 );
763
764 let id = PotentialRenewalId { core, when };
765 let record = PotentialRenewalRecord {
766 price: 1_000_000u32.into(),
767 completion: CompletionStatus::Complete(new_schedule()),
768 };
769 PotentialRenewals::<T>::insert(id, record);
770
771 let caller: T::AccountId = whitelisted_caller();
772
773 #[extrinsic_call]
774 _(RawOrigin::Signed(caller), core, when);
775
776 assert!(PotentialRenewals::<T>::get(id).is_none());
777 assert_last_event::<T>(Event::PotentialRenewalDropped { core, when }.into());
778
779 Ok(())
780 }
781
782 #[benchmark]
783 fn request_core_count(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
784 let admin_origin =
785 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
786
787 #[extrinsic_call]
788 _(admin_origin as T::RuntimeOrigin, n.try_into().unwrap());
789
790 assert_last_event::<T>(
791 Event::CoreCountRequested { core_count: n.try_into().unwrap() }.into(),
792 );
793
794 Ok(())
795 }
796
797 #[benchmark]
798 fn process_core_count(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
799 setup_and_start_sale::<T>()?;
800
801 let core_count = n.try_into().unwrap();
802
803 CoreCountInbox::<T>::put(core_count);
804
805 let mut status = Status::<T>::get().ok_or(BenchmarkError::Weightless)?;
806
807 #[block]
808 {
809 Broker::<T>::process_core_count(&mut status);
810 }
811
812 assert_last_event::<T>(Event::CoreCountChanged { core_count }.into());
813
814 Ok(())
815 }
816
817 #[benchmark]
818 fn process_revenue() -> Result<(), BenchmarkError> {
819 setup_and_start_sale::<T>()?;
820
821 advance_to::<T>(2);
822
823 let caller: T::AccountId = whitelisted_caller();
824 T::Currency::set_balance(
825 &caller.clone(),
826 T::Currency::minimum_balance().saturating_add(30_000_000u32.into()),
827 );
828 T::Currency::set_balance(
829 &Broker::<T>::account_id(),
830 T::Currency::minimum_balance().saturating_add(90_000_000u32.into()),
831 );
832
833 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
834 let multiplicator = 5;
835
836 RevenueInbox::<T>::put(OnDemandRevenueRecord {
837 until: (timeslice_period * multiplicator).into(),
838 amount: 10_000_000u32.into(),
839 });
840
841 let timeslice = multiplicator - 1;
842 InstaPoolHistory::<T>::insert(
843 timeslice,
844 InstaPoolHistoryRecord {
845 private_contributions: 4u32.into(),
846 system_contributions: 6u32.into(),
847 maybe_payout: None,
848 },
849 );
850
851 #[block]
852 {
853 Broker::<T>::process_revenue();
854 }
855
856 assert_last_event::<T>(
857 Event::ClaimsReady {
858 when: timeslice.into(),
859 system_payout: 6_000_000u32.into(),
860 private_payout: 4_000_000u32.into(),
861 }
862 .into(),
863 );
864
865 Ok(())
866 }
867
868 #[benchmark]
869 fn rotate_sale(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
870 let config = new_config_record::<T>();
871 Configuration::<T>::put(config.clone());
872
873 let n_reservations = T::MaxReservedCores::get().min(n.saturating_sub(1));
878 setup_reservations::<T>(n_reservations);
879 let n_leases =
881 T::MaxLeasedCores::get().min(n.saturating_sub(1).saturating_sub(n_reservations));
882 setup_leases::<T>(n_leases, 1, 20);
883
884 let initial_price = 10_000_000u32.into();
886 let (start_price, _) = get_start_end_price::<T>(initial_price);
887 Broker::<T>::do_start_sales(
888 initial_price,
889 n.saturating_sub(n_reservations)
890 .saturating_sub(n_leases)
891 .try_into()
892 .expect("Upper limit of n is a u16."),
893 )
894 .expect("Configuration was initialized before; qed");
895
896 advance_to::<T>(2);
898
899 let n_renewable = T::MaxAutoRenewals::get()
902 .min(n.saturating_sub(n_leases).saturating_sub(n_reservations));
903
904 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
905 let sale = SaleInfo::<T>::get().expect("Sale has started.");
906
907 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
908 let price = Broker::<T>::sale_price(&sale, now);
909 (0..n_renewable.into()).try_for_each(|indx| -> Result<(), BenchmarkError> {
910 let task = 1000 + indx;
911 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
912 .expect("Failed to get sovereign account");
913 T::Currency::set_balance(
914 &caller.clone(),
915 T::Currency::minimum_balance()
916 .saturating_add(start_price)
917 .saturating_add(start_price),
918 );
919
920 let region = Broker::<T>::do_purchase(caller.clone(), start_price)
921 .expect("Offer not high enough for configuration.");
922
923 Broker::<T>::do_assign(region, None, task, Final)
924 .map_err(|_| BenchmarkError::Weightless)?;
925
926 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
927
928 Ok(())
929 })?;
930
931 let rotate_block = timeslice_period.saturating_mul(config.region_length) - 2;
933 advance_to::<T>(rotate_block - 1);
934
935 System::<T>::set_block_number(rotate_block.into());
937 RCBlockNumberProviderOf::<T::Coretime>::set_block_number(rotate_block.into());
938 let mut status = Status::<T>::get().expect("Sale has started.");
939 let sale = SaleInfo::<T>::get().expect("Sale has started.");
940 Broker::<T>::process_core_count(&mut status);
941 Broker::<T>::process_revenue();
942 status.last_committed_timeslice = config.region_length;
943
944 #[block]
945 {
946 Broker::<T>::rotate_sale(sale.clone(), &config, &status);
947 }
948
949 let new_prices = T::PriceAdapter::adapt_price(SalePerformance::from_sale(&sale));
951 let new_sale = SaleInfo::<T>::get().expect("Sale has started.");
952 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
953 let sale_start = config.interlude_length.saturating_add(rotate_block.into());
954
955 assert_has_event::<T>(
956 Event::SaleInitialized {
957 sale_start,
958 leadin_length: 1u32.into(),
959 start_price: Broker::<T>::sale_price(&new_sale, now),
960 end_price: new_prices.end_price,
961 region_begin: sale.region_begin + config.region_length,
962 region_end: sale.region_end + config.region_length,
963 ideal_cores_sold: 0,
964 cores_offered: n
965 .saturating_sub(n_reservations)
966 .saturating_sub(n_leases)
967 .try_into()
968 .unwrap(),
969 sale_index: sale.sale_index.saturating_add(1),
970 }
971 .into(),
972 );
973
974 (0..n_renewable).for_each(|indx| {
976 let task = 1000 + indx;
977 let who = T::SovereignAccountOf::maybe_convert(task)
978 .expect("Failed to get sovereign account");
979 assert_has_event::<T>(
980 Event::Renewed {
981 who,
982 old_core: n_reservations as u16 + n_leases as u16 + indx as u16,
983 core: n_reservations as u16 + n_leases as u16 + indx as u16,
984 price,
985 begin: new_sale.region_begin,
986 duration: config.region_length,
987 workload: Schedule::truncate_from(vec![ScheduleItem {
988 assignment: Task(task),
989 mask: CoreMask::complete(),
990 }]),
991 }
992 .into(),
993 );
994 });
995
996 Ok(())
997 }
998
999 #[benchmark]
1000 fn process_pool() {
1001 let when = 10u32.into();
1002 let private_pool_size = 5u32.into();
1003 let system_pool_size = 4u32.into();
1004
1005 let config = new_config_record::<T>();
1006 let commit_timeslice = Broker::<T>::latest_timeslice_ready_to_commit(&config);
1007 let mut status = StatusRecord {
1008 core_count: 5u16.into(),
1009 private_pool_size,
1010 system_pool_size,
1011 last_committed_timeslice: commit_timeslice.saturating_sub(1),
1012 last_timeslice: Broker::<T>::current_timeslice(),
1013 };
1014
1015 #[block]
1016 {
1017 Broker::<T>::process_pool(when, &mut status);
1018 }
1019
1020 assert!(InstaPoolHistory::<T>::get(when).is_some());
1021 assert_last_event::<T>(
1022 Event::HistoryInitialized { when, private_pool_size, system_pool_size }.into(),
1023 );
1024 }
1025
1026 #[benchmark]
1027 fn process_core_schedule() {
1028 let timeslice = 10u32.into();
1029 let core = 5u16.into();
1030 let rc_begin = 1u32.into();
1031
1032 Workplan::<T>::insert((timeslice, core), new_schedule());
1033
1034 #[block]
1035 {
1036 Broker::<T>::process_core_schedule(timeslice, rc_begin, core);
1037 }
1038
1039 assert_eq!(Workload::<T>::get(core).len(), CORE_MASK_BITS);
1040
1041 let mut assignment: Vec<(CoreAssignment, PartsOf57600)> = vec![];
1042 for i in 0..CORE_MASK_BITS {
1043 assignment.push((CoreAssignment::Task(i.try_into().unwrap()), 57600));
1044 }
1045 assert_last_event::<T>(Event::CoreAssigned { core, when: rc_begin, assignment }.into());
1046 }
1047
1048 #[benchmark]
1049 fn request_revenue_info_at() {
1050 let current_timeslice = Broker::<T>::current_timeslice();
1051 let rc_block = T::TimeslicePeriod::get() * current_timeslice.into();
1052
1053 #[block]
1054 {
1055 T::Coretime::request_revenue_info_at(rc_block);
1056 }
1057 }
1058
1059 #[benchmark]
1060 fn notify_core_count() -> Result<(), BenchmarkError> {
1061 let admin_origin =
1062 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1063
1064 #[extrinsic_call]
1065 _(admin_origin as T::RuntimeOrigin, 100);
1066
1067 assert!(CoreCountInbox::<T>::take().is_some());
1068 Ok(())
1069 }
1070
1071 #[benchmark]
1072 fn notify_revenue() -> Result<(), BenchmarkError> {
1073 let admin_origin =
1074 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1075
1076 #[extrinsic_call]
1077 _(
1078 admin_origin as T::RuntimeOrigin,
1079 OnDemandRevenueRecord { until: 100u32.into(), amount: 100_000_000u32.into() },
1080 );
1081
1082 assert!(RevenueInbox::<T>::take().is_some());
1083 Ok(())
1084 }
1085
1086 #[benchmark]
1087 fn do_tick_base() -> Result<(), BenchmarkError> {
1088 setup_and_start_sale::<T>()?;
1089
1090 advance_to::<T>(5);
1091
1092 let mut status = Status::<T>::get().unwrap();
1093 status.last_committed_timeslice = 3;
1094 Status::<T>::put(&status);
1095
1096 #[block]
1097 {
1098 Broker::<T>::do_tick();
1099 }
1100
1101 let updated_status = Status::<T>::get().unwrap();
1102 assert_eq!(status, updated_status);
1103
1104 Ok(())
1105 }
1106
1107 #[benchmark]
1108 fn force_reserve() -> Result<(), BenchmarkError> {
1109 Configuration::<T>::put(new_config_record::<T>());
1110 let reservation_count = T::MaxReservedCores::get().saturating_sub(1);
1112 setup_reservations::<T>(reservation_count);
1113
1114 setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
1116
1117 let origin =
1118 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1119
1120 Broker::<T>::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap())
1122 .map_err(|_| BenchmarkError::Weightless)?;
1123
1124 let core_count = Status::<T>::get().unwrap().core_count;
1126 Broker::<T>::do_request_core_count(core_count + 1).unwrap();
1127
1128 advance_to::<T>(T::TimeslicePeriod::get().try_into().ok().unwrap());
1129
1130 let schedule = new_schedule();
1131
1132 #[extrinsic_call]
1133 _(origin as T::RuntimeOrigin, schedule.clone(), core_count);
1134
1135 assert_eq!(Reservations::<T>::decode_len().unwrap(), T::MaxReservedCores::get() as usize);
1136 assert_eq!(ForceReservations::<T>::get(), vec![schedule]);
1137
1138 Ok(())
1139 }
1140
1141 #[benchmark]
1142 fn swap_leases() -> Result<(), BenchmarkError> {
1143 let admin_origin =
1144 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1145
1146 let n = (T::MaxLeasedCores::get() / 2) as usize;
1148 let mut leases = vec![LeaseRecordItem { task: 1, until: 10u32.into() }; n];
1149 leases.extend(vec![LeaseRecordItem { task: 2, until: 20u32.into() }; n]);
1150 Leases::<T>::put(BoundedVec::try_from(leases).unwrap());
1151
1152 #[extrinsic_call]
1153 _(admin_origin as T::RuntimeOrigin, 1, 2);
1154
1155 Ok(())
1156 }
1157
1158 #[benchmark]
1159 fn enable_auto_renew() -> Result<(), BenchmarkError> {
1160 let sale_data = setup_and_start_sale::<T>()?;
1161
1162 advance_to::<T>(2);
1163
1164 let sale = SaleInfo::<T>::get().expect("Sale has already started.");
1165 (0..T::MaxAutoRenewals::get() - 1).try_for_each(|indx| -> Result<(), BenchmarkError> {
1167 let task = 1000 + indx;
1168 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
1169 .expect("Failed to get sovereign account");
1170 T::Currency::set_balance(
1172 &caller.clone(),
1173 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1174 );
1175
1176 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1177 .expect("Offer not high enough for configuration.");
1178
1179 Broker::<T>::do_assign(region, None, task, Final)
1180 .map_err(|_| BenchmarkError::Weightless)?;
1181
1182 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
1183
1184 Ok(())
1185 })?;
1186
1187 let caller: T::AccountId =
1188 T::SovereignAccountOf::maybe_convert(2001).expect("Failed to get sovereign account");
1189 T::Currency::set_balance(
1191 &caller.clone(),
1192 T::Currency::minimum_balance()
1193 .saturating_add(sale_data.start_price.saturating_add(sale_data.start_price)),
1194 );
1195
1196 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1198 .expect("Offer not high enough for configuration.");
1199 Broker::<T>::do_assign(region, None, 2001, Final)
1200 .map_err(|_| BenchmarkError::Weightless)?;
1201
1202 let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap();
1205 let config = Configuration::<T>::get().expect("Already configured.");
1206 advance_to::<T>(config.region_length * timeslice_period);
1207
1208 #[extrinsic_call]
1209 _(RawOrigin::Signed(caller), region.core, 2001, None);
1210
1211 assert_last_event::<T>(Event::AutoRenewalEnabled { core: region.core, task: 2001 }.into());
1212 let sale = SaleInfo::<T>::get().expect("Sales have started.");
1214 assert!(PotentialRenewals::<T>::get(PotentialRenewalId {
1215 core: region.core,
1216 when: sale.region_end,
1217 })
1218 .is_some());
1219
1220 Ok(())
1221 }
1222
1223 #[benchmark]
1224 fn disable_auto_renew() -> Result<(), BenchmarkError> {
1225 let sale_data = setup_and_start_sale::<T>()?;
1226 let core = sale_data.first_core;
1227
1228 advance_to::<T>(2);
1229
1230 let sale = SaleInfo::<T>::get().expect("Sale has already started.");
1231 (0..T::MaxAutoRenewals::get()).try_for_each(|indx| -> Result<(), BenchmarkError> {
1233 let task = 1000 + indx;
1234 let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task)
1235 .expect("Failed to get sovereign account");
1236 T::Currency::set_balance(
1237 &caller.clone(),
1238 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1239 );
1240
1241 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1242 .expect("Offer not high enough for configuration.");
1243
1244 Broker::<T>::do_assign(region, None, task, Final)
1245 .map_err(|_| BenchmarkError::Weightless)?;
1246
1247 Broker::<T>::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?;
1248
1249 Ok(())
1250 })?;
1251
1252 let task = 1000;
1253
1254 let caller: T::AccountId =
1255 T::SovereignAccountOf::maybe_convert(task).expect("Failed to get sovereign account");
1256
1257 #[extrinsic_call]
1258 _(RawOrigin::Signed(caller), core, task);
1259
1260 assert_last_event::<T>(Event::AutoRenewalDisabled { core, task }.into());
1261
1262 Ok(())
1263 }
1264
1265 #[benchmark]
1266 fn on_new_timeslice() -> Result<(), BenchmarkError> {
1267 let sale_data = setup_and_start_sale::<T>()?;
1268
1269 advance_to::<T>(2);
1270
1271 let caller: T::AccountId = whitelisted_caller();
1272 T::Currency::set_balance(
1273 &caller.clone(),
1274 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1275 );
1276
1277 let _region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1278 .expect("Offer not high enough for configuration.");
1279
1280 let timeslice = Broker::<T>::current_timeslice();
1281
1282 #[block]
1283 {
1284 T::Coretime::on_new_timeslice(timeslice);
1285 }
1286
1287 Ok(())
1288 }
1289
1290 #[benchmark]
1291 fn remove_assignment() -> Result<(), BenchmarkError> {
1292 let sale_data = setup_and_start_sale::<T>()?;
1293
1294 advance_to::<T>(2);
1295
1296 let caller: T::AccountId = whitelisted_caller();
1297 T::Currency::set_balance(
1298 &caller.clone(),
1299 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1300 );
1301
1302 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1303 .expect("Offer not high enough for configuration.");
1304
1305 Broker::<T>::do_assign(region, None, 1000, Provisional)
1306 .map_err(|_| BenchmarkError::Weightless)?;
1307
1308 let origin =
1309 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1310
1311 #[extrinsic_call]
1312 _(origin as T::RuntimeOrigin, region);
1313
1314 Ok(())
1315 }
1316
1317 #[benchmark]
1318 fn remove_potential_renewal() -> Result<(), BenchmarkError> {
1319 let sale_data = setup_and_start_sale::<T>()?;
1320 advance_to::<T>(2);
1321
1322 let caller: T::AccountId = whitelisted_caller();
1323 T::Currency::set_balance(
1324 &caller.clone(),
1325 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1326 );
1327
1328 let region_id = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1329 .expect("Offer not high enough for configuration.");
1330 let region = Regions::<T>::get(region_id).expect("Requested region not found");
1331
1332 Broker::<T>::do_assign(region_id, None, 1000, Final)
1333 .map_err(|_| BenchmarkError::Weightless)?;
1334
1335 let origin =
1336 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1337
1338 #[extrinsic_call]
1339 _(origin as T::RuntimeOrigin, region_id.core, region.end);
1340
1341 assert_last_event::<T>(
1342 Event::PotentialRenewalRemoved { core: region_id.core, timeslice: region.end }.into(),
1343 );
1344
1345 Ok(())
1346 }
1347
1348 #[benchmark]
1349 fn force_transfer() -> Result<(), BenchmarkError> {
1350 let sale_data = setup_and_start_sale::<T>()?;
1351 advance_to::<T>(2);
1352
1353 let caller: T::AccountId = whitelisted_caller();
1354 T::Currency::set_balance(
1355 &caller.clone(),
1356 T::Currency::minimum_balance().saturating_add(sale_data.start_price),
1357 );
1358
1359 let region = Broker::<T>::do_purchase(caller.clone(), sale_data.start_price)
1360 .expect("Offer not high enough for configuration.");
1361
1362 let recipient: T::AccountId = account("recipient", 0, SEED);
1363
1364 let origin =
1365 T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1366
1367 #[extrinsic_call]
1368 _(origin, region, recipient.clone());
1369
1370 assert_last_event::<T>(
1371 Event::Transferred {
1372 region_id: region,
1373 old_owner: Some(caller),
1374 owner: Some(recipient),
1375 duration: 3u32.into(),
1376 }
1377 .into(),
1378 );
1379
1380 Ok(())
1381 }
1382
1383 impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
1386}