1use crate::fee::epoch::{EpochIndex, SignedCreditsPerEpoch, PERPETUAL_STORAGE_ERAS};
40use rust_decimal::prelude::*;
41use rust_decimal::Decimal;
42use rust_decimal_macros::dec;
43use std::cmp::Ordering;
44
45use crate::balances::credits::Credits;
46use crate::ProtocolError;
47use std::ops::Mul;
48
49#[rustfmt::skip]
53pub const FEE_DISTRIBUTION_TABLE: [Decimal; PERPETUAL_STORAGE_ERAS as usize] = [
54 dec!(0.05000), dec!(0.04800), dec!(0.04600), dec!(0.04400), dec!(0.04200),
55 dec!(0.04000), dec!(0.03850), dec!(0.03700), dec!(0.03550), dec!(0.03400),
56 dec!(0.03250), dec!(0.03100), dec!(0.02950), dec!(0.02850), dec!(0.02750),
57 dec!(0.02650), dec!(0.02550), dec!(0.02450), dec!(0.02350), dec!(0.02250),
58 dec!(0.02150), dec!(0.02050), dec!(0.01950), dec!(0.01875), dec!(0.01800),
59 dec!(0.01725), dec!(0.01650), dec!(0.01575), dec!(0.01500), dec!(0.01425),
60 dec!(0.01350), dec!(0.01275), dec!(0.01200), dec!(0.01125), dec!(0.01050),
61 dec!(0.00975), dec!(0.00900), dec!(0.00825), dec!(0.00750), dec!(0.00675),
62 dec!(0.00600), dec!(0.00525), dec!(0.00475), dec!(0.00425), dec!(0.00375),
63 dec!(0.00325), dec!(0.00275), dec!(0.00225), dec!(0.00175), dec!(0.00125),
64];
65
66type DistributionAmount = Credits;
67type DistributionLeftovers = Credits;
68
69pub fn distribute_storage_fee_to_epochs_collection(
71 credits_per_epochs: &mut SignedCreditsPerEpoch,
72 storage_fee: Credits,
73 start_epoch_index: EpochIndex,
74 epochs_per_era: u16,
75) -> Result<DistributionLeftovers, ProtocolError> {
76 distribution_storage_fee_to_epochs_map(
77 storage_fee,
78 start_epoch_index,
79 |epoch_index, epoch_fee_share| {
80 let epoch_credits = credits_per_epochs.entry(epoch_index).or_default();
81
82 *epoch_credits = epoch_credits
83 .checked_add_unsigned(epoch_fee_share)
84 .ok_or_else(|| {
85 ProtocolError::Overflow(
86 "updated epoch credits are not fitting to credits max size",
87 )
88 })?;
89
90 Ok(())
91 },
92 epochs_per_era,
93 )
94}
95
96pub fn subtract_refunds_from_epoch_credits_collection(
99 credits_per_epochs: &mut SignedCreditsPerEpoch,
100 storage_fee: Credits,
101 start_epoch_index: EpochIndex,
102 current_epoch_index: EpochIndex,
103 epochs_per_era: u16,
104) -> Result<(), ProtocolError> {
105 let leftovers = refund_storage_fee_to_epochs_map(
106 storage_fee,
107 start_epoch_index,
108 current_epoch_index + 1,
109 |epoch_index, epoch_fee_share| {
110 let epoch_credits = credits_per_epochs.entry(epoch_index).or_default();
111
112 *epoch_credits = epoch_credits
113 .checked_sub_unsigned(epoch_fee_share)
114 .ok_or_else(|| {
115 ProtocolError::Overflow(
116 "updated epoch credits are not fitting to credits min size",
117 )
118 })?;
119
120 Ok(())
121 },
122 epochs_per_era,
123 )?;
124
125 if leftovers > 0 {
127 let epoch_credits = credits_per_epochs.entry(current_epoch_index).or_default();
128
129 *epoch_credits = epoch_credits
130 .checked_sub_unsigned(leftovers)
131 .ok_or_else(|| {
132 ProtocolError::Overflow("updated epoch credits are not fitting to credits min size")
133 })?;
134 }
135
136 Ok(())
137}
138
139pub fn subtract_refunds_priced_in_epoch_from_epoch_credits_collection(
151 credits_per_epochs: &mut SignedCreditsPerEpoch,
152 storage_fee: Credits,
153 start_epoch_index: EpochIndex,
154 pricing_epoch_index: EpochIndex,
155 current_epoch_index: EpochIndex,
156 epochs_per_era: u16,
157) -> Result<(), ProtocolError> {
158 let first_refunded_epoch_index =
159 pricing_epoch_index
160 .checked_add(1)
161 .ok_or(ProtocolError::Overflow(
162 "the epoch after the refund pricing epoch is past the last epoch index",
163 ))?;
164
165 let leftovers = if start_epoch_index > pricing_epoch_index {
169 storage_fee
170 } else {
171 refund_storage_fee_to_epochs_map(
172 storage_fee,
173 start_epoch_index,
174 first_refunded_epoch_index,
175 |epoch_index, epoch_fee_share| {
176 subtract_from_epoch_credits(
177 credits_per_epochs,
178 epoch_index.max(current_epoch_index),
179 epoch_fee_share,
180 )
181 },
182 epochs_per_era,
183 )?
184 };
185
186 if leftovers > 0 {
187 subtract_from_epoch_credits(credits_per_epochs, current_epoch_index, leftovers)?;
188 }
189
190 Ok(())
191}
192
193fn subtract_from_epoch_credits(
194 credits_per_epochs: &mut SignedCreditsPerEpoch,
195 epoch_index: EpochIndex,
196 credits: Credits,
197) -> Result<(), ProtocolError> {
198 let epoch_credits = credits_per_epochs.entry(epoch_index).or_default();
199
200 *epoch_credits = epoch_credits
201 .checked_sub_unsigned(credits)
202 .ok_or(ProtocolError::Overflow(
203 "updated epoch credits are not fitting to credits min size",
204 ))?;
205
206 Ok(())
207}
208
209pub fn calculate_storage_fee_refund_amount_and_leftovers(
211 storage_fee: Credits,
212 start_epoch_index: EpochIndex,
213 current_epoch_index: EpochIndex,
214 epochs_per_era: u16,
215) -> Result<(DistributionAmount, DistributionLeftovers), ProtocolError> {
216 let mut skipped_amount = 0;
217
218 let leftovers = distribution_storage_fee_to_epochs_map(
219 storage_fee,
220 start_epoch_index,
221 |epoch_index, epoch_fee_share| {
222 if epoch_index < current_epoch_index + 1 {
223 skipped_amount += epoch_fee_share;
224 }
225
226 Ok(())
227 },
228 epochs_per_era,
229 )?;
230
231 Ok((storage_fee - skipped_amount - leftovers, leftovers))
232}
233
234fn original_removed_credits_multiplier_from(
235 start_epoch_index: EpochIndex,
236 start_repayment_from_epoch_index: EpochIndex,
237 epochs_per_era: u16,
238) -> Result<Decimal, ProtocolError> {
239 let paid_epochs = start_repayment_from_epoch_index
245 .checked_sub(start_epoch_index)
246 .ok_or(ProtocolError::Overflow(
247 "start repayment epoch is before the original storage epoch",
248 ))?;
249
250 let current_era = (paid_epochs / epochs_per_era) as usize;
251
252 let ratio_used: Decimal =
253 FEE_DISTRIBUTION_TABLE
254 .iter()
255 .enumerate()
256 .filter_map(|(era, epoch_multiplier)| match era.cmp(¤t_era) {
257 Ordering::Less => None,
258 Ordering::Equal => {
259 let amount_epochs_left_in_era = epochs_per_era - paid_epochs % epochs_per_era;
260 Some(epoch_multiplier.mul(
261 Decimal::from(amount_epochs_left_in_era) / Decimal::from(epochs_per_era),
262 ))
263 }
264 Ordering::Greater => Some(*epoch_multiplier),
265 })
266 .sum();
267
268 if ratio_used.is_zero() {
277 return Err(ProtocolError::DivideByZero(
278 "storage fee refund is older than the entire perpetual storage window",
279 ));
280 }
281
282 Ok(dec!(1) / ratio_used)
283}
284
285fn restore_original_removed_credits_amount(
290 refund_amount: Decimal,
291 start_epoch_index: EpochIndex,
292 start_repayment_from_epoch_index: EpochIndex,
293 epochs_per_era: u16,
294) -> Result<Decimal, ProtocolError> {
295 let multiplier = original_removed_credits_multiplier_from(
296 start_epoch_index,
297 start_repayment_from_epoch_index,
298 epochs_per_era,
299 )?;
300
301 refund_amount
302 .checked_mul(multiplier)
303 .ok_or(ProtocolError::Overflow(
304 "overflow when multiplying with the multiplier (this should be impossible)",
305 ))
306}
307
308fn distribution_storage_fee_to_epochs_map<F>(
311 storage_fee: Credits,
312 start_epoch_index: EpochIndex,
313 mut map_function: F,
314 epochs_per_era: u16,
315) -> Result<DistributionLeftovers, ProtocolError>
316where
317 F: FnMut(EpochIndex, Credits) -> Result<(), ProtocolError>,
318{
319 if storage_fee == 0 {
320 return Ok(0);
321 }
322
323 let storage_fee_dec: Decimal = storage_fee.into();
324
325 let mut distribution_leftover_credits = storage_fee;
326
327 let epochs_per_era_dec = Decimal::from(epochs_per_era);
328
329 for era in 0..PERPETUAL_STORAGE_ERAS {
330 let distribution_for_that_era_ratio = FEE_DISTRIBUTION_TABLE[era as usize];
331
332 let era_fee_share = storage_fee_dec * distribution_for_that_era_ratio;
333
334 let epoch_fee_share_dec = era_fee_share / epochs_per_era_dec;
335
336 let epoch_fee_share: Credits = epoch_fee_share_dec
337 .floor()
338 .to_u64()
339 .ok_or_else(|| ProtocolError::Overflow("storage fees are not fitting in a u64"))?;
340
341 let era_start_epoch_index = start_epoch_index + epochs_per_era * era;
342
343 for epoch_index in era_start_epoch_index..era_start_epoch_index + epochs_per_era {
344 map_function(epoch_index, epoch_fee_share)?;
346
347 distribution_leftover_credits = distribution_leftover_credits
348 .checked_sub(epoch_fee_share)
349 .ok_or(ProtocolError::Overflow(
350 "leftovers bigger than initial value",
351 ))?;
352 }
353 }
354
355 Ok(distribution_leftover_credits)
356}
357
358fn refund_storage_fee_to_epochs_map<F>(
362 storage_fee: Credits,
363 start_epoch_index: EpochIndex,
364 skip_until_epoch_index: EpochIndex,
365 mut map_function: F,
366 epochs_per_era: u16,
367) -> Result<DistributionLeftovers, ProtocolError>
368where
369 F: FnMut(EpochIndex, Credits) -> Result<(), ProtocolError>,
370{
371 if storage_fee == 0 {
372 return Ok(0);
373 }
374
375 let storage_fee_dec: Decimal = storage_fee.into();
376
377 let mut distribution_leftover_credits = storage_fee;
378
379 let epochs_per_era_dec = Decimal::from(epochs_per_era);
380
381 let start_era: u16 = (skip_until_epoch_index - start_epoch_index) / epochs_per_era;
382
383 if start_era >= PERPETUAL_STORAGE_ERAS {
400 return Ok(storage_fee);
401 }
402
403 let estimated_storage_fee_dec = restore_original_removed_credits_amount(
408 storage_fee_dec,
409 start_epoch_index,
410 skip_until_epoch_index,
411 epochs_per_era,
412 )?;
413
414 for era in start_era..PERPETUAL_STORAGE_ERAS {
415 let distribution_for_that_era_ratio = FEE_DISTRIBUTION_TABLE[era as usize];
416
417 let estimated_era_fee_share = estimated_storage_fee_dec * distribution_for_that_era_ratio;
418
419 let estimated_epoch_fee_share_dec = estimated_era_fee_share / epochs_per_era_dec;
420
421 let estimated_epoch_fee_share: Credits = estimated_epoch_fee_share_dec
422 .floor()
423 .to_u64()
424 .ok_or_else(|| ProtocolError::Overflow("storage fees are not fitting in a u64"))?;
425
426 let era_start_epoch_index = if era == start_era {
427 skip_until_epoch_index
428 } else {
429 start_epoch_index + epochs_per_era * era
430 };
431
432 let era_end_epoch_index = start_epoch_index + ((era + 1) * epochs_per_era);
433
434 for epoch_index in era_start_epoch_index..era_end_epoch_index {
435 map_function(epoch_index, estimated_epoch_fee_share)?;
436
437 distribution_leftover_credits = distribution_leftover_credits
438 .checked_sub(estimated_epoch_fee_share)
439 .ok_or(ProtocolError::Overflow(
440 "leftovers bigger than initial value",
441 ))?;
442 }
443 }
444 Ok(distribution_leftover_credits)
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use crate::fee::epoch::GENESIS_EPOCH_INDEX;
451 use rust_decimal::Decimal;
452 use rust_decimal_macros::dec;
453
454 mod original_removed_credits_multiplier_from {
455 use super::*;
456
457 #[test]
458 fn should_create_multiplier_for_epochs_since_the_beginning() {
459 let epoch_0_cost = dec!(0.05000) / dec!(20.0);
461 let multiplier_should_be = dec!(1.0) / (dec!(1.0) - epoch_0_cost);
462
463 let multiplier = original_removed_credits_multiplier_from(0, 1, 20)
464 .expect("multiplier within perpetual storage window");
465
466 assert_eq!(multiplier_should_be, multiplier);
467 }
468
469 #[test]
470 fn should_create_multiplier_for_epochs_since_24_and_repaid_since_43() {
471 let epoch_0_cost = dec!(19.0) * dec!(0.05000) / dec!(20.0);
473
474 let multiplier_should_be = dec!(1.0) / (dec!(1.0) - epoch_0_cost);
475
476 let multiplier = original_removed_credits_multiplier_from(24, 43, 20)
477 .expect("multiplier within perpetual storage window");
478
479 assert_eq!(multiplier_should_be, multiplier);
480 }
481 }
482
483 mod fee_distribution_table {
484 use super::*;
485
486 #[test]
487 fn should_have_sum_of_1() {
488 assert_eq!(FEE_DISTRIBUTION_TABLE.iter().sum::<Decimal>(), dec!(1.0),);
489 }
490
491 #[test]
492 fn should_distribute_value() {
493 let value = Decimal::from(i64::MAX);
494
495 let calculated_value: Decimal = FEE_DISTRIBUTION_TABLE
496 .into_iter()
497 .map(|ratio| value * ratio)
498 .sum();
499
500 assert_eq!(calculated_value, value);
501 }
502 }
503
504 mod distribution_storage_fee_to_epochs_map {
505 use super::*;
506
507 #[test]
508 fn should_distribute_nothing_if_storage_fees_are_zero() {
509 let mut calls = 0;
510
511 let leftovers = distribution_storage_fee_to_epochs_map(
512 0,
513 GENESIS_EPOCH_INDEX,
514 |_, _| {
515 calls += 1;
516
517 Ok(())
518 },
519 20,
520 )
521 .expect("should distribute storage fee");
522
523 assert_eq!(calls, 0);
524 assert_eq!(leftovers, 0);
525 }
526
527 #[test]
528 fn should_call_function_for_each_epoch_for_50_eras_sequentially() {
529 let mut calls = 0;
530
531 let mut previous_epoch_index = -1;
532
533 let leftovers = distribution_storage_fee_to_epochs_map(
534 100000,
535 GENESIS_EPOCH_INDEX,
536 |epoch_index, _| {
537 assert_eq!(epoch_index as i32, previous_epoch_index + 1);
538 previous_epoch_index = epoch_index as i32;
539
540 calls += 1;
541
542 Ok(())
543 },
544 20,
545 )
546 .expect("should distribute storage fee");
547
548 assert_eq!(calls, 1000); assert_eq!(leftovers, 360);
550 }
551 }
552
553 mod distribute_storage_fee_to_epochs_collection {
554 use super::*;
555 use crate::balances::credits::{Creditable, MAX_CREDITS};
556 use crate::fee::SignedCredits;
557
558 #[test]
559 fn should_distribute_max_credits_value_without_overflow() {
560 let storage_fee = MAX_CREDITS;
561
562 let mut credits_per_epochs = SignedCreditsPerEpoch::default();
563
564 let leftovers = distribute_storage_fee_to_epochs_collection(
565 &mut credits_per_epochs,
566 storage_fee,
567 GENESIS_EPOCH_INDEX,
568 20,
569 )
570 .expect("should distribute storage fee");
571
572 assert_eq!(leftovers, 507);
574 }
575
576 #[test]
577 fn should_deterministically_distribute_fees() {
578 let storage_fee = 1000000;
579 let current_epoch_index = 42;
580
581 let mut credits_per_epochs = SignedCreditsPerEpoch::default();
582
583 let leftovers = distribute_storage_fee_to_epochs_collection(
584 &mut credits_per_epochs,
585 storage_fee,
586 current_epoch_index,
587 20,
588 )
589 .expect("should distribute storage fee");
590
591 assert_eq!(leftovers, 180);
593
594 #[rustfmt::skip]
596 let reference_fees: [SignedCredits; 1000] = [
597 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500,
598 2500, 2500, 2500, 2500, 2500, 2500, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400,
599 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2300, 2300,
600 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300,
601 2300, 2300, 2300, 2300, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200,
602 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2100, 2100, 2100, 2100,
603 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100,
604 2100, 2100, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000,
605 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 1925, 1925, 1925, 1925, 1925, 1925,
606 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925,
607 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850,
608 1850, 1850, 1850, 1850, 1850, 1850, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775,
609 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1700, 1700,
610 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700,
611 1700, 1700, 1700, 1700, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625,
612 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1550, 1550, 1550, 1550,
613 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550,
614 1550, 1550, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475,
615 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1425, 1425, 1425, 1425, 1425, 1425,
616 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425,
617 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375,
618 1375, 1375, 1375, 1375, 1375, 1375, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325,
619 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1275, 1275,
620 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275,
621 1275, 1275, 1275, 1275, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225,
622 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1175, 1175, 1175, 1175,
623 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175,
624 1175, 1175, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125,
625 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1075, 1075, 1075, 1075, 1075, 1075,
626 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075,
627 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025,
628 1025, 1025, 1025, 1025, 1025, 1025, 975, 975, 975, 975, 975, 975, 975, 975, 975,
629 975, 975, 975, 975, 975, 975, 975, 975, 975, 975, 975, 937, 937, 937, 937, 937,
630 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 900,
631 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900,
632 900, 900, 900, 862, 862, 862, 862, 862, 862, 862, 862, 862, 862, 862, 862, 862,
633 862, 862, 862, 862, 862, 862, 862, 825, 825, 825, 825, 825, 825, 825, 825, 825,
634 825, 825, 825, 825, 825, 825, 825, 825, 825, 825, 825, 787, 787, 787, 787, 787,
635 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 750,
636 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750,
637 750, 750, 750, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
638 712, 712, 712, 712, 712, 712, 712, 675, 675, 675, 675, 675, 675, 675, 675, 675,
639 675, 675, 675, 675, 675, 675, 675, 675, 675, 675, 675, 637, 637, 637, 637, 637,
640 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 600,
641 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
642 600, 600, 600, 562, 562, 562, 562, 562, 562, 562, 562, 562, 562, 562, 562, 562,
643 562, 562, 562, 562, 562, 562, 562, 525, 525, 525, 525, 525, 525, 525, 525, 525,
644 525, 525, 525, 525, 525, 525, 525, 525, 525, 525, 525, 487, 487, 487, 487, 487,
645 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 450,
646 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450,
647 450, 450, 450, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412,
648 412, 412, 412, 412, 412, 412, 412, 375, 375, 375, 375, 375, 375, 375, 375, 375,
649 375, 375, 375, 375, 375, 375, 375, 375, 375, 375, 375, 337, 337, 337, 337, 337,
650 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 300,
651 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
652 300, 300, 300, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262,
653 262, 262, 262, 262, 262, 262, 262, 237, 237, 237, 237, 237, 237, 237, 237, 237,
654 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 212, 212, 212, 212, 212,
655 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 187,
656 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187,
657 187, 187, 187, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162,
658 162, 162, 162, 162, 162, 162, 162, 137, 137, 137, 137, 137, 137, 137, 137, 137,
659 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 112, 112, 112, 112, 112,
660 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 87,
661 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 62,
662 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62
663 ];
664
665 assert_eq!(
666 credits_per_epochs.clone().into_values().collect::<Vec<_>>(),
667 reference_fees
668 );
669
670 let total_distributed: SignedCredits = credits_per_epochs.values().sum();
671
672 assert_eq!(total_distributed.to_unsigned() + leftovers, storage_fee);
673
674 let leftovers = distribute_storage_fee_to_epochs_collection(
681 &mut credits_per_epochs,
682 storage_fee,
683 current_epoch_index,
684 20,
685 )
686 .expect("should distribute storage fee");
687
688 assert_eq!(
690 credits_per_epochs.into_values().collect::<Vec<_>>(),
691 reference_fees
692 .into_iter()
693 .map(|val| val * 2)
694 .collect::<Vec<_>>()
695 );
696
697 assert_eq!(leftovers, 180);
698 }
699 }
700
701 mod subtract_refunds_from_epoch_credits_collection {
702 use super::*;
703 use crate::balances::credits::Creditable;
704 use crate::fee::SignedCredits;
705
706 #[test]
707 fn should_deduct_refunds_from_collection_since_specific_epoch_start_at_genesis() {
708 let start_epoch_index: EpochIndex = GENESIS_EPOCH_INDEX;
714 const REFUNDED_EPOCH_INDEX: EpochIndex = 42;
715 let original_storage_fee = 1200005;
716
717 let (refund_amount, leftovers) = calculate_storage_fee_refund_amount_and_leftovers(
718 original_storage_fee,
719 start_epoch_index,
720 REFUNDED_EPOCH_INDEX,
721 20,
722 )
723 .expect("should distribute storage fee");
724
725 assert_eq!(refund_amount, 1074120);
726 assert_eq!(leftovers, 5);
727
728 let mut credits_per_epochs = SignedCreditsPerEpoch::default();
729
730 subtract_refunds_from_epoch_credits_collection(
731 &mut credits_per_epochs,
732 refund_amount,
733 start_epoch_index,
734 REFUNDED_EPOCH_INDEX,
735 20,
736 )
737 .expect("should distribute storage fee");
738
739 #[rustfmt::skip]
743 let reference_fees: [SignedCredits;
744 (1000 - REFUNDED_EPOCH_INDEX - 1) as usize] = [-2760, -2760, -2760,
745 -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760,
746 -2760, -2760, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640,
747 -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2520, -2520,
748 -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520,
749 -2520, -2520, -2520, -2520, -2520, -2520, -2400, -2400, -2400, -2400, -2400, -2400,
750 -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400,
751 -2400, -2400, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310,
752 -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2220, -2220,
753 -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220,
754 -2220, -2220, -2220, -2220, -2220, -2220, -2130, -2130, -2130, -2130, -2130, -2130,
755 -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130,
756 -2130, -2130, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040,
757 -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -1950, -1950,
758 -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950,
759 -1950, -1950, -1950, -1950, -1950, -1950, -1860, -1860, -1860, -1860, -1860, -1860,
760 -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860,
761 -1860, -1860, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770,
762 -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1710, -1710,
763 -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710,
764 -1710, -1710, -1710, -1710, -1710, -1710, -1650, -1650, -1650, -1650, -1650, -1650,
765 -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650,
766 -1650, -1650, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590,
767 -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1530, -1530,
768 -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530,
769 -1530, -1530, -1530, -1530, -1530, -1530, -1470, -1470, -1470, -1470, -1470, -1470,
770 -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470,
771 -1470, -1470, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410,
772 -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1350, -1350,
773 -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350,
774 -1350, -1350, -1350, -1350, -1350, -1350, -1290, -1290, -1290, -1290, -1290, -1290,
775 -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290,
776 -1290, -1290, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230,
777 -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1170, -1170,
778 -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170,
779 -1170, -1170, -1170, -1170, -1170, -1170, -1125, -1125, -1125, -1125, -1125, -1125,
780 -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125,
781 -1125, -1125, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080,
782 -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1035, -1035,
783 -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035,
784 -1035, -1035, -1035, -1035, -1035, -1035, -990, -990, -990, -990, -990, -990, -990,
785 -990, -990, -990, -990, -990, -990, -990, -990, -990, -990, -990, -990, -990, -945,
786 -945, -945, -945, -945, -945, -945, -945, -945, -945, -945, -945, -945, -945, -945,
787 -945, -945, -945, -945, -945, -900, -900, -900, -900, -900, -900, -900, -900, -900,
788 -900, -900, -900, -900, -900, -900, -900, -900, -900, -900, -900, -855, -855, -855,
789 -855, -855, -855, -855, -855, -855, -855, -855, -855, -855, -855, -855, -855, -855,
790 -855, -855, -855, -810, -810, -810, -810, -810, -810, -810, -810, -810, -810, -810,
791 -810, -810, -810, -810, -810, -810, -810, -810, -810, -765, -765, -765, -765, -765,
792 -765, -765, -765, -765, -765, -765, -765, -765, -765, -765, -765, -765, -765, -765,
793 -765, -720, -720, -720, -720, -720, -720, -720, -720, -720, -720, -720, -720, -720,
794 -720, -720, -720, -720, -720, -720, -720, -675, -675, -675, -675, -675, -675, -675,
795 -675, -675, -675, -675, -675, -675, -675, -675, -675, -675, -675, -675, -675, -630,
796 -630, -630, -630, -630, -630, -630, -630, -630, -630, -630, -630, -630, -630, -630,
797 -630, -630, -630, -630, -630, -585, -585, -585, -585, -585, -585, -585, -585, -585,
798 -585, -585, -585, -585, -585, -585, -585, -585, -585, -585, -585, -540, -540, -540,
799 -540, -540, -540, -540, -540, -540, -540, -540, -540, -540, -540, -540, -540, -540,
800 -540, -540, -540, -495, -495, -495, -495, -495, -495, -495, -495, -495, -495, -495,
801 -495, -495, -495, -495, -495, -495, -495, -495, -495, -450, -450, -450, -450, -450,
802 -450, -450, -450, -450, -450, -450, -450, -450, -450, -450, -450, -450, -450, -450,
803 -450, -405, -405, -405, -405, -405, -405, -405, -405, -405, -405, -405, -405, -405,
804 -405, -405, -405, -405, -405, -405, -405, -360, -360, -360, -360, -360, -360, -360,
805 -360, -360, -360, -360, -360, -360, -360, -360, -360, -360, -360, -360, -360, -315,
806 -315, -315, -315, -315, -315, -315, -315, -315, -315, -315, -315, -315, -315, -315,
807 -315, -315, -315, -315, -315, -285, -285, -285, -285, -285, -285, -285, -285, -285,
808 -285, -285, -285, -285, -285, -285, -285, -285, -285, -285, -285, -255, -255, -255,
809 -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, -255,
810 -255, -255, -255, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225,
811 -225, -225, -225, -225, -225, -225, -225, -225, -225, -195, -195, -195, -195, -195,
812 -195, -195, -195, -195, -195, -195, -195, -195, -195, -195, -195, -195, -195, -195,
813 -195, -165, -165, -165, -165, -165, -165, -165, -165, -165, -165, -165, -165, -165,
814 -165, -165, -165, -165, -165, -165, -165, -135, -135, -135, -135, -135, -135, -135,
815 -135, -135, -135, -135, -135, -135, -135, -135, -135, -135, -135, -135, -135, -105,
816 -105, -105, -105, -105, -105, -105, -105, -105, -105, -105, -105, -105, -105, -105,
817 -105, -105, -105, -105, -105, -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, -75,
818 -75, -75, -75, -75, -75, -75, -75, -75, -75];
819
820 assert_eq!(
821 credits_per_epochs.clone().into_values().collect::<Vec<_>>(),
822 reference_fees
823 );
824
825 let total_distributed: SignedCredits = credits_per_epochs.values().sum();
826
827 assert_eq!(total_distributed.to_unsigned(), refund_amount);
828 }
829
830 #[test]
857 fn should_not_halt_when_refund_distributed_as_window_boundary_is_crossed() {
858 const EPOCHS_PER_ERA: u16 = 40; const WRITE_EPOCH: EpochIndex = 0;
860
861 let window_end_epoch = WRITE_EPOCH + EPOCHS_PER_ERA * PERPETUAL_STORAGE_ERAS; let removal_epoch = window_end_epoch - 2; let distribution_epoch = removal_epoch + 1; let original_storage_fee: Credits = 10_000_000_000;
877
878 let (refund_amount, _leftovers) = calculate_storage_fee_refund_amount_and_leftovers(
879 original_storage_fee,
880 WRITE_EPOCH,
881 removal_epoch,
882 EPOCHS_PER_ERA,
883 )
884 .expect("refund amount computation should succeed");
885
886 assert!(
887 refund_amount > 0,
888 "removing data before the final in-window epoch ({}) must leave a \
889 non-zero refund; got {refund_amount}",
890 window_end_epoch - 1,
891 );
892
893 let mut credits_per_epochs = SignedCreditsPerEpoch::default();
894
895 subtract_refunds_from_epoch_credits_collection(
901 &mut credits_per_epochs,
902 refund_amount,
903 WRITE_EPOCH,
904 distribution_epoch,
905 EPOCHS_PER_ERA,
906 )
907 .expect("refund distribution must not halt the chain at the window boundary");
908
909 let entries: Vec<(EpochIndex, SignedCredits)> =
914 credits_per_epochs.into_iter().collect();
915 assert_eq!(
916 entries,
917 vec![(distribution_epoch, -(refund_amount as SignedCredits))],
918 "the full refund should be clawed back from only the current epoch",
919 );
920 }
921
922 #[test]
923 fn should_deduct_refunds_from_collection_start_epoch_doesnt_matter_check() {
924 for start_epoch_index in 0..150 {
925 let current_epoch_index_where_refund_occurred: EpochIndex = start_epoch_index + 14;
926
927 let original_storage_fee = 3405507;
928 let (refund_amount, leftovers) = calculate_storage_fee_refund_amount_and_leftovers(
929 original_storage_fee,
930 start_epoch_index,
931 current_epoch_index_where_refund_occurred,
932 20,
933 )
934 .expect("should distribute storage fee");
935
936 assert_eq!(refund_amount, 3277305);
937 assert_eq!(leftovers, 507);
938
939 let multiplier = original_removed_credits_multiplier_from(
940 start_epoch_index,
941 current_epoch_index_where_refund_occurred + 1,
942 20,
943 )
944 .expect("multiplier within perpetual storage window");
945
946 assert!(
951 (Decimal::from(refund_amount) * multiplier)
952 .abs_sub(&Decimal::from(original_storage_fee - leftovers))
953 < dec!(100)
954 );
955
956 assert!(
958 (Decimal::from(refund_amount) * multiplier)
959 < Decimal::from(original_storage_fee - leftovers)
960 );
961
962 let mut credits_per_epochs = SignedCreditsPerEpoch::default();
963
964 subtract_refunds_from_epoch_credits_collection(
965 &mut credits_per_epochs,
966 refund_amount,
967 start_epoch_index,
968 current_epoch_index_where_refund_occurred,
969 20,
970 )
971 .expect("should distribute storage fee");
972 #[rustfmt::skip]
976 let reference_fees: Vec<SignedCredits> =
977 vec![-525, -8512, -8512, -8512, -8512, -8512, -8171, -8171, -8171, -8171, -8171, -8171,
978 -8171, -8171, -8171, -8171, -8171, -8171, -8171, -8171, -8171, -8171, -8171,
979 -8171, -8171, -8171, -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831,
980 -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831,
981 -7831, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490,
982 -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7150,
983 -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150,
984 -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150, -6809, -6809, -6809,
985 -6809, -6809, -6809, -6809, -6809, -6809, -6809, -6809, -6809, -6809, -6809,
986 -6809, -6809, -6809, -6809, -6809, -6809, -6554, -6554, -6554, -6554, -6554,
987 -6554, -6554, -6554, -6554, -6554, -6554, -6554, -6554, -6554, -6554, -6554,
988 -6554, -6554, -6554, -6554, -6299, -6299, -6299, -6299, -6299, -6299, -6299,
989 -6299, -6299, -6299, -6299, -6299, -6299, -6299, -6299, -6299, -6299, -6299,
990 -6299, -6299, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043,
991 -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043,
992 -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788,
993 -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5533, -5533,
994 -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5533,
995 -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5277, -5277, -5277, -5277,
996 -5277, -5277, -5277, -5277, -5277, -5277, -5277, -5277, -5277, -5277, -5277,
997 -5277, -5277, -5277, -5277, -5277, -5022, -5022, -5022, -5022, -5022, -5022,
998 -5022, -5022, -5022, -5022, -5022, -5022, -5022, -5022, -5022, -5022, -5022,
999 -5022, -5022, -5022, -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852,
1000 -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852,
1001 -4852, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681,
1002 -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4511,
1003 -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511,
1004 -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4341, -4341, -4341,
1005 -4341, -4341, -4341, -4341, -4341, -4341, -4341, -4341, -4341, -4341, -4341,
1006 -4341, -4341, -4341, -4341, -4341, -4341, -4171, -4171, -4171, -4171, -4171,
1007 -4171, -4171, -4171, -4171, -4171, -4171, -4171, -4171, -4171, -4171, -4171,
1008 -4171, -4171, -4171, -4171, -4000, -4000, -4000, -4000, -4000, -4000, -4000,
1009 -4000, -4000, -4000, -4000, -4000, -4000, -4000, -4000, -4000, -4000, -4000,
1010 -4000, -4000, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830,
1011 -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830,
1012 -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660,
1013 -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3490, -3490,
1014 -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3490,
1015 -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3319, -3319, -3319, -3319,
1016 -3319, -3319, -3319, -3319, -3319, -3319, -3319, -3319, -3319, -3319, -3319,
1017 -3319, -3319, -3319, -3319, -3319, -3192, -3192, -3192, -3192, -3192, -3192,
1018 -3192, -3192, -3192, -3192, -3192, -3192, -3192, -3192, -3192, -3192, -3192,
1019 -3192, -3192, -3192, -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064,
1020 -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064,
1021 -3064, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936,
1022 -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2809,
1023 -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809,
1024 -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2681, -2681, -2681,
1025 -2681, -2681, -2681, -2681, -2681, -2681, -2681, -2681, -2681, -2681, -2681,
1026 -2681, -2681, -2681, -2681, -2681, -2681, -2553, -2553, -2553, -2553, -2553,
1027 -2553, -2553, -2553, -2553, -2553, -2553, -2553, -2553, -2553, -2553, -2553,
1028 -2553, -2553, -2553, -2553, -2426, -2426, -2426, -2426, -2426, -2426, -2426,
1029 -2426, -2426, -2426, -2426, -2426, -2426, -2426, -2426, -2426, -2426, -2426,
1030 -2426, -2426, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298,
1031 -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298,
1032 -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170,
1033 -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2042, -2042,
1034 -2042, -2042, -2042, -2042, -2042, -2042, -2042, -2042, -2042, -2042, -2042,
1035 -2042, -2042, -2042, -2042, -2042, -2042, -2042, -1915, -1915, -1915, -1915,
1036 -1915, -1915, -1915, -1915, -1915, -1915, -1915, -1915, -1915, -1915, -1915,
1037 -1915, -1915, -1915, -1915, -1915, -1787, -1787, -1787, -1787, -1787, -1787,
1038 -1787, -1787, -1787, -1787, -1787, -1787, -1787, -1787, -1787, -1787, -1787,
1039 -1787, -1787, -1787, -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659,
1040 -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659,
1041 -1659, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532,
1042 -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1404,
1043 -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404,
1044 -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1276, -1276, -1276,
1045 -1276, -1276, -1276, -1276, -1276, -1276, -1276, -1276, -1276, -1276, -1276,
1046 -1276, -1276, -1276, -1276, -1276, -1276, -1149, -1149, -1149, -1149, -1149,
1047 -1149, -1149, -1149, -1149, -1149, -1149, -1149, -1149, -1149, -1149, -1149,
1048 -1149, -1149, -1149, -1149, -1021, -1021, -1021, -1021, -1021, -1021, -1021,
1049 -1021, -1021, -1021, -1021, -1021, -1021, -1021, -1021, -1021, -1021, -1021,
1050 -1021, -1021, -893, -893, -893, -893, -893, -893, -893, -893, -893, -893,
1051 -893, -893, -893, -893, -893, -893, -893, -893, -893, -893, -808, -808, -808,
1052 -808, -808, -808, -808, -808, -808, -808, -808, -808, -808, -808, -808, -808,
1053 -808, -808, -808, -808, -723, -723, -723, -723, -723, -723, -723, -723, -723,
1054 -723, -723, -723, -723, -723, -723, -723, -723, -723, -723, -723, -638, -638,
1055 -638, -638, -638, -638, -638, -638, -638, -638, -638, -638, -638, -638, -638,
1056 -638, -638, -638, -638, -638, -553, -553, -553, -553, -553, -553, -553, -553,
1057 -553, -553, -553, -553, -553, -553, -553, -553, -553, -553, -553, -553, -468,
1058 -468, -468, -468, -468, -468, -468, -468, -468, -468, -468, -468, -468, -468,
1059 -468, -468, -468, -468, -468, -468, -383, -383, -383, -383, -383, -383, -383,
1060 -383, -383, -383, -383, -383, -383, -383, -383, -383, -383, -383, -383, -383,
1061 -297, -297, -297, -297, -297, -297, -297, -297, -297, -297, -297, -297, -297,
1062 -297, -297, -297, -297, -297, -297, -297, -212, -212, -212, -212, -212, -212,
1063 -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212,
1064 -212];
1065
1066 assert_eq!(
1067 credits_per_epochs.clone().into_values().collect::<Vec<_>>(),
1068 reference_fees
1069 );
1070
1071 let total_distributed: SignedCredits = credits_per_epochs.values().sum();
1072
1073 assert_eq!(total_distributed.to_unsigned(), refund_amount);
1074 }
1075 }
1076
1077 #[test]
1078 fn should_deduct_refunds_from_two_collection_since_specific_epoch() {
1079 const CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED: EpochIndex = 42;
1080 let mut credits_per_epochs = SignedCreditsPerEpoch::default();
1081
1082 let first_start_epoch_index: EpochIndex = GENESIS_EPOCH_INDEX;
1090
1091 let first_original_storage_fee = 1200005;
1092 let (first_refund_amount, leftovers) =
1093 calculate_storage_fee_refund_amount_and_leftovers(
1094 first_original_storage_fee,
1095 first_start_epoch_index,
1096 CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED,
1097 20,
1098 )
1099 .expect("should distribute storage fee");
1100
1101 assert_eq!(first_refund_amount, 1074120);
1102 assert_eq!(leftovers, 5);
1103
1104 subtract_refunds_from_epoch_credits_collection(
1105 &mut credits_per_epochs,
1106 first_refund_amount,
1107 first_start_epoch_index,
1108 CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED,
1109 20,
1110 )
1111 .expect("should distribute storage fee");
1112
1113 const SECOND_START_EPOCH_INDEX: EpochIndex = 28;
1120
1121 let second_original_storage_fee = 3405507;
1122 let (second_refund_amount, leftovers) =
1123 calculate_storage_fee_refund_amount_and_leftovers(
1124 second_original_storage_fee,
1125 SECOND_START_EPOCH_INDEX,
1126 CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED,
1127 20,
1128 )
1129 .expect("should distribute storage fee");
1130
1131 assert_eq!(second_refund_amount, 3277305);
1132 assert_eq!(leftovers, 507);
1133
1134 let multiplier = original_removed_credits_multiplier_from(
1135 SECOND_START_EPOCH_INDEX,
1136 CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED + 1,
1137 20,
1138 )
1139 .expect("multiplier within perpetual storage window");
1140
1141 assert!(
1146 (Decimal::from(second_refund_amount) * multiplier)
1147 .abs_sub(&Decimal::from(second_original_storage_fee - leftovers))
1148 < dec!(100)
1149 );
1150
1151 assert!(
1153 (Decimal::from(second_refund_amount) * multiplier)
1154 < Decimal::from(second_original_storage_fee - leftovers)
1155 );
1156
1157 subtract_refunds_from_epoch_credits_collection(
1158 &mut credits_per_epochs,
1159 second_refund_amount,
1160 SECOND_START_EPOCH_INDEX,
1161 CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED,
1162 20,
1163 )
1164 .expect("should distribute storage fee");
1165 #[rustfmt::skip]
1169 let reference_fees: [SignedCredits;
1170 (SECOND_START_EPOCH_INDEX + 1000 - CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED) as usize] =
1171 [-525, -11272, -11272, -11272, -11272, -11272, -10931, -10931, -10931, -10931,
1172 -10931, -10931, -10931, -10931, -10931, -10931, -10931, -10931, -10811, -10811,
1173 -10811, -10811, -10811, -10811, -10811, -10811, -10471, -10471, -10471, -10471,
1174 -10471, -10471, -10471, -10471, -10471, -10471, -10471, -10471, -10351, -10351,
1175 -10351, -10351, -10351, -10351, -10351, -10351, -10010, -10010, -10010, -10010,
1176 -10010, -10010, -10010, -10010, -10010, -10010, -10010, -10010, -9890, -9890,
1177 -9890, -9890, -9890, -9890, -9890, -9890, -9550, -9550, -9550, -9550, -9550,
1178 -9550, -9550, -9550, -9550, -9550, -9550, -9550, -9460, -9460, -9460, -9460,
1179 -9460, -9460, -9460, -9460, -9119, -9119, -9119, -9119, -9119, -9119, -9119,
1180 -9119, -9119, -9119, -9119, -9119, -9029, -9029, -9029, -9029, -9029, -9029,
1181 -9029, -9029, -8774, -8774, -8774, -8774, -8774, -8774, -8774, -8774, -8774,
1182 -8774, -8774, -8774, -8684, -8684, -8684, -8684, -8684, -8684, -8684, -8684,
1183 -8429, -8429, -8429, -8429, -8429, -8429, -8429, -8429, -8429, -8429, -8429,
1184 -8429, -8339, -8339, -8339, -8339, -8339, -8339, -8339, -8339, -8083, -8083,
1185 -8083, -8083, -8083, -8083, -8083, -8083, -8083, -8083, -8083, -8083, -7993,
1186 -7993, -7993, -7993, -7993, -7993, -7993, -7993, -7738, -7738, -7738, -7738,
1187 -7738, -7738, -7738, -7738, -7738, -7738, -7738, -7738, -7648, -7648, -7648,
1188 -7648, -7648, -7648, -7648, -7648, -7393, -7393, -7393, -7393, -7393, -7393,
1189 -7393, -7393, -7393, -7393, -7393, -7393, -7303, -7303, -7303, -7303, -7303,
1190 -7303, -7303, -7303, -7047, -7047, -7047, -7047, -7047, -7047, -7047, -7047,
1191 -7047, -7047, -7047, -7047, -6987, -6987, -6987, -6987, -6987, -6987, -6987,
1192 -6987, -6732, -6732, -6732, -6732, -6732, -6732, -6732, -6732, -6732, -6732,
1193 -6732, -6732, -6672, -6672, -6672, -6672, -6672, -6672, -6672, -6672, -6502,
1194 -6502, -6502, -6502, -6502, -6502, -6502, -6502, -6502, -6502, -6502, -6502,
1195 -6442, -6442, -6442, -6442, -6442, -6442, -6442, -6442, -6271, -6271, -6271,
1196 -6271, -6271, -6271, -6271, -6271, -6271, -6271, -6271, -6271, -6211, -6211,
1197 -6211, -6211, -6211, -6211, -6211, -6211, -6041, -6041, -6041, -6041, -6041,
1198 -6041, -6041, -6041, -6041, -6041, -6041, -6041, -5981, -5981, -5981, -5981,
1199 -5981, -5981, -5981, -5981, -5811, -5811, -5811, -5811, -5811, -5811, -5811,
1200 -5811, -5811, -5811, -5811, -5811, -5751, -5751, -5751, -5751, -5751, -5751,
1201 -5751, -5751, -5581, -5581, -5581, -5581, -5581, -5581, -5581, -5581, -5581,
1202 -5581, -5581, -5581, -5521, -5521, -5521, -5521, -5521, -5521, -5521, -5521,
1203 -5350, -5350, -5350, -5350, -5350, -5350, -5350, -5350, -5350, -5350, -5350,
1204 -5350, -5290, -5290, -5290, -5290, -5290, -5290, -5290, -5290, -5120, -5120,
1205 -5120, -5120, -5120, -5120, -5120, -5120, -5120, -5120, -5120, -5120, -5060,
1206 -5060, -5060, -5060, -5060, -5060, -5060, -5060, -4890, -4890, -4890, -4890,
1207 -4890, -4890, -4890, -4890, -4890, -4890, -4890, -4890, -4830, -4830, -4830,
1208 -4830, -4830, -4830, -4830, -4830, -4660, -4660, -4660, -4660, -4660, -4660,
1209 -4660, -4660, -4660, -4660, -4660, -4660, -4615, -4615, -4615, -4615, -4615,
1210 -4615, -4615, -4615, -4444, -4444, -4444, -4444, -4444, -4444, -4444, -4444,
1211 -4444, -4444, -4444, -4444, -4399, -4399, -4399, -4399, -4399, -4399, -4399,
1212 -4399, -4272, -4272, -4272, -4272, -4272, -4272, -4272, -4272, -4272, -4272,
1213 -4272, -4272, -4227, -4227, -4227, -4227, -4227, -4227, -4227, -4227, -4099,
1214 -4099, -4099, -4099, -4099, -4099, -4099, -4099, -4099, -4099, -4099, -4099,
1215 -4054, -4054, -4054, -4054, -4054, -4054, -4054, -4054, -3926, -3926, -3926,
1216 -3926, -3926, -3926, -3926, -3926, -3926, -3926, -3926, -3926, -3881, -3881,
1217 -3881, -3881, -3881, -3881, -3881, -3881, -3754, -3754, -3754, -3754, -3754,
1218 -3754, -3754, -3754, -3754, -3754, -3754, -3754, -3709, -3709, -3709, -3709,
1219 -3709, -3709, -3709, -3709, -3581, -3581, -3581, -3581, -3581, -3581, -3581,
1220 -3581, -3581, -3581, -3581, -3581, -3536, -3536, -3536, -3536, -3536, -3536,
1221 -3536, -3536, -3408, -3408, -3408, -3408, -3408, -3408, -3408, -3408, -3408,
1222 -3408, -3408, -3408, -3363, -3363, -3363, -3363, -3363, -3363, -3363, -3363,
1223 -3236, -3236, -3236, -3236, -3236, -3236, -3236, -3236, -3236, -3236, -3236,
1224 -3236, -3191, -3191, -3191, -3191, -3191, -3191, -3191, -3191, -3063, -3063,
1225 -3063, -3063, -3063, -3063, -3063, -3063, -3063, -3063, -3063, -3063, -3018,
1226 -3018, -3018, -3018, -3018, -3018, -3018, -3018, -2890, -2890, -2890, -2890,
1227 -2890, -2890, -2890, -2890, -2890, -2890, -2890, -2890, -2845, -2845, -2845,
1228 -2845, -2845, -2845, -2845, -2845, -2717, -2717, -2717, -2717, -2717, -2717,
1229 -2717, -2717, -2717, -2717, -2717, -2717, -2672, -2672, -2672, -2672, -2672,
1230 -2672, -2672, -2672, -2545, -2545, -2545, -2545, -2545, -2545, -2545, -2545,
1231 -2545, -2545, -2545, -2545, -2500, -2500, -2500, -2500, -2500, -2500, -2500,
1232 -2500, -2372, -2372, -2372, -2372, -2372, -2372, -2372, -2372, -2372, -2372,
1233 -2372, -2372, -2327, -2327, -2327, -2327, -2327, -2327, -2327, -2327, -2199,
1234 -2199, -2199, -2199, -2199, -2199, -2199, -2199, -2199, -2199, -2199, -2199,
1235 -2154, -2154, -2154, -2154, -2154, -2154, -2154, -2154, -2027, -2027, -2027,
1236 -2027, -2027, -2027, -2027, -2027, -2027, -2027, -2027, -2027, -1982, -1982,
1237 -1982, -1982, -1982, -1982, -1982, -1982, -1854, -1854, -1854, -1854, -1854,
1238 -1854, -1854, -1854, -1854, -1854, -1854, -1854, -1809, -1809, -1809, -1809,
1239 -1809, -1809, -1809, -1809, -1681, -1681, -1681, -1681, -1681, -1681, -1681,
1240 -1681, -1681, -1681, -1681, -1681, -1636, -1636, -1636, -1636, -1636, -1636,
1241 -1636, -1636, -1509, -1509, -1509, -1509, -1509, -1509, -1509, -1509, -1509,
1242 -1509, -1509, -1509, -1464, -1464, -1464, -1464, -1464, -1464, -1464, -1464,
1243 -1336, -1336, -1336, -1336, -1336, -1336, -1336, -1336, -1336, -1336, -1336,
1244 -1336, -1306, -1306, -1306, -1306, -1306, -1306, -1306, -1306, -1178, -1178,
1245 -1178, -1178, -1178, -1178, -1178, -1178, -1178, -1178, -1178, -1178, -1148,
1246 -1148, -1148, -1148, -1148, -1148, -1148, -1148, -1063, -1063, -1063, -1063,
1247 -1063, -1063, -1063, -1063, -1063, -1063, -1063, -1063, -1033, -1033, -1033,
1248 -1033, -1033, -1033, -1033, -1033, -948, -948, -948, -948, -948, -948, -948,
1249 -948, -948, -948, -948, -948, -918, -918, -918, -918, -918, -918, -918, -918,
1250 -833, -833, -833, -833, -833, -833, -833, -833, -833, -833, -833, -833, -803,
1251 -803, -803, -803, -803, -803, -803, -803, -718, -718, -718, -718, -718, -718,
1252 -718, -718, -718, -718, -718, -718, -688, -688, -688, -688, -688, -688, -688,
1253 -688, -603, -603, -603, -603, -603, -603, -603, -603, -603, -603, -603, -603,
1254 -573, -573, -573, -573, -573, -573, -573, -573, -488, -488, -488, -488, -488,
1255 -488, -488, -488, -488, -488, -488, -488, -458, -458, -458, -458, -458, -458,
1256 -458, -458, -372, -372, -372, -372, -372, -372, -372, -372, -372, -372, -372,
1257 -372, -297, -297, -297, -297, -297, -297, -297, -297, -212, -212, -212, -212,
1258 -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212,
1259 -212, -212, -212];
1260
1261 assert_eq!(
1262 credits_per_epochs.clone().into_values().collect::<Vec<_>>(),
1263 reference_fees
1264 );
1265
1266 let total_distributed: SignedCredits = credits_per_epochs.values().sum();
1267
1268 assert_eq!(
1269 total_distributed.to_unsigned(),
1270 first_refund_amount + second_refund_amount
1271 );
1272 }
1273 }
1274
1275 mod subtract_refunds_priced_in_epoch_from_epoch_credits_collection {
1276 use super::*;
1277 use crate::fee::SignedCredits;
1278 use std::collections::BTreeMap;
1279
1280 const EPOCHS_PER_ERA: u16 = 40;
1281
1282 fn refunded_shares(
1286 storage_fee: Credits,
1287 start_epoch_index: EpochIndex,
1288 pricing_epoch_index: EpochIndex,
1289 current_epoch_index: EpochIndex,
1290 ) -> BTreeMap<EpochIndex, SignedCredits> {
1291 let mut shares = SignedCreditsPerEpoch::default();
1292 distribute_storage_fee_to_epochs_collection(
1293 &mut shares,
1294 storage_fee,
1295 start_epoch_index,
1296 EPOCHS_PER_ERA,
1297 )
1298 .expect("should distribute storage fee");
1299
1300 let mut refunded = BTreeMap::new();
1301 for (epoch_index, share) in shares {
1302 if epoch_index > pricing_epoch_index {
1303 *refunded
1304 .entry(epoch_index.max(current_epoch_index))
1305 .or_insert(0) -= share;
1306 }
1307 }
1308 refunded
1309 }
1310
1311 fn claw_back(
1312 refund_amount: Credits,
1313 start_epoch_index: EpochIndex,
1314 pricing_epoch_index: EpochIndex,
1315 current_epoch_index: EpochIndex,
1316 ) -> BTreeMap<EpochIndex, SignedCredits> {
1317 let mut credits_per_epochs = SignedCreditsPerEpoch::default();
1318 subtract_refunds_priced_in_epoch_from_epoch_credits_collection(
1319 &mut credits_per_epochs,
1320 refund_amount,
1321 start_epoch_index,
1322 pricing_epoch_index,
1323 current_epoch_index,
1324 EPOCHS_PER_ERA,
1325 )
1326 .expect("should subtract the refund");
1327 credits_per_epochs.into_iter().collect()
1328 }
1329
1330 fn priced_refund(
1331 storage_fee: Credits,
1332 start_epoch_index: EpochIndex,
1333 pricing_epoch_index: EpochIndex,
1334 ) -> Credits {
1335 calculate_storage_fee_refund_amount_and_leftovers(
1336 storage_fee,
1337 start_epoch_index,
1338 pricing_epoch_index,
1339 EPOCHS_PER_ERA,
1340 )
1341 .expect("should price the refund")
1342 .0
1343 }
1344
1345 #[test]
1346 fn should_take_each_refunded_share_back_from_its_epoch() {
1347 let storage_fee = 10_000_000_000;
1350 let refund = priced_refund(storage_fee, 1, 3);
1351
1352 let clawed_back = claw_back(refund, 1, 3, 4);
1353
1354 assert_eq!(clawed_back, refunded_shares(storage_fee, 1, 3, 4));
1355 assert_eq!(clawed_back[&4], -12_500_000);
1357 assert_eq!(
1358 clawed_back.values().sum::<SignedCredits>(),
1359 -(refund as SignedCredits)
1360 );
1361 }
1362
1363 #[test]
1364 fn should_take_the_shares_of_closed_epochs_from_the_current_epoch() {
1365 let storage_fee = 10_000_000_000;
1366 let refund = priced_refund(storage_fee, 1, 3);
1367
1368 let clawed_back = claw_back(refund, 1, 3, 6);
1370
1371 assert_eq!(clawed_back, refunded_shares(storage_fee, 1, 3, 6));
1372 assert!(!clawed_back.contains_key(&4) && !clawed_back.contains_key(&5));
1373 assert_eq!(clawed_back[&6], -3 * 12_500_000);
1374 assert_eq!(
1375 clawed_back.values().sum::<SignedCredits>(),
1376 -(refund as SignedCredits)
1377 );
1378 }
1379
1380 #[test]
1381 fn should_leave_only_rounding_to_the_current_epoch() {
1382 let storage_fee = 482 * 27_000;
1387 let refund = priced_refund(storage_fee, 1, 3);
1388 let refunded = refunded_shares(storage_fee, 1, 3, 4);
1389
1390 let clawed_back = claw_back(refund, 1, 3, 4);
1391
1392 assert_eq!(
1393 clawed_back.keys().collect::<Vec<_>>(),
1394 refunded.keys().collect::<Vec<_>>()
1395 );
1396
1397 let mut rounding = 0;
1398 for (epoch_index, share) in &refunded {
1399 if *epoch_index == 4 {
1400 continue;
1401 }
1402 let shortfall = clawed_back[epoch_index] - share;
1403 assert!(
1404 (0..=1).contains(&shortfall),
1405 "epoch {epoch_index} gave back {} for a share of {}",
1406 -clawed_back[epoch_index],
1407 -share
1408 );
1409 rounding += shortfall;
1410 }
1411
1412 assert_eq!(rounding, 1356);
1413 assert_eq!(clawed_back[&4], refunded[&4] - rounding);
1414 assert_eq!(
1415 clawed_back.values().sum::<SignedCredits>(),
1416 -(refund as SignedCredits)
1417 );
1418 }
1419
1420 #[test]
1421 fn should_match_the_current_epoch_clawback_for_a_refund_priced_in_the_current_epoch() {
1422 for (start_epoch_index, epoch_index) in [(0, 42), (1, 3), (24, 43)] {
1423 let refund = priced_refund(1_200_005, start_epoch_index, epoch_index);
1424
1425 let mut current_epoch_clawback = SignedCreditsPerEpoch::default();
1426 subtract_refunds_from_epoch_credits_collection(
1427 &mut current_epoch_clawback,
1428 refund,
1429 start_epoch_index,
1430 epoch_index,
1431 EPOCHS_PER_ERA,
1432 )
1433 .expect("should subtract the refund");
1434
1435 assert_eq!(
1436 claw_back(refund, start_epoch_index, epoch_index, epoch_index),
1437 current_epoch_clawback.into_iter().collect()
1438 );
1439 }
1440 }
1441
1442 #[test]
1443 fn should_take_the_last_share_of_the_storage_window_from_the_current_epoch() {
1444 let window_end_epoch_index = PERPETUAL_STORAGE_ERAS * EPOCHS_PER_ERA;
1447 let refund = priced_refund(10_000_000_000, 0, window_end_epoch_index - 2);
1448 assert_eq!(refund, 312_500);
1449
1450 let clawed_back = claw_back(
1451 refund,
1452 0,
1453 window_end_epoch_index - 2,
1454 window_end_epoch_index - 1,
1455 );
1456
1457 assert_eq!(
1458 clawed_back,
1459 BTreeMap::from([(window_end_epoch_index - 1, -312_500)])
1460 );
1461 }
1462
1463 #[test]
1464 fn should_take_a_refund_priced_past_the_storage_window_from_the_current_epoch() {
1465 let window_end_epoch_index = PERPETUAL_STORAGE_ERAS * EPOCHS_PER_ERA;
1468
1469 let clawed_back =
1470 claw_back(1_000, 0, window_end_epoch_index - 1, window_end_epoch_index);
1471
1472 assert_eq!(
1473 clawed_back,
1474 BTreeMap::from([(window_end_epoch_index, -1_000)])
1475 );
1476 }
1477
1478 #[test]
1479 fn should_take_a_refund_priced_before_its_storage_epoch_from_the_current_epoch() {
1480 assert_eq!(claw_back(1_000, 5, 3, 4), BTreeMap::from([(4, -1_000)]));
1481 }
1482
1483 #[test]
1484 fn should_take_nothing_for_a_zero_refund() {
1485 assert!(claw_back(0, 1, 3, 4).is_empty());
1486 }
1487
1488 #[test]
1489 fn should_return_an_error_when_the_pricing_epoch_is_the_last_epoch_index() {
1490 let result = subtract_refunds_priced_in_epoch_from_epoch_credits_collection(
1491 &mut SignedCreditsPerEpoch::default(),
1492 1_000,
1493 0,
1494 EpochIndex::MAX,
1495 EpochIndex::MAX,
1496 EPOCHS_PER_ERA,
1497 );
1498
1499 assert!(matches!(result, Err(ProtocolError::Overflow(_))));
1500 }
1501 }
1502
1503 mod calculate_storage_fee_refund_amount_and_leftovers {
1504 use super::*;
1505
1506 #[test]
1507 fn should_calculate_amount_and_leftovers() {
1508 let storage_fee = 10000;
1509
1510 let (amount, leftovers) = calculate_storage_fee_refund_amount_and_leftovers(
1511 storage_fee,
1512 GENESIS_EPOCH_INDEX + 1,
1513 2,
1514 20,
1515 )
1516 .expect("should distribute storage fee");
1517
1518 let first_two_epochs_amount = 50;
1519
1520 assert_eq!(leftovers, 400);
1521 assert_eq!(amount, storage_fee - leftovers - first_two_epochs_amount);
1522 }
1523
1524 #[test]
1525 fn should_return_zero_amount_and_zero_leftovers_for_zero_storage_fee() {
1526 let (amount, leftovers) =
1527 calculate_storage_fee_refund_amount_and_leftovers(0, GENESIS_EPOCH_INDEX, 10, 20)
1528 .expect("should handle zero storage fee");
1529
1530 assert_eq!(amount, 0);
1531 assert_eq!(leftovers, 0);
1532 }
1533
1534 #[test]
1535 fn should_return_zero_refund_when_start_epoch_equals_current_epoch() {
1536 let storage_fee = 1000000;
1539 let epoch = 0;
1540
1541 let (amount, leftovers) =
1542 calculate_storage_fee_refund_amount_and_leftovers(storage_fee, epoch, epoch, 20)
1543 .expect("should distribute storage fee");
1544
1545 assert_eq!(amount, storage_fee - 2500 - leftovers);
1548 }
1549
1550 #[test]
1551 fn should_calculate_correctly_with_non_genesis_start() {
1552 let storage_fee = 500000;
1553 let start = 100;
1554 let current = 110;
1555
1556 let (amount, leftovers) =
1557 calculate_storage_fee_refund_amount_and_leftovers(storage_fee, start, current, 20)
1558 .expect("should distribute storage fee");
1559
1560 assert_eq!(
1562 amount + (storage_fee - amount - leftovers) + leftovers,
1563 storage_fee
1564 );
1565 assert!(amount < storage_fee);
1567 assert!(leftovers < storage_fee);
1568 }
1569
1570 #[test]
1571 fn should_handle_large_epoch_gap() {
1572 let storage_fee = 10_000_000;
1574 let start = 0;
1575 let current = 500; let (amount, leftovers) =
1578 calculate_storage_fee_refund_amount_and_leftovers(storage_fee, start, current, 20)
1579 .expect("should handle large epoch gap");
1580
1581 assert!(amount < storage_fee / 2);
1583 assert!(leftovers < storage_fee);
1584 }
1585 }
1586
1587 mod additional_original_removed_credits_multiplier_from {
1588 use super::*;
1589
1590 #[test]
1591 fn should_create_multiplier_of_one_when_no_epochs_have_passed() {
1592 let multiplier = original_removed_credits_multiplier_from(0, 0, 20)
1595 .expect("multiplier within perpetual storage window");
1596 assert_eq!(multiplier, dec!(1));
1597 }
1598
1599 #[test]
1600 fn should_increase_multiplier_as_more_epochs_pass() {
1601 let m1 = original_removed_credits_multiplier_from(0, 5, 20)
1602 .expect("multiplier within perpetual storage window");
1603 let m2 = original_removed_credits_multiplier_from(0, 10, 20)
1604 .expect("multiplier within perpetual storage window");
1605 let m3 = original_removed_credits_multiplier_from(0, 19, 20)
1606 .expect("multiplier within perpetual storage window");
1607
1608 assert!(m1 < m2);
1610 assert!(m2 < m3);
1611 }
1612
1613 #[test]
1614 fn should_handle_era_boundary_crossing() {
1615 let m_at_boundary = original_removed_credits_multiplier_from(0, 20, 20)
1617 .expect("multiplier within perpetual storage window");
1618 let m_before_boundary = original_removed_credits_multiplier_from(0, 19, 20)
1619 .expect("multiplier within perpetual storage window");
1620 let m_after_boundary = original_removed_credits_multiplier_from(0, 21, 20)
1621 .expect("multiplier within perpetual storage window");
1622
1623 assert!(m_at_boundary > m_before_boundary);
1625 assert!(m_after_boundary > m_at_boundary);
1626 }
1627
1628 #[test]
1629 fn should_handle_different_epochs_per_era() {
1630 let m_40 = original_removed_credits_multiplier_from(0, 40, 40)
1632 .expect("multiplier within perpetual storage window");
1633 let m_20 = original_removed_credits_multiplier_from(0, 20, 20)
1635 .expect("multiplier within perpetual storage window");
1636
1637 assert_eq!(m_40, m_20);
1639 }
1640
1641 #[test]
1642 fn should_produce_same_multiplier_regardless_of_absolute_epoch_offset() {
1643 let m1 = original_removed_credits_multiplier_from(0, 15, 20)
1645 .expect("multiplier within perpetual storage window");
1646 let m2 = original_removed_credits_multiplier_from(100, 115, 20)
1647 .expect("multiplier within perpetual storage window");
1648 let m3 = original_removed_credits_multiplier_from(5000, 5015, 20)
1649 .expect("multiplier within perpetual storage window");
1650
1651 assert_eq!(m1, m2);
1652 assert_eq!(m2, m3);
1653 }
1654
1655 #[test]
1656 fn should_return_error_instead_of_panicking_when_window_fully_elapsed() {
1657 let result = original_removed_credits_multiplier_from(0, 1000, 20);
1664 assert!(
1665 matches!(result, Err(ProtocolError::DivideByZero(_))),
1666 "expected DivideByZero error once the window is fully elapsed, got {:?}",
1667 result
1668 );
1669
1670 let restored = restore_original_removed_credits_amount(dec!(1_000_000), 0, 1000, 20);
1672 assert!(
1673 matches!(restored, Err(ProtocolError::DivideByZero(_))),
1674 "restore_original_removed_credits_amount must propagate the error, got {:?}",
1675 restored
1676 );
1677 }
1678
1679 #[test]
1680 fn should_return_error_when_repayment_epoch_precedes_start() {
1681 let result = original_removed_credits_multiplier_from(10, 5, 20);
1684 assert!(
1685 matches!(result, Err(ProtocolError::Overflow(_))),
1686 "expected Overflow error on underflowing epoch difference, got {:?}",
1687 result
1688 );
1689 }
1690 }
1691
1692 mod additional_restore_original_removed_credits_amount {
1693 use super::*;
1694
1695 #[test]
1696 fn should_restore_to_original_when_no_epochs_passed() {
1697 let refund = dec!(1000000);
1699 let restored = restore_original_removed_credits_amount(refund, 0, 0, 20)
1700 .expect("should not overflow");
1701 assert_eq!(restored, refund);
1702 }
1703
1704 #[test]
1705 fn should_increase_amount_when_epochs_have_passed() {
1706 let refund = dec!(500000);
1708 let restored = restore_original_removed_credits_amount(refund, 0, 10, 20)
1709 .expect("should not overflow");
1710 assert!(restored > refund);
1711 }
1712
1713 #[test]
1714 fn should_handle_zero_refund_amount() {
1715 let restored = restore_original_removed_credits_amount(dec!(0), 0, 10, 20)
1716 .expect("should handle zero");
1717 assert_eq!(restored, dec!(0));
1718 }
1719 }
1720
1721 mod additional_refund_storage_fee_to_epochs_map {
1722 use super::*;
1723
1724 #[test]
1725 fn should_return_zero_leftovers_for_zero_storage_fee() {
1726 let leftovers = refund_storage_fee_to_epochs_map(0, 0, 1, |_, _| Ok(()), 20)
1727 .expect("should handle zero");
1728 assert_eq!(leftovers, 0);
1729 }
1730
1731 #[test]
1732 fn should_skip_epochs_before_skip_until_index() {
1733 let storage_fee = 1000000u64;
1734 let start = 0u16;
1735 let skip_until = 10u16;
1736
1737 let mut min_epoch_seen = u16::MAX;
1738
1739 let _leftovers = refund_storage_fee_to_epochs_map(
1740 storage_fee,
1741 start,
1742 skip_until,
1743 |epoch_index, _amount| {
1744 if epoch_index < min_epoch_seen {
1745 min_epoch_seen = epoch_index;
1746 }
1747 Ok(())
1748 },
1749 20,
1750 )
1751 .expect("should distribute refund");
1752
1753 assert!(min_epoch_seen >= skip_until);
1755 }
1756
1757 #[test]
1758 fn should_distribute_to_single_remaining_epoch_in_era() {
1759 let storage_fee = 100000u64;
1762
1763 let mut epoch_count = 0u32;
1764
1765 let leftovers = refund_storage_fee_to_epochs_map(
1766 storage_fee,
1767 0,
1768 19,
1769 |_epoch_index, _amount| {
1770 epoch_count += 1;
1771 Ok(())
1772 },
1773 20,
1774 )
1775 .expect("should distribute");
1776
1777 assert_eq!(epoch_count, 981);
1779 assert!(leftovers < storage_fee);
1780 }
1781
1782 #[test]
1783 fn should_handle_skip_at_era_boundary() {
1784 let storage_fee = 500000u64;
1786 let start = 0u16;
1787 let skip_until = 20u16; let mut epochs_called = Vec::new();
1790
1791 let _leftovers = refund_storage_fee_to_epochs_map(
1792 storage_fee,
1793 start,
1794 skip_until,
1795 |epoch_index, _amount| {
1796 epochs_called.push(epoch_index);
1797 Ok(())
1798 },
1799 20,
1800 )
1801 .expect("should distribute");
1802
1803 assert_eq!(*epochs_called.first().unwrap(), skip_until);
1805 assert_eq!(epochs_called.len(), 980);
1807 }
1808 }
1809}