referrerpolicy=no-referrer-when-downgrade

fp_coretime/
market.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//! Generic coretime market interface.
19//!
20//! Contains the [`Market`] trait — an abstraction that allows `pallet-broker` to work with any
21//! market logic implementing [`Market`].
22
23use alloc::vec::Vec;
24use core::fmt::Debug;
25
26use codec::{Codec, DecodeWithMemTracking, EncodeLike, MaxEncodedLen};
27use scale_info::TypeInfo;
28use sp_runtime::DispatchError;
29use sp_weights::WeightMeter;
30
31use crate::{CoreIndex, PotentialRenewalId, RegionId, Timeslice};
32
33/// Trait representing generic coretime market logic.
34///
35/// ## Assumptions about the market implementations
36/// - There are two types of orders: *purchase* and *renewal*.
37/// - Every successful order either creates a bid or is resolved immediately.
38/// - Coretime regions are equivalent from the user's perspective.
39///
40/// ## Market lifecycle
41/// 1. [`Market::start_sales`] — initializes the market (if required).
42/// 2. [`Market::place_order`], [`Market::place_renewal_order`], and [`Market::adjust_bid`] — users
43///    purchase or bid for coretime regions and renew existing ones.
44/// 3. [`Market::tick`] — called from `on_initialize` hook to execute time-dependent logic.
45pub trait Market<RelayBlockNumber, Balance, AccountId> {
46	/// Error type returned by market operations.
47	type Error: Into<DispatchError>;
48
49	/// Unique identifier assigned to each bid.
50	type BidId: Copy + Debug + Codec + MaxEncodedLen + TypeInfo + Eq;
51
52	/// Initialization data used in [`Market::start_sales`].
53	type InitData: Codec + DecodeWithMemTracking + EncodeLike + Clone + Eq + Debug + TypeInfo;
54
55	/// Configuration of the market.
56	///
57	/// Can be set in the [`Market::configure`].
58	type Configuration: Codec + DecodeWithMemTracking + EncodeLike + Clone + Eq + Debug + TypeInfo;
59
60	/// Provides information about available cores.
61	type CoreRangeProvider: CoreRangeProvider;
62
63	/// Provides information about timeslice scheduling.
64	type TimesliceProvider: TimesliceProvider;
65
66	/// Set or update the market configuration.
67	///
68	/// ### Parameters
69	/// - `configuration`: a new configuration to use.
70	fn configure(configuration: Self::Configuration) -> Result<(), Self::Error>;
71
72	/// Start the coretime sales.
73	///
74	/// ### Parameters
75	/// - `block_number`: Current relay chain block number.
76	/// - `init_data`: Market-specific initialization data.
77	fn start_sales(
78		block_number: RelayBlockNumber,
79		init_data: Self::InitData,
80	) -> Result<SalesStarted<RelayBlockNumber>, Self::Error>;
81
82	/// Place an order to purchase one coretime region.
83	///
84	/// Depending on the implementation, this either: places a bid, or immediately executes the
85	/// purchase.
86	///
87	/// ### Parameters
88	/// - `block_number`: Current relay chain block number.
89	/// - `who`: Account placing the order.
90	/// - `price_limit`: Maximum price the buyer is willing to pay.
91	fn place_order(
92		block_number: RelayBlockNumber,
93		who: &AccountId,
94		price_limit: Balance,
95	) -> Result<OrderResult<Balance, Self::BidId>, Self::Error>;
96
97	/// Place an order to renew a coretime region.
98	///
99	/// Depending on the implementation, this either: places a bid, or immediately executes the
100	/// purchase.
101	///
102	/// ### Parameters
103	/// - `block_number`: Current relay chain block number.
104	/// - `who`: Account placing the order.
105	/// - `renewal`: Renewal identifier.
106	fn place_renewal_order(
107		block_number: RelayBlockNumber,
108		who: &AccountId,
109		renewal: PotentialRenewalId,
110	) -> Result<RenewalOrderResult<Balance, Self::BidId>, Self::Error>;
111
112	/// Adjust the price of an existing bid.
113	///
114	/// This call may fail if the market does not allow increasing, decreasing,
115	/// or withdrawing bids.
116	///
117	/// ### Parameters
118	/// - `block_number`: Current relay chain block number.
119	/// - `id`: The identifier of the bid to adjust.
120	/// - `who`: Account adjusting the bid.
121	/// - `new_price`: The new bid price. If `None` is provided, the bid will be withdrawn.
122	fn adjust_bid(
123		block_number: RelayBlockNumber,
124		id: Self::BidId,
125		who: &AccountId,
126		new_price: Option<Balance>,
127	) -> Result<AdjustBidResult<Balance>, Self::Error>;
128
129	/// Execute time-based market logic.
130	///
131	/// This function is called from the `on_initialize` hook by `pallet-broker`.
132	///
133	/// ### Parameters
134	/// - `now`: Current relay chain block number.
135	/// - `weight_meter`: Used for advanced weight accounting.
136	fn tick(
137		now: RelayBlockNumber,
138		weight_meter: &mut WeightMeter,
139	) -> Vec<TickAction<AccountId, Balance, RelayBlockNumber>>;
140}
141
142/// Provides information about the range of cores that can be sold on a market.
143pub trait CoreRangeProvider {
144	/// Returns the range of core indices that can be sold on a market.
145	///
146	/// Returns `None` if the range is unknown (e.g., the [`CoreRangeProvider`]
147	/// implementer is not initialized).
148	fn core_range() -> Option<SoldCoresRange>;
149}
150
151/// A range of cores available for sale on a coretime market.
152///
153/// Represents the half-open range `[from, to)`.
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
155pub struct SoldCoresRange {
156	/// Minimum core index (inclusive).
157	pub from: CoreIndex,
158	/// Maximum core index (exclusive).
159	pub to: CoreIndex,
160}
161
162/// Provides timeslice-related information to the market implementation.
163pub trait TimesliceProvider {
164	/// Returns the next timeslice pending commitment, if any.
165	fn next_timeslice_to_commit() -> Option<Timeslice>;
166	/// Returns the latest timeslice ready to be committed to the relay chain.
167	///
168	/// Returns `None` if the timeslice is unknown (e.g., when [`TimesliceProvider`] implementer is
169	/// not yet initialized).
170	fn latest_timeslice_ready_to_commit() -> Option<Timeslice>;
171}
172
173/// Information about the sale.
174pub struct MarketSaleInfo<RelayBlockNumber> {
175	/// The relay block number at which the sale will/did start.
176	pub sale_start: RelayBlockNumber,
177	/// The first timeslice of the Regions which are being sold in this sale.
178	pub region_begin: Timeslice,
179	/// The timeslice on which the Regions which are being sold in the sale terminate. (i.e. One
180	/// after the last timeslice which the Regions control.)
181	pub region_end: Timeslice,
182	/// Number of cores which are/have been offered for sale.
183	pub cores_offered: CoreIndex,
184	/// The index of the first core which is for sale. Core of Regions which are sold have
185	/// incrementing indices from this.
186	pub first_core: CoreIndex,
187	/// Number of cores which have been sold; never more than cores_offered.
188	pub cores_sold: CoreIndex,
189}
190
191/// Outcome of [`Market::start_sales`].
192pub struct SalesStarted<RelayBlockNumber> {
193	/// The first active sale.
194	pub sale: MarketSaleInfo<RelayBlockNumber>,
195}
196
197/// Possible outcomes of [`Market::place_order`].
198pub enum OrderResult<Balance, BidId> {
199	/// A bid was placed.
200	BidPlaced {
201		/// Identifier of the bid.
202		id: BidId,
203		/// Amount to lock when placing the bid.
204		bid_price: Balance,
205	},
206	/// The region was purchased immediately.
207	Sold {
208		/// Price paid.
209		price: Balance,
210		/// Purchased region identifier.
211		region_id: RegionId,
212		/// End of the purchased region.
213		region_end: Timeslice,
214	},
215}
216
217/// Possible outcomes of [`Market::place_renewal_order`].
218pub enum RenewalOrderResult<Balance, BidId> {
219	/// A renewal bid was placed.
220	BidPlaced {
221		/// Identifier of the bid.
222		id: BidId,
223		/// Amount to lock when placing the bid.
224		bid_price: Balance,
225	},
226	/// The region was renewed immediately.
227	Renewed {
228		/// Price paid for the renewal.
229		price: Balance,
230		/// Identifier of the renewed region.
231		region_id: RegionId,
232		/// End of the renewed region.
233		effective_to: Timeslice,
234	},
235}
236
237/// Outcome of [`Market::adjust_bid`].
238pub enum AdjustBidResult<Balance> {
239	/// Indicates that additional balance must be locked.
240	Lock {
241		/// The additional amount to lock.
242		amount: Balance,
243	},
244	/// Indicates that part or all of the bid should be refunded.
245	Refund {
246		/// The amount to refund.
247		amount: Balance,
248	},
249}
250
251/// Outcome of [`Market::tick`].
252///
253/// When `pallet-broker` calls [`Market::tick`], it receives a list of [`TickAction`]s
254/// which will be executed in order.
255///
256/// These actions are **not** executed by the market itself as they don't fall into the scope of the
257/// market logic(e.g., transferring balances, updating region ownership). Instead, the market
258/// relies on `pallet-broker` to execute them.
259pub enum TickAction<AccountId, Balance, RelayBlockNumber> {
260	/// Sell a region to an account.
261	SellRegion {
262		/// New owner.
263		owner: AccountId,
264		/// Total price paid.
265		paid: Balance,
266		/// Region identifier.
267		region_id: RegionId,
268		/// End of the region.
269		region_end: Timeslice,
270	},
271	/// Renew an existing region.
272	RenewRegion {
273		/// Current owner.
274		owner: AccountId,
275		/// Renewal identifier.
276		renewal_id: PotentialRenewalId,
277	},
278	/// Refund previously locked balance.
279	Refund {
280		/// Amount to return.
281		amount: Balance,
282		/// Recipient.
283		who: AccountId,
284	},
285	/// Process the auto renewals which are stored in `pallet-broker`.
286	ProcessAutoRenewals {
287		/// Only auto-renewals allowing renewals after this timeslice should be processed.
288		after_timeslice: Timeslice,
289		/// When the next auto-renewal of this core can be made.
290		next_renewal_at: Timeslice,
291	},
292	/// Indicates that a new sale cycle has started.
293	///
294	/// This allows `pallet-broker` to handle sale boundary transitions.
295	SaleRotated {
296		/// Previously active sale.
297		old_sale: MarketSaleInfo<RelayBlockNumber>,
298		/// Newly active sale.
299		new_sale: MarketSaleInfo<RelayBlockNumber>,
300	},
301}