referrerpolicy=no-referrer-when-downgrade

pallet_broker/
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, MaxEncodedLen};
27use frame_support::{weights::WeightMeter, Parameter};
28use scale_info::TypeInfo;
29use sp_runtime::DispatchError;
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: Parameter;
54
55	/// Configuration of the market.
56	///
57	/// Can be set in the [`Market::configure`].
58	type Configuration: Parameter;
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)`.
154pub struct SoldCoresRange {
155	/// Minimum core index (inclusive).
156	pub from: CoreIndex,
157	/// Maximum core index (exclusive).
158	pub to: CoreIndex,
159}
160
161/// Provides timeslice-related information to the market implementation.
162pub trait TimesliceProvider {
163	/// Returns the next timeslice pending commitment, if any.
164	fn next_timeslice_to_commit() -> Option<Timeslice>;
165	/// Returns the latest timeslice ready to be committed to the relay chain.
166	///
167	/// Returns `None` if the timeslice is unknown (e.g., when [`TimesliceProvider`] implementer is
168	/// not yet initialized).
169	fn latest_timeslice_ready_to_commit() -> Option<Timeslice>;
170}
171
172/// Information about the sale.
173pub struct MarketSaleInfo<RelayBlockNumber> {
174	/// The relay block number at which the sale will/did start.
175	pub sale_start: RelayBlockNumber,
176	/// The first timeslice of the Regions which are being sold in this sale.
177	pub region_begin: Timeslice,
178	/// The timeslice on which the Regions which are being sold in the sale terminate. (i.e. One
179	/// after the last timeslice which the Regions control.)
180	pub region_end: Timeslice,
181	/// Number of cores which are/have been offered for sale.
182	pub cores_offered: CoreIndex,
183	/// The index of the first core which is for sale. Core of Regions which are sold have
184	/// incrementing indices from this.
185	pub first_core: CoreIndex,
186	/// Number of cores which have been sold; never more than cores_offered.
187	pub cores_sold: CoreIndex,
188}
189
190/// Outcome of [`Market::start_sales`].
191pub struct SalesStarted<RelayBlockNumber> {
192	/// The first active sale.
193	pub sale: MarketSaleInfo<RelayBlockNumber>,
194}
195
196/// Possible outcomes of [`Market::place_order`].
197pub enum OrderResult<Balance, BidId> {
198	/// A bid was placed.
199	BidPlaced {
200		/// Identifier of the bid.
201		id: BidId,
202		/// Amount to lock when placing the bid.
203		bid_price: Balance,
204	},
205	/// The region was purchased immediately.
206	Sold {
207		/// Price paid.
208		price: Balance,
209		/// Purchased region identifier.
210		region_id: RegionId,
211		/// End of the purchased region.
212		region_end: Timeslice,
213	},
214}
215
216/// Possible outcomes of [`Market::place_renewal_order`].
217pub enum RenewalOrderResult<Balance, BidId> {
218	/// A renewal bid was placed.
219	BidPlaced {
220		/// Identifier of the bid.
221		id: BidId,
222		/// Amount to lock when placing the bid.
223		bid_price: Balance,
224	},
225	/// The region was renewed immediately.
226	Renewed {
227		/// Price paid for the renewal.
228		price: Balance,
229		/// Identifier of the renewed region.
230		region_id: RegionId,
231		/// End of the renewed region.
232		effective_to: Timeslice,
233	},
234}
235
236/// Outcome of [`Market::adjust_bid`].
237pub enum AdjustBidResult<Balance> {
238	/// Indicates that additional balance must be locked.
239	Lock {
240		/// The additional amount to lock.
241		amount: Balance,
242	},
243	/// Indicates that part or all of the bid should be refunded.
244	Refund {
245		/// The amount to refund.
246		amount: Balance,
247	},
248}
249
250/// Outcome of [`Market::tick`].
251///
252/// When `pallet-broker` calls [`Market::tick`], it receives a list of [`TickAction`]s
253/// which will be executed in order.
254///
255/// These actions are **not** executed by the market itself as they don't fall into the scope of the
256/// market logic(e.g., transferring balances, updating region ownership). Instead, the market
257/// relies on `pallet-broker` to execute them.
258pub enum TickAction<AccountId, Balance, RelayBlockNumber> {
259	/// Sell a region to an account.
260	SellRegion {
261		/// New owner.
262		owner: AccountId,
263		/// Total price paid.
264		paid: Balance,
265		/// Region identifier.
266		region_id: RegionId,
267		/// End of the region.
268		region_end: Timeslice,
269	},
270	/// Renew an existing region.
271	RenewRegion {
272		/// Current owner.
273		owner: AccountId,
274		/// Renewal identifier.
275		renewal_id: PotentialRenewalId,
276	},
277	/// Refund previously locked balance.
278	Refund {
279		/// Amount to return.
280		amount: Balance,
281		/// Recipient.
282		who: AccountId,
283	},
284	/// Process the auto renewals which are stored in `pallet-broker`.
285	ProcessAutoRenewals {
286		/// Only auto-renewals allowing renewals after this timeslice should be processed.
287		after_timeslice: Timeslice,
288		/// When the next auto-renewal of this core can be made.
289		next_renewal_at: Timeslice,
290	},
291	/// Indicates that a new sale cycle has started.
292	///
293	/// This allows `pallet-broker` to handle sale boundary transitions.
294	SaleRotated {
295		/// Previously active sale.
296		old_sale: MarketSaleInfo<RelayBlockNumber>,
297		/// Newly active sale.
298		new_sale: MarketSaleInfo<RelayBlockNumber>,
299	},
300}