cardano_serialization_lib/
fees.rs

1use super::*;
2use crate::rational::Rational;
3
4#[wasm_bindgen]
5#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
6pub struct LinearFee {
7    constant: Coin,
8    coefficient: Coin,
9}
10
11#[wasm_bindgen]
12impl LinearFee {
13    pub fn constant(&self) -> Coin {
14        self.constant
15    }
16
17    pub fn coefficient(&self) -> Coin {
18        self.coefficient
19    }
20
21    pub fn new(coefficient: &Coin, constant: &Coin) -> Self {
22        Self {
23            constant: constant.clone(),
24            coefficient: coefficient.clone(),
25        }
26    }
27}
28
29#[wasm_bindgen]
30pub fn min_fee(tx: &Transaction, linear_fee: &LinearFee) -> Result<Coin, JsError> {
31    min_fee_for_size(tx.to_bytes().len(), linear_fee)
32}
33
34pub fn min_fee_for_size(size: usize, linear_fee: &LinearFee) -> Result<Coin, JsError> {
35    BigNum::from(size)
36        .checked_mul(&linear_fee.coefficient())?
37        .checked_add(&linear_fee.constant())
38}
39
40#[wasm_bindgen]
41pub fn calculate_ex_units_ceil_cost(
42    ex_units: &ExUnits,
43    ex_unit_prices: &ExUnitPrices,
44) -> Result<Coin, JsError> {
45    let mem_price: Rational = ex_unit_prices.mem_price().into();
46    let steps_price: Rational = ex_unit_prices.step_price().into();
47    let mem_ratio = mem_price.mul_bignum(&ex_units.mem())?;
48    let steps_ratio = steps_price.mul_bignum(&ex_units.steps())?;
49    let total = mem_ratio.add(&steps_ratio);
50    total.to_bignum_ceil()
51}
52
53#[wasm_bindgen]
54pub fn min_script_fee(tx: &Transaction, ex_unit_prices: &ExUnitPrices) -> Result<Coin, JsError> {
55    if let Some(redeemers) = &tx.witness_set.redeemers {
56        let total_ex_units: ExUnits = redeemers.total_ex_units()?;
57        return calculate_ex_units_ceil_cost(&total_ex_units, ex_unit_prices);
58    }
59    Ok(Coin::zero())
60}
61
62#[wasm_bindgen]
63pub fn min_ref_script_fee(
64    total_ref_scripts_size: usize,
65    ref_script_coins_per_byte: &UnitInterval,
66) -> Result<Coin, JsError> {
67    let multiplier = Rational::new(BigInt::from(12), BigInt::from(10)); // 1.2
68    let size_increment: usize = 25_600; // 25KiB
69    let ref_multiplier: Rational = ref_script_coins_per_byte.into();
70    let total_fee = tier_ref_script_fee(
71        multiplier,
72        size_increment,
73        ref_multiplier,
74        total_ref_scripts_size,
75    )?;
76
77    Ok(total_fee)
78}
79
80fn tier_ref_script_fee(
81    multiplier: Rational,
82    size_increment: usize,
83    base_fee: Rational,
84    total_size: usize,
85) -> Result<BigNum, JsError> {
86    if multiplier.is_negative_or_zero() || size_increment == 0 {
87        return Err(JsError::from_str(
88            "Size increment and multiplier must be positive",
89        ));
90    }
91
92    let full_tiers = (total_size / size_increment) as u32;
93    let partial_tier_size = total_size % size_increment;
94    let tier_price = base_fee.mul_usize(size_increment);
95
96    let mut acc = Rational::zero();
97
98    if full_tiers > 0 {
99        let progression_enumerator = Rational::one().sub(&multiplier.pow(full_tiers));
100        let progression_denominator = Rational::one().sub(&multiplier);
101        let tier_progression_sum = progression_enumerator.div_ratio(&progression_denominator);
102        acc = acc.add(&tier_price.mul_ratio(&tier_progression_sum));
103    }
104
105    // Add the partial tier
106    if partial_tier_size > 0 {
107        let last_tier_price = base_fee.mul_ratio(&multiplier.pow(full_tiers));
108        let partial_tier_fee = last_tier_price.mul_usize(partial_tier_size);
109        acc = acc.add(&partial_tier_fee);
110    }
111
112    acc.to_bignum_floor()
113}