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}