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