use chrono::{DateTime, Utc};
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use crate::error::{CoreError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum MEVOpportunityType {
Arbitrage,
Sandwich,
Liquidation,
Backrun,
Frontrun,
JITLiquidity,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MEVOpportunity {
pub id: String,
pub opportunity_type: MEVOpportunityType,
pub estimated_profit: Decimal,
pub block_number: u64,
pub transaction_indices: Vec<u64>,
pub token_pairs: Vec<(String, String)>,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MEVSearcher {
pub id: String,
pub address: String,
pub total_mev_captured: Decimal,
pub successful_captures: u64,
pub failed_attempts: u64,
pub success_rate: f64,
pub reputation: f64,
pub stake: Decimal,
pub registered_at: DateTime<Utc>,
}
impl MEVSearcher {
pub fn new(id: String, address: String, stake: Decimal) -> Self {
Self {
id,
address,
total_mev_captured: Decimal::ZERO,
successful_captures: 0,
failed_attempts: 0,
success_rate: 0.0,
reputation: 0.5, stake,
registered_at: Utc::now(),
}
}
pub fn record_success(&mut self, mev_captured: Decimal) {
self.total_mev_captured += mev_captured;
self.successful_captures += 1;
self.update_success_rate();
self.update_reputation();
}
pub fn record_failure(&mut self) {
self.failed_attempts += 1;
self.update_success_rate();
self.update_reputation();
}
fn update_success_rate(&mut self) {
let total_attempts = self.successful_captures + self.failed_attempts;
if total_attempts > 0 {
self.success_rate = self.successful_captures as f64 / total_attempts as f64;
}
}
fn update_reputation(&mut self) {
let stake_ratio = self.stake / dec!(1000);
let stake_component = if self.stake >= dec!(1000) {
1.0
} else {
stake_ratio.to_string().parse::<f64>().unwrap_or(0.0)
};
self.reputation = (self.success_rate * 0.7) + (stake_component * 0.3);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MEVAuctionBid {
pub id: String,
pub searcher_id: String,
pub opportunity_id: String,
pub bid_amount: Decimal,
pub share_percentage: Decimal,
pub gas_price: Decimal,
pub timestamp: DateTime<Utc>,
}
impl MEVAuctionBid {
pub fn new(
id: String,
searcher_id: String,
opportunity_id: String,
bid_amount: Decimal,
share_percentage: Decimal,
gas_price: Decimal,
) -> Result<Self> {
if share_percentage < Decimal::ZERO || share_percentage > dec!(100) {
return Err(CoreError::Validation(
"Share percentage must be between 0 and 100".to_string(),
));
}
Ok(Self {
id,
searcher_id,
opportunity_id,
bid_amount,
share_percentage,
gas_price,
timestamp: Utc::now(),
})
}
pub fn value_score(&self) -> Decimal {
self.bid_amount * (self.share_percentage / dec!(100))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MEVRecipient {
pub address: String,
pub weight: Decimal,
pub total_received: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MEVRedistribution {
pub id: String,
pub opportunity_id: String,
pub total_mev: Decimal,
pub redistributed_amount: Decimal,
pub protocol_fee: Decimal,
pub recipients: Vec<(String, Decimal)>,
pub searcher_id: String,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum OrderingMode {
FCFS,
FairSequencing,
BatchAuction,
PriorityFee,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FairOrderingConfig {
pub mode: OrderingMode,
pub min_time_between_txs: u64,
pub max_position_change: u32,
pub anti_sandwich: bool,
pub frontrun_detection: bool,
}
impl Default for FairOrderingConfig {
fn default() -> Self {
Self {
mode: OrderingMode::FairSequencing,
min_time_between_txs: 100, max_position_change: 3,
anti_sandwich: true,
frontrun_detection: true,
}
}
}
#[derive(Debug)]
pub struct MEVRedistributionManager {
pub searchers: HashMap<String, MEVSearcher>,
pub opportunities: HashMap<String, MEVOpportunity>,
pub bids: HashMap<String, Vec<MEVAuctionBid>>,
pub recipients: HashMap<String, MEVRecipient>,
pub redistributions: Vec<MEVRedistribution>,
pub fair_ordering_config: FairOrderingConfig,
pub protocol_fee_percentage: Decimal,
pub min_share_percentage: Decimal,
}
impl MEVRedistributionManager {
pub fn new(protocol_fee_percentage: Decimal, min_share_percentage: Decimal) -> Result<Self> {
if protocol_fee_percentage < Decimal::ZERO || protocol_fee_percentage > dec!(100) {
return Err(CoreError::Validation(
"Protocol fee percentage must be between 0 and 100".to_string(),
));
}
if min_share_percentage < Decimal::ZERO || min_share_percentage > dec!(100) {
return Err(CoreError::Validation(
"Min share percentage must be between 0 and 100".to_string(),
));
}
Ok(Self {
searchers: HashMap::new(),
opportunities: HashMap::new(),
bids: HashMap::new(),
recipients: HashMap::new(),
redistributions: Vec::new(),
fair_ordering_config: FairOrderingConfig::default(),
protocol_fee_percentage,
min_share_percentage,
})
}
pub fn register_searcher(&mut self, searcher: MEVSearcher) -> Result<()> {
if self.searchers.contains_key(&searcher.id) {
return Err(CoreError::Validation(format!(
"Searcher {} already registered",
searcher.id
)));
}
self.searchers.insert(searcher.id.clone(), searcher);
Ok(())
}
pub fn detect_opportunity(&mut self, opportunity: MEVOpportunity) -> Result<()> {
if self.opportunities.contains_key(&opportunity.id) {
return Err(CoreError::Validation(format!(
"Opportunity {} already exists",
opportunity.id
)));
}
let opportunity_id = opportunity.id.clone();
self.opportunities
.insert(opportunity_id.clone(), opportunity);
self.bids.insert(opportunity_id, Vec::new());
Ok(())
}
pub fn submit_bid(&mut self, bid: MEVAuctionBid) -> Result<()> {
if !self.searchers.contains_key(&bid.searcher_id) {
return Err(CoreError::Validation(format!(
"Searcher {} not registered",
bid.searcher_id
)));
}
if !self.opportunities.contains_key(&bid.opportunity_id) {
return Err(CoreError::Validation(format!(
"Opportunity {} not found",
bid.opportunity_id
)));
}
if bid.share_percentage < self.min_share_percentage {
return Err(CoreError::Validation(format!(
"Share percentage {} is below minimum {}",
bid.share_percentage, self.min_share_percentage
)));
}
self.bids.get_mut(&bid.opportunity_id).unwrap().push(bid);
Ok(())
}
pub fn select_winner(&mut self, opportunity_id: &str) -> Result<MEVAuctionBid> {
let bids = self.bids.get(opportunity_id).ok_or_else(|| {
CoreError::Validation(format!("No bids for opportunity {}", opportunity_id))
})?;
if bids.is_empty() {
return Err(CoreError::Validation(format!(
"No bids submitted for opportunity {}",
opportunity_id
)));
}
let winning_bid = bids
.iter()
.max_by(|a, b| {
a.value_score()
.partial_cmp(&b.value_score())
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap()
.clone();
Ok(winning_bid)
}
pub fn add_recipient(&mut self, address: String, weight: Decimal) {
self.recipients
.entry(address.clone())
.and_modify(|r| r.weight = weight)
.or_insert(MEVRecipient {
address,
weight,
total_received: Decimal::ZERO,
});
}
pub fn execute_redistribution(
&mut self,
opportunity_id: &str,
actual_mev_captured: Decimal,
) -> Result<MEVRedistribution> {
let _opportunity = self.opportunities.get(opportunity_id).ok_or_else(|| {
CoreError::Validation(format!("Opportunity {} not found", opportunity_id))
})?;
let winning_bid = self.select_winner(opportunity_id)?;
let protocol_fee = actual_mev_captured * (self.protocol_fee_percentage / dec!(100));
let searcher_share = actual_mev_captured - protocol_fee;
let redistributed_amount = searcher_share * (winning_bid.share_percentage / dec!(100));
let total_weight: Decimal = self.recipients.values().map(|r| r.weight).sum();
let mut recipient_amounts = Vec::new();
if total_weight > Decimal::ZERO {
for recipient in self.recipients.values_mut() {
let share = (recipient.weight / total_weight) * redistributed_amount;
recipient.total_received += share;
recipient_amounts.push((recipient.address.clone(), share));
}
}
if let Some(searcher) = self.searchers.get_mut(&winning_bid.searcher_id) {
searcher.record_success(actual_mev_captured);
}
let redistribution = MEVRedistribution {
id: format!("redistrib-{}", uuid::Uuid::new_v4()),
opportunity_id: opportunity_id.to_string(),
total_mev: actual_mev_captured,
redistributed_amount,
protocol_fee,
recipients: recipient_amounts,
searcher_id: winning_bid.searcher_id.clone(),
timestamp: Utc::now(),
};
self.redistributions.push(redistribution.clone());
self.bids.remove(opportunity_id);
Ok(redistribution)
}
pub fn total_redistributed(&self) -> Decimal {
self.redistributions
.iter()
.map(|r| r.redistributed_amount)
.sum()
}
pub fn total_protocol_fees(&self) -> Decimal {
self.redistributions.iter().map(|r| r.protocol_fee).sum()
}
pub fn get_searcher_stats(&self, searcher_id: &str) -> Option<&MEVSearcher> {
self.searchers.get(searcher_id)
}
pub fn get_recipient_stats(&self, address: &str) -> Option<&MEVRecipient> {
self.recipients.get(address)
}
}
impl Default for MEVRedistributionManager {
fn default() -> Self {
Self::new(dec!(10), dec!(50)).unwrap()
}
}
#[derive(Debug)]
pub struct PGAMitigator {
pub recent_gas_prices: VecDeque<Decimal>,
pub max_gas_price_multiplier: Decimal,
pub flagged_txs: HashMap<String, u32>,
pub window_size: usize,
}
impl PGAMitigator {
pub fn new(max_gas_price_multiplier: Decimal, window_size: usize) -> Self {
Self {
recent_gas_prices: VecDeque::with_capacity(window_size),
max_gas_price_multiplier,
flagged_txs: HashMap::new(),
window_size,
}
}
pub fn check_transaction(&mut self, tx_hash: &str, gas_price: Decimal) -> Result<bool> {
let median_gas_price = if self.recent_gas_prices.is_empty() {
gas_price
} else {
let mut sorted: Vec<Decimal> = self.recent_gas_prices.iter().copied().collect();
sorted.sort();
sorted[sorted.len() / 2]
};
let is_excessive = gas_price > median_gas_price * self.max_gas_price_multiplier;
if is_excessive {
*self.flagged_txs.entry(tx_hash.to_string()).or_insert(0) += 1;
}
if self.recent_gas_prices.len() >= self.window_size {
self.recent_gas_prices.pop_front();
}
self.recent_gas_prices.push_back(gas_price);
Ok(is_excessive)
}
pub fn get_flag_count(&self, tx_hash: &str) -> u32 {
*self.flagged_txs.get(tx_hash).unwrap_or(&0)
}
pub fn clear_old_flags(&mut self) {
self.flagged_txs.retain(|_, count| *count < 10);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mev_searcher() {
let mut searcher =
MEVSearcher::new("searcher-1".to_string(), "0x123".to_string(), dec!(1000));
assert_eq!(searcher.successful_captures, 0);
assert_eq!(searcher.total_mev_captured, Decimal::ZERO);
searcher.record_success(dec!(100));
assert_eq!(searcher.successful_captures, 1);
assert_eq!(searcher.total_mev_captured, dec!(100));
searcher.record_failure();
assert_eq!(searcher.failed_attempts, 1);
assert_eq!(searcher.success_rate, 0.5);
}
#[test]
fn test_mev_auction_bid() {
let bid = MEVAuctionBid::new(
"bid-1".to_string(),
"searcher-1".to_string(),
"opp-1".to_string(),
dec!(100),
dec!(60),
dec!(50),
)
.unwrap();
assert_eq!(bid.value_score(), dec!(60));
let invalid = MEVAuctionBid::new(
"bid-2".to_string(),
"searcher-1".to_string(),
"opp-1".to_string(),
dec!(100),
dec!(150), dec!(50),
);
assert!(invalid.is_err());
}
#[test]
fn test_mev_redistribution_manager() {
let mut manager = MEVRedistributionManager::default();
let searcher = MEVSearcher::new("searcher-1".to_string(), "0x123".to_string(), dec!(1000));
manager.register_searcher(searcher).unwrap();
let opportunity = MEVOpportunity {
id: "opp-1".to_string(),
opportunity_type: MEVOpportunityType::Arbitrage,
estimated_profit: dec!(100),
block_number: 1000,
transaction_indices: vec![1, 2],
token_pairs: vec![("USDC".to_string(), "ETH".to_string())],
timestamp: Utc::now(),
};
manager.detect_opportunity(opportunity).unwrap();
let bid = MEVAuctionBid::new(
"bid-1".to_string(),
"searcher-1".to_string(),
"opp-1".to_string(),
dec!(100),
dec!(60),
dec!(50),
)
.unwrap();
manager.submit_bid(bid).unwrap();
manager.add_recipient("user1".to_string(), dec!(1));
manager.add_recipient("user2".to_string(), dec!(2));
let redistribution = manager.execute_redistribution("opp-1", dec!(100)).unwrap();
assert_eq!(redistribution.total_mev, dec!(100));
assert!(redistribution.redistributed_amount > Decimal::ZERO);
assert!(redistribution.protocol_fee > Decimal::ZERO);
}
#[test]
fn test_pga_mitigator() {
let mut mitigator = PGAMitigator::new(dec!(1.5), 10);
assert!(!mitigator.check_transaction("tx1", dec!(50)).unwrap());
assert!(!mitigator.check_transaction("tx2", dec!(55)).unwrap());
assert!(mitigator.check_transaction("tx3", dec!(150)).unwrap());
assert_eq!(mitigator.get_flag_count("tx3"), 1);
}
}