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