1use crate::plutus::utils::compute_total_ex_units;
2use crate::plutus::ExUnitPrices;
3use crate::transaction::Transaction;
4use crate::Coin;
5use cml_core::{serialization::Serialize, ArithmeticError};
6use num::{rational::BigRational, CheckedAdd, CheckedMul};
7use std::convert::TryFrom;
8
9#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
12pub struct LinearFee {
13 pub coefficient: Coin,
15 pub constant: Coin,
17 pub ref_script_cost_per_byte: Coin,
19}
20
21impl LinearFee {
22 pub fn new(coefficient: Coin, constant: Coin, ref_script_cost_per_byte: Coin) -> Self {
28 Self {
29 constant,
30 coefficient,
31 ref_script_cost_per_byte,
32 }
33 }
34}
35
36pub fn min_script_fee(
40 tx: &Transaction,
41 ex_unit_prices: &ExUnitPrices,
42) -> Result<Coin, ArithmeticError> {
43 if let Some(redeemers) = &tx.witness_set.redeemers {
44 let total_ex_units = compute_total_ex_units(&redeemers.clone().to_flat_format())?;
45 let script_fee = ((BigRational::new(total_ex_units.mem.into(), 1u64.into())
46 * BigRational::new(
47 ex_unit_prices.mem_price.numerator.into(),
48 ex_unit_prices.mem_price.denominator.into(),
49 ))
50 + (BigRational::new(total_ex_units.steps.into(), 1u64.into())
51 * BigRational::new(
52 ex_unit_prices.step_price.numerator.into(),
53 ex_unit_prices.step_price.denominator.into(),
54 )))
55 .ceil()
56 .to_integer();
57 u64::try_from(script_fee).map_err(|_| ArithmeticError::IntegerOverflow)
58 } else {
59 Ok(0)
60 }
61}
62
63pub fn min_ref_script_fee(
68 linear_fee: &LinearFee,
69 total_ref_script_size: u64,
70) -> Result<Coin, ArithmeticError> {
71 if total_ref_script_size > 0 {
76 let multiplier = BigRational::new(12u64.into(), 10u64.into());
77 let size_increment = 25_600u64; let mut fee: BigRational = BigRational::from_integer(0.into());
79 let mut fee_tier: BigRational =
80 BigRational::from_integer(linear_fee.ref_script_cost_per_byte.into());
81 let mut ref_scripts_size_left = total_ref_script_size;
82
83 loop {
84 fee = BigRational::from_integer(
85 std::cmp::min(size_increment, ref_scripts_size_left).into(),
86 )
87 .checked_mul(&fee_tier)
88 .and_then(|x| x.checked_add(&fee))
89 .ok_or(ArithmeticError::IntegerOverflow)?;
90 if ref_scripts_size_left <= size_increment {
91 break;
92 }
93 ref_scripts_size_left -= size_increment;
94 fee_tier = fee_tier
95 .checked_mul(&multiplier)
96 .ok_or(ArithmeticError::IntegerOverflow)?;
97 }
98 u64::try_from(fee.ceil().to_integer()).map_err(|_e| ArithmeticError::IntegerOverflow)
99 } else {
100 Ok(0)
101 }
102}
103
104pub fn min_no_script_fee(
105 tx: &Transaction,
106 linear_fee: &LinearFee,
107) -> Result<Coin, ArithmeticError> {
108 (tx.to_cbor_bytes().len() as u64)
109 .checked_mul(linear_fee.coefficient)
110 .and_then(|x| x.checked_add(linear_fee.constant))
111 .ok_or(ArithmeticError::IntegerOverflow)
112}
113
114pub fn min_fee(
115 tx: &Transaction,
116 linear_fee: &LinearFee,
117 ex_unit_prices: &ExUnitPrices,
118 total_ref_script_size: u64,
119) -> Result<Coin, ArithmeticError> {
120 let base_fee = min_no_script_fee(tx, linear_fee)?;
122 let script_fee = min_script_fee(tx, ex_unit_prices)?;
123 let ref_scripts_fee = min_ref_script_fee(linear_fee, total_ref_script_size)?;
124 base_fee
125 .checked_add(script_fee)
126 .and_then(|x| x.checked_add(ref_scripts_fee))
127 .ok_or(ArithmeticError::IntegerOverflow)
128}