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				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		// Worst case has an existing provisional pool assignment.
409		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		// Worst case has an existing provisional pool assignment.
458		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		// Worst case has an existing provisional pool assignment.
499		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		// Ensure there is one buyable core then use the rest to max out reservations and leases, if
883		// possible for worst case.
884
885		// First allocate up to MaxReservedCores for reservations
886		let n_reservations = T::MaxReservedCores::get().min(n.saturating_sub(1));
887		setup_reservations::<T>(n_reservations);
888		// Then allocate remaining cores to leases, up to MaxLeasedCores
889		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		// Start sales so we can test the auto-renewals.
894		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 the fixed price period.
906		advance_to::<T>(2);
907
908		// Assume max auto renewals for worst case. This is between 1 and the value of
909		// MaxAutoRenewals.
910		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		// Advance to the block before the rotate_sale in which the auto-renewals will take place.
941		let rotate_block = timeslice_period.saturating_mul(config.region_length) - 2;
942		advance_to::<T>(rotate_block - 1);
943
944		// Advance one block and manually tick so we can isolate the `rotate_sale` call.
945		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		// Get prices from the actual price adapter.
958		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		// Make sure all cores got renewed:
983		(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		// Assume Reservations to be almost filled for worst case.
1119		let reservation_count = T::MaxReservedCores::get().saturating_sub(1);
1120		setup_reservations::<T>(reservation_count);
1121
1122		// Assume leases to be filled for worst case
1123		setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
1124
1125		let origin =
1126			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
1127
1128		// Sales must be started.
1129		Broker::<T>::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap())
1130			.map_err(|_| BenchmarkError::Weightless)?;
1131
1132		// Add a core.
1133		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		// Add two leases in `Leases`
1155		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		// We assume max auto renewals for worst case.
1174		(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			// Sovereign account needs sufficient funds to purchase and renew.
1179			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		// Sovereign account needs sufficient funds to purchase and renew.
1198		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		// The region for which we benchmark enable auto renew.
1205		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		// The most 'intensive' path is when we renew the core upon enabling auto-renewal.
1211		// Therefore, we advance to next bulk sale:
1212		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		// Make sure we indeed renewed:
1221		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		// We assume max auto renewals for worst case.
1240		(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	// Implements a test for each benchmark. Execute with:
1392	// `cargo test -p pallet-broker --features runtime-benchmarks`.
1393	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
1394}