kabu_types_entities/
tips.rs1use std::collections::HashMap;
2use std::fmt::{Display, Formatter};
3use std::sync::Arc;
4
5use crate::{Swap, Token};
6use alloy_primitives::utils::format_units;
7use alloy_primitives::{Address, U256};
8use eyre::{eyre, OptionExt, Result};
9use kabu_evm_utils::NWETH;
10use lazy_static::lazy_static;
11use rand::random;
12use tracing::{error, info};
13
14#[derive(Clone, Debug)]
15pub struct Tips {
16 pub token_in: Arc<Token>,
17 pub profit: U256,
18 pub profit_eth: U256,
19 pub tips: U256,
20 pub min_change: U256,
21}
22
23impl Display for Tips {
24 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
25 write!(
26 f,
27 "{} : tips {} min_change {} profit : {} eth : {} ",
28 self.token_in.get_symbol(),
29 format_units(self.tips, "ether").unwrap_or_default(),
30 self.token_in.to_float(self.min_change),
31 self.token_in.to_float(self.profit),
32 format_units(self.profit_eth, "ether").unwrap_or_default(),
33 )
34 }
35}
36
37lazy_static! {
38 static ref START_PCT : U256 = U256::from(9900);
39
40 static ref SLOPES : Vec<(U256,U256)> = vec![
41(U256::from(10).pow(U256::from(19)), U256::from(7000)),
43 (U256::from(10).pow(U256::from(19))*U256::from(5), U256::from(5000))
44 ];
45}
46pub fn tips_pct_advanced(profit: &U256) -> u32 {
47 let mut start_point = U256::ZERO;
48 let mut start_pct = *START_PCT;
49 for (x, y) in SLOPES.iter() {
50 if x > profit {
51 return (start_pct - ((start_pct - y) * (profit - start_point) / (x - start_point))).to::<u32>();
52 }
53 start_point = *x;
54 start_pct = *y;
55 }
56 start_pct.to()
57}
58
59pub fn randomize_tips_pct(tips_pct: u32) -> u32 {
60 let rnd: u32 = random::<u32>() % 50;
61 tips_pct - rnd
62}
63
64pub fn tips_and_value_for_swap_type(
65 swap: &Swap,
66 tips_pct: Option<u32>,
67 gas_cost: Option<U256>,
68 eth_balance: U256,
69) -> Result<(Vec<Tips>, U256)> {
70 let total_profit_eth = swap.arb_profit_eth();
71 info!("Total profit eth : {}", format_units(total_profit_eth, "ether").unwrap_or_default());
72 let tips_pct = randomize_tips_pct(tips_pct.unwrap_or(tips_pct_advanced(&total_profit_eth)));
73
74 if let Some(gas_cost) = gas_cost {
75 if total_profit_eth < gas_cost {
76 info!(
77 "total_profit_eth={} < {}",
78 format_units(total_profit_eth, "ether").unwrap_or_default(),
79 format_units(gas_cost, "ether").unwrap_or_default()
80 );
81 return Err(eyre!("NOT_ENOUGH_PROFIT"));
82 }
83 }
84
85 match swap {
86 Swap::BackrunSwapLine(_) | Swap::BackrunSwapSteps(_) => {
87 let profit = swap.arb_profit();
88 if profit.is_zero() {
89 error!(profit = NWETH::to_float(profit), %swap, "Zero profit");
90 return Err(eyre!("NO_PROFIT"));
91 }
92 let token_in = swap.get_first_token().ok_or_eyre("NO_FIRST_TOKEN")?.clone();
93 let profit_eth = token_in.calc_eth_value(profit).ok_or_eyre("CALC_ETH_VALUE_FAILED")?;
94
95 if let Some(gas_cost) = gas_cost {
96 if profit_eth < gas_cost {
97 error!(
98 profit_eth = NWETH::to_float(profit_eth),
99 gas_cost = NWETH::to_float(gas_cost),
100 %swap,
101 "Profit doesn't exceed the gas cost"
102 );
103 return Err(eyre!("NO_PROFIT_EXCEEDING_GAS"));
104 }
105 }
106
107 let mut tips = profit_eth.checked_sub(gas_cost.unwrap_or_default()).ok_or_eyre("SUBTRACTION_OVERFLOWN")? * U256::from(tips_pct)
108 / U256::from(10000);
109 let min_change = token_in.calc_token_value_from_eth(gas_cost.unwrap_or_default() + tips).unwrap();
110 let mut value = if token_in.is_weth() { U256::ZERO } else { tips };
111
112 if !token_in.is_weth() && (tips > ((eth_balance * U256::from(9000)) / U256::from(10000))) {
113 tips = (eth_balance * U256::from(9000)) / U256::from(10000);
114 value = tips;
115 }
116
117 Ok((vec![Tips { token_in, profit, profit_eth, tips, min_change }], value))
118 }
119 Swap::Multiple(swap_vec) => {
120 let mut tips_hashset: HashMap<Address, Tips> = HashMap::new();
121
122 let profit_eth = swap.arb_profit_eth();
123
124 if let Some(gas_cost) = gas_cost {
125 if profit_eth < gas_cost {
126 error!(
127 profit_eth = NWETH::to_float(profit_eth),
128 gas_cost = NWETH::to_float(gas_cost),
129 %swap,
130 "Profit doesn't exceed the gas cost"
131 );
132 return Err(eyre!("NO_PROFIT_EXCEEDING_GAS"));
133 }
134 }
135
136 let gas_cost_per_record = gas_cost.unwrap_or_default() / U256::from(swap_vec.len());
137
138 for swap_record in swap_vec.iter() {
139 let token_in = swap_record.get_first_token().ok_or_eyre("NO_FIRST_TOKEN")?.clone();
140
141 let profit = swap_record.arb_profit();
142 if profit.is_zero() {
143 error!(profit = NWETH::to_float(profit), %swap, "Zero profit");
144 return Err(eyre!("NO_PROFIT"));
145 }
146
147 let profit_eth = token_in.calc_eth_value(profit).ok_or_eyre("CALC_ETH_VALUE_FAILED")?;
148
149 let tips = profit_eth.checked_sub(gas_cost_per_record).ok_or_eyre("SUBTRACTION_OVERFLOWN")? * U256::from(tips_pct)
150 / U256::from(10000);
151 let min_change = token_in.calc_token_value_from_eth(tips + gas_cost_per_record).unwrap();
152
153 let entry = tips_hashset.entry(token_in.get_address()).or_insert(Tips {
154 token_in,
155 profit: U256::ZERO,
156 profit_eth: U256::ZERO,
157 tips: U256::ZERO,
158 min_change: U256::ZERO,
159 });
160
161 entry.profit += profit;
162 entry.profit_eth += profit_eth;
163 entry.tips += tips;
164 entry.min_change += min_change;
165 }
166
167 let mut value = U256::ZERO;
168
169 if tips_hashset.iter().any(|(_, x)| x.token_in.is_weth()) {
170 let total_tips_eth: U256 = tips_hashset.iter().filter(|(_, x)| x.token_in.is_weth()).map(|(_, x)| x.tips).sum();
171 let total_tips_non_eth: U256 = tips_hashset.iter().filter(|(_, x)| !x.token_in.is_weth()).map(|(_, x)| x.tips).sum();
172 let total_profit_eth: U256 = tips_hashset.iter().filter(|(_, x)| x.token_in.is_weth()).map(|(_, x)| x.profit_eth).sum();
173 for (_, token_tips) in tips_hashset.iter_mut() {
174 if token_tips.token_in.is_weth() {
175 token_tips.tips = total_tips_eth + total_tips_non_eth;
176 if total_tips_eth + total_tips_non_eth > total_profit_eth {
177 value = total_tips_eth + total_tips_non_eth - total_profit_eth;
178 }
179
180 if value > eth_balance {
181 token_tips.tips = token_tips.tips.checked_sub(value).ok_or_eyre("SUBTRACTION_OVERFLOWN")?;
182 value = eth_balance * U256::from(9000) / U256::from(10000);
183 token_tips.tips += value;
184 }
185 } else {
186 token_tips.tips = U256::ZERO;
187 }
188 }
189 } else {
190 let total_tips = tips_hashset.values().map(|x| x.tips).sum::<U256>();
191 value = if total_tips >= eth_balance { eth_balance * U256::from(9000) / U256::from(10000) } else { total_tips };
192
193 for (idx, (_, token_tips)) in tips_hashset.iter_mut().enumerate() {
194 token_tips.tips = if idx == 0 { value } else { U256::ZERO };
195 }
196 }
197 let tips_vec = tips_hashset.values().cloned().collect();
198
199 Ok((tips_vec, value + U256::from(100)))
200 }
201
202 _ => Err(eyre!("NOT_IMPLEMENTED")),
203 }
204}