From 91965474ed06cd6215c9bc48eb6c262bbe4b4c6b Mon Sep 17 00:00:00 2001 From: "rayane.lattari" Date: Fri, 28 Feb 2025 11:15:32 +0100 Subject: [PATCH 1/7] [Ethereum-data-analysis] create sandwich algorithm --- src/common/types.ml | 86 ++- src/ethereum-analysis/dune | 4 + .../eth_analysis_sandwich.ml | 675 ++++++++++++++++++ 3 files changed, 763 insertions(+), 2 deletions(-) create mode 100644 src/ethereum-analysis/dune create mode 100644 src/ethereum-analysis/eth_analysis_sandwich.ml diff --git a/src/common/types.ml b/src/common/types.ml index 97614b9c..1df1f7fb 100644 --- a/src/common/types.ml +++ b/src/common/types.ml @@ -1,6 +1,6 @@ -module Ethereum_indexer = struct - open Eth +open Eth +module Ethereum_indexer = struct type contract_type = | ERC20 [@key "Erc20"] | UniswapV2 [@key "UniswapV2"] @@ -366,6 +366,88 @@ module Bitcoin_indexer = struct [@@deriving encoding { remove_prefix = "cn_" }] end +module Ethereum_analysis = struct + type swap = { + sw_amountIn : bz; + sw_amountOut : bz; + sw_in_token : address; + sw_out_token : address; + sw_sender : address; + sw_recipient : address; + sw_contract : address; + } + [@@deriving encoding { remove_prefix = "sw_" }] + + type token_swaped = { + tsw_in_token : address; + tsw_out_token : address; + tsw_contract : address; + } + [@@deriving encoding { remove_prefix = "tsw_" }] + + type swap_transaction = { + swtx_transaction_hash : b; + swtx_index : bint; + swtx_sender : address; + swtx_swap_list : swap list; + swtx_summed_swap_list : swap list; + swtx_exchange_token_contract : token_swaped list; + } + [@@deriving encoding { remove_prefix = "swtx_" }] + + type uni_v2_swap = { + v2_contract : address; + v2_sender : address; + v2_recipient : address; + v2_amount0In : bz; + v2_amount1In : bz; + v2_amount0Out : bz; + v2_amount1Out : bz; + v2_token0 : address; + v2_token1 : address; + } + [@@deriving encoding { remove_prefix = "v2_" }] + + type uni_v3_swap = { + v3_contract : address; + v3_sender : Eth.address; + v3_recipient : Eth.address; + v3_amount0 : bz; + v3_amount1 : bz; + v3_sqrt_price : bz; + v3_liquidity : bz; + v3_tick : bz; + v3_token0 : address; + v3_token1 : address; + } + [@@deriving encoding { remove_prefix = "v3_" }] + + (* A sandwich is defined for a unique token_swap. We can have multiple + sandwich with the same victim, front run and back run, but with different + token_swap.*) + type sandwich = { + sd_front_run : swap_transaction; + sd_back_run : swap_transaction; + sd_victims : swap_transaction list; + sd_token_sandwich : token_swaped; + } + [@@deriving encoding { remove_prefix = "sd_" }] + + type token_ratio_swap = { + trsw_swap_transaction : swap_transaction; + trsw_ratio : (bz * bz) list; + } + [@@deriving encoding { remove_prefix = "trsw_" }] + + type sandwich_analysed = { + sda_sandwich : sandwich; + sda_token_ratio_swap_fr : token_ratio_swap; + sda_token_ratio_swap_br : token_ratio_swap; + sda_token_ratio_swap_victim : token_ratio_swap list; + } + [@@deriving encoding { remove_prefix = "sda_" }] +end + type verbose = | Nothing | Low diff --git a/src/ethereum-analysis/dune b/src/ethereum-analysis/dune new file mode 100644 index 00000000..88a4e309 --- /dev/null +++ b/src/ethereum-analysis/dune @@ -0,0 +1,4 @@ +(library + (name eth_analysis_sandwich) + (modules eth_analysis_sandwich) + (libraries eth_indexer lwt common)) diff --git a/src/ethereum-analysis/eth_analysis_sandwich.ml b/src/ethereum-analysis/eth_analysis_sandwich.ml new file mode 100644 index 00000000..f4d7392a --- /dev/null +++ b/src/ethereum-analysis/eth_analysis_sandwich.ml @@ -0,0 +1,675 @@ +open Lwt +open Eth +open Common.Types.Ethereum_analysis +open Common.Types.Ethereum_indexer +open Common.Tools +open Eth_indexer + +let eq_inverse_token_swaped ts1 ts2 = + ts1.tsw_out_token = ts2.tsw_in_token + && ts1.tsw_in_token = ts2.tsw_out_token + && ts1.tsw_contract = ts2.tsw_contract + +let eq_token_swaped ts1 ts2 = + ts1.tsw_in_token = ts2.tsw_in_token + && ts1.tsw_out_token = ts2.tsw_out_token + && ts1.tsw_contract = ts2.tsw_contract + +let list_uniq_token_swaped list = + List.fold_left + (fun acc arg -> + match List.find_opt (eq_token_swaped arg) acc with + | None -> arg :: acc + | Some _ -> acc) + [] list + +let get_token_address (log : debug_trace_result_log) = + match log.dtrl_decode_signature with + | None -> failwith "couldn't retreive token address" + | Some arg -> ( + match arg.event_decode with + | Event_unknown_abi _ -> failwith "couldn't retreive token address" + | Event_abi event_abi -> ( + let contract_info = event_abi.ea_contract_info in + match contract_info.dci_pool_contracts with + | None -> failwith "couldn't retreive token address" + | Some (contract1, contract2) -> + (contract_info.dci_address, contract1.dci_address, contract2.dci_address) + )) + +let get_tokens_unkown_abi_token adr token = + Node.call_contract ~typ:`address adr token [] [] >>= function + | Error e -> Lwt.fail_with (EzEncoding.construct error_enc e) + | Ok r -> ( + match r with + | `address address -> Lwt.return address + | _ -> Lwt.fail_with "not address") + +let v2_event_id = + b "d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822" + +let v3_event_id = + b "c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67" + +let v2_event_description = + "Swap(address,uint256,uint256,uint256,uint256,address)" + +let v3_event_description = + "Swap(address,address,int256,int256,uint160,uint128,int24)" + +let get_address_from_evmvalue (evm_value : Eth.evm_value) = + match evm_value with + | `address a -> a + | `bytes bytes -> ( + match Eth.Evm.decode `address bytes with + | `address a -> a + | e -> + failwith + @@ Format.sprintf "fail get_address_from_evmvalue %s" + @@ EzEncoding.construct Eth.evm_value_enc e) + | e -> + failwith + @@ Format.sprintf "fail get_address_from_evmvalue %s" + @@ EzEncoding.construct Eth.evm_value_enc e + +let get_int_from_evmvalue (evm_value : Eth.evm_value) = + match evm_value with + | `int int -> int + | e -> + failwith + @@ Format.sprintf "fail get_address_from_evmvalue %s" + @@ EzEncoding.construct Eth.evm_value_enc e + +let general_swapV2 (swap : uni_v2_swap) = + let sw_amountIn, sw_in_token = + match Z.compare swap.v2_amount0In swap.v2_amount1In with + | -1 -> (swap.v2_amount1In, swap.v2_token1) + | 1 -> (swap.v2_amount0In, swap.v2_token0) + | _ -> assert false in + let sw_amountOut, sw_out_token = + match Z.compare swap.v2_amount0Out swap.v2_amount1Out with + | -1 -> (swap.v2_amount1Out, swap.v2_token1) + | 1 -> (swap.v2_amount0Out, swap.v2_token0) + | _ -> assert false in + let sw_sender, sw_recipient, sw_contract = + (swap.v2_sender, swap.v2_recipient, swap.v2_contract) in + { + sw_amountIn; + sw_amountOut; + sw_in_token; + sw_out_token; + sw_sender; + sw_recipient; + sw_contract; + } + +let decode_know_abi_v2 (log : debug_trace_result_log) (abi : event_decode_abi) = + try + let contract = abi.ea_contract_info in + match contract.dci_type with + | None -> None + | Some contract_type -> ( + match contract_type with + | ERC20 | UniswapV3 -> None + | UniswapV2 -> ( + match log.dtrl_decode_signature with + | None -> None + | Some decode_signature -> ( + match + String.equal + (decode_signature.event_hash :> string) + (v2_event_id :> string) + with + | false -> None + | true -> + let inputs = + List.sort + (fun arg1 arg2 -> compare arg1.pda_index arg2.pda_index) + abi.ea_inputs.eai_inputs in + let v2_contract, v2_token0, v2_token1 = get_token_address log in + let v2_sender = + let tmp = List.nth inputs 0 in + get_address_from_evmvalue tmp.pda_data in + let v2_amount0In = + let tmp = List.nth inputs 1 in + get_int_from_evmvalue tmp.pda_data in + let v2_amount1In = + let tmp = List.nth inputs 2 in + get_int_from_evmvalue tmp.pda_data in + let v2_amount0Out = + let tmp = List.nth inputs 3 in + get_int_from_evmvalue tmp.pda_data in + let v2_amount1Out = + let tmp = List.nth inputs 4 in + get_int_from_evmvalue tmp.pda_data in + let v2_recipient = + let tmp = List.nth inputs 5 in + get_address_from_evmvalue tmp.pda_data in + Some + { + v2_contract; + v2_sender; + v2_recipient; + v2_amount0In; + v2_amount1In; + v2_amount0Out; + v2_amount1Out; + v2_token0; + v2_token1; + }))) + with _ -> None + +let decode_unknow_abi_v2 (log : debug_trace_result_log) + (abi : event_decode_unknown_abi) = + let decode_topics = abi.edu_topics in + let bytes = + List.filter_map + (fun x -> + match x with + | `bytes b -> Some b + | _ -> None) + abi.edu_data in + let types_inputs = [`int 256; `int 256; `int 256; `int 256] in + let types_decode_topics = [`address; `address] in + + let length_types_decode_topics = List.length types_decode_topics in + let length_decode_topics = List.length decode_topics in + + let length_bytes = List.length bytes in + let length_types_inputs = List.length types_inputs in + + let b1 = length_types_inputs = length_bytes in + let b2 = length_types_decode_topics = length_decode_topics in + + match b1 && b2 with + | false -> Lwt.return @@ None + | true -> ( + let decode_inputs = + List.fold_left2 + (fun acc t b -> try Eth.Evm.decode t b :: acc with _ -> acc) + [] types_inputs bytes in + let inputs = + match decode_inputs with + | [v2_amount0In; v2_amount1In; v2_amount0Out; v2_amount1Out] -> + Some + ( get_int_from_evmvalue v2_amount0In, + get_int_from_evmvalue v2_amount1In, + get_int_from_evmvalue v2_amount0Out, + get_int_from_evmvalue v2_amount1Out ) + | _ -> None in + let topics = + match decode_topics with + | [v2_sender; v2_recipient] -> + Some + ( get_address_from_evmvalue v2_sender, + get_address_from_evmvalue v2_recipient ) + | _ -> None in + match (inputs, topics) with + | Some inputs, Some topics -> + let v2_contract = log.dtrl_address in + let> v2_token0 = get_tokens_unkown_abi_token v2_contract "token0" in + let> v2_token1 = get_tokens_unkown_abi_token v2_contract "token1" in + + let v2_amount0In, v2_amount1In, v2_amount0Out, v2_amount1Out = inputs in + let v2_sender, v2_recipient = topics in + + Lwt.return + @@ Some + { + v2_contract; + v2_sender; + v2_recipient; + v2_amount0In; + v2_amount1In; + v2_amount0Out; + v2_amount1Out; + v2_token0; + v2_token1; + } + | _ -> Lwt.return @@ None) + +let decode_know_abi_v3 (log : debug_trace_result_log) (abi : event_decode_abi) = + try + let contract = abi.ea_contract_info in + match contract.dci_type with + | None -> None + | Some contract_type -> ( + match contract_type with + | ERC20 | UniswapV2 -> None + | UniswapV3 -> ( + match log.dtrl_decode_signature with + | None -> None + | Some decode_signature -> ( + match + String.equal + (decode_signature.event_hash :> string) + (v3_event_id :> string) + with + | false -> None + | true -> + let inputs = + List.sort + (fun arg1 arg2 -> compare arg1.pda_index arg2.pda_index) + abi.ea_inputs.eai_inputs in + + let v3_contract, v3_token0, v3_token1 = get_token_address log in + let v3_sender = + let tmp = List.nth inputs 0 in + get_address_from_evmvalue tmp.pda_data in + let v3_recipient = + let tmp = List.nth inputs 1 in + get_address_from_evmvalue tmp.pda_data in + let v3_amount0 = + let tmp = List.nth inputs 2 in + get_int_from_evmvalue tmp.pda_data in + let v3_amount1 = + let tmp = List.nth inputs 3 in + get_int_from_evmvalue tmp.pda_data in + let v3_sqrt_price = + let tmp = List.nth inputs 4 in + get_int_from_evmvalue tmp.pda_data in + let v3_liquidity = + let tmp = List.nth inputs 5 in + get_int_from_evmvalue tmp.pda_data in + let v3_tick = + let tmp = List.nth inputs 6 in + get_int_from_evmvalue tmp.pda_data in + + Some + { + v3_contract; + v3_sender; + v3_recipient; + v3_amount0; + v3_amount1; + v3_sqrt_price; + v3_liquidity; + v3_tick; + v3_token0; + v3_token1; + }))) + with _ -> None + +let decode_unknow_abi_v3 (log : debug_trace_result_log) + (abi : event_decode_unknown_abi) = + let decode_topics = abi.edu_topics in + let bytes = + List.filter_map + (fun x -> + match x with + | `bytes b -> Some b + | _ -> None) + abi.edu_data in + let types_inputs = [`int 256; `int 256; `uint 160; `uint 128; `int 24] in + let types_decode_topics = [`address; `address] in + + let length_types_decode_topics = List.length types_decode_topics in + let length_decode_topics = List.length decode_topics in + + let length_bytes = List.length bytes in + let length_types_inputs = List.length types_inputs in + + let b1 = length_types_inputs = length_bytes in + let b2 = length_types_decode_topics = length_decode_topics in + + match b1 && b2 with + | false -> Lwt.return @@ None + | true -> ( + let decode_inputs = + List.fold_left2 + (fun acc t b -> try Eth.Evm.decode t b :: acc with _ -> acc) + [] types_inputs bytes in + let inputs = + match decode_inputs with + | [v3_amount0; v3_amount1; v3_sqrt_price; v3_liquidity; v3_tick] -> + Some + ( get_int_from_evmvalue v3_amount0, + get_int_from_evmvalue v3_amount1, + get_int_from_evmvalue v3_sqrt_price, + get_int_from_evmvalue v3_liquidity, + get_int_from_evmvalue v3_tick ) + | _ -> None in + + let topics = + match decode_topics with + | [v3_sender; v3_recipient] -> + Some + ( get_address_from_evmvalue v3_sender, + get_address_from_evmvalue v3_recipient ) + | _ -> None in + match (inputs, topics) with + | Some inputs, Some topics -> + let v3_contract = log.dtrl_address in + let> v3_token0 = get_tokens_unkown_abi_token v3_contract "token0" in + let> v3_token1 = get_tokens_unkown_abi_token v3_contract "token1" in + + let v3_amount0, v3_amount1, v3_sqrt_price, v3_liquidity, v3_tick = + inputs in + let v3_sender, v3_recipient = topics in + Lwt.return + @@ Some + { + v3_contract; + v3_sender; + v3_recipient; + v3_amount0; + v3_amount1; + v3_sqrt_price; + v3_liquidity; + v3_tick; + v3_token0; + v3_token1; + } + | _ -> Lwt.return @@ None) + +let get_v2_swap (log : debug_trace_result_log) = + match log.dtrl_decode_signature with + | None -> Lwt.return @@ None + | Some s -> ( + let event_decode = s.event_decode in + match String.equal (s.event_hash :> string) (v2_event_id :> string) with + | false -> Lwt.return @@ None + | true -> ( + match event_decode with + | Event_abi abi -> Lwt.return @@ decode_know_abi_v2 log abi + | Event_unknown_abi _ -> Lwt.return_none)) + +let get_v3_swap (log : debug_trace_result_log) = + match log.dtrl_decode_signature with + | None -> Lwt.return @@ None + | Some s -> ( + let event_decode = s.event_decode in + match String.equal (s.event_hash :> string) (v3_event_id :> string) with + | false -> Lwt.return @@ None + | true -> ( + match event_decode with + | Event_abi abi -> Lwt.return @@ decode_know_abi_v3 log abi + | Event_unknown_abi _ -> Lwt.return_none)) + +let get_v2_swaps tx_logs = Lwt_list.filter_map_s get_v2_swap tx_logs + +let get_v3_swaps tx_logs = Lwt_list.filter_map_s get_v3_swap tx_logs + +let general_swapV3 (swap : uni_v3_swap) = + try + let sw_amountIn, sw_amountOut, sw_in_token, sw_out_token = + match Z.gt swap.v3_amount0 Z.zero with + | true -> + let b1 = not @@ Z.equal swap.v3_amount1 Z.zero in + let b2 = Z.gt Z.zero swap.v3_amount1 in + if b1 && b2 then + ( swap.v3_amount0, + Z.neg swap.v3_amount1, + swap.v3_token0, + swap.v3_token1 ) + else + failwith "assert failed general_swapV3" + | false -> + let b1 = not @@ Z.equal swap.v3_amount0 Z.zero in + let b2 = Z.gt swap.v3_amount1 Z.zero in + if b1 && b2 then + ( swap.v3_amount1, + Z.neg swap.v3_amount0, + swap.v3_token1, + swap.v3_token0 ) + else + failwith "assert failed general_swapV3" in + let sw_sender, sw_recipient, sw_contract = + (swap.v3_sender, swap.v3_recipient, swap.v3_contract) in + Some + { + sw_amountIn; + sw_amountOut; + sw_in_token; + sw_out_token; + sw_sender; + sw_recipient; + sw_contract; + } + with _ -> None + +let find_swaps (transaction_trace : debug_trace_result) = + let rec get_pair_swaps_v2_v3 (log : debug_trace_result) (swapv2, swapv3) : + ('a * 'b) t = + let> swapv2', swapv3' = + match log.dtr_logs with + | None -> Lwt.return (swapv2, swapv3) + | Some dtr_logs -> + let> v2 = get_v2_swaps dtr_logs in + let> v3 = get_v3_swaps dtr_logs in + Lwt.return (concat_list swapv2 v2, concat_list swapv3 v3) in + match log.dtr_calls with + | None -> Lwt.return (swapv2', swapv3') + | Some dtr_calls -> + let> sv2, sv3 = + Lwt_list.fold_left_s + (fun (acc_s2, acc_s3) arg -> + let> s2, s3 = get_pair_swaps_v2_v3 arg (acc_s2, acc_s3) in + Lwt.return (s2, s3)) + ([], []) dtr_calls in + Lwt.return (concat_list sv2 swapv2', concat_list sv3 swapv3') in + get_pair_swaps_v2_v3 transaction_trace ([], []) + +let gen_swap_transaction (swtx_sender : address) (swtx_transaction_hash : b) + (swtx_index : bint) (swtx_swap_list : swap list) = + let swtx_exchange_token_contract = + list_uniq_token_swaped + @@ List.fold_left + (fun acc (arg : swap) -> + let tsw_in_token, tsw_out_token, tsw_contract = + (arg.sw_in_token, arg.sw_out_token, arg.sw_contract) in + { tsw_in_token; tsw_out_token; tsw_contract } :: acc) + [] swtx_swap_list in + let swtx_summed_swap_list = + List.fold_left + (fun acc (arg : token_swaped) -> + let swaps = + List.find_all + (fun (arg' : swap) -> + arg'.sw_in_token = arg.tsw_in_token + && arg'.sw_out_token = arg.tsw_out_token + && arg'.sw_contract = arg.tsw_contract) + swtx_swap_list in + match swaps with + | [] -> acc + | swaps -> + let sw_amountIn, sw_amountOut = + List.fold_left + (fun (amountIn, amountOut) arg -> + ( Z.add amountIn arg.sw_amountIn, + Z.add amountOut arg.sw_amountOut )) + (Z.zero, Z.zero) swaps in + let swap = List.hd swaps in + let sw_in_token, sw_out_token, sw_sender, sw_recipient, sw_contract = + ( swap.sw_in_token, + swap.sw_out_token, + swap.sw_sender, + swap.sw_recipient, + swap.sw_contract ) in + { + sw_amountIn; + sw_amountOut; + sw_in_token; + sw_out_token; + sw_sender; + sw_recipient; + sw_contract; + } + :: acc) + [] swtx_exchange_token_contract in + { + swtx_transaction_hash; + swtx_index; + swtx_sender; + swtx_swap_list; + swtx_summed_swap_list; + swtx_exchange_token_contract; + } + +let detect_fr_br (swap_transaction_list : swap_transaction list) = + let rec inter (swap_transaction_list : swap_transaction list) acc_fr_br = + match swap_transaction_list with + | [] | _ :: [] -> acc_fr_br + | swap :: tl_swaps -> ( + let sender = swap.swtx_sender in + let exchange_token_contract = swap.swtx_exchange_token_contract in + let possible_br = + List.find_all (fun arg -> arg.swtx_sender = sender) tl_swaps in + let br : (swap_transaction * token_swaped list) list = + List.fold_left + (fun acc arg -> + let br_exchange_token_contract = arg.swtx_exchange_token_contract in + let br_token_swap = + (* I only keep the token_swaped from the Front Run *) + List.fold_left + (fun acc_token_swaped arg_token_swaped -> + match + List.find_opt + (eq_inverse_token_swaped arg_token_swaped) + br_exchange_token_contract + with + | None -> acc_token_swaped + | Some _ -> arg_token_swaped :: acc_token_swaped) + [] exchange_token_contract in + match br_token_swap with + | [] -> acc + | br_token_swap -> (arg, br_token_swap) :: acc) + [] possible_br in + match br with + | [] -> inter tl_swaps acc_fr_br + | br -> inter tl_swaps @@ ((swap, br) :: acc_fr_br)) in + let swap_transaction_list = + List.sort + (fun swap1 swap2 -> + if swap1.swtx_index > swap2.swtx_index then + 1 + else if swap1.swtx_index = swap2.swtx_index then + 0 + else + -1) + swap_transaction_list in + inter swap_transaction_list [] + +let find_victim + (fr_br : swap_transaction * (swap_transaction * token_swaped list)) + (swap_list : swap_transaction list) = + let fr, (br, token_swaped_list) = fr_br in + let deb_index, fin_index = (fr.swtx_index, br.swtx_index) in + let in_index_swap_list = + List.filter + (fun arg -> arg.swtx_index > deb_index && arg.swtx_index < fin_index) + swap_list in + match in_index_swap_list with + | [] -> [] + | in_index_swap_list -> + let victim_by_token = + List.fold_left + (fun acc arg -> + (* We're looking for swap_transactions where for a token_exchange of a + front run, it exist in his token_exchange list. Knowing that each + token_swap of swap_transaction should be unique in the list) *) + let victims = + List.find_all + (fun in_index_swap -> + List.exists (eq_token_swaped arg) + in_index_swap.swtx_exchange_token_contract) + in_index_swap_list in + match victims with + | [] -> acc + | sd_victims -> + let sandwich = + { + sd_front_run = fr; + sd_back_run = br; + sd_victims; + sd_token_sandwich = arg; + } in + sandwich :: acc) + [] token_swaped_list in + victim_by_token + +(* Each Fr can be used for multiple Br with different token_swaped *) +let general_find_victim + (fr_br_list : + (swap_transaction * (swap_transaction * token_swaped list) list) list) + (swap_list : swap_transaction list) = + List.fold_left + (fun acc arg -> + let fr, br_token_swap_list = arg in + let sandwichs = + List.fold_left + (fun acc_sandwich br_token_swap -> + match find_victim (fr, br_token_swap) swap_list with + | [] -> acc_sandwich + | sandwichs -> concat_list sandwichs acc_sandwich) + [] br_token_swap_list in + match sandwichs with + | [] -> acc + | sandwichs -> concat_list sandwichs acc) + [] fr_br_list + +let analyse_sandwich (sda_sandwich : sandwich) = + let get_ratio_fr_victims (token_sandwich : token_swaped) + (trsw_swap_transaction : swap_transaction) = + let swaps = + List.filter + (fun (swap : swap) -> + swap.sw_in_token = token_sandwich.tsw_in_token + && swap.sw_out_token = token_sandwich.tsw_out_token + && swap.sw_contract = token_sandwich.tsw_contract) + trsw_swap_transaction.swtx_swap_list in + let trsw_ratio = + List.map (fun swap -> (swap.sw_amountIn, swap.sw_amountOut)) swaps in + { trsw_swap_transaction; trsw_ratio } in + let get_ratio_br (token_sandwich : token_swaped) + (trsw_swap_transaction : swap_transaction) = + let swaps = + List.filter + (fun (swap : swap) -> + swap.sw_in_token = token_sandwich.tsw_out_token + && swap.sw_out_token = token_sandwich.tsw_in_token + && swap.sw_contract = token_sandwich.tsw_contract) + trsw_swap_transaction.swtx_swap_list in + let trsw_ratio = + List.map (fun swap -> (swap.sw_amountIn, swap.sw_amountOut)) swaps in + { trsw_swap_transaction; trsw_ratio } in + let token = sda_sandwich.sd_token_sandwich in + let sda_token_ratio_swap_fr = + get_ratio_fr_victims token sda_sandwich.sd_front_run in + let sda_token_ratio_swap_br = get_ratio_br token sda_sandwich.sd_back_run in + let sda_token_ratio_swap_victim = + List.map (get_ratio_fr_victims token) sda_sandwich.sd_victims in + { + sda_sandwich; + sda_token_ratio_swap_fr; + sda_token_ratio_swap_br; + sda_token_ratio_swap_victim; + } + +let analyse_sandwich_block (sandwich_list : sandwich list) = + List.map analyse_sandwich sandwich_list + +let detect_sandwich debug_trace_list = + let> swap_transaction_list = + Lwt_list.fold_left_s + (fun acc arg -> + let transaction_hash = arg.dt_tx_hash in + let sender = arg.dt_result.dtr_from in + let index = arg.dt_tx_index in + let> swapV2, swapV3 = find_swaps arg.dt_result in + let swap_list = + let v2 = List.map general_swapV2 swapV2 in + let v3 = List.filter_map general_swapV3 swapV3 in + concat_list v2 v3 in + match swap_list with + | [] -> Lwt.return acc + | swap_list -> + Lwt.return + (gen_swap_transaction sender transaction_hash index swap_list :: acc)) + [] debug_trace_list in + let detect_fr_br = detect_fr_br swap_transaction_list in + Lwt.return + @@ + match detect_fr_br with + | [] -> [] + | detect_fr_br -> general_find_victim detect_fr_br swap_transaction_list -- GitLab From 3ef710a78372de941ac1959996b569480dcc2d20 Mon Sep 17 00:00:00 2001 From: "rayane.lattari" Date: Mon, 3 Mar 2025 12:37:54 +0100 Subject: [PATCH 2/7] [Ethereum-data-analysis] Fix types name for Ethereum_analysis --- src/common/types.ml | 100 ++++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/common/types.ml b/src/common/types.ml index 1df1f7fb..57f89333 100644 --- a/src/common/types.ml +++ b/src/common/types.ml @@ -368,59 +368,59 @@ end module Ethereum_analysis = struct type swap = { - sw_amountIn : bz; - sw_amountOut : bz; - sw_in_token : address; - sw_out_token : address; - sw_sender : address; - sw_recipient : address; - sw_contract : address; + s_amountIn : bz; + s_amountOut : bz; + s_in_token : address; + s_out_token : address; + s_sender : address; + s_recipient : address; + s_contract : address; } - [@@deriving encoding { remove_prefix = "sw_" }] + [@@deriving encoding { remove_prefix = "s_" }] type token_swaped = { - tsw_in_token : address; - tsw_out_token : address; - tsw_contract : address; + ts_in_token : address; + ts_out_token : address; + ts_contract : address; } - [@@deriving encoding { remove_prefix = "tsw_" }] + [@@deriving encoding { remove_prefix = "ts_" }] type swap_transaction = { - swtx_transaction_hash : b; - swtx_index : bint; - swtx_sender : address; - swtx_swap_list : swap list; - swtx_summed_swap_list : swap list; - swtx_exchange_token_contract : token_swaped list; + st_transaction_hash : b; + st_index : bint; + st_sender : address; + st_swap_list : swap list; + st_summed_swap_list : swap list; + st_exchange_token_contract : token_swaped list; } - [@@deriving encoding { remove_prefix = "swtx_" }] + [@@deriving encoding { remove_prefix = "st_" }] type uni_v2_swap = { - v2_contract : address; - v2_sender : address; - v2_recipient : address; - v2_amount0In : bz; - v2_amount1In : bz; - v2_amount0Out : bz; - v2_amount1Out : bz; - v2_token0 : address; - v2_token1 : address; - } - [@@deriving encoding { remove_prefix = "v2_" }] + uv2s_contract : address; + uv2s_sender : address; + uv2s_recipient : address; + uv2s_amount0In : bz; + uv2s_amount1In : bz; + uv2s_amount0Out : bz; + uv2s_amount1Out : bz; + uv2s_token0 : address; + uv2s_token1 : address; + } + [@@deriving encoding { remove_prefix = "uv2s_" }] type uni_v3_swap = { - v3_contract : address; - v3_sender : Eth.address; - v3_recipient : Eth.address; - v3_amount0 : bz; - v3_amount1 : bz; - v3_sqrt_price : bz; - v3_liquidity : bz; - v3_tick : bz; - v3_token0 : address; - v3_token1 : address; - } - [@@deriving encoding { remove_prefix = "v3_" }] + uv3s_contract : address; + uv3s_sender : Eth.address; + uv3s_recipient : Eth.address; + uv3s_amount0 : bz; + uv3s_amount1 : bz; + uv3s_sqrt_price : bz; + uv3s_liquidity : bz; + uv3s_tick : bz; + uv3s_token0 : address; + uv3s_token1 : address; + } + [@@deriving encoding { remove_prefix = "uv3s_" }] (* A sandwich is defined for a unique token_swap. We can have multiple sandwich with the same victim, front run and back run, but with different @@ -434,18 +434,18 @@ module Ethereum_analysis = struct [@@deriving encoding { remove_prefix = "sd_" }] type token_ratio_swap = { - trsw_swap_transaction : swap_transaction; - trsw_ratio : (bz * bz) list; + trs_swap_transaction : swap_transaction; + trs_ratio : (bz * bz) list; } - [@@deriving encoding { remove_prefix = "trsw_" }] + [@@deriving encoding { remove_prefix = "trs_" }] type sandwich_analysed = { - sda_sandwich : sandwich; - sda_token_ratio_swap_fr : token_ratio_swap; - sda_token_ratio_swap_br : token_ratio_swap; - sda_token_ratio_swap_victim : token_ratio_swap list; + sa_sandwich : sandwich; + sa_token_ratio_swap_fr : token_ratio_swap; + sa_token_ratio_swap_br : token_ratio_swap; + sa_token_ratio_swap_victim : token_ratio_swap list; } - [@@deriving encoding { remove_prefix = "sda_" }] + [@@deriving encoding { remove_prefix = "sa_" }] end type verbose = -- GitLab From 136a88e01f0c660969ca5db5513e0b73e5dbefc6 Mon Sep 17 00:00:00 2001 From: "rayane.lattari" Date: Mon, 3 Mar 2025 13:22:18 +0100 Subject: [PATCH 3/7] [Ethereum-data-analysis] Fix changed types --- .../eth_analysis_sandwich.ml | 395 +++++++++--------- 1 file changed, 199 insertions(+), 196 deletions(-) diff --git a/src/ethereum-analysis/eth_analysis_sandwich.ml b/src/ethereum-analysis/eth_analysis_sandwich.ml index f4d7392a..529c8f67 100644 --- a/src/ethereum-analysis/eth_analysis_sandwich.ml +++ b/src/ethereum-analysis/eth_analysis_sandwich.ml @@ -6,14 +6,14 @@ open Common.Tools open Eth_indexer let eq_inverse_token_swaped ts1 ts2 = - ts1.tsw_out_token = ts2.tsw_in_token - && ts1.tsw_in_token = ts2.tsw_out_token - && ts1.tsw_contract = ts2.tsw_contract + ts1.ts_out_token = ts2.ts_in_token + && ts1.ts_in_token = ts2.ts_out_token + && ts1.ts_contract = ts2.ts_contract let eq_token_swaped ts1 ts2 = - ts1.tsw_in_token = ts2.tsw_in_token - && ts1.tsw_out_token = ts2.tsw_out_token - && ts1.tsw_contract = ts2.tsw_contract + ts1.ts_in_token = ts2.ts_in_token + && ts1.ts_out_token = ts2.ts_out_token + && ts1.ts_contract = ts2.ts_contract let list_uniq_token_swaped list = List.fold_left @@ -27,10 +27,10 @@ let get_token_address (log : debug_trace_result_log) = match log.dtrl_decode_signature with | None -> failwith "couldn't retreive token address" | Some arg -> ( - match arg.event_decode with + match arg.evdi_decode with | Event_unknown_abi _ -> failwith "couldn't retreive token address" | Event_abi event_abi -> ( - let contract_info = event_abi.ea_contract_info in + let contract_info = event_abi.evda_contract_info in match contract_info.dci_pool_contracts with | None -> failwith "couldn't retreive token address" | Some (contract1, contract2) -> @@ -81,31 +81,31 @@ let get_int_from_evmvalue (evm_value : Eth.evm_value) = @@ EzEncoding.construct Eth.evm_value_enc e let general_swapV2 (swap : uni_v2_swap) = - let sw_amountIn, sw_in_token = - match Z.compare swap.v2_amount0In swap.v2_amount1In with - | -1 -> (swap.v2_amount1In, swap.v2_token1) - | 1 -> (swap.v2_amount0In, swap.v2_token0) + let s_amountIn, s_in_token = + match Z.compare swap.uv2s_amount0In swap.uv2s_amount1In with + | -1 -> (swap.uv2s_amount1In, swap.uv2s_token1) + | 1 -> (swap.uv2s_amount0In, swap.uv2s_token0) | _ -> assert false in - let sw_amountOut, sw_out_token = - match Z.compare swap.v2_amount0Out swap.v2_amount1Out with - | -1 -> (swap.v2_amount1Out, swap.v2_token1) - | 1 -> (swap.v2_amount0Out, swap.v2_token0) + let s_amountOut, s_out_token = + match Z.compare swap.uv2s_amount0Out swap.uv2s_amount1Out with + | -1 -> (swap.uv2s_amount1Out, swap.uv2s_token1) + | 1 -> (swap.uv2s_amount0Out, swap.uv2s_token0) | _ -> assert false in - let sw_sender, sw_recipient, sw_contract = - (swap.v2_sender, swap.v2_recipient, swap.v2_contract) in + let s_sender, s_recipient, s_contract = + (swap.uv2s_sender, swap.uv2s_recipient, swap.uv2s_contract) in { - sw_amountIn; - sw_amountOut; - sw_in_token; - sw_out_token; - sw_sender; - sw_recipient; - sw_contract; + s_amountIn; + s_amountOut; + s_in_token; + s_out_token; + s_sender; + s_recipient; + s_contract; } let decode_know_abi_v2 (log : debug_trace_result_log) (abi : event_decode_abi) = try - let contract = abi.ea_contract_info in + let contract = abi.evda_contract_info in match contract.dci_type with | None -> None | Some contract_type -> ( @@ -117,7 +117,7 @@ let decode_know_abi_v2 (log : debug_trace_result_log) (abi : event_decode_abi) = | Some decode_signature -> ( match String.equal - (decode_signature.event_hash :> string) + (decode_signature.evdi_hash :> string) (v2_event_id :> string) with | false -> None @@ -125,50 +125,51 @@ let decode_know_abi_v2 (log : debug_trace_result_log) (abi : event_decode_abi) = let inputs = List.sort (fun arg1 arg2 -> compare arg1.pda_index arg2.pda_index) - abi.ea_inputs.eai_inputs in - let v2_contract, v2_token0, v2_token1 = get_token_address log in - let v2_sender = + abi.evda_inputs.evdai_inputs in + let uv2s_contract, uv2s_token0, uv2s_token1 = + get_token_address log in + let uv2s_sender = let tmp = List.nth inputs 0 in get_address_from_evmvalue tmp.pda_data in - let v2_amount0In = + let uv2s_amount0In = let tmp = List.nth inputs 1 in get_int_from_evmvalue tmp.pda_data in - let v2_amount1In = + let uv2s_amount1In = let tmp = List.nth inputs 2 in get_int_from_evmvalue tmp.pda_data in - let v2_amount0Out = + let uv2s_amount0Out = let tmp = List.nth inputs 3 in get_int_from_evmvalue tmp.pda_data in - let v2_amount1Out = + let uv2s_amount1Out = let tmp = List.nth inputs 4 in get_int_from_evmvalue tmp.pda_data in - let v2_recipient = + let uv2s_recipient = let tmp = List.nth inputs 5 in get_address_from_evmvalue tmp.pda_data in Some { - v2_contract; - v2_sender; - v2_recipient; - v2_amount0In; - v2_amount1In; - v2_amount0Out; - v2_amount1Out; - v2_token0; - v2_token1; + uv2s_contract; + uv2s_sender; + uv2s_recipient; + uv2s_amount0In; + uv2s_amount1In; + uv2s_amount0Out; + uv2s_amount1Out; + uv2s_token0; + uv2s_token1; }))) with _ -> None let decode_unknow_abi_v2 (log : debug_trace_result_log) (abi : event_decode_unknown_abi) = - let decode_topics = abi.edu_topics in + let decode_topics = abi.edua_topics in let bytes = List.filter_map (fun x -> match x with | `bytes b -> Some b | _ -> None) - abi.edu_data in + abi.edua_data in let types_inputs = [`int 256; `int 256; `int 256; `int 256] in let types_decode_topics = [`address; `address] in @@ -206,31 +207,32 @@ let decode_unknow_abi_v2 (log : debug_trace_result_log) | _ -> None in match (inputs, topics) with | Some inputs, Some topics -> - let v2_contract = log.dtrl_address in - let> v2_token0 = get_tokens_unkown_abi_token v2_contract "token0" in - let> v2_token1 = get_tokens_unkown_abi_token v2_contract "token1" in + let uv2s_contract = log.dtrl_address in + let> uv2s_token0 = get_tokens_unkown_abi_token uv2s_contract "token0" in + let> uv2s_token1 = get_tokens_unkown_abi_token uv2s_contract "token1" in - let v2_amount0In, v2_amount1In, v2_amount0Out, v2_amount1Out = inputs in - let v2_sender, v2_recipient = topics in + let uv2s_amount0In, uv2s_amount1In, uv2s_amount0Out, uv2s_amount1Out = + inputs in + let uv2s_sender, uv2s_recipient = topics in Lwt.return @@ Some { - v2_contract; - v2_sender; - v2_recipient; - v2_amount0In; - v2_amount1In; - v2_amount0Out; - v2_amount1Out; - v2_token0; - v2_token1; + uv2s_contract; + uv2s_sender; + uv2s_recipient; + uv2s_amount0In; + uv2s_amount1In; + uv2s_amount0Out; + uv2s_amount1Out; + uv2s_token0; + uv2s_token1; } | _ -> Lwt.return @@ None) let decode_know_abi_v3 (log : debug_trace_result_log) (abi : event_decode_abi) = try - let contract = abi.ea_contract_info in + let contract = abi.evda_contract_info in match contract.dci_type with | None -> None | Some contract_type -> ( @@ -242,7 +244,7 @@ let decode_know_abi_v3 (log : debug_trace_result_log) (abi : event_decode_abi) = | Some decode_signature -> ( match String.equal - (decode_signature.event_hash :> string) + (decode_signature.evdi_hash :> string) (v3_event_id :> string) with | false -> None @@ -250,56 +252,57 @@ let decode_know_abi_v3 (log : debug_trace_result_log) (abi : event_decode_abi) = let inputs = List.sort (fun arg1 arg2 -> compare arg1.pda_index arg2.pda_index) - abi.ea_inputs.eai_inputs in + abi.evda_inputs.evdai_inputs in - let v3_contract, v3_token0, v3_token1 = get_token_address log in - let v3_sender = + let uv3s_contract, uv3s_token0, uv3s_token1 = + get_token_address log in + let uv3s_sender = let tmp = List.nth inputs 0 in get_address_from_evmvalue tmp.pda_data in - let v3_recipient = + let uv3s_recipient = let tmp = List.nth inputs 1 in get_address_from_evmvalue tmp.pda_data in - let v3_amount0 = + let uv3s_amount0 = let tmp = List.nth inputs 2 in get_int_from_evmvalue tmp.pda_data in - let v3_amount1 = + let uv3s_amount1 = let tmp = List.nth inputs 3 in get_int_from_evmvalue tmp.pda_data in - let v3_sqrt_price = + let uv3s_sqrt_price = let tmp = List.nth inputs 4 in get_int_from_evmvalue tmp.pda_data in - let v3_liquidity = + let uv3s_liquidity = let tmp = List.nth inputs 5 in get_int_from_evmvalue tmp.pda_data in - let v3_tick = + let uv3s_tick = let tmp = List.nth inputs 6 in get_int_from_evmvalue tmp.pda_data in Some { - v3_contract; - v3_sender; - v3_recipient; - v3_amount0; - v3_amount1; - v3_sqrt_price; - v3_liquidity; - v3_tick; - v3_token0; - v3_token1; + uv3s_contract; + uv3s_sender; + uv3s_recipient; + uv3s_amount0; + uv3s_amount1; + uv3s_sqrt_price; + uv3s_liquidity; + uv3s_tick; + uv3s_token0; + uv3s_token1; }))) with _ -> None let decode_unknow_abi_v3 (log : debug_trace_result_log) (abi : event_decode_unknown_abi) = - let decode_topics = abi.edu_topics in + let decode_topics = abi.edua_topics in let bytes = List.filter_map (fun x -> match x with | `bytes b -> Some b | _ -> None) - abi.edu_data in + abi.edua_data in let types_inputs = [`int 256; `int 256; `uint 160; `uint 128; `int 24] in let types_decode_topics = [`address; `address] in @@ -339,26 +342,27 @@ let decode_unknow_abi_v3 (log : debug_trace_result_log) | _ -> None in match (inputs, topics) with | Some inputs, Some topics -> - let v3_contract = log.dtrl_address in - let> v3_token0 = get_tokens_unkown_abi_token v3_contract "token0" in - let> v3_token1 = get_tokens_unkown_abi_token v3_contract "token1" in + let uv3s_contract = log.dtrl_address in + let> uv3s_token0 = get_tokens_unkown_abi_token uv3s_contract "token0" in + let> uv3s_token1 = get_tokens_unkown_abi_token uv3s_contract "token1" in - let v3_amount0, v3_amount1, v3_sqrt_price, v3_liquidity, v3_tick = + let uv3s_amount0, uv3s_amount1, uv3s_sqrt_price, uv3s_liquidity, uv3s_tick + = inputs in - let v3_sender, v3_recipient = topics in + let uv3s_sender, uv3s_recipient = topics in Lwt.return @@ Some { - v3_contract; - v3_sender; - v3_recipient; - v3_amount0; - v3_amount1; - v3_sqrt_price; - v3_liquidity; - v3_tick; - v3_token0; - v3_token1; + uv3s_contract; + uv3s_sender; + uv3s_recipient; + uv3s_amount0; + uv3s_amount1; + uv3s_sqrt_price; + uv3s_liquidity; + uv3s_tick; + uv3s_token0; + uv3s_token1; } | _ -> Lwt.return @@ None) @@ -366,8 +370,8 @@ let get_v2_swap (log : debug_trace_result_log) = match log.dtrl_decode_signature with | None -> Lwt.return @@ None | Some s -> ( - let event_decode = s.event_decode in - match String.equal (s.event_hash :> string) (v2_event_id :> string) with + let event_decode = s.evdi_decode in + match String.equal (s.evdi_hash :> string) (v2_event_id :> string) with | false -> Lwt.return @@ None | true -> ( match event_decode with @@ -378,8 +382,8 @@ let get_v3_swap (log : debug_trace_result_log) = match log.dtrl_decode_signature with | None -> Lwt.return @@ None | Some s -> ( - let event_decode = s.event_decode in - match String.equal (s.event_hash :> string) (v3_event_id :> string) with + let event_decode = s.evdi_decode in + match String.equal (s.evdi_hash :> string) (v3_event_id :> string) with | false -> Lwt.return @@ None | true -> ( match event_decode with @@ -392,39 +396,39 @@ let get_v3_swaps tx_logs = Lwt_list.filter_map_s get_v3_swap tx_logs let general_swapV3 (swap : uni_v3_swap) = try - let sw_amountIn, sw_amountOut, sw_in_token, sw_out_token = - match Z.gt swap.v3_amount0 Z.zero with + let s_amountIn, s_amountOut, s_in_token, s_out_token = + match Z.gt swap.uv3s_amount0 Z.zero with | true -> - let b1 = not @@ Z.equal swap.v3_amount1 Z.zero in - let b2 = Z.gt Z.zero swap.v3_amount1 in + let b1 = not @@ Z.equal swap.uv3s_amount1 Z.zero in + let b2 = Z.gt Z.zero swap.uv3s_amount1 in if b1 && b2 then - ( swap.v3_amount0, - Z.neg swap.v3_amount1, - swap.v3_token0, - swap.v3_token1 ) + ( swap.uv3s_amount0, + Z.neg swap.uv3s_amount1, + swap.uv3s_token0, + swap.uv3s_token1 ) else failwith "assert failed general_swapV3" | false -> - let b1 = not @@ Z.equal swap.v3_amount0 Z.zero in - let b2 = Z.gt swap.v3_amount1 Z.zero in + let b1 = not @@ Z.equal swap.uv3s_amount0 Z.zero in + let b2 = Z.gt swap.uv3s_amount1 Z.zero in if b1 && b2 then - ( swap.v3_amount1, - Z.neg swap.v3_amount0, - swap.v3_token1, - swap.v3_token0 ) + ( swap.uv3s_amount1, + Z.neg swap.uv3s_amount0, + swap.uv3s_token1, + swap.uv3s_token0 ) else failwith "assert failed general_swapV3" in - let sw_sender, sw_recipient, sw_contract = - (swap.v3_sender, swap.v3_recipient, swap.v3_contract) in + let s_sender, s_recipient, s_contract = + (swap.uv3s_sender, swap.uv3s_recipient, swap.uv3s_contract) in Some { - sw_amountIn; - sw_amountOut; - sw_in_token; - sw_out_token; - sw_sender; - sw_recipient; - sw_contract; + s_amountIn; + s_amountOut; + s_in_token; + s_out_token; + s_sender; + s_recipient; + s_contract; } with _ -> None @@ -450,60 +454,59 @@ let find_swaps (transaction_trace : debug_trace_result) = Lwt.return (concat_list sv2 swapv2', concat_list sv3 swapv3') in get_pair_swaps_v2_v3 transaction_trace ([], []) -let gen_swap_transaction (swtx_sender : address) (swtx_transaction_hash : b) - (swtx_index : bint) (swtx_swap_list : swap list) = - let swtx_exchange_token_contract = +let gen_swap_transaction (st_sender : address) (st_transaction_hash : b) + (st_index : bint) (st_swap_list : swap list) = + let st_exchange_token_contract = list_uniq_token_swaped @@ List.fold_left (fun acc (arg : swap) -> - let tsw_in_token, tsw_out_token, tsw_contract = - (arg.sw_in_token, arg.sw_out_token, arg.sw_contract) in - { tsw_in_token; tsw_out_token; tsw_contract } :: acc) - [] swtx_swap_list in - let swtx_summed_swap_list = + let ts_in_token, ts_out_token, ts_contract = + (arg.s_in_token, arg.s_out_token, arg.s_contract) in + { ts_in_token; ts_out_token; ts_contract } :: acc) + [] st_swap_list in + let st_summed_swap_list = List.fold_left (fun acc (arg : token_swaped) -> let swaps = List.find_all (fun (arg' : swap) -> - arg'.sw_in_token = arg.tsw_in_token - && arg'.sw_out_token = arg.tsw_out_token - && arg'.sw_contract = arg.tsw_contract) - swtx_swap_list in + arg'.s_in_token = arg.ts_in_token + && arg'.s_out_token = arg.ts_out_token + && arg'.s_contract = arg.ts_contract) + st_swap_list in match swaps with | [] -> acc | swaps -> - let sw_amountIn, sw_amountOut = + let s_amountIn, s_amountOut = List.fold_left (fun (amountIn, amountOut) arg -> - ( Z.add amountIn arg.sw_amountIn, - Z.add amountOut arg.sw_amountOut )) + (Z.add amountIn arg.s_amountIn, Z.add amountOut arg.s_amountOut)) (Z.zero, Z.zero) swaps in let swap = List.hd swaps in - let sw_in_token, sw_out_token, sw_sender, sw_recipient, sw_contract = - ( swap.sw_in_token, - swap.sw_out_token, - swap.sw_sender, - swap.sw_recipient, - swap.sw_contract ) in + let s_in_token, s_out_token, s_sender, s_recipient, s_contract = + ( swap.s_in_token, + swap.s_out_token, + swap.s_sender, + swap.s_recipient, + swap.s_contract ) in { - sw_amountIn; - sw_amountOut; - sw_in_token; - sw_out_token; - sw_sender; - sw_recipient; - sw_contract; + s_amountIn; + s_amountOut; + s_in_token; + s_out_token; + s_sender; + s_recipient; + s_contract; } :: acc) - [] swtx_exchange_token_contract in + [] st_exchange_token_contract in { - swtx_transaction_hash; - swtx_index; - swtx_sender; - swtx_swap_list; - swtx_summed_swap_list; - swtx_exchange_token_contract; + st_transaction_hash; + st_index; + st_sender; + st_swap_list; + st_summed_swap_list; + st_exchange_token_contract; } let detect_fr_br (swap_transaction_list : swap_transaction list) = @@ -511,14 +514,14 @@ let detect_fr_br (swap_transaction_list : swap_transaction list) = match swap_transaction_list with | [] | _ :: [] -> acc_fr_br | swap :: tl_swaps -> ( - let sender = swap.swtx_sender in - let exchange_token_contract = swap.swtx_exchange_token_contract in + let sender = swap.st_sender in + let exchange_token_contract = swap.st_exchange_token_contract in let possible_br = - List.find_all (fun arg -> arg.swtx_sender = sender) tl_swaps in + List.find_all (fun arg -> arg.st_sender = sender) tl_swaps in let br : (swap_transaction * token_swaped list) list = List.fold_left (fun acc arg -> - let br_exchange_token_contract = arg.swtx_exchange_token_contract in + let br_exchange_token_contract = arg.st_exchange_token_contract in let br_token_swap = (* I only keep the token_swaped from the Front Run *) List.fold_left @@ -541,9 +544,9 @@ let detect_fr_br (swap_transaction_list : swap_transaction list) = let swap_transaction_list = List.sort (fun swap1 swap2 -> - if swap1.swtx_index > swap2.swtx_index then + if swap1.st_index > swap2.st_index then 1 - else if swap1.swtx_index = swap2.swtx_index then + else if swap1.st_index = swap2.st_index then 0 else -1) @@ -554,10 +557,10 @@ let find_victim (fr_br : swap_transaction * (swap_transaction * token_swaped list)) (swap_list : swap_transaction list) = let fr, (br, token_swaped_list) = fr_br in - let deb_index, fin_index = (fr.swtx_index, br.swtx_index) in + let deb_index, fin_index = (fr.st_index, br.st_index) in let in_index_swap_list = List.filter - (fun arg -> arg.swtx_index > deb_index && arg.swtx_index < fin_index) + (fun arg -> arg.st_index > deb_index && arg.st_index < fin_index) swap_list in match in_index_swap_list with | [] -> [] @@ -572,7 +575,7 @@ let find_victim List.find_all (fun in_index_swap -> List.exists (eq_token_swaped arg) - in_index_swap.swtx_exchange_token_contract) + in_index_swap.st_exchange_token_contract) in_index_swap_list in match victims with | [] -> acc @@ -608,42 +611,42 @@ let general_find_victim | sandwichs -> concat_list sandwichs acc) [] fr_br_list -let analyse_sandwich (sda_sandwich : sandwich) = +let analyse_sandwich (sa_sandwich : sandwich) = let get_ratio_fr_victims (token_sandwich : token_swaped) - (trsw_swap_transaction : swap_transaction) = + (trs_swap_transaction : swap_transaction) = let swaps = List.filter (fun (swap : swap) -> - swap.sw_in_token = token_sandwich.tsw_in_token - && swap.sw_out_token = token_sandwich.tsw_out_token - && swap.sw_contract = token_sandwich.tsw_contract) - trsw_swap_transaction.swtx_swap_list in - let trsw_ratio = - List.map (fun swap -> (swap.sw_amountIn, swap.sw_amountOut)) swaps in - { trsw_swap_transaction; trsw_ratio } in + swap.s_in_token = token_sandwich.ts_in_token + && swap.s_out_token = token_sandwich.ts_out_token + && swap.s_contract = token_sandwich.ts_contract) + trs_swap_transaction.st_swap_list in + let trs_ratio = + List.map (fun swap -> (swap.s_amountIn, swap.s_amountOut)) swaps in + { trs_swap_transaction; trs_ratio } in let get_ratio_br (token_sandwich : token_swaped) - (trsw_swap_transaction : swap_transaction) = + (trs_swap_transaction : swap_transaction) = let swaps = List.filter (fun (swap : swap) -> - swap.sw_in_token = token_sandwich.tsw_out_token - && swap.sw_out_token = token_sandwich.tsw_in_token - && swap.sw_contract = token_sandwich.tsw_contract) - trsw_swap_transaction.swtx_swap_list in - let trsw_ratio = - List.map (fun swap -> (swap.sw_amountIn, swap.sw_amountOut)) swaps in - { trsw_swap_transaction; trsw_ratio } in - let token = sda_sandwich.sd_token_sandwich in - let sda_token_ratio_swap_fr = - get_ratio_fr_victims token sda_sandwich.sd_front_run in - let sda_token_ratio_swap_br = get_ratio_br token sda_sandwich.sd_back_run in - let sda_token_ratio_swap_victim = - List.map (get_ratio_fr_victims token) sda_sandwich.sd_victims in + swap.s_in_token = token_sandwich.ts_out_token + && swap.s_out_token = token_sandwich.ts_in_token + && swap.s_contract = token_sandwich.ts_contract) + trs_swap_transaction.st_swap_list in + let trs_ratio = + List.map (fun swap -> (swap.s_amountIn, swap.s_amountOut)) swaps in + { trs_ratio; trs_swap_transaction } in + let token = sa_sandwich.sd_token_sandwich in + let sa_token_ratio_swap_fr = + get_ratio_fr_victims token sa_sandwich.sd_front_run in + let sa_token_ratio_swap_br = get_ratio_br token sa_sandwich.sd_back_run in + let sa_token_ratio_swap_victim = + List.map (get_ratio_fr_victims token) sa_sandwich.sd_victims in { - sda_sandwich; - sda_token_ratio_swap_fr; - sda_token_ratio_swap_br; - sda_token_ratio_swap_victim; + sa_sandwich; + sa_token_ratio_swap_fr; + sa_token_ratio_swap_br; + sa_token_ratio_swap_victim; } let analyse_sandwich_block (sandwich_list : sandwich list) = -- GitLab From 2a4a66e7a9ff9e948889e19c05d00279d2584311 Mon Sep 17 00:00:00 2001 From: "rayane.lattari" Date: Wed, 5 Mar 2025 09:50:56 +0100 Subject: [PATCH 4/7] [Ethereum-data-analysis] Delete st_summed_swap_list from swap_transaction --- src/common/types.ml | 1 - .../eth_analysis_sandwich.ml | 37 ------------------- 2 files changed, 38 deletions(-) diff --git a/src/common/types.ml b/src/common/types.ml index 57f89333..c4963d21 100644 --- a/src/common/types.ml +++ b/src/common/types.ml @@ -390,7 +390,6 @@ module Ethereum_analysis = struct st_index : bint; st_sender : address; st_swap_list : swap list; - st_summed_swap_list : swap list; st_exchange_token_contract : token_swaped list; } [@@deriving encoding { remove_prefix = "st_" }] diff --git a/src/ethereum-analysis/eth_analysis_sandwich.ml b/src/ethereum-analysis/eth_analysis_sandwich.ml index 529c8f67..3373fcf9 100644 --- a/src/ethereum-analysis/eth_analysis_sandwich.ml +++ b/src/ethereum-analysis/eth_analysis_sandwich.ml @@ -464,48 +464,11 @@ let gen_swap_transaction (st_sender : address) (st_transaction_hash : b) (arg.s_in_token, arg.s_out_token, arg.s_contract) in { ts_in_token; ts_out_token; ts_contract } :: acc) [] st_swap_list in - let st_summed_swap_list = - List.fold_left - (fun acc (arg : token_swaped) -> - let swaps = - List.find_all - (fun (arg' : swap) -> - arg'.s_in_token = arg.ts_in_token - && arg'.s_out_token = arg.ts_out_token - && arg'.s_contract = arg.ts_contract) - st_swap_list in - match swaps with - | [] -> acc - | swaps -> - let s_amountIn, s_amountOut = - List.fold_left - (fun (amountIn, amountOut) arg -> - (Z.add amountIn arg.s_amountIn, Z.add amountOut arg.s_amountOut)) - (Z.zero, Z.zero) swaps in - let swap = List.hd swaps in - let s_in_token, s_out_token, s_sender, s_recipient, s_contract = - ( swap.s_in_token, - swap.s_out_token, - swap.s_sender, - swap.s_recipient, - swap.s_contract ) in - { - s_amountIn; - s_amountOut; - s_in_token; - s_out_token; - s_sender; - s_recipient; - s_contract; - } - :: acc) - [] st_exchange_token_contract in { st_transaction_hash; st_index; st_sender; st_swap_list; - st_summed_swap_list; st_exchange_token_contract; } -- GitLab From 097f50d62b979ab23eb28da9a1f53d1cf2a1beda Mon Sep 17 00:00:00 2001 From: "rayane.lattari" Date: Fri, 28 Feb 2025 11:15:32 +0100 Subject: [PATCH 5/7] [Ethereum-data-analysis] create sandwich algorithm --- src/common/types.ml | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/common/types.ml b/src/common/types.ml index c4963d21..42e2e45b 100644 --- a/src/common/types.ml +++ b/src/common/types.ml @@ -1,5 +1,7 @@ open Eth +open Eth + module Ethereum_indexer = struct type contract_type = | ERC20 [@key "Erc20"] @@ -447,6 +449,88 @@ module Ethereum_analysis = struct [@@deriving encoding { remove_prefix = "sa_" }] end +module Ethereum_analysis = struct + type swap = { + sw_amountIn : bz; + sw_amountOut : bz; + sw_in_token : address; + sw_out_token : address; + sw_sender : address; + sw_recipient : address; + sw_contract : address; + } + [@@deriving encoding { remove_prefix = "sw_" }] + + type token_swaped = { + tsw_in_token : address; + tsw_out_token : address; + tsw_contract : address; + } + [@@deriving encoding { remove_prefix = "tsw_" }] + + type swap_transaction = { + swtx_transaction_hash : b; + swtx_index : bint; + swtx_sender : address; + swtx_swap_list : swap list; + swtx_summed_swap_list : swap list; + swtx_exchange_token_contract : token_swaped list; + } + [@@deriving encoding { remove_prefix = "swtx_" }] + + type uni_v2_swap = { + v2_contract : address; + v2_sender : address; + v2_recipient : address; + v2_amount0In : bz; + v2_amount1In : bz; + v2_amount0Out : bz; + v2_amount1Out : bz; + v2_token0 : address; + v2_token1 : address; + } + [@@deriving encoding { remove_prefix = "v2_" }] + + type uni_v3_swap = { + v3_contract : address; + v3_sender : Eth.address; + v3_recipient : Eth.address; + v3_amount0 : bz; + v3_amount1 : bz; + v3_sqrt_price : bz; + v3_liquidity : bz; + v3_tick : bz; + v3_token0 : address; + v3_token1 : address; + } + [@@deriving encoding { remove_prefix = "v3_" }] + + (* A sandwich is defined for a unique token_swap. We can have multiple + sandwich with the same victim, front run and back run, but with different + token_swap.*) + type sandwich = { + sd_front_run : swap_transaction; + sd_back_run : swap_transaction; + sd_victims : swap_transaction list; + sd_token_sandwich : token_swaped; + } + [@@deriving encoding { remove_prefix = "sd_" }] + + type token_ratio_swap = { + trsw_swap_transaction : swap_transaction; + trsw_ratio : (bz * bz) list; + } + [@@deriving encoding { remove_prefix = "trsw_" }] + + type sandwich_analysed = { + sda_sandwich : sandwich; + sda_token_ratio_swap_fr : token_ratio_swap; + sda_token_ratio_swap_br : token_ratio_swap; + sda_token_ratio_swap_victim : token_ratio_swap list; + } + [@@deriving encoding { remove_prefix = "sda_" }] +end + type verbose = | Nothing | Low -- GitLab From b6ed9de5b15fac4ab8f424a65a5340c67e5f3f20 Mon Sep 17 00:00:00 2001 From: "rayane.lattari" Date: Wed, 5 Mar 2025 11:20:40 +0100 Subject: [PATCH 6/7] [Ethereum-data-analysis] delete duplicate definition --- src/common/types.ml | 84 --------------------------------------------- 1 file changed, 84 deletions(-) diff --git a/src/common/types.ml b/src/common/types.ml index 42e2e45b..c4963d21 100644 --- a/src/common/types.ml +++ b/src/common/types.ml @@ -1,7 +1,5 @@ open Eth -open Eth - module Ethereum_indexer = struct type contract_type = | ERC20 [@key "Erc20"] @@ -449,88 +447,6 @@ module Ethereum_analysis = struct [@@deriving encoding { remove_prefix = "sa_" }] end -module Ethereum_analysis = struct - type swap = { - sw_amountIn : bz; - sw_amountOut : bz; - sw_in_token : address; - sw_out_token : address; - sw_sender : address; - sw_recipient : address; - sw_contract : address; - } - [@@deriving encoding { remove_prefix = "sw_" }] - - type token_swaped = { - tsw_in_token : address; - tsw_out_token : address; - tsw_contract : address; - } - [@@deriving encoding { remove_prefix = "tsw_" }] - - type swap_transaction = { - swtx_transaction_hash : b; - swtx_index : bint; - swtx_sender : address; - swtx_swap_list : swap list; - swtx_summed_swap_list : swap list; - swtx_exchange_token_contract : token_swaped list; - } - [@@deriving encoding { remove_prefix = "swtx_" }] - - type uni_v2_swap = { - v2_contract : address; - v2_sender : address; - v2_recipient : address; - v2_amount0In : bz; - v2_amount1In : bz; - v2_amount0Out : bz; - v2_amount1Out : bz; - v2_token0 : address; - v2_token1 : address; - } - [@@deriving encoding { remove_prefix = "v2_" }] - - type uni_v3_swap = { - v3_contract : address; - v3_sender : Eth.address; - v3_recipient : Eth.address; - v3_amount0 : bz; - v3_amount1 : bz; - v3_sqrt_price : bz; - v3_liquidity : bz; - v3_tick : bz; - v3_token0 : address; - v3_token1 : address; - } - [@@deriving encoding { remove_prefix = "v3_" }] - - (* A sandwich is defined for a unique token_swap. We can have multiple - sandwich with the same victim, front run and back run, but with different - token_swap.*) - type sandwich = { - sd_front_run : swap_transaction; - sd_back_run : swap_transaction; - sd_victims : swap_transaction list; - sd_token_sandwich : token_swaped; - } - [@@deriving encoding { remove_prefix = "sd_" }] - - type token_ratio_swap = { - trsw_swap_transaction : swap_transaction; - trsw_ratio : (bz * bz) list; - } - [@@deriving encoding { remove_prefix = "trsw_" }] - - type sandwich_analysed = { - sda_sandwich : sandwich; - sda_token_ratio_swap_fr : token_ratio_swap; - sda_token_ratio_swap_br : token_ratio_swap; - sda_token_ratio_swap_victim : token_ratio_swap list; - } - [@@deriving encoding { remove_prefix = "sda_" }] -end - type verbose = | Nothing | Low -- GitLab From 2bd9b4b109543f77aa33d43062e01af25fc2708a Mon Sep 17 00:00:00 2001 From: "rayane.lattari" Date: Wed, 5 Mar 2025 12:55:48 +0100 Subject: [PATCH 7/7] [Ethereum-data-analysis] remove decode unkown abi --- .../eth_analysis_sandwich.ml | 166 +----------------- .../eth_analysis_sandwich.mli | 7 + 2 files changed, 11 insertions(+), 162 deletions(-) create mode 100644 src/ethereum-analysis/eth_analysis_sandwich.mli diff --git a/src/ethereum-analysis/eth_analysis_sandwich.ml b/src/ethereum-analysis/eth_analysis_sandwich.ml index 3373fcf9..94358e11 100644 --- a/src/ethereum-analysis/eth_analysis_sandwich.ml +++ b/src/ethereum-analysis/eth_analysis_sandwich.ml @@ -3,7 +3,6 @@ open Eth open Common.Types.Ethereum_analysis open Common.Types.Ethereum_indexer open Common.Tools -open Eth_indexer let eq_inverse_token_swaped ts1 ts2 = ts1.ts_out_token = ts2.ts_in_token @@ -37,26 +36,12 @@ let get_token_address (log : debug_trace_result_log) = (contract_info.dci_address, contract1.dci_address, contract2.dci_address) )) -let get_tokens_unkown_abi_token adr token = - Node.call_contract ~typ:`address adr token [] [] >>= function - | Error e -> Lwt.fail_with (EzEncoding.construct error_enc e) - | Ok r -> ( - match r with - | `address address -> Lwt.return address - | _ -> Lwt.fail_with "not address") - let v2_event_id = b "d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822" let v3_event_id = b "c42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67" -let v2_event_description = - "Swap(address,uint256,uint256,uint256,uint256,address)" - -let v3_event_description = - "Swap(address,address,int256,int256,uint160,uint128,int24)" - let get_address_from_evmvalue (evm_value : Eth.evm_value) = match evm_value with | `address a -> a @@ -103,7 +88,7 @@ let general_swapV2 (swap : uni_v2_swap) = s_contract; } -let decode_know_abi_v2 (log : debug_trace_result_log) (abi : event_decode_abi) = +let decode_abi_v2 (log : debug_trace_result_log) (abi : event_decode_abi) = try let contract = abi.evda_contract_info in match contract.dci_type with @@ -160,77 +145,7 @@ let decode_know_abi_v2 (log : debug_trace_result_log) (abi : event_decode_abi) = }))) with _ -> None -let decode_unknow_abi_v2 (log : debug_trace_result_log) - (abi : event_decode_unknown_abi) = - let decode_topics = abi.edua_topics in - let bytes = - List.filter_map - (fun x -> - match x with - | `bytes b -> Some b - | _ -> None) - abi.edua_data in - let types_inputs = [`int 256; `int 256; `int 256; `int 256] in - let types_decode_topics = [`address; `address] in - - let length_types_decode_topics = List.length types_decode_topics in - let length_decode_topics = List.length decode_topics in - - let length_bytes = List.length bytes in - let length_types_inputs = List.length types_inputs in - - let b1 = length_types_inputs = length_bytes in - let b2 = length_types_decode_topics = length_decode_topics in - - match b1 && b2 with - | false -> Lwt.return @@ None - | true -> ( - let decode_inputs = - List.fold_left2 - (fun acc t b -> try Eth.Evm.decode t b :: acc with _ -> acc) - [] types_inputs bytes in - let inputs = - match decode_inputs with - | [v2_amount0In; v2_amount1In; v2_amount0Out; v2_amount1Out] -> - Some - ( get_int_from_evmvalue v2_amount0In, - get_int_from_evmvalue v2_amount1In, - get_int_from_evmvalue v2_amount0Out, - get_int_from_evmvalue v2_amount1Out ) - | _ -> None in - let topics = - match decode_topics with - | [v2_sender; v2_recipient] -> - Some - ( get_address_from_evmvalue v2_sender, - get_address_from_evmvalue v2_recipient ) - | _ -> None in - match (inputs, topics) with - | Some inputs, Some topics -> - let uv2s_contract = log.dtrl_address in - let> uv2s_token0 = get_tokens_unkown_abi_token uv2s_contract "token0" in - let> uv2s_token1 = get_tokens_unkown_abi_token uv2s_contract "token1" in - - let uv2s_amount0In, uv2s_amount1In, uv2s_amount0Out, uv2s_amount1Out = - inputs in - let uv2s_sender, uv2s_recipient = topics in - - Lwt.return - @@ Some - { - uv2s_contract; - uv2s_sender; - uv2s_recipient; - uv2s_amount0In; - uv2s_amount1In; - uv2s_amount0Out; - uv2s_amount1Out; - uv2s_token0; - uv2s_token1; - } - | _ -> Lwt.return @@ None) - -let decode_know_abi_v3 (log : debug_trace_result_log) (abi : event_decode_abi) = +let decode_abi_v3 (log : debug_trace_result_log) (abi : event_decode_abi) = try let contract = abi.evda_contract_info in match contract.dci_type with @@ -293,79 +208,6 @@ let decode_know_abi_v3 (log : debug_trace_result_log) (abi : event_decode_abi) = }))) with _ -> None -let decode_unknow_abi_v3 (log : debug_trace_result_log) - (abi : event_decode_unknown_abi) = - let decode_topics = abi.edua_topics in - let bytes = - List.filter_map - (fun x -> - match x with - | `bytes b -> Some b - | _ -> None) - abi.edua_data in - let types_inputs = [`int 256; `int 256; `uint 160; `uint 128; `int 24] in - let types_decode_topics = [`address; `address] in - - let length_types_decode_topics = List.length types_decode_topics in - let length_decode_topics = List.length decode_topics in - - let length_bytes = List.length bytes in - let length_types_inputs = List.length types_inputs in - - let b1 = length_types_inputs = length_bytes in - let b2 = length_types_decode_topics = length_decode_topics in - - match b1 && b2 with - | false -> Lwt.return @@ None - | true -> ( - let decode_inputs = - List.fold_left2 - (fun acc t b -> try Eth.Evm.decode t b :: acc with _ -> acc) - [] types_inputs bytes in - let inputs = - match decode_inputs with - | [v3_amount0; v3_amount1; v3_sqrt_price; v3_liquidity; v3_tick] -> - Some - ( get_int_from_evmvalue v3_amount0, - get_int_from_evmvalue v3_amount1, - get_int_from_evmvalue v3_sqrt_price, - get_int_from_evmvalue v3_liquidity, - get_int_from_evmvalue v3_tick ) - | _ -> None in - - let topics = - match decode_topics with - | [v3_sender; v3_recipient] -> - Some - ( get_address_from_evmvalue v3_sender, - get_address_from_evmvalue v3_recipient ) - | _ -> None in - match (inputs, topics) with - | Some inputs, Some topics -> - let uv3s_contract = log.dtrl_address in - let> uv3s_token0 = get_tokens_unkown_abi_token uv3s_contract "token0" in - let> uv3s_token1 = get_tokens_unkown_abi_token uv3s_contract "token1" in - - let uv3s_amount0, uv3s_amount1, uv3s_sqrt_price, uv3s_liquidity, uv3s_tick - = - inputs in - let uv3s_sender, uv3s_recipient = topics in - Lwt.return - @@ Some - { - uv3s_contract; - uv3s_sender; - uv3s_recipient; - uv3s_amount0; - uv3s_amount1; - uv3s_sqrt_price; - uv3s_liquidity; - uv3s_tick; - uv3s_token0; - uv3s_token1; - } - | _ -> Lwt.return @@ None) - let get_v2_swap (log : debug_trace_result_log) = match log.dtrl_decode_signature with | None -> Lwt.return @@ None @@ -375,7 +217,7 @@ let get_v2_swap (log : debug_trace_result_log) = | false -> Lwt.return @@ None | true -> ( match event_decode with - | Event_abi abi -> Lwt.return @@ decode_know_abi_v2 log abi + | Event_abi abi -> Lwt.return @@ decode_abi_v2 log abi | Event_unknown_abi _ -> Lwt.return_none)) let get_v3_swap (log : debug_trace_result_log) = @@ -387,7 +229,7 @@ let get_v3_swap (log : debug_trace_result_log) = | false -> Lwt.return @@ None | true -> ( match event_decode with - | Event_abi abi -> Lwt.return @@ decode_know_abi_v3 log abi + | Event_abi abi -> Lwt.return @@ decode_abi_v3 log abi | Event_unknown_abi _ -> Lwt.return_none)) let get_v2_swaps tx_logs = Lwt_list.filter_map_s get_v2_swap tx_logs diff --git a/src/ethereum-analysis/eth_analysis_sandwich.mli b/src/ethereum-analysis/eth_analysis_sandwich.mli new file mode 100644 index 00000000..0b66cb0a --- /dev/null +++ b/src/ethereum-analysis/eth_analysis_sandwich.mli @@ -0,0 +1,7 @@ +val analyse_sandwich_block : + Common.Types.Ethereum_analysis.sandwich list -> + Common.Types.Ethereum_analysis.sandwich_analysed list + +val detect_sandwich : + Common.Types.Ethereum_indexer.debug_trace list -> + Common.Types.Ethereum_analysis.sandwich list Lwt.t -- GitLab