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/// Calculates leftovers and amount of credits by distributing storage fees to epochs
140pub fn calculate_storage_fee_refund_amount_and_leftovers(
141    storage_fee: Credits,
142    start_epoch_index: EpochIndex,
143    current_epoch_index: EpochIndex,
144    epochs_per_era: u16,
145) -> Result<(DistributionAmount, DistributionLeftovers), ProtocolError> {
146    let mut skipped_amount = 0;
147
148    let leftovers = distribution_storage_fee_to_epochs_map(
149        storage_fee,
150        start_epoch_index,
151        |epoch_index, epoch_fee_share| {
152            if epoch_index < current_epoch_index + 1 {
153                skipped_amount += epoch_fee_share;
154            }
155
156            Ok(())
157        },
158        epochs_per_era,
159    )?;
160
161    Ok((storage_fee - skipped_amount - leftovers, leftovers))
162}
163
164fn original_removed_credits_multiplier_from(
165    start_epoch_index: EpochIndex,
166    start_repayment_from_epoch_index: EpochIndex,
167    epochs_per_era: u16,
168) -> Result<Decimal, ProtocolError> {
169    // `start_repayment_from_epoch_index` is `current_epoch_index + 1` and
170    // `start_epoch_index` is the (earlier) epoch the storage was originally
171    // paid in, so this subtraction normally cannot underflow. Guard it anyway
172    // so corrupted/unexpected inputs return an error rather than panicking
173    // (debug) or wrapping (release) on the consensus path.
174    let paid_epochs = start_repayment_from_epoch_index
175        .checked_sub(start_epoch_index)
176        .ok_or(ProtocolError::Overflow(
177            "start repayment epoch is before the original storage epoch",
178        ))?;
179
180    let current_era = (paid_epochs / epochs_per_era) as usize;
181
182    let ratio_used: Decimal =
183        FEE_DISTRIBUTION_TABLE
184            .iter()
185            .enumerate()
186            .filter_map(|(era, epoch_multiplier)| match era.cmp(&current_era) {
187                Ordering::Less => None,
188                Ordering::Equal => {
189                    let amount_epochs_left_in_era = epochs_per_era - paid_epochs % epochs_per_era;
190                    Some(epoch_multiplier.mul(
191                        Decimal::from(amount_epochs_left_in_era) / Decimal::from(epochs_per_era),
192                    ))
193                }
194                Ordering::Greater => Some(*epoch_multiplier),
195            })
196            .sum();
197
198    // `FEE_DISTRIBUTION_TABLE` has exactly `PERPETUAL_STORAGE_ERAS` entries.
199    // Once the refund's original storage epoch is at least that whole window
200    // behind the repayment epoch (`current_era >= PERPETUAL_STORAGE_ERAS`),
201    // every table era compares `Ordering::Less`, the iterator yields nothing,
202    // and `ratio_used` sums to zero. `rust_decimal::Decimal`'s `/` operator
203    // PANICS on a zero divisor (unlike integer/`f64` division and unlike its
204    // own `checked_div`), which on the consensus path would abort every node
205    // simultaneously and halt the chain. Return a propagable error instead.
206    if ratio_used.is_zero() {
207        return Err(ProtocolError::DivideByZero(
208            "storage fee refund is older than the entire perpetual storage window",
209        ));
210    }
211
212    Ok(dec!(1) / ratio_used)
213}
214
215/// Let's imagine that we are refunding something from epoch 5
216/// We are at Epoch 12
217/// The refund amount is from Epoch 13 (current + 1) to Epoch 1005 (5 + 1000)
218/// We need to figure out the amount extra those 8 costed
219fn restore_original_removed_credits_amount(
220    refund_amount: Decimal,
221    start_epoch_index: EpochIndex,
222    start_repayment_from_epoch_index: EpochIndex,
223    epochs_per_era: u16,
224) -> Result<Decimal, ProtocolError> {
225    let multiplier = original_removed_credits_multiplier_from(
226        start_epoch_index,
227        start_repayment_from_epoch_index,
228        epochs_per_era,
229    )?;
230
231    refund_amount
232        .checked_mul(multiplier)
233        .ok_or(ProtocolError::Overflow(
234            "overflow when multiplying with the multiplier (this should be impossible)",
235        ))
236}
237
238/// Distributes storage fees to epochs and call function for each epoch.
239/// Returns leftovers
240fn distribution_storage_fee_to_epochs_map<F>(
241    storage_fee: Credits,
242    start_epoch_index: EpochIndex,
243    mut map_function: F,
244    epochs_per_era: u16,
245) -> Result<DistributionLeftovers, ProtocolError>
246where
247    F: FnMut(EpochIndex, Credits) -> Result<(), ProtocolError>,
248{
249    if storage_fee == 0 {
250        return Ok(0);
251    }
252
253    let storage_fee_dec: Decimal = storage_fee.into();
254
255    let mut distribution_leftover_credits = storage_fee;
256
257    let epochs_per_era_dec = Decimal::from(epochs_per_era);
258
259    for era in 0..PERPETUAL_STORAGE_ERAS {
260        let distribution_for_that_era_ratio = FEE_DISTRIBUTION_TABLE[era as usize];
261
262        let era_fee_share = storage_fee_dec * distribution_for_that_era_ratio;
263
264        let epoch_fee_share_dec = era_fee_share / epochs_per_era_dec;
265
266        let epoch_fee_share: Credits = epoch_fee_share_dec
267            .floor()
268            .to_u64()
269            .ok_or_else(|| ProtocolError::Overflow("storage fees are not fitting in a u64"))?;
270
271        let era_start_epoch_index = start_epoch_index + epochs_per_era * era;
272
273        for epoch_index in era_start_epoch_index..era_start_epoch_index + epochs_per_era {
274            //todo: this can lead to many many calls once we are further along in epochs
275            map_function(epoch_index, epoch_fee_share)?;
276
277            distribution_leftover_credits = distribution_leftover_credits
278                .checked_sub(epoch_fee_share)
279                .ok_or(ProtocolError::Overflow(
280                    "leftovers bigger than initial value",
281                ))?;
282        }
283    }
284
285    Ok(distribution_leftover_credits)
286}
287
288/// Distributes recovered by multiplier original removed
289/// credits to epochs and call function for each epoch.
290/// Leftovers are added to current epoch
291fn refund_storage_fee_to_epochs_map<F>(
292    storage_fee: Credits,
293    start_epoch_index: EpochIndex,
294    skip_until_epoch_index: EpochIndex,
295    mut map_function: F,
296    epochs_per_era: u16,
297) -> Result<DistributionLeftovers, ProtocolError>
298where
299    F: FnMut(EpochIndex, Credits) -> Result<(), ProtocolError>,
300{
301    if storage_fee == 0 {
302        return Ok(0);
303    }
304
305    let storage_fee_dec: Decimal = storage_fee.into();
306
307    let mut distribution_leftover_credits = storage_fee;
308
309    let epochs_per_era_dec = Decimal::from(epochs_per_era);
310
311    let start_era: u16 = (skip_until_epoch_index - start_epoch_index) / epochs_per_era;
312
313    // The perpetual storage window for this data ends `PERPETUAL_STORAGE_ERAS`
314    // eras after `start_epoch_index`. Once the distribution epoch reaches or
315    // passes that end (`start_era >= PERPETUAL_STORAGE_ERAS`) there are no future
316    // epoch pools left to claw the refund back from: the per-era loop below is
317    // empty, and `original_removed_credits_multiplier_from` returns
318    // `DivideByZero` (`ratio_used == 0`) computing a multiplier that is never
319    // used. Returning that error here still halts the chain — it propagates up
320    // the consensus path to a Tenderdash `ResponseException`. Instead treat the
321    // whole refund as leftovers so the caller removes it from the current
322    // epoch's pool, keeping the chain live.
323    //
324    // This is reachable on the consensus path: the refund amount is computed at
325    // the removal epoch (`FeeRefunds::from_storage_removal`) but this clawback
326    // runs at least one epoch later, at the next epoch change, against the
327    // then-current epoch. So a non-zero refund for data removed just before
328    // expiry can be distributed just after the window boundary is crossed.
329    if start_era >= PERPETUAL_STORAGE_ERAS {
330        return Ok(storage_fee);
331    }
332
333    // Let's imagine that we are refunding something from epoch 5
334    // We are at Epoch 12
335    // The refund amount is from Epoch 13 (current + 1) to Epoch 1005 (5 + 1000)
336    // We need to figure out the amount extra those 8 costed
337    let estimated_storage_fee_dec = restore_original_removed_credits_amount(
338        storage_fee_dec,
339        start_epoch_index,
340        skip_until_epoch_index,
341        epochs_per_era,
342    )?;
343
344    for era in start_era..PERPETUAL_STORAGE_ERAS {
345        let distribution_for_that_era_ratio = FEE_DISTRIBUTION_TABLE[era as usize];
346
347        let estimated_era_fee_share = estimated_storage_fee_dec * distribution_for_that_era_ratio;
348
349        let estimated_epoch_fee_share_dec = estimated_era_fee_share / epochs_per_era_dec;
350
351        let estimated_epoch_fee_share: Credits = estimated_epoch_fee_share_dec
352            .floor()
353            .to_u64()
354            .ok_or_else(|| ProtocolError::Overflow("storage fees are not fitting in a u64"))?;
355
356        let era_start_epoch_index = if era == start_era {
357            skip_until_epoch_index
358        } else {
359            start_epoch_index + epochs_per_era * era
360        };
361
362        let era_end_epoch_index = start_epoch_index + ((era + 1) * epochs_per_era);
363
364        for epoch_index in era_start_epoch_index..era_end_epoch_index {
365            map_function(epoch_index, estimated_epoch_fee_share)?;
366
367            distribution_leftover_credits = distribution_leftover_credits
368                .checked_sub(estimated_epoch_fee_share)
369                .ok_or(ProtocolError::Overflow(
370                    "leftovers bigger than initial value",
371                ))?;
372        }
373    }
374    Ok(distribution_leftover_credits)
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380    use crate::fee::epoch::GENESIS_EPOCH_INDEX;
381    use rust_decimal::Decimal;
382    use rust_decimal_macros::dec;
383
384    mod original_removed_credits_multiplier_from {
385        use super::*;
386
387        #[test]
388        fn should_create_multiplier_for_epochs_since_the_beginning() {
389            // the multiplier should be
390            let epoch_0_cost = dec!(0.05000) / dec!(20.0);
391            let multiplier_should_be = dec!(1.0) / (dec!(1.0) - epoch_0_cost);
392
393            let multiplier = original_removed_credits_multiplier_from(0, 1, 20)
394                .expect("multiplier within perpetual storage window");
395
396            assert_eq!(multiplier_should_be, multiplier);
397        }
398
399        #[test]
400        fn should_create_multiplier_for_epochs_since_24_and_repaid_since_43() {
401            // there were 19 epochs
402            let epoch_0_cost = dec!(19.0) * dec!(0.05000) / dec!(20.0);
403
404            let multiplier_should_be = dec!(1.0) / (dec!(1.0) - epoch_0_cost);
405
406            let multiplier = original_removed_credits_multiplier_from(24, 43, 20)
407                .expect("multiplier within perpetual storage window");
408
409            assert_eq!(multiplier_should_be, multiplier);
410        }
411    }
412
413    mod fee_distribution_table {
414        use super::*;
415
416        #[test]
417        fn should_have_sum_of_1() {
418            assert_eq!(FEE_DISTRIBUTION_TABLE.iter().sum::<Decimal>(), dec!(1.0),);
419        }
420
421        #[test]
422        fn should_distribute_value() {
423            let value = Decimal::from(i64::MAX);
424
425            let calculated_value: Decimal = FEE_DISTRIBUTION_TABLE
426                .into_iter()
427                .map(|ratio| value * ratio)
428                .sum();
429
430            assert_eq!(calculated_value, value);
431        }
432    }
433
434    mod distribution_storage_fee_to_epochs_map {
435        use super::*;
436
437        #[test]
438        fn should_distribute_nothing_if_storage_fees_are_zero() {
439            let mut calls = 0;
440
441            let leftovers = distribution_storage_fee_to_epochs_map(
442                0,
443                GENESIS_EPOCH_INDEX,
444                |_, _| {
445                    calls += 1;
446
447                    Ok(())
448                },
449                20,
450            )
451            .expect("should distribute storage fee");
452
453            assert_eq!(calls, 0);
454            assert_eq!(leftovers, 0);
455        }
456
457        #[test]
458        fn should_call_function_for_each_epoch_for_50_eras_sequentially() {
459            let mut calls = 0;
460
461            let mut previous_epoch_index = -1;
462
463            let leftovers = distribution_storage_fee_to_epochs_map(
464                100000,
465                GENESIS_EPOCH_INDEX,
466                |epoch_index, _| {
467                    assert_eq!(epoch_index as i32, previous_epoch_index + 1);
468                    previous_epoch_index = epoch_index as i32;
469
470                    calls += 1;
471
472                    Ok(())
473                },
474                20,
475            )
476            .expect("should distribute storage fee");
477
478            assert_eq!(calls, 1000); //20*50
479            assert_eq!(leftovers, 360);
480        }
481    }
482
483    mod distribute_storage_fee_to_epochs_collection {
484        use super::*;
485        use crate::balances::credits::{Creditable, MAX_CREDITS};
486        use crate::fee::SignedCredits;
487
488        #[test]
489        fn should_distribute_max_credits_value_without_overflow() {
490            let storage_fee = MAX_CREDITS;
491
492            let mut credits_per_epochs = SignedCreditsPerEpoch::default();
493
494            let leftovers = distribute_storage_fee_to_epochs_collection(
495                &mut credits_per_epochs,
496                storage_fee,
497                GENESIS_EPOCH_INDEX,
498                20,
499            )
500            .expect("should distribute storage fee");
501
502            // check leftover
503            assert_eq!(leftovers, 507);
504        }
505
506        #[test]
507        fn should_deterministically_distribute_fees() {
508            let storage_fee = 1000000;
509            let current_epoch_index = 42;
510
511            let mut credits_per_epochs = SignedCreditsPerEpoch::default();
512
513            let leftovers = distribute_storage_fee_to_epochs_collection(
514                &mut credits_per_epochs,
515                storage_fee,
516                current_epoch_index,
517                20,
518            )
519            .expect("should distribute storage fee");
520
521            // check leftover
522            assert_eq!(leftovers, 180);
523
524            // compare them with reference table for 20 epochs per era (1000)
525            #[rustfmt::skip]
526                let reference_fees: [SignedCredits; 1000] = [
527                2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500, 2500,
528                2500, 2500, 2500, 2500, 2500, 2500, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400,
529                2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2400, 2300, 2300,
530                2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300, 2300,
531                2300, 2300, 2300, 2300, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200,
532                2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2200, 2100, 2100, 2100, 2100,
533                2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100, 2100,
534                2100, 2100, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000,
535                2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 1925, 1925, 1925, 1925, 1925, 1925,
536                1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925, 1925,
537                1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850, 1850,
538                1850, 1850, 1850, 1850, 1850, 1850, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775,
539                1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1775, 1700, 1700,
540                1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700, 1700,
541                1700, 1700, 1700, 1700, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625,
542                1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1625, 1550, 1550, 1550, 1550,
543                1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550,
544                1550, 1550, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475,
545                1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1425, 1425, 1425, 1425, 1425, 1425,
546                1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425, 1425,
547                1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375, 1375,
548                1375, 1375, 1375, 1375, 1375, 1375, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325,
549                1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1325, 1275, 1275,
550                1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275, 1275,
551                1275, 1275, 1275, 1275, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225,
552                1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1225, 1175, 1175, 1175, 1175,
553                1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175,
554                1175, 1175, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125,
555                1125, 1125, 1125, 1125, 1125, 1125, 1125, 1125, 1075, 1075, 1075, 1075, 1075, 1075,
556                1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075, 1075,
557                1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025, 1025,
558                1025, 1025, 1025, 1025, 1025, 1025, 975, 975, 975, 975, 975, 975, 975, 975, 975,
559                975, 975, 975, 975, 975, 975, 975, 975, 975, 975, 975, 937, 937, 937, 937, 937,
560                937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 937, 900,
561                900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900,
562                900, 900, 900, 862, 862, 862, 862, 862, 862, 862, 862, 862, 862, 862, 862, 862,
563                862, 862, 862, 862, 862, 862, 862, 825, 825, 825, 825, 825, 825, 825, 825, 825,
564                825, 825, 825, 825, 825, 825, 825, 825, 825, 825, 825, 787, 787, 787, 787, 787,
565                787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 787, 750,
566                750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750, 750,
567                750, 750, 750, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712,
568                712, 712, 712, 712, 712, 712, 712, 675, 675, 675, 675, 675, 675, 675, 675, 675,
569                675, 675, 675, 675, 675, 675, 675, 675, 675, 675, 675, 637, 637, 637, 637, 637,
570                637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 637, 600,
571                600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
572                600, 600, 600, 562, 562, 562, 562, 562, 562, 562, 562, 562, 562, 562, 562, 562,
573                562, 562, 562, 562, 562, 562, 562, 525, 525, 525, 525, 525, 525, 525, 525, 525,
574                525, 525, 525, 525, 525, 525, 525, 525, 525, 525, 525, 487, 487, 487, 487, 487,
575                487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 487, 450,
576                450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450, 450,
577                450, 450, 450, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412, 412,
578                412, 412, 412, 412, 412, 412, 412, 375, 375, 375, 375, 375, 375, 375, 375, 375,
579                375, 375, 375, 375, 375, 375, 375, 375, 375, 375, 375, 337, 337, 337, 337, 337,
580                337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 337, 300,
581                300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300, 300,
582                300, 300, 300, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262, 262,
583                262, 262, 262, 262, 262, 262, 262, 237, 237, 237, 237, 237, 237, 237, 237, 237,
584                237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 237, 212, 212, 212, 212, 212,
585                212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 212, 187,
586                187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187,
587                187, 187, 187, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162,
588                162, 162, 162, 162, 162, 162, 162, 137, 137, 137, 137, 137, 137, 137, 137, 137,
589                137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 112, 112, 112, 112, 112,
590                112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 112, 87,
591                87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 62,
592                62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62
593            ];
594
595            assert_eq!(
596                credits_per_epochs.clone().into_values().collect::<Vec<_>>(),
597                reference_fees
598            );
599
600            let total_distributed: SignedCredits = credits_per_epochs.values().sum();
601
602            assert_eq!(total_distributed.to_unsigned() + leftovers, storage_fee);
603
604            /*
605
606            Repeat distribution to ensure deterministic results
607
608             */
609
610            let leftovers = distribute_storage_fee_to_epochs_collection(
611                &mut credits_per_epochs,
612                storage_fee,
613                current_epoch_index,
614                20,
615            )
616            .expect("should distribute storage fee");
617
618            // assert that all the values doubled meaning that distribution is reproducible
619            assert_eq!(
620                credits_per_epochs.into_values().collect::<Vec<_>>(),
621                reference_fees
622                    .into_iter()
623                    .map(|val| val * 2)
624                    .collect::<Vec<_>>()
625            );
626
627            assert_eq!(leftovers, 180);
628        }
629    }
630
631    mod subtract_refunds_from_epoch_credits_collection {
632        use super::*;
633        use crate::balances::credits::Creditable;
634        use crate::fee::SignedCredits;
635
636        #[test]
637        fn should_deduct_refunds_from_collection_since_specific_epoch_start_at_genesis() {
638            // Example: Bob inserted an element into the tree
639            // He paid slightly more than 1.2 Million credits for this operation that happened at epoch 0.
640            // At epoch 42 we are asking for a refund.
641            // The refund is 1.07 Million credits that were left from the 1.2.
642
643            let start_epoch_index: EpochIndex = GENESIS_EPOCH_INDEX;
644            const REFUNDED_EPOCH_INDEX: EpochIndex = 42;
645            let original_storage_fee = 1200005;
646
647            let (refund_amount, leftovers) = calculate_storage_fee_refund_amount_and_leftovers(
648                original_storage_fee,
649                start_epoch_index,
650                REFUNDED_EPOCH_INDEX,
651                20,
652            )
653            .expect("should distribute storage fee");
654
655            assert_eq!(refund_amount, 1074120);
656            assert_eq!(leftovers, 5);
657
658            let mut credits_per_epochs = SignedCreditsPerEpoch::default();
659
660            subtract_refunds_from_epoch_credits_collection(
661                &mut credits_per_epochs,
662                refund_amount,
663                start_epoch_index,
664                REFUNDED_EPOCH_INDEX,
665                20,
666            )
667            .expect("should distribute storage fee");
668
669            // compare them with reference table
670            // we expect to get 0 for the change of the current epochs balance
671            // this is because there was only 1 refund so leftovers wouldn't have any effect
672            #[rustfmt::skip]
673            let reference_fees: [SignedCredits;
674                (1000 - REFUNDED_EPOCH_INDEX - 1) as usize] = [-2760, -2760, -2760,
675                -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760, -2760,
676                -2760, -2760, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640,
677                -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2640, -2520, -2520,
678                -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520, -2520,
679                -2520, -2520, -2520, -2520, -2520, -2520, -2400, -2400, -2400, -2400, -2400, -2400,
680                -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400, -2400,
681                -2400, -2400, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310,
682                -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2310, -2220, -2220,
683                -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220, -2220,
684                -2220, -2220, -2220, -2220, -2220, -2220, -2130, -2130, -2130, -2130, -2130, -2130,
685                -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130, -2130,
686                -2130, -2130, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040,
687                -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -2040, -1950, -1950,
688                -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950, -1950,
689                -1950, -1950, -1950, -1950, -1950, -1950, -1860, -1860, -1860, -1860, -1860, -1860,
690                -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860, -1860,
691                -1860, -1860, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770,
692                -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1770, -1710, -1710,
693                -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710, -1710,
694                -1710, -1710, -1710, -1710, -1710, -1710, -1650, -1650, -1650, -1650, -1650, -1650,
695                -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650, -1650,
696                -1650, -1650, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590,
697                -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1590, -1530, -1530,
698                -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530, -1530,
699                -1530, -1530, -1530, -1530, -1530, -1530, -1470, -1470, -1470, -1470, -1470, -1470,
700                -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470, -1470,
701                -1470, -1470, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410,
702                -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1410, -1350, -1350,
703                -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350, -1350,
704                -1350, -1350, -1350, -1350, -1350, -1350, -1290, -1290, -1290, -1290, -1290, -1290,
705                -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290, -1290,
706                -1290, -1290, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230,
707                -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1230, -1170, -1170,
708                -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170, -1170,
709                -1170, -1170, -1170, -1170, -1170, -1170, -1125, -1125, -1125, -1125, -1125, -1125,
710                -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125, -1125,
711                -1125, -1125, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080,
712                -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1080, -1035, -1035,
713                -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035, -1035,
714                -1035, -1035, -1035, -1035, -1035, -1035, -990, -990, -990, -990, -990, -990, -990,
715                -990, -990, -990, -990, -990, -990, -990, -990, -990, -990, -990, -990, -990, -945,
716                -945, -945, -945, -945, -945, -945, -945, -945, -945, -945, -945, -945, -945, -945,
717                -945, -945, -945, -945, -945, -900, -900, -900, -900, -900, -900, -900, -900, -900,
718                -900, -900, -900, -900, -900, -900, -900, -900, -900, -900, -900, -855, -855, -855,
719                -855, -855, -855, -855, -855, -855, -855, -855, -855, -855, -855, -855, -855, -855,
720                -855, -855, -855, -810, -810, -810, -810, -810, -810, -810, -810, -810, -810, -810,
721                -810, -810, -810, -810, -810, -810, -810, -810, -810, -765, -765, -765, -765, -765,
722                -765, -765, -765, -765, -765, -765, -765, -765, -765, -765, -765, -765, -765, -765,
723                -765, -720, -720, -720, -720, -720, -720, -720, -720, -720, -720, -720, -720, -720,
724                -720, -720, -720, -720, -720, -720, -720, -675, -675, -675, -675, -675, -675, -675,
725                -675, -675, -675, -675, -675, -675, -675, -675, -675, -675, -675, -675, -675, -630,
726                -630, -630, -630, -630, -630, -630, -630, -630, -630, -630, -630, -630, -630, -630,
727                -630, -630, -630, -630, -630, -585, -585, -585, -585, -585, -585, -585, -585, -585,
728                -585, -585, -585, -585, -585, -585, -585, -585, -585, -585, -585, -540, -540, -540,
729                -540, -540, -540, -540, -540, -540, -540, -540, -540, -540, -540, -540, -540, -540,
730                -540, -540, -540, -495, -495, -495, -495, -495, -495, -495, -495, -495, -495, -495,
731                -495, -495, -495, -495, -495, -495, -495, -495, -495, -450, -450, -450, -450, -450,
732                -450, -450, -450, -450, -450, -450, -450, -450, -450, -450, -450, -450, -450, -450,
733                -450, -405, -405, -405, -405, -405, -405, -405, -405, -405, -405, -405, -405, -405,
734                -405, -405, -405, -405, -405, -405, -405, -360, -360, -360, -360, -360, -360, -360,
735                -360, -360, -360, -360, -360, -360, -360, -360, -360, -360, -360, -360, -360, -315,
736                -315, -315, -315, -315, -315, -315, -315, -315, -315, -315, -315, -315, -315, -315,
737                -315, -315, -315, -315, -315, -285, -285, -285, -285, -285, -285, -285, -285, -285,
738                -285, -285, -285, -285, -285, -285, -285, -285, -285, -285, -285, -255, -255, -255,
739                -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, -255, -255,
740                -255, -255, -255, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225,
741                -225, -225, -225, -225, -225, -225, -225, -225, -225, -195, -195, -195, -195, -195,
742                -195, -195, -195, -195, -195, -195, -195, -195, -195, -195, -195, -195, -195, -195,
743                -195, -165, -165, -165, -165, -165, -165, -165, -165, -165, -165, -165, -165, -165,
744                -165, -165, -165, -165, -165, -165, -165, -135, -135, -135, -135, -135, -135, -135,
745                -135, -135, -135, -135, -135, -135, -135, -135, -135, -135, -135, -135, -135, -105,
746                -105, -105, -105, -105, -105, -105, -105, -105, -105, -105, -105, -105, -105, -105,
747                -105, -105, -105, -105, -105, -75, -75, -75, -75, -75, -75, -75, -75, -75, -75, -75,
748                -75, -75, -75, -75, -75, -75, -75, -75, -75];
749
750            assert_eq!(
751                credits_per_epochs.clone().into_values().collect::<Vec<_>>(),
752                reference_fees
753            );
754
755            let total_distributed: SignedCredits = credits_per_epochs.values().sum();
756
757            assert_eq!(total_distributed.to_unsigned(), refund_amount);
758        }
759
760        /// Regression test for the decoupled-epoch storage-fee refund
761        /// divide-by-zero (chain-halt) bug.
762        ///
763        /// In production the refund AMOUNT and the clawback DISTRIBUTION are
764        /// computed at two different epochs:
765        ///   * `FeeRefunds::from_storage_removal` computes the amount at the
766        ///     removal epoch (`c_store`) and persists it keyed by the original
767        ///     write epoch — the removal epoch is then discarded.
768        ///   * one epoch later, at the next epoch change,
769        ///     `add_distribute_storage_fee_to_epochs_operations_v0` consumes it
770        ///     via `subtract_refunds_from_epoch_credits_collection` using the
771        ///     *new* current epoch (`c_consume = c_store + 1`), which re-derives
772        ///     the `1 / ratio_used` multiplier against a different window
773        ///     position.
774        ///
775        /// Every other test in this module feeds the SAME epoch to both calls
776        /// (the self-consistent case), which is exactly why this was never
777        /// exercised. Here we reproduce the real wiring: data written at epoch
778        /// 0 is removed two epochs before its 50-era window expires — so the
779        /// refund is legitimately non-zero (it is the share of the single
780        /// remaining in-window epoch) — and then distributed one epoch later,
781        /// precisely as the window boundary is crossed. At that point
782        /// `current_era == PERPETUAL_STORAGE_ERAS`, so `ratio_used == 0`.
783        /// Without the `start_era >= PERPETUAL_STORAGE_ERAS` guard in
784        /// `refund_storage_fee_to_epochs_map`, this returns `DivideByZero` (or,
785        /// before that error existed, panicked) and halts every node.
786        #[test]
787        fn should_not_halt_when_refund_distributed_as_window_boundary_is_crossed() {
788            const EPOCHS_PER_ERA: u16 = 40; // production default
789            const WRITE_EPOCH: EpochIndex = 0;
790
791            // The perpetual storage window for data written at WRITE_EPOCH spans
792            // epochs [0, EPOCHS_PER_ERA * PERPETUAL_STORAGE_ERAS) = [0, 2000).
793            let window_end_epoch = WRITE_EPOCH + EPOCHS_PER_ERA * PERPETUAL_STORAGE_ERAS; // 2000
794
795            // Remove the data two epochs before the window fully elapses, so the
796            // refund still covers exactly one in-window epoch (the final one,
797            // 1999) and is therefore NON-ZERO.
798            let removal_epoch = window_end_epoch - 2; // c_store = 1998
799
800            // One epoch change later the refund is distributed with the THEN
801            // current epoch index (the natural +1-epoch lag). This value flows
802            // into original_removed_credits_multiplier_from via skip_until =
803            // current + 1, which now spans the full window.
804            let distribution_epoch = removal_epoch + 1; // c_consume = 1999
805
806            let original_storage_fee: Credits = 10_000_000_000;
807
808            let (refund_amount, _leftovers) = calculate_storage_fee_refund_amount_and_leftovers(
809                original_storage_fee,
810                WRITE_EPOCH,
811                removal_epoch,
812                EPOCHS_PER_ERA,
813            )
814            .expect("refund amount computation should succeed");
815
816            assert!(
817                refund_amount > 0,
818                "removing data before the final in-window epoch ({}) must leave a \
819                 non-zero refund; got {refund_amount}",
820                window_end_epoch - 1,
821            );
822
823            let mut credits_per_epochs = SignedCreditsPerEpoch::default();
824
825            // Without the guard this returns Err(DivideByZero) (which still
826            // halts the chain via Tenderdash) — or panicked before that error
827            // existed — inside original_removed_credits_multiplier_from, because
828            // the multiplier is recomputed at the distribution epoch and now
829            // spans the entire perpetual-storage window.
830            subtract_refunds_from_epoch_credits_collection(
831                &mut credits_per_epochs,
832                refund_amount,
833                WRITE_EPOCH,
834                distribution_epoch,
835                EPOCHS_PER_ERA,
836            )
837            .expect("refund distribution must not halt the chain at the window boundary");
838
839            // With the window fully elapsed there are no future epoch pools left
840            // to claw the refund back from, so the entire refund must come out of
841            // the current (distribution) epoch's pool — and nothing else should
842            // be touched.
843            let entries: Vec<(EpochIndex, SignedCredits)> =
844                credits_per_epochs.into_iter().collect();
845            assert_eq!(
846                entries,
847                vec![(distribution_epoch, -(refund_amount as SignedCredits))],
848                "the full refund should be clawed back from only the current epoch",
849            );
850        }
851
852        #[test]
853        fn should_deduct_refunds_from_collection_start_epoch_doesnt_matter_check() {
854            for start_epoch_index in 0..150 {
855                let current_epoch_index_where_refund_occurred: EpochIndex = start_epoch_index + 14;
856
857                let original_storage_fee = 3405507;
858                let (refund_amount, leftovers) = calculate_storage_fee_refund_amount_and_leftovers(
859                    original_storage_fee,
860                    start_epoch_index,
861                    current_epoch_index_where_refund_occurred,
862                    20,
863                )
864                .expect("should distribute storage fee");
865
866                assert_eq!(refund_amount, 3277305);
867                assert_eq!(leftovers, 507);
868
869                let multiplier = original_removed_credits_multiplier_from(
870                    start_epoch_index,
871                    current_epoch_index_where_refund_occurred + 1,
872                    20,
873                )
874                .expect("multiplier within perpetual storage window");
875
876                // it's not going to be completely perfect but it's good enough
877                // there were 24 epochs, on average we would be 12 off
878                // while we could incorporate this offset into the multiplier it would
879                // be overkill for such low credit amounts
880                assert!(
881                    (Decimal::from(refund_amount) * multiplier)
882                        .abs_sub(&Decimal::from(original_storage_fee - leftovers))
883                        < dec!(100)
884                );
885
886                // we do however want to make sure the multiplier makes things smaller
887                assert!(
888                    (Decimal::from(refund_amount) * multiplier)
889                        < Decimal::from(original_storage_fee - leftovers)
890                );
891
892                let mut credits_per_epochs = SignedCreditsPerEpoch::default();
893
894                subtract_refunds_from_epoch_credits_collection(
895                    &mut credits_per_epochs,
896                    refund_amount,
897                    start_epoch_index,
898                    current_epoch_index_where_refund_occurred,
899                    20,
900                )
901                .expect("should distribute storage fee");
902                // compare them with reference table
903                // we expect to get 0 for the change of the current epochs balance
904                // this is because there was only 1 refund so leftovers wouldn't have any effect
905                #[rustfmt::skip]
906                    let reference_fees: Vec<SignedCredits> =
907                    vec![-525, -8512, -8512, -8512, -8512, -8512, -8171, -8171, -8171, -8171, -8171, -8171,
908                        -8171, -8171, -8171, -8171, -8171, -8171, -8171, -8171, -8171, -8171, -8171,
909                        -8171, -8171, -8171, -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831,
910                        -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831, -7831,
911                        -7831, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490,
912                        -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7490, -7150,
913                        -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150,
914                        -7150, -7150, -7150, -7150, -7150, -7150, -7150, -7150, -6809, -6809, -6809,
915                        -6809, -6809, -6809, -6809, -6809, -6809, -6809, -6809, -6809, -6809, -6809,
916                        -6809, -6809, -6809, -6809, -6809, -6809, -6554, -6554, -6554, -6554, -6554,
917                        -6554, -6554, -6554, -6554, -6554, -6554, -6554, -6554, -6554, -6554, -6554,
918                        -6554, -6554, -6554, -6554, -6299, -6299, -6299, -6299, -6299, -6299, -6299,
919                        -6299, -6299, -6299, -6299, -6299, -6299, -6299, -6299, -6299, -6299, -6299,
920                        -6299, -6299, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043,
921                        -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043, -6043,
922                        -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788,
923                        -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5788, -5533, -5533,
924                        -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5533,
925                        -5533, -5533, -5533, -5533, -5533, -5533, -5533, -5277, -5277, -5277, -5277,
926                        -5277, -5277, -5277, -5277, -5277, -5277, -5277, -5277, -5277, -5277, -5277,
927                        -5277, -5277, -5277, -5277, -5277, -5022, -5022, -5022, -5022, -5022, -5022,
928                        -5022, -5022, -5022, -5022, -5022, -5022, -5022, -5022, -5022, -5022, -5022,
929                        -5022, -5022, -5022, -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852,
930                        -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852, -4852,
931                        -4852, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681,
932                        -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4681, -4511,
933                        -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511,
934                        -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4511, -4341, -4341, -4341,
935                        -4341, -4341, -4341, -4341, -4341, -4341, -4341, -4341, -4341, -4341, -4341,
936                        -4341, -4341, -4341, -4341, -4341, -4341, -4171, -4171, -4171, -4171, -4171,
937                        -4171, -4171, -4171, -4171, -4171, -4171, -4171, -4171, -4171, -4171, -4171,
938                        -4171, -4171, -4171, -4171, -4000, -4000, -4000, -4000, -4000, -4000, -4000,
939                        -4000, -4000, -4000, -4000, -4000, -4000, -4000, -4000, -4000, -4000, -4000,
940                        -4000, -4000, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830,
941                        -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830, -3830,
942                        -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660,
943                        -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3660, -3490, -3490,
944                        -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3490,
945                        -3490, -3490, -3490, -3490, -3490, -3490, -3490, -3319, -3319, -3319, -3319,
946                        -3319, -3319, -3319, -3319, -3319, -3319, -3319, -3319, -3319, -3319, -3319,
947                        -3319, -3319, -3319, -3319, -3319, -3192, -3192, -3192, -3192, -3192, -3192,
948                        -3192, -3192, -3192, -3192, -3192, -3192, -3192, -3192, -3192, -3192, -3192,
949                        -3192, -3192, -3192, -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064,
950                        -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064, -3064,
951                        -3064, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936,
952                        -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2936, -2809,
953                        -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809,
954                        -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2809, -2681, -2681, -2681,
955                        -2681, -2681, -2681, -2681, -2681, -2681, -2681, -2681, -2681, -2681, -2681,
956                        -2681, -2681, -2681, -2681, -2681, -2681, -2553, -2553, -2553, -2553, -2553,
957                        -2553, -2553, -2553, -2553, -2553, -2553, -2553, -2553, -2553, -2553, -2553,
958                        -2553, -2553, -2553, -2553, -2426, -2426, -2426, -2426, -2426, -2426, -2426,
959                        -2426, -2426, -2426, -2426, -2426, -2426, -2426, -2426, -2426, -2426, -2426,
960                        -2426, -2426, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298,
961                        -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298, -2298,
962                        -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170,
963                        -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2170, -2042, -2042,
964                        -2042, -2042, -2042, -2042, -2042, -2042, -2042, -2042, -2042, -2042, -2042,
965                        -2042, -2042, -2042, -2042, -2042, -2042, -2042, -1915, -1915, -1915, -1915,
966                        -1915, -1915, -1915, -1915, -1915, -1915, -1915, -1915, -1915, -1915, -1915,
967                        -1915, -1915, -1915, -1915, -1915, -1787, -1787, -1787, -1787, -1787, -1787,
968                        -1787, -1787, -1787, -1787, -1787, -1787, -1787, -1787, -1787, -1787, -1787,
969                        -1787, -1787, -1787, -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659,
970                        -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659, -1659,
971                        -1659, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532,
972                        -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1532, -1404,
973                        -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404,
974                        -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1404, -1276, -1276, -1276,
975                        -1276, -1276, -1276, -1276, -1276, -1276, -1276, -1276, -1276, -1276, -1276,
976                        -1276, -1276, -1276, -1276, -1276, -1276, -1149, -1149, -1149, -1149, -1149,
977                        -1149, -1149, -1149, -1149, -1149, -1149, -1149, -1149, -1149, -1149, -1149,
978                        -1149, -1149, -1149, -1149, -1021, -1021, -1021, -1021, -1021, -1021, -1021,
979                        -1021, -1021, -1021, -1021, -1021, -1021, -1021, -1021, -1021, -1021, -1021,
980                        -1021, -1021, -893, -893, -893, -893, -893, -893, -893, -893, -893, -893,
981                        -893, -893, -893, -893, -893, -893, -893, -893, -893, -893, -808, -808, -808,
982                        -808, -808, -808, -808, -808, -808, -808, -808, -808, -808, -808, -808, -808,
983                        -808, -808, -808, -808, -723, -723, -723, -723, -723, -723, -723, -723, -723,
984                        -723, -723, -723, -723, -723, -723, -723, -723, -723, -723, -723, -638, -638,
985                        -638, -638, -638, -638, -638, -638, -638, -638, -638, -638, -638, -638, -638,
986                        -638, -638, -638, -638, -638, -553, -553, -553, -553, -553, -553, -553, -553,
987                        -553, -553, -553, -553, -553, -553, -553, -553, -553, -553, -553, -553, -468,
988                        -468, -468, -468, -468, -468, -468, -468, -468, -468, -468, -468, -468, -468,
989                        -468, -468, -468, -468, -468, -468, -383, -383, -383, -383, -383, -383, -383,
990                        -383, -383, -383, -383, -383, -383, -383, -383, -383, -383, -383, -383, -383,
991                        -297, -297, -297, -297, -297, -297, -297, -297, -297, -297, -297, -297, -297,
992                        -297, -297, -297, -297, -297, -297, -297, -212, -212, -212, -212, -212, -212,
993                        -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212,
994                        -212];
995
996                assert_eq!(
997                    credits_per_epochs.clone().into_values().collect::<Vec<_>>(),
998                    reference_fees
999                );
1000
1001                let total_distributed: SignedCredits = credits_per_epochs.values().sum();
1002
1003                assert_eq!(total_distributed.to_unsigned(), refund_amount);
1004            }
1005        }
1006
1007        #[test]
1008        fn should_deduct_refunds_from_two_collection_since_specific_epoch() {
1009            const CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED: EpochIndex = 42;
1010            let mut credits_per_epochs = SignedCreditsPerEpoch::default();
1011
1012            // First_refund
1013
1014            // Example: Bob inserted an element into the tree
1015            // He paid slightly more than 1.2 Million credits for this operation that happened at epoch 0.
1016            // At epoch 42 we are asking for a refund.
1017            // The refund is 1.07 Million credits that were left from the 1.2.
1018
1019            let first_start_epoch_index: EpochIndex = GENESIS_EPOCH_INDEX;
1020
1021            let first_original_storage_fee = 1200005;
1022            let (first_refund_amount, leftovers) =
1023                calculate_storage_fee_refund_amount_and_leftovers(
1024                    first_original_storage_fee,
1025                    first_start_epoch_index,
1026                    CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED,
1027                    20,
1028                )
1029                .expect("should distribute storage fee");
1030
1031            assert_eq!(first_refund_amount, 1074120);
1032            assert_eq!(leftovers, 5);
1033
1034            subtract_refunds_from_epoch_credits_collection(
1035                &mut credits_per_epochs,
1036                first_refund_amount,
1037                first_start_epoch_index,
1038                CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED,
1039                20,
1040            )
1041            .expect("should distribute storage fee");
1042
1043            // Second_refund
1044
1045            // Example: Bob inserted an element into the tree
1046            // He paid slightly more than 3.4 Million credits for this operation that happened at epoch 0.
1047            // At epoch 42 we are asking for a refund.
1048
1049            const SECOND_START_EPOCH_INDEX: EpochIndex = 28;
1050
1051            let second_original_storage_fee = 3405507;
1052            let (second_refund_amount, leftovers) =
1053                calculate_storage_fee_refund_amount_and_leftovers(
1054                    second_original_storage_fee,
1055                    SECOND_START_EPOCH_INDEX,
1056                    CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED,
1057                    20,
1058                )
1059                .expect("should distribute storage fee");
1060
1061            assert_eq!(second_refund_amount, 3277305);
1062            assert_eq!(leftovers, 507);
1063
1064            let multiplier = original_removed_credits_multiplier_from(
1065                SECOND_START_EPOCH_INDEX,
1066                CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED + 1,
1067                20,
1068            )
1069            .expect("multiplier within perpetual storage window");
1070
1071            // it's not going to be completely perfect but it's good enough
1072            // there were 24 epochs, on average we would be 12 off
1073            // while we could incorporate this offset into the multiplier it would
1074            // be overkill for such low credit amounts
1075            assert!(
1076                (Decimal::from(second_refund_amount) * multiplier)
1077                    .abs_sub(&Decimal::from(second_original_storage_fee - leftovers))
1078                    < dec!(100)
1079            );
1080
1081            // we do however want to make sure the multiplier makes things smaller
1082            assert!(
1083                (Decimal::from(second_refund_amount) * multiplier)
1084                    < Decimal::from(second_original_storage_fee - leftovers)
1085            );
1086
1087            subtract_refunds_from_epoch_credits_collection(
1088                &mut credits_per_epochs,
1089                second_refund_amount,
1090                SECOND_START_EPOCH_INDEX,
1091                CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED,
1092                20,
1093            )
1094            .expect("should distribute storage fee");
1095            // compare them with reference table
1096            // we expect to get 0 for the change of the current epochs balance
1097            // this is because there was only 1 refund so leftovers wouldn't have any effect
1098            #[rustfmt::skip]
1099                let reference_fees: [SignedCredits;
1100                (SECOND_START_EPOCH_INDEX + 1000 - CURRENT_EPOCH_INDEX_WHERE_REFUND_OCCURRED) as usize] =
1101                [-525, -11272, -11272, -11272, -11272, -11272, -10931, -10931, -10931, -10931,
1102                    -10931, -10931, -10931, -10931, -10931, -10931, -10931, -10931, -10811, -10811,
1103                    -10811, -10811, -10811, -10811, -10811, -10811, -10471, -10471, -10471, -10471,
1104                    -10471, -10471, -10471, -10471, -10471, -10471, -10471, -10471, -10351, -10351,
1105                    -10351, -10351, -10351, -10351, -10351, -10351, -10010, -10010, -10010, -10010,
1106                    -10010, -10010, -10010, -10010, -10010, -10010, -10010, -10010, -9890, -9890,
1107                    -9890, -9890, -9890, -9890, -9890, -9890, -9550, -9550, -9550, -9550, -9550,
1108                    -9550, -9550, -9550, -9550, -9550, -9550, -9550, -9460, -9460, -9460, -9460,
1109                    -9460, -9460, -9460, -9460, -9119, -9119, -9119, -9119, -9119, -9119, -9119,
1110                    -9119, -9119, -9119, -9119, -9119, -9029, -9029, -9029, -9029, -9029, -9029,
1111                    -9029, -9029, -8774, -8774, -8774, -8774, -8774, -8774, -8774, -8774, -8774,
1112                    -8774, -8774, -8774, -8684, -8684, -8684, -8684, -8684, -8684, -8684, -8684,
1113                    -8429, -8429, -8429, -8429, -8429, -8429, -8429, -8429, -8429, -8429, -8429,
1114                    -8429, -8339, -8339, -8339, -8339, -8339, -8339, -8339, -8339, -8083, -8083,
1115                    -8083, -8083, -8083, -8083, -8083, -8083, -8083, -8083, -8083, -8083, -7993,
1116                    -7993, -7993, -7993, -7993, -7993, -7993, -7993, -7738, -7738, -7738, -7738,
1117                    -7738, -7738, -7738, -7738, -7738, -7738, -7738, -7738, -7648, -7648, -7648,
1118                    -7648, -7648, -7648, -7648, -7648, -7393, -7393, -7393, -7393, -7393, -7393,
1119                    -7393, -7393, -7393, -7393, -7393, -7393, -7303, -7303, -7303, -7303, -7303,
1120                    -7303, -7303, -7303, -7047, -7047, -7047, -7047, -7047, -7047, -7047, -7047,
1121                    -7047, -7047, -7047, -7047, -6987, -6987, -6987, -6987, -6987, -6987, -6987,
1122                    -6987, -6732, -6732, -6732, -6732, -6732, -6732, -6732, -6732, -6732, -6732,
1123                    -6732, -6732, -6672, -6672, -6672, -6672, -6672, -6672, -6672, -6672, -6502,
1124                    -6502, -6502, -6502, -6502, -6502, -6502, -6502, -6502, -6502, -6502, -6502,
1125                    -6442, -6442, -6442, -6442, -6442, -6442, -6442, -6442, -6271, -6271, -6271,
1126                    -6271, -6271, -6271, -6271, -6271, -6271, -6271, -6271, -6271, -6211, -6211,
1127                    -6211, -6211, -6211, -6211, -6211, -6211, -6041, -6041, -6041, -6041, -6041,
1128                    -6041, -6041, -6041, -6041, -6041, -6041, -6041, -5981, -5981, -5981, -5981,
1129                    -5981, -5981, -5981, -5981, -5811, -5811, -5811, -5811, -5811, -5811, -5811,
1130                    -5811, -5811, -5811, -5811, -5811, -5751, -5751, -5751, -5751, -5751, -5751,
1131                    -5751, -5751, -5581, -5581, -5581, -5581, -5581, -5581, -5581, -5581, -5581,
1132                    -5581, -5581, -5581, -5521, -5521, -5521, -5521, -5521, -5521, -5521, -5521,
1133                    -5350, -5350, -5350, -5350, -5350, -5350, -5350, -5350, -5350, -5350, -5350,
1134                    -5350, -5290, -5290, -5290, -5290, -5290, -5290, -5290, -5290, -5120, -5120,
1135                    -5120, -5120, -5120, -5120, -5120, -5120, -5120, -5120, -5120, -5120, -5060,
1136                    -5060, -5060, -5060, -5060, -5060, -5060, -5060, -4890, -4890, -4890, -4890,
1137                    -4890, -4890, -4890, -4890, -4890, -4890, -4890, -4890, -4830, -4830, -4830,
1138                    -4830, -4830, -4830, -4830, -4830, -4660, -4660, -4660, -4660, -4660, -4660,
1139                    -4660, -4660, -4660, -4660, -4660, -4660, -4615, -4615, -4615, -4615, -4615,
1140                    -4615, -4615, -4615, -4444, -4444, -4444, -4444, -4444, -4444, -4444, -4444,
1141                    -4444, -4444, -4444, -4444, -4399, -4399, -4399, -4399, -4399, -4399, -4399,
1142                    -4399, -4272, -4272, -4272, -4272, -4272, -4272, -4272, -4272, -4272, -4272,
1143                    -4272, -4272, -4227, -4227, -4227, -4227, -4227, -4227, -4227, -4227, -4099,
1144                    -4099, -4099, -4099, -4099, -4099, -4099, -4099, -4099, -4099, -4099, -4099,
1145                    -4054, -4054, -4054, -4054, -4054, -4054, -4054, -4054, -3926, -3926, -3926,
1146                    -3926, -3926, -3926, -3926, -3926, -3926, -3926, -3926, -3926, -3881, -3881,
1147                    -3881, -3881, -3881, -3881, -3881, -3881, -3754, -3754, -3754, -3754, -3754,
1148                    -3754, -3754, -3754, -3754, -3754, -3754, -3754, -3709, -3709, -3709, -3709,
1149                    -3709, -3709, -3709, -3709, -3581, -3581, -3581, -3581, -3581, -3581, -3581,
1150                    -3581, -3581, -3581, -3581, -3581, -3536, -3536, -3536, -3536, -3536, -3536,
1151                    -3536, -3536, -3408, -3408, -3408, -3408, -3408, -3408, -3408, -3408, -3408,
1152                    -3408, -3408, -3408, -3363, -3363, -3363, -3363, -3363, -3363, -3363, -3363,
1153                    -3236, -3236, -3236, -3236, -3236, -3236, -3236, -3236, -3236, -3236, -3236,
1154                    -3236, -3191, -3191, -3191, -3191, -3191, -3191, -3191, -3191, -3063, -3063,
1155                    -3063, -3063, -3063, -3063, -3063, -3063, -3063, -3063, -3063, -3063, -3018,
1156                    -3018, -3018, -3018, -3018, -3018, -3018, -3018, -2890, -2890, -2890, -2890,
1157                    -2890, -2890, -2890, -2890, -2890, -2890, -2890, -2890, -2845, -2845, -2845,
1158                    -2845, -2845, -2845, -2845, -2845, -2717, -2717, -2717, -2717, -2717, -2717,
1159                    -2717, -2717, -2717, -2717, -2717, -2717, -2672, -2672, -2672, -2672, -2672,
1160                    -2672, -2672, -2672, -2545, -2545, -2545, -2545, -2545, -2545, -2545, -2545,
1161                    -2545, -2545, -2545, -2545, -2500, -2500, -2500, -2500, -2500, -2500, -2500,
1162                    -2500, -2372, -2372, -2372, -2372, -2372, -2372, -2372, -2372, -2372, -2372,
1163                    -2372, -2372, -2327, -2327, -2327, -2327, -2327, -2327, -2327, -2327, -2199,
1164                    -2199, -2199, -2199, -2199, -2199, -2199, -2199, -2199, -2199, -2199, -2199,
1165                    -2154, -2154, -2154, -2154, -2154, -2154, -2154, -2154, -2027, -2027, -2027,
1166                    -2027, -2027, -2027, -2027, -2027, -2027, -2027, -2027, -2027, -1982, -1982,
1167                    -1982, -1982, -1982, -1982, -1982, -1982, -1854, -1854, -1854, -1854, -1854,
1168                    -1854, -1854, -1854, -1854, -1854, -1854, -1854, -1809, -1809, -1809, -1809,
1169                    -1809, -1809, -1809, -1809, -1681, -1681, -1681, -1681, -1681, -1681, -1681,
1170                    -1681, -1681, -1681, -1681, -1681, -1636, -1636, -1636, -1636, -1636, -1636,
1171                    -1636, -1636, -1509, -1509, -1509, -1509, -1509, -1509, -1509, -1509, -1509,
1172                    -1509, -1509, -1509, -1464, -1464, -1464, -1464, -1464, -1464, -1464, -1464,
1173                    -1336, -1336, -1336, -1336, -1336, -1336, -1336, -1336, -1336, -1336, -1336,
1174                    -1336, -1306, -1306, -1306, -1306, -1306, -1306, -1306, -1306, -1178, -1178,
1175                    -1178, -1178, -1178, -1178, -1178, -1178, -1178, -1178, -1178, -1178, -1148,
1176                    -1148, -1148, -1148, -1148, -1148, -1148, -1148, -1063, -1063, -1063, -1063,
1177                    -1063, -1063, -1063, -1063, -1063, -1063, -1063, -1063, -1033, -1033, -1033,
1178                    -1033, -1033, -1033, -1033, -1033, -948, -948, -948, -948, -948, -948, -948,
1179                    -948, -948, -948, -948, -948, -918, -918, -918, -918, -918, -918, -918, -918,
1180                    -833, -833, -833, -833, -833, -833, -833, -833, -833, -833, -833, -833, -803,
1181                    -803, -803, -803, -803, -803, -803, -803, -718, -718, -718, -718, -718, -718,
1182                    -718, -718, -718, -718, -718, -718, -688, -688, -688, -688, -688, -688, -688,
1183                    -688, -603, -603, -603, -603, -603, -603, -603, -603, -603, -603, -603, -603,
1184                    -573, -573, -573, -573, -573, -573, -573, -573, -488, -488, -488, -488, -488,
1185                    -488, -488, -488, -488, -488, -488, -488, -458, -458, -458, -458, -458, -458,
1186                    -458, -458, -372, -372, -372, -372, -372, -372, -372, -372, -372, -372, -372,
1187                    -372, -297, -297, -297, -297, -297, -297, -297, -297, -212, -212, -212, -212,
1188                    -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, -212,
1189                    -212, -212, -212];
1190
1191            assert_eq!(
1192                credits_per_epochs.clone().into_values().collect::<Vec<_>>(),
1193                reference_fees
1194            );
1195
1196            let total_distributed: SignedCredits = credits_per_epochs.values().sum();
1197
1198            assert_eq!(
1199                total_distributed.to_unsigned(),
1200                first_refund_amount + second_refund_amount
1201            );
1202        }
1203    }
1204
1205    mod calculate_storage_fee_refund_amount_and_leftovers {
1206        use super::*;
1207
1208        #[test]
1209        fn should_calculate_amount_and_leftovers() {
1210            let storage_fee = 10000;
1211
1212            let (amount, leftovers) = calculate_storage_fee_refund_amount_and_leftovers(
1213                storage_fee,
1214                GENESIS_EPOCH_INDEX + 1,
1215                2,
1216                20,
1217            )
1218            .expect("should distribute storage fee");
1219
1220            let first_two_epochs_amount = 50;
1221
1222            assert_eq!(leftovers, 400);
1223            assert_eq!(amount, storage_fee - leftovers - first_two_epochs_amount);
1224        }
1225
1226        #[test]
1227        fn should_return_zero_amount_and_zero_leftovers_for_zero_storage_fee() {
1228            let (amount, leftovers) =
1229                calculate_storage_fee_refund_amount_and_leftovers(0, GENESIS_EPOCH_INDEX, 10, 20)
1230                    .expect("should handle zero storage fee");
1231
1232            assert_eq!(amount, 0);
1233            assert_eq!(leftovers, 0);
1234        }
1235
1236        #[test]
1237        fn should_return_zero_refund_when_start_epoch_equals_current_epoch() {
1238            // When start == current, skipped_amount covers epoch 0 only (the one epoch
1239            // between start_epoch_index and current_epoch_index + 1 = 1).
1240            let storage_fee = 1000000;
1241            let epoch = 0;
1242
1243            let (amount, leftovers) =
1244                calculate_storage_fee_refund_amount_and_leftovers(storage_fee, epoch, epoch, 20)
1245                    .expect("should distribute storage fee");
1246
1247            // Only epoch 0 is skipped (cost = floor(1000000 * 0.05 / 20) = 2500).
1248            // The refund amount is everything except the skipped epoch and leftovers.
1249            assert_eq!(amount, storage_fee - 2500 - leftovers);
1250        }
1251
1252        #[test]
1253        fn should_calculate_correctly_with_non_genesis_start() {
1254            let storage_fee = 500000;
1255            let start = 100;
1256            let current = 110;
1257
1258            let (amount, leftovers) =
1259                calculate_storage_fee_refund_amount_and_leftovers(storage_fee, start, current, 20)
1260                    .expect("should distribute storage fee");
1261
1262            // Verify invariant: amount + skipped + leftovers = storage_fee
1263            assert_eq!(
1264                amount + (storage_fee - amount - leftovers) + leftovers,
1265                storage_fee
1266            );
1267            // Amount must be less than total
1268            assert!(amount < storage_fee);
1269            assert!(leftovers < storage_fee);
1270        }
1271
1272        #[test]
1273        fn should_handle_large_epoch_gap() {
1274            // current_epoch far from start
1275            let storage_fee = 10_000_000;
1276            let start = 0;
1277            let current = 500; // halfway through the 1000 total epochs
1278
1279            let (amount, leftovers) =
1280                calculate_storage_fee_refund_amount_and_leftovers(storage_fee, start, current, 20)
1281                    .expect("should handle large epoch gap");
1282
1283            // Refund amount should be smaller because most epochs have been paid out
1284            assert!(amount < storage_fee / 2);
1285            assert!(leftovers < storage_fee);
1286        }
1287    }
1288
1289    mod additional_original_removed_credits_multiplier_from {
1290        use super::*;
1291
1292        #[test]
1293        fn should_create_multiplier_of_one_when_no_epochs_have_passed() {
1294            // When start_repayment == start, paid_epochs = 0, ratio_used = full table sum = 1.0
1295            // So multiplier = 1/1 = 1
1296            let multiplier = original_removed_credits_multiplier_from(0, 0, 20)
1297                .expect("multiplier within perpetual storage window");
1298            assert_eq!(multiplier, dec!(1));
1299        }
1300
1301        #[test]
1302        fn should_increase_multiplier_as_more_epochs_pass() {
1303            let m1 = original_removed_credits_multiplier_from(0, 5, 20)
1304                .expect("multiplier within perpetual storage window");
1305            let m2 = original_removed_credits_multiplier_from(0, 10, 20)
1306                .expect("multiplier within perpetual storage window");
1307            let m3 = original_removed_credits_multiplier_from(0, 19, 20)
1308                .expect("multiplier within perpetual storage window");
1309
1310            // More paid epochs means less ratio remaining, so multiplier increases
1311            assert!(m1 < m2);
1312            assert!(m2 < m3);
1313        }
1314
1315        #[test]
1316        fn should_handle_era_boundary_crossing() {
1317            // paid_epochs = 20 means we enter the second era exactly
1318            let m_at_boundary = original_removed_credits_multiplier_from(0, 20, 20)
1319                .expect("multiplier within perpetual storage window");
1320            let m_before_boundary = original_removed_credits_multiplier_from(0, 19, 20)
1321                .expect("multiplier within perpetual storage window");
1322            let m_after_boundary = original_removed_credits_multiplier_from(0, 21, 20)
1323                .expect("multiplier within perpetual storage window");
1324
1325            // At the boundary, the entire first era (0.05) is consumed
1326            assert!(m_at_boundary > m_before_boundary);
1327            assert!(m_after_boundary > m_at_boundary);
1328        }
1329
1330        #[test]
1331        fn should_handle_different_epochs_per_era() {
1332            // With 40 epochs per era (the default), 40 paid epochs = 1 full era
1333            let m_40 = original_removed_credits_multiplier_from(0, 40, 40)
1334                .expect("multiplier within perpetual storage window");
1335            // With 20 epochs per era, 20 paid epochs = 1 full era
1336            let m_20 = original_removed_credits_multiplier_from(0, 20, 20)
1337                .expect("multiplier within perpetual storage window");
1338
1339            // Both consume exactly one full era of 0.05, so multipliers should be equal
1340            assert_eq!(m_40, m_20);
1341        }
1342
1343        #[test]
1344        fn should_produce_same_multiplier_regardless_of_absolute_epoch_offset() {
1345            // The multiplier depends only on the difference, not absolute indices
1346            let m1 = original_removed_credits_multiplier_from(0, 15, 20)
1347                .expect("multiplier within perpetual storage window");
1348            let m2 = original_removed_credits_multiplier_from(100, 115, 20)
1349                .expect("multiplier within perpetual storage window");
1350            let m3 = original_removed_credits_multiplier_from(5000, 5015, 20)
1351                .expect("multiplier within perpetual storage window");
1352
1353            assert_eq!(m1, m2);
1354            assert_eq!(m2, m3);
1355        }
1356
1357        #[test]
1358        fn should_return_error_instead_of_panicking_when_window_fully_elapsed() {
1359            // PERPETUAL_STORAGE_ERAS == 50 and the distribution table has 50
1360            // entries. With epochs_per_era = 20, `current_era` reaches 50 (the
1361            // full window) at paid_epochs = 50 * 20 = 1000, at which point every
1362            // table era compares `Less`, `ratio_used` sums to zero, and the old
1363            // code panicked on `dec!(1) / 0`. It must now return a DivideByZero
1364            // error so the consensus path can propagate it instead of aborting.
1365            let result = original_removed_credits_multiplier_from(0, 1000, 20);
1366            assert!(
1367                matches!(result, Err(ProtocolError::DivideByZero(_))),
1368                "expected DivideByZero error once the window is fully elapsed, got {:?}",
1369                result
1370            );
1371
1372            // The caller propagates the error rather than panicking.
1373            let restored = restore_original_removed_credits_amount(dec!(1_000_000), 0, 1000, 20);
1374            assert!(
1375                matches!(restored, Err(ProtocolError::DivideByZero(_))),
1376                "restore_original_removed_credits_amount must propagate the error, got {:?}",
1377                restored
1378            );
1379        }
1380
1381        #[test]
1382        fn should_return_error_when_repayment_epoch_precedes_start() {
1383            // Defensive guard: a repayment epoch before the original storage
1384            // epoch must not underflow the `paid_epochs` subtraction.
1385            let result = original_removed_credits_multiplier_from(10, 5, 20);
1386            assert!(
1387                matches!(result, Err(ProtocolError::Overflow(_))),
1388                "expected Overflow error on underflowing epoch difference, got {:?}",
1389                result
1390            );
1391        }
1392    }
1393
1394    mod additional_restore_original_removed_credits_amount {
1395        use super::*;
1396
1397        #[test]
1398        fn should_restore_to_original_when_no_epochs_passed() {
1399            // If start_repayment == start, multiplier is 1.0, so restored == refund_amount
1400            let refund = dec!(1000000);
1401            let restored = restore_original_removed_credits_amount(refund, 0, 0, 20)
1402                .expect("should not overflow");
1403            assert_eq!(restored, refund);
1404        }
1405
1406        #[test]
1407        fn should_increase_amount_when_epochs_have_passed() {
1408            // After some epochs, the multiplier > 1, so restored > refund
1409            let refund = dec!(500000);
1410            let restored = restore_original_removed_credits_amount(refund, 0, 10, 20)
1411                .expect("should not overflow");
1412            assert!(restored > refund);
1413        }
1414
1415        #[test]
1416        fn should_handle_zero_refund_amount() {
1417            let restored = restore_original_removed_credits_amount(dec!(0), 0, 10, 20)
1418                .expect("should handle zero");
1419            assert_eq!(restored, dec!(0));
1420        }
1421    }
1422
1423    mod additional_refund_storage_fee_to_epochs_map {
1424        use super::*;
1425
1426        #[test]
1427        fn should_return_zero_leftovers_for_zero_storage_fee() {
1428            let leftovers = refund_storage_fee_to_epochs_map(0, 0, 1, |_, _| Ok(()), 20)
1429                .expect("should handle zero");
1430            assert_eq!(leftovers, 0);
1431        }
1432
1433        #[test]
1434        fn should_skip_epochs_before_skip_until_index() {
1435            let storage_fee = 1000000u64;
1436            let start = 0u16;
1437            let skip_until = 10u16;
1438
1439            let mut min_epoch_seen = u16::MAX;
1440
1441            let _leftovers = refund_storage_fee_to_epochs_map(
1442                storage_fee,
1443                start,
1444                skip_until,
1445                |epoch_index, _amount| {
1446                    if epoch_index < min_epoch_seen {
1447                        min_epoch_seen = epoch_index;
1448                    }
1449                    Ok(())
1450                },
1451                20,
1452            )
1453            .expect("should distribute refund");
1454
1455            // The first epoch called should be >= skip_until
1456            assert!(min_epoch_seen >= skip_until);
1457        }
1458
1459        #[test]
1460        fn should_distribute_to_single_remaining_epoch_in_era() {
1461            // skip_until is 19 (last epoch of era 0), start is 0
1462            // This means only 1 epoch remains in era 0
1463            let storage_fee = 100000u64;
1464
1465            let mut epoch_count = 0u32;
1466
1467            let leftovers = refund_storage_fee_to_epochs_map(
1468                storage_fee,
1469                0,
1470                19,
1471                |_epoch_index, _amount| {
1472                    epoch_count += 1;
1473                    Ok(())
1474                },
1475                20,
1476            )
1477            .expect("should distribute");
1478
1479            // Total epochs = (1000 - 19) = 981 epochs should be called
1480            assert_eq!(epoch_count, 981);
1481            assert!(leftovers < storage_fee);
1482        }
1483
1484        #[test]
1485        fn should_handle_skip_at_era_boundary() {
1486            // skip_until exactly at era 1 start
1487            let storage_fee = 500000u64;
1488            let start = 0u16;
1489            let skip_until = 20u16; // era 1 starts here
1490
1491            let mut epochs_called = Vec::new();
1492
1493            let _leftovers = refund_storage_fee_to_epochs_map(
1494                storage_fee,
1495                start,
1496                skip_until,
1497                |epoch_index, _amount| {
1498                    epochs_called.push(epoch_index);
1499                    Ok(())
1500                },
1501                20,
1502            )
1503            .expect("should distribute");
1504
1505            // First epoch called should be exactly skip_until
1506            assert_eq!(*epochs_called.first().unwrap(), skip_until);
1507            // Total = 1000 - 20 = 980
1508            assert_eq!(epochs_called.len(), 980);
1509        }
1510    }
1511}