Skip to main content

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