Skip to main content

dpp/fee/epoch/
distribution.rs

1// MIT LICENSE
2//
3// Copyright (c) 2021 Dash Core Group
4//
5// Permission is hereby granted, free of charge, to any
6// person obtaining a copy of this software and associated
7// documentation files (the "Software"), to deal in the
8// Software without restriction, including without
9// limitation the rights to use, copy, modify, merge,
10// publish, distribute, sublicense, and/or sell copies of
11// the Software, and to permit persons to whom the Software
12// is furnished to do so, subject to the following
13// conditions:
14//
15// The above copyright notice and this permission notice
16// shall be included in all copies or substantial portions
17// of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
20// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
22// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
23// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
26// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27// DEALINGS IN THE SOFTWARE.
28//
29
30//! Storage fee distribution into epochs
31//!
32//! Data is stored in Platform "forever" currently, which is 50 eras (50 years by default).
33//! To incentivise masternodes to continue store and serve this data,
34//! payments are distributed for entire period split into epochs.
35//! Every epoch, new aggregated storage fees are distributed among epochs
36//! and masternodes receive payouts for previous epoch.
37//!
38
39use 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// TODO: Should be updated from the doc
50
51/// The amount of the perpetual storage fee to be paid out to masternodes per era. Adds up to 1.
52#[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
69/// Distributes storage fees to epochs into `SignedCreditsPerEpoch` and returns leftovers
70pub 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
96/// Distributes refunds to epochs into `SignedCreditsPerEpoch` and returns leftovers
97/// It skips epochs up to specified `skip_until_epoch_index`
98pub 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    // We need to remove the leftovers from the current epoch
126    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
139/// Subtracts a storage refund from `credits_per_epochs`, taking from each epoch the share the
140/// refund returned for it.
141///
142/// `calculate_storage_fee_refund_amount_and_leftovers` prices a refund in the epoch the data is
143/// removed in (`pricing_epoch_index`) and returns the shares of every epoch after it. The
144/// clawback runs later, at an epoch change into `current_epoch_index`, so the original storage
145/// fee is restored from `pricing_epoch_index + 1` and subtracted per epoch from there. Epochs
146/// between the pricing epoch and the current one had no block (a halt skipped them), so their
147/// shares are taken from the current epoch, as are the leftovers. A refund priced at the end of
148/// the storage window has no epoch left to restore over and is taken whole from the current
149/// epoch.
150pub 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    // Data is removed in or after the epoch it was stored in, so its refund is priced there
166    // too. Should a refund say otherwise, it is taken whole from the current epoch rather than
167    // restored from before the epoch it was stored in.
168    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
209/// Calculates leftovers and amount of credits by distributing storage fees to epochs
210pub 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    // `start_repayment_from_epoch_index` is `current_epoch_index + 1` and
240    // `start_epoch_index` is the (earlier) epoch the storage was originally
241    // paid in, so this subtraction normally cannot underflow. Guard it anyway
242    // so corrupted/unexpected inputs return an error rather than panicking
243    // (debug) or wrapping (release) on the consensus path.
244    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(&current_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    // `FEE_DISTRIBUTION_TABLE` has exactly `PERPETUAL_STORAGE_ERAS` entries.
269    // Once the refund's original storage epoch is at least that whole window
270    // behind the repayment epoch (`current_era >= PERPETUAL_STORAGE_ERAS`),
271    // every table era compares `Ordering::Less`, the iterator yields nothing,
272    // and `ratio_used` sums to zero. `rust_decimal::Decimal`'s `/` operator
273    // PANICS on a zero divisor (unlike integer/`f64` division and unlike its
274    // own `checked_div`), which on the consensus path would abort every node
275    // simultaneously and halt the chain. Return a propagable error instead.
276    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
285/// Let's imagine that we are refunding something from epoch 5
286/// We are at Epoch 12
287/// The refund amount is from Epoch 13 (current + 1) to Epoch 1005 (5 + 1000)
288/// We need to figure out the amount extra those 8 costed
289fn 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
308/// Distributes storage fees to epochs and call function for each epoch.
309/// Returns leftovers
310fn 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            //todo: this can lead to many many calls once we are further along in epochs
345            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
358/// Distributes recovered by multiplier original removed
359/// credits to epochs and call function for each epoch.
360/// Leftovers are added to current epoch
361fn 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    // The perpetual storage window for this data ends `PERPETUAL_STORAGE_ERAS`
384    // eras after `start_epoch_index`. Once the distribution epoch reaches or
385    // passes that end (`start_era >= PERPETUAL_STORAGE_ERAS`) there are no future
386    // epoch pools left to claw the refund back from: the per-era loop below is
387    // empty, and `original_removed_credits_multiplier_from` returns
388    // `DivideByZero` (`ratio_used == 0`) computing a multiplier that is never
389    // used. Returning that error here still halts the chain — it propagates up
390    // the consensus path to a Tenderdash `ResponseException`. Instead treat the
391    // whole refund as leftovers so the caller removes it from the current
392    // epoch's pool, keeping the chain live.
393    //
394    // This is reachable on the consensus path: the refund amount is computed at
395    // the removal epoch (`FeeRefunds::from_storage_removal`) but this clawback
396    // runs at least one epoch later, at the next epoch change, against the
397    // then-current epoch. So a non-zero refund for data removed just before
398    // expiry can be distributed just after the window boundary is crossed.
399    if start_era >= PERPETUAL_STORAGE_ERAS {
400        return Ok(storage_fee);
401    }
402
403    // Let's imagine that we are refunding something from epoch 5
404    // We are at Epoch 12
405    // The refund amount is from Epoch 13 (current + 1) to Epoch 1005 (5 + 1000)
406    // We need to figure out the amount extra those 8 costed
407    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            // the multiplier should be
460            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            // there were 19 epochs
472            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); //20*50
549            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            // check leftover
573            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            // check leftover
592            assert_eq!(leftovers, 180);
593
594            // compare them with reference table for 20 epochs per era (1000)
595            #[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            /*
675
676            Repeat distribution to ensure deterministic results
677
678             */
679
680            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 that all the values doubled meaning that distribution is reproducible
689            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            // Example: Bob inserted an element into the tree
709            // He paid slightly more than 1.2 Million credits for this operation that happened at epoch 0.
710            // At epoch 42 we are asking for a refund.
711            // The refund is 1.07 Million credits that were left from the 1.2.
712
713            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            // compare them with reference table
740            // we expect to get 0 for the change of the current epochs balance
741            // this is because there was only 1 refund so leftovers wouldn't have any effect
742            #[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        /// Regression test for the decoupled-epoch storage-fee refund
831        /// divide-by-zero (chain-halt) bug.
832        ///
833        /// In production the refund AMOUNT and the clawback DISTRIBUTION are
834        /// computed at two different epochs:
835        ///   * `FeeRefunds::from_storage_removal` computes the amount at the
836        ///     removal epoch (`c_store`) and persists it keyed by the original
837        ///     write epoch — the removal epoch is then discarded.
838        ///   * one epoch later, at the next epoch change,
839        ///     `add_distribute_storage_fee_to_epochs_operations_v0` consumes it
840        ///     via `subtract_refunds_from_epoch_credits_collection` using the
841        ///     *new* current epoch (`c_consume = c_store + 1`), which re-derives
842        ///     the `1 / ratio_used` multiplier against a different window
843        ///     position.
844        ///
845        /// Every other test in this module feeds the SAME epoch to both calls
846        /// (the self-consistent case), which is exactly why this was never
847        /// exercised. Here we reproduce the real wiring: data written at epoch
848        /// 0 is removed two epochs before its 50-era window expires — so the
849        /// refund is legitimately non-zero (it is the share of the single
850        /// remaining in-window epoch) — and then distributed one epoch later,
851        /// precisely as the window boundary is crossed. At that point
852        /// `current_era == PERPETUAL_STORAGE_ERAS`, so `ratio_used == 0`.
853        /// Without the `start_era >= PERPETUAL_STORAGE_ERAS` guard in
854        /// `refund_storage_fee_to_epochs_map`, this returns `DivideByZero` (or,
855        /// before that error existed, panicked) and halts every node.
856        #[test]
857        fn should_not_halt_when_refund_distributed_as_window_boundary_is_crossed() {
858            const EPOCHS_PER_ERA: u16 = 40; // production default
859            const WRITE_EPOCH: EpochIndex = 0;
860
861            // The perpetual storage window for data written at WRITE_EPOCH spans
862            // epochs [0, EPOCHS_PER_ERA * PERPETUAL_STORAGE_ERAS) = [0, 2000).
863            let window_end_epoch = WRITE_EPOCH + EPOCHS_PER_ERA * PERPETUAL_STORAGE_ERAS; // 2000
864
865            // Remove the data two epochs before the window fully elapses, so the
866            // refund still covers exactly one in-window epoch (the final one,
867            // 1999) and is therefore NON-ZERO.
868            let removal_epoch = window_end_epoch - 2; // c_store = 1998
869
870            // One epoch change later the refund is distributed with the THEN
871            // current epoch index (the natural +1-epoch lag). This value flows
872            // into original_removed_credits_multiplier_from via skip_until =
873            // current + 1, which now spans the full window.
874            let distribution_epoch = removal_epoch + 1; // c_consume = 1999
875
876            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            // Without the guard this returns Err(DivideByZero) (which still
896            // halts the chain via Tenderdash) — or panicked before that error
897            // existed — inside original_removed_credits_multiplier_from, because
898            // the multiplier is recomputed at the distribution epoch and now
899            // spans the entire perpetual-storage window.
900            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            // With the window fully elapsed there are no future epoch pools left
910            // to claw the refund back from, so the entire refund must come out of
911            // the current (distribution) epoch's pool — and nothing else should
912            // be touched.
913            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                // it's not going to be completely perfect but it's good enough
947                // there were 24 epochs, on average we would be 12 off
948                // while we could incorporate this offset into the multiplier it would
949                // be overkill for such low credit amounts
950                assert!(
951                    (Decimal::from(refund_amount) * multiplier)
952                        .abs_sub(&Decimal::from(original_storage_fee - leftovers))
953                        < dec!(100)
954                );
955
956                // we do however want to make sure the multiplier makes things smaller
957                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                // compare them with reference table
973                // we expect to get 0 for the change of the current epochs balance
974                // this is because there was only 1 refund so leftovers wouldn't have any effect
975                #[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            // First_refund
1083
1084            // Example: Bob inserted an element into the tree
1085            // He paid slightly more than 1.2 Million credits for this operation that happened at epoch 0.
1086            // At epoch 42 we are asking for a refund.
1087            // The refund is 1.07 Million credits that were left from the 1.2.
1088
1089            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            // Second_refund
1114
1115            // Example: Bob inserted an element into the tree
1116            // He paid slightly more than 3.4 Million credits for this operation that happened at epoch 0.
1117            // At epoch 42 we are asking for a refund.
1118
1119            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            // it's not going to be completely perfect but it's good enough
1142            // there were 24 epochs, on average we would be 12 off
1143            // while we could incorporate this offset into the multiplier it would
1144            // be overkill for such low credit amounts
1145            assert!(
1146                (Decimal::from(second_refund_amount) * multiplier)
1147                    .abs_sub(&Decimal::from(second_original_storage_fee - leftovers))
1148                    < dec!(100)
1149            );
1150
1151            // we do however want to make sure the multiplier makes things smaller
1152            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            // compare them with reference table
1166            // we expect to get 0 for the change of the current epochs balance
1167            // this is because there was only 1 refund so leftovers wouldn't have any effect
1168            #[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        /// The shares a refund priced in `pricing_epoch_index` returned, as the pools they must
1283        /// come back from: each epoch after the pricing epoch gives its own share, and the
1284        /// epochs before `current_epoch_index` give theirs through the current epoch.
1285        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            // Every share of this fee is a whole number of credits, so the fee restored from
1348            // the refund is the one it was priced from
1349            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            // The current epoch gives back its own share, not only the leftovers
1356            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            // Epochs 4 and 5 were skipped
1369            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            // The shares of a 482 byte removal are not whole credits. The refund is a sum of
1383            // floored shares, and the fee restored from it through the refunded fraction comes
1384            // out slightly lower, so a later epoch gives back its share or one credit less and
1385            // the current epoch gives back the rest
1386            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            // Data stored in epoch 0 is paid for until epoch 1999. Removed in epoch 1998, it
1445            // is refunded the share of epoch 1999 alone, clawed back at the change into it
1446            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            // The pricing epoch is the last epoch of the window, so no epoch after it is left
1466            // to restore the refund over
1467            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            // When start == current, skipped_amount covers epoch 0 only (the one epoch
1537            // between start_epoch_index and current_epoch_index + 1 = 1).
1538            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            // Only epoch 0 is skipped (cost = floor(1000000 * 0.05 / 20) = 2500).
1546            // The refund amount is everything except the skipped epoch and leftovers.
1547            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            // Verify invariant: amount + skipped + leftovers = storage_fee
1561            assert_eq!(
1562                amount + (storage_fee - amount - leftovers) + leftovers,
1563                storage_fee
1564            );
1565            // Amount must be less than total
1566            assert!(amount < storage_fee);
1567            assert!(leftovers < storage_fee);
1568        }
1569
1570        #[test]
1571        fn should_handle_large_epoch_gap() {
1572            // current_epoch far from start
1573            let storage_fee = 10_000_000;
1574            let start = 0;
1575            let current = 500; // halfway through the 1000 total epochs
1576
1577            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            // Refund amount should be smaller because most epochs have been paid out
1582            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            // When start_repayment == start, paid_epochs = 0, ratio_used = full table sum = 1.0
1593            // So multiplier = 1/1 = 1
1594            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            // More paid epochs means less ratio remaining, so multiplier increases
1609            assert!(m1 < m2);
1610            assert!(m2 < m3);
1611        }
1612
1613        #[test]
1614        fn should_handle_era_boundary_crossing() {
1615            // paid_epochs = 20 means we enter the second era exactly
1616            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            // At the boundary, the entire first era (0.05) is consumed
1624            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            // With 40 epochs per era (the default), 40 paid epochs = 1 full era
1631            let m_40 = original_removed_credits_multiplier_from(0, 40, 40)
1632                .expect("multiplier within perpetual storage window");
1633            // With 20 epochs per era, 20 paid epochs = 1 full era
1634            let m_20 = original_removed_credits_multiplier_from(0, 20, 20)
1635                .expect("multiplier within perpetual storage window");
1636
1637            // Both consume exactly one full era of 0.05, so multipliers should be equal
1638            assert_eq!(m_40, m_20);
1639        }
1640
1641        #[test]
1642        fn should_produce_same_multiplier_regardless_of_absolute_epoch_offset() {
1643            // The multiplier depends only on the difference, not absolute indices
1644            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            // PERPETUAL_STORAGE_ERAS == 50 and the distribution table has 50
1658            // entries. With epochs_per_era = 20, `current_era` reaches 50 (the
1659            // full window) at paid_epochs = 50 * 20 = 1000, at which point every
1660            // table era compares `Less`, `ratio_used` sums to zero, and the old
1661            // code panicked on `dec!(1) / 0`. It must now return a DivideByZero
1662            // error so the consensus path can propagate it instead of aborting.
1663            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            // The caller propagates the error rather than panicking.
1671            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            // Defensive guard: a repayment epoch before the original storage
1682            // epoch must not underflow the `paid_epochs` subtraction.
1683            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            // If start_repayment == start, multiplier is 1.0, so restored == refund_amount
1698            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            // After some epochs, the multiplier > 1, so restored > refund
1707            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            // The first epoch called should be >= skip_until
1754            assert!(min_epoch_seen >= skip_until);
1755        }
1756
1757        #[test]
1758        fn should_distribute_to_single_remaining_epoch_in_era() {
1759            // skip_until is 19 (last epoch of era 0), start is 0
1760            // This means only 1 epoch remains in era 0
1761            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            // Total epochs = (1000 - 19) = 981 epochs should be called
1778            assert_eq!(epoch_count, 981);
1779            assert!(leftovers < storage_fee);
1780        }
1781
1782        #[test]
1783        fn should_handle_skip_at_era_boundary() {
1784            // skip_until exactly at era 1 start
1785            let storage_fee = 500000u64;
1786            let start = 0u16;
1787            let skip_until = 20u16; // era 1 starts here
1788
1789            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            // First epoch called should be exactly skip_until
1804            assert_eq!(*epochs_called.first().unwrap(), skip_until);
1805            // Total = 1000 - 20 = 980
1806            assert_eq!(epochs_called.len(), 980);
1807        }
1808    }
1809}