pezpallet_broker/
lib.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
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_attr(not(feature = "std"), no_std)]
19#![doc = include_str!("../README.md")]
20
21pub use pezpallet::*;
22
23mod adapt_price;
24mod benchmarking;
25mod core_mask;
26mod coretime_interface;
27mod dispatchable_impls;
28#[cfg(test)]
29mod mock;
30mod nonfungible_impl;
31#[cfg(test)]
32mod test_fungibles;
33#[cfg(test)]
34mod tests;
35mod tick_impls;
36mod types;
37mod utility_impls;
38
39pub mod migration;
40pub mod runtime_api;
41
42pub mod weights;
43pub use weights::WeightInfo;
44
45pub use adapt_price::*;
46pub use core_mask::*;
47pub use coretime_interface::*;
48pub use types::*;
49
50extern crate alloc;
51
52/// The log target for this pezpallet.
53const LOG_TARGET: &str = "runtime::broker";
54
55#[pezframe_support::pezpallet]
56pub mod pezpallet {
57	use super::*;
58	use alloc::vec::Vec;
59	use pezframe_support::{
60		pezpallet_prelude::{DispatchResult, DispatchResultWithPostInfo, *},
61		traits::{
62			fungible::{Balanced, Credit, Mutate},
63			BuildGenesisConfig, EnsureOrigin, OnUnbalanced,
64		},
65		PalletId,
66	};
67	use pezframe_system::pezpallet_prelude::*;
68	use pezsp_runtime::traits::{Convert, ConvertBack, MaybeConvert};
69
70	const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
71
72	#[pezpallet::pezpallet]
73	#[pezpallet::storage_version(STORAGE_VERSION)]
74	pub struct Pezpallet<T>(_);
75
76	#[pezpallet::config]
77	pub trait Config: pezframe_system::Config {
78		#[allow(deprecated)]
79		type RuntimeEvent: From<Event<Self>>
80			+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
81
82		/// Weight information for all calls of this pezpallet.
83		type WeightInfo: WeightInfo;
84
85		/// Currency used to pay for Coretime.
86		type Currency: Mutate<Self::AccountId> + Balanced<Self::AccountId>;
87
88		/// The origin test needed for administrating this pezpallet.
89		type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
90
91		/// What to do with any revenues collected from the sale of Coretime.
92		type OnRevenue: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
93
94		/// Relay chain's Coretime API used to interact with and instruct the low-level scheduling
95		/// system.
96		type Coretime: CoretimeInterface;
97
98		/// The algorithm to determine the next price on the basis of market performance.
99		type PriceAdapter: AdaptPrice<BalanceOf<Self>>;
100
101		/// Reversible conversion from local balance to Relay-chain balance. This will typically be
102		/// the `Identity`, but provided just in case the chains use different representations.
103		type ConvertBalance: Convert<BalanceOf<Self>, RelayBalanceOf<Self>>
104			+ ConvertBack<BalanceOf<Self>, RelayBalanceOf<Self>>;
105
106		/// Type used for getting the associated account of a task. This account is controlled by
107		/// the task itself.
108		type SovereignAccountOf: MaybeConvert<TaskId, Self::AccountId>;
109
110		/// Identifier from which the internal Pot is generated.
111		#[pezpallet::constant]
112		type PalletId: Get<PalletId>;
113
114		/// Number of Relay-chain blocks per timeslice.
115		#[pezpallet::constant]
116		type TimeslicePeriod: Get<RelayBlockNumberOf<Self>>;
117
118		/// Maximum number of legacy leases.
119		#[pezpallet::constant]
120		type MaxLeasedCores: Get<u32>;
121
122		/// Maximum number of system cores.
123		#[pezpallet::constant]
124		type MaxReservedCores: Get<u32>;
125
126		/// Given that we are performing all auto-renewals in a single block, it has to be limited.
127		#[pezpallet::constant]
128		type MaxAutoRenewals: Get<u32>;
129
130		/// The smallest amount of credits a user can purchase.
131		///
132		/// Needed to prevent spam attacks.
133		#[pezpallet::constant]
134		type MinimumCreditPurchase: Get<BalanceOf<Self>>;
135	}
136
137	/// The current configuration of this pezpallet.
138	#[pezpallet::storage]
139	pub type Configuration<T> = StorageValue<_, ConfigRecordOf<T>, OptionQuery>;
140
141	/// The Pezkuwi Core reservations (generally tasked with the maintenance of System Chains).
142	#[pezpallet::storage]
143	pub type Reservations<T> = StorageValue<_, ReservationsRecordOf<T>, ValueQuery>;
144
145	/// The Pezkuwi Core legacy leases.
146	#[pezpallet::storage]
147	pub type Leases<T> = StorageValue<_, LeasesRecordOf<T>, ValueQuery>;
148
149	/// The current status of miscellaneous subsystems of this pezpallet.
150	#[pezpallet::storage]
151	pub type Status<T> = StorageValue<_, StatusRecord, OptionQuery>;
152
153	/// The details of the current sale, including its properties and status.
154	#[pezpallet::storage]
155	pub type SaleInfo<T> = StorageValue<_, SaleInfoRecordOf<T>, OptionQuery>;
156
157	/// Records of potential renewals.
158	///
159	/// Renewals will only actually be allowed if `CompletionStatus` is actually `Complete`.
160	#[pezpallet::storage]
161	pub type PotentialRenewals<T> =
162		StorageMap<_, Twox64Concat, PotentialRenewalId, PotentialRenewalRecordOf<T>, OptionQuery>;
163
164	/// The current (unassigned or provisionally assigend) Regions.
165	#[pezpallet::storage]
166	pub type Regions<T> = StorageMap<_, Blake2_128Concat, RegionId, RegionRecordOf<T>, OptionQuery>;
167
168	/// The work we plan on having each core do at a particular time in the future.
169	#[pezpallet::storage]
170	pub type Workplan<T> =
171		StorageMap<_, Twox64Concat, (Timeslice, CoreIndex), Schedule, OptionQuery>;
172
173	/// The current workload of each core. This gets updated with workplan as timeslices pass.
174	#[pezpallet::storage]
175	pub type Workload<T> = StorageMap<_, Twox64Concat, CoreIndex, Schedule, ValueQuery>;
176
177	/// Record of a single contribution to the Instantaneous Coretime Pool.
178	#[pezpallet::storage]
179	pub type InstaPoolContribution<T> =
180		StorageMap<_, Blake2_128Concat, RegionId, ContributionRecordOf<T>, OptionQuery>;
181
182	/// Record of Coretime entering or leaving the Instantaneous Coretime Pool.
183	#[pezpallet::storage]
184	pub type InstaPoolIo<T> = StorageMap<_, Blake2_128Concat, Timeslice, PoolIoRecord, ValueQuery>;
185
186	/// Total InstaPool rewards for each Timeslice and the number of core parts which contributed.
187	#[pezpallet::storage]
188	pub type InstaPoolHistory<T> =
189		StorageMap<_, Blake2_128Concat, Timeslice, InstaPoolHistoryRecordOf<T>>;
190
191	/// Received core count change from the relay chain.
192	#[pezpallet::storage]
193	pub type CoreCountInbox<T> = StorageValue<_, CoreIndex, OptionQuery>;
194
195	/// Keeping track of cores which have auto-renewal enabled.
196	///
197	/// Sorted by `CoreIndex` to make the removal of cores from auto-renewal more efficient.
198	#[pezpallet::storage]
199	pub type AutoRenewals<T: Config> =
200		StorageValue<_, BoundedVec<AutoRenewalRecord, T::MaxAutoRenewals>, ValueQuery>;
201
202	/// Received revenue info from the relay chain.
203	#[pezpallet::storage]
204	pub type RevenueInbox<T> = StorageValue<_, OnDemandRevenueRecordOf<T>, OptionQuery>;
205
206	#[pezpallet::event]
207	#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
208	pub enum Event<T: Config> {
209		/// A Region of Bulk Coretime has been purchased.
210		Purchased {
211			/// The identity of the purchaser.
212			who: T::AccountId,
213			/// The identity of the Region.
214			region_id: RegionId,
215			/// The price paid for this Region.
216			price: BalanceOf<T>,
217			/// The duration of the Region.
218			duration: Timeslice,
219		},
220		/// The workload of a core has become renewable.
221		Renewable {
222			/// The core whose workload can be renewed.
223			core: CoreIndex,
224			/// The price at which the workload can be renewed.
225			price: BalanceOf<T>,
226			/// The time at which the workload would recommence of this renewal. The call to renew
227			/// cannot happen before the beginning of the interlude prior to the sale for regions
228			/// which begin at this time.
229			begin: Timeslice,
230			/// The actual workload which can be renewed.
231			workload: Schedule,
232		},
233		/// A workload has been renewed.
234		Renewed {
235			/// The identity of the renewer.
236			who: T::AccountId,
237			/// The price paid for this renewal.
238			price: BalanceOf<T>,
239			/// The index of the core on which the `workload` was previously scheduled.
240			old_core: CoreIndex,
241			/// The index of the core on which the renewed `workload` has been scheduled.
242			core: CoreIndex,
243			/// The time at which the `workload` will begin on the `core`.
244			begin: Timeslice,
245			/// The number of timeslices for which this `workload` is newly scheduled.
246			duration: Timeslice,
247			/// The workload which was renewed.
248			workload: Schedule,
249		},
250		/// Ownership of a Region has been transferred.
251		Transferred {
252			/// The Region which has been transferred.
253			region_id: RegionId,
254			/// The duration of the Region.
255			duration: Timeslice,
256			/// The old owner of the Region.
257			old_owner: Option<T::AccountId>,
258			/// The new owner of the Region.
259			owner: Option<T::AccountId>,
260		},
261		/// A Region has been split into two non-overlapping Regions.
262		Partitioned {
263			/// The Region which was split.
264			old_region_id: RegionId,
265			/// The new Regions into which it became.
266			new_region_ids: (RegionId, RegionId),
267		},
268		/// A Region has been converted into two overlapping Regions each of lesser regularity.
269		Interlaced {
270			/// The Region which was interlaced.
271			old_region_id: RegionId,
272			/// The new Regions into which it became.
273			new_region_ids: (RegionId, RegionId),
274		},
275		/// A Region has been assigned to a particular task.
276		Assigned {
277			/// The Region which was assigned.
278			region_id: RegionId,
279			/// The duration of the assignment.
280			duration: Timeslice,
281			/// The task to which the Region was assigned.
282			task: TaskId,
283		},
284		/// An assignment has been removed from the workplan.
285		AssignmentRemoved {
286			/// The Region which was removed from the workplan.
287			region_id: RegionId,
288		},
289		/// A Region has been added to the Instantaneous Coretime Pool.
290		Pooled {
291			/// The Region which was added to the Instantaneous Coretime Pool.
292			region_id: RegionId,
293			/// The duration of the Region.
294			duration: Timeslice,
295		},
296		/// A new number of cores has been requested.
297		CoreCountRequested {
298			/// The number of cores requested.
299			core_count: CoreIndex,
300		},
301		/// The number of cores available for scheduling has changed.
302		CoreCountChanged {
303			/// The new number of cores available for scheduling.
304			core_count: CoreIndex,
305		},
306		/// There is a new reservation for a workload.
307		ReservationMade {
308			/// The index of the reservation.
309			index: u32,
310			/// The workload of the reservation.
311			workload: Schedule,
312		},
313		/// A reservation for a workload has been cancelled.
314		ReservationCancelled {
315			/// The index of the reservation which was cancelled.
316			index: u32,
317			/// The workload of the now cancelled reservation.
318			workload: Schedule,
319		},
320		/// A new sale has been initialized.
321		SaleInitialized {
322			/// The relay block number at which the sale will/did start.
323			sale_start: RelayBlockNumberOf<T>,
324			/// The length in relay chain blocks of the Leadin Period (where the price is
325			/// decreasing).
326			leadin_length: RelayBlockNumberOf<T>,
327			/// The price of Bulk Coretime at the beginning of the Leadin Period.
328			start_price: BalanceOf<T>,
329			/// The price of Bulk Coretime after the Leadin Period.
330			end_price: BalanceOf<T>,
331			/// The first timeslice of the Regions which are being sold in this sale.
332			region_begin: Timeslice,
333			/// The timeslice on which the Regions which are being sold in the sale terminate.
334			/// (i.e. One after the last timeslice which the Regions control.)
335			region_end: Timeslice,
336			/// The number of cores we want to sell, ideally.
337			ideal_cores_sold: CoreIndex,
338			/// Number of cores which are/have been offered for sale.
339			cores_offered: CoreIndex,
340		},
341		/// A new lease has been created.
342		Leased {
343			/// The task to which a core will be assigned.
344			task: TaskId,
345			/// The timeslice contained in the sale period after which this lease will
346			/// self-terminate (and therefore the earliest timeslice at which the lease may no
347			/// longer apply).
348			until: Timeslice,
349		},
350		/// A lease has been removed.
351		LeaseRemoved {
352			/// The task to which a core was assigned.
353			task: TaskId,
354		},
355		/// A lease is about to end.
356		LeaseEnding {
357			/// The task to which a core was assigned.
358			task: TaskId,
359			/// The timeslice at which the task will no longer be scheduled.
360			when: Timeslice,
361		},
362		/// The sale rotation has been started and a new sale is imminent.
363		SalesStarted {
364			/// The nominal price of an Region of Bulk Coretime.
365			price: BalanceOf<T>,
366			/// The maximum number of cores which this pezpallet will attempt to assign.
367			core_count: CoreIndex,
368		},
369		/// The act of claiming revenue has begun.
370		RevenueClaimBegun {
371			/// The region to be claimed for.
372			region: RegionId,
373			/// The maximum number of timeslices which should be searched for claimed.
374			max_timeslices: Timeslice,
375		},
376		/// A particular timeslice has a non-zero claim.
377		RevenueClaimItem {
378			/// The timeslice whose claim is being processed.
379			when: Timeslice,
380			/// The amount which was claimed at this timeslice.
381			amount: BalanceOf<T>,
382		},
383		/// A revenue claim has (possibly only in part) been paid.
384		RevenueClaimPaid {
385			/// The account to whom revenue has been paid.
386			who: T::AccountId,
387			/// The total amount of revenue claimed and paid.
388			amount: BalanceOf<T>,
389			/// The next region which should be claimed for the continuation of this contribution.
390			next: Option<RegionId>,
391		},
392		/// Some Instantaneous Coretime Pool credit has been purchased.
393		CreditPurchased {
394			/// The account which purchased the credit.
395			who: T::AccountId,
396			/// The Relay-chain account to which the credit will be made.
397			beneficiary: RelayAccountIdOf<T>,
398			/// The amount of credit purchased.
399			amount: BalanceOf<T>,
400		},
401		/// A Region has been dropped due to being out of date.
402		RegionDropped {
403			/// The Region which no longer exists.
404			region_id: RegionId,
405			/// The duration of the Region.
406			duration: Timeslice,
407		},
408		/// Some historical Instantaneous Core Pool contribution record has been dropped.
409		ContributionDropped {
410			/// The Region whose contribution is no longer exists.
411			region_id: RegionId,
412		},
413		/// A region has been force-removed from the pool. This is usually due to a provisionally
414		/// pooled region being redeployed.
415		RegionUnpooled {
416			/// The Region which has been force-removed from the pool.
417			region_id: RegionId,
418			/// The timeslice at which the region was force-removed.
419			when: Timeslice,
420		},
421		/// Some historical Instantaneous Core Pool payment record has been initialized.
422		HistoryInitialized {
423			/// The timeslice whose history has been initialized.
424			when: Timeslice,
425			/// The amount of privately contributed Coretime to the Instantaneous Coretime Pool.
426			private_pool_size: CoreMaskBitCount,
427			/// The amount of Coretime contributed to the Instantaneous Coretime Pool by the
428			/// Pezkuwi System.
429			system_pool_size: CoreMaskBitCount,
430		},
431		/// Some historical Instantaneous Core Pool payment record has been dropped.
432		HistoryDropped {
433			/// The timeslice whose history is no longer available.
434			when: Timeslice,
435			/// The amount of revenue the system has taken.
436			revenue: BalanceOf<T>,
437		},
438		/// Some historical Instantaneous Core Pool payment record has been ignored because the
439		/// timeslice was already known. Governance may need to intervene.
440		HistoryIgnored {
441			/// The timeslice whose history is was ignored.
442			when: Timeslice,
443			/// The amount of revenue which was ignored.
444			revenue: BalanceOf<T>,
445		},
446		/// Some historical Instantaneous Core Pool Revenue is ready for payout claims.
447		ClaimsReady {
448			/// The timeslice whose history is available.
449			when: Timeslice,
450			/// The amount of revenue the Pezkuwi System has already taken.
451			system_payout: BalanceOf<T>,
452			/// The total amount of revenue remaining to be claimed.
453			private_payout: BalanceOf<T>,
454		},
455		/// A Core has been assigned to one or more tasks and/or the Pool on the Relay-chain.
456		CoreAssigned {
457			/// The index of the Core which has been assigned.
458			core: CoreIndex,
459			/// The Relay-chain block at which this assignment should take effect.
460			when: RelayBlockNumberOf<T>,
461			/// The workload to be done on the Core.
462			assignment: Vec<(CoreAssignment, PartsOf57600)>,
463		},
464		/// Some historical Instantaneous Core Pool payment record has been dropped.
465		PotentialRenewalDropped {
466			/// The timeslice whose renewal is no longer available.
467			when: Timeslice,
468			/// The core whose workload is no longer available to be renewed for `when`.
469			core: CoreIndex,
470		},
471		AutoRenewalEnabled {
472			/// The core for which the renewal was enabled.
473			core: CoreIndex,
474			/// The task for which the renewal was enabled.
475			task: TaskId,
476		},
477		AutoRenewalDisabled {
478			/// The core for which the renewal was disabled.
479			core: CoreIndex,
480			/// The task for which the renewal was disabled.
481			task: TaskId,
482		},
483		/// Failed to auto-renew a core, likely due to the payer account not being sufficiently
484		/// funded.
485		AutoRenewalFailed {
486			/// The core for which the renewal failed.
487			core: CoreIndex,
488			/// The account which was supposed to pay for renewal.
489			///
490			/// If `None` it indicates that we failed to get the sovereign account of a task.
491			payer: Option<T::AccountId>,
492		},
493		/// The auto-renewal limit has been reached upon renewing cores.
494		///
495		/// This should never happen, given that enable_auto_renew checks for this before enabling
496		/// auto-renewal.
497		AutoRenewalLimitReached,
498	}
499
500	#[pezpallet::error]
501	#[derive(PartialEq)]
502	pub enum Error<T> {
503		/// The given region identity is not known.
504		UnknownRegion,
505		/// The owner of the region is not the origin.
506		NotOwner,
507		/// The pivot point of the partition at or after the end of the region.
508		PivotTooLate,
509		/// The pivot point of the partition at the beginning of the region.
510		PivotTooEarly,
511		/// The pivot mask for the interlacing is not contained within the region's interlace mask.
512		ExteriorPivot,
513		/// The pivot mask for the interlacing is void (and therefore unschedulable).
514		VoidPivot,
515		/// The pivot mask for the interlacing is complete (and therefore not a strict subset).
516		CompletePivot,
517		/// The workplan of the pezpallet's state is invalid. This indicates a state corruption.
518		CorruptWorkplan,
519		/// There is no sale happening currently.
520		NoSales,
521		/// The price limit is exceeded.
522		Overpriced,
523		/// There are no cores available.
524		Unavailable,
525		/// The sale limit has been reached.
526		SoldOut,
527		/// The renewal operation is not valid at the current time (it may become valid in the next
528		/// sale).
529		WrongTime,
530		/// Invalid attempt to renew.
531		NotAllowed,
532		/// This pezpallet has not yet been initialized.
533		Uninitialized,
534		/// The purchase cannot happen yet as the sale period is yet to begin.
535		TooEarly,
536		/// There is no work to be done.
537		NothingToDo,
538		/// The maximum amount of reservations has already been reached.
539		TooManyReservations,
540		/// The maximum amount of leases has already been reached.
541		TooManyLeases,
542		/// The lease does not exist.
543		LeaseNotFound,
544		/// The revenue for the Instantaneous Core Sales of this period is not (yet) known and thus
545		/// this operation cannot proceed.
546		UnknownRevenue,
547		/// The identified contribution to the Instantaneous Core Pool is unknown.
548		UnknownContribution,
549		/// The workload assigned for renewal is incomplete. This is unexpected and indicates a
550		/// logic error.
551		IncompleteAssignment,
552		/// An item cannot be dropped because it is still valid.
553		StillValid,
554		/// The history item does not exist.
555		NoHistory,
556		/// No reservation of the given index exists.
557		UnknownReservation,
558		/// The renewal record cannot be found.
559		UnknownRenewal,
560		/// The lease expiry time has already passed.
561		AlreadyExpired,
562		/// The configuration could not be applied because it is invalid.
563		InvalidConfig,
564		/// The revenue must be claimed for 1 or more timeslices.
565		NoClaimTimeslices,
566		/// The caller doesn't have the permission to enable or disable auto-renewal.
567		NoPermission,
568		/// We reached the limit for auto-renewals.
569		TooManyAutoRenewals,
570		/// Only cores which are assigned to a task can be auto-renewed.
571		NonTaskAutoRenewal,
572		/// Failed to get the sovereign account of a task.
573		SovereignAccountNotFound,
574		/// Attempted to disable auto-renewal for a core that didn't have it enabled.
575		AutoRenewalNotEnabled,
576		/// Attempted to force remove an assignment that doesn't exist.
577		AssignmentNotFound,
578		/// Needed to prevent spam attacks.The amount of credits the user attempted to purchase is
579		/// below `T::MinimumCreditPurchase`.
580		CreditPurchaseTooSmall,
581	}
582
583	#[derive(pezframe_support::DefaultNoBound)]
584	#[pezpallet::genesis_config]
585	pub struct GenesisConfig<T: Config> {
586		#[serde(skip)]
587		pub _config: core::marker::PhantomData<T>,
588	}
589
590	#[pezpallet::genesis_build]
591	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
592		fn build(&self) {
593			pezframe_system::Pezpallet::<T>::inc_providers(&Pezpallet::<T>::account_id());
594		}
595	}
596
597	#[pezpallet::hooks]
598	impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
599		fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
600			Self::do_tick()
601		}
602	}
603
604	#[pezpallet::call(weight(<T as Config>::WeightInfo))]
605	impl<T: Config> Pezpallet<T> {
606		/// Configure the pezpallet.
607		///
608		/// - `origin`: Must be Root or pass `AdminOrigin`.
609		/// - `config`: The configuration for this pezpallet.
610		#[pezpallet::call_index(0)]
611		pub fn configure(
612			origin: OriginFor<T>,
613			config: ConfigRecordOf<T>,
614		) -> DispatchResultWithPostInfo {
615			T::AdminOrigin::ensure_origin_or_root(origin)?;
616			Self::do_configure(config)?;
617			Ok(Pays::No.into())
618		}
619
620		/// Reserve a core for a workload.
621		///
622		/// The workload will be given a reservation, but two sale period boundaries must pass
623		/// before the core is actually assigned.
624		///
625		/// - `origin`: Must be Root or pass `AdminOrigin`.
626		/// - `workload`: The workload which should be permanently placed on a core.
627		#[pezpallet::call_index(1)]
628		pub fn reserve(origin: OriginFor<T>, workload: Schedule) -> DispatchResultWithPostInfo {
629			T::AdminOrigin::ensure_origin_or_root(origin)?;
630			Self::do_reserve(workload)?;
631			Ok(Pays::No.into())
632		}
633
634		/// Cancel a reservation for a workload.
635		///
636		/// - `origin`: Must be Root or pass `AdminOrigin`.
637		/// - `item_index`: The index of the reservation. Usually this will also be the index of the
638		///   core on which the reservation has been scheduled. However, it is possible that if
639		///   other cores are reserved or unreserved in the same sale rotation that they won't
640		///   correspond, so it's better to look up the core properly in the `Reservations` storage.
641		#[pezpallet::call_index(2)]
642		pub fn unreserve(origin: OriginFor<T>, item_index: u32) -> DispatchResultWithPostInfo {
643			T::AdminOrigin::ensure_origin_or_root(origin)?;
644			Self::do_unreserve(item_index)?;
645			Ok(Pays::No.into())
646		}
647
648		/// Reserve a core for a single task workload for a limited period.
649		///
650		/// In the interlude and sale period where Bulk Coretime is sold for the period immediately
651		/// after `until`, then the same workload may be renewed.
652		///
653		/// - `origin`: Must be Root or pass `AdminOrigin`.
654		/// - `task`: The workload which should be placed on a core.
655		/// - `until`: The timeslice now earlier than which `task` should be placed as a workload on
656		///   a core.
657		#[pezpallet::call_index(3)]
658		pub fn set_lease(
659			origin: OriginFor<T>,
660			task: TaskId,
661			until: Timeslice,
662		) -> DispatchResultWithPostInfo {
663			T::AdminOrigin::ensure_origin_or_root(origin)?;
664			Self::do_set_lease(task, until)?;
665			Ok(Pays::No.into())
666		}
667
668		/// Begin the Bulk Coretime sales rotation.
669		///
670		/// - `origin`: Must be Root or pass `AdminOrigin`.
671		/// - `end_price`: The price after the leadin period of Bulk Coretime in the first sale.
672		/// - `extra_cores`: Number of extra cores that should be requested on top of the cores
673		///   required for `Reservations` and `Leases`.
674		///
675		/// This will call [`Self::request_core_count`] internally to set the correct core count on
676		/// the relay chain.
677		#[pezpallet::call_index(4)]
678		#[pezpallet::weight(T::WeightInfo::start_sales(
679			T::MaxLeasedCores::get() + T::MaxReservedCores::get() + *extra_cores as u32
680		))]
681		pub fn start_sales(
682			origin: OriginFor<T>,
683			end_price: BalanceOf<T>,
684			extra_cores: CoreIndex,
685		) -> DispatchResultWithPostInfo {
686			T::AdminOrigin::ensure_origin_or_root(origin)?;
687			Self::do_start_sales(end_price, extra_cores)?;
688			Ok(Pays::No.into())
689		}
690
691		/// Purchase Bulk Coretime in the ongoing Sale.
692		///
693		/// - `origin`: Must be a Signed origin with at least enough funds to pay the current price
694		///   of Bulk Coretime.
695		/// - `price_limit`: An amount no more than which should be paid.
696		#[pezpallet::call_index(5)]
697		pub fn purchase(
698			origin: OriginFor<T>,
699			price_limit: BalanceOf<T>,
700		) -> DispatchResultWithPostInfo {
701			let who = ensure_signed(origin)?;
702			Self::do_purchase(who, price_limit)?;
703			Ok(Pays::No.into())
704		}
705
706		/// Renew Bulk Coretime in the ongoing Sale or its prior Interlude Period.
707		///
708		/// - `origin`: Must be a Signed origin with at least enough funds to pay the renewal price
709		///   of the core.
710		/// - `core`: The core which should be renewed.
711		#[pezpallet::call_index(6)]
712		pub fn renew(origin: OriginFor<T>, core: CoreIndex) -> DispatchResultWithPostInfo {
713			let who = ensure_signed(origin)?;
714			Self::do_renew(who, core)?;
715			Ok(Pays::No.into())
716		}
717
718		/// Transfer a Bulk Coretime Region to a new owner.
719		///
720		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
721		/// - `region_id`: The Region whose ownership should change.
722		/// - `new_owner`: The new owner for the Region.
723		#[pezpallet::call_index(7)]
724		pub fn transfer(
725			origin: OriginFor<T>,
726			region_id: RegionId,
727			new_owner: T::AccountId,
728		) -> DispatchResult {
729			let who = ensure_signed(origin)?;
730			Self::do_transfer(region_id, Some(who), new_owner)?;
731			Ok(())
732		}
733
734		/// Split a Bulk Coretime Region into two non-overlapping Regions at a particular time into
735		/// the region.
736		///
737		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
738		/// - `region_id`: The Region which should be partitioned into two non-overlapping Regions.
739		/// - `pivot`: The offset in time into the Region at which to make the split.
740		#[pezpallet::call_index(8)]
741		pub fn partition(
742			origin: OriginFor<T>,
743			region_id: RegionId,
744			pivot: Timeslice,
745		) -> DispatchResult {
746			let who = ensure_signed(origin)?;
747			Self::do_partition(region_id, Some(who), pivot)?;
748			Ok(())
749		}
750
751		/// Split a Bulk Coretime Region into two wholly-overlapping Regions with complementary
752		/// interlace masks which together make up the original Region's interlace mask.
753		///
754		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
755		/// - `region_id`: The Region which should become two interlaced Regions of incomplete
756		///   regularity.
757		/// - `pivot`: The interlace mask of one of the two new regions (the other is its partial
758		///   complement).
759		#[pezpallet::call_index(9)]
760		pub fn interlace(
761			origin: OriginFor<T>,
762			region_id: RegionId,
763			pivot: CoreMask,
764		) -> DispatchResult {
765			let who = ensure_signed(origin)?;
766			Self::do_interlace(region_id, Some(who), pivot)?;
767			Ok(())
768		}
769
770		/// Assign a Bulk Coretime Region to a task.
771		///
772		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
773		/// - `region_id`: The Region which should be assigned to the task.
774		/// - `task`: The task to assign.
775		/// - `finality`: Indication of whether this assignment is final (in which case it may be
776		///   eligible for renewal) or provisional (in which case it may be manipulated and/or
777		/// reassigned at a later stage).
778		#[pezpallet::call_index(10)]
779		pub fn assign(
780			origin: OriginFor<T>,
781			region_id: RegionId,
782			task: TaskId,
783			finality: Finality,
784		) -> DispatchResultWithPostInfo {
785			let who = ensure_signed(origin)?;
786			Self::do_assign(region_id, Some(who), task, finality)?;
787			Ok(if finality == Finality::Final { Pays::No } else { Pays::Yes }.into())
788		}
789
790		/// Place a Bulk Coretime Region into the Instantaneous Coretime Pool.
791		///
792		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
793		/// - `region_id`: The Region which should be assigned to the Pool.
794		/// - `payee`: The account which is able to collect any revenue due for the usage of this
795		///   Coretime.
796		#[pezpallet::call_index(11)]
797		pub fn pool(
798			origin: OriginFor<T>,
799			region_id: RegionId,
800			payee: T::AccountId,
801			finality: Finality,
802		) -> DispatchResultWithPostInfo {
803			let who = ensure_signed(origin)?;
804			Self::do_pool(region_id, Some(who), payee, finality)?;
805			Ok(if finality == Finality::Final { Pays::No } else { Pays::Yes }.into())
806		}
807
808		/// Claim the revenue owed from inclusion in the Instantaneous Coretime Pool.
809		///
810		/// - `origin`: Must be a Signed origin.
811		/// - `region_id`: The Region which was assigned to the Pool.
812		/// - `max_timeslices`: The maximum number of timeslices which should be processed. This
813		///   must be greater than 0. This may affect the weight of the call but should be ideally
814		///   made equivalent to the length of the Region `region_id`. If less, further dispatches
815		///   will be required with the same `region_id` to claim revenue for the remainder.
816		#[pezpallet::call_index(12)]
817		#[pezpallet::weight(T::WeightInfo::claim_revenue(*max_timeslices))]
818		pub fn claim_revenue(
819			origin: OriginFor<T>,
820			region_id: RegionId,
821			max_timeslices: Timeslice,
822		) -> DispatchResultWithPostInfo {
823			ensure_signed(origin)?;
824			Self::do_claim_revenue(region_id, max_timeslices)?;
825			Ok(Pays::No.into())
826		}
827
828		/// Purchase credit for use in the Instantaneous Coretime Pool.
829		///
830		/// - `origin`: Must be a Signed origin able to pay at least `amount`.
831		/// - `amount`: The amount of credit to purchase.
832		/// - `beneficiary`: The account on the Relay-chain which controls the credit (generally
833		///   this will be the collator's hot wallet).
834		#[pezpallet::call_index(13)]
835		pub fn purchase_credit(
836			origin: OriginFor<T>,
837			amount: BalanceOf<T>,
838			beneficiary: RelayAccountIdOf<T>,
839		) -> DispatchResult {
840			let who = ensure_signed(origin)?;
841			Self::do_purchase_credit(who, amount, beneficiary)?;
842			Ok(())
843		}
844
845		/// Drop an expired Region from the chain.
846		///
847		/// - `origin`: Can be any kind of origin.
848		/// - `region_id`: The Region which has expired.
849		#[pezpallet::call_index(14)]
850		pub fn drop_region(
851			_origin: OriginFor<T>,
852			region_id: RegionId,
853		) -> DispatchResultWithPostInfo {
854			Self::do_drop_region(region_id)?;
855			Ok(Pays::No.into())
856		}
857
858		/// Drop an expired Instantaneous Pool Contribution record from the chain.
859		///
860		/// - `origin`: Can be any kind of origin.
861		/// - `region_id`: The Region identifying the Pool Contribution which has expired.
862		#[pezpallet::call_index(15)]
863		pub fn drop_contribution(
864			_origin: OriginFor<T>,
865			region_id: RegionId,
866		) -> DispatchResultWithPostInfo {
867			Self::do_drop_contribution(region_id)?;
868			Ok(Pays::No.into())
869		}
870
871		/// Drop an expired Instantaneous Pool History record from the chain.
872		///
873		/// - `origin`: Can be any kind of origin.
874		/// - `region_id`: The time of the Pool History record which has expired.
875		#[pezpallet::call_index(16)]
876		pub fn drop_history(_origin: OriginFor<T>, when: Timeslice) -> DispatchResultWithPostInfo {
877			Self::do_drop_history(when)?;
878			Ok(Pays::No.into())
879		}
880
881		/// Drop an expired Allowed Renewal record from the chain.
882		///
883		/// - `origin`: Can be any kind of origin.
884		/// - `core`: The core to which the expired renewal refers.
885		/// - `when`: The timeslice to which the expired renewal refers. This must have passed.
886		#[pezpallet::call_index(17)]
887		pub fn drop_renewal(
888			_origin: OriginFor<T>,
889			core: CoreIndex,
890			when: Timeslice,
891		) -> DispatchResultWithPostInfo {
892			Self::do_drop_renewal(core, when)?;
893			Ok(Pays::No.into())
894		}
895
896		/// Request a change to the number of cores available for scheduling work.
897		///
898		/// - `origin`: Must be Root or pass `AdminOrigin`.
899		/// - `core_count`: The desired number of cores to be made available.
900		#[pezpallet::call_index(18)]
901		#[pezpallet::weight(T::WeightInfo::request_core_count((*core_count).into()))]
902		pub fn request_core_count(origin: OriginFor<T>, core_count: CoreIndex) -> DispatchResult {
903			T::AdminOrigin::ensure_origin_or_root(origin)?;
904			Self::do_request_core_count(core_count)?;
905			Ok(())
906		}
907
908		#[pezpallet::call_index(19)]
909		#[pezpallet::weight(T::WeightInfo::notify_core_count())]
910		pub fn notify_core_count(origin: OriginFor<T>, core_count: CoreIndex) -> DispatchResult {
911			T::AdminOrigin::ensure_origin_or_root(origin)?;
912			Self::do_notify_core_count(core_count)?;
913			Ok(())
914		}
915
916		#[pezpallet::call_index(20)]
917		#[pezpallet::weight(T::WeightInfo::notify_revenue())]
918		pub fn notify_revenue(
919			origin: OriginFor<T>,
920			revenue: OnDemandRevenueRecordOf<T>,
921		) -> DispatchResult {
922			T::AdminOrigin::ensure_origin_or_root(origin)?;
923			Self::do_notify_revenue(revenue)?;
924			Ok(())
925		}
926
927		/// Extrinsic for enabling auto renewal.
928		///
929		/// Callable by the sovereign account of the task on the specified core. This account
930		/// will be charged at the start of every bulk period for renewing core time.
931		///
932		/// - `origin`: Must be the sovereign account of the task
933		/// - `core`: The core to which the task to be renewed is currently assigned.
934		/// - `task`: The task for which we want to enable auto renewal.
935		/// - `workload_end_hint`: should be used when enabling auto-renewal for a core that is not
936		///   expiring in the upcoming bulk period (e.g., due to holding a lease) since it would be
937		///   inefficient to look up when the core expires to schedule the next renewal.
938		#[pezpallet::call_index(21)]
939		#[pezpallet::weight(T::WeightInfo::enable_auto_renew())]
940		pub fn enable_auto_renew(
941			origin: OriginFor<T>,
942			core: CoreIndex,
943			task: TaskId,
944			workload_end_hint: Option<Timeslice>,
945		) -> DispatchResult {
946			let who = ensure_signed(origin)?;
947
948			let sovereign_account = T::SovereignAccountOf::maybe_convert(task)
949				.ok_or(Error::<T>::SovereignAccountNotFound)?;
950			// Only the sovereign account of a task can enable auto renewal for its own core.
951			ensure!(who == sovereign_account, Error::<T>::NoPermission);
952
953			Self::do_enable_auto_renew(sovereign_account, core, task, workload_end_hint)?;
954			Ok(())
955		}
956
957		/// Extrinsic for disabling auto renewal.
958		///
959		/// Callable by the sovereign account of the task on the specified core.
960		///
961		/// - `origin`: Must be the sovereign account of the task.
962		/// - `core`: The core for which we want to disable auto renewal.
963		/// - `task`: The task for which we want to disable auto renewal.
964		#[pezpallet::call_index(22)]
965		#[pezpallet::weight(T::WeightInfo::disable_auto_renew())]
966		pub fn disable_auto_renew(
967			origin: OriginFor<T>,
968			core: CoreIndex,
969			task: TaskId,
970		) -> DispatchResult {
971			let who = ensure_signed(origin)?;
972
973			let sovereign_account = T::SovereignAccountOf::maybe_convert(task)
974				.ok_or(Error::<T>::SovereignAccountNotFound)?;
975			// Only the sovereign account of the task can disable auto-renewal.
976			ensure!(who == sovereign_account, Error::<T>::NoPermission);
977
978			Self::do_disable_auto_renew(core, task)?;
979
980			Ok(())
981		}
982
983		/// Reserve a core for a workload immediately.
984		///
985		/// - `origin`: Must be Root or pass `AdminOrigin`.
986		/// - `workload`: The workload which should be permanently placed on a core starting
987		///   immediately.
988		/// - `core`: The core to which the assignment should be made until the reservation takes
989		///   effect. It is left to the caller to either add this new core or reassign any other
990		///   tasks to this existing core.
991		///
992		/// This reserves the workload and then injects the workload into the Workplan for the next
993		/// two sale periods. This overwrites any existing assignments for this core at the start of
994		/// the next sale period.
995		#[pezpallet::call_index(23)]
996		pub fn force_reserve(
997			origin: OriginFor<T>,
998			workload: Schedule,
999			core: CoreIndex,
1000		) -> DispatchResultWithPostInfo {
1001			T::AdminOrigin::ensure_origin_or_root(origin)?;
1002			Self::do_force_reserve(workload, core)?;
1003			Ok(Pays::No.into())
1004		}
1005
1006		/// Remove a lease.
1007		///
1008		/// - `origin`: Must be Root or pass `AdminOrigin`.
1009		/// - `task`: The task id of the lease which should be removed.
1010		#[pezpallet::call_index(24)]
1011		pub fn remove_lease(origin: OriginFor<T>, task: TaskId) -> DispatchResult {
1012			T::AdminOrigin::ensure_origin_or_root(origin)?;
1013			Self::do_remove_lease(task)
1014		}
1015
1016		/// Remove an assignment from the Workplan.
1017		///
1018		/// - `origin`: Must be Root or pass `AdminOrigin`.
1019		/// - `region_id`: The Region to be removed from the workplan.
1020		#[pezpallet::call_index(26)]
1021		pub fn remove_assignment(origin: OriginFor<T>, region_id: RegionId) -> DispatchResult {
1022			T::AdminOrigin::ensure_origin_or_root(origin)?;
1023			Self::do_remove_assignment(region_id)
1024		}
1025
1026		#[pezpallet::call_index(99)]
1027		#[pezpallet::weight(T::WeightInfo::swap_leases())]
1028		pub fn swap_leases(origin: OriginFor<T>, id: TaskId, other: TaskId) -> DispatchResult {
1029			T::AdminOrigin::ensure_origin_or_root(origin)?;
1030			Self::do_swap_leases(id, other)?;
1031			Ok(())
1032		}
1033	}
1034}