1use core::cmp;
19
20use super::*;
21use frame_support::{
22 pallet_prelude::*,
23 traits::{fungible::Mutate, tokens::Preservation::Expendable, DefensiveResult},
24};
25use sp_arithmetic::traits::{CheckedDiv, Saturating, Zero};
26use sp_runtime::traits::{BlockNumberProvider, Convert};
27use CompletionStatus::{Complete, Partial};
28
29impl<T: Config> Pallet<T> {
30 pub(crate) fn do_configure(config: ConfigRecordOf<T>) -> DispatchResult {
31 config.validate().map_err(|()| Error::<T>::InvalidConfig)?;
32 Configuration::<T>::put(config);
33 Ok(())
34 }
35
36 pub(crate) fn do_request_core_count(core_count: CoreIndex) -> DispatchResult {
37 T::Coretime::request_core_count(core_count);
38 Self::deposit_event(Event::<T>::CoreCountRequested { core_count });
39 Ok(())
40 }
41
42 pub(crate) fn do_notify_core_count(core_count: CoreIndex) -> DispatchResult {
43 CoreCountInbox::<T>::put(core_count);
44 Ok(())
45 }
46
47 pub(crate) fn do_reserve(workload: Schedule) -> DispatchResult {
48 let mut r = Reservations::<T>::get();
49 let index = r.len() as u32;
50 r.try_push(workload.clone()).map_err(|_| Error::<T>::TooManyReservations)?;
51 Reservations::<T>::put(r);
52 Self::deposit_event(Event::<T>::ReservationMade { index, workload });
53 Ok(())
54 }
55
56 pub(crate) fn do_unreserve(index: u32) -> DispatchResult {
57 let mut r = Reservations::<T>::get();
58 ensure!(index < r.len() as u32, Error::<T>::UnknownReservation);
59 let workload = r.remove(index as usize);
60 Reservations::<T>::put(r);
61 Self::deposit_event(Event::<T>::ReservationCancelled { index, workload });
62 Ok(())
63 }
64
65 pub(crate) fn do_force_reserve(workload: Schedule, core: CoreIndex) -> DispatchResult {
66 let sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?;
68
69 Self::do_reserve(workload.clone())?;
71
72 ForceReservations::<T>::try_mutate(|r| {
74 r.try_push(workload.clone()).map_err(|_| Error::<T>::TooManyReservations)
75 })?;
76
77 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
80 let timeslice = status.last_committed_timeslice.saturating_add(1);
81 if timeslice < sale.region_begin {
82 Workplan::<T>::insert((timeslice, core), &workload);
83 }
84
85 Ok(())
86 }
87
88 pub(crate) fn do_set_lease(task: TaskId, until: Timeslice) -> DispatchResult {
89 let mut r = Leases::<T>::get();
90 ensure!(until > Self::current_timeslice(), Error::<T>::AlreadyExpired);
91 r.try_push(LeaseRecordItem { until, task })
92 .map_err(|_| Error::<T>::TooManyLeases)?;
93 Leases::<T>::put(r);
94 Self::deposit_event(Event::<T>::Leased { until, task });
95 Ok(())
96 }
97
98 pub(crate) fn do_remove_lease(task: TaskId) -> DispatchResult {
99 let mut r = Leases::<T>::get();
100 let i = r.iter().position(|lease| lease.task == task).ok_or(Error::<T>::LeaseNotFound)?;
101 r.remove(i);
102 Leases::<T>::put(r);
103 Self::deposit_event(Event::<T>::LeaseRemoved { task });
104 Ok(())
105 }
106
107 pub(crate) fn do_start_sales(
108 end_price: BalanceOf<T>,
109 extra_cores: CoreIndex,
110 ) -> DispatchResult {
111 let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
112
113 let core_count = Leases::<T>::decode_len().unwrap_or(0) as CoreIndex +
115 Reservations::<T>::decode_len().unwrap_or(0) as CoreIndex +
116 extra_cores;
117
118 Self::do_request_core_count(core_count)?;
119
120 let commit_timeslice = Self::latest_timeslice_ready_to_commit(&config);
121 let status = StatusRecord {
122 core_count,
123 private_pool_size: 0,
124 system_pool_size: 0,
125 last_committed_timeslice: commit_timeslice.saturating_sub(1),
126 last_timeslice: Self::current_timeslice(),
127 };
128 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
129 let old_sale = SaleInfoRecord {
131 sale_start: now,
132 leadin_length: Zero::zero(),
133 end_price,
134 sellout_price: None,
135 region_begin: commit_timeslice,
136 region_end: commit_timeslice.saturating_add(config.region_length),
137 first_core: 0,
138 ideal_cores_sold: 0,
139 cores_offered: 0,
140 cores_sold: 0,
141 };
142 Self::deposit_event(Event::<T>::SalesStarted { price: end_price, core_count });
143 Self::rotate_sale(old_sale, &config, &status);
144 Status::<T>::put(&status);
145 Ok(())
146 }
147
148 pub(crate) fn do_purchase(
149 who: T::AccountId,
150 price_limit: BalanceOf<T>,
151 ) -> Result<RegionId, DispatchError> {
152 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
153 let mut sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?;
154 Self::ensure_cores_for_sale(&status, &sale)?;
155
156 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
157 ensure!(now > sale.sale_start, Error::<T>::TooEarly);
158 let price = Self::sale_price(&sale, now);
159 ensure!(price_limit >= price, Error::<T>::Overpriced);
160
161 let core = Self::purchase_core(&who, price, &mut sale)?;
162
163 SaleInfo::<T>::put(&sale);
164 let id = Self::issue(
165 core,
166 sale.region_begin,
167 CoreMask::complete(),
168 sale.region_end,
169 Some(who.clone()),
170 Some(price),
171 );
172 let duration = sale.region_end.saturating_sub(sale.region_begin);
173 Self::deposit_event(Event::Purchased { who, region_id: id, price, duration });
174 Ok(id)
175 }
176
177 pub(crate) fn do_renew(who: T::AccountId, core: CoreIndex) -> Result<CoreIndex, DispatchError> {
180 let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
181 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
182 let mut sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?;
183 Self::ensure_cores_for_sale(&status, &sale)?;
184
185 let renewal_id = PotentialRenewalId { core, when: sale.region_begin };
186 let record = PotentialRenewals::<T>::get(renewal_id).ok_or(Error::<T>::NotAllowed)?;
187 let workload =
188 record.completion.drain_complete().ok_or(Error::<T>::IncompleteAssignment)?;
189
190 let old_core = core;
191
192 let core = Self::purchase_core(&who, record.price, &mut sale)?;
193
194 Self::deposit_event(Event::Renewed {
195 who,
196 old_core,
197 core,
198 price: record.price,
199 begin: sale.region_begin,
200 duration: sale.region_end.saturating_sub(sale.region_begin),
201 workload: workload.clone(),
202 });
203
204 Workplan::<T>::insert((sale.region_begin, core), &workload);
205
206 let begin = sale.region_end;
207 let end_price = sale.end_price;
208 let price_cap = cmp::max(record.price + config.renewal_bump * record.price, end_price);
210 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
211 let price = Self::sale_price(&sale, now).min(price_cap);
212 log::debug!(
213 "Renew with: sale price: {:?}, price cap: {:?}, old price: {:?}",
214 price,
215 price_cap,
216 record.price
217 );
218 let new_record = PotentialRenewalRecord { price, completion: Complete(workload) };
219 PotentialRenewals::<T>::remove(renewal_id);
220 PotentialRenewals::<T>::insert(PotentialRenewalId { core, when: begin }, &new_record);
221 SaleInfo::<T>::put(&sale);
222 if let Some(workload) = new_record.completion.drain_complete() {
223 log::debug!("Recording renewable price for next run: {:?}", price);
224 Self::deposit_event(Event::Renewable { core, price, begin, workload });
225 }
226 Ok(core)
227 }
228
229 pub(crate) fn do_transfer(
230 region_id: RegionId,
231 maybe_check_owner: Option<T::AccountId>,
232 new_owner: T::AccountId,
233 ) -> Result<(), Error<T>> {
234 let mut region = Regions::<T>::get(®ion_id).ok_or(Error::<T>::UnknownRegion)?;
235
236 if let Some(check_owner) = maybe_check_owner {
237 ensure!(Some(check_owner) == region.owner, Error::<T>::NotOwner);
238 }
239
240 let old_owner = region.owner;
241 region.owner = Some(new_owner);
242 Regions::<T>::insert(®ion_id, ®ion);
243 let duration = region.end.saturating_sub(region_id.begin);
244 Self::deposit_event(Event::Transferred {
245 region_id,
246 old_owner,
247 owner: region.owner,
248 duration,
249 });
250
251 Ok(())
252 }
253
254 pub(crate) fn do_partition(
255 region_id: RegionId,
256 maybe_check_owner: Option<T::AccountId>,
257 pivot_offset: Timeslice,
258 ) -> Result<(RegionId, RegionId), Error<T>> {
259 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
260 let mut region = Regions::<T>::get(®ion_id).ok_or(Error::<T>::UnknownRegion)?;
261
262 if let Some(check_owner) = maybe_check_owner {
263 ensure!(Some(check_owner) == region.owner, Error::<T>::NotOwner);
264 }
265 let pivot = region_id.begin.saturating_add(pivot_offset);
266 ensure!(pivot < region.end, Error::<T>::PivotTooLate);
267 ensure!(pivot > region_id.begin, Error::<T>::PivotTooEarly);
268
269 region.paid = None;
270 let new_region_ids = (region_id, RegionId { begin: pivot, ..region_id });
271
272 Self::force_unpool_region(region_id, ®ion, &status);
276
277 Regions::<T>::insert(&new_region_ids.0, &RegionRecord { end: pivot, ..region.clone() });
280 Regions::<T>::insert(&new_region_ids.1, ®ion);
281 Self::deposit_event(Event::Partitioned { old_region_id: region_id, new_region_ids });
282
283 Ok(new_region_ids)
284 }
285
286 pub(crate) fn do_interlace(
287 region_id: RegionId,
288 maybe_check_owner: Option<T::AccountId>,
289 pivot: CoreMask,
290 ) -> Result<(RegionId, RegionId), Error<T>> {
291 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
292 let region = Regions::<T>::get(®ion_id).ok_or(Error::<T>::UnknownRegion)?;
293
294 if let Some(check_owner) = maybe_check_owner {
295 ensure!(Some(check_owner) == region.owner, Error::<T>::NotOwner);
296 }
297
298 ensure!((pivot & !region_id.mask).is_void(), Error::<T>::ExteriorPivot);
299 ensure!(!pivot.is_void(), Error::<T>::VoidPivot);
300 ensure!(pivot != region_id.mask, Error::<T>::CompletePivot);
301
302 Self::force_unpool_region(region_id, ®ion, &status);
306
307 Regions::<T>::remove(®ion_id);
309
310 let one = RegionId { mask: pivot, ..region_id };
311 Regions::<T>::insert(&one, ®ion);
312 let other = RegionId { mask: region_id.mask ^ pivot, ..region_id };
313 Regions::<T>::insert(&other, ®ion);
314
315 let new_region_ids = (one, other);
316 Self::deposit_event(Event::Interlaced { old_region_id: region_id, new_region_ids });
317 Ok(new_region_ids)
318 }
319
320 pub(crate) fn do_assign(
321 region_id: RegionId,
322 maybe_check_owner: Option<T::AccountId>,
323 target: TaskId,
324 finality: Finality,
325 ) -> Result<(), Error<T>> {
326 let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
327 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
328
329 if let Some((region_id, region)) = Self::utilize(region_id, maybe_check_owner, finality)? {
330 let workplan_key = (region_id.begin, region_id.core);
331 let mut workplan = Workplan::<T>::get(&workplan_key).unwrap_or_default();
332
333 Self::force_unpool_region(region_id, ®ion, &status);
337
338 workplan.retain(|i| (i.mask & region_id.mask).is_void());
340 if workplan
341 .try_push(ScheduleItem {
342 mask: region_id.mask,
343 assignment: CoreAssignment::Task(target),
344 })
345 .is_ok()
346 {
347 Workplan::<T>::insert(&workplan_key, &workplan);
348 }
349
350 let duration = region.end.saturating_sub(region_id.begin);
351 if duration == config.region_length && finality == Finality::Final {
352 if let Some(price) = region.paid {
353 let renewal_id = PotentialRenewalId { core: region_id.core, when: region.end };
354 let assigned = match PotentialRenewals::<T>::get(renewal_id) {
355 Some(PotentialRenewalRecord { completion: Partial(w), price: p })
356 if price == p =>
357 {
358 w
359 },
360 _ => CoreMask::void(),
361 } | region_id.mask;
362 let workload =
363 if assigned.is_complete() { Complete(workplan) } else { Partial(assigned) };
364 let record = PotentialRenewalRecord { price, completion: workload };
365 PotentialRenewals::<T>::insert(&renewal_id, &record);
368 if let Some(workload) = record.completion.drain_complete() {
369 Self::deposit_event(Event::Renewable {
370 core: region_id.core,
371 price,
372 begin: region.end,
373 workload,
374 });
375 }
376 }
377 }
378 Self::deposit_event(Event::Assigned { region_id, task: target, duration });
379 }
380 Ok(())
381 }
382
383 pub(crate) fn do_remove_assignment(region_id: RegionId) -> DispatchResult {
384 let workplan_key = (region_id.begin, region_id.core);
385 ensure!(Workplan::<T>::contains_key(&workplan_key), Error::<T>::AssignmentNotFound);
386 Workplan::<T>::remove(&workplan_key);
387 Self::deposit_event(Event::<T>::AssignmentRemoved { region_id });
388 Ok(())
389 }
390
391 pub(crate) fn do_pool(
392 region_id: RegionId,
393 maybe_check_owner: Option<T::AccountId>,
394 payee: T::AccountId,
395 finality: Finality,
396 ) -> Result<(), Error<T>> {
397 if let Some((region_id, region)) = Self::utilize(region_id, maybe_check_owner, finality)? {
398 let workplan_key = (region_id.begin, region_id.core);
399 let mut workplan = Workplan::<T>::get(&workplan_key).unwrap_or_default();
400 let duration = region.end.saturating_sub(region_id.begin);
401 if workplan
402 .try_push(ScheduleItem { mask: region_id.mask, assignment: CoreAssignment::Pool })
403 .is_ok()
404 {
405 Workplan::<T>::insert(&workplan_key, &workplan);
406 let size = region_id.mask.count_ones() as i32;
407 InstaPoolIo::<T>::mutate(region_id.begin, |a| a.private.saturating_accrue(size));
408 InstaPoolIo::<T>::mutate(region.end, |a| a.private.saturating_reduce(size));
409 let record = ContributionRecord { length: duration, payee };
410 InstaPoolContribution::<T>::insert(®ion_id, record);
411 }
412
413 Self::deposit_event(Event::Pooled { region_id, duration });
414 }
415 Ok(())
416 }
417
418 pub(crate) fn do_claim_revenue(
419 mut region: RegionId,
420 max_timeslices: Timeslice,
421 ) -> DispatchResult {
422 ensure!(max_timeslices > 0, Error::<T>::NoClaimTimeslices);
423 let mut contribution =
424 InstaPoolContribution::<T>::take(region).ok_or(Error::<T>::UnknownContribution)?;
425 let contributed_parts = region.mask.count_ones();
426
427 Self::deposit_event(Event::RevenueClaimBegun { region, max_timeslices });
428
429 let mut payout = BalanceOf::<T>::zero();
430 let last = region.begin + contribution.length.min(max_timeslices);
431 for r in region.begin..last {
432 region.begin = r + 1;
433 contribution.length.saturating_dec();
434
435 let Some(mut pool_record) = InstaPoolHistory::<T>::get(r) else { continue };
436 let Some(total_payout) = pool_record.maybe_payout else { break };
437 let p = total_payout
438 .saturating_mul(contributed_parts.into())
439 .checked_div(&pool_record.private_contributions.into())
440 .unwrap_or_default();
441
442 payout.saturating_accrue(p);
443 pool_record.private_contributions.saturating_reduce(contributed_parts);
444
445 let remaining_payout = total_payout.saturating_sub(p);
446 if !remaining_payout.is_zero() && pool_record.private_contributions > 0 {
447 pool_record.maybe_payout = Some(remaining_payout);
448 InstaPoolHistory::<T>::insert(r, &pool_record);
449 } else {
450 InstaPoolHistory::<T>::remove(r);
451 }
452 if !p.is_zero() {
453 Self::deposit_event(Event::RevenueClaimItem { when: r, amount: p });
454 }
455 }
456
457 if contribution.length > 0 {
458 InstaPoolContribution::<T>::insert(region, &contribution);
459 }
460 T::Currency::transfer(&Self::account_id(), &contribution.payee, payout, Expendable)
461 .defensive_ok();
462 let next = if last < region.begin + contribution.length { Some(region) } else { None };
463 Self::deposit_event(Event::RevenueClaimPaid {
464 who: contribution.payee,
465 amount: payout,
466 next,
467 });
468 Ok(())
469 }
470
471 pub(crate) fn do_purchase_credit(
472 who: T::AccountId,
473 amount: BalanceOf<T>,
474 beneficiary: RelayAccountIdOf<T>,
475 ) -> DispatchResult {
476 ensure!(amount >= T::MinimumCreditPurchase::get(), Error::<T>::CreditPurchaseTooSmall);
477 T::Currency::transfer(&who, &Self::account_id(), amount, Expendable)?;
478 let rc_amount = T::ConvertBalance::convert(amount);
479 T::Coretime::credit_account(beneficiary.clone(), rc_amount);
480 Self::deposit_event(Event::<T>::CreditPurchased { who, beneficiary, amount });
481 Ok(())
482 }
483
484 pub(crate) fn do_drop_region(region_id: RegionId) -> DispatchResult {
485 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
486 let region = Regions::<T>::get(®ion_id).ok_or(Error::<T>::UnknownRegion)?;
487 ensure!(status.last_committed_timeslice >= region.end, Error::<T>::StillValid);
488
489 Regions::<T>::remove(®ion_id);
490 let duration = region.end.saturating_sub(region_id.begin);
491 Self::deposit_event(Event::RegionDropped { region_id, duration });
492 Ok(())
493 }
494
495 pub(crate) fn do_drop_contribution(region_id: RegionId) -> DispatchResult {
496 let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
497 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
498 let contrib =
499 InstaPoolContribution::<T>::get(®ion_id).ok_or(Error::<T>::UnknownContribution)?;
500 let end = region_id.begin.saturating_add(contrib.length);
501 ensure!(
502 status.last_timeslice >= end.saturating_add(config.contribution_timeout),
503 Error::<T>::StillValid
504 );
505 InstaPoolContribution::<T>::remove(region_id);
506 Self::deposit_event(Event::ContributionDropped { region_id });
507 Ok(())
508 }
509
510 pub(crate) fn do_drop_history(when: Timeslice) -> DispatchResult {
511 let config = Configuration::<T>::get().ok_or(Error::<T>::Uninitialized)?;
512 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
513 ensure!(
514 status.last_timeslice > when.saturating_add(config.contribution_timeout),
515 Error::<T>::StillValid
516 );
517 let record = InstaPoolHistory::<T>::take(when).ok_or(Error::<T>::NoHistory)?;
518 if let Some(payout) = record.maybe_payout {
519 let _ = Self::charge(&Self::account_id(), payout);
520 }
521 let revenue = record.maybe_payout.unwrap_or_default();
522 Self::deposit_event(Event::HistoryDropped { when, revenue });
523 Ok(())
524 }
525
526 pub(crate) fn do_drop_renewal(core: CoreIndex, when: Timeslice) -> DispatchResult {
527 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
528 ensure!(status.last_committed_timeslice >= when, Error::<T>::StillValid);
529 let id = PotentialRenewalId { core, when };
530 ensure!(PotentialRenewals::<T>::contains_key(id), Error::<T>::UnknownRenewal);
531 PotentialRenewals::<T>::remove(id);
532 Self::deposit_event(Event::PotentialRenewalDropped { core, when });
533 Ok(())
534 }
535
536 pub(crate) fn do_notify_revenue(revenue: OnDemandRevenueRecordOf<T>) -> DispatchResult {
537 RevenueInbox::<T>::put(revenue);
538 Ok(())
539 }
540
541 pub(crate) fn do_swap_leases(id: TaskId, other: TaskId) -> DispatchResult {
542 let mut id_leases_count = 0;
543 let mut other_leases_count = 0;
544 Leases::<T>::mutate(|leases| {
545 leases.iter_mut().for_each(|lease| {
546 if lease.task == id {
547 lease.task = other;
548 id_leases_count += 1;
549 } else if lease.task == other {
550 lease.task = id;
551 other_leases_count += 1;
552 }
553 })
554 });
555 Ok(())
556 }
557
558 pub(crate) fn do_enable_auto_renew(
559 sovereign_account: T::AccountId,
560 core: CoreIndex,
561 task: TaskId,
562 workload_end_hint: Option<Timeslice>,
563 ) -> DispatchResult {
564 let sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?;
565 let mut core = core;
566
567 if PotentialRenewals::<T>::get(PotentialRenewalId { core, when: sale.region_begin })
572 .is_some()
573 {
574 core = Self::do_renew(sovereign_account.clone(), core)?;
575 } else if let Some(workload_end) = workload_end_hint {
576 ensure!(
577 PotentialRenewals::<T>::get(PotentialRenewalId { core, when: workload_end })
578 .is_some(),
579 Error::<T>::NotAllowed
580 );
581 } else {
582 return Err(Error::<T>::NotAllowed.into());
583 }
584
585 AutoRenewals::<T>::try_mutate(|renewals| {
587 let pos = renewals
588 .binary_search_by(|r: &AutoRenewalRecord| r.core.cmp(&core))
589 .unwrap_or_else(|e| e);
590 renewals.try_insert(
591 pos,
592 AutoRenewalRecord {
593 core,
594 task,
595 next_renewal: workload_end_hint.unwrap_or(sale.region_end),
596 },
597 )
598 })
599 .map_err(|_| Error::<T>::TooManyAutoRenewals)?;
600
601 Self::deposit_event(Event::AutoRenewalEnabled { core, task });
602 Ok(())
603 }
604
605 pub(crate) fn do_disable_auto_renew(core: CoreIndex, task: TaskId) -> DispatchResult {
606 AutoRenewals::<T>::try_mutate(|renewals| -> DispatchResult {
607 let pos = renewals
608 .binary_search_by(|r: &AutoRenewalRecord| r.core.cmp(&core))
609 .map_err(|_| Error::<T>::AutoRenewalNotEnabled)?;
610
611 let renewal_record = renewals.get(pos).ok_or(Error::<T>::AutoRenewalNotEnabled)?;
612
613 ensure!(
614 renewal_record.core == core && renewal_record.task == task,
615 Error::<T>::NoPermission
616 );
617 renewals.remove(pos);
618 Ok(())
619 })?;
620
621 Self::deposit_event(Event::AutoRenewalDisabled { core, task });
622 Ok(())
623 }
624
625 pub(crate) fn do_remove_potential_renewal(core: CoreIndex, when: Timeslice) -> DispatchResult {
626 let renewal_id = PotentialRenewalId { core, when };
627
628 PotentialRenewals::<T>::take(renewal_id).ok_or(Error::<T>::UnknownRenewal)?;
629
630 Self::deposit_event(Event::PotentialRenewalRemoved { core, timeslice: when });
631
632 Ok(())
633 }
634
635 pub(crate) fn ensure_cores_for_sale(
636 status: &StatusRecord,
637 sale: &SaleInfoRecordOf<T>,
638 ) -> Result<(), DispatchError> {
639 ensure!(sale.first_core < status.core_count, Error::<T>::Unavailable);
640 ensure!(sale.cores_sold < sale.cores_offered, Error::<T>::SoldOut);
641
642 Ok(())
643 }
644
645 pub fn current_price() -> Result<BalanceOf<T>, DispatchError> {
647 let status = Status::<T>::get().ok_or(Error::<T>::Uninitialized)?;
648 let sale = SaleInfo::<T>::get().ok_or(Error::<T>::NoSales)?;
649
650 Self::ensure_cores_for_sale(&status, &sale)?;
651
652 let now = RCBlockNumberProviderOf::<T::Coretime>::current_block_number();
653 Ok(Self::sale_price(&sale, now))
654 }
655}