mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 08:21:05 +00:00
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:
Generated
+18
@@ -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"
|
||||
|
||||
@@ -131,6 +131,7 @@ members = [
|
||||
"frame/benchmarking",
|
||||
"frame/benchmarking/pov",
|
||||
"frame/bounties",
|
||||
"frame/broker",
|
||||
"frame/child-bounties",
|
||||
"frame/collective",
|
||||
"frame/contracts",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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 -/
|
||||
```
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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(®ion_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(®ion_id, ®ion);
|
||||
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(®ion_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, ®ion);
|
||||
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(®ion_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, ®ion);
|
||||
let other = RegionId { mask: region_id.mask ^ pivot, ..region_id };
|
||||
Regions::<T>::insert(&other, ®ion);
|
||||
|
||||
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(®ion_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(®ion_id).ok_or(Error::<T>::UnknownRegion)?;
|
||||
ensure!(status.last_committed_timeslice >= region.end, Error::<T>::StillValid);
|
||||
|
||||
Regions::<T>::remove(®ion_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(®ion_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(())
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
{
|
||||
}
|
||||
@@ -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(®ion.into(), &2));
|
||||
assert_eq!(<Broker as NftInspect<_>>::owner(®ion.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);
|
||||
});
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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(®ion_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(®ion_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(®ion_id, ®ion);
|
||||
}
|
||||
|
||||
Ok(Some((region_id, region)))
|
||||
}
|
||||
}
|
||||
Generated
+794
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user