use crate::plutus::utils::compute_total_ex_units;
use crate::plutus::ExUnitPrices;
use crate::transaction::Transaction;
use crate::Coin;
use cml_core::{serialization::Serialize, ArithmeticError};
use num::{rational::BigRational, CheckedAdd, CheckedMul};
use std::convert::TryFrom;
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct LinearFee {
pub coefficient: Coin,
pub constant: Coin,
pub ref_script_cost_per_byte: Coin,
}
impl LinearFee {
pub fn new(coefficient: Coin, constant: Coin, ref_script_cost_per_byte: Coin) -> Self {
Self {
constant,
coefficient,
ref_script_cost_per_byte,
}
}
}
pub fn min_script_fee(
tx: &Transaction,
ex_unit_prices: &ExUnitPrices,
) -> Result<Coin, ArithmeticError> {
if let Some(redeemers) = &tx.witness_set.redeemers {
let total_ex_units = compute_total_ex_units(&redeemers.clone().to_flat_format())?;
let script_fee = ((BigRational::new(total_ex_units.mem.into(), 1u64.into())
* BigRational::new(
ex_unit_prices.mem_price.numerator.into(),
ex_unit_prices.mem_price.denominator.into(),
))
+ (BigRational::new(total_ex_units.steps.into(), 1u64.into())
* BigRational::new(
ex_unit_prices.step_price.numerator.into(),
ex_unit_prices.step_price.denominator.into(),
)))
.ceil()
.to_integer();
u64::try_from(script_fee).map_err(|_| ArithmeticError::IntegerOverflow)
} else {
Ok(0)
}
}
pub fn min_ref_script_fee(
linear_fee: &LinearFee,
total_ref_script_size: u64,
) -> Result<Coin, ArithmeticError> {
if total_ref_script_size > 0 {
let multiplier = BigRational::new(12u64.into(), 10u64.into());
let size_increment = 25_600u64; let mut fee: BigRational = BigRational::from_integer(0.into());
let mut fee_tier: BigRational =
BigRational::from_integer(linear_fee.ref_script_cost_per_byte.into());
let mut ref_scripts_size_left = total_ref_script_size;
loop {
fee = BigRational::from_integer(
std::cmp::min(size_increment, ref_scripts_size_left).into(),
)
.checked_mul(&fee_tier)
.and_then(|x| x.checked_add(&fee))
.ok_or(ArithmeticError::IntegerOverflow)?;
if ref_scripts_size_left <= size_increment {
break;
}
ref_scripts_size_left -= size_increment;
fee_tier = fee_tier
.checked_mul(&multiplier)
.ok_or(ArithmeticError::IntegerOverflow)?;
}
u64::try_from(fee.ceil().to_integer()).map_err(|_e| ArithmeticError::IntegerOverflow)
} else {
Ok(0)
}
}
pub fn min_no_script_fee(
tx: &Transaction,
linear_fee: &LinearFee,
) -> Result<Coin, ArithmeticError> {
(tx.to_cbor_bytes().len() as u64)
.checked_mul(linear_fee.coefficient)
.and_then(|x| x.checked_add(linear_fee.constant))
.ok_or(ArithmeticError::IntegerOverflow)
}
pub fn min_fee(
tx: &Transaction,
linear_fee: &LinearFee,
ex_unit_prices: &ExUnitPrices,
total_ref_script_size: u64,
) -> Result<Coin, ArithmeticError> {
let base_fee = min_no_script_fee(tx, linear_fee)?;
let script_fee = min_script_fee(tx, ex_unit_prices)?;
let ref_scripts_fee = min_ref_script_fee(linear_fee, total_ref_script_size)?;
base_fee
.checked_add(script_fee)
.and_then(|x| x.checked_add(ref_scripts_fee))
.ok_or(ArithmeticError::IntegerOverflow)
}