1use crate::{
22 ids::{MessageId, ReservationId, prelude::*},
23 message::IncomingDispatch,
24};
25use alloc::{collections::BTreeMap, format};
26use gear_core_errors::ReservationError;
27use scale_decode::DecodeAsType;
28use scale_encode::EncodeAsType;
29use scale_info::{
30 TypeInfo,
31 scale::{Decode, Encode},
32};
33
34#[derive(
43 Clone,
44 Copy,
45 Default,
46 Debug,
47 Eq,
48 Hash,
49 Ord,
50 PartialEq,
51 PartialOrd,
52 Decode,
53 DecodeAsType,
54 Encode,
55 EncodeAsType,
56 TypeInfo,
57)]
58#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
59pub struct ReservationNonce(u64);
60
61impl From<&InnerNonce> for ReservationNonce {
62 fn from(nonce: &InnerNonce) -> Self {
63 ReservationNonce(nonce.0)
64 }
65}
66
67#[derive(Debug, Clone, Encode, EncodeAsType, Decode, DecodeAsType, PartialEq, Eq)]
70struct InnerNonce(u64);
71
72impl InnerNonce {
73 fn fetch_inc(&mut self) -> u64 {
76 let current = self.0;
77 self.0 = self.0.saturating_add(1);
78
79 current
80 }
81}
82
83impl From<ReservationNonce> for InnerNonce {
84 fn from(frozen_nonce: ReservationNonce) -> Self {
85 InnerNonce(frozen_nonce.0)
86 }
87}
88
89#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)]
93pub struct GasReserver {
94 message_id: MessageId,
97 nonce: InnerNonce,
104 states: GasReservationStates,
106 max_reservations: u64,
114}
115
116impl GasReserver {
117 pub fn new(
122 incoming_dispatch: &IncomingDispatch,
123 map: GasReservationMap,
124 max_reservations: u64,
125 ) -> Self {
126 let message_id = incoming_dispatch.id();
127 let nonce = incoming_dispatch
128 .context()
129 .as_ref()
130 .map(|c| c.reservation_nonce())
131 .unwrap_or_default()
132 .into();
133 Self {
134 message_id,
135 nonce,
136 states: {
137 let mut states = BTreeMap::new();
138 states.extend(map.into_iter().map(|(id, slot)| (id, slot.into())));
139 states
140 },
141 max_reservations,
142 }
143 }
144
145 pub fn is_empty(&self) -> bool {
147 self.states.is_empty()
148 }
149
150 fn check_execution_limit(&self) -> Result<(), ReservationError> {
155 let current_reservations = self
158 .states
159 .values()
160 .map(|state| {
161 matches!(
162 state,
163 GasReservationState::Exists { .. } | GasReservationState::Created { .. }
164 ) as u64
165 })
166 .sum::<u64>();
167 if current_reservations > self.max_reservations {
168 Err(ReservationError::ReservationsLimitReached)
169 } else {
170 Ok(())
171 }
172 }
173
174 pub fn limit_of(&self, reservation_id: &ReservationId) -> Option<u64> {
176 self.states.get(reservation_id).and_then(|v| match v {
177 GasReservationState::Exists { amount, .. }
178 | GasReservationState::Created { amount, .. } => Some(*amount),
179 _ => None,
180 })
181 }
182
183 pub fn reserve(
189 &mut self,
190 amount: u64,
191 duration: u32,
192 ) -> Result<ReservationId, ReservationError> {
193 self.check_execution_limit()?;
194
195 let id = ReservationId::generate(self.message_id, self.nonce.fetch_inc());
196
197 let maybe_reservation = self.states.insert(
198 id,
199 GasReservationState::Created {
200 amount,
201 duration,
202 used: false,
203 },
204 );
205
206 if maybe_reservation.is_some() {
207 let err_msg = format!(
208 "GasReserver::reserve: created a duplicate reservation. \
209 Message id - {message_id}, nonce - {nonce}",
210 message_id = self.message_id,
211 nonce = self.nonce.0
212 );
213
214 log::error!("{err_msg}");
215 unreachable!("{err_msg}");
216 }
217
218 Ok(id)
219 }
220
221 pub fn unreserve(
228 &mut self,
229 id: ReservationId,
230 ) -> Result<(u64, Option<UnreservedReimbursement>), ReservationError> {
231 let state = self
233 .states
234 .get(&id)
235 .ok_or(ReservationError::InvalidReservationId)?;
236
237 if matches!(
238 state,
239 GasReservationState::Removed { .. } |
241 GasReservationState::Exists { used: true, .. } |
243 GasReservationState::Created { used: true, .. }
244 ) {
245 return Err(ReservationError::InvalidReservationId);
246 }
247
248 let state = self.states.remove(&id).unwrap();
249
250 Ok(match state {
251 GasReservationState::Exists { amount, finish, .. } => {
252 self.states
253 .insert(id, GasReservationState::Removed { expiration: finish });
254 (amount, None)
255 }
256 GasReservationState::Created {
257 amount, duration, ..
258 } => (amount, Some(UnreservedReimbursement(duration))),
259 GasReservationState::Removed { .. } => {
260 let err_msg =
261 "GasReserver::unreserve: `Removed` variant is unreachable, checked above";
262
263 log::error!("{err_msg}");
264 unreachable!("{err_msg}")
265 }
266 })
267 }
268
269 pub fn mark_used(&mut self, id: ReservationId) -> Result<(), ReservationError> {
275 let used = self.check_not_used(id)?;
276 *used = true;
277 Ok(())
278 }
279
280 pub fn check_not_used(&mut self, id: ReservationId) -> Result<&mut bool, ReservationError> {
284 if let Some(
285 GasReservationState::Created { used, .. } | GasReservationState::Exists { used, .. },
286 ) = self.states.get_mut(&id)
287 {
288 if *used {
289 Err(ReservationError::InvalidReservationId)
290 } else {
291 Ok(used)
292 }
293 } else {
294 Err(ReservationError::InvalidReservationId)
295 }
296 }
297
298 pub fn nonce(&self) -> ReservationNonce {
300 (&self.nonce).into()
301 }
302
303 pub fn states(&self) -> &GasReservationStates {
305 &self.states
306 }
307
308 pub fn into_map<F>(
310 self,
311 current_block_height: u32,
312 duration_into_expiration: F,
313 ) -> GasReservationMap
314 where
315 F: Fn(u32) -> u32,
316 {
317 self.states
318 .into_iter()
319 .flat_map(|(id, state)| match state {
320 GasReservationState::Exists {
321 amount,
322 start,
323 finish,
324 ..
325 } => Some((
326 id,
327 GasReservationSlot {
328 amount,
329 start,
330 finish,
331 },
332 )),
333 GasReservationState::Created {
334 amount, duration, ..
335 } => {
336 let expiration = duration_into_expiration(duration);
337 Some((
338 id,
339 GasReservationSlot {
340 amount,
341 start: current_block_height,
342 finish: expiration,
343 },
344 ))
345 }
346 GasReservationState::Removed { .. } => None,
347 })
348 .collect()
349 }
350}
351
352#[derive(Debug, PartialEq, Eq)]
356pub struct UnreservedReimbursement(u32);
357
358impl UnreservedReimbursement {
359 pub fn duration(&self) -> u32 {
361 self.0
362 }
363}
364
365pub type GasReservationStates = BTreeMap<ReservationId, GasReservationState>;
367
368#[derive(Debug, Clone, Copy, Eq, PartialEq, Encode, EncodeAsType, Decode, DecodeAsType)]
372pub enum GasReservationState {
373 Exists {
375 amount: u64,
377 start: u32,
379 finish: u32,
381 used: bool,
383 },
384 Created {
386 amount: u64,
388 duration: u32,
390 used: bool,
392 },
393 Removed {
395 expiration: u32,
397 },
398}
399
400impl From<GasReservationSlot> for GasReservationState {
401 fn from(slot: GasReservationSlot) -> Self {
402 Self::Exists {
403 amount: slot.amount,
404 start: slot.start,
405 finish: slot.finish,
406 used: false,
407 }
408 }
409}
410
411pub type GasReservationMap = BTreeMap<ReservationId, GasReservationSlot>;
415
416#[derive(Debug, Clone, Eq, PartialEq, Encode, EncodeAsType, Decode, DecodeAsType, TypeInfo)]
418pub struct GasReservationSlot {
419 pub amount: u64,
421 pub start: u32,
423 pub finish: u32,
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 const MAX_RESERVATIONS: u64 = 256;
432
433 fn new_reserver() -> GasReserver {
434 let d = IncomingDispatch::default();
435 GasReserver::new(&d, Default::default(), MAX_RESERVATIONS)
436 }
437
438 #[test]
439 fn max_reservations_limit_works() {
440 let mut reserver = new_reserver();
441 for n in 0..(MAX_RESERVATIONS * 10) {
442 let res = reserver.reserve(100, 10);
443 if n > MAX_RESERVATIONS {
444 assert_eq!(res, Err(ReservationError::ReservationsLimitReached));
445 } else {
446 assert!(res.is_ok());
447 }
448 }
449 }
450
451 #[test]
452 fn mark_used_for_unreserved_fails() {
453 let mut reserver = new_reserver();
454 let id = reserver.reserve(1, 1).unwrap();
455 reserver.unreserve(id).unwrap();
456
457 assert_eq!(
458 reserver.mark_used(id),
459 Err(ReservationError::InvalidReservationId)
460 );
461 }
462
463 #[test]
464 fn mark_used_twice_fails() {
465 let mut reserver = new_reserver();
466 let id = reserver.reserve(1, 1).unwrap();
467 reserver.mark_used(id).unwrap();
468 assert_eq!(
469 reserver.mark_used(id),
470 Err(ReservationError::InvalidReservationId)
471 );
472
473 assert_eq!(
475 reserver.mark_used(ReservationId::default()),
476 Err(ReservationError::InvalidReservationId)
477 );
478 }
479
480 #[test]
481 fn remove_reservation_twice_fails() {
482 let mut reserver = new_reserver();
483 let id = reserver.reserve(1, 1).unwrap();
484 reserver.unreserve(id).unwrap();
485 assert_eq!(
486 reserver.unreserve(id),
487 Err(ReservationError::InvalidReservationId)
488 );
489 }
490
491 #[test]
492 fn remove_non_existing_reservation_fails() {
493 let id = ReservationId::from([0xff; 32]);
494
495 let mut map = GasReservationMap::new();
496 map.insert(
497 id,
498 GasReservationSlot {
499 amount: 1,
500 start: 1,
501 finish: 100,
502 },
503 );
504
505 let mut reserver = GasReserver::new(&Default::default(), map, 256);
506 reserver.unreserve(id).unwrap();
507
508 assert_eq!(
509 reserver.unreserve(id),
510 Err(ReservationError::InvalidReservationId)
511 );
512 }
513
514 #[test]
515 fn fresh_reserve_unreserve() {
516 let mut reserver = new_reserver();
517 let id = reserver.reserve(10_000, 5).unwrap();
518 reserver.mark_used(id).unwrap();
519 assert_eq!(
520 reserver.unreserve(id),
521 Err(ReservationError::InvalidReservationId)
522 );
523 }
524
525 #[test]
526 fn existing_reserve_unreserve() {
527 let id = ReservationId::from([0xff; 32]);
528
529 let mut map = GasReservationMap::new();
530 map.insert(
531 id,
532 GasReservationSlot {
533 amount: 1,
534 start: 1,
535 finish: 100,
536 },
537 );
538
539 let mut reserver = GasReserver::new(&Default::default(), map, 256);
540 reserver.mark_used(id).unwrap();
541 assert_eq!(
542 reserver.unreserve(id),
543 Err(ReservationError::InvalidReservationId)
544 );
545 }
546
547 #[test]
548 fn unreserving_unreserved() {
549 let id = ReservationId::from([0xff; 32]);
550 let slot = GasReservationSlot {
551 amount: 1,
552 start: 2,
553 finish: 3,
554 };
555
556 let mut map = GasReservationMap::new();
557 map.insert(id, slot.clone());
558
559 let mut reserver = GasReserver::new(&Default::default(), map, 256);
560
561 let (amount, _) = reserver.unreserve(id).expect("Shouldn't fail");
562 assert_eq!(amount, slot.amount);
563
564 assert!(reserver.unreserve(id).is_err());
565 assert_eq!(
566 reserver.states().get(&id).cloned(),
567 Some(GasReservationState::Removed {
568 expiration: slot.finish
569 })
570 );
571 }
572}