Skip to main content

darkpool_client/
economics.rs

1//! Fee calculation: gas costs to payment-asset fees with configurable premium.
2
3use ethers::types::U256;
4use tracing::warn;
5
6#[derive(Debug, Clone)]
7pub struct PriceData {
8    /// Scaled by 10^8
9    pub eth_usd: U256,
10    /// Scaled by 10^8
11    pub asset_usd: U256,
12    pub gas_price: U256,
13}
14
15#[derive(Debug, Clone)]
16pub struct FeeEstimate {
17    pub fee_amount: U256,
18    pub gas_limit: U256,
19    pub gas_price: U256,
20    pub premium_bps: u64,
21}
22
23#[derive(Debug, Clone)]
24pub struct FeeConfig {
25    /// Relayer profit margin in basis points (1200 = 12%).
26    pub premium_bps: u64,
27    pub default_gas_limit: U256,
28}
29
30impl Default for FeeConfig {
31    fn default() -> Self {
32        Self {
33            premium_bps: 1200,
34            default_gas_limit: U256::from(300_000),
35        }
36    }
37}
38
39#[derive(Debug, Clone)]
40pub struct FeeManager {
41    pub config: FeeConfig,
42}
43
44impl FeeManager {
45    #[must_use]
46    pub fn new(config: FeeConfig) -> Self {
47        Self { config }
48    }
49
50    /// `fee = gas_limit * gas_price * eth_usd * (1 + premium) / asset_usd`
51    #[must_use]
52    pub fn calculate_fee(&self, gas_limit: U256, prices: &PriceData) -> FeeEstimate {
53        let gas_cost_wei = gas_limit * prices.gas_price;
54        let premium_multiplier = U256::from(10_000 + self.config.premium_bps);
55        let fee_with_premium = (gas_cost_wei * premium_multiplier) / U256::from(10_000);
56
57        let fee_in_asset = if prices.asset_usd > U256::zero() {
58            (fee_with_premium * prices.eth_usd) / prices.asset_usd
59        } else {
60            warn!(
61                "Asset price is zero in calculate_fee. Returning max fee to prevent \
62                 incorrect fee calculation. Check price oracle."
63            );
64            U256::MAX
65        };
66
67        FeeEstimate {
68            fee_amount: fee_in_asset,
69            gas_limit,
70            gas_price: prices.gas_price,
71            premium_bps: self.config.premium_bps,
72        }
73    }
74
75    /// Adjusts for asset decimals (e.g., USDC has 6 vs ETH's 18).
76    #[must_use]
77    pub fn calculate_fee_with_decimals(
78        &self,
79        gas_limit: U256,
80        prices: &PriceData,
81        asset_decimals: u8,
82    ) -> FeeEstimate {
83        let mut estimate = self.calculate_fee(gas_limit, prices);
84        if asset_decimals < 18 {
85            let decimal_diff = 18 - asset_decimals;
86            let divisor = U256::from(10u64).pow(U256::from(decimal_diff));
87            estimate.fee_amount /= divisor;
88        } else if asset_decimals > 18 {
89            let decimal_diff = asset_decimals - 18;
90            let multiplier = U256::from(10u64).pow(U256::from(decimal_diff));
91            estimate.fee_amount *= multiplier;
92        }
93
94        estimate
95    }
96}
97
98impl Default for FeeManager {
99    fn default() -> Self {
100        Self::new(FeeConfig::default())
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_fee_manager_calculate() {
110        let manager = FeeManager::new(FeeConfig {
111            premium_bps: 1200,
112            default_gas_limit: U256::from(300_000),
113        });
114
115        let prices = PriceData {
116            eth_usd: U256::from(3000_00000000u64),
117            asset_usd: U256::from(1_00000000u64),
118            gas_price: U256::from(20_000_000_000u64),
119        };
120
121        let estimate = manager.calculate_fee(U256::from(100_000), &prices);
122        assert!(estimate.fee_amount > U256::zero());
123        assert_eq!(estimate.premium_bps, 1200);
124    }
125
126    #[test]
127    fn test_fee_manager_with_decimals() {
128        let manager = FeeManager::default();
129
130        let prices = PriceData {
131            eth_usd: U256::from(3000_00000000u64),
132            asset_usd: U256::from(1_00000000u64),
133            gas_price: U256::from(20_000_000_000u64),
134        };
135
136        let estimate_18 = manager.calculate_fee(U256::from(100_000), &prices);
137        let estimate_6 = manager.calculate_fee_with_decimals(U256::from(100_000), &prices, 6);
138
139        assert!(estimate_6.fee_amount < estimate_18.fee_amount);
140    }
141
142    #[test]
143    fn test_fee_estimate() {
144        let prices = PriceData {
145            eth_usd: U256::from(3000_00000000u64),
146            asset_usd: U256::from(1_00000000u64),
147            gas_price: U256::from(20_000_000_000u64),
148        };
149
150        let gas_limit = U256::from(100_000);
151        let gas_cost = gas_limit * prices.gas_price;
152        let with_premium = (gas_cost * U256::from(11200)) / U256::from(10000);
153        assert!(with_premium > gas_cost);
154    }
155}