mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-20 06:55:42 +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-bags-list",
|
||||||
"pallet-balances",
|
"pallet-balances",
|
||||||
"pallet-bounties",
|
"pallet-bounties",
|
||||||
|
"pallet-broker",
|
||||||
"pallet-child-bounties",
|
"pallet-child-bounties",
|
||||||
"pallet-collective",
|
"pallet-collective",
|
||||||
"pallet-contracts",
|
"pallet-contracts",
|
||||||
@@ -6436,6 +6437,23 @@ dependencies = [
|
|||||||
"sp-std",
|
"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]]
|
[[package]]
|
||||||
name = "pallet-child-bounties"
|
name = "pallet-child-bounties"
|
||||||
version = "4.0.0-dev"
|
version = "4.0.0-dev"
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ members = [
|
|||||||
"frame/benchmarking",
|
"frame/benchmarking",
|
||||||
"frame/benchmarking/pov",
|
"frame/benchmarking/pov",
|
||||||
"frame/bounties",
|
"frame/bounties",
|
||||||
|
"frame/broker",
|
||||||
"frame/child-bounties",
|
"frame/child-bounties",
|
||||||
"frame/collective",
|
"frame/collective",
|
||||||
"frame/contracts",
|
"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-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-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-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-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-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" }
|
pallet-contracts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts" }
|
||||||
@@ -160,6 +161,7 @@ std = [
|
|||||||
"pallet-bags-list/std",
|
"pallet-bags-list/std",
|
||||||
"pallet-balances/std",
|
"pallet-balances/std",
|
||||||
"pallet-bounties/std",
|
"pallet-bounties/std",
|
||||||
|
"pallet-broker/std",
|
||||||
"pallet-child-bounties/std",
|
"pallet-child-bounties/std",
|
||||||
"pallet-collective/std",
|
"pallet-collective/std",
|
||||||
"pallet-contracts-primitives/std",
|
"pallet-contracts-primitives/std",
|
||||||
@@ -254,6 +256,7 @@ runtime-benchmarks = [
|
|||||||
"pallet-bags-list/runtime-benchmarks",
|
"pallet-bags-list/runtime-benchmarks",
|
||||||
"pallet-balances/runtime-benchmarks",
|
"pallet-balances/runtime-benchmarks",
|
||||||
"pallet-bounties/runtime-benchmarks",
|
"pallet-bounties/runtime-benchmarks",
|
||||||
|
"pallet-broker/runtime-benchmarks",
|
||||||
"pallet-child-bounties/runtime-benchmarks",
|
"pallet-child-bounties/runtime-benchmarks",
|
||||||
"pallet-collective/runtime-benchmarks",
|
"pallet-collective/runtime-benchmarks",
|
||||||
"pallet-contracts/runtime-benchmarks",
|
"pallet-contracts/runtime-benchmarks",
|
||||||
@@ -324,6 +327,7 @@ try-runtime = [
|
|||||||
"pallet-bags-list/try-runtime",
|
"pallet-bags-list/try-runtime",
|
||||||
"pallet-balances/try-runtime",
|
"pallet-balances/try-runtime",
|
||||||
"pallet-bounties/try-runtime",
|
"pallet-bounties/try-runtime",
|
||||||
|
"pallet-broker/try-runtime",
|
||||||
"pallet-child-bounties/try-runtime",
|
"pallet-child-bounties/try-runtime",
|
||||||
"pallet-collective/try-runtime",
|
"pallet-collective/try-runtime",
|
||||||
"pallet-contracts/try-runtime",
|
"pallet-contracts/try-runtime",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ use frame_support::{
|
|||||||
pallet_prelude::Get,
|
pallet_prelude::Get,
|
||||||
parameter_types,
|
parameter_types,
|
||||||
traits::{
|
traits::{
|
||||||
fungible::ItemOf,
|
fungible::{Balanced, Credit, ItemOf},
|
||||||
tokens::{nonfungibles_v2::Inspect, GetSalary, PayFromAccount},
|
tokens::{nonfungibles_v2::Inspect, GetSalary, PayFromAccount},
|
||||||
AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse,
|
AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse,
|
||||||
EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem,
|
EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem,
|
||||||
@@ -56,6 +56,7 @@ use frame_system::{
|
|||||||
pub use node_primitives::{AccountId, Signature};
|
pub use node_primitives::{AccountId, Signature};
|
||||||
use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Moment, Nonce};
|
use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Moment, Nonce};
|
||||||
use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter};
|
use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter};
|
||||||
|
use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600};
|
||||||
use pallet_election_provider_multi_phase::SolutionAccuracyOf;
|
use pallet_election_provider_multi_phase::SolutionAccuracyOf;
|
||||||
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
|
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
|
||||||
use pallet_nfts::PalletFeatures;
|
use pallet_nfts::PalletFeatures;
|
||||||
@@ -1877,6 +1878,77 @@ impl pallet_statement::Config for Runtime {
|
|||||||
type MaxAllowedBytes = MaxAllowedBytes;
|
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!(
|
construct_runtime!(
|
||||||
pub struct Runtime
|
pub struct Runtime
|
||||||
{
|
{
|
||||||
@@ -1950,6 +2022,7 @@ construct_runtime!(
|
|||||||
MessageQueue: pallet_message_queue,
|
MessageQueue: pallet_message_queue,
|
||||||
Pov: frame_benchmarking_pallet_pov,
|
Pov: frame_benchmarking_pallet_pov,
|
||||||
Statement: pallet_statement,
|
Statement: pallet_statement,
|
||||||
|
Broker: pallet_broker,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2030,6 +2103,7 @@ mod benches {
|
|||||||
[pallet_bags_list, VoterList]
|
[pallet_bags_list, VoterList]
|
||||||
[pallet_balances, Balances]
|
[pallet_balances, Balances]
|
||||||
[pallet_bounties, Bounties]
|
[pallet_bounties, Bounties]
|
||||||
|
[pallet_broker, Broker]
|
||||||
[pallet_child_bounties, ChildBounties]
|
[pallet_child_bounties, ChildBounties]
|
||||||
[pallet_collective, Council]
|
[pallet_collective, Council]
|
||||||
[pallet_conviction_voting, ConvictionVoting]
|
[pallet_conviction_voting, ConvictionVoting]
|
||||||
|
|||||||
@@ -546,9 +546,7 @@ impl<N: Ord> Peers<N> {
|
|||||||
who: &PeerId,
|
who: &PeerId,
|
||||||
update: NeighborPacket<N>,
|
update: NeighborPacket<N>,
|
||||||
) -> Result<Option<&View<N>>, Misbehavior> {
|
) -> Result<Option<&View<N>>, Misbehavior> {
|
||||||
let Some(peer) = self.inner.get_mut(who) else {
|
let Some(peer) = self.inner.get_mut(who) else { return Ok(None) };
|
||||||
return Ok(None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let invalid_change = peer.view.set_id > update.set_id ||
|
let invalid_change = peer.view.set_id > update.set_id ||
|
||||||
peer.view.round > update.round && 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::weight(T::WeightInfo::set_params())]
|
||||||
#[pallet::call_index(1)]
|
#[pallet::call_index(1)]
|
||||||
pub fn set_params(origin: OriginFor<T>, params: Box<ParamsOf<T, I>>) -> DispatchResult {
|
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());
|
Params::<T, I>::put(params.as_ref());
|
||||||
Self::deposit_event(Event::<T, I>::ParamsChanged { params: *params });
|
Self::deposit_event(Event::<T, I>::ParamsChanged { params: *params });
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ pub mod pallet {
|
|||||||
new_count: u32,
|
new_count: u32,
|
||||||
witness_count: Option<u32>,
|
witness_count: Option<u32>,
|
||||||
) -> DispatchResult {
|
) -> 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();
|
let current_count = TrashDataCount::<T>::get();
|
||||||
ensure!(
|
ensure!(
|
||||||
@@ -252,7 +252,7 @@ pub mod pallet {
|
|||||||
/// Only callable by Root or `AdminOrigin`.
|
/// Only callable by Root or `AdminOrigin`.
|
||||||
#[pallet::call_index(1)]
|
#[pallet::call_index(1)]
|
||||||
pub fn set_compute(origin: OriginFor<T>, compute: FixedU64) -> DispatchResult {
|
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);
|
ensure!(compute <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
|
||||||
Compute::<T>::set(compute);
|
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`.
|
/// 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
|
/// `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
|
/// results that are used here are likely an over-estimation. 100% intended consumption will
|
||||||
/// therefore translate to less than 100% actual consumption.
|
/// therefore translate to less than 100% actual consumption.
|
||||||
@@ -270,7 +270,7 @@ pub mod pallet {
|
|||||||
/// Only callable by Root or `AdminOrigin`.
|
/// Only callable by Root or `AdminOrigin`.
|
||||||
#[pallet::call_index(2)]
|
#[pallet::call_index(2)]
|
||||||
pub fn set_storage(origin: OriginFor<T>, storage: FixedU64) -> DispatchResult {
|
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);
|
ensure!(storage <= RESOURCE_HARD_LIMIT, Error::<T>::InsaneLimit);
|
||||||
Storage::<T>::set(storage);
|
Storage::<T>::set(storage);
|
||||||
|
|||||||
@@ -111,6 +111,15 @@ pub trait StorageValue<T: FullCodec> {
|
|||||||
/// Mutate the value
|
/// Mutate the value
|
||||||
fn mutate<R, F: FnOnce(&mut Self::Query) -> R>(f: F) -> R;
|
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`
|
/// 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>;
|
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)
|
<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`
|
/// Mutate the value if closure returns `Ok`
|
||||||
pub fn try_mutate<R, E, F: FnOnce(&mut QueryKind::Query) -> Result<R, E>>(
|
pub fn try_mutate<R, E, F: FnOnce(&mut QueryKind::Query) -> Result<R, E>>(
|
||||||
f: F,
|
f: F,
|
||||||
|
|||||||
@@ -37,9 +37,35 @@ pub trait EnsureOrigin<OuterOrigin> {
|
|||||||
Self::try_origin(o).map_err(|_| BadOrigin)
|
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.
|
/// Perform the origin check.
|
||||||
fn try_origin(o: OuterOrigin) -> Result<Self::Success, OuterOrigin>;
|
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
|
/// Attempt to get an outer origin capable of passing `try_origin` check. May return `Err` if it
|
||||||
/// is impossible.
|
/// 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
|
/// restrictions on the minimum amount of the account. Note: This cannot bring the account into
|
||||||
/// an inconsistent state with regards any required existential deposit.
|
/// 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(who: &AccountId, force: Fortitude) -> Self::Balance;
|
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`.
|
/// Amount of funds on hold (for the given reason) of `who`.
|
||||||
fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance;
|
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`
|
/// 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
|
/// (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.
|
/// 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
|
/// 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:
|
/// `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
|
/// restrictions on the minimum amount of the account. Note: This cannot bring the account into
|
||||||
/// an inconsistent state with regards any required existential deposit.
|
/// 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(
|
fn reducible_total_balance_on_hold(
|
||||||
asset: Self::AssetId,
|
asset: Self::AssetId,
|
||||||
who: &AccountId,
|
who: &AccountId,
|
||||||
force: Fortitude,
|
_force: Fortitude,
|
||||||
) -> Self::Balance;
|
) -> Self::Balance {
|
||||||
|
Self::total_balance_on_hold(asset, who)
|
||||||
|
}
|
||||||
|
|
||||||
/// Amount of funds on hold (for the given reason) of `who`.
|
/// Amount of funds on hold (for the given reason) of `who`.
|
||||||
fn balance_on_hold(
|
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`
|
/// 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
|
/// (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.
|
/// 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
|
/// 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:
|
/// `reason`. Reasons why this may not be true:
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ pub trait FixedPointOperand:
|
|||||||
+ Bounded
|
+ Bounded
|
||||||
+ Zero
|
+ Zero
|
||||||
+ Saturating
|
+ Saturating
|
||||||
+ PartialOrd
|
+ PartialOrd<Self>
|
||||||
+ UniqueSaturatedInto<u128>
|
+ UniqueSaturatedInto<u128>
|
||||||
+ TryFrom<u128>
|
+ TryFrom<u128>
|
||||||
+ CheckedNeg
|
+ CheckedNeg
|
||||||
@@ -58,7 +58,7 @@ impl<T> FixedPointOperand for T where
|
|||||||
+ Bounded
|
+ Bounded
|
||||||
+ Zero
|
+ Zero
|
||||||
+ Saturating
|
+ Saturating
|
||||||
+ PartialOrd
|
+ PartialOrd<Self>
|
||||||
+ UniqueSaturatedInto<u128>
|
+ UniqueSaturatedInto<u128>
|
||||||
+ TryFrom<u128>
|
+ TryFrom<u128>
|
||||||
+ CheckedNeg
|
+ CheckedNeg
|
||||||
|
|||||||
Reference in New Issue
Block a user