// This file is part of Bizinikiwi. // 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, Pezpallet as Broker}; use alloc::{vec, vec::Vec}; use pezframe_benchmarking::v2::*; use pezframe_support::{ storage::bounded_vec::BoundedVec, traits::{ fungible::{Inspect, Mutate}, EnsureOrigin, Hooks, }, }; use pezframe_system::{Pezpallet as System, RawOrigin}; use pezsp_arithmetic::{FixedU64, Perbill}; use pezsp_core::Get; use pezsp_runtime::{ traits::{BlockNumberProvider, MaybeConvert}, FixedPointNumber, Saturating, }; const SEED: u32 = 0; const MAX_CORE_COUNT: u16 = 1_000; fn assert_last_event(generic_event: ::RuntimeEvent) { pezframe_system::Pezpallet::::assert_last_event(generic_event.into()); } fn assert_has_event(generic_event: ::RuntimeEvent) { pezframe_system::Pezpallet::::assert_has_event(generic_event.into()); } fn new_config_record() -> ConfigRecordOf { 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(n: u32) { let schedule = new_schedule(); Reservations::::put(BoundedVec::try_from(vec![schedule.clone(); n as usize]).unwrap()); } fn setup_leases(n: u32, task: u32, until: u32) { Leases::::put( BoundedVec::try_from(vec![LeaseRecordItem { task, until: until.into() }; n as usize]) .unwrap(), ); } fn advance_to(b: u32) { while System::::block_number() < b.into() { System::::set_block_number(System::::block_number().saturating_add(1u32.into())); let block_number: u32 = System::::block_number().try_into().ok().unwrap(); RCBlockNumberProviderOf::::set_block_number(block_number.into()); Broker::::on_initialize(System::::block_number()); } } struct StartedSale { start_price: Balance, end_price: Balance, first_core: CoreIndex, } fn setup_and_start_sale() -> Result>, BenchmarkError> { Configuration::::put(new_config_record::()); // Assume Reservations to be filled for worst case setup_reservations::(T::MaxReservedCores::get()); // Assume Leases to be filled for worst case setup_leases::(T::MaxLeasedCores::get(), 1, 10); let initial_price = 10_000_000u32.into(); let (start_price, end_price) = get_start_end_price::(initial_price); Broker::::do_start_sales(initial_price, MAX_CORE_COUNT.into()) .map_err(|_| BenchmarkError::Weightless)?; let sale_data = StartedSale { start_price, end_price, first_core: T::MaxReservedCores::get() .saturating_add(T::MaxLeasedCores::get()) .try_into() .unwrap(), }; Ok(sale_data) } fn get_start_end_price(initial_price: BalanceOf) -> (BalanceOf, BalanceOf) { let end_price = ::PriceAdapter::adapt_price(SalePerformance { sellout_price: None, end_price: initial_price, ideal_cores_sold: 0, cores_offered: 0, cores_sold: 0, }) .end_price; let start_price = ::PriceAdapter::leadin_factor_at(FixedU64::from(0)) .saturating_mul_int(end_price); (start_price, end_price) } #[benchmarks] mod benches { use super::*; use crate::Finality::*; #[benchmark] fn configure() -> Result<(), BenchmarkError> { let config = new_config_record::(); let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, config.clone()); assert_eq!(Configuration::::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::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::::get().len(), T::MaxReservedCores::get() as usize); Ok(()) } #[benchmark] fn unreserve() -> Result<(), BenchmarkError> { // Assume Reservations to be filled for worst case setup_reservations::(T::MaxReservedCores::get()); let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, 0); assert_eq!( Reservations::::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::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::::get().len(), T::MaxLeasedCores::get() as usize); Ok(()) } #[benchmark] fn remove_lease() -> Result<(), BenchmarkError> { let task = 1u32; let until = 10u32; // Assume Leases to be almost filled for worst case let mut leases = vec![ LeaseRecordItem { task, until }; T::MaxLeasedCores::get().saturating_sub(1) as usize ]; let task = 2u32; leases.push(LeaseRecordItem { task, until }); Leases::::put(BoundedVec::try_from(leases).unwrap()); let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, task); assert_eq!(Leases::::get().len(), T::MaxLeasedCores::get().saturating_sub(1) as usize); Ok(()) } #[benchmark] fn start_sales(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> { let config = new_config_record::(); Configuration::::put(config.clone()); let mut extra_cores = n; // Assume Reservations to be filled for worst case setup_reservations::(extra_cores.min(T::MaxReservedCores::get())); extra_cores = extra_cores.saturating_sub(T::MaxReservedCores::get()); // Assume Leases to be filled for worst case setup_leases::(extra_cores.min(T::MaxLeasedCores::get()), 1, 10); extra_cores = extra_cores.saturating_sub(T::MaxLeasedCores::get()); let latest_region_begin = Broker::::latest_timeslice_ready_to_commit(&config); let initial_price = 10_000_000u32.into(); let (start_price, end_price) = get_start_end_price::(initial_price); let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, initial_price, extra_cores.try_into().unwrap()); assert!(SaleInfo::::get().is_some()); let sale_start = RCBlockNumberProviderOf::::current_block_number() + config.interlude_length; assert_last_event::( Event::SaleInitialized { sale_start, leadin_length: 1u32.into(), start_price, end_price, region_begin: latest_region_begin + config.region_length, region_end: latest_region_begin + config.region_length * 2, 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 sale_data = setup_and_start_sale::()?; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), sale_data.start_price); assert_eq!(SaleInfo::::get().unwrap().sellout_price.unwrap(), sale_data.end_price); assert_last_event::( Event::Purchased { who: caller, region_id: RegionId { begin: SaleInfo::::get().unwrap().region_begin, core: sale_data.first_core, mask: CoreMask::complete(), }, price: sale_data.end_price, duration: 3u32.into(), } .into(), ); Ok(()) } #[benchmark] fn renew() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let region_len = Configuration::::get().unwrap().region_length; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, 1001, Final) .map_err(|_| BenchmarkError::Weightless)?; advance_to::((T::TimeslicePeriod::get() * region_len.into()).try_into().ok().unwrap()); #[extrinsic_call] _(RawOrigin::Signed(caller), region.core); let id = PotentialRenewalId { core: region.core, when: region.begin + region_len * 2 }; assert!(PotentialRenewals::::get(id).is_some()); Ok(()) } #[benchmark] fn transfer() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), region, recipient.clone()); assert_last_event::( Event::Transferred { region_id: region, old_owner: Some(caller), owner: Some(recipient), duration: 3u32.into(), } .into(), ); Ok(()) } #[benchmark] fn partition() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); // Worst case has an existing provisional pool assignment. Broker::::do_pool(region, None, caller.clone(), Finality::Provisional) .map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(RawOrigin::Signed(caller), region, 2); assert_last_event::( Event::Partitioned { old_region_id: RegionId { begin: region.begin, core: sale_data.first_core, mask: CoreMask::complete(), }, new_region_ids: ( RegionId { begin: region.begin, core: sale_data.first_core, mask: CoreMask::complete(), }, RegionId { begin: region.begin + 2, core: sale_data.first_core, mask: CoreMask::complete(), }, ), } .into(), ); Ok(()) } #[benchmark] fn interlace() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let core = sale_data.first_core; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); // Worst case has an existing provisional pool assignment. Broker::::do_pool(region, None, caller.clone(), Finality::Provisional) .map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(RawOrigin::Signed(caller), region, 0x00000_fffff_fffff_00000.into()); assert_last_event::( Event::Interlaced { old_region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, new_region_ids: ( RegionId { begin: region.begin, core, mask: 0x00000_fffff_fffff_00000.into() }, RegionId { begin: region.begin, core, mask: CoreMask::complete() ^ 0x00000_fffff_fffff_00000.into(), }, ), } .into(), ); Ok(()) } #[benchmark] fn assign() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let core = sale_data.first_core; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); // Worst case has an existing provisional pool assignment. Broker::::do_pool(region, None, caller.clone(), Finality::Provisional) .map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(RawOrigin::Signed(caller), region, 1000, Provisional); let workplan_key = (region.begin, region.core); assert!(Workplan::::get(workplan_key).is_some()); assert!(Regions::::get(region).is_some()); assert_last_event::( Event::Assigned { region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, task: 1000, duration: 3u32.into(), } .into(), ); Ok(()) } #[benchmark] fn pool() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let core = sale_data.first_core; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); 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::::get(workplan_key).is_some()); assert_last_event::( Event::Pooled { region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, duration: 3u32.into(), } .into(), ); Ok(()) } #[benchmark] fn claim_revenue( m: Linear<1, { new_config_record::().region_length }>, ) -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let core = sale_data.first_core; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); T::Currency::set_balance( &Broker::::account_id(), T::Currency::minimum_balance().saturating_add(200_000_000u32.into()), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); T::Currency::set_balance(&recipient.clone(), T::Currency::minimum_balance()); Broker::::do_pool(region, None, recipient.clone(), Final) .map_err(|_| BenchmarkError::Weightless)?; let revenue = 10_000_000u32.into(); InstaPoolHistory::::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::::get(region.begin).is_none()); assert_last_event::( Event::RevenueClaimPaid { who: recipient, amount: 200_000_000u32.into(), next: if m < new_config_record::().region_length { Some(RegionId { begin: region.begin.saturating_add(m), core, mask: CoreMask::complete(), }) } else { None }, } .into(), ); Ok(()) } #[benchmark] fn purchase_credit() -> Result<(), BenchmarkError> { setup_and_start_sale::()?; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(T::MinimumCreditPurchase::get()), ); T::Currency::set_balance(&Broker::::account_id(), T::Currency::minimum_balance()); let beneficiary: RelayAccountIdOf = account("beneficiary", 0, SEED); #[extrinsic_call] _(RawOrigin::Signed(caller.clone()), T::MinimumCreditPurchase::get(), beneficiary.clone()); assert_last_event::( Event::CreditPurchased { who: caller, beneficiary, amount: T::MinimumCreditPurchase::get(), } .into(), ); Ok(()) } #[benchmark] fn drop_region() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let core = sale_data.first_core; let region_len = Configuration::::get().unwrap().region_length; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); advance_to::( (T::TimeslicePeriod::get() * (region_len * 4).into()).try_into().ok().unwrap(), ); #[extrinsic_call] _(RawOrigin::Signed(caller), region); assert_last_event::( Event::RegionDropped { region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, duration: 3u32.into(), } .into(), ); Ok(()) } #[benchmark] fn drop_contribution() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let core = sale_data.first_core; let region_len = Configuration::::get().unwrap().region_length; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); Broker::::do_pool(region, None, recipient, Final) .map_err(|_| BenchmarkError::Weightless)?; advance_to::( (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(), ); #[extrinsic_call] _(RawOrigin::Signed(caller), region); assert_last_event::( Event::ContributionDropped { region_id: RegionId { begin: region.begin, core, mask: CoreMask::complete() }, } .into(), ); Ok(()) } #[benchmark] fn drop_history() -> Result<(), BenchmarkError> { setup_and_start_sale::()?; let when = 5u32.into(); let revenue = 10_000_000u32.into(); let region_len = Configuration::::get().unwrap().region_length; advance_to::( (T::TimeslicePeriod::get() * (region_len * 8).into()).try_into().ok().unwrap(), ); let caller: T::AccountId = whitelisted_caller(); InstaPoolHistory::::insert( when, InstaPoolHistoryRecord { private_contributions: 4u32.into(), system_contributions: 3u32.into(), maybe_payout: Some(revenue), }, ); #[extrinsic_call] _(RawOrigin::Signed(caller), when); assert!(InstaPoolHistory::::get(when).is_none()); assert_last_event::(Event::HistoryDropped { when, revenue }.into()); Ok(()) } #[benchmark] fn drop_renewal() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let core = sale_data.first_core; let when = 5u32.into(); let region_len = Configuration::::get().unwrap().region_length; advance_to::( (T::TimeslicePeriod::get() * (region_len * 3).into()).try_into().ok().unwrap(), ); let id = PotentialRenewalId { core, when }; let record = PotentialRenewalRecord { price: 1_000_000u32.into(), completion: CompletionStatus::Complete(new_schedule()), }; PotentialRenewals::::insert(id, record); let caller: T::AccountId = whitelisted_caller(); #[extrinsic_call] _(RawOrigin::Signed(caller), core, when); assert!(PotentialRenewals::::get(id).is_none()); assert_last_event::(Event::PotentialRenewalDropped { 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::( 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::()?; let core_count = n.try_into().unwrap(); CoreCountInbox::::put(core_count); let mut status = Status::::get().ok_or(BenchmarkError::Weightless)?; #[block] { Broker::::process_core_count(&mut status); } assert_last_event::(Event::CoreCountChanged { core_count }.into()); Ok(()) } #[benchmark] fn process_revenue() -> Result<(), BenchmarkError> { setup_and_start_sale::()?; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(30_000_000u32.into()), ); T::Currency::set_balance( &Broker::::account_id(), T::Currency::minimum_balance().saturating_add(90_000_000u32.into()), ); let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap(); let multiplicator = 5; RevenueInbox::::put(OnDemandRevenueRecord { until: (timeslice_period * multiplicator).into(), amount: 10_000_000u32.into(), }); let timeslice = multiplicator - 1; InstaPoolHistory::::insert( timeslice, InstaPoolHistoryRecord { private_contributions: 4u32.into(), system_contributions: 6u32.into(), maybe_payout: None, }, ); #[block] { Broker::::process_revenue(); } assert_last_event::( Event::ClaimsReady { when: timeslice.into(), system_payout: 6_000_000u32.into(), private_payout: 4_000_000u32.into(), } .into(), ); Ok(()) } #[benchmark] fn rotate_sale(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> { let config = new_config_record::(); Configuration::::put(config.clone()); // Ensure there is one buyable core then use the rest to max out reservations and leases, if // possible for worst case. // First allocate up to MaxReservedCores for reservations let n_reservations = T::MaxReservedCores::get().min(n.saturating_sub(1)); setup_reservations::(n_reservations); // Then allocate remaining cores to leases, up to MaxLeasedCores let n_leases = T::MaxLeasedCores::get().min(n.saturating_sub(1).saturating_sub(n_reservations)); setup_leases::(n_leases, 1, 20); // Start sales so we can test the auto-renewals. let initial_price = 10_000_000u32.into(); let (start_price, _) = get_start_end_price::(initial_price); Broker::::do_start_sales( initial_price, n.saturating_sub(n_reservations) .saturating_sub(n_leases) .try_into() .expect("Upper limit of n is a u16."), ) .expect("Configuration was initialized before; qed"); // Advance to the fixed price period. advance_to::(2); // Assume max auto renewals for worst case. This is between 1 and the value of // MaxAutoRenewals. let n_renewable = T::MaxAutoRenewals::get() .min(n.saturating_sub(n_leases).saturating_sub(n_reservations)); let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap(); let sale = SaleInfo::::get().expect("Sale has started."); let now = RCBlockNumberProviderOf::::current_block_number(); let price = Broker::::sale_price(&sale, now); (0..n_renewable.into()).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance() .saturating_add(start_price) .saturating_add(start_price), ); let region = Broker::::do_purchase(caller.clone(), start_price) .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; Broker::::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; // Advance to the block before the rotate_sale in which the auto-renewals will take place. let rotate_block = timeslice_period.saturating_mul(config.region_length) - 2; advance_to::(rotate_block - 1); // Advance one block and manually tick so we can isolate the `rotate_sale` call. System::::set_block_number(rotate_block.into()); RCBlockNumberProviderOf::::set_block_number(rotate_block.into()); let mut status = Status::::get().expect("Sale has started."); let sale = SaleInfo::::get().expect("Sale has started."); Broker::::process_core_count(&mut status); Broker::::process_revenue(); status.last_committed_timeslice = config.region_length; #[block] { Broker::::rotate_sale(sale.clone(), &config, &status); } // Get prices from the actual price adapter. let new_prices = T::PriceAdapter::adapt_price(SalePerformance::from_sale(&sale)); let new_sale = SaleInfo::::get().expect("Sale has started."); let now = RCBlockNumberProviderOf::::current_block_number(); let sale_start = config.interlude_length.saturating_add(rotate_block.into()); assert_has_event::( Event::SaleInitialized { sale_start, leadin_length: 1u32.into(), start_price: Broker::::sale_price(&new_sale, now), end_price: new_prices.end_price, region_begin: sale.region_begin + config.region_length, region_end: sale.region_end + config.region_length, ideal_cores_sold: 0, cores_offered: n .saturating_sub(n_reservations) .saturating_sub(n_leases) .try_into() .unwrap(), } .into(), ); // Make sure all cores got renewed: (0..n_renewable).for_each(|indx| { let task = 1000 + indx; let who = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); assert_has_event::( Event::Renewed { who, old_core: n_reservations as u16 + n_leases as u16 + indx as u16, core: n_reservations as u16 + n_leases as u16 + indx as u16, price, begin: new_sale.region_begin, duration: config.region_length, workload: Schedule::truncate_from(vec![ScheduleItem { assignment: Task(task), mask: CoreMask::complete(), }]), } .into(), ); }); Ok(()) } #[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::(); let commit_timeslice = Broker::::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::::current_timeslice(), }; #[block] { Broker::::process_pool(when, &mut status); } assert!(InstaPoolHistory::::get(when).is_some()); assert_last_event::( 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::::insert((timeslice, core), new_schedule()); #[block] { Broker::::process_core_schedule(timeslice, rc_begin, core); } assert_eq!(Workload::::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::(Event::CoreAssigned { core, when: rc_begin, assignment }.into()); } #[benchmark] fn request_revenue_info_at() { let current_timeslice = Broker::::current_timeslice(); let rc_block = T::TimeslicePeriod::get() * current_timeslice.into(); #[block] { T::Coretime::request_revenue_info_at(rc_block); } } #[benchmark] fn notify_core_count() -> Result<(), BenchmarkError> { let admin_origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(admin_origin as T::RuntimeOrigin, 100); assert!(CoreCountInbox::::take().is_some()); Ok(()) } #[benchmark] fn notify_revenue() -> Result<(), BenchmarkError> { let admin_origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _( admin_origin as T::RuntimeOrigin, OnDemandRevenueRecord { until: 100u32.into(), amount: 100_000_000u32.into() }, ); assert!(RevenueInbox::::take().is_some()); Ok(()) } #[benchmark] fn do_tick_base() -> Result<(), BenchmarkError> { setup_and_start_sale::()?; advance_to::(5); let mut status = Status::::get().unwrap(); status.last_committed_timeslice = 3; Status::::put(&status); #[block] { Broker::::do_tick(); } let updated_status = Status::::get().unwrap(); assert_eq!(status, updated_status); Ok(()) } #[benchmark] fn force_reserve() -> Result<(), BenchmarkError> { Configuration::::put(new_config_record::()); // Assume Reservations to be almost filled for worst case. let reservation_count = T::MaxReservedCores::get().saturating_sub(1); setup_reservations::(reservation_count); // Assume leases to be filled for worst case setup_leases::(T::MaxLeasedCores::get(), 1, 10); let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; // Sales must be started. Broker::::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap()) .map_err(|_| BenchmarkError::Weightless)?; // Add a core. let status = Status::::get().unwrap(); Broker::::do_request_core_count(status.core_count + 1).unwrap(); advance_to::(T::TimeslicePeriod::get().try_into().ok().unwrap()); let schedule = new_schedule(); #[extrinsic_call] _(origin as T::RuntimeOrigin, schedule.clone(), status.core_count); assert_eq!(Reservations::::decode_len().unwrap(), T::MaxReservedCores::get() as usize); let sale_info = SaleInfo::::get().unwrap(); assert_eq!( Workplan::::get((sale_info.region_begin, status.core_count)), Some(schedule.clone()) ); // We called at timeslice 1, therefore 2 was already processed and 3 is the next possible // assignment point. assert_eq!(Workplan::::get((3, status.core_count)), Some(schedule)); Ok(()) } #[benchmark] fn swap_leases() -> Result<(), BenchmarkError> { let admin_origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; // Add two leases in `Leases` let n = (T::MaxLeasedCores::get() / 2) as usize; let mut leases = vec![LeaseRecordItem { task: 1, until: 10u32.into() }; n]; leases.extend(vec![LeaseRecordItem { task: 2, until: 20u32.into() }; n]); Leases::::put(BoundedVec::try_from(leases).unwrap()); #[extrinsic_call] _(admin_origin as T::RuntimeOrigin, 1, 2); Ok(()) } #[benchmark] fn enable_auto_renew() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; advance_to::(2); let sale = SaleInfo::::get().expect("Sale has already started."); // We assume max auto renewals for worst case. (0..T::MaxAutoRenewals::get() - 1).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); // Sovereign account needs sufficient funds to purchase and renew. T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; Broker::::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(2001).expect("Failed to get sovereign account"); // Sovereign account needs sufficient funds to purchase and renew. T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance() .saturating_add(sale_data.start_price.saturating_add(sale_data.start_price)), ); // The region for which we benchmark enable auto renew. let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, 2001, Final) .map_err(|_| BenchmarkError::Weightless)?; // The most 'intensive' path is when we renew the core upon enabling auto-renewal. // Therefore, we advance to next bulk sale: let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap(); let config = Configuration::::get().expect("Already configured."); advance_to::(config.region_length * timeslice_period); #[extrinsic_call] _(RawOrigin::Signed(caller), region.core, 2001, None); assert_last_event::(Event::AutoRenewalEnabled { core: region.core, task: 2001 }.into()); // Make sure we indeed renewed: let sale = SaleInfo::::get().expect("Sales have started."); assert!(PotentialRenewals::::get(PotentialRenewalId { core: region.core, when: sale.region_end, }) .is_some()); Ok(()) } #[benchmark] fn disable_auto_renew() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; let core = sale_data.first_core; advance_to::(2); let sale = SaleInfo::::get().expect("Sale has already started."); // We assume max auto renewals for worst case. (0..T::MaxAutoRenewals::get()).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; Broker::::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; let task = 1000; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task).expect("Failed to get sovereign account"); #[extrinsic_call] _(RawOrigin::Signed(caller), core, task); assert_last_event::(Event::AutoRenewalDisabled { core, task }.into()); Ok(()) } #[benchmark] fn on_new_timeslice() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let _region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); let timeslice = Broker::::current_timeslice(); #[block] { T::Coretime::on_new_timeslice(timeslice); } Ok(()) } #[benchmark] fn remove_assignment() -> Result<(), BenchmarkError> { let sale_data = setup_and_start_sale::()?; advance_to::(2); let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), T::Currency::minimum_balance().saturating_add(sale_data.start_price), ); let region = Broker::::do_purchase(caller.clone(), sale_data.start_price) .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, 1000, Provisional) .map_err(|_| BenchmarkError::Weightless)?; let origin = T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; #[extrinsic_call] _(origin as T::RuntimeOrigin, region); Ok(()) } // Implements a test for each benchmark. Execute with: // `cargo test -p pezpallet-broker --features runtime-benchmarks`. impl_benchmark_test_suite!(Pezpallet, crate::mock::new_test_ext(), crate::mock::Test); }