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}