referrerpolicy=no-referrer-when-downgrade

pallet_broker/
benchmarking.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![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	// Max items for worst case
66	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	// Assume Reservations to be filled for worst case
110	setup_reservations::<T>(T::MaxReservedCores::get());
111
112	// Assume Leases to be filled for worst case
113	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		// Assume Reservations to be almost filled for worst case
171		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		// Assume Reservations to be filled for worst case
187		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		// Assume Leases to be almost filled for worst case
209		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		// Assume Leases to be almost filled for worst case
228		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		// Assume Reservations to be filled for worst case
255		setup_reservations::<T>(extra_cores.min(T::MaxReservedCores::get()));
256		extra_cores = extra_cores.saturating_sub(T::MaxReservedCores::get());
257
258		// Assume Leases to be filled for worst case
259		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		// Worst case has an existing provisional pool assignment.
408		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		// Worst case has an existing provisional pool assignment.
457		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		// Worst case has an existing provisional pool assignment.
498		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		// Ensure there is one buyable core then use the rest to max out reservations and leases, if
873		// possible for worst case.
874
875		// First allocate up to MaxReservedCores for reservations
876		let n_reservations = T::MaxReservedCores::get().min(n.saturating_sub(1));
877		setup_reservations::<T>(n_reservations);
878		// Then allocate remaining cores to leases, up to MaxLeasedCores
879		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		// Start sales so we can test the auto-renewals.
884		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 the fixed price period.
896		advance_to::<T>(2);
897
898		// Assume max auto renewals for worst case. This is between 1 and the value of
899		// MaxAutoRenewals.
900		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		// Advance to the block before the rotate_sale in which the auto-renewals will take place.
931		let rotate_block = timeslice_period.saturating_mul(config.region_length) - 2;
932		advance_to::<T>(rotate_block - 1);
933
934		// Advance one block and manually tick so we can isolate the `rotate_sale` call.
935		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		// Get prices from the actual price adapter.
949		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		// Make sure all cores got renewed:
973		(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		// Assume Reservations to be almost filled for worst case.
1109		let reservation_count = T::MaxReservedCores::get().saturating_sub(1);
1110		setup_reservations::<T>(reservation_count);
1111
1112		// Assume leases to be filled for worst case
1113		setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
1114
1115		let origin =
1116			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1117
1118		// Sales must be started.
1119		Broker::<T>::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap())
1120			.map_err(|_| BenchmarkError::Weightless)?;
1121
1122		// Add a core.
1123		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		// We called at timeslice 1, therefore 2 was already processed and 3 is the next possible
1140		// assignment point.
1141		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		// Add two leases in `Leases`
1152		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		// We assume max auto renewals for worst case.
1171		(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			// Sovereign account needs sufficient funds to purchase and renew.
1176			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		// Sovereign account needs sufficient funds to purchase and renew.
1195		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		// The region for which we benchmark enable auto renew.
1202		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		// The most 'intensive' path is when we renew the core upon enabling auto-renewal.
1208		// Therefore, we advance to next bulk sale:
1209		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		// Make sure we indeed renewed:
1218		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		// We assume max auto renewals for worst case.
1237		(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	// Implements a test for each benchmark. Execute with:
1323	// `cargo test -p pallet-broker --features runtime-benchmarks`.
1324	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
1325}