use std::cmp::Ordering;
use zakura_chain::transaction::{zip317::MARGINAL_FEE, MEMPOOL_TRANSACTION_COST_THRESHOLD};
#[derive(Copy, Clone, Debug)]
pub struct EvictionCost {
fee: u64,
cost: u64,
}
impl EvictionCost {
pub fn new(fee: u64, cost: u64) -> Self {
assert!(
cost > 0,
"ZIP-401 cost is at least MEMPOOL_TRANSACTION_COST_THRESHOLD"
);
Self { fee, cost }
}
pub fn combine(self, other: Self) -> Self {
Self {
fee: self.fee.saturating_add(other.fee),
cost: self.cost.saturating_add(other.cost),
}
}
pub fn cost(self) -> u64 {
self.cost
}
pub fn exceeds_by_increment(self, victim: Self) -> bool {
let threshold = u128::from(MEMPOOL_TRANSACTION_COST_THRESHOLD);
let lhs = u128::from(self.fee)
.saturating_mul(u128::from(victim.cost))
.saturating_mul(threshold);
let rhs = u128::from(victim.fee)
.saturating_mul(u128::from(self.cost))
.saturating_mul(threshold)
.saturating_add(
u128::from(MARGINAL_FEE)
.saturating_mul(u128::from(self.cost))
.saturating_mul(u128::from(victim.cost)),
);
lhs >= rhs
}
pub fn zat_per_threshold_cost(self) -> f64 {
self.fee as f64 * MEMPOOL_TRANSACTION_COST_THRESHOLD as f64 / self.cost as f64
}
}
impl PartialEq for EvictionCost {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl Eq for EvictionCost {}
impl PartialOrd for EvictionCost {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for EvictionCost {
fn cmp(&self, other: &Self) -> Ordering {
(u128::from(self.fee) * u128::from(other.cost))
.cmp(&(u128::from(other.fee) * u128::from(self.cost)))
}
}
#[cfg(test)]
mod tests {
use zakura_chain::amount::MAX_MONEY;
use super::*;
#[test]
fn orders_by_fee_per_cost() {
assert!(EvictionCost::new(1, 2) < EvictionCost::new(1, 1));
assert_eq!(EvictionCost::new(1, 2), EvictionCost::new(2, 4));
assert!(EvictionCost::new(0, 10_000) < EvictionCost::new(1, u64::MAX));
}
#[test]
fn increment_is_one_marginal_fee_per_threshold_cost() {
let victim = EvictionCost::new(10_000, 10_000);
let at_increment = EvictionCost::new(10_000 + MARGINAL_FEE, 10_000);
let below_increment = EvictionCost::new(10_000 + MARGINAL_FEE - 1, 10_000);
assert!(at_increment.exceeds_by_increment(victim));
assert!(!below_increment.exceeds_by_increment(victim));
assert!(!victim.exceeds_by_increment(victim));
let large = EvictionCost::new(2 * (10_000 + MARGINAL_FEE), 20_000);
assert!(large.exceeds_by_increment(victim));
let large_short = EvictionCost::new(2 * (10_000 + MARGINAL_FEE) - 1, 20_000);
assert!(!large_short.exceeds_by_increment(victim));
}
#[test]
fn extreme_values_do_not_overflow() {
let max_fee = u64::try_from(MAX_MONEY).expect("MAX_MONEY is positive");
let max_cost = 80_000_000;
let rich = EvictionCost::new(max_fee, MEMPOOL_TRANSACTION_COST_THRESHOLD);
let poor = EvictionCost::new(0, max_cost);
assert!(poor < rich);
assert!(rich.exceeds_by_increment(poor));
assert!(!poor.exceeds_by_increment(rich));
let saturated = EvictionCost::new(u64::MAX, 1).combine(EvictionCost::new(u64::MAX, 1));
assert!(saturated > rich);
assert!(!EvictionCost::new(u64::MAX, u64::MAX).exceeds_by_increment(saturated));
}
}