diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock
index e6ca24864b2f6267e55a142778708cb6cac206bd..199196bc48c29cc36cf4e55440b909b0593276e3 100644
--- a/substrate/Cargo.lock
+++ b/substrate/Cargo.lock
@@ -4242,6 +4242,7 @@ dependencies = [
  "pallet-bags-list",
  "pallet-balances",
  "pallet-bounties",
+ "pallet-broker",
  "pallet-child-bounties",
  "pallet-collective",
  "pallet-contracts",
@@ -6436,6 +6437,23 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "pallet-broker"
+version = "0.1.0"
+dependencies = [
+ "bitvec",
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-arithmetic",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 name = "pallet-child-bounties"
 version = "4.0.0-dev"
diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml
index 9ee8142e23e767fe27685438f21c2bee207fa12d..1411e2ca796da72335f930379520764871bfea1e 100644
--- a/substrate/Cargo.toml
+++ b/substrate/Cargo.toml
@@ -131,6 +131,7 @@ members = [
 	"frame/benchmarking",
 	"frame/benchmarking/pov",
 	"frame/bounties",
+	"frame/broker",
 	"frame/child-bounties",
 	"frame/collective",
 	"frame/contracts",
diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml
index 61202a30d69e187bf9c799bba9b74da8712907de..135621a3be730c601303914ad5bd8fcf3bf7f1bb 100644
--- a/substrate/bin/node/runtime/Cargo.toml
+++ b/substrate/bin/node/runtime/Cargo.toml
@@ -67,6 +67,7 @@ pallet-babe = { version = "4.0.0-dev", default-features = false, path = "../../.
 pallet-bags-list = { version = "4.0.0-dev", default-features = false, path = "../../../frame/bags-list" }
 pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../../frame/balances" }
 pallet-bounties = { version = "4.0.0-dev", default-features = false, path = "../../../frame/bounties" }
+pallet-broker = { version = "0.1.0", default-features = false, path = "../../../frame/broker" }
 pallet-child-bounties = { version = "4.0.0-dev", default-features = false, path = "../../../frame/child-bounties" }
 pallet-collective = { version = "4.0.0-dev", default-features = false, path = "../../../frame/collective" }
 pallet-contracts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts" }
@@ -160,6 +161,7 @@ std = [
 	"pallet-bags-list/std",
 	"pallet-balances/std",
 	"pallet-bounties/std",
+	"pallet-broker/std",
 	"pallet-child-bounties/std",
 	"pallet-collective/std",
 	"pallet-contracts-primitives/std",
@@ -254,6 +256,7 @@ runtime-benchmarks = [
 	"pallet-bags-list/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
 	"pallet-bounties/runtime-benchmarks",
+	"pallet-broker/runtime-benchmarks",
 	"pallet-child-bounties/runtime-benchmarks",
 	"pallet-collective/runtime-benchmarks",
 	"pallet-contracts/runtime-benchmarks",
@@ -324,6 +327,7 @@ try-runtime = [
 	"pallet-bags-list/try-runtime",
 	"pallet-balances/try-runtime",
 	"pallet-bounties/try-runtime",
+	"pallet-broker/try-runtime",
 	"pallet-child-bounties/try-runtime",
 	"pallet-collective/try-runtime",
 	"pallet-contracts/try-runtime",
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index dc9230d927f708428dc4a94e17f869697569fd82..fe2382cce613c086ac7bd01e223cb6001ffc2635 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -35,7 +35,7 @@ use frame_support::{
 	pallet_prelude::Get,
 	parameter_types,
 	traits::{
-		fungible::ItemOf,
+		fungible::{Balanced, Credit, ItemOf},
 		tokens::{nonfungibles_v2::Inspect, GetSalary, PayFromAccount},
 		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse,
 		EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem,
@@ -56,6 +56,7 @@ use frame_system::{
 pub use node_primitives::{AccountId, Signature};
 use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Moment, Nonce};
 use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter};
+use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600};
 use pallet_election_provider_multi_phase::SolutionAccuracyOf;
 use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
 use pallet_nfts::PalletFeatures;
@@ -1877,6 +1878,77 @@ impl pallet_statement::Config for Runtime {
 	type MaxAllowedBytes = MaxAllowedBytes;
 }
 
+parameter_types! {
+	pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
+}
+
+pub struct IntoAuthor;
+impl OnUnbalanced<Credit<AccountId, Balances>> for IntoAuthor {
+	fn on_nonzero_unbalanced(credit: Credit<AccountId, Balances>) {
+		if let Some(author) = Authorship::author() {
+			let _ = <Balances as Balanced<_>>::resolve(&author, credit);
+		}
+	}
+}
+
+parameter_types! {
+	pub storage CoreCount: Option<CoreIndex> = None;
+	pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None;
+}
+
+pub struct CoretimeProvider;
+impl CoretimeInterface for CoretimeProvider {
+	type AccountId = AccountId;
+	type Balance = Balance;
+	type BlockNumber = BlockNumber;
+	fn latest() -> Self::BlockNumber {
+		System::block_number()
+	}
+	fn request_core_count(_count: CoreIndex) {}
+	fn request_revenue_info_at(_when: Self::BlockNumber) {}
+	fn credit_account(_who: Self::AccountId, _amount: Self::Balance) {}
+	fn assign_core(
+		_core: CoreIndex,
+		_begin: Self::BlockNumber,
+		_assignment: Vec<(CoreAssignment, PartsOf57600)>,
+		_end_hint: Option<Self::BlockNumber>,
+	) {
+	}
+	fn check_notify_core_count() -> Option<u16> {
+		let count = CoreCount::get();
+		CoreCount::set(&None);
+		count
+	}
+	fn check_notify_revenue_info() -> Option<(Self::BlockNumber, Self::Balance)> {
+		let revenue = CoretimeRevenue::get();
+		CoretimeRevenue::set(&None);
+		revenue
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_notify_core_count(count: u16) {
+		CoreCount::set(&Some(count));
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_notify_revenue_info(when: Self::BlockNumber, revenue: Self::Balance) {
+		CoretimeRevenue::set(&Some((when, revenue)));
+	}
+}
+
+impl pallet_broker::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type Currency = Balances;
+	type OnRevenue = IntoAuthor;
+	type TimeslicePeriod = ConstU32<2>;
+	type MaxLeasedCores = ConstU32<5>;
+	type MaxReservedCores = ConstU32<5>;
+	type Coretime = CoretimeProvider;
+	type ConvertBalance = traits::Identity;
+	type WeightInfo = ();
+	type PalletId = BrokerPalletId;
+	type AdminOrigin = EnsureRoot<AccountId>;
+	type PriceAdapter = pallet_broker::Linear;
+}
+
 construct_runtime!(
 	pub struct Runtime
 	{
@@ -1950,6 +2022,7 @@ construct_runtime!(
 		MessageQueue: pallet_message_queue,
 		Pov: frame_benchmarking_pallet_pov,
 		Statement: pallet_statement,
+		Broker: pallet_broker,
 	}
 );
 
@@ -2030,6 +2103,7 @@ mod benches {
 		[pallet_bags_list, VoterList]
 		[pallet_balances, Balances]
 		[pallet_bounties, Bounties]
+		[pallet_broker, Broker]
 		[pallet_child_bounties, ChildBounties]
 		[pallet_collective, Council]
 		[pallet_conviction_voting, ConvictionVoting]
diff --git a/substrate/client/consensus/grandpa/src/communication/gossip.rs b/substrate/client/consensus/grandpa/src/communication/gossip.rs
index 5688aff3ea717efbe7bc03a4d934ef749ab029b0..3a78b157d5b1bcb00b7ff83afb407a0015ed529f 100644
--- a/substrate/client/consensus/grandpa/src/communication/gossip.rs
+++ b/substrate/client/consensus/grandpa/src/communication/gossip.rs
@@ -546,9 +546,7 @@ impl<N: Ord> Peers<N> {
 		who: &PeerId,
 		update: NeighborPacket<N>,
 	) -> Result<Option<&View<N>>, Misbehavior> {
-		let Some(peer) = self.inner.get_mut(who) else {
-			return Ok(None)
-		};
+		let Some(peer) = self.inner.get_mut(who) else { return Ok(None) };
 
 		let invalid_change = peer.view.set_id > update.set_id ||
 			peer.view.round > update.round && peer.view.set_id == update.set_id ||
diff --git a/substrate/frame/broker/Cargo.toml b/substrate/frame/broker/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..daa2426b2c34ad20a3a8c2dc0f2263e6eaeb1307
--- /dev/null
+++ b/substrate/frame/broker/Cargo.toml
@@ -0,0 +1,56 @@
+[package]
+name = "pallet-broker"
+version = "0.1.0"
+description = "Brokerage tool for managing Polkadot Core scheduling"
+authors = ["Parity Technologies <admin@parity.io>"]
+homepage = "https://substrate.io"
+edition = "2021"
+license = "Apache-2.0"
+repository = "https://github.com/paritytech/substrate"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive"] }
+scale-info = { version = "2.0.0", default-features = false, features = ["derive"] }
+bitvec = "1"
+sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" }
+sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" }
+sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" }
+sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" }
+frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
+frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
+frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
+
+[dev-dependencies]
+sp-io = { version = "23.0.0", path = "../../primitives/io" }
+
+[features]
+default = [ "std" ]
+
+std = [
+	"codec/std",
+	"frame-benchmarking?/std",
+	"frame-support/std",
+	"frame-system/std",
+	"scale-info/std",
+	"sp-arithmetic/std",
+	"sp-core/std",
+	"sp-io/std",
+	"sp-runtime/std",
+	"sp-std/std",
+]
+
+runtime-benchmarks = [
+	"frame-benchmarking/runtime-benchmarks",
+	"frame-support/runtime-benchmarks",
+	"frame-system/runtime-benchmarks",
+	"sp-runtime/runtime-benchmarks",
+]
+
+try-runtime = [
+	"frame-support/try-runtime",
+	"frame-system/try-runtime",
+	"sp-runtime/try-runtime",
+]
diff --git a/substrate/frame/broker/README.md b/substrate/frame/broker/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..65b6179863e3d9bd69f6286538ea5ff696413f5c
--- /dev/null
+++ b/substrate/frame/broker/README.md
@@ -0,0 +1,26 @@
+# Pallet Broker
+
+Brokerage tool for managing Polkadot Core scheduling.
+
+Properly described in RFC-0001 Agile Coretime.
+
+## Implemnentation Specifics
+
+### Core Mask Bits
+
+This is 1/80th of a Polkadot Core per timeslice. Assuming timeslices are 80 blocks, then this
+indicates usage of a single core one time over a timeslice.
+
+### The Sale
+
+```nocompile
+					1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2
+0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7
+--------------------------------------------------------
+< interlude  >
+			  <                   sale                 >
+							... of which ...
+			  <  descending-price   ><   fixed-price   >
+														| <-------\
+price fixed, unsold assigned to instapool, system cores reserved -/
+```
diff --git a/substrate/frame/broker/src/adapt_price.rs b/substrate/frame/broker/src/adapt_price.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8266625687a23073a335bcab58db6ef7c33c8d80
--- /dev/null
+++ b/substrate/frame/broker/src/adapt_price.rs
@@ -0,0 +1,84 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![deny(missing_docs)]
+
+use crate::CoreIndex;
+use sp_arithmetic::{traits::One, FixedU64};
+
+/// Type for determining how to set price.
+pub trait AdaptPrice {
+	/// Return the factor by which the regular price must be multiplied during the leadin period.
+	///
+	/// - `when`: The amount through the leadin period; between zero and one.
+	fn leadin_factor_at(when: FixedU64) -> FixedU64;
+	/// Return the correction factor by which the regular price must be multiplied based on market
+	/// performance.
+	///
+	/// - `sold`: The number of cores sold.
+	/// - `target`: The target number of cores to be sold (must be larger than zero).
+	/// - `limit`: The maximum number of cores to be sold.
+	fn adapt_price(sold: CoreIndex, target: CoreIndex, limit: CoreIndex) -> FixedU64;
+}
+
+impl AdaptPrice for () {
+	fn leadin_factor_at(_: FixedU64) -> FixedU64 {
+		FixedU64::one()
+	}
+	fn adapt_price(_: CoreIndex, _: CoreIndex, _: CoreIndex) -> FixedU64 {
+		FixedU64::one()
+	}
+}
+
+/// Simple implementation of `AdaptPrice` giving a monotonic leadin and a linear price change based
+/// on cores sold.
+pub struct Linear;
+impl AdaptPrice for Linear {
+	fn leadin_factor_at(when: FixedU64) -> FixedU64 {
+		FixedU64::from(2) - when
+	}
+	fn adapt_price(sold: CoreIndex, target: CoreIndex, limit: CoreIndex) -> FixedU64 {
+		if sold <= target {
+			FixedU64::from_rational(sold.into(), target.into())
+		} else {
+			FixedU64::one() +
+				FixedU64::from_rational((sold - target).into(), (limit - target).into())
+		}
+	}
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn linear_no_panic() {
+		for limit in 0..10 {
+			for target in 1..10 {
+				for sold in 0..=limit {
+					let price = Linear::adapt_price(sold, target, limit);
+
+					if sold > target {
+						assert!(price > FixedU64::one());
+					} else {
+						assert!(price <= FixedU64::one());
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/substrate/frame/broker/src/benchmarking.rs b/substrate/frame/broker/src/benchmarking.rs
new file mode 100644
index 0000000000000000000000000000000000000000..663bf2f466cf3c0090cd7b832bb38ed26da90613
--- /dev/null
+++ b/substrate/frame/broker/src/benchmarking.rs
@@ -0,0 +1,858 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use super::*;
+
+use crate::{CoreAssignment::Task, Pallet as Broker};
+use frame_benchmarking::v2::*;
+use frame_support::{
+	storage::bounded_vec::BoundedVec,
+	traits::{
+		fungible::{Inspect, Mutate},
+		EnsureOrigin, Hooks,
+	},
+};
+use frame_system::{Pallet as System, RawOrigin};
+use sp_arithmetic::{traits::Zero, Perbill};
+use sp_core::Get;
+use sp_runtime::Saturating;
+use sp_std::{vec, vec::Vec};
+
+const SEED: u32 = 0;
+const MAX_CORE_COUNT: u16 = 1_000;
+
+fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
+	frame_system::Pallet::<T>::assert_last_event(generic_event.into());
+}
+
+fn new_config_record<T: Config>() -> ConfigRecordOf<T> {
+	ConfigRecord {
+		advance_notice: 2u32.into(),
+		interlude_length: 1u32.into(),
+		leadin_length: 1u32.into(),
+		ideal_bulk_proportion: Default::default(),
+		limit_cores_offered: None,
+		region_length: 3,
+		renewal_bump: Perbill::from_percent(10),
+		contribution_timeout: 5,
+	}
+}
+
+fn new_schedule() -> Schedule {
+	// Max items for worst case
+	let mut items = Vec::new();
+	for i in 0..CORE_MASK_BITS {
+		items.push(ScheduleItem {
+			assignment: Task(i.try_into().unwrap()),
+			mask: CoreMask::complete(),
+		});
+	}
+	Schedule::truncate_from(items)
+}
+
+fn setup_reservations<T: Config>(n: u32) {
+	let schedule = new_schedule();
+
+	Reservations::<T>::put(BoundedVec::try_from(vec![schedule.clone(); n as usize]).unwrap());
+}
+
+fn setup_leases<T: Config>(n: u32, task: u32, until: u32) {
+	Leases::<T>::put(
+		BoundedVec::try_from(vec![LeaseRecordItem { task, until: until.into() }; n as usize])
+			.unwrap(),
+	);
+}
+
+fn advance_to<T: Config>(b: u32) {
+	while System::<T>::block_number() < b.into() {
+		System::<T>::set_block_number(System::<T>::block_number().saturating_add(1u32.into()));
+		Broker::<T>::on_initialize(System::<T>::block_number());
+	}
+}
+
+fn setup_and_start_sale<T: Config>() -> Result<u16, BenchmarkError> {
+	Configuration::<T>::put(new_config_record::<T>());
+
+	// Assume Reservations to be filled for worst case
+	setup_reservations::<T>(T::MaxReservedCores::get());
+
+	// Assume Leases to be filled for worst case
+	setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
+
+	Broker::<T>::do_start_sales(10u32.into(), MAX_CORE_COUNT.into())
+		.map_err(|_| BenchmarkError::Weightless)?;
+
+	Ok(T::MaxReservedCores::get()
+		.saturating_add(T::MaxLeasedCores::get())
+		.try_into()
+		.unwrap())
+}
+
+#[benchmarks]
+mod benches {
+	use super::*;
+	use crate::Finality::*;
+
+	#[benchmark]
+	fn configure() -> Result<(), BenchmarkError> {
+		let config = new_config_record::<T>();
+
+		let origin =
+			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(origin as T::RuntimeOrigin, config.clone());
+
+		assert_eq!(Configuration::<T>::get(), Some(config));
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn reserve() -> Result<(), BenchmarkError> {
+		let schedule = new_schedule();
+
+		// Assume Reservations to be almost filled for worst case
+		setup_reservations::<T>(T::MaxReservedCores::get().saturating_sub(1));
+
+		let origin =
+			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(origin as T::RuntimeOrigin, schedule);
+
+		assert_eq!(Reservations::<T>::get().len(), T::MaxReservedCores::get() as usize);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn unreserve() -> Result<(), BenchmarkError> {
+		// Assume Reservations to be filled for worst case
+		setup_reservations::<T>(T::MaxReservedCores::get());
+
+		let origin =
+			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(origin as T::RuntimeOrigin, 0);
+
+		assert_eq!(
+			Reservations::<T>::get().len(),
+			T::MaxReservedCores::get().saturating_sub(1) as usize
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn set_lease() -> Result<(), BenchmarkError> {
+		let task = 1u32;
+		let until = 10u32.into();
+
+		// Assume Leases to be almost filled for worst case
+		setup_leases::<T>(T::MaxLeasedCores::get().saturating_sub(1), task, until);
+
+		let origin =
+			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(origin as T::RuntimeOrigin, task, until);
+
+		assert_eq!(Leases::<T>::get().len(), T::MaxLeasedCores::get() as usize);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn start_sales(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
+		Configuration::<T>::put(new_config_record::<T>());
+
+		// Assume Reservations to be filled for worst case
+		setup_reservations::<T>(T::MaxReservedCores::get());
+
+		// Assume Leases to be filled for worst case
+		setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
+
+		let initial_price = 10u32.into();
+
+		let origin =
+			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(origin as T::RuntimeOrigin, initial_price, n.try_into().unwrap());
+
+		assert!(SaleInfo::<T>::get().is_some());
+		assert_last_event::<T>(
+			Event::SaleInitialized {
+				sale_start: 2u32.into(),
+				leadin_length: 1u32.into(),
+				start_price: 20u32.into(),
+				regular_price: 10u32.into(),
+				region_begin: 4,
+				region_end: 7,
+				ideal_cores_sold: 0,
+				cores_offered: n
+					.saturating_sub(T::MaxReservedCores::get())
+					.saturating_sub(T::MaxLeasedCores::get())
+					.try_into()
+					.unwrap(),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn purchase() -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller.clone()), 10u32.into());
+
+		assert_eq!(SaleInfo::<T>::get().unwrap().sellout_price, Some(10u32.into()));
+		assert_last_event::<T>(
+			Event::Purchased {
+				who: caller,
+				region_id: RegionId { begin: 4, core, mask: CoreMask::complete() },
+				price: 10u32.into(),
+				duration: 3u32.into(),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn renew() -> Result<(), BenchmarkError> {
+		setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(20u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		Broker::<T>::do_assign(region, None, 1001, Final)
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		advance_to::<T>(6);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), region.core);
+
+		let id = AllowedRenewalId { core: region.core, when: 10 };
+		assert!(AllowedRenewals::<T>::get(id).is_some());
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn transfer() -> Result<(), BenchmarkError> {
+		setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		let recipient: T::AccountId = account("recipient", 0, SEED);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller.clone()), region, recipient.clone());
+
+		assert_last_event::<T>(
+			Event::Transferred {
+				region_id: region,
+				old_owner: caller,
+				owner: recipient,
+				duration: 3u32.into(),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn partition() -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), region, 2);
+
+		assert_last_event::<T>(
+			Event::Partitioned {
+				old_region_id: RegionId { begin: 4, core, mask: CoreMask::complete() },
+				new_region_ids: (
+					RegionId { begin: 4, core, mask: CoreMask::complete() },
+					RegionId { begin: 6, core, mask: CoreMask::complete() },
+				),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn interlace() -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), region, 0x00000_fffff_fffff_00000.into());
+
+		assert_last_event::<T>(
+			Event::Interlaced {
+				old_region_id: RegionId { begin: 4, core, mask: CoreMask::complete() },
+				new_region_ids: (
+					RegionId { begin: 4, core, mask: 0x00000_fffff_fffff_00000.into() },
+					RegionId {
+						begin: 4,
+						core,
+						mask: CoreMask::complete() ^ 0x00000_fffff_fffff_00000.into(),
+					},
+				),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn assign() -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), region, 1000, Provisional);
+
+		let workplan_key = (region.begin, region.core);
+		assert!(Workplan::<T>::get(workplan_key).is_some());
+
+		assert!(Regions::<T>::get(region).is_some());
+
+		assert_last_event::<T>(
+			Event::Assigned {
+				region_id: RegionId { begin: 4, core, mask: CoreMask::complete() },
+				task: 1000,
+				duration: 3u32.into(),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn pool() -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		let recipient: T::AccountId = account("recipient", 0, SEED);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), region, recipient, Final);
+
+		let workplan_key = (region.begin, region.core);
+		assert!(Workplan::<T>::get(workplan_key).is_some());
+
+		assert_last_event::<T>(
+			Event::Pooled {
+				region_id: RegionId { begin: 4, core, mask: CoreMask::complete() },
+				duration: 3u32.into(),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn claim_revenue(
+		m: Linear<1, { new_config_record::<T>().region_length }>,
+	) -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+		T::Currency::set_balance(
+			&Broker::<T>::account_id(),
+			T::Currency::minimum_balance().saturating_add(200u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		let recipient: T::AccountId = account("recipient", 0, SEED);
+		T::Currency::set_balance(&recipient.clone(), T::Currency::minimum_balance());
+
+		Broker::<T>::do_pool(region, None, recipient.clone(), Final)
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		let revenue = 10u32.into();
+		InstaPoolHistory::<T>::insert(
+			region.begin,
+			InstaPoolHistoryRecord {
+				private_contributions: 4u32.into(),
+				system_contributions: 3u32.into(),
+				maybe_payout: Some(revenue),
+			},
+		);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), region, m);
+
+		assert!(InstaPoolHistory::<T>::get(region.begin).is_none());
+		assert_last_event::<T>(
+			Event::RevenueClaimPaid {
+				who: recipient,
+				amount: 200u32.into(),
+				next: if m < new_config_record::<T>().region_length {
+					Some(RegionId { begin: 4.saturating_add(m), core, mask: CoreMask::complete() })
+				} else {
+					None
+				},
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn purchase_credit() -> Result<(), BenchmarkError> {
+		setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(30u32.into()),
+		);
+		T::Currency::set_balance(&Broker::<T>::account_id(), T::Currency::minimum_balance());
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		let recipient: T::AccountId = account("recipient", 0, SEED);
+
+		Broker::<T>::do_pool(region, None, recipient, Final)
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		let beneficiary: RelayAccountIdOf<T> = account("beneficiary", 0, SEED);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller.clone()), 20u32.into(), beneficiary.clone());
+
+		assert_last_event::<T>(
+			Event::CreditPurchased { who: caller, beneficiary, amount: 20u32.into() }.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn drop_region() -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		advance_to::<T>(12);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), region);
+
+		assert_last_event::<T>(
+			Event::RegionDropped {
+				region_id: RegionId { begin: 4, core, mask: CoreMask::complete() },
+				duration: 3u32.into(),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn drop_contribution() -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(10u32.into()),
+		);
+
+		let region = Broker::<T>::do_purchase(caller.clone(), 10u32.into())
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		let recipient: T::AccountId = account("recipient", 0, SEED);
+
+		Broker::<T>::do_pool(region, None, recipient, Final)
+			.map_err(|_| BenchmarkError::Weightless)?;
+
+		advance_to::<T>(26);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), region);
+
+		assert_last_event::<T>(
+			Event::ContributionDropped {
+				region_id: RegionId { begin: 4, core, mask: CoreMask::complete() },
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn drop_history() -> Result<(), BenchmarkError> {
+		setup_and_start_sale::<T>()?;
+		let when = 5u32.into();
+		let revenue = 10u32.into();
+
+		advance_to::<T>(25);
+
+		let caller: T::AccountId = whitelisted_caller();
+		InstaPoolHistory::<T>::insert(
+			when,
+			InstaPoolHistoryRecord {
+				private_contributions: 4u32.into(),
+				system_contributions: 3u32.into(),
+				maybe_payout: Some(revenue),
+			},
+		);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), when);
+
+		assert!(InstaPoolHistory::<T>::get(when).is_none());
+		assert_last_event::<T>(Event::HistoryDropped { when, revenue }.into());
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn drop_renewal() -> Result<(), BenchmarkError> {
+		let core = setup_and_start_sale::<T>()?;
+		let when = 5u32.into();
+
+		advance_to::<T>(10);
+
+		let id = AllowedRenewalId { core, when };
+		let record = AllowedRenewalRecord {
+			price: 1u32.into(),
+			completion: CompletionStatus::Complete(new_schedule()),
+		};
+		AllowedRenewals::<T>::insert(id, record);
+
+		let caller: T::AccountId = whitelisted_caller();
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), core, when);
+
+		assert!(AllowedRenewals::<T>::get(id).is_none());
+		assert_last_event::<T>(Event::AllowedRenewalDropped { core, when }.into());
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn request_core_count(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
+		let admin_origin =
+			T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(admin_origin as T::RuntimeOrigin, n.try_into().unwrap());
+
+		assert_last_event::<T>(
+			Event::CoreCountRequested { core_count: n.try_into().unwrap() }.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn process_core_count(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> {
+		setup_and_start_sale::<T>()?;
+
+		let core_count = n.try_into().unwrap();
+
+		<T::Coretime as CoretimeInterface>::ensure_notify_core_count(core_count);
+
+		let mut status = Status::<T>::get().ok_or(BenchmarkError::Weightless)?;
+
+		#[block]
+		{
+			Broker::<T>::process_core_count(&mut status);
+		}
+
+		assert_last_event::<T>(Event::CoreCountChanged { core_count }.into());
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn process_revenue() -> Result<(), BenchmarkError> {
+		setup_and_start_sale::<T>()?;
+
+		advance_to::<T>(2);
+
+		let caller: T::AccountId = whitelisted_caller();
+		T::Currency::set_balance(
+			&caller.clone(),
+			T::Currency::minimum_balance().saturating_add(30u32.into()),
+		);
+		T::Currency::set_balance(&Broker::<T>::account_id(), T::Currency::minimum_balance());
+
+		<T::Coretime as CoretimeInterface>::ensure_notify_revenue_info(10u32.into(), 10u32.into());
+
+		InstaPoolHistory::<T>::insert(
+			4u32,
+			InstaPoolHistoryRecord {
+				private_contributions: 1u32.into(),
+				system_contributions: 9u32.into(),
+				maybe_payout: None,
+			},
+		);
+
+		#[block]
+		{
+			Broker::<T>::process_revenue();
+		}
+
+		assert_last_event::<T>(
+			Event::ClaimsReady {
+				when: 4u32.into(),
+				system_payout: 9u32.into(),
+				private_payout: 1u32.into(),
+			}
+			.into(),
+		);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn rotate_sale(n: Linear<0, { MAX_CORE_COUNT.into() }>) {
+		let core_count = n.try_into().unwrap();
+		let config = new_config_record::<T>();
+
+		let now = frame_system::Pallet::<T>::block_number();
+		let price = 10u32.into();
+		let commit_timeslice = Broker::<T>::latest_timeslice_ready_to_commit(&config);
+		let sale = SaleInfoRecordOf::<T> {
+			sale_start: now,
+			leadin_length: Zero::zero(),
+			price,
+			sellout_price: None,
+			region_begin: commit_timeslice,
+			region_end: commit_timeslice.saturating_add(config.region_length),
+			first_core: 0,
+			ideal_cores_sold: 0,
+			cores_offered: 0,
+			cores_sold: 0,
+		};
+
+		let status = StatusRecord {
+			core_count,
+			private_pool_size: 0,
+			system_pool_size: 0,
+			last_committed_timeslice: commit_timeslice.saturating_sub(1),
+			last_timeslice: Broker::<T>::current_timeslice(),
+		};
+
+		// Assume Reservations to be filled for worst case
+		setup_reservations::<T>(T::MaxReservedCores::get());
+
+		// Assume Leases to be filled for worst case
+		setup_leases::<T>(T::MaxLeasedCores::get(), 1, 10);
+
+		#[block]
+		{
+			Broker::<T>::rotate_sale(sale, &config, &status);
+		}
+
+		assert!(SaleInfo::<T>::get().is_some());
+		assert_last_event::<T>(
+			Event::SaleInitialized {
+				sale_start: 2u32.into(),
+				leadin_length: 1u32.into(),
+				start_price: 20u32.into(),
+				regular_price: 10u32.into(),
+				region_begin: 4,
+				region_end: 7,
+				ideal_cores_sold: 0,
+				cores_offered: n
+					.saturating_sub(T::MaxReservedCores::get())
+					.saturating_sub(T::MaxLeasedCores::get())
+					.try_into()
+					.unwrap(),
+			}
+			.into(),
+		);
+	}
+
+	#[benchmark]
+	fn process_pool() {
+		let when = 10u32.into();
+		let private_pool_size = 5u32.into();
+		let system_pool_size = 4u32.into();
+
+		let config = new_config_record::<T>();
+		let commit_timeslice = Broker::<T>::latest_timeslice_ready_to_commit(&config);
+		let mut status = StatusRecord {
+			core_count: 5u16.into(),
+			private_pool_size,
+			system_pool_size,
+			last_committed_timeslice: commit_timeslice.saturating_sub(1),
+			last_timeslice: Broker::<T>::current_timeslice(),
+		};
+
+		#[block]
+		{
+			Broker::<T>::process_pool(when, &mut status);
+		}
+
+		assert!(InstaPoolHistory::<T>::get(when).is_some());
+		assert_last_event::<T>(
+			Event::HistoryInitialized { when, private_pool_size, system_pool_size }.into(),
+		);
+	}
+
+	#[benchmark]
+	fn process_core_schedule() {
+		let timeslice = 10u32.into();
+		let core = 5u16.into();
+		let rc_begin = 1u32.into();
+
+		Workplan::<T>::insert((timeslice, core), new_schedule());
+
+		#[block]
+		{
+			Broker::<T>::process_core_schedule(timeslice, rc_begin, core);
+		}
+
+		assert_eq!(Workload::<T>::get(core).len(), CORE_MASK_BITS);
+
+		let mut assignment: Vec<(CoreAssignment, PartsOf57600)> = vec![];
+		for i in 0..CORE_MASK_BITS {
+			assignment.push((CoreAssignment::Task(i.try_into().unwrap()), 57600));
+		}
+		assert_last_event::<T>(Event::CoreAssigned { core, when: rc_begin, assignment }.into());
+	}
+
+	#[benchmark]
+	fn request_revenue_info_at() {
+		let current_timeslice = Broker::<T>::current_timeslice();
+		let rc_block = T::TimeslicePeriod::get() * current_timeslice.into();
+
+		#[block]
+		{
+			T::Coretime::request_revenue_info_at(rc_block);
+		}
+	}
+
+	// Implements a test for each benchmark. Execute with:
+	// `cargo test -p pallet-broker --features runtime-benchmarks`.
+	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
+}
diff --git a/substrate/frame/broker/src/core_mask.rs b/substrate/frame/broker/src/core_mask.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b8d045077d8285c1b7998e109733d25f4f946d5d
--- /dev/null
+++ b/substrate/frame/broker/src/core_mask.rs
@@ -0,0 +1,227 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use codec::{Decode, Encode, MaxEncodedLen};
+use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
+use scale_info::TypeInfo;
+use sp_core::RuntimeDebug;
+
+/// The number of bits in the `CoreMask`.
+pub const CORE_MASK_BITS: usize = 80;
+
+// TODO: Use BitArr instead; for this, we'll need to ensure Codec is impl'ed for `BitArr`.
+#[derive(
+	Encode, Decode, Default, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen,
+)]
+pub struct CoreMask([u8; 10]);
+impl CoreMask {
+	pub fn void() -> Self {
+		Self([0u8; 10])
+	}
+	pub fn complete() -> Self {
+		Self([255u8; 10])
+	}
+	pub fn is_void(&self) -> bool {
+		&self.0 == &[0u8; 10]
+	}
+	pub fn is_complete(&self) -> bool {
+		&self.0 == &[255u8; 10]
+	}
+	pub fn set(&mut self, i: u32) -> Self {
+		if i < 80 {
+			self.0[(i / 8) as usize] |= 128 >> (i % 8);
+		}
+		*self
+	}
+	pub fn clear(&mut self, i: u32) -> Self {
+		if i < 80 {
+			self.0[(i / 8) as usize] &= !(128 >> (i % 8));
+		}
+		*self
+	}
+	pub fn count_zeros(&self) -> u32 {
+		self.0.iter().map(|i| i.count_zeros()).sum()
+	}
+	pub fn count_ones(&self) -> u32 {
+		self.0.iter().map(|i| i.count_ones()).sum()
+	}
+	pub fn from_chunk(from: u32, to: u32) -> Self {
+		let mut v = [0u8; 10];
+		for i in (from.min(80) as usize)..(to.min(80) as usize) {
+			v[i / 8] |= 128 >> (i % 8);
+		}
+		Self(v)
+	}
+}
+impl From<u128> for CoreMask {
+	fn from(x: u128) -> Self {
+		let mut v = [0u8; 10];
+		v.iter_mut().rev().fold(x, |a, i| {
+			*i = a as u8;
+			a >> 8
+		});
+		Self(v)
+	}
+}
+impl From<CoreMask> for u128 {
+	fn from(x: CoreMask) -> Self {
+		x.0.into_iter().fold(0u128, |a, i| a << 8 | i as u128)
+	}
+}
+impl BitAnd<Self> for CoreMask {
+	type Output = Self;
+	fn bitand(mut self, rhs: Self) -> Self {
+		self.bitand_assign(rhs);
+		self
+	}
+}
+impl BitAndAssign<Self> for CoreMask {
+	fn bitand_assign(&mut self, rhs: Self) {
+		for i in 0..10 {
+			self.0[i].bitand_assign(rhs.0[i]);
+		}
+	}
+}
+impl BitOr<Self> for CoreMask {
+	type Output = Self;
+	fn bitor(mut self, rhs: Self) -> Self {
+		self.bitor_assign(rhs);
+		self
+	}
+}
+impl BitOrAssign<Self> for CoreMask {
+	fn bitor_assign(&mut self, rhs: Self) {
+		for i in 0..10 {
+			self.0[i].bitor_assign(rhs.0[i]);
+		}
+	}
+}
+impl BitXor<Self> for CoreMask {
+	type Output = Self;
+	fn bitxor(mut self, rhs: Self) -> Self {
+		self.bitxor_assign(rhs);
+		self
+	}
+}
+impl BitXorAssign<Self> for CoreMask {
+	fn bitxor_assign(&mut self, rhs: Self) {
+		for i in 0..10 {
+			self.0[i].bitxor_assign(rhs.0[i]);
+		}
+	}
+}
+impl Not for CoreMask {
+	type Output = Self;
+	fn not(self) -> Self {
+		let mut result = [0u8; 10];
+		for i in 0..10 {
+			result[i] = self.0[i].not();
+		}
+		Self(result)
+	}
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn complete_works() {
+		assert_eq!(CoreMask::complete(), CoreMask([0xff; 10]));
+		assert!(CoreMask([0xff; 10]).is_complete());
+		for i in 0..80 {
+			assert!(!CoreMask([0xff; 10]).clear(i).is_complete());
+		}
+	}
+
+	#[test]
+	fn void_works() {
+		assert_eq!(CoreMask::void(), CoreMask([0; 10]));
+		assert!(CoreMask([0; 10]).is_void());
+		for i in 0..80 {
+			assert!(!(CoreMask([0; 10]).set(i).is_void()));
+		}
+	}
+
+	#[test]
+	fn from_works() {
+		assert!(CoreMask::from(0xfffff_fffff_fffff_fffff).is_complete());
+		assert_eq!(
+			CoreMask::from(0x12345_67890_abcde_f0123),
+			CoreMask([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x01, 0x23]),
+		);
+	}
+
+	#[test]
+	fn into_works() {
+		assert_eq!(u128::from(CoreMask::complete()), 0xfffff_fffff_fffff_fffff);
+		assert_eq!(
+			0x12345_67890_abcde_f0123u128,
+			CoreMask([0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x01, 0x23]).into(),
+		);
+	}
+
+	#[test]
+	fn chunk_works() {
+		assert_eq!(CoreMask::from_chunk(40, 60), CoreMask::from(0x00000_00000_fffff_00000),);
+	}
+
+	#[test]
+	fn bit_or_works() {
+		assert_eq!(
+			CoreMask::from(0x02040_a0c0e_d0a0b_0ffff) | CoreMask::from(0x10305_0b0d0_0e0d0_e0000),
+			CoreMask::from(0x12345_abcde_deadb_effff),
+		);
+	}
+
+	#[test]
+	fn bit_or_assign_works() {
+		let mut a = CoreMask::from(0x02040_a0c0e_d0a0b_0ffff);
+		a |= CoreMask::from(0x10305_0b0d0_0e0d0_e0000);
+		assert_eq!(a, CoreMask::from(0x12345_abcde_deadb_effff));
+	}
+
+	#[test]
+	fn bit_and_works() {
+		assert_eq!(
+			CoreMask::from(0x00000_abcde_deadb_efff0) & CoreMask::from(0x02040_00000_d0a0b_0ff0f),
+			CoreMask::from(0x00000_00000_d0a0b_0ff00),
+		);
+	}
+
+	#[test]
+	fn bit_and_assign_works() {
+		let mut a = CoreMask::from(0x00000_abcde_deadb_efff0);
+		a &= CoreMask::from(0x02040_00000_d0a0b_0ff0f);
+		assert_eq!(a, CoreMask::from(0x00000_00000_d0a0b_0ff00));
+	}
+
+	#[test]
+	fn bit_xor_works() {
+		assert_eq!(
+			CoreMask::from(0x10010_10010_10010_10010) ^ CoreMask::from(0x01110_01110_01110_01110),
+			CoreMask::from(0x11100_11100_11100_11100),
+		);
+	}
+
+	#[test]
+	fn bit_xor_assign_works() {
+		let mut a = CoreMask::from(0x10010_10010_10010_10010);
+		a ^= CoreMask::from(0x01110_01110_01110_01110);
+		assert_eq!(a, CoreMask::from(0x11100_11100_11100_11100));
+	}
+}
diff --git a/substrate/frame/broker/src/coretime_interface.rs b/substrate/frame/broker/src/coretime_interface.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fec40b9fdd7b3d0e18216a2f634040b7bffc9279
--- /dev/null
+++ b/substrate/frame/broker/src/coretime_interface.rs
@@ -0,0 +1,168 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![deny(missing_docs)]
+
+use codec::{Decode, Encode, MaxEncodedLen};
+use frame_support::Parameter;
+use scale_info::TypeInfo;
+use sp_arithmetic::traits::AtLeast32BitUnsigned;
+use sp_core::RuntimeDebug;
+use sp_std::{fmt::Debug, vec::Vec};
+
+/// Index of a Polkadot Core.
+pub type CoreIndex = u16;
+
+/// A Task Id. In general this is called a ParachainId.
+pub type TaskId = u32;
+
+/// Fraction expressed as a nominator with an assumed denominator of 57,600.
+pub type PartsOf57600 = u16;
+
+/// An element to which a core can be assigned.
+#[derive(
+	Encode, Decode, Clone, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo, MaxEncodedLen,
+)]
+pub enum CoreAssignment {
+	/// Core need not be used for anything.
+	Idle,
+	/// Core should be used for the Instantaneous Coretime Pool.
+	Pool,
+	/// Core should be used to process the given task.
+	Task(TaskId),
+}
+
+/// Type able to accept Coretime scheduling instructions and provide certain usage information.
+/// Generally implemented by the Relay-chain or some means of communicating with it.
+///
+/// The trait representation of RFC#5 `<https://github.com/polkadot-fellows/RFCs/pull/5>`.
+pub trait CoretimeInterface {
+	/// A (Relay-chain-side) account ID.
+	type AccountId: Parameter;
+
+	/// A (Relay-chain-side) balance.
+	type Balance: AtLeast32BitUnsigned;
+
+	/// A (Relay-chain-side) block number.
+	type BlockNumber: AtLeast32BitUnsigned
+		+ Copy
+		+ TypeInfo
+		+ Encode
+		+ Decode
+		+ MaxEncodedLen
+		+ Debug;
+
+	/// Return the latest block number on the Relay-chain.
+	fn latest() -> Self::BlockNumber;
+
+	/// Requests the Relay-chain to alter the number of schedulable cores to `count`. Under normal
+	/// operation, the Relay-chain SHOULD send a `notify_core_count(count)` message back.
+	fn request_core_count(count: CoreIndex);
+
+	/// Requests that the Relay-chain send a `notify_revenue` message back at or soon after
+	/// Relay-chain block number `when` whose `until` parameter is equal to `when`.
+	///
+	/// `when` may never be greater than the result of `Self::latest()`.
+	/// The period in to the past which `when` is allowed to be may be limited; if so the limit
+	/// should be understood on a channel outside of this proposal. In the case that the request
+	/// cannot be serviced because `when` is too old a block then a `notify_revenue` message must
+	/// still be returned, but its `revenue` field may be `None`.
+	fn request_revenue_info_at(when: Self::BlockNumber);
+
+	/// Instructs the Relay-chain to add the `amount` of DOT to the Instantaneous Coretime Market
+	/// Credit account of `who`.
+	///
+	/// It is expected that Instantaneous Coretime Market Credit on the Relay-chain is NOT
+	/// transferrable and only redeemable when used to assign cores in the Instantaneous Coretime
+	/// Pool.
+	fn credit_account(who: Self::AccountId, amount: Self::Balance);
+
+	/// Instructs the Relay-chain to ensure that the core indexed as `core` is utilised for a number
+	/// of assignments in specific ratios given by `assignment` starting as soon after `begin` as
+	/// possible. Core assignments take the form of a `CoreAssignment` value which can either task
+	/// the core to a `ParaId` value or indicate that the core should be used in the Instantaneous
+	/// Pool. Each assignment comes with a ratio value, represented as the numerator of the fraction
+	/// with a denominator of 57,600.
+	///
+	/// If `end_hint` is `Some` and the inner is greater than the current block number, then the
+	/// Relay-chain should optimize in the expectation of receiving a new `assign_core(core, ...)`
+	/// message at or prior to the block number of the inner value. Specific functionality should
+	/// remain unchanged regardless of the `end_hint` value.
+	fn assign_core(
+		core: CoreIndex,
+		begin: Self::BlockNumber,
+		assignment: Vec<(CoreAssignment, PartsOf57600)>,
+		end_hint: Option<Self::BlockNumber>,
+	);
+
+	/// Indicate that from this block onwards, the range of acceptable values of the `core`
+	/// parameter of `assign_core` message is `[0, count)`. `assign_core` will be a no-op if
+	/// provided with a value for `core` outside of this range.
+	fn check_notify_core_count() -> Option<u16>;
+
+	/// Provide the amount of revenue accumulated from Instantaneous Coretime Sales from Relay-chain
+	/// block number `last_until` to `until`, not including `until` itself. `last_until` is defined
+	/// as being the `until` argument of the last `notify_revenue` message sent, or zero for the
+	/// first call. If `revenue` is `None`, this indicates that the information is no longer
+	/// available.
+	///
+	/// This explicitly disregards the possibility of multiple parachains requesting and being
+	/// notified of revenue information. The Relay-chain must be configured to ensure that only a
+	/// single revenue information destination exists.
+	fn check_notify_revenue_info() -> Option<(Self::BlockNumber, Self::Balance)>;
+
+	/// Ensure that core count is updated to the provided value.
+	///
+	/// This is only used for benchmarking.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_notify_core_count(count: u16);
+
+	/// Ensure that revenue information is updated to the provided value.
+	///
+	/// This is only used for benchmarking.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_notify_revenue_info(when: Self::BlockNumber, revenue: Self::Balance);
+}
+
+impl CoretimeInterface for () {
+	type AccountId = ();
+	type Balance = u64;
+	type BlockNumber = u32;
+	fn latest() -> Self::BlockNumber {
+		0
+	}
+	fn request_core_count(_count: CoreIndex) {}
+	fn request_revenue_info_at(_when: Self::BlockNumber) {}
+	fn credit_account(_who: Self::AccountId, _amount: Self::Balance) {}
+	fn assign_core(
+		_core: CoreIndex,
+		_begin: Self::BlockNumber,
+		_assignment: Vec<(CoreAssignment, PartsOf57600)>,
+		_end_hint: Option<Self::BlockNumber>,
+	) {
+	}
+	fn check_notify_core_count() -> Option<u16> {
+		None
+	}
+	fn check_notify_revenue_info() -> Option<(Self::BlockNumber, Self::Balance)> {
+		None
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_notify_core_count(_count: u16) {}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_notify_revenue_info(_when: Self::BlockNumber, _revenue: Self::Balance) {}
+}
diff --git a/substrate/frame/broker/src/dispatchable_impls.rs b/substrate/frame/broker/src/dispatchable_impls.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7c1d5a786b7cfb1f5474b2a1764a36b9f6478d57
--- /dev/null
+++ b/substrate/frame/broker/src/dispatchable_impls.rs
@@ -0,0 +1,436 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::*;
+use frame_support::{
+	pallet_prelude::{DispatchResult, *},
+	traits::{fungible::Mutate, tokens::Preservation::Expendable, DefensiveResult},
+};
+use sp_arithmetic::traits::{CheckedDiv, Saturating, Zero};
+use sp_runtime::traits::Convert;
+use CompletionStatus::{Complete, Partial};
+
+impl<T: Config> Pallet<T> {
+	pub(crate) fn do_configure(config: ConfigRecordOf<T>) -> DispatchResult {
+		config.validate().map_err(|()| Error::<T>::InvalidConfig)?;
+		Configuration::<T>::put(config);
+		Ok(())
+	}
+
+	pub(crate) fn do_request_core_count(core_count: CoreIndex) -> DispatchResult {
+		T::Coretime::request_core_count(core_count);
+		Self::deposit_event(Event::<T>::CoreCountRequested { core_count });
+		Ok(())
+	}
+
+	pub(crate) fn do_reserve(workload: Schedule) -> DispatchResult {
+		let mut r = Reservations::<T>::get();
+		let index = r.len() as u32;
+		r.try_push(workload.clone()).map_err(|_| Error::<T>::TooManyReservations)?;
+		Reservations::<T>::put(r);
+		Self::deposit_event(Event::<T>::ReservationMade { index, workload });
+		Ok(())
+	}
+
+	pub(crate) fn do_unreserve(index: u32) -> DispatchResult {
+		let mut r = Reservations::<T>::get();
+		ensure!(index < r.len() as u32, Error::<T>::UnknownReservation);
+		let workload = r.remove(index as usize);
+		Reservations::<T>::put(r);
+		Self::deposit_event(Event::<T>::ReservationCancelled { index, workload });
+		Ok(())
+	}
+
+	pub(crate) fn do_set_lease(task: TaskId, until: Timeslice) -> DispatchResult {
+		let mut r = Leases::<T>::get();
+		ensure!(until > Self::current_timeslice(), Error::<T>::AlreadyExpired);
+		r.try_push(LeaseRecordItem { until, task })
+			.map_err(|_| Error::<T>::TooManyLeases)?;
+		Leases::<T>::put(r);
+		Self::deposit_event(Event::<T>::Leased { until, task });
+		Ok(())
+	}
+
+	pub(crate) fn do_start_sales(price: BalanceOf<T>, core_count: CoreIndex) -> DispatchResult {
+		let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let commit_timeslice = Self::latest_timeslice_ready_to_commit(&config);
+		let status = StatusRecord {
+			core_count,
+			private_pool_size: 0,
+			system_pool_size: 0,
+			last_committed_timeslice: commit_timeslice.saturating_sub(1),
+			last_timeslice: Self::current_timeslice(),
+		};
+		let now = frame_system::Pallet::<T>::block_number();
+		let dummy_sale = SaleInfoRecord {
+			sale_start: now,
+			leadin_length: Zero::zero(),
+			price,
+			sellout_price: None,
+			region_begin: commit_timeslice,
+			region_end: commit_timeslice.saturating_add(config.region_length),
+			first_core: 0,
+			ideal_cores_sold: 0,
+			cores_offered: 0,
+			cores_sold: 0,
+		};
+		Self::deposit_event(Event::<T>::SalesStarted { price, core_count });
+		Self::rotate_sale(dummy_sale, &config, &status);
+		Status::<T>::put(&status);
+		Ok(())
+	}
+
+	pub(crate) fn do_purchase(
+		who: T::AccountId,
+		price_limit: BalanceOf<T>,
+	) -> Result<RegionId, DispatchError> {
+		let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let mut sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?;
+		ensure!(sale.first_core < status.core_count, Error::<T>::Unavailable);
+		ensure!(sale.cores_sold < sale.cores_offered, Error::<T>::SoldOut);
+		let now = frame_system::Pallet::<T>::block_number();
+		ensure!(now > sale.sale_start, Error::<T>::TooEarly);
+		let price = Self::sale_price(&sale, now);
+		ensure!(price_limit >= price, Error::<T>::Overpriced);
+
+		Self::charge(&who, price)?;
+		let core = sale.first_core.saturating_add(sale.cores_sold);
+		sale.cores_sold.saturating_inc();
+		if sale.cores_sold <= sale.ideal_cores_sold || sale.sellout_price.is_none() {
+			sale.sellout_price = Some(price);
+		}
+		SaleInfo::<T>::put(&sale);
+		let id = Self::issue(core, sale.region_begin, sale.region_end, who.clone(), Some(price));
+		let duration = sale.region_end.saturating_sub(sale.region_begin);
+		Self::deposit_event(Event::Purchased { who, region_id: id, price, duration });
+		Ok(id)
+	}
+
+	/// Must be called on a core in `AllowedRenewals` whose value is a timeslice equal to the
+	/// current sale status's `region_end`.
+	pub(crate) fn do_renew(who: T::AccountId, core: CoreIndex) -> Result<CoreIndex, DispatchError> {
+		let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let mut sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?;
+		ensure!(sale.first_core < status.core_count, Error::<T>::Unavailable);
+		ensure!(sale.cores_sold < sale.cores_offered, Error::<T>::SoldOut);
+
+		let renewal_id = AllowedRenewalId { core, when: sale.region_begin };
+		let record = AllowedRenewals::<T>::get(renewal_id).ok_or(Error::<T>::NotAllowed)?;
+		let workload =
+			record.completion.drain_complete().ok_or(Error::<T>::IncompleteAssignment)?;
+
+		let old_core = core;
+		let core = sale.first_core.saturating_add(sale.cores_sold);
+		Self::charge(&who, record.price)?;
+		Self::deposit_event(Event::Renewed {
+			who,
+			old_core,
+			core,
+			price: record.price,
+			begin: sale.region_begin,
+			duration: sale.region_end.saturating_sub(sale.region_begin),
+			workload: workload.clone(),
+		});
+
+		sale.cores_sold.saturating_inc();
+
+		Workplan::<T>::insert((sale.region_begin, core), &workload);
+
+		let begin = sale.region_end;
+		let price_cap = record.price + config.renewal_bump * record.price;
+		let now = frame_system::Pallet::<T>::block_number();
+		let price = Self::sale_price(&sale, now).min(price_cap);
+		let new_record = AllowedRenewalRecord { price, completion: Complete(workload) };
+		AllowedRenewals::<T>::remove(renewal_id);
+		AllowedRenewals::<T>::insert(AllowedRenewalId { core, when: begin }, &new_record);
+		SaleInfo::<T>::put(&sale);
+		if let Some(workload) = new_record.completion.drain_complete() {
+			Self::deposit_event(Event::Renewable { core, price, begin, workload });
+		}
+		Ok(core)
+	}
+
+	pub(crate) fn do_transfer(
+		region_id: RegionId,
+		maybe_check_owner: Option<T::AccountId>,
+		new_owner: T::AccountId,
+	) -> Result<(), Error<T>> {
+		let mut region = Regions::<T>::get(&region_id).ok_or(Error::<T>::UnknownRegion)?;
+
+		if let Some(check_owner) = maybe_check_owner {
+			ensure!(check_owner == region.owner, Error::<T>::NotOwner);
+		}
+
+		let old_owner = region.owner;
+		region.owner = new_owner;
+		Regions::<T>::insert(&region_id, &region);
+		let duration = region.end.saturating_sub(region_id.begin);
+		Self::deposit_event(Event::Transferred {
+			region_id,
+			old_owner,
+			owner: region.owner,
+			duration,
+		});
+
+		Ok(())
+	}
+
+	pub(crate) fn do_partition(
+		region_id: RegionId,
+		maybe_check_owner: Option<T::AccountId>,
+		pivot_offset: Timeslice,
+	) -> Result<(RegionId, RegionId), Error<T>> {
+		let mut region = Regions::<T>::get(&region_id).ok_or(Error::<T>::UnknownRegion)?;
+
+		if let Some(check_owner) = maybe_check_owner {
+			ensure!(check_owner == region.owner, Error::<T>::NotOwner);
+		}
+		let pivot = region_id.begin.saturating_add(pivot_offset);
+		ensure!(pivot < region.end, Error::<T>::PivotTooLate);
+		ensure!(pivot > region_id.begin, Error::<T>::PivotTooEarly);
+
+		region.paid = None;
+		let new_region_ids = (region_id, RegionId { begin: pivot, ..region_id });
+
+		Regions::<T>::insert(&new_region_ids.0, &RegionRecord { end: pivot, ..region.clone() });
+		Regions::<T>::insert(&new_region_ids.1, &region);
+		Self::deposit_event(Event::Partitioned { old_region_id: region_id, new_region_ids });
+
+		Ok(new_region_ids)
+	}
+
+	pub(crate) fn do_interlace(
+		region_id: RegionId,
+		maybe_check_owner: Option<T::AccountId>,
+		pivot: CoreMask,
+	) -> Result<(RegionId, RegionId), Error<T>> {
+		let region = Regions::<T>::get(&region_id).ok_or(Error::<T>::UnknownRegion)?;
+
+		if let Some(check_owner) = maybe_check_owner {
+			ensure!(check_owner == region.owner, Error::<T>::NotOwner);
+		}
+
+		ensure!((pivot & !region_id.mask).is_void(), Error::<T>::ExteriorPivot);
+		ensure!(!pivot.is_void(), Error::<T>::VoidPivot);
+		ensure!(pivot != region_id.mask, Error::<T>::CompletePivot);
+
+		let one = RegionId { mask: pivot, ..region_id };
+		Regions::<T>::insert(&one, &region);
+		let other = RegionId { mask: region_id.mask ^ pivot, ..region_id };
+		Regions::<T>::insert(&other, &region);
+
+		let new_region_ids = (one, other);
+		Self::deposit_event(Event::Interlaced { old_region_id: region_id, new_region_ids });
+		Ok(new_region_ids)
+	}
+
+	pub(crate) fn do_assign(
+		region_id: RegionId,
+		maybe_check_owner: Option<T::AccountId>,
+		target: TaskId,
+		finality: Finality,
+	) -> Result<(), Error<T>> {
+		let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		if let Some((region_id, region)) = Self::utilize(region_id, maybe_check_owner, finality)? {
+			let workplan_key = (region_id.begin, region_id.core);
+			let mut workplan = Workplan::<T>::get(&workplan_key).unwrap_or_default();
+			// Ensure no previous allocations exist.
+			workplan.retain(|i| (i.mask & region_id.mask).is_void());
+			if workplan
+				.try_push(ScheduleItem {
+					mask: region_id.mask,
+					assignment: CoreAssignment::Task(target),
+				})
+				.is_ok()
+			{
+				Workplan::<T>::insert(&workplan_key, &workplan);
+			}
+
+			let duration = region.end.saturating_sub(region_id.begin);
+			if duration == config.region_length && finality == Finality::Final {
+				if let Some(price) = region.paid {
+					let renewal_id = AllowedRenewalId { core: region_id.core, when: region.end };
+					let assigned = match AllowedRenewals::<T>::get(renewal_id) {
+						Some(AllowedRenewalRecord { completion: Partial(w), price: p })
+							if price == p =>
+							w,
+						_ => CoreMask::void(),
+					} | region_id.mask;
+					let workload =
+						if assigned.is_complete() { Complete(workplan) } else { Partial(assigned) };
+					let record = AllowedRenewalRecord { price, completion: workload };
+					AllowedRenewals::<T>::insert(&renewal_id, &record);
+					if let Some(workload) = record.completion.drain_complete() {
+						Self::deposit_event(Event::Renewable {
+							core: region_id.core,
+							price,
+							begin: region.end,
+							workload,
+						});
+					}
+				}
+			}
+			Self::deposit_event(Event::Assigned { region_id, task: target, duration });
+		}
+		Ok(())
+	}
+
+	pub(crate) fn do_pool(
+		region_id: RegionId,
+		maybe_check_owner: Option<T::AccountId>,
+		payee: T::AccountId,
+		finality: Finality,
+	) -> Result<(), Error<T>> {
+		if let Some((region_id, region)) = Self::utilize(region_id, maybe_check_owner, finality)? {
+			let workplan_key = (region_id.begin, region_id.core);
+			let mut workplan = Workplan::<T>::get(&workplan_key).unwrap_or_default();
+			let duration = region.end.saturating_sub(region_id.begin);
+			if workplan
+				.try_push(ScheduleItem { mask: region_id.mask, assignment: CoreAssignment::Pool })
+				.is_ok()
+			{
+				Workplan::<T>::insert(&workplan_key, &workplan);
+				let size = region_id.mask.count_ones() as i32;
+				InstaPoolIo::<T>::mutate(region_id.begin, |a| a.private.saturating_accrue(size));
+				InstaPoolIo::<T>::mutate(region.end, |a| a.private.saturating_reduce(size));
+				let record = ContributionRecord { length: duration, payee };
+				InstaPoolContribution::<T>::insert(&region_id, record);
+			}
+
+			Self::deposit_event(Event::Pooled { region_id, duration });
+		}
+		Ok(())
+	}
+
+	pub(crate) fn do_claim_revenue(
+		mut region: RegionId,
+		max_timeslices: Timeslice,
+	) -> DispatchResult {
+		let mut contribution =
+			InstaPoolContribution::<T>::take(region).ok_or(Error::<T>::UnknownContribution)?;
+		let contributed_parts = region.mask.count_ones();
+
+		Self::deposit_event(Event::RevenueClaimBegun { region, max_timeslices });
+
+		let mut payout = BalanceOf::<T>::zero();
+		let last = region.begin + contribution.length.min(max_timeslices);
+		for r in region.begin..last {
+			region.begin = r + 1;
+			contribution.length.saturating_dec();
+
+			let Some(mut pool_record) = InstaPoolHistory::<T>::get(r) else {
+				continue;
+			};
+			let Some(total_payout) = pool_record.maybe_payout else {
+				break;
+			};
+			let p = total_payout
+				.saturating_mul(contributed_parts.into())
+				.checked_div(&pool_record.private_contributions.into())
+				.unwrap_or_default();
+
+			payout.saturating_accrue(p);
+			pool_record.private_contributions.saturating_reduce(contributed_parts);
+
+			let remaining_payout = total_payout.saturating_sub(p);
+			if !remaining_payout.is_zero() && pool_record.private_contributions > 0 {
+				pool_record.maybe_payout = Some(remaining_payout);
+				InstaPoolHistory::<T>::insert(r, &pool_record);
+			} else {
+				InstaPoolHistory::<T>::remove(r);
+			}
+			if !p.is_zero() {
+				Self::deposit_event(Event::RevenueClaimItem { when: r, amount: p });
+			}
+		}
+
+		if contribution.length > 0 {
+			InstaPoolContribution::<T>::insert(region, &contribution);
+		}
+		T::Currency::transfer(&Self::account_id(), &contribution.payee, payout, Expendable)
+			.defensive_ok();
+		let next = if last < region.begin + contribution.length { Some(region) } else { None };
+		Self::deposit_event(Event::RevenueClaimPaid {
+			who: contribution.payee,
+			amount: payout,
+			next,
+		});
+		Ok(())
+	}
+
+	pub(crate) fn do_purchase_credit(
+		who: T::AccountId,
+		amount: BalanceOf<T>,
+		beneficiary: RelayAccountIdOf<T>,
+	) -> DispatchResult {
+		T::Currency::transfer(&who, &Self::account_id(), amount, Expendable)?;
+		let rc_amount = T::ConvertBalance::convert(amount);
+		T::Coretime::credit_account(beneficiary.clone(), rc_amount);
+		Self::deposit_event(Event::<T>::CreditPurchased { who, beneficiary, amount });
+		Ok(())
+	}
+
+	pub(crate) fn do_drop_region(region_id: RegionId) -> DispatchResult {
+		let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let region = Regions::<T>::get(&region_id).ok_or(Error::<T>::UnknownRegion)?;
+		ensure!(status.last_committed_timeslice >= region.end, Error::<T>::StillValid);
+
+		Regions::<T>::remove(&region_id);
+		let duration = region.end.saturating_sub(region_id.begin);
+		Self::deposit_event(Event::RegionDropped { region_id, duration });
+		Ok(())
+	}
+
+	pub(crate) fn do_drop_contribution(region_id: RegionId) -> DispatchResult {
+		let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let contrib =
+			InstaPoolContribution::<T>::get(&region_id).ok_or(Error::<T>::UnknownContribution)?;
+		let end = region_id.begin.saturating_add(contrib.length);
+		ensure!(
+			status.last_timeslice >= end.saturating_add(config.contribution_timeout),
+			Error::<T>::StillValid
+		);
+		InstaPoolContribution::<T>::remove(region_id);
+		Self::deposit_event(Event::ContributionDropped { region_id });
+		Ok(())
+	}
+
+	pub(crate) fn do_drop_history(when: Timeslice) -> DispatchResult {
+		let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		ensure!(status.last_timeslice > when + config.contribution_timeout, Error::<T>::StillValid);
+		let record = InstaPoolHistory::<T>::take(when).ok_or(Error::<T>::NoHistory)?;
+		if let Some(payout) = record.maybe_payout {
+			let _ = Self::charge(&Self::account_id(), payout);
+		}
+		let revenue = record.maybe_payout.unwrap_or_default();
+		Self::deposit_event(Event::HistoryDropped { when, revenue });
+		Ok(())
+	}
+
+	pub(crate) fn do_drop_renewal(core: CoreIndex, when: Timeslice) -> DispatchResult {
+		let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		ensure!(status.last_committed_timeslice >= when, Error::<T>::StillValid);
+		let id = AllowedRenewalId { core, when };
+		ensure!(AllowedRenewals::<T>::contains_key(id), Error::<T>::UnknownRenewal);
+		AllowedRenewals::<T>::remove(id);
+		Self::deposit_event(Event::AllowedRenewalDropped { core, when });
+		Ok(())
+	}
+}
diff --git a/substrate/frame/broker/src/lib.rs b/substrate/frame/broker/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4abd041f5f397c977ca06b8dbd6c294155b09b4f
--- /dev/null
+++ b/substrate/frame/broker/src/lib.rs
@@ -0,0 +1,784 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![doc = include_str!("../README.md")]
+
+pub use pallet::*;
+
+mod adapt_price;
+mod benchmarking;
+mod core_mask;
+mod coretime_interface;
+mod dispatchable_impls;
+#[cfg(test)]
+mod mock;
+mod nonfungible_impl;
+#[cfg(test)]
+mod test_fungibles;
+#[cfg(test)]
+mod tests;
+mod tick_impls;
+mod types;
+mod utility_impls;
+
+pub mod weights;
+pub use weights::WeightInfo;
+
+pub use adapt_price::*;
+pub use core_mask::*;
+pub use coretime_interface::*;
+pub use nonfungible_impl::*;
+pub use types::*;
+pub use utility_impls::*;
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::{
+		pallet_prelude::{DispatchResult, DispatchResultWithPostInfo, *},
+		traits::{
+			fungible::{Balanced, Credit, Mutate},
+			EnsureOrigin, OnUnbalanced,
+		},
+		PalletId,
+	};
+	use frame_system::pallet_prelude::*;
+	use sp_runtime::traits::{Convert, ConvertBack};
+	use sp_std::vec::Vec;
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
+
+		/// Weight information for all calls of this pallet.
+		type WeightInfo: WeightInfo;
+
+		/// Currency used to pay for Coretime.
+		type Currency: Mutate<Self::AccountId> + Balanced<Self::AccountId>;
+
+		/// The origin test needed for administrating this pallet.
+		type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
+
+		/// What to do with any revenues collected from the sale of Coretime.
+		type OnRevenue: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
+
+		/// Relay chain's Coretime API used to interact with and instruct the low-level scheduling
+		/// system.
+		type Coretime: CoretimeInterface;
+
+		/// The algorithm to determine the next price on the basis of market performance.
+		type PriceAdapter: AdaptPrice;
+
+		/// Reversible conversion from local balance to Relay-chain balance. This will typically be
+		/// the `Identity`, but provided just in case the chains use different representations.
+		type ConvertBalance: Convert<BalanceOf<Self>, RelayBalanceOf<Self>>
+			+ ConvertBack<BalanceOf<Self>, RelayBalanceOf<Self>>;
+
+		/// Identifier from which the internal Pot is generated.
+		#[pallet::constant]
+		type PalletId: Get<PalletId>;
+
+		/// Number of Relay-chain blocks per timeslice.
+		#[pallet::constant]
+		type TimeslicePeriod: Get<RelayBlockNumberOf<Self>>;
+
+		/// Maximum number of legacy leases.
+		#[pallet::constant]
+		type MaxLeasedCores: Get<u32>;
+
+		/// Maximum number of system cores.
+		#[pallet::constant]
+		type MaxReservedCores: Get<u32>;
+	}
+
+	/// The current configuration of this pallet.
+	#[pallet::storage]
+	pub type Configuration<T> = StorageValue<_, ConfigRecordOf<T>, OptionQuery>;
+
+	/// The Polkadot Core reservations (generally tasked with the maintenance of System Chains).
+	#[pallet::storage]
+	pub type Reservations<T> = StorageValue<_, ReservationsRecordOf<T>, ValueQuery>;
+
+	/// The Polkadot Core legacy leases.
+	#[pallet::storage]
+	pub type Leases<T> = StorageValue<_, LeasesRecordOf<T>, ValueQuery>;
+
+	/// The current status of miscellaneous subsystems of this pallet.
+	#[pallet::storage]
+	pub type Status<T> = StorageValue<_, StatusRecord, OptionQuery>;
+
+	/// The details of the current sale, including its properties and status.
+	#[pallet::storage]
+	pub type SaleInfo<T> = StorageValue<_, SaleInfoRecordOf<T>, OptionQuery>;
+
+	/// Records of allowed renewals.
+	#[pallet::storage]
+	pub type AllowedRenewals<T> =
+		StorageMap<_, Twox64Concat, AllowedRenewalId, AllowedRenewalRecordOf<T>, OptionQuery>;
+
+	/// The current (unassigned) Regions.
+	#[pallet::storage]
+	pub type Regions<T> = StorageMap<_, Blake2_128Concat, RegionId, RegionRecordOf<T>, OptionQuery>;
+
+	/// The work we plan on having each core do at a particular time in the future.
+	#[pallet::storage]
+	pub type Workplan<T> =
+		StorageMap<_, Twox64Concat, (Timeslice, CoreIndex), Schedule, OptionQuery>;
+
+	/// The current workload of each core. This gets updated with workplan as timeslices pass.
+	#[pallet::storage]
+	pub type Workload<T> = StorageMap<_, Twox64Concat, CoreIndex, Schedule, ValueQuery>;
+
+	/// Record of a single contribution to the Instantaneous Coretime Pool.
+	#[pallet::storage]
+	pub type InstaPoolContribution<T> =
+		StorageMap<_, Blake2_128Concat, RegionId, ContributionRecordOf<T>, OptionQuery>;
+
+	/// Record of Coretime entering or leaving the Instantaneous Coretime Pool.
+	#[pallet::storage]
+	pub type InstaPoolIo<T> = StorageMap<_, Blake2_128Concat, Timeslice, PoolIoRecord, ValueQuery>;
+
+	/// Total InstaPool rewards for each Timeslice and the number of core parts which contributed.
+	#[pallet::storage]
+	pub type InstaPoolHistory<T> =
+		StorageMap<_, Blake2_128Concat, Timeslice, InstaPoolHistoryRecordOf<T>>;
+
+	#[pallet::event]
+	#[pallet::generate_deposit(pub(super) fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// A Region of Bulk Coretime has been purchased.
+		Purchased {
+			/// The identity of the purchaser.
+			who: T::AccountId,
+			/// The identity of the Region.
+			region_id: RegionId,
+			/// The price paid for this Region.
+			price: BalanceOf<T>,
+			/// The duration of the Region.
+			duration: Timeslice,
+		},
+		/// The workload of a core has become renewable.
+		Renewable {
+			/// The core whose workload can be renewed.
+			core: CoreIndex,
+			/// The price at which the workload can be renewed.
+			price: BalanceOf<T>,
+			/// The time at which the workload would recommence of this renewal. The call to renew
+			/// cannot happen before the beginning of the interlude prior to the sale for regions
+			/// which begin at this time.
+			begin: Timeslice,
+			/// The actual workload which can be renewed.
+			workload: Schedule,
+		},
+		/// A workload has been renewed.
+		Renewed {
+			/// The identity of the renewer.
+			who: T::AccountId,
+			/// The price paid for this renewal.
+			price: BalanceOf<T>,
+			/// The index of the core on which the `workload` was previously scheduled.
+			old_core: CoreIndex,
+			/// The index of the core on which the renewed `workload` has been scheduled.
+			core: CoreIndex,
+			/// The time at which the `workload` will begin on the `core`.
+			begin: Timeslice,
+			/// The number of timeslices for which this `workload` is newly scheduled.
+			duration: Timeslice,
+			/// The workload which was renewed.
+			workload: Schedule,
+		},
+		/// Ownership of a Region has been transferred.
+		Transferred {
+			/// The Region which has been transferred.
+			region_id: RegionId,
+			/// The duration of the Region.
+			duration: Timeslice,
+			/// The old owner of the Region.
+			old_owner: T::AccountId,
+			/// The new owner of the Region.
+			owner: T::AccountId,
+		},
+		/// A Region has been split into two non-overlapping Regions.
+		Partitioned {
+			/// The Region which was split.
+			old_region_id: RegionId,
+			/// The new Regions into which it became.
+			new_region_ids: (RegionId, RegionId),
+		},
+		/// A Region has been converted into two overlapping Regions each of lesser regularity.
+		Interlaced {
+			/// The Region which was interlaced.
+			old_region_id: RegionId,
+			/// The new Regions into which it became.
+			new_region_ids: (RegionId, RegionId),
+		},
+		/// A Region has been assigned to a particular task.
+		Assigned {
+			/// The Region which was assigned.
+			region_id: RegionId,
+			/// The duration of the assignment.
+			duration: Timeslice,
+			/// The task to which the Region was assigned.
+			task: TaskId,
+		},
+		/// A Region has been added to the Instantaneous Coretime Pool.
+		Pooled {
+			/// The Region which was added to the Instantaneous Coretime Pool.
+			region_id: RegionId,
+			/// The duration of the Region.
+			duration: Timeslice,
+		},
+		/// A new number of cores has been requested.
+		CoreCountRequested {
+			/// The number of cores requested.
+			core_count: CoreIndex,
+		},
+		/// The number of cores available for scheduling has changed.
+		CoreCountChanged {
+			/// The new number of cores available for scheduling.
+			core_count: CoreIndex,
+		},
+		/// There is a new reservation for a workload.
+		ReservationMade {
+			/// The index of the reservation.
+			index: u32,
+			/// The workload of the reservation.
+			workload: Schedule,
+		},
+		/// A reservation for a workload has been cancelled.
+		ReservationCancelled {
+			/// The index of the reservation which was cancelled.
+			index: u32,
+			/// The workload of the now cancelled reservation.
+			workload: Schedule,
+		},
+		/// A new sale has been initialized.
+		SaleInitialized {
+			/// The local block number at which the sale will/did start.
+			sale_start: BlockNumberFor<T>,
+			/// The length in blocks of the Leadin Period (where the price is decreasing).
+			leadin_length: BlockNumberFor<T>,
+			/// The price of Bulk Coretime at the beginning of the Leadin Period.
+			start_price: BalanceOf<T>,
+			/// The price of Bulk Coretime after the Leadin Period.
+			regular_price: BalanceOf<T>,
+			/// The first timeslice of the Regions which are being sold in this sale.
+			region_begin: Timeslice,
+			/// The timeslice on which the Regions which are being sold in the sale terminate.
+			/// (i.e. One after the last timeslice which the Regions control.)
+			region_end: Timeslice,
+			/// The number of cores we want to sell, ideally. Selling this amount would result in
+			/// no change to the price for the next sale.
+			ideal_cores_sold: CoreIndex,
+			/// Number of cores which are/have been offered for sale.
+			cores_offered: CoreIndex,
+		},
+		/// A new lease has been created.
+		Leased {
+			/// The task to which a core will be assigned.
+			task: TaskId,
+			/// The timeslice contained in the sale period after which this lease will
+			/// self-terminate (and therefore the earliest timeslice at which the lease may no
+			/// longer apply).
+			until: Timeslice,
+		},
+		/// A lease is about to end.
+		LeaseEnding {
+			/// The task to which a core was assigned.
+			task: TaskId,
+			/// The timeslice at which the task will no longer be scheduled.
+			when: Timeslice,
+		},
+		/// The sale rotation has been started and a new sale is imminent.
+		SalesStarted {
+			/// The nominal price of an Region of Bulk Coretime.
+			price: BalanceOf<T>,
+			/// The maximum number of cores which this pallet will attempt to assign.
+			core_count: CoreIndex,
+		},
+		/// The act of claiming revenue has begun.
+		RevenueClaimBegun {
+			/// The region to be claimed for.
+			region: RegionId,
+			/// The maximum number of timeslices which should be searched for claimed.
+			max_timeslices: Timeslice,
+		},
+		/// A particular timeslice has a non-zero claim.
+		RevenueClaimItem {
+			/// The timeslice whose claim is being processed.
+			when: Timeslice,
+			/// The amount which was claimed at this timeslice.
+			amount: BalanceOf<T>,
+		},
+		/// A revenue claim has (possibly only in part) been paid.
+		RevenueClaimPaid {
+			/// The account to whom revenue has been paid.
+			who: T::AccountId,
+			/// The total amount of revenue claimed and paid.
+			amount: BalanceOf<T>,
+			/// The next region which should be claimed for the continuation of this contribution.
+			next: Option<RegionId>,
+		},
+		/// Some Instantaneous Coretime Pool credit has been purchased.
+		CreditPurchased {
+			/// The account which purchased the credit.
+			who: T::AccountId,
+			/// The Relay-chain account to which the credit will be made.
+			beneficiary: RelayAccountIdOf<T>,
+			/// The amount of credit purchased.
+			amount: BalanceOf<T>,
+		},
+		/// A Region has been dropped due to being out of date.
+		RegionDropped {
+			/// The Region which no longer exists.
+			region_id: RegionId,
+			/// The duration of the Region.
+			duration: Timeslice,
+		},
+		/// Some historical Instantaneous Core Pool contribution record has been dropped.
+		ContributionDropped {
+			/// The Region whose contribution is no longer exists.
+			region_id: RegionId,
+		},
+		/// Some historical Instantaneous Core Pool payment record has been initialized.
+		HistoryInitialized {
+			/// The timeslice whose history has been initialized.
+			when: Timeslice,
+			/// The amount of privately contributed Coretime to the Instantaneous Coretime Pool.
+			private_pool_size: CoreMaskBitCount,
+			/// The amount of Coretime contributed to the Instantaneous Coretime Pool by the
+			/// Polkadot System.
+			system_pool_size: CoreMaskBitCount,
+		},
+		/// Some historical Instantaneous Core Pool payment record has been dropped.
+		HistoryDropped {
+			/// The timeslice whose history is no longer available.
+			when: Timeslice,
+			/// The amount of revenue the system has taken.
+			revenue: BalanceOf<T>,
+		},
+		/// Some historical Instantaneous Core Pool payment record has been ignored because the
+		/// timeslice was already known. Governance may need to intervene.
+		HistoryIgnored {
+			/// The timeslice whose history is was ignored.
+			when: Timeslice,
+			/// The amount of revenue which was ignored.
+			revenue: BalanceOf<T>,
+		},
+		/// Some historical Instantaneous Core Pool Revenue is ready for payout claims.
+		ClaimsReady {
+			/// The timeslice whose history is available.
+			when: Timeslice,
+			/// The amount of revenue the Polkadot System has already taken.
+			system_payout: BalanceOf<T>,
+			/// The total amount of revenue remaining to be claimed.
+			private_payout: BalanceOf<T>,
+		},
+		/// A Core has been assigned to one or more tasks and/or the Pool on the Relay-chain.
+		CoreAssigned {
+			/// The index of the Core which has been assigned.
+			core: CoreIndex,
+			/// The Relay-chain block at which this assignment should take effect.
+			when: RelayBlockNumberOf<T>,
+			/// The workload to be done on the Core.
+			assignment: Vec<(CoreAssignment, PartsOf57600)>,
+		},
+		/// Some historical Instantaneous Core Pool payment record has been dropped.
+		AllowedRenewalDropped {
+			/// The timeslice whose renewal is no longer available.
+			when: Timeslice,
+			/// The core whose workload is no longer available to be renewed for `when`.
+			core: CoreIndex,
+		},
+	}
+
+	#[pallet::error]
+	#[derive(PartialEq)]
+	pub enum Error<T> {
+		/// The given region identity is not known.
+		UnknownRegion,
+		/// The owner of the region is not the origin.
+		NotOwner,
+		/// The pivot point of the partition at or after the end of the region.
+		PivotTooLate,
+		/// The pivot point of the partition at the beginning of the region.
+		PivotTooEarly,
+		/// The pivot mask for the interlacing is not contained within the region's interlace mask.
+		ExteriorPivot,
+		/// The pivot mask for the interlacing is void (and therefore unschedulable).
+		VoidPivot,
+		/// The pivot mask for the interlacing is complete (and therefore not a strict subset).
+		CompletePivot,
+		/// The workplan of the pallet's state is invalid. This indicates a state corruption.
+		CorruptWorkplan,
+		/// There is no sale happening currently.
+		NoSales,
+		/// The price limit is exceeded.
+		Overpriced,
+		/// There are no cores available.
+		Unavailable,
+		/// The sale limit has been reached.
+		SoldOut,
+		/// The renewal operation is not valid at the current time (it may become valid in the next
+		/// sale).
+		WrongTime,
+		/// Invalid attempt to renew.
+		NotAllowed,
+		/// This pallet has not yet been initialized.
+		Uninitialized,
+		/// The purchase cannot happen yet as the sale period is yet to begin.
+		TooEarly,
+		/// There is no work to be done.
+		NothingToDo,
+		/// The maximum amount of reservations has already been reached.
+		TooManyReservations,
+		/// The maximum amount of leases has already been reached.
+		TooManyLeases,
+		/// The revenue for the Instantaneous Core Sales of this period is not (yet) known and thus
+		/// this operation cannot proceed.
+		UnknownRevenue,
+		/// The identified contribution to the Instantaneous Core Pool is unknown.
+		UnknownContribution,
+		/// The workload assigned for renewal is incomplete. This is unexpected and indicates a
+		/// logic error.
+		IncompleteAssignment,
+		/// An item cannot be dropped because it is still valid.
+		StillValid,
+		/// The history item does not exist.
+		NoHistory,
+		/// No reservation of the given index exists.
+		UnknownReservation,
+		/// The renewal record cannot be found.
+		UnknownRenewal,
+		/// The lease expiry time has already passed.
+		AlreadyExpired,
+		/// The configuration could not be applied because it is invalid.
+		InvalidConfig,
+	}
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
+			Self::do_tick()
+		}
+	}
+
+	#[pallet::call(weight(<T as Config>::WeightInfo))]
+	impl<T: Config> Pallet<T> {
+		/// Configure the pallet.
+		///
+		/// - `origin`: Must be Root or pass `AdminOrigin`.
+		/// - `config`: The configuration for this pallet.
+		#[pallet::call_index(0)]
+		pub fn configure(
+			origin: OriginFor<T>,
+			config: ConfigRecordOf<T>,
+		) -> DispatchResultWithPostInfo {
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
+			Self::do_configure(config)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Reserve a core for a workload.
+		///
+		/// - `origin`: Must be Root or pass `AdminOrigin`.
+		/// - `workload`: The workload which should be permanently placed on a core.
+		#[pallet::call_index(1)]
+		pub fn reserve(origin: OriginFor<T>, workload: Schedule) -> DispatchResultWithPostInfo {
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
+			Self::do_reserve(workload)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Cancel a reservation for a workload.
+		///
+		/// - `origin`: Must be Root or pass `AdminOrigin`.
+		/// - `item_index`: The index of the reservation. Usually this will also be the index of the
+		///   core on which the reservation has been scheduled. However, it is possible that if
+		///   other cores are reserved or unreserved in the same sale rotation that they won't
+		///   correspond, so it's better to look up the core properly in the `Reservations` storage.
+		#[pallet::call_index(2)]
+		pub fn unreserve(origin: OriginFor<T>, item_index: u32) -> DispatchResultWithPostInfo {
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
+			Self::do_unreserve(item_index)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Reserve a core for a single task workload for a limited period.
+		///
+		/// In the interlude and sale period where Bulk Coretime is sold for the period immediately
+		/// after `until`, then the same workload may be renewed.
+		///
+		/// - `origin`: Must be Root or pass `AdminOrigin`.
+		/// - `task`: The workload which should be placed on a core.
+		/// - `until`: The timeslice now earlier than which `task` should be placed as a workload on
+		///   a core.
+		#[pallet::call_index(3)]
+		pub fn set_lease(
+			origin: OriginFor<T>,
+			task: TaskId,
+			until: Timeslice,
+		) -> DispatchResultWithPostInfo {
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
+			Self::do_set_lease(task, until)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Begin the Bulk Coretime sales rotation.
+		///
+		/// - `origin`: Must be Root or pass `AdminOrigin`.
+		/// - `initial_price`: The price of Bulk Coretime in the first sale.
+		/// - `core_count`: The number of cores which can be allocated.
+		#[pallet::call_index(4)]
+		#[pallet::weight(T::WeightInfo::start_sales((*core_count).into()))]
+		pub fn start_sales(
+			origin: OriginFor<T>,
+			initial_price: BalanceOf<T>,
+			core_count: CoreIndex,
+		) -> DispatchResultWithPostInfo {
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
+			Self::do_start_sales(initial_price, core_count)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Purchase Bulk Coretime in the ongoing Sale.
+		///
+		/// - `origin`: Must be a Signed origin with at least enough funds to pay the current price
+		///   of Bulk Coretime.
+		/// - `price_limit`: An amount no more than which should be paid.
+		#[pallet::call_index(5)]
+		pub fn purchase(
+			origin: OriginFor<T>,
+			price_limit: BalanceOf<T>,
+		) -> DispatchResultWithPostInfo {
+			let who = ensure_signed(origin)?;
+			Self::do_purchase(who, price_limit)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Renew Bulk Coretime in the ongoing Sale or its prior Interlude Period.
+		///
+		/// - `origin`: Must be a Signed origin with at least enough funds to pay the renewal price
+		///   of the core.
+		/// - `core`: The core which should be renewed.
+		#[pallet::call_index(6)]
+		pub fn renew(origin: OriginFor<T>, core: CoreIndex) -> DispatchResultWithPostInfo {
+			let who = ensure_signed(origin)?;
+			Self::do_renew(who, core)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Transfer a Bulk Coretime Region to a new owner.
+		///
+		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
+		/// - `region_id`: The Region whose ownership should change.
+		/// - `new_owner`: The new owner for the Region.
+		#[pallet::call_index(7)]
+		pub fn transfer(
+			origin: OriginFor<T>,
+			region_id: RegionId,
+			new_owner: T::AccountId,
+		) -> DispatchResult {
+			let who = ensure_signed(origin)?;
+			Self::do_transfer(region_id, Some(who), new_owner)?;
+			Ok(())
+		}
+
+		/// Split a Bulk Coretime Region into two non-overlapping Regions at a particular time into
+		/// the region.
+		///
+		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
+		/// - `region_id`: The Region which should be partitioned into two non-overlapping Regions.
+		/// - `pivot`: The offset in time into the Region at which to make the split.
+		#[pallet::call_index(8)]
+		pub fn partition(
+			origin: OriginFor<T>,
+			region_id: RegionId,
+			pivot: Timeslice,
+		) -> DispatchResult {
+			let who = ensure_signed(origin)?;
+			Self::do_partition(region_id, Some(who), pivot)?;
+			Ok(())
+		}
+
+		/// Split a Bulk Coretime Region into two wholly-overlapping Regions with complementary
+		/// interlace masks which together make up the original Region's interlace mask.
+		///
+		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
+		/// - `region_id`: The Region which should become two interlaced Regions of incomplete
+		///   regularity.
+		/// - `pivot`: The interlace mask of on of the two new regions (the other it its partial
+		///   complement).
+		#[pallet::call_index(9)]
+		pub fn interlace(
+			origin: OriginFor<T>,
+			region_id: RegionId,
+			pivot: CoreMask,
+		) -> DispatchResult {
+			let who = ensure_signed(origin)?;
+			Self::do_interlace(region_id, Some(who), pivot)?;
+			Ok(())
+		}
+
+		/// Assign a Bulk Coretime Region to a task.
+		///
+		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
+		/// - `region_id`: The Region which should be assigned to the task.
+		/// - `task`: The task to assign.
+		/// - `finality`: Indication of whether this assignment is final (in which case it may be
+		///   eligible for renewal) or provisional (in which case it may be manipulated and/or
+		/// reassigned at a later stage).
+		#[pallet::call_index(10)]
+		pub fn assign(
+			origin: OriginFor<T>,
+			region_id: RegionId,
+			task: TaskId,
+			finality: Finality,
+		) -> DispatchResultWithPostInfo {
+			let who = ensure_signed(origin)?;
+			Self::do_assign(region_id, Some(who), task, finality)?;
+			Ok(if finality == Finality::Final { Pays::No } else { Pays::Yes }.into())
+		}
+
+		/// Place a Bulk Coretime Region into the Instantaneous Coretime Pool.
+		///
+		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
+		/// - `region_id`: The Region which should be assigned to the Pool.
+		/// - `payee`: The account which is able to collect any revenue due for the usage of this
+		///   Coretime.
+		#[pallet::call_index(11)]
+		pub fn pool(
+			origin: OriginFor<T>,
+			region_id: RegionId,
+			payee: T::AccountId,
+			finality: Finality,
+		) -> DispatchResultWithPostInfo {
+			let who = ensure_signed(origin)?;
+			Self::do_pool(region_id, Some(who), payee, finality)?;
+			Ok(if finality == Finality::Final { Pays::No } else { Pays::Yes }.into())
+		}
+
+		/// Claim the revenue owed from inclusion in the Instantaneous Coretime Pool.
+		///
+		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
+		/// - `region_id`: The Region which was assigned to the Pool.
+		/// - `max_timeslices`: The maximum number of timeslices which should be processed. This may
+		///   effect the weight of the call but should be ideally made equivalant to the length of
+		///   the Region `region_id`. If it is less than this, then further dispatches will be
+		///   required with the `region_id` which makes up any remainders of the region to be
+		///   collected.
+		#[pallet::call_index(12)]
+		#[pallet::weight(T::WeightInfo::claim_revenue(*max_timeslices))]
+		pub fn claim_revenue(
+			origin: OriginFor<T>,
+			region_id: RegionId,
+			max_timeslices: Timeslice,
+		) -> DispatchResultWithPostInfo {
+			let _ = ensure_signed(origin)?;
+			Self::do_claim_revenue(region_id, max_timeslices)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Purchase credit for use in the Instantaneous Coretime Pool.
+		///
+		/// - `origin`: Must be a Signed origin able to pay at least `amount`.
+		/// - `amount`: The amount of credit to purchase.
+		/// - `beneficiary`: The account on the Relay-chain which controls the credit (generally
+		///   this will be the collator's hot wallet).
+		#[pallet::call_index(13)]
+		pub fn purchase_credit(
+			origin: OriginFor<T>,
+			amount: BalanceOf<T>,
+			beneficiary: RelayAccountIdOf<T>,
+		) -> DispatchResult {
+			let who = ensure_signed(origin)?;
+			Self::do_purchase_credit(who, amount, beneficiary)?;
+			Ok(())
+		}
+
+		/// Drop an expired Region from the chain.
+		///
+		/// - `origin`: Must be a Signed origin.
+		/// - `region_id`: The Region which has expired.
+		#[pallet::call_index(14)]
+		pub fn drop_region(
+			origin: OriginFor<T>,
+			region_id: RegionId,
+		) -> DispatchResultWithPostInfo {
+			let _ = ensure_signed(origin)?;
+			Self::do_drop_region(region_id)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Drop an expired Instantaneous Pool Contribution record from the chain.
+		///
+		/// - `origin`: Must be a Signed origin.
+		/// - `region_id`: The Region identifying the Pool Contribution which has expired.
+		#[pallet::call_index(15)]
+		pub fn drop_contribution(
+			origin: OriginFor<T>,
+			region_id: RegionId,
+		) -> DispatchResultWithPostInfo {
+			let _ = ensure_signed(origin)?;
+			Self::do_drop_contribution(region_id)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Drop an expired Instantaneous Pool History record from the chain.
+		///
+		/// - `origin`: Must be a Signed origin.
+		/// - `region_id`: The time of the Pool History record which has expired.
+		#[pallet::call_index(16)]
+		pub fn drop_history(origin: OriginFor<T>, when: Timeslice) -> DispatchResultWithPostInfo {
+			let _ = ensure_signed(origin)?;
+			Self::do_drop_history(when)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Drop an expired Allowed Renewal record from the chain.
+		///
+		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
+		/// - `core`: The core to which the expired renewal refers.
+		/// - `when`: The timeslice to which the expired renewal refers. This must have passed.
+		#[pallet::call_index(17)]
+		pub fn drop_renewal(
+			origin: OriginFor<T>,
+			core: CoreIndex,
+			when: Timeslice,
+		) -> DispatchResultWithPostInfo {
+			let _ = ensure_signed(origin)?;
+			Self::do_drop_renewal(core, when)?;
+			Ok(Pays::No.into())
+		}
+
+		/// Request a change to the number of cores available for scheduling work.
+		///
+		/// - `origin`: Must be Root or pass `AdminOrigin`.
+		/// - `core_count`: The desired number of cores to be made available.
+		#[pallet::call_index(18)]
+		#[pallet::weight(T::WeightInfo::request_core_count((*core_count).into()))]
+		pub fn request_core_count(origin: OriginFor<T>, core_count: CoreIndex) -> DispatchResult {
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
+			Self::do_request_core_count(core_count)?;
+			Ok(())
+		}
+	}
+}
diff --git a/substrate/frame/broker/src/mock.rs b/substrate/frame/broker/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..cab6b7389c06494a0bcddb4d8d02f8f734f67b8c
--- /dev/null
+++ b/substrate/frame/broker/src/mock.rs
@@ -0,0 +1,322 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![cfg(test)]
+
+use crate::{test_fungibles::TestFungibles, *};
+use frame_support::{
+	assert_ok, ensure, ord_parameter_types, parameter_types,
+	traits::{
+		fungible::{Balanced, Credit, Inspect, ItemOf, Mutate},
+		nonfungible::Inspect as NftInspect,
+		EitherOfDiverse, Hooks, OnUnbalanced,
+	},
+	PalletId,
+};
+use frame_system::{EnsureRoot, EnsureSignedBy};
+use sp_arithmetic::Perbill;
+use sp_core::{ConstU16, ConstU32, ConstU64, H256};
+use sp_runtime::{
+	traits::{BlakeTwo256, Identity, IdentityLookup},
+	BuildStorage, Saturating,
+};
+use sp_std::collections::btree_map::BTreeMap;
+
+type Block = frame_system::mocking::MockBlock<Test>;
+
+// Configure a mock runtime to test the pallet.
+frame_support::construct_runtime!(
+	pub enum Test
+	{
+		System: frame_system,
+		Broker: crate,
+	}
+);
+
+impl frame_system::Config for Test {
+	type BaseCallFilter = frame_support::traits::Everything;
+	type BlockWeights = ();
+	type BlockLength = ();
+	type DbWeight = ();
+	type RuntimeOrigin = RuntimeOrigin;
+	type RuntimeCall = RuntimeCall;
+	type Nonce = u64;
+	type Hash = H256;
+	type Hashing = BlakeTwo256;
+	type AccountId = u64;
+	type Lookup = IdentityLookup<Self::AccountId>;
+	type Block = Block;
+	type RuntimeEvent = RuntimeEvent;
+	type BlockHashCount = ConstU64<250>;
+	type Version = ();
+	type PalletInfo = PalletInfo;
+	type AccountData = ();
+	type OnNewAccount = ();
+	type OnKilledAccount = ();
+	type SystemWeightInfo = ();
+	type SS58Prefix = ConstU16<42>;
+	type OnSetCode = ();
+	type MaxConsumers = frame_support::traits::ConstU32<16>;
+}
+
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum CoretimeTraceItem {
+	AssignCore {
+		core: CoreIndex,
+		begin: u32,
+		assignment: Vec<(CoreAssignment, PartsOf57600)>,
+		end_hint: Option<u32>,
+	},
+}
+use CoretimeTraceItem::*;
+
+parameter_types! {
+	pub static CoretimeTrace: Vec<(u32, CoretimeTraceItem)> = Default::default();
+	pub static CoretimeCredit: BTreeMap<u64, u64> = Default::default();
+	pub static CoretimeSpending: Vec<(u32, u64)> = Default::default();
+	pub static CoretimeWorkplan: BTreeMap<(u32, CoreIndex), Vec<(CoreAssignment, PartsOf57600)>> = Default::default();
+	pub static CoretimeUsage: BTreeMap<CoreIndex, Vec<(CoreAssignment, PartsOf57600)>> = Default::default();
+	pub static CoretimeInPool: CoreMaskBitCount = 0;
+	pub static NotifyCoreCount: Vec<u16> = Default::default();
+	pub static NotifyRevenueInfo: Vec<(u32, u64)> = Default::default();
+}
+
+pub struct TestCoretimeProvider;
+impl CoretimeInterface for TestCoretimeProvider {
+	type AccountId = u64;
+	type Balance = u64;
+	type BlockNumber = u32;
+	fn latest() -> Self::BlockNumber {
+		System::block_number() as u32
+	}
+	fn request_core_count(count: CoreIndex) {
+		NotifyCoreCount::mutate(|s| s.insert(0, count));
+	}
+	fn request_revenue_info_at(when: Self::BlockNumber) {
+		if when > Self::latest() {
+			panic!("Asking for revenue info in the future {:?} {:?}", when, Self::latest());
+		}
+
+		let mut total = 0;
+		CoretimeSpending::mutate(|s| {
+			s.retain(|(n, a)| {
+				if *n < when {
+					total += a;
+					false
+				} else {
+					true
+				}
+			})
+		});
+		NotifyRevenueInfo::mutate(|s| s.insert(0, (when, total)));
+	}
+	fn credit_account(who: Self::AccountId, amount: Self::Balance) {
+		CoretimeCredit::mutate(|c| c.entry(who).or_default().saturating_accrue(amount));
+	}
+	fn assign_core(
+		core: CoreIndex,
+		begin: Self::BlockNumber,
+		assignment: Vec<(CoreAssignment, PartsOf57600)>,
+		end_hint: Option<Self::BlockNumber>,
+	) {
+		CoretimeWorkplan::mutate(|p| p.insert((begin, core), assignment.clone()));
+		let item = (Self::latest(), AssignCore { core, begin, assignment, end_hint });
+		CoretimeTrace::mutate(|v| v.push(item));
+	}
+	fn check_notify_core_count() -> Option<u16> {
+		NotifyCoreCount::mutate(|s| s.pop())
+	}
+	fn check_notify_revenue_info() -> Option<(Self::BlockNumber, Self::Balance)> {
+		NotifyRevenueInfo::mutate(|s| s.pop())
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_notify_core_count(count: u16) {
+		NotifyCoreCount::mutate(|s| s.insert(0, count));
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_notify_revenue_info(when: Self::BlockNumber, revenue: Self::Balance) {
+		NotifyRevenueInfo::mutate(|s| s.push((when, revenue)));
+	}
+}
+impl TestCoretimeProvider {
+	pub fn spend_instantaneous(who: u64, price: u64) -> Result<(), ()> {
+		let mut c = CoretimeCredit::get();
+		ensure!(CoretimeInPool::get() > 0, ());
+		c.insert(who, c.get(&who).ok_or(())?.checked_sub(price).ok_or(())?);
+		CoretimeCredit::set(c);
+		CoretimeSpending::mutate(|v| v.push((Self::latest(), price)));
+		Ok(())
+	}
+	pub fn bump() {
+		let mut pool_size = CoretimeInPool::get();
+		let mut workplan = CoretimeWorkplan::get();
+		let mut usage = CoretimeUsage::get();
+		let now = Self::latest();
+		workplan.retain(|(when, core), assignment| {
+			if *when <= now {
+				if let Some(old_assignment) = usage.get(core) {
+					if let Some(a) = old_assignment.iter().find(|i| i.0 == CoreAssignment::Pool) {
+						pool_size -= (a.1 / 720) as CoreMaskBitCount;
+					}
+				}
+				if let Some(a) = assignment.iter().find(|i| i.0 == CoreAssignment::Pool) {
+					pool_size += (a.1 / 720) as CoreMaskBitCount;
+				}
+				usage.insert(*core, assignment.clone());
+				false
+			} else {
+				true
+			}
+		});
+		CoretimeInPool::set(pool_size);
+		CoretimeWorkplan::set(workplan);
+		CoretimeUsage::set(usage);
+	}
+}
+
+parameter_types! {
+	pub const TestBrokerId: PalletId = PalletId(*b"TsBroker");
+}
+
+pub struct IntoZero;
+impl OnUnbalanced<Credit<u64, <Test as Config>::Currency>> for IntoZero {
+	fn on_nonzero_unbalanced(credit: Credit<u64, <Test as Config>::Currency>) {
+		let _ = <<Test as Config>::Currency as Balanced<_>>::resolve(&0, credit);
+	}
+}
+
+ord_parameter_types! {
+	pub const One: u64 = 1;
+}
+type EnsureOneOrRoot = EitherOfDiverse<EnsureRoot<u64>, EnsureSignedBy<One, u64>>;
+
+impl crate::Config for Test {
+	type RuntimeEvent = RuntimeEvent;
+	type Currency = ItemOf<TestFungibles<(), u64, (), ConstU64<0>, ()>, (), u64>;
+	type OnRevenue = IntoZero;
+	type TimeslicePeriod = ConstU32<2>;
+	type MaxLeasedCores = ConstU32<5>;
+	type MaxReservedCores = ConstU32<5>;
+	type Coretime = TestCoretimeProvider;
+	type ConvertBalance = Identity;
+	type WeightInfo = ();
+	type PalletId = TestBrokerId;
+	type AdminOrigin = EnsureOneOrRoot;
+	type PriceAdapter = Linear;
+}
+
+pub fn advance_to(b: u64) {
+	while System::block_number() < b {
+		System::set_block_number(System::block_number() + 1);
+		TestCoretimeProvider::bump();
+		Broker::on_initialize(System::block_number());
+	}
+}
+
+pub fn pot() -> u64 {
+	balance(Broker::account_id())
+}
+
+pub fn revenue() -> u64 {
+	balance(0)
+}
+
+pub fn balance(who: u64) -> u64 {
+	<<Test as Config>::Currency as Inspect<_>>::total_balance(&who)
+}
+
+pub fn attribute<T: codec::Decode>(nft: RegionId, attribute: impl codec::Encode) -> T {
+	<Broker as NftInspect<_>>::typed_attribute::<_, T>(&nft.into(), &attribute).unwrap()
+}
+
+pub fn new_config() -> ConfigRecordOf<Test> {
+	ConfigRecord {
+		advance_notice: 2,
+		interlude_length: 1,
+		leadin_length: 1,
+		ideal_bulk_proportion: Default::default(),
+		limit_cores_offered: None,
+		region_length: 3,
+		renewal_bump: Perbill::from_percent(10),
+		contribution_timeout: 5,
+	}
+}
+
+pub struct TestExt(ConfigRecordOf<Test>);
+#[allow(dead_code)]
+impl TestExt {
+	pub fn new() -> Self {
+		Self(new_config())
+	}
+
+	pub fn advance_notice(mut self, advance_notice: Timeslice) -> Self {
+		self.0.advance_notice = advance_notice;
+		self
+	}
+
+	pub fn interlude_length(mut self, interlude_length: u64) -> Self {
+		self.0.interlude_length = interlude_length;
+		self
+	}
+
+	pub fn leadin_length(mut self, leadin_length: u64) -> Self {
+		self.0.leadin_length = leadin_length;
+		self
+	}
+
+	pub fn region_length(mut self, region_length: Timeslice) -> Self {
+		self.0.region_length = region_length;
+		self
+	}
+
+	pub fn ideal_bulk_proportion(mut self, ideal_bulk_proportion: Perbill) -> Self {
+		self.0.ideal_bulk_proportion = ideal_bulk_proportion;
+		self
+	}
+
+	pub fn limit_cores_offered(mut self, limit_cores_offered: Option<CoreIndex>) -> Self {
+		self.0.limit_cores_offered = limit_cores_offered;
+		self
+	}
+
+	pub fn renewal_bump(mut self, renewal_bump: Perbill) -> Self {
+		self.0.renewal_bump = renewal_bump;
+		self
+	}
+
+	pub fn contribution_timeout(mut self, contribution_timeout: Timeslice) -> Self {
+		self.0.contribution_timeout = contribution_timeout;
+		self
+	}
+
+	pub fn endow(self, who: u64, amount: u64) -> Self {
+		assert_ok!(<<Test as Config>::Currency as Mutate<_>>::mint_into(&who, amount));
+		self
+	}
+
+	pub fn execute_with<R>(self, f: impl Fn() -> R) -> R {
+		new_test_ext().execute_with(|| {
+			assert_ok!(Broker::do_configure(self.0));
+			f()
+		})
+	}
+}
+
+pub fn new_test_ext() -> sp_io::TestExternalities {
+	let c = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
+	sp_io::TestExternalities::from(c)
+}
diff --git a/substrate/frame/broker/src/nonfungible_impl.rs b/substrate/frame/broker/src/nonfungible_impl.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fe95438cb1afa0eaae8a409352ebc2855739435c
--- /dev/null
+++ b/substrate/frame/broker/src/nonfungible_impl.rs
@@ -0,0 +1,52 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::*;
+use frame_support::{
+	pallet_prelude::{DispatchResult, *},
+	traits::nonfungible::{Inspect, Transfer},
+};
+use sp_std::vec::Vec;
+
+impl<T: Config> Inspect<T::AccountId> for Pallet<T> {
+	type ItemId = u128;
+
+	fn owner(index: &Self::ItemId) -> Option<T::AccountId> {
+		Regions::<T>::get(RegionId::from(*index)).map(|r| r.owner)
+	}
+
+	fn attribute(index: &Self::ItemId, key: &[u8]) -> Option<Vec<u8>> {
+		let id = RegionId::from(*index);
+		let item = Regions::<T>::get(id)?;
+		match key {
+			b"begin" => Some(id.begin.encode()),
+			b"end" => Some(item.end.encode()),
+			b"length" => Some(item.end.saturating_sub(id.begin).encode()),
+			b"core" => Some(id.core.encode()),
+			b"part" => Some(id.mask.encode()),
+			b"owner" => Some(item.owner.encode()),
+			b"paid" => Some(item.paid.encode()),
+			_ => None,
+		}
+	}
+}
+
+impl<T: Config> Transfer<T::AccountId> for Pallet<T> {
+	fn transfer(index: &Self::ItemId, dest: &T::AccountId) -> DispatchResult {
+		Self::do_transfer((*index).into(), None, dest.clone()).map_err(Into::into)
+	}
+}
diff --git a/substrate/frame/broker/src/test_fungibles.rs b/substrate/frame/broker/src/test_fungibles.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f6ac5a49dedd28060dae9c4802fcb2842054051e
--- /dev/null
+++ b/substrate/frame/broker/src/test_fungibles.rs
@@ -0,0 +1,283 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use codec::{Decode, Encode};
+use frame_support::{
+	parameter_types,
+	traits::{
+		fungibles::{self, Dust},
+		tokens::{
+			self, DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence,
+		},
+	},
+};
+use scale_info::TypeInfo;
+use sp_arithmetic::traits::Zero;
+use sp_core::{Get, TypedGet};
+use sp_runtime::{DispatchError, DispatchResult};
+use sp_std::collections::btree_map::BTreeMap;
+
+parameter_types! {
+	static TestAssetOf: BTreeMap<(u32, Vec<u8>), Vec<u8>> = Default::default();
+	static TestBalanceOf: BTreeMap<(u32, Vec<u8>, Vec<u8>), Vec<u8>> = Default::default();
+	static TestHoldOf: BTreeMap<(u32, Vec<u8>, Vec<u8>, Vec<u8>), Vec<u8>> = Default::default();
+}
+
+pub struct TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>(
+	core::marker::PhantomData<(Instance, AccountId, AssetId, MinimumBalance, HoldReason)>,
+);
+impl<
+		Instance: Get<u32>,
+		AccountId: Encode,
+		AssetId: tokens::AssetId + Copy,
+		MinimumBalance: TypedGet,
+		HoldReason,
+	> fungibles::Inspect<AccountId>
+	for TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>
+where
+	MinimumBalance::Type: tokens::Balance,
+{
+	type AssetId = AssetId;
+	type Balance = MinimumBalance::Type;
+
+	fn total_issuance(asset: Self::AssetId) -> Self::Balance {
+		TestAssetOf::get()
+			.get(&(Instance::get(), asset.encode()))
+			.and_then(|data| Decode::decode(&mut &data[..]).ok())
+			.unwrap_or_default()
+	}
+
+	fn active_issuance(asset: Self::AssetId) -> Self::Balance {
+		Self::total_issuance(asset)
+	}
+
+	/// The minimum balance any single account may have.
+	fn minimum_balance(_asset: Self::AssetId) -> Self::Balance {
+		MinimumBalance::get()
+	}
+
+	fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance {
+		TestBalanceOf::get()
+			.get(&(Instance::get(), asset.encode(), who.encode()))
+			.and_then(|data| Decode::decode(&mut &data[..]).ok())
+			.unwrap_or_default()
+	}
+
+	fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance {
+		Self::total_balance(asset, who)
+	}
+
+	fn reducible_balance(
+		asset: Self::AssetId,
+		who: &AccountId,
+		_preservation: Preservation,
+		_force: Fortitude,
+	) -> Self::Balance {
+		Self::total_balance(asset, who)
+	}
+
+	fn can_deposit(
+		asset: Self::AssetId,
+		who: &AccountId,
+		amount: Self::Balance,
+		_provenance: Provenance,
+	) -> DepositConsequence {
+		if !Self::asset_exists(asset) {
+			return DepositConsequence::UnknownAsset
+		}
+		if amount + Self::balance(asset, who) < Self::minimum_balance(asset) {
+			return DepositConsequence::BelowMinimum
+		}
+		DepositConsequence::Success
+	}
+
+	fn can_withdraw(
+		asset: Self::AssetId,
+		who: &AccountId,
+		amount: Self::Balance,
+	) -> WithdrawConsequence<Self::Balance> {
+		if Self::reducible_balance(asset, who, Preservation::Expendable, Fortitude::Polite) < amount
+		{
+			return WithdrawConsequence::BalanceLow
+		}
+		if Self::total_balance(asset, who) < Self::minimum_balance(asset) + amount {
+			return WithdrawConsequence::WouldDie
+		}
+		WithdrawConsequence::Success
+	}
+
+	fn asset_exists(asset: Self::AssetId) -> bool {
+		TestAssetOf::get().contains_key(&(Instance::get(), asset.encode()))
+	}
+}
+
+impl<
+		Instance: Get<u32>,
+		AccountId: Encode,
+		AssetId: tokens::AssetId + Copy,
+		MinimumBalance: TypedGet,
+		HoldReason,
+	> fungibles::Unbalanced<AccountId>
+	for TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>
+where
+	MinimumBalance::Type: tokens::Balance,
+{
+	fn handle_dust(_dust: Dust<AccountId, Self>) {}
+
+	fn write_balance(
+		asset: Self::AssetId,
+		who: &AccountId,
+		amount: Self::Balance,
+	) -> Result<Option<Self::Balance>, DispatchError> {
+		let mut tb = TestBalanceOf::get();
+		let maybe_dust = if amount < MinimumBalance::get() {
+			tb.remove(&(Instance::get(), asset.encode(), who.encode()));
+			if amount.is_zero() {
+				None
+			} else {
+				Some(amount)
+			}
+		} else {
+			tb.insert((Instance::get(), asset.encode(), who.encode()), amount.encode());
+			None
+		};
+		TestBalanceOf::set(tb);
+		Ok(maybe_dust)
+	}
+
+	fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) {
+		let mut ta = TestAssetOf::get();
+		ta.insert((Instance::get(), asset.encode()), amount.encode());
+		TestAssetOf::set(ta);
+	}
+}
+
+impl<
+		Instance: Get<u32>,
+		AccountId: Encode,
+		AssetId: tokens::AssetId + Copy,
+		MinimumBalance: TypedGet,
+		HoldReason,
+	> fungibles::Mutate<AccountId>
+	for TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>
+where
+	MinimumBalance::Type: tokens::Balance,
+{
+}
+
+impl<
+		Instance: Get<u32>,
+		AccountId: Encode,
+		AssetId: tokens::AssetId + Copy,
+		MinimumBalance: TypedGet,
+		HoldReason,
+	> fungibles::Balanced<AccountId>
+	for TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>
+where
+	MinimumBalance::Type: tokens::Balance,
+{
+	type OnDropCredit = fungibles::DecreaseIssuance<AccountId, Self>;
+	type OnDropDebt = fungibles::IncreaseIssuance<AccountId, Self>;
+}
+
+impl<
+		Instance: Get<u32>,
+		AccountId: Encode,
+		AssetId: tokens::AssetId + Copy,
+		MinimumBalance: TypedGet,
+		HoldReason: Encode + Decode + TypeInfo + 'static,
+	> fungibles::InspectHold<AccountId>
+	for TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>
+where
+	MinimumBalance::Type: tokens::Balance,
+{
+	type Reason = HoldReason;
+
+	fn total_balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance {
+		let asset = asset.encode();
+		let who = who.encode();
+		TestHoldOf::get()
+			.iter()
+			.filter(|(k, _)| k.0 == Instance::get() && k.1 == asset && k.2 == who)
+			.filter_map(|(_, b)| Self::Balance::decode(&mut &b[..]).ok())
+			.fold(Zero::zero(), |a, i| a + i)
+	}
+
+	fn balance_on_hold(
+		asset: Self::AssetId,
+		reason: &Self::Reason,
+		who: &AccountId,
+	) -> Self::Balance {
+		TestHoldOf::get()
+			.get(&(Instance::get(), asset.encode(), who.encode(), reason.encode()))
+			.and_then(|data| Decode::decode(&mut &data[..]).ok())
+			.unwrap_or_default()
+	}
+}
+
+impl<
+		Instance: Get<u32>,
+		AccountId: Encode,
+		AssetId: tokens::AssetId + Copy,
+		MinimumBalance: TypedGet,
+		HoldReason: Encode + Decode + TypeInfo + 'static,
+	> fungibles::UnbalancedHold<AccountId>
+	for TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>
+where
+	MinimumBalance::Type: tokens::Balance,
+{
+	fn set_balance_on_hold(
+		asset: Self::AssetId,
+		reason: &Self::Reason,
+		who: &AccountId,
+		amount: Self::Balance,
+	) -> DispatchResult {
+		let mut th = TestHoldOf::get();
+		th.insert(
+			(Instance::get(), asset.encode(), who.encode(), reason.encode()),
+			amount.encode(),
+		);
+		TestHoldOf::set(th);
+		Ok(())
+	}
+}
+
+impl<
+		Instance: Get<u32>,
+		AccountId: Encode,
+		AssetId: tokens::AssetId + Copy,
+		MinimumBalance: TypedGet,
+		HoldReason: Encode + Decode + TypeInfo + 'static,
+	> fungibles::MutateHold<AccountId>
+	for TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>
+where
+	MinimumBalance::Type: tokens::Balance,
+{
+}
+
+impl<
+		Instance: Get<u32>,
+		AccountId: Encode,
+		AssetId: tokens::AssetId + Copy,
+		MinimumBalance: TypedGet,
+		HoldReason: Encode + Decode + TypeInfo + 'static,
+	> fungibles::BalancedHold<AccountId>
+	for TestFungibles<Instance, AccountId, AssetId, MinimumBalance, HoldReason>
+where
+	MinimumBalance::Type: tokens::Balance,
+{
+}
diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3c326010dddfc7a01396064af1e5f706ed085ce1
--- /dev/null
+++ b/substrate/frame/broker/src/tests.rs
@@ -0,0 +1,896 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#![cfg(test)]
+
+use crate::{core_mask::*, mock::*, *};
+use frame_support::{
+	assert_noop, assert_ok,
+	traits::nonfungible::{Inspect as NftInspect, Transfer},
+	BoundedVec,
+};
+use frame_system::RawOrigin::Root;
+use sp_runtime::traits::Get;
+use CoreAssignment::*;
+use CoretimeTraceItem::*;
+use Finality::*;
+
+#[test]
+fn basic_initialize_works() {
+	TestExt::new().execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		assert_eq!(CoretimeTrace::get(), vec![]);
+		assert_eq!(Broker::current_timeslice(), 0);
+	});
+}
+
+#[test]
+fn drop_region_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_ok!(Broker::do_assign(region, Some(1), 1001, Provisional));
+		advance_to(11);
+		assert_noop!(Broker::do_drop_region(region), Error::<Test>::StillValid);
+		advance_to(12);
+		// assignment worked.
+		let just_1001 = vec![(Task(1001), 57600)];
+		let just_pool = vec![(Pool, 57600)];
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![
+				(6, AssignCore { core: 0, begin: 8, assignment: just_1001, end_hint: None }),
+				(12, AssignCore { core: 0, begin: 14, assignment: just_pool, end_hint: None }),
+			]
+		);
+		// `region` still exists as it was never finalized.
+		assert_eq!(Regions::<Test>::iter().count(), 1);
+		assert_ok!(Broker::do_drop_region(region));
+		assert_eq!(Regions::<Test>::iter().count(), 0);
+		assert_noop!(Broker::do_drop_region(region), Error::<Test>::UnknownRegion);
+	});
+}
+
+#[test]
+fn drop_renewal_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_ok!(Broker::do_assign(region, Some(1), 1001, Final));
+		advance_to(11);
+		let e = Error::<Test>::StillValid;
+		assert_noop!(Broker::do_drop_renewal(region.core, region.begin + 3), e);
+		advance_to(12);
+		assert_ok!(Broker::do_drop_renewal(region.core, region.begin + 3));
+		let e = Error::<Test>::UnknownRenewal;
+		assert_noop!(Broker::do_drop_renewal(region.core, region.begin + 3), e);
+	});
+}
+
+#[test]
+fn drop_contribution_works() {
+	TestExt::new().contribution_timeout(3).endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		// Place region in pool. Active in pool timeslices 4, 5, 6 = rcblocks 8, 10, 12; we
+		// expect the contribution record to timeout 3 timeslices following 7 = 10
+		assert_ok!(Broker::do_pool(region, Some(1), 1, Final));
+		assert_eq!(InstaPoolContribution::<Test>::iter().count(), 1);
+		advance_to(19);
+		assert_noop!(Broker::do_drop_contribution(region), Error::<Test>::StillValid);
+		advance_to(20);
+		assert_ok!(Broker::do_drop_contribution(region));
+		assert_eq!(InstaPoolContribution::<Test>::iter().count(), 0);
+		assert_noop!(Broker::do_drop_contribution(region), Error::<Test>::UnknownContribution);
+	});
+}
+
+#[test]
+fn drop_history_works() {
+	TestExt::new()
+		.contribution_timeout(4)
+		.endow(1, 1000)
+		.endow(2, 30)
+		.execute_with(|| {
+			assert_ok!(Broker::do_start_sales(100, 1));
+			advance_to(2);
+			let mut region = Broker::do_purchase(1, u64::max_value()).unwrap();
+			// Place region in pool. Active in pool timeslices 4, 5, 6 = rcblocks 8, 10, 12; we
+			// expect to make/receive revenue reports on blocks 10, 12, 14.
+			assert_ok!(Broker::do_pool(region, Some(1), 1, Final));
+			assert_ok!(Broker::do_purchase_credit(2, 30, 2));
+			advance_to(6);
+			// In the stable state with no pending payouts, we expect to see 3 items in
+			// InstaPoolHistory here since there is a latency of 1 timeslice (for generating the
+			// revenue report), the forward notice period (equivalent to another timeslice) and a
+			// block between the revenue report being requested and the response being processed.
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 3);
+			advance_to(7);
+			// One block later, the most recent report will have been processed, so the effective
+			// queue drops to 2 items.
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 2);
+			advance_to(8);
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 3);
+			assert_ok!(TestCoretimeProvider::spend_instantaneous(2, 10));
+			advance_to(10);
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 3);
+			assert_ok!(TestCoretimeProvider::spend_instantaneous(2, 10));
+			advance_to(12);
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 4);
+			assert_ok!(TestCoretimeProvider::spend_instantaneous(2, 10));
+			advance_to(14);
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 5);
+			advance_to(16);
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 6);
+			advance_to(17);
+			assert_noop!(Broker::do_drop_history(region.begin), Error::<Test>::StillValid);
+			advance_to(18);
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 6);
+			// Block 18 is 8 blocks ()= 4 timeslices = contribution timeout) after first region.
+			// Its revenue should now be droppable.
+			assert_ok!(Broker::do_drop_history(region.begin));
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 5);
+			assert_noop!(Broker::do_drop_history(region.begin), Error::<Test>::NoHistory);
+			advance_to(19);
+			region.begin += 1;
+			assert_noop!(Broker::do_drop_history(region.begin), Error::<Test>::StillValid);
+			advance_to(20);
+			assert_ok!(Broker::do_drop_history(region.begin));
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 4);
+			assert_noop!(Broker::do_drop_history(region.begin), Error::<Test>::NoHistory);
+			advance_to(21);
+			region.begin += 1;
+			assert_noop!(Broker::do_drop_history(region.begin), Error::<Test>::StillValid);
+			advance_to(22);
+			assert_ok!(Broker::do_drop_history(region.begin));
+			assert_eq!(InstaPoolHistory::<Test>::iter().count(), 3);
+			assert_noop!(Broker::do_drop_history(region.begin), Error::<Test>::NoHistory);
+		});
+}
+
+#[test]
+fn request_core_count_works() {
+	TestExt::new().execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 0));
+		assert_ok!(Broker::request_core_count(RuntimeOrigin::root(), 1));
+		advance_to(12);
+		let assignment = vec![(Pool, 57600)];
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![(12, AssignCore { core: 0, begin: 14, assignment, end_hint: None })],
+		);
+	});
+}
+
+#[test]
+fn transfer_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_ok!(<Broker as Transfer<_>>::transfer(&region.into(), &2));
+		assert_eq!(<Broker as NftInspect<_>>::owner(&region.into()), Some(2));
+		assert_noop!(Broker::do_assign(region, Some(1), 1001, Final), Error::<Test>::NotOwner);
+		assert_ok!(Broker::do_assign(region, Some(2), 1002, Final));
+	});
+}
+
+#[test]
+fn permanent_is_not_reassignable() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_ok!(Broker::do_assign(region, Some(1), 1001, Final));
+		assert_noop!(Broker::do_assign(region, Some(1), 1002, Final), Error::<Test>::UnknownRegion);
+		assert_noop!(Broker::do_pool(region, Some(1), 1002, Final), Error::<Test>::UnknownRegion);
+		assert_noop!(Broker::do_partition(region, Some(1), 1), Error::<Test>::UnknownRegion);
+		assert_noop!(
+			Broker::do_interlace(region, Some(1), CoreMask::from_chunk(0, 40)),
+			Error::<Test>::UnknownRegion
+		);
+	});
+}
+
+#[test]
+fn provisional_is_reassignable() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_ok!(Broker::do_assign(region, Some(1), 1001, Provisional));
+		let (region1, region) = Broker::do_partition(region, Some(1), 1).unwrap();
+		let (region2, region3) =
+			Broker::do_interlace(region, Some(1), CoreMask::from_chunk(0, 40)).unwrap();
+		assert_ok!(Broker::do_pool(region1, Some(1), 1, Provisional));
+		assert_ok!(Broker::do_assign(region2, Some(1), 1002, Provisional));
+		assert_ok!(Broker::do_assign(region3, Some(1), 1003, Provisional));
+		advance_to(8);
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![
+				(
+					6,
+					AssignCore {
+						core: 0,
+						begin: 8,
+						assignment: vec![(Pool, 57600),],
+						end_hint: None
+					}
+				),
+				(
+					8,
+					AssignCore {
+						core: 0,
+						begin: 10,
+						assignment: vec![(Task(1002), 28800), (Task(1003), 28800),],
+						end_hint: None
+					}
+				),
+			]
+		);
+	});
+}
+
+#[test]
+fn nft_metadata_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_eq!(attribute::<Timeslice>(region, b"begin"), 4);
+		assert_eq!(attribute::<Timeslice>(region, b"length"), 3);
+		assert_eq!(attribute::<Timeslice>(region, b"end"), 7);
+		assert_eq!(attribute::<u64>(region, b"owner"), 1);
+		assert_eq!(attribute::<CoreMask>(region, b"part"), 0xfffff_fffff_fffff_fffff.into());
+		assert_eq!(attribute::<CoreIndex>(region, b"core"), 0);
+		assert_eq!(attribute::<Option<u64>>(region, b"paid"), Some(100));
+
+		assert_ok!(Broker::do_transfer(region, None, 42));
+		let (_, region) = Broker::do_partition(region, None, 2).unwrap();
+		let (region, _) =
+			Broker::do_interlace(region, None, 0x00000_fffff_fffff_00000.into()).unwrap();
+		assert_eq!(attribute::<Timeslice>(region, b"begin"), 6);
+		assert_eq!(attribute::<Timeslice>(region, b"length"), 1);
+		assert_eq!(attribute::<Timeslice>(region, b"end"), 7);
+		assert_eq!(attribute::<u64>(region, b"owner"), 42);
+		assert_eq!(attribute::<CoreMask>(region, b"part"), 0x00000_fffff_fffff_00000.into());
+		assert_eq!(attribute::<CoreIndex>(region, b"core"), 0);
+		assert_eq!(attribute::<Option<u64>>(region, b"paid"), None);
+	});
+}
+
+#[test]
+fn migration_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_set_lease(1000, 8));
+		assert_ok!(Broker::do_start_sales(100, 2));
+
+		// Sale is for regions from TS4..7
+		// Not ending in this sale period.
+		assert_noop!(Broker::do_renew(1, 0), Error::<Test>::NotAllowed);
+
+		advance_to(12);
+		// Sale is now for regions from TS10..13
+		// Ending in this sale period.
+		// Should now be renewable.
+		assert_ok!(Broker::do_renew(1, 0));
+		assert_eq!(balance(1), 900);
+		advance_to(18);
+
+		let just_pool = || vec![(Pool, 57600)];
+		let just_1000 = || vec![(Task(1000), 57600)];
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![
+				(6, AssignCore { core: 0, begin: 8, assignment: just_1000(), end_hint: None }),
+				(6, AssignCore { core: 1, begin: 8, assignment: just_pool(), end_hint: None }),
+				(12, AssignCore { core: 0, begin: 14, assignment: just_1000(), end_hint: None }),
+				(12, AssignCore { core: 1, begin: 14, assignment: just_pool(), end_hint: None }),
+				(18, AssignCore { core: 0, begin: 20, assignment: just_1000(), end_hint: None }),
+				(18, AssignCore { core: 1, begin: 20, assignment: just_pool(), end_hint: None }),
+			]
+		);
+	});
+}
+
+#[test]
+fn renewal_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_eq!(balance(1), 900);
+		assert_ok!(Broker::do_assign(region, None, 1001, Final));
+		// Should now be renewable.
+		advance_to(6);
+		assert_noop!(Broker::do_purchase(1, u64::max_value()), Error::<Test>::TooEarly);
+		let core = Broker::do_renew(1, region.core).unwrap();
+		assert_eq!(balance(1), 800);
+		advance_to(8);
+		assert_noop!(Broker::do_purchase(1, u64::max_value()), Error::<Test>::SoldOut);
+		advance_to(12);
+		assert_ok!(Broker::do_renew(1, core));
+		assert_eq!(balance(1), 690);
+	});
+}
+
+#[test]
+fn instapool_payouts_work() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		let item = ScheduleItem { assignment: Pool, mask: CoreMask::complete() };
+		assert_ok!(Broker::do_reserve(Schedule::truncate_from(vec![item])));
+		assert_ok!(Broker::do_start_sales(100, 3));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_ok!(Broker::do_pool(region, None, 2, Final));
+		assert_ok!(Broker::do_purchase_credit(1, 20, 1));
+		advance_to(8);
+		assert_ok!(TestCoretimeProvider::spend_instantaneous(1, 10));
+		advance_to(11);
+		assert_eq!(pot(), 14);
+		assert_eq!(revenue(), 106);
+		assert_ok!(Broker::do_claim_revenue(region, 100));
+		assert_eq!(pot(), 10);
+		assert_eq!(balance(2), 4);
+	});
+}
+
+#[test]
+fn instapool_partial_core_payouts_work() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		let item = ScheduleItem { assignment: Pool, mask: CoreMask::complete() };
+		assert_ok!(Broker::do_reserve(Schedule::truncate_from(vec![item])));
+		assert_ok!(Broker::do_start_sales(100, 2));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		let (region1, region2) =
+			Broker::do_interlace(region, None, CoreMask::from_chunk(0, 20)).unwrap();
+		assert_ok!(Broker::do_pool(region1, None, 2, Final));
+		assert_ok!(Broker::do_pool(region2, None, 3, Final));
+		assert_ok!(Broker::do_purchase_credit(1, 40, 1));
+		advance_to(8);
+		assert_ok!(TestCoretimeProvider::spend_instantaneous(1, 40));
+		advance_to(11);
+		assert_ok!(Broker::do_claim_revenue(region1, 100));
+		assert_ok!(Broker::do_claim_revenue(region2, 100));
+		assert_eq!(revenue(), 120);
+		assert_eq!(balance(2), 5);
+		assert_eq!(balance(3), 15);
+		assert_eq!(pot(), 0);
+	});
+}
+
+#[test]
+fn initialize_with_system_paras_works() {
+	TestExt::new().execute_with(|| {
+		let item = ScheduleItem { assignment: Task(1u32), mask: CoreMask::complete() };
+		assert_ok!(Broker::do_reserve(Schedule::truncate_from(vec![item])));
+		let items = vec![
+			ScheduleItem { assignment: Task(2u32), mask: 0xfffff_fffff_00000_00000.into() },
+			ScheduleItem { assignment: Task(3u32), mask: 0x00000_00000_fffff_00000.into() },
+			ScheduleItem { assignment: Task(4u32), mask: 0x00000_00000_00000_fffff.into() },
+		];
+		assert_ok!(Broker::do_reserve(Schedule::truncate_from(items)));
+		assert_ok!(Broker::do_start_sales(100, 2));
+		advance_to(10);
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![
+				(
+					6,
+					AssignCore {
+						core: 0,
+						begin: 8,
+						assignment: vec![(Task(1), 57600),],
+						end_hint: None
+					}
+				),
+				(
+					6,
+					AssignCore {
+						core: 1,
+						begin: 8,
+						assignment: vec![(Task(2), 28800), (Task(3), 14400), (Task(4), 14400),],
+						end_hint: None
+					}
+				),
+			]
+		);
+	});
+}
+
+#[test]
+fn initialize_with_leased_slots_works() {
+	TestExt::new().execute_with(|| {
+		assert_ok!(Broker::do_set_lease(1000, 6));
+		assert_ok!(Broker::do_set_lease(1001, 7));
+		assert_ok!(Broker::do_start_sales(100, 2));
+		advance_to(18);
+		let end_hint = None;
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![
+				(
+					6,
+					AssignCore {
+						core: 0,
+						begin: 8,
+						assignment: vec![(Task(1000), 57600),],
+						end_hint
+					}
+				),
+				(
+					6,
+					AssignCore {
+						core: 1,
+						begin: 8,
+						assignment: vec![(Task(1001), 57600),],
+						end_hint
+					}
+				),
+				(
+					12,
+					AssignCore {
+						core: 0,
+						begin: 14,
+						assignment: vec![(Task(1001), 57600),],
+						end_hint
+					}
+				),
+				(12, AssignCore { core: 1, begin: 14, assignment: vec![(Pool, 57600),], end_hint }),
+				(18, AssignCore { core: 0, begin: 20, assignment: vec![(Pool, 57600),], end_hint }),
+				(18, AssignCore { core: 1, begin: 20, assignment: vec![(Pool, 57600),], end_hint }),
+			]
+		);
+	});
+}
+
+#[test]
+fn purchase_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_ok!(Broker::do_assign(region, None, 1000, Final));
+		advance_to(6);
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![(
+				6,
+				AssignCore {
+					core: 0,
+					begin: 8,
+					assignment: vec![(Task(1000), 57600),],
+					end_hint: None
+				}
+			),]
+		);
+	});
+}
+
+#[test]
+fn partition_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		let (region1, region) = Broker::do_partition(region, None, 1).unwrap();
+		let (region2, region3) = Broker::do_partition(region, None, 1).unwrap();
+		assert_ok!(Broker::do_assign(region1, None, 1001, Final));
+		assert_ok!(Broker::do_assign(region2, None, 1002, Final));
+		assert_ok!(Broker::do_assign(region3, None, 1003, Final));
+		advance_to(10);
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![
+				(
+					6,
+					AssignCore {
+						core: 0,
+						begin: 8,
+						assignment: vec![(Task(1001), 57600),],
+						end_hint: None
+					}
+				),
+				(
+					8,
+					AssignCore {
+						core: 0,
+						begin: 10,
+						assignment: vec![(Task(1002), 57600),],
+						end_hint: None
+					}
+				),
+				(
+					10,
+					AssignCore {
+						core: 0,
+						begin: 12,
+						assignment: vec![(Task(1003), 57600),],
+						end_hint: None
+					}
+				),
+			]
+		);
+	});
+}
+
+#[test]
+fn interlace_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		let (region1, region) =
+			Broker::do_interlace(region, None, CoreMask::from_chunk(0, 30)).unwrap();
+		let (region2, region3) =
+			Broker::do_interlace(region, None, CoreMask::from_chunk(30, 60)).unwrap();
+		assert_ok!(Broker::do_assign(region1, None, 1001, Final));
+		assert_ok!(Broker::do_assign(region2, None, 1002, Final));
+		assert_ok!(Broker::do_assign(region3, None, 1003, Final));
+		advance_to(10);
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![(
+				6,
+				AssignCore {
+					core: 0,
+					begin: 8,
+					assignment: vec![(Task(1001), 21600), (Task(1002), 21600), (Task(1003), 14400),],
+					end_hint: None
+				}
+			),]
+		);
+	});
+}
+
+#[test]
+fn interlace_then_partition_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		let (region1, region2) =
+			Broker::do_interlace(region, None, CoreMask::from_chunk(0, 20)).unwrap();
+		let (region1, region3) = Broker::do_partition(region1, None, 1).unwrap();
+		let (region2, region4) = Broker::do_partition(region2, None, 2).unwrap();
+		assert_ok!(Broker::do_assign(region1, None, 1001, Final));
+		assert_ok!(Broker::do_assign(region2, None, 1002, Final));
+		assert_ok!(Broker::do_assign(region3, None, 1003, Final));
+		assert_ok!(Broker::do_assign(region4, None, 1004, Final));
+		advance_to(10);
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![
+				(
+					6,
+					AssignCore {
+						core: 0,
+						begin: 8,
+						assignment: vec![(Task(1001), 14400), (Task(1002), 43200),],
+						end_hint: None
+					}
+				),
+				(
+					8,
+					AssignCore {
+						core: 0,
+						begin: 10,
+						assignment: vec![(Task(1002), 43200), (Task(1003), 14400),],
+						end_hint: None
+					}
+				),
+				(
+					10,
+					AssignCore {
+						core: 0,
+						begin: 12,
+						assignment: vec![(Task(1003), 14400), (Task(1004), 43200),],
+						end_hint: None
+					}
+				),
+			]
+		);
+	});
+}
+
+#[test]
+fn partition_then_interlace_works() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		let (region1, region2) = Broker::do_partition(region, None, 1).unwrap();
+		let (region1, region3) =
+			Broker::do_interlace(region1, None, CoreMask::from_chunk(0, 20)).unwrap();
+		let (region2, region4) =
+			Broker::do_interlace(region2, None, CoreMask::from_chunk(0, 30)).unwrap();
+		assert_ok!(Broker::do_assign(region1, None, 1001, Final));
+		assert_ok!(Broker::do_assign(region2, None, 1002, Final));
+		assert_ok!(Broker::do_assign(region3, None, 1003, Final));
+		assert_ok!(Broker::do_assign(region4, None, 1004, Final));
+		advance_to(10);
+		assert_eq!(
+			CoretimeTrace::get(),
+			vec![
+				(
+					6,
+					AssignCore {
+						core: 0,
+						begin: 8,
+						assignment: vec![(Task(1001), 14400), (Task(1003), 43200),],
+						end_hint: None
+					}
+				),
+				(
+					8,
+					AssignCore {
+						core: 0,
+						begin: 10,
+						assignment: vec![(Task(1002), 21600), (Task(1004), 36000),],
+						end_hint: None
+					}
+				),
+			]
+		);
+	});
+}
+
+#[test]
+fn reservations_are_limited() {
+	TestExt::new().execute_with(|| {
+		let schedule = Schedule::truncate_from(vec![ScheduleItem {
+			assignment: Pool,
+			mask: CoreMask::complete(),
+		}]);
+		let max_cores: u32 = <Test as Config>::MaxReservedCores::get();
+		Reservations::<Test>::put(
+			BoundedVec::try_from(vec![schedule.clone(); max_cores as usize]).unwrap(),
+		);
+		assert_noop!(Broker::do_reserve(schedule), Error::<Test>::TooManyReservations);
+	});
+}
+
+#[test]
+fn cannot_unreserve_unknown() {
+	TestExt::new().execute_with(|| {
+		let schedule = Schedule::truncate_from(vec![ScheduleItem {
+			assignment: Pool,
+			mask: CoreMask::complete(),
+		}]);
+		Reservations::<Test>::put(BoundedVec::try_from(vec![schedule.clone(); 1usize]).unwrap());
+		assert_noop!(Broker::do_unreserve(2), Error::<Test>::UnknownReservation);
+	});
+}
+
+#[test]
+fn cannot_set_expired_lease() {
+	TestExt::new().execute_with(|| {
+		advance_to(2);
+		let current_timeslice = Broker::current_timeslice();
+		assert_noop!(
+			Broker::do_set_lease(1000, current_timeslice.saturating_sub(1)),
+			Error::<Test>::AlreadyExpired
+		);
+	});
+}
+
+#[test]
+fn leases_are_limited() {
+	TestExt::new().execute_with(|| {
+		let max_leases: u32 = <Test as Config>::MaxLeasedCores::get();
+		Leases::<Test>::put(
+			BoundedVec::try_from(vec![
+				LeaseRecordItem { task: 1u32, until: 10u32 };
+				max_leases as usize
+			])
+			.unwrap(),
+		);
+		assert_noop!(Broker::do_set_lease(1000, 10), Error::<Test>::TooManyLeases);
+	});
+}
+
+#[test]
+fn purchase_requires_valid_status_and_sale_info() {
+	TestExt::new().execute_with(|| {
+		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::Uninitialized);
+
+		let status = StatusRecord {
+			core_count: 2,
+			private_pool_size: 0,
+			system_pool_size: 0,
+			last_committed_timeslice: 0,
+			last_timeslice: 1,
+		};
+		Status::<Test>::put(&status);
+		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::NoSales);
+
+		let mut dummy_sale = SaleInfoRecord {
+			sale_start: 0,
+			leadin_length: 0,
+			price: 200,
+			sellout_price: None,
+			region_begin: 0,
+			region_end: 3,
+			first_core: 3,
+			ideal_cores_sold: 0,
+			cores_offered: 1,
+			cores_sold: 2,
+		};
+		SaleInfo::<Test>::put(&dummy_sale);
+		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::Unavailable);
+
+		dummy_sale.first_core = 1;
+		SaleInfo::<Test>::put(&dummy_sale);
+		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::SoldOut);
+
+		assert_ok!(Broker::do_start_sales(200, 1));
+		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::TooEarly);
+
+		advance_to(2);
+		assert_noop!(Broker::do_purchase(1, 100), Error::<Test>::Overpriced);
+	});
+}
+
+#[test]
+fn renewal_requires_valid_status_and_sale_info() {
+	TestExt::new().execute_with(|| {
+		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::Uninitialized);
+
+		let status = StatusRecord {
+			core_count: 2,
+			private_pool_size: 0,
+			system_pool_size: 0,
+			last_committed_timeslice: 0,
+			last_timeslice: 1,
+		};
+		Status::<Test>::put(&status);
+		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::NoSales);
+
+		let mut dummy_sale = SaleInfoRecord {
+			sale_start: 0,
+			leadin_length: 0,
+			price: 200,
+			sellout_price: None,
+			region_begin: 0,
+			region_end: 3,
+			first_core: 3,
+			ideal_cores_sold: 0,
+			cores_offered: 1,
+			cores_sold: 2,
+		};
+		SaleInfo::<Test>::put(&dummy_sale);
+		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::Unavailable);
+
+		dummy_sale.first_core = 1;
+		SaleInfo::<Test>::put(&dummy_sale);
+		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::SoldOut);
+
+		assert_ok!(Broker::do_start_sales(200, 1));
+		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::NotAllowed);
+
+		let record = AllowedRenewalRecord {
+			price: 100,
+			completion: CompletionStatus::Partial(CoreMask::from_chunk(0, 20)),
+		};
+		AllowedRenewals::<Test>::insert(AllowedRenewalId { core: 1, when: 4 }, &record);
+		assert_noop!(Broker::do_renew(1, 1), Error::<Test>::IncompleteAssignment);
+	});
+}
+
+#[test]
+fn cannot_transfer_or_partition_or_interlace_unknown() {
+	TestExt::new().execute_with(|| {
+		let region_id = RegionId { begin: 0, core: 0, mask: CoreMask::complete() };
+		assert_noop!(Broker::do_transfer(region_id, None, 2), Error::<Test>::UnknownRegion);
+		assert_noop!(Broker::do_partition(region_id, None, 2), Error::<Test>::UnknownRegion);
+		assert_noop!(
+			Broker::do_interlace(region_id, None, CoreMask::from_chunk(0, 20)),
+			Error::<Test>::UnknownRegion
+		);
+	});
+}
+
+#[test]
+fn check_ownership_for_transfer_or_partition_or_interlace() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_noop!(Broker::do_transfer(region, Some(2), 2), Error::<Test>::NotOwner);
+		assert_noop!(Broker::do_partition(region, Some(2), 2), Error::<Test>::NotOwner);
+		assert_noop!(
+			Broker::do_interlace(region, Some(2), CoreMask::from_chunk(0, 20)),
+			Error::<Test>::NotOwner
+		);
+	});
+}
+
+#[test]
+fn cannot_partition_invalid_offset() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		assert_noop!(Broker::do_partition(region, None, 0), Error::<Test>::PivotTooEarly);
+		assert_noop!(Broker::do_partition(region, None, 5), Error::<Test>::PivotTooLate);
+	});
+}
+
+#[test]
+fn cannot_interlace_invalid_pivot() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		let (region1, _) = Broker::do_interlace(region, None, CoreMask::from_chunk(0, 20)).unwrap();
+		assert_noop!(
+			Broker::do_interlace(region1, None, CoreMask::from_chunk(20, 40)),
+			Error::<Test>::ExteriorPivot
+		);
+		assert_noop!(
+			Broker::do_interlace(region1, None, CoreMask::void()),
+			Error::<Test>::VoidPivot
+		);
+		assert_noop!(
+			Broker::do_interlace(region1, None, CoreMask::from_chunk(0, 20)),
+			Error::<Test>::CompletePivot
+		);
+	});
+}
+
+#[test]
+fn assign_should_drop_invalid_region() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let mut region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		advance_to(10);
+		assert_ok!(Broker::do_assign(region, Some(1), 1001, Provisional));
+		region.begin = 7;
+		System::assert_last_event(Event::RegionDropped { region_id: region, duration: 0 }.into());
+	});
+}
+
+#[test]
+fn pool_should_drop_invalid_region() {
+	TestExt::new().endow(1, 1000).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+		let mut region = Broker::do_purchase(1, u64::max_value()).unwrap();
+		advance_to(10);
+		assert_ok!(Broker::do_pool(region, Some(1), 1001, Provisional));
+		region.begin = 7;
+		System::assert_last_event(Event::RegionDropped { region_id: region, duration: 0 }.into());
+	});
+}
+
+#[test]
+fn config_works() {
+	TestExt::new().execute_with(|| {
+		let mut cfg = new_config();
+		// Good config works:
+		assert_ok!(Broker::configure(Root.into(), cfg.clone()));
+		// Bad config is a noop:
+		cfg.leadin_length = 0;
+		assert_noop!(Broker::configure(Root.into(), cfg), Error::<Test>::InvalidConfig);
+	});
+}
diff --git a/substrate/frame/broker/src/tick_impls.rs b/substrate/frame/broker/src/tick_impls.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0677d2793e21acd17e3591738ec97c9df6687f7e
--- /dev/null
+++ b/substrate/frame/broker/src/tick_impls.rs
@@ -0,0 +1,326 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::*;
+use frame_support::{pallet_prelude::*, weights::WeightMeter};
+use sp_arithmetic::{
+	traits::{One, SaturatedConversion, Saturating, Zero},
+	FixedPointNumber,
+};
+use sp_runtime::traits::ConvertBack;
+use sp_std::{vec, vec::Vec};
+use CompletionStatus::Complete;
+
+impl<T: Config> Pallet<T> {
+	/// Attempt to tick things along.
+	///
+	/// This may do several things:
+	/// - Processes notifications of the core count changing
+	/// - Processes reports of Instantaneous Core Market Revenue
+	/// - Commit a timeslice
+	/// - Rotate the sale period
+	/// - Request revenue information for a previous timeslice
+	/// - Initialize an instantaneous core pool historical revenue record
+	pub(crate) fn do_tick() -> Weight {
+		let (mut status, config) = match (Status::<T>::get(), Configuration::<T>::get()) {
+			(Some(s), Some(c)) => (s, c),
+			_ => return Weight::zero(),
+		};
+
+		let mut meter = WeightMeter::max_limit();
+
+		if Self::process_core_count(&mut status) {
+			meter.consume(T::WeightInfo::process_core_count(status.core_count.into()));
+		}
+
+		if Self::process_revenue() {
+			meter.consume(T::WeightInfo::process_revenue());
+		}
+
+		if let Some(commit_timeslice) = Self::next_timeslice_to_commit(&config, &status) {
+			status.last_committed_timeslice = commit_timeslice;
+			if let Some(sale) = SaleInfo::<T>::get() {
+				if commit_timeslice >= sale.region_begin {
+					// Sale can be rotated.
+					Self::rotate_sale(sale, &config, &status);
+					meter.consume(T::WeightInfo::rotate_sale(status.core_count.into()));
+				}
+			}
+
+			Self::process_pool(commit_timeslice, &mut status);
+			meter.consume(T::WeightInfo::process_pool());
+
+			let timeslice_period = T::TimeslicePeriod::get();
+			let rc_begin = RelayBlockNumberOf::<T>::from(commit_timeslice) * timeslice_period;
+			for core in 0..status.core_count {
+				Self::process_core_schedule(commit_timeslice, rc_begin, core);
+				meter.consume(T::WeightInfo::process_core_schedule());
+			}
+		}
+
+		let current_timeslice = Self::current_timeslice();
+		if status.last_timeslice < current_timeslice {
+			status.last_timeslice.saturating_inc();
+			let rc_block = T::TimeslicePeriod::get() * status.last_timeslice.into();
+			T::Coretime::request_revenue_info_at(rc_block);
+			meter.consume(T::WeightInfo::request_revenue_info_at());
+		}
+
+		Status::<T>::put(&status);
+
+		meter.consumed()
+	}
+
+	pub(crate) fn process_core_count(status: &mut StatusRecord) -> bool {
+		if let Some(core_count) = T::Coretime::check_notify_core_count() {
+			status.core_count = core_count;
+			Self::deposit_event(Event::<T>::CoreCountChanged { core_count });
+			return true
+		}
+		false
+	}
+
+	pub(crate) fn process_revenue() -> bool {
+		let Some((until, amount)) = T::Coretime::check_notify_revenue_info() else {
+			return false;
+		};
+		let when: Timeslice =
+			(until / T::TimeslicePeriod::get()).saturating_sub(One::one()).saturated_into();
+		let mut revenue = T::ConvertBalance::convert_back(amount);
+		if revenue.is_zero() {
+			Self::deposit_event(Event::<T>::HistoryDropped { when, revenue });
+			InstaPoolHistory::<T>::remove(when);
+			return true
+		}
+		let mut r = InstaPoolHistory::<T>::get(when).unwrap_or_default();
+		if r.maybe_payout.is_some() {
+			Self::deposit_event(Event::<T>::HistoryIgnored { when, revenue });
+			return true
+		}
+		// Payout system InstaPool Cores.
+		let total_contrib = r.system_contributions.saturating_add(r.private_contributions);
+		let system_payout =
+			revenue.saturating_mul(r.system_contributions.into()) / total_contrib.into();
+		let _ = Self::charge(&Self::account_id(), system_payout);
+		revenue.saturating_reduce(system_payout);
+
+		if !revenue.is_zero() && r.private_contributions > 0 {
+			r.maybe_payout = Some(revenue);
+			InstaPoolHistory::<T>::insert(when, &r);
+			Self::deposit_event(Event::<T>::ClaimsReady {
+				when,
+				system_payout,
+				private_payout: revenue,
+			});
+		} else {
+			InstaPoolHistory::<T>::remove(when);
+			Self::deposit_event(Event::<T>::HistoryDropped { when, revenue });
+		}
+		true
+	}
+
+	/// Begin selling for the next sale period.
+	///
+	/// Triggered by Relay-chain block number/timeslice.
+	pub(crate) fn rotate_sale(
+		old_sale: SaleInfoRecordOf<T>,
+		config: &ConfigRecordOf<T>,
+		status: &StatusRecord,
+	) -> Option<()> {
+		let now = frame_system::Pallet::<T>::block_number();
+
+		let pool_item =
+			ScheduleItem { assignment: CoreAssignment::Pool, mask: CoreMask::complete() };
+		let just_pool = Schedule::truncate_from(vec![pool_item]);
+
+		// Clean up the old sale - we need to use up any unused cores by putting them into the
+		// InstaPool.
+		let mut old_pooled: SignedCoreMaskBitCount = 0;
+		for i in old_sale.cores_sold..old_sale.cores_offered {
+			old_pooled.saturating_accrue(80);
+			Workplan::<T>::insert((old_sale.region_begin, old_sale.first_core + i), &just_pool);
+		}
+		InstaPoolIo::<T>::mutate(old_sale.region_begin, |r| r.system.saturating_accrue(old_pooled));
+		InstaPoolIo::<T>::mutate(old_sale.region_end, |r| r.system.saturating_reduce(old_pooled));
+
+		// Calculate the start price for the upcoming sale.
+		let price = {
+			let offered = old_sale.cores_offered;
+			let ideal = old_sale.ideal_cores_sold;
+			let sold = old_sale.cores_sold;
+
+			let maybe_purchase_price = if offered == 0 {
+				// No cores offered for sale - no purchase price.
+				None
+			} else if sold >= ideal {
+				// Sold more than the ideal amount. We should look for the last purchase price
+				// before the sell-out. If there was no purchase at all, then we avoid having a
+				// price here so that we make no alterations to it (since otherwise we would
+				// increase it).
+				old_sale.sellout_price
+			} else {
+				// Sold less than the ideal - we fall back to the regular price.
+				Some(old_sale.price)
+			};
+			if let Some(purchase_price) = maybe_purchase_price {
+				T::PriceAdapter::adapt_price(sold.min(offered), ideal, offered)
+					.saturating_mul_int(purchase_price)
+			} else {
+				old_sale.price
+			}
+		};
+
+		// Set workload for the reserved (system, probably) workloads.
+		let region_begin = old_sale.region_end;
+		let region_end = region_begin + config.region_length;
+
+		let mut first_core = 0;
+		let mut total_pooled: SignedCoreMaskBitCount = 0;
+		for schedule in Reservations::<T>::get().into_iter() {
+			let parts: u32 = schedule
+				.iter()
+				.filter(|i| matches!(i.assignment, CoreAssignment::Pool))
+				.map(|i| i.mask.count_ones())
+				.sum();
+			total_pooled.saturating_accrue(parts as i32);
+
+			Workplan::<T>::insert((region_begin, first_core), &schedule);
+			first_core.saturating_inc();
+		}
+		InstaPoolIo::<T>::mutate(region_begin, |r| r.system.saturating_accrue(total_pooled));
+		InstaPoolIo::<T>::mutate(region_end, |r| r.system.saturating_reduce(total_pooled));
+
+		let mut leases = Leases::<T>::get();
+		// Can morph to a renewable as long as it's >=begin and <end.
+		leases.retain(|&LeaseRecordItem { until, task }| {
+			let mask = CoreMask::complete();
+			let assignment = CoreAssignment::Task(task);
+			let schedule = BoundedVec::truncate_from(vec![ScheduleItem { mask, assignment }]);
+			Workplan::<T>::insert((region_begin, first_core), &schedule);
+			let expiring = until >= region_begin && until < region_end;
+			if expiring {
+				// last time for this one - make it renewable.
+				let renewal_id = AllowedRenewalId { core: first_core, when: region_end };
+				let record = AllowedRenewalRecord { price, completion: Complete(schedule) };
+				AllowedRenewals::<T>::insert(renewal_id, &record);
+				Self::deposit_event(Event::Renewable {
+					core: first_core,
+					price,
+					begin: region_end,
+					workload: record.completion.drain_complete().unwrap_or_default(),
+				});
+				Self::deposit_event(Event::LeaseEnding { when: region_end, task });
+			}
+			first_core.saturating_inc();
+			!expiring
+		});
+		Leases::<T>::put(&leases);
+
+		let max_possible_sales = status.core_count.saturating_sub(first_core);
+		let limit_cores_offered = config.limit_cores_offered.unwrap_or(CoreIndex::max_value());
+		let cores_offered = limit_cores_offered.min(max_possible_sales);
+		let sale_start = now.saturating_add(config.interlude_length);
+		let leadin_length = config.leadin_length;
+		let ideal_cores_sold = (config.ideal_bulk_proportion * cores_offered as u32) as u16;
+		// Update SaleInfo
+		let new_sale = SaleInfoRecord {
+			sale_start,
+			leadin_length,
+			price,
+			sellout_price: None,
+			region_begin,
+			region_end,
+			first_core,
+			ideal_cores_sold,
+			cores_offered,
+			cores_sold: 0,
+		};
+		SaleInfo::<T>::put(&new_sale);
+		Self::deposit_event(Event::SaleInitialized {
+			sale_start,
+			leadin_length,
+			start_price: Self::sale_price(&new_sale, now),
+			regular_price: price,
+			region_begin,
+			region_end,
+			ideal_cores_sold,
+			cores_offered,
+		});
+
+		Some(())
+	}
+
+	pub(crate) fn process_pool(when: Timeslice, status: &mut StatusRecord) {
+		let pool_io = InstaPoolIo::<T>::take(when);
+		status.private_pool_size = (status.private_pool_size as SignedCoreMaskBitCount)
+			.saturating_add(pool_io.private) as CoreMaskBitCount;
+		status.system_pool_size = (status.system_pool_size as SignedCoreMaskBitCount)
+			.saturating_add(pool_io.system) as CoreMaskBitCount;
+		let record = InstaPoolHistoryRecord {
+			private_contributions: status.private_pool_size,
+			system_contributions: status.system_pool_size,
+			maybe_payout: None,
+		};
+		InstaPoolHistory::<T>::insert(when, record);
+		Self::deposit_event(Event::<T>::HistoryInitialized {
+			when,
+			private_pool_size: status.private_pool_size,
+			system_pool_size: status.system_pool_size,
+		});
+	}
+
+	/// Schedule cores for the given `timeslice`.
+	pub(crate) fn process_core_schedule(
+		timeslice: Timeslice,
+		rc_begin: RelayBlockNumberOf<T>,
+		core: CoreIndex,
+	) {
+		let Some(workplan) = Workplan::<T>::take((timeslice, core)) else {
+			return;
+		};
+		let workload = Workload::<T>::get(core);
+		let parts_used = workplan.iter().map(|i| i.mask).fold(CoreMask::void(), |a, i| a | i);
+		let mut workplan = workplan.into_inner();
+		workplan.extend(workload.into_iter().filter(|i| (i.mask & parts_used).is_void()));
+		let workplan = Schedule::truncate_from(workplan);
+		Workload::<T>::insert(core, &workplan);
+
+		let mut total_used = 0;
+		let mut intermediate = workplan
+			.into_iter()
+			.map(|i| (i.assignment, i.mask.count_ones() as u16 * (57_600 / 80)))
+			.inspect(|i| total_used.saturating_accrue(i.1))
+			.collect::<Vec<_>>();
+		if total_used < 57_600 {
+			intermediate.push((CoreAssignment::Idle, 57_600 - total_used));
+		}
+		intermediate.sort();
+		let mut assignment: Vec<(CoreAssignment, PartsOf57600)> =
+			Vec::with_capacity(intermediate.len());
+		for i in intermediate.into_iter() {
+			if let Some(ref mut last) = assignment.last_mut() {
+				if last.0 == i.0 {
+					last.1 += i.1;
+					continue
+				}
+			}
+			assignment.push(i);
+		}
+		T::Coretime::assign_core(core, rc_begin, assignment.clone(), None);
+		Self::deposit_event(Event::<T>::CoreAssigned { core, when: rc_begin, assignment });
+	}
+}
diff --git a/substrate/frame/broker/src/types.rs b/substrate/frame/broker/src/types.rs
new file mode 100644
index 0000000000000000000000000000000000000000..89222ca8e95271098fe2d335142015024439a71a
--- /dev/null
+++ b/substrate/frame/broker/src/types.rs
@@ -0,0 +1,290 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{
+	Config, CoreAssignment, CoreIndex, CoreMask, CoretimeInterface, TaskId, CORE_MASK_BITS,
+};
+use codec::{Decode, Encode, MaxEncodedLen};
+use frame_support::traits::fungible::Inspect;
+use frame_system::{pallet_prelude::BlockNumberFor, Config as SConfig};
+use scale_info::TypeInfo;
+use sp_arithmetic::Perbill;
+use sp_core::{ConstU32, RuntimeDebug};
+use sp_runtime::BoundedVec;
+
+pub type BalanceOf<T> = <<T as Config>::Currency as Inspect<<T as SConfig>::AccountId>>::Balance;
+pub type RelayBalanceOf<T> = <<T as Config>::Coretime as CoretimeInterface>::Balance;
+pub type RelayBlockNumberOf<T> = <<T as Config>::Coretime as CoretimeInterface>::BlockNumber;
+pub type RelayAccountIdOf<T> = <<T as Config>::Coretime as CoretimeInterface>::AccountId;
+
+/// Relay-chain block number with a fixed divisor of Config::TimeslicePeriod.
+pub type Timeslice = u32;
+/// Counter for the total number of set bits over every core's `CoreMask`. `u32` so we don't
+/// ever get an overflow. This is 1/80th of a Polkadot Core per timeslice. Assuming timeslices are
+/// 80 blocks, then this indicates usage of a single core one time over a timeslice.
+pub type CoreMaskBitCount = u32;
+/// The same as `CoreMaskBitCount` but signed.
+pub type SignedCoreMaskBitCount = i32;
+
+/// Whether a core assignment is revokable or not.
+#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub enum Finality {
+	/// The region remains with the same owner allowing the assignment to be altered.
+	Provisional,
+	/// The region is removed; the assignment may be eligible for renewal.
+	Final,
+}
+
+/// Self-describing identity for a Region of Bulk Coretime.
+#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct RegionId {
+	/// The timeslice at which this Region begins.
+	pub begin: Timeslice,
+	/// The index of the Polakdot Core on which this Region will be scheduled.
+	pub core: CoreIndex,
+	/// The regularity parts in which this Region will be scheduled.
+	pub mask: CoreMask,
+}
+impl From<u128> for RegionId {
+	fn from(x: u128) -> Self {
+		Self { begin: (x >> 96) as u32, core: (x >> 80) as u16, mask: x.into() }
+	}
+}
+impl From<RegionId> for u128 {
+	fn from(x: RegionId) -> Self {
+		(x.begin as u128) << 96 | (x.core as u128) << 80 | u128::from(x.mask)
+	}
+}
+#[test]
+fn region_id_converts_u128() {
+	let r = RegionId { begin: 0x12345678u32, core: 0xabcdu16, mask: 0xdeadbeefcafef00d0123.into() };
+	let u = 0x12345678_abcd_deadbeefcafef00d0123u128;
+	assert_eq!(RegionId::from(u), r);
+	assert_eq!(u128::from(r), u);
+}
+
+/// The rest of the information describing a Region.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct RegionRecord<AccountId, Balance> {
+	/// The end of the Region.
+	pub end: Timeslice,
+	/// The owner of the Region.
+	pub owner: AccountId,
+	/// The amount paid to Polkadot for this Region, or `None` if renewal is not allowed.
+	pub paid: Option<Balance>,
+}
+pub type RegionRecordOf<T> = RegionRecord<<T as SConfig>::AccountId, BalanceOf<T>>;
+
+/// An distinct item which can be scheduled on a Polkadot Core.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct ScheduleItem {
+	/// The regularity parts in which this Item will be scheduled on the Core.
+	pub mask: CoreMask,
+	/// The job that the Core should be doing.
+	pub assignment: CoreAssignment,
+}
+pub type Schedule = BoundedVec<ScheduleItem, ConstU32<{ CORE_MASK_BITS as u32 }>>;
+
+/// The record body of a Region which was contributed to the Instantaneous Coretime Pool. This helps
+/// with making pro rata payments to contributors.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct ContributionRecord<AccountId> {
+	/// The end of the Region contributed.
+	pub length: Timeslice,
+	/// The identity of the contributor.
+	pub payee: AccountId,
+}
+pub type ContributionRecordOf<T> = ContributionRecord<<T as SConfig>::AccountId>;
+
+/// A per-timeslice bookkeeping record for tracking Instantaneous Coretime Pool activity and
+/// making proper payments to contributors.
+#[derive(Encode, Decode, Clone, Default, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct InstaPoolHistoryRecord<Balance> {
+	/// The total amount of Coretime (measured in Core Mask Bits minus any contributions which have
+	/// already been paid out.
+	pub private_contributions: CoreMaskBitCount,
+	/// The total amount of Coretime (measured in Core Mask Bits contributed by the Polkadot System
+	/// in this timeslice.
+	pub system_contributions: CoreMaskBitCount,
+	/// The payout remaining for the `private_contributions`, or `None` if the revenue is not yet
+	/// known.
+	pub maybe_payout: Option<Balance>,
+}
+pub type InstaPoolHistoryRecordOf<T> = InstaPoolHistoryRecord<BalanceOf<T>>;
+
+/// How much of a core has been assigned or, if completely assigned, the workload itself.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub enum CompletionStatus {
+	/// The core is not fully assigned; the inner is the parts which have.
+	Partial(CoreMask),
+	/// The core is fully assigned; the inner is the workload which has been assigned.
+	Complete(Schedule),
+}
+impl CompletionStatus {
+	/// Return reference to the complete workload, or `None` if incomplete.
+	pub fn complete(&self) -> Option<&Schedule> {
+		match self {
+			Self::Complete(s) => Some(s),
+			Self::Partial(_) => None,
+		}
+	}
+	/// Return the complete workload, or `None` if incomplete.
+	pub fn drain_complete(self) -> Option<Schedule> {
+		match self {
+			Self::Complete(s) => Some(s),
+			Self::Partial(_) => None,
+		}
+	}
+}
+
+/// The identity of a possible Core workload renewal.
+#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct AllowedRenewalId {
+	/// The core whose workload at the sale ending with `when` may be renewed to begin at `when`.
+	pub core: CoreIndex,
+	/// The point in time that the renewable workload on `core` ends and a fresh renewal may begin.
+	pub when: Timeslice,
+}
+
+/// A record of an allowed renewal.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct AllowedRenewalRecord<Balance> {
+	/// The price for which the next renewal can be made.
+	pub price: Balance,
+	/// The workload which will be scheduled on the Core in the case a renewal is made, or if
+	/// incomplete, then the parts of the core which have been scheduled.
+	pub completion: CompletionStatus,
+}
+pub type AllowedRenewalRecordOf<T> = AllowedRenewalRecord<BalanceOf<T>>;
+
+/// General status of the system.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct StatusRecord {
+	/// The total number of cores which can be assigned (one plus the maximum index which can
+	/// be used in `Coretime::assign`).
+	pub core_count: CoreIndex,
+	/// The current size of the Instantaneous Coretime Pool, measured in
+	/// Core Mask Bits.
+	pub private_pool_size: CoreMaskBitCount,
+	/// The current amount of the Instantaneous Coretime Pool which is provided by the Polkadot
+	/// System, rather than provided as a result of privately operated Coretime.
+	pub system_pool_size: CoreMaskBitCount,
+	/// The last (Relay-chain) timeslice which we committed to the Relay-chain.
+	pub last_committed_timeslice: Timeslice,
+	/// The timeslice of the last time we ticked.
+	pub last_timeslice: Timeslice,
+}
+
+/// A record of flux in the InstaPool.
+#[derive(
+	Encode, Decode, Clone, Copy, Default, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen,
+)]
+pub struct PoolIoRecord {
+	/// The total change of the portion of the pool supplied by purchased Bulk Coretime, measured
+	/// in Core Mask Bits.
+	pub private: SignedCoreMaskBitCount,
+	/// The total change of the portion of the pool supplied by the Polkaot System, measured in
+	/// Core Mask Bits.
+	pub system: SignedCoreMaskBitCount,
+}
+
+/// The status of a Bulk Coretime Sale.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct SaleInfoRecord<Balance, BlockNumber> {
+	/// The local block number at which the sale will/did start.
+	pub sale_start: BlockNumber,
+	/// The length in blocks of the Leadin Period (where the price is decreasing).
+	pub leadin_length: BlockNumber,
+	/// The price of Bulk Coretime after the Leadin Period.
+	pub price: Balance,
+	/// The first timeslice of the Regions which are being sold in this sale.
+	pub region_begin: Timeslice,
+	/// The timeslice on which the Regions which are being sold in the sale terminate. (i.e. One
+	/// after the last timeslice which the Regions control.)
+	pub region_end: Timeslice,
+	/// The number of cores we want to sell, ideally. Selling this amount would result in no
+	/// change to the price for the next sale.
+	pub ideal_cores_sold: CoreIndex,
+	/// Number of cores which are/have been offered for sale.
+	pub cores_offered: CoreIndex,
+	/// The index of the first core which is for sale. Core of Regions which are sold have
+	/// incrementing indices from this.
+	pub first_core: CoreIndex,
+	/// The latest price at which Bulk Coretime was purchased until surpassing the ideal number of
+	/// cores were sold.
+	pub sellout_price: Option<Balance>,
+	/// Number of cores which have been sold; never more than cores_offered.
+	pub cores_sold: CoreIndex,
+}
+pub type SaleInfoRecordOf<T> = SaleInfoRecord<BalanceOf<T>, BlockNumberFor<T>>;
+
+/// Record for Polkadot Core reservations (generally tasked with the maintenance of System
+/// Chains).
+pub type ReservationsRecord<Max> = BoundedVec<Schedule, Max>;
+pub type ReservationsRecordOf<T> = ReservationsRecord<<T as Config>::MaxReservedCores>;
+
+/// Information on a single legacy lease.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct LeaseRecordItem {
+	/// The timeslice until the lease is valid.
+	pub until: Timeslice,
+	/// The task which the lease is for.
+	pub task: TaskId,
+}
+
+/// Record for Polkadot Core legacy leases.
+pub type LeasesRecord<Max> = BoundedVec<LeaseRecordItem, Max>;
+pub type LeasesRecordOf<T> = LeasesRecord<<T as Config>::MaxLeasedCores>;
+
+/// Configuration of this pallet.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+pub struct ConfigRecord<BlockNumber, RelayBlockNumber> {
+	/// The number of Relay-chain blocks in advance which scheduling should be fixed and the
+	/// `Coretime::assign` API used to inform the Relay-chain.
+	pub advance_notice: RelayBlockNumber,
+	/// The length in blocks of the Interlude Period for forthcoming sales.
+	pub interlude_length: BlockNumber,
+	/// The length in blocks of the Leadin Period for forthcoming sales.
+	pub leadin_length: BlockNumber,
+	/// The length in timeslices of Regions which are up for sale in forthcoming sales.
+	pub region_length: Timeslice,
+	/// The proportion of cores available for sale which should be sold in order for the price
+	/// to remain the same in the next sale.
+	pub ideal_bulk_proportion: Perbill,
+	/// An artificial limit to the number of cores which are allowed to be sold. If `Some` then
+	/// no more cores will be sold than this.
+	pub limit_cores_offered: Option<CoreIndex>,
+	/// The amount by which the renewal price increases each sale period.
+	pub renewal_bump: Perbill,
+	/// The duration by which rewards for contributions to the InstaPool must be collected.
+	pub contribution_timeout: Timeslice,
+}
+pub type ConfigRecordOf<T> = ConfigRecord<BlockNumberFor<T>, RelayBlockNumberOf<T>>;
+
+impl<BlockNumber, RelayBlockNumber> ConfigRecord<BlockNumber, RelayBlockNumber>
+where
+	BlockNumber: sp_arithmetic::traits::Zero,
+{
+	/// Check the config for basic validity constraints.
+	pub(crate) fn validate(&self) -> Result<(), ()> {
+		if self.leadin_length.is_zero() {
+			return Err(())
+		}
+
+		Ok(())
+	}
+}
diff --git a/substrate/frame/broker/src/utility_impls.rs b/substrate/frame/broker/src/utility_impls.rs
new file mode 100644
index 0000000000000000000000000000000000000000..99c4de32f77678f4df9568e7c5a2c442b255c4c8
--- /dev/null
+++ b/substrate/frame/broker/src/utility_impls.rs
@@ -0,0 +1,121 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use super::*;
+use frame_support::{
+	pallet_prelude::{DispatchResult, *},
+	traits::{
+		fungible::Balanced,
+		tokens::{Fortitude::Polite, Precision::Exact, Preservation::Expendable},
+		OnUnbalanced,
+	},
+};
+use frame_system::pallet_prelude::BlockNumberFor;
+use sp_arithmetic::{
+	traits::{SaturatedConversion, Saturating},
+	FixedPointNumber, FixedU64,
+};
+use sp_runtime::traits::AccountIdConversion;
+
+impl<T: Config> Pallet<T> {
+	pub fn current_timeslice() -> Timeslice {
+		let latest = T::Coretime::latest();
+		let timeslice_period = T::TimeslicePeriod::get();
+		(latest / timeslice_period).saturated_into()
+	}
+
+	pub fn latest_timeslice_ready_to_commit(config: &ConfigRecordOf<T>) -> Timeslice {
+		let latest = T::Coretime::latest();
+		let advanced = latest.saturating_add(config.advance_notice);
+		let timeslice_period = T::TimeslicePeriod::get();
+		(advanced / timeslice_period).saturated_into()
+	}
+
+	pub fn next_timeslice_to_commit(
+		config: &ConfigRecordOf<T>,
+		status: &StatusRecord,
+	) -> Option<Timeslice> {
+		if status.last_committed_timeslice < Self::latest_timeslice_ready_to_commit(config) {
+			Some(status.last_committed_timeslice + 1)
+		} else {
+			None
+		}
+	}
+
+	pub fn account_id() -> T::AccountId {
+		T::PalletId::get().into_account_truncating()
+	}
+
+	pub fn sale_price(sale: &SaleInfoRecordOf<T>, now: BlockNumberFor<T>) -> BalanceOf<T> {
+		let num = now.saturating_sub(sale.sale_start).min(sale.leadin_length).saturated_into();
+		let through = FixedU64::from_rational(num, sale.leadin_length.saturated_into());
+		T::PriceAdapter::leadin_factor_at(through).saturating_mul_int(sale.price)
+	}
+
+	pub(crate) fn charge(who: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
+		let credit = T::Currency::withdraw(&who, amount, Exact, Expendable, Polite)?;
+		T::OnRevenue::on_unbalanced(credit);
+		Ok(())
+	}
+
+	pub(crate) fn issue(
+		core: CoreIndex,
+		begin: Timeslice,
+		end: Timeslice,
+		owner: T::AccountId,
+		paid: Option<BalanceOf<T>>,
+	) -> RegionId {
+		let id = RegionId { begin, core, mask: CoreMask::complete() };
+		let record = RegionRecord { end, owner, paid };
+		Regions::<T>::insert(&id, &record);
+		id
+	}
+
+	pub(crate) fn utilize(
+		mut region_id: RegionId,
+		maybe_check_owner: Option<T::AccountId>,
+		finality: Finality,
+	) -> Result<Option<(RegionId, RegionRecordOf<T>)>, Error<T>> {
+		let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
+		let region = Regions::<T>::get(&region_id).ok_or(Error::<T>::UnknownRegion)?;
+
+		if let Some(check_owner) = maybe_check_owner {
+			ensure!(check_owner == region.owner, Error::<T>::NotOwner);
+		}
+
+		Regions::<T>::remove(&region_id);
+
+		let last_committed_timeslice = status.last_committed_timeslice;
+		if region_id.begin <= last_committed_timeslice {
+			region_id.begin = last_committed_timeslice + 1;
+			if region_id.begin >= region.end {
+				let duration = region.end.saturating_sub(region_id.begin);
+				Self::deposit_event(Event::RegionDropped { region_id, duration });
+				return Ok(None)
+			}
+		} else {
+			Workplan::<T>::mutate_extant((region_id.begin, region_id.core), |p| {
+				p.retain(|i| (i.mask & region_id.mask).is_void())
+			});
+		}
+		if finality == Finality::Provisional {
+			Regions::<T>::insert(&region_id, &region);
+		}
+
+		Ok(Some((region_id, region)))
+	}
+}
diff --git a/substrate/frame/broker/src/weights.rs b/substrate/frame/broker/src/weights.rs
new file mode 100644
index 0000000000000000000000000000000000000000..93b568bf2a035af7542056026ab3c8238bfc6cfd
--- /dev/null
+++ b/substrate/frame/broker/src/weights.rs
@@ -0,0 +1,794 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Autogenerated weights for `pallet_broker`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2023-07-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `runner-ynta1nyy-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
+
+// Executed Command:
+// target/production/substrate-node
+// benchmark
+// pallet
+// --steps=50
+// --repeat=20
+// --extrinsic=*
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json
+// --pallet=pallet_broker
+// --chain=dev
+// --header=./HEADER-APACHE2
+// --output=./frame/broker/src/weights.rs
+// --template=./.maintain/frame-weight-template.hbs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use core::marker::PhantomData;
+
+/// Weight functions needed for `pallet_broker`.
+pub trait WeightInfo {
+	fn configure() -> Weight;
+	fn reserve() -> Weight;
+	fn unreserve() -> Weight;
+	fn set_lease() -> Weight;
+	fn start_sales(n: u32, ) -> Weight;
+	fn purchase() -> Weight;
+	fn renew() -> Weight;
+	fn transfer() -> Weight;
+	fn partition() -> Weight;
+	fn interlace() -> Weight;
+	fn assign() -> Weight;
+	fn pool() -> Weight;
+	fn claim_revenue(m: u32, ) -> Weight;
+	fn purchase_credit() -> Weight;
+	fn drop_region() -> Weight;
+	fn drop_contribution() -> Weight;
+	fn drop_history() -> Weight;
+	fn drop_renewal() -> Weight;
+	fn request_core_count(n: u32, ) -> Weight;
+	fn process_core_count(n: u32, ) -> Weight;
+	fn process_revenue() -> Weight;
+	fn rotate_sale(n: u32, ) -> Weight;
+	fn process_pool() -> Weight;
+	fn process_core_schedule() -> Weight;
+	fn request_revenue_info_at() -> Weight;
+}
+
+/// Weights for `pallet_broker` using the Substrate node and recommended hardware.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+	/// Storage: `Broker::Configuration` (r:0 w:1)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	fn configure() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 3_448_000 picoseconds.
+		Weight::from_parts(3_729_000, 0)
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Reservations` (r:1 w:1)
+	/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`)
+	fn reserve() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `5016`
+		//  Estimated: `7496`
+		// Minimum execution time: 22_537_000 picoseconds.
+		Weight::from_parts(23_335_000, 7496)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Reservations` (r:1 w:1)
+	/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`)
+	fn unreserve() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `6218`
+		//  Estimated: `7496`
+		// Minimum execution time: 21_668_000 picoseconds.
+		Weight::from_parts(22_442_000, 7496)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Leases` (r:1 w:1)
+	/// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`)
+	fn set_lease() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `239`
+		//  Estimated: `1526`
+		// Minimum execution time: 13_606_000 picoseconds.
+		Weight::from_parts(14_104_000, 1526)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolIo` (r:3 w:3)
+	/// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Reservations` (r:1 w:0)
+	/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Leases` (r:1 w:1)
+	/// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::SaleInfo` (r:0 w:1)
+	/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:0 w:1)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workplan` (r:0 w:10)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	/// The range of component `n` is `[0, 1000]`.
+	fn start_sales(_n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `6330`
+		//  Estimated: `8499`
+		// Minimum execution time: 64_012_000 picoseconds.
+		Weight::from_parts(67_819_922, 8499)
+			.saturating_add(T::DbWeight::get().reads(6_u64))
+			.saturating_add(T::DbWeight::get().writes(16_u64))
+	}
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::SaleInfo` (r:1 w:1)
+	/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
+	/// Storage: `Authorship::Author` (r:1 w:0)
+	/// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `System::Digest` (r:1 w:0)
+	/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `Broker::Regions` (r:0 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn purchase() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `568`
+		//  Estimated: `2053`
+		// Minimum execution time: 48_110_000 picoseconds.
+		Weight::from_parts(49_234_000, 2053)
+			.saturating_add(T::DbWeight::get().reads(4_u64))
+			.saturating_add(T::DbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::SaleInfo` (r:1 w:1)
+	/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::AllowedRenewals` (r:1 w:2)
+	/// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`)
+	/// Storage: `Authorship::Author` (r:1 w:0)
+	/// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `System::Digest` (r:1 w:0)
+	/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `Broker::Workplan` (r:0 w:1)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	fn renew() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `686`
+		//  Estimated: `4698`
+		// Minimum execution time: 69_580_000 picoseconds.
+		Weight::from_parts(70_914_000, 4698)
+			.saturating_add(T::DbWeight::get().reads(6_u64))
+			.saturating_add(T::DbWeight::get().writes(4_u64))
+	}
+	/// Storage: `Broker::Regions` (r:1 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn transfer() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `495`
+		//  Estimated: `3550`
+		// Minimum execution time: 17_687_000 picoseconds.
+		Weight::from_parts(18_573_000, 3550)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Regions` (r:1 w:2)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn partition() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `495`
+		//  Estimated: `3550`
+		// Minimum execution time: 19_675_000 picoseconds.
+		Weight::from_parts(20_234_000, 3550)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Broker::Regions` (r:1 w:2)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn interlace() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `495`
+		//  Estimated: `3550`
+		// Minimum execution time: 19_426_000 picoseconds.
+		Weight::from_parts(20_414_000, 3550)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Regions` (r:1 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workplan` (r:1 w:1)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	fn assign() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `740`
+		//  Estimated: `4681`
+		// Minimum execution time: 31_751_000 picoseconds.
+		Weight::from_parts(32_966_000, 4681)
+			.saturating_add(T::DbWeight::get().reads(4_u64))
+			.saturating_add(T::DbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Regions` (r:1 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workplan` (r:1 w:1)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolIo` (r:2 w:2)
+	/// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolContribution` (r:0 w:1)
+	/// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	fn pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `775`
+		//  Estimated: `5996`
+		// Minimum execution time: 36_709_000 picoseconds.
+		Weight::from_parts(38_930_000, 5996)
+			.saturating_add(T::DbWeight::get().reads(5_u64))
+			.saturating_add(T::DbWeight::get().writes(5_u64))
+	}
+	/// Storage: `Broker::InstaPoolContribution` (r:1 w:1)
+	/// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolHistory` (r:3 w:1)
+	/// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:0)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// The range of component `m` is `[1, 3]`.
+	fn claim_revenue(m: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `720`
+		//  Estimated: `6196 + m * (2520 ±0)`
+		// Minimum execution time: 55_510_000 picoseconds.
+		Weight::from_parts(56_665_061, 6196)
+			// Standard Error: 61_729
+			.saturating_add(Weight::from_parts(1_724_824, 0).saturating_mul(m.into()))
+			.saturating_add(T::DbWeight::get().reads(3_u64))
+			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into())))
+			.saturating_add(T::DbWeight::get().writes(3_u64))
+			.saturating_add(Weight::from_parts(0, 2520).saturating_mul(m.into()))
+	}
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn purchase_credit() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `103`
+		//  Estimated: `3593`
+		// Minimum execution time: 44_992_000 picoseconds.
+		Weight::from_parts(46_225_000, 3593)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Regions` (r:1 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn drop_region() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `603`
+		//  Estimated: `3550`
+		// Minimum execution time: 28_207_000 picoseconds.
+		Weight::from_parts(28_707_000, 3550)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolContribution` (r:1 w:1)
+	/// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	fn drop_contribution() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `601`
+		//  Estimated: `3533`
+		// Minimum execution time: 31_813_000 picoseconds.
+		Weight::from_parts(32_612_000, 3533)
+			.saturating_add(T::DbWeight::get().reads(3_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolHistory` (r:1 w:1)
+	/// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:0)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn drop_history() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `829`
+		//  Estimated: `3593`
+		// Minimum execution time: 38_571_000 picoseconds.
+		Weight::from_parts(39_493_000, 3593)
+			.saturating_add(T::DbWeight::get().reads(4_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::AllowedRenewals` (r:1 w:1)
+	/// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`)
+	fn drop_renewal() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `525`
+		//  Estimated: `4698`
+		// Minimum execution time: 24_714_000 picoseconds.
+		Weight::from_parts(25_288_000, 4698)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x18194fcb5c1fcace44d2d0a004272614` (r:0 w:1)
+	/// Proof: UNKNOWN KEY `0x18194fcb5c1fcace44d2d0a004272614` (r:0 w:1)
+	/// The range of component `n` is `[0, 1000]`.
+	fn request_core_count(_n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 7_258_000 picoseconds.
+		Weight::from_parts(7_925_570, 0)
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x18194fcb5c1fcace44d2d0a004272614` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x18194fcb5c1fcace44d2d0a004272614` (r:1 w:0)
+	/// The range of component `n` is `[0, 1000]`.
+	fn process_core_count(_n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `97`
+		//  Estimated: `3562`
+		// Minimum execution time: 7_136_000 picoseconds.
+		Weight::from_parts(7_788_194, 3562)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+	}
+	/// Storage: `Broker::InstaPoolHistory` (r:0 w:1)
+	/// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	fn process_revenue() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 6_049_000 picoseconds.
+		Weight::from_parts(6_311_000, 0)
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::InstaPoolIo` (r:3 w:3)
+	/// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Reservations` (r:1 w:0)
+	/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Leases` (r:1 w:1)
+	/// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::SaleInfo` (r:0 w:1)
+	/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workplan` (r:0 w:10)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	/// The range of component `n` is `[0, 1000]`.
+	fn rotate_sale(n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `6281`
+		//  Estimated: `8499`
+		// Minimum execution time: 47_504_000 picoseconds.
+		Weight::from_parts(49_778_098, 8499)
+			// Standard Error: 109
+			.saturating_add(Weight::from_parts(427, 0).saturating_mul(n.into()))
+			.saturating_add(T::DbWeight::get().reads(5_u64))
+			.saturating_add(T::DbWeight::get().writes(15_u64))
+	}
+	/// Storage: `Broker::InstaPoolIo` (r:1 w:0)
+	/// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolHistory` (r:0 w:1)
+	/// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	fn process_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `180`
+		//  Estimated: `3493`
+		// Minimum execution time: 9_573_000 picoseconds.
+		Weight::from_parts(10_034_000, 3493)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Workplan` (r:1 w:1)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workload` (r:1 w:1)
+	/// Proof: `Broker::Workload` (`max_values`: None, `max_size`: Some(1212), added: 3687, mode: `MaxEncodedLen`)
+	fn process_core_schedule() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1423`
+		//  Estimated: `4681`
+		// Minimum execution time: 21_331_000 picoseconds.
+		Weight::from_parts(22_235_000, 4681)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(2_u64))
+	}
+	fn request_revenue_info_at() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 191_000 picoseconds.
+		Weight::from_parts(234_000, 0)
+	}
+}
+
+// For backwards compatibility and tests.
+impl WeightInfo for () {
+	/// Storage: `Broker::Configuration` (r:0 w:1)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	fn configure() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 3_448_000 picoseconds.
+		Weight::from_parts(3_729_000, 0)
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Reservations` (r:1 w:1)
+	/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`)
+	fn reserve() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `5016`
+		//  Estimated: `7496`
+		// Minimum execution time: 22_537_000 picoseconds.
+		Weight::from_parts(23_335_000, 7496)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Reservations` (r:1 w:1)
+	/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`)
+	fn unreserve() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `6218`
+		//  Estimated: `7496`
+		// Minimum execution time: 21_668_000 picoseconds.
+		Weight::from_parts(22_442_000, 7496)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Leases` (r:1 w:1)
+	/// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`)
+	fn set_lease() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `239`
+		//  Estimated: `1526`
+		// Minimum execution time: 13_606_000 picoseconds.
+		Weight::from_parts(14_104_000, 1526)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolIo` (r:3 w:3)
+	/// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Reservations` (r:1 w:0)
+	/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Leases` (r:1 w:1)
+	/// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::SaleInfo` (r:0 w:1)
+	/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:0 w:1)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workplan` (r:0 w:10)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	/// The range of component `n` is `[0, 1000]`.
+	fn start_sales(_n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `6330`
+		//  Estimated: `8499`
+		// Minimum execution time: 64_012_000 picoseconds.
+		Weight::from_parts(67_819_922, 8499)
+			.saturating_add(RocksDbWeight::get().reads(6_u64))
+			.saturating_add(RocksDbWeight::get().writes(16_u64))
+	}
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::SaleInfo` (r:1 w:1)
+	/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
+	/// Storage: `Authorship::Author` (r:1 w:0)
+	/// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `System::Digest` (r:1 w:0)
+	/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `Broker::Regions` (r:0 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn purchase() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `568`
+		//  Estimated: `2053`
+		// Minimum execution time: 48_110_000 picoseconds.
+		Weight::from_parts(49_234_000, 2053)
+			.saturating_add(RocksDbWeight::get().reads(4_u64))
+			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::SaleInfo` (r:1 w:1)
+	/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::AllowedRenewals` (r:1 w:2)
+	/// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`)
+	/// Storage: `Authorship::Author` (r:1 w:0)
+	/// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `System::Digest` (r:1 w:0)
+	/// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `Broker::Workplan` (r:0 w:1)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	fn renew() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `686`
+		//  Estimated: `4698`
+		// Minimum execution time: 69_580_000 picoseconds.
+		Weight::from_parts(70_914_000, 4698)
+			.saturating_add(RocksDbWeight::get().reads(6_u64))
+			.saturating_add(RocksDbWeight::get().writes(4_u64))
+	}
+	/// Storage: `Broker::Regions` (r:1 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn transfer() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `495`
+		//  Estimated: `3550`
+		// Minimum execution time: 17_687_000 picoseconds.
+		Weight::from_parts(18_573_000, 3550)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Regions` (r:1 w:2)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn partition() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `495`
+		//  Estimated: `3550`
+		// Minimum execution time: 19_675_000 picoseconds.
+		Weight::from_parts(20_234_000, 3550)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Broker::Regions` (r:1 w:2)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn interlace() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `495`
+		//  Estimated: `3550`
+		// Minimum execution time: 19_426_000 picoseconds.
+		Weight::from_parts(20_414_000, 3550)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Regions` (r:1 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workplan` (r:1 w:1)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	fn assign() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `740`
+		//  Estimated: `4681`
+		// Minimum execution time: 31_751_000 picoseconds.
+		Weight::from_parts(32_966_000, 4681)
+			.saturating_add(RocksDbWeight::get().reads(4_u64))
+			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	}
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Regions` (r:1 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workplan` (r:1 w:1)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolIo` (r:2 w:2)
+	/// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolContribution` (r:0 w:1)
+	/// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	fn pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `775`
+		//  Estimated: `5996`
+		// Minimum execution time: 36_709_000 picoseconds.
+		Weight::from_parts(38_930_000, 5996)
+			.saturating_add(RocksDbWeight::get().reads(5_u64))
+			.saturating_add(RocksDbWeight::get().writes(5_u64))
+	}
+	/// Storage: `Broker::InstaPoolContribution` (r:1 w:1)
+	/// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolHistory` (r:3 w:1)
+	/// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:0)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// The range of component `m` is `[1, 3]`.
+	fn claim_revenue(m: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `720`
+		//  Estimated: `6196 + m * (2520 ±0)`
+		// Minimum execution time: 55_510_000 picoseconds.
+		Weight::from_parts(56_665_061, 6196)
+			// Standard Error: 61_729
+			.saturating_add(Weight::from_parts(1_724_824, 0).saturating_mul(m.into()))
+			.saturating_add(RocksDbWeight::get().reads(3_u64))
+			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(m.into())))
+			.saturating_add(RocksDbWeight::get().writes(3_u64))
+			.saturating_add(Weight::from_parts(0, 2520).saturating_mul(m.into()))
+	}
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn purchase_credit() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `103`
+		//  Estimated: `3593`
+		// Minimum execution time: 44_992_000 picoseconds.
+		Weight::from_parts(46_225_000, 3593)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Regions` (r:1 w:1)
+	/// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	fn drop_region() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `603`
+		//  Estimated: `3550`
+		// Minimum execution time: 28_207_000 picoseconds.
+		Weight::from_parts(28_707_000, 3550)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolContribution` (r:1 w:1)
+	/// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
+	fn drop_contribution() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `601`
+		//  Estimated: `3533`
+		// Minimum execution time: 31_813_000 picoseconds.
+		Weight::from_parts(32_612_000, 3533)
+			.saturating_add(RocksDbWeight::get().reads(3_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Configuration` (r:1 w:0)
+	/// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolHistory` (r:1 w:1)
+	/// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:0)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn drop_history() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `829`
+		//  Estimated: `3593`
+		// Minimum execution time: 38_571_000 picoseconds.
+		Weight::from_parts(39_493_000, 3593)
+			.saturating_add(RocksDbWeight::get().reads(4_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Status` (r:1 w:0)
+	/// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::AllowedRenewals` (r:1 w:1)
+	/// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`)
+	fn drop_renewal() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `525`
+		//  Estimated: `4698`
+		// Minimum execution time: 24_714_000 picoseconds.
+		Weight::from_parts(25_288_000, 4698)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x18194fcb5c1fcace44d2d0a004272614` (r:0 w:1)
+	/// Proof: UNKNOWN KEY `0x18194fcb5c1fcace44d2d0a004272614` (r:0 w:1)
+	/// The range of component `n` is `[0, 1000]`.
+	fn request_core_count(_n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 7_258_000 picoseconds.
+		Weight::from_parts(7_925_570, 0)
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: UNKNOWN KEY `0x18194fcb5c1fcace44d2d0a004272614` (r:1 w:0)
+	/// Proof: UNKNOWN KEY `0x18194fcb5c1fcace44d2d0a004272614` (r:1 w:0)
+	/// The range of component `n` is `[0, 1000]`.
+	fn process_core_count(_n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `97`
+		//  Estimated: `3562`
+		// Minimum execution time: 7_136_000 picoseconds.
+		Weight::from_parts(7_788_194, 3562)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+	}
+	/// Storage: `Broker::InstaPoolHistory` (r:0 w:1)
+	/// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	fn process_revenue() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 6_049_000 picoseconds.
+		Weight::from_parts(6_311_000, 0)
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::InstaPoolIo` (r:3 w:3)
+	/// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Reservations` (r:1 w:0)
+	/// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Leases` (r:1 w:1)
+	/// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::SaleInfo` (r:0 w:1)
+	/// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workplan` (r:0 w:10)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	/// The range of component `n` is `[0, 1000]`.
+	fn rotate_sale(n: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `6281`
+		//  Estimated: `8499`
+		// Minimum execution time: 47_504_000 picoseconds.
+		Weight::from_parts(49_778_098, 8499)
+			// Standard Error: 109
+			.saturating_add(Weight::from_parts(427, 0).saturating_mul(n.into()))
+			.saturating_add(RocksDbWeight::get().reads(5_u64))
+			.saturating_add(RocksDbWeight::get().writes(15_u64))
+	}
+	/// Storage: `Broker::InstaPoolIo` (r:1 w:0)
+	/// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::InstaPoolHistory` (r:0 w:1)
+	/// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	fn process_pool() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `180`
+		//  Estimated: `3493`
+		// Minimum execution time: 9_573_000 picoseconds.
+		Weight::from_parts(10_034_000, 3493)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: `Broker::Workplan` (r:1 w:1)
+	/// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`)
+	/// Storage: `Broker::Workload` (r:1 w:1)
+	/// Proof: `Broker::Workload` (`max_values`: None, `max_size`: Some(1212), added: 3687, mode: `MaxEncodedLen`)
+	fn process_core_schedule() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1423`
+		//  Estimated: `4681`
+		// Minimum execution time: 21_331_000 picoseconds.
+		Weight::from_parts(22_235_000, 4681)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	}
+	fn request_revenue_info_at() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 191_000 picoseconds.
+		Weight::from_parts(234_000, 0)
+	}
+}
diff --git a/substrate/frame/core-fellowship/src/lib.rs b/substrate/frame/core-fellowship/src/lib.rs
index 5eb7bf8caba0efa5733fe2aa6f276e913f1d149a..ace614d2bddb9d1fec24bb24252ebe18e8f51dec 100644
--- a/substrate/frame/core-fellowship/src/lib.rs
+++ b/substrate/frame/core-fellowship/src/lib.rs
@@ -328,7 +328,7 @@ pub mod pallet {
 		#[pallet::weight(T::WeightInfo::set_params())]
 		#[pallet::call_index(1)]
 		pub fn set_params(origin: OriginFor<T>, params: Box<ParamsOf<T, I>>) -> DispatchResult {
-			T::ParamsOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?;
+			T::ParamsOrigin::ensure_origin_or_root(origin)?;
 			Params::<T, I>::put(params.as_ref());
 			Self::deposit_event(Event::<T, I>::ParamsChanged { params: *params });
 			Ok(())
diff --git a/substrate/frame/glutton/src/lib.rs b/substrate/frame/glutton/src/lib.rs
index 5950bd676b3789c1c10b0a55aea76f6132586711..c76cc30017cf00e639c0061ff73dc6f95062ab07 100644
--- a/substrate/frame/glutton/src/lib.rs
+++ b/substrate/frame/glutton/src/lib.rs
@@ -227,7 +227,7 @@ pub mod pallet {
 			new_count: u32,
 			witness_count: Option<u32>,
 		) -> DispatchResult {
-			T::AdminOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?;
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
 
 			let current_count = TrashDataCount::<T>::get();
 			ensure!(
@@ -252,7 +252,7 @@ pub mod pallet {
 		/// Only callable by Root or `AdminOrigin`.
 		#[pallet::call_index(1)]
 		pub fn set_compute(origin: OriginFor<T>, compute: FixedU64) -> DispatchResult {
-			T::AdminOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?;
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
 
 			ensure!(compute <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
 			Compute::<T>::set(compute);
@@ -262,7 +262,7 @@ pub mod pallet {
 		}
 
 		/// Set how much of the remaining `proof_size` weight should be consumed by `on_idle`.
-		//
+		///
 		/// `1.0` means that all remaining `proof_size` will be consumed. The PoV benchmarking
 		/// results that are used here are likely an over-estimation. 100% intended consumption will
 		/// therefore translate to less than 100% actual consumption.
@@ -270,7 +270,7 @@ pub mod pallet {
 		/// Only callable by Root or `AdminOrigin`.
 		#[pallet::call_index(2)]
 		pub fn set_storage(origin: OriginFor<T>, storage: FixedU64) -> DispatchResult {
-			T::AdminOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?;
+			T::AdminOrigin::ensure_origin_or_root(origin)?;
 
 			ensure!(storage <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
 			Storage::<T>::set(storage);
diff --git a/substrate/frame/support/src/storage/mod.rs b/substrate/frame/support/src/storage/mod.rs
index 36e2c47383d193d67321e8c9f467b93fd31772d1..d52908fa366c6cc144a4027bcafade19e50fe2dd 100644
--- a/substrate/frame/support/src/storage/mod.rs
+++ b/substrate/frame/support/src/storage/mod.rs
@@ -111,6 +111,15 @@ pub trait StorageValue<T: FullCodec> {
 	/// Mutate the value
 	fn mutate<R, F: FnOnce(&mut Self::Query) -> R>(f: F) -> R;
 
+	/// Mutate the value under a key if the value already exists. Do nothing and return the default
+	/// value if not.
+	fn mutate_extant<R: Default, F: FnOnce(&mut T) -> R>(f: F) -> R {
+		Self::mutate_exists(|maybe_v| match maybe_v {
+			Some(ref mut value) => f(value),
+			None => R::default(),
+		})
+	}
+
 	/// Mutate the value if closure returns `Ok`
 	fn try_mutate<R, E, F: FnOnce(&mut Self::Query) -> Result<R, E>>(f: F) -> Result<R, E>;
 
diff --git a/substrate/frame/support/src/storage/types/value.rs b/substrate/frame/support/src/storage/types/value.rs
index 14be6bb402e3832bb170ce3fb7227fc16a250820..3c7f24715ac94026b3bea3c1aeb5eb7a775b41ed 100644
--- a/substrate/frame/support/src/storage/types/value.rs
+++ b/substrate/frame/support/src/storage/types/value.rs
@@ -135,6 +135,11 @@ where
 		<Self as crate::storage::StorageValue<Value>>::mutate(f)
 	}
 
+	/// Mutate the value under a key iff it exists. Do nothing and return the default value if not.
+	pub fn mutate_extant<R: Default, F: FnOnce(&mut Value) -> R>(f: F) -> R {
+		<Self as crate::storage::StorageValue<Value>>::mutate_extant(f)
+	}
+
 	/// Mutate the value if closure returns `Ok`
 	pub fn try_mutate<R, E, F: FnOnce(&mut QueryKind::Query) -> Result<R, E>>(
 		f: F,
diff --git a/substrate/frame/support/src/traits/dispatch.rs b/substrate/frame/support/src/traits/dispatch.rs
index 9ea58479a0dfee384d1fb34c23e7d47947c5ffb4..d0cedb708cf1d7c24d1d0a6be3ee5596a4c2df10 100644
--- a/substrate/frame/support/src/traits/dispatch.rs
+++ b/substrate/frame/support/src/traits/dispatch.rs
@@ -37,9 +37,35 @@ pub trait EnsureOrigin<OuterOrigin> {
 		Self::try_origin(o).map_err(|_| BadOrigin)
 	}
 
+	/// The same as `ensure_origin` except that Root origin will always pass. This can only be
+	/// used if `Success` has a sensible impl of `Default` since that will be used in the result.
+	fn ensure_origin_or_root(o: OuterOrigin) -> Result<Option<Self::Success>, BadOrigin>
+	where
+		OuterOrigin: OriginTrait,
+	{
+		if o.caller().is_root() {
+			return Ok(None)
+		} else {
+			Self::ensure_origin(o).map(Some)
+		}
+	}
+
 	/// Perform the origin check.
 	fn try_origin(o: OuterOrigin) -> Result<Self::Success, OuterOrigin>;
 
+	/// The same as `try_origin` except that Root origin will always pass. This can only be
+	/// used if `Success` has a sensible impl of `Default` since that will be used in the result.
+	fn try_origin_or_root(o: OuterOrigin) -> Result<Option<Self::Success>, OuterOrigin>
+	where
+		OuterOrigin: OriginTrait,
+	{
+		if o.caller().is_root() {
+			return Ok(None)
+		} else {
+			Self::try_origin(o).map(Some)
+		}
+	}
+
 	/// Attempt to get an outer origin capable of passing `try_origin` check. May return `Err` if it
 	/// is impossible.
 	///
diff --git a/substrate/frame/support/src/traits/tokens/fungible/hold.rs b/substrate/frame/support/src/traits/tokens/fungible/hold.rs
index 3a0ff62e0965a3a6e9c0feb75e970a7ca5dc0525..aa15e9df63a48f886883554687d13150c4c4a78d 100644
--- a/substrate/frame/support/src/traits/tokens/fungible/hold.rs
+++ b/substrate/frame/support/src/traits/tokens/fungible/hold.rs
@@ -52,8 +52,10 @@ pub trait Inspect<AccountId>: super::Inspect<AccountId> {
 	/// restrictions on the minimum amount of the account. Note: This cannot bring the account into
 	/// an inconsistent state with regards any required existential deposit.
 	///
-	/// Always less than `total_balance_on_hold()`.
-	fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance;
+	/// Never more than `total_balance_on_hold()`.
+	fn reducible_total_balance_on_hold(who: &AccountId, _force: Fortitude) -> Self::Balance {
+		Self::total_balance_on_hold(who)
+	}
 
 	/// Amount of funds on hold (for the given reason) of `who`.
 	fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance;
@@ -65,7 +67,9 @@ pub trait Inspect<AccountId>: super::Inspect<AccountId> {
 	/// NOTE: This does not take into account changes which could be made to the account of `who`
 	/// (such as removing a provider reference) after this call is made. Any usage of this should
 	/// therefore ensure the account is already in the appropriate state prior to calling it.
-	fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool;
+	fn hold_available(_reason: &Self::Reason, _who: &AccountId) -> bool {
+		true
+	}
 
 	/// Check to see if some `amount` of funds of `who` may be placed on hold with the given
 	/// `reason`. Reasons why this may not be true:
diff --git a/substrate/frame/support/src/traits/tokens/fungibles/hold.rs b/substrate/frame/support/src/traits/tokens/fungibles/hold.rs
index 55f9d51c6556494fb9c8da0b078e29fffeac13b8..c751a836d1f4335e406ea584f5f74c85798aef68 100644
--- a/substrate/frame/support/src/traits/tokens/fungibles/hold.rs
+++ b/substrate/frame/support/src/traits/tokens/fungibles/hold.rs
@@ -52,12 +52,14 @@ pub trait Inspect<AccountId>: super::Inspect<AccountId> {
 	/// restrictions on the minimum amount of the account. Note: This cannot bring the account into
 	/// an inconsistent state with regards any required existential deposit.
 	///
-	/// Always less than `total_balance_on_hold()`.
+	/// Never more than `total_balance_on_hold()`.
 	fn reducible_total_balance_on_hold(
 		asset: Self::AssetId,
 		who: &AccountId,
-		force: Fortitude,
-	) -> Self::Balance;
+		_force: Fortitude,
+	) -> Self::Balance {
+		Self::total_balance_on_hold(asset, who)
+	}
 
 	/// Amount of funds on hold (for the given reason) of `who`.
 	fn balance_on_hold(
@@ -73,7 +75,9 @@ pub trait Inspect<AccountId>: super::Inspect<AccountId> {
 	/// NOTE: This does not take into account changes which could be made to the account of `who`
 	/// (such as removing a provider reference) after this call is made. Any usage of this should
 	/// therefore ensure the account is already in the appropriate state prior to calling it.
-	fn hold_available(asset: Self::AssetId, reason: &Self::Reason, who: &AccountId) -> bool;
+	fn hold_available(_asset: Self::AssetId, _reason: &Self::Reason, _who: &AccountId) -> bool {
+		true
+	}
 
 	/// Check to see if some `amount` of funds of `who` may be placed on hold with the given
 	/// `reason`. Reasons why this may not be true:
diff --git a/substrate/primitives/arithmetic/src/fixed_point.rs b/substrate/primitives/arithmetic/src/fixed_point.rs
index d3e75f6f781cdb9319c1298ed4458248f2de93b8..ce14d2957b5e216325a2af02fbb1d68af2b756bb 100644
--- a/substrate/primitives/arithmetic/src/fixed_point.rs
+++ b/substrate/primitives/arithmetic/src/fixed_point.rs
@@ -45,7 +45,7 @@ pub trait FixedPointOperand:
 	+ Bounded
 	+ Zero
 	+ Saturating
-	+ PartialOrd
+	+ PartialOrd<Self>
 	+ UniqueSaturatedInto<u128>
 	+ TryFrom<u128>
 	+ CheckedNeg
@@ -58,7 +58,7 @@ impl<T> FixedPointOperand for T where
 		+ Bounded
 		+ Zero
 		+ Saturating
-		+ PartialOrd
+		+ PartialOrd<Self>
 		+ UniqueSaturatedInto<u128>
 		+ TryFrom<u128>
 		+ CheckedNeg