Frame: Agile Coretime Broker pallet (RFC-1) (#14568)

* Add Broker pallet

* Flesh out CorePart

* Repotting and fleshing out

* more drafting

* process timeslice

* Test Fungibles completed

* Auctions

* Price morphing

* First tests

* Tidying up config/status

* Docs

* Timeslice todying

* More Timeslice tidying

* Tests]

* Repotting.

* Tests

* Tests

* System InstaPool cores and payout

* Better Relay Test framework

* Tests and instapool fixes

* Support NFT interface

* Proper renewals

* Better events, results

* Test transfer

* Renewal test

* Repot some impls and make dispatchables.

* Better weight

* Test migration

* Document events

* Introduce durations

* Core count

* Allow reassignment

* Better naming

* Error docs

* Docs

* Formatting

* Advance notice period is in RC blocks, not timeslices

* Docs

* Formatting

* Docs

* Missing file

* Added some events

* Events for all dispatchables

* Remove benchmark

* Fix

* Adds benchmark for configure and some basic setup

* Adds benchmark for reserve and unreserve

* Adds a couple of more benchmarks

* Docs

* Event

* Fix

* Adds benchmark for purchase

* Dedup

* Add some weight breakdowns

* Repotting

* Adds more benchmarks

* Renaming and one more event

* Sale event

* Better price API and docs

* Avoid possibility of clobbering renewal record

* Avoid possibility of clobbering renewal record

* Fixes a few benchmarks

* Another test

* More tests

* Drop history test

* Rename and CORE_MASK_BITS constant

* Update frame/broker/src/dispatchable_impls.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/broker/src/dispatchable_impls.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/broker/src/dispatchable_impls.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/broker/src/utility_impls.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/broker/src/dispatchable_impls.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Update frame/broker/src/mock.rs

Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Addresses few review comments

* Addresses few review comments

* Addresses few review comments

* Merge

* Merge

* ".git/.scripts/commands/fmt/fmt.sh"

* Integrates broker in kitchensink

* Minor update

* Fixes typo

* Moves balance back to u64

* Fixes kitchensink build

* Fixes worst case for assign

* Adds benchmark for process_core_count

* Adds a couple of more benchmarks

* Adds an assert for partition

* Uses max_timeslices as input in claim_revenue benchmark

* Adds benchmark for drop_renewal

* Adds benchmark for process_core_schedule

* Adds benchmark for process_pool

* Adds assertion for transfer

* Fixes benchmark for broker in kitchensink

* Adds todo for process_revenue benchmark

* Minor update

* Fix for pool revenue history

* remove TODOs

* Fix tests

* Document CoretimeInterface

* rename part to mask

* Fixes

* Grumble

* ".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_broker

* Adds benchmark for drop_history and fixes worst case for claim_revenue

* Adds drop_history in WeightInfo

* ".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_broker

* Minor fix for Quick Benchmark CI

* Fixes

* Docs

* Headers

* Expose a couple of APIs for benchmarking (#14688)

* Expose a couple of APIs for benchmarking

* Adds doc

* Minor fix in CoretimeInterface impl for kitchensik

* Minor

* Cap renewal price

* Adds a few tests

* Adds more tests

* Minor updates

* Adds a test for an edge case

* Fixes feature propagation

* Fixes feature propagation

* Adds doc fix

* Syntax nits

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Reuse Bit assign functions

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Bitwise tests

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* adapt_price: Edge case for sold == target

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add sanity checking to ConfigRecord

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Add deny(missing_docs) where possible

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* partition: forbid pivot_offset == 0

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Sort features

zepter format features

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Import Zero from new location

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Clippy: remove redundant clone

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* try to fix build

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

* Fix CI

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Nikhil Gupta <17176722+gupnik@users.noreply.github.com>
Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: command-bot <>
This commit is contained in:
Gavin Wood
2023-08-24 22:37:20 +02:00
committed by GitHub
parent bc2b3d9d28
commit 46bd466e48
29 changed files with 5884 additions and 18 deletions
+18
View File
@@ -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"
+1
View File
@@ -131,6 +131,7 @@ members = [
"frame/benchmarking",
"frame/benchmarking/pov",
"frame/bounties",
"frame/broker",
"frame/child-bounties",
"frame/collective",
"frame/contracts",
+4
View File
@@ -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",
+75 -1
View File
@@ -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]
@@ -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 ||
+56
View File
@@ -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",
]
+26
View File
@@ -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 -/
```
+84
View File
@@ -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());
}
}
}
}
}
}
+858
View File
@@ -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);
}
+227
View File
@@ -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));
}
}
@@ -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) {}
}
@@ -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(())
}
}
+784
View File
@@ -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(())
}
}
}
+322
View File
@@ -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)
}
@@ -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)
}
}
@@ -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,
{
}
+896
View File
@@ -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);
});
}
+326
View File
@@ -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 });
}
}
+290
View File
@@ -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(())
}
}
+121
View File
@@ -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)))
}
}
+794
View File
@@ -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)
}
}
+1 -1
View File
@@ -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(())
+4 -4
View File
@@ -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);
@@ -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>;
@@ -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,
@@ -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.
///
@@ -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:
@@ -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:
@@ -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