From 48402655ebb0b0e073220cfaff699635c85da493 Mon Sep 17 00:00:00 2001 From: zizou <111426680+flopell@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:32:30 +0100 Subject: [PATCH] feat(example): add Curve to `explorer` example --- examples/explorer/data_feed/tycho.rs | 82 +++++++++- .../vm/assets/CurveSwapAdapter.evm.runtime | Bin 0 -> 9842 bytes src/evm/protocol/vm/utils.rs | 142 ++++++++++++++++++ 3 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 src/evm/protocol/vm/assets/CurveSwapAdapter.evm.runtime diff --git a/examples/explorer/data_feed/tycho.rs b/examples/explorer/data_feed/tycho.rs index e2ed3645..dc97fbdb 100644 --- a/examples/explorer/data_feed/tycho.rs +++ b/examples/explorer/data_feed/tycho.rs @@ -6,6 +6,7 @@ use std::{ use alloy_primitives::{Address, B256}; use chrono::Utc; +use num_bigint::BigInt; use tokio::sync::mpsc::Sender; use tracing::{debug, info, warn}; @@ -22,8 +23,9 @@ use tycho_simulation::{ simulation_db::BlockHeader, tycho_db::PreCachedDB, update_engine, SHARED_TYCHO_DB, }, protocol::{ - uniswap_v2::state::UniswapV2State, uniswap_v3::state::UniswapV3State, - vm::state::EVMPoolState, + uniswap_v2::state::UniswapV2State, + uniswap_v3::state::UniswapV3State, + vm::{state::EVMPoolState, utils::json_deserialize_be_bigint_list}, }, tycho_models::{AccountUpdate, ResponseAccount}, }, @@ -106,6 +108,58 @@ fn balancer_pool_filter(component: &ComponentWithState) -> bool { true } +fn curve_pool_filter(component: &ComponentWithState) -> bool { + if let Some(asset_types) = component + .component + .static_attributes + .get("asset_types") + { + if json_deserialize_be_bigint_list(asset_types) + .unwrap() + .iter() + .any(|t| t != &BigInt::ZERO) + { + info!( + "Filtering out Curve pool {} because it has unsupported token type", + component.component.id + ); + return false; + } + } + + if let Some(asset_type) = component + .component + .static_attributes + .get("asset_type") + { + let types_str = str::from_utf8(asset_type).expect("Invalid UTF-8 data"); + if types_str != "0x00" { + info!( + "Filtering out Curve pool {} because it has unsupported token type", + component.component.id + ); + return false; + } + } + + if let Some(stateless_addrs) = component + .state + .attributes + .get("stateless_contract_addr_0") + { + let impl_str = str::from_utf8(stateless_addrs).expect("Invalid UTF-8 data"); + // Uses oracles + if impl_str == "0x847ee1227a9900b73aeeb3a47fac92c52fd54ed9" { + info!( + "Filtering out Curve pool {} because it has proxy implementation {}", + component.component.id, impl_str + ); + return false; + } + } + true +} + // TODO: Make extractors configurable pub async fn process_messages( tycho_url: String, @@ -118,6 +172,7 @@ pub async fn process_messages( .exchange("uniswap_v2", ComponentFilter::with_tvl_range(tvl_threshold, tvl_threshold)) .exchange("uniswap_v3", ComponentFilter::with_tvl_range(tvl_threshold, tvl_threshold)) // .exchange("vm:balancer", ComponentFilter::with_tvl_range(tvl_threshold, tvl_threshold)) + // .exchange("vm:curve", ComponentFilter::with_tvl_range(tvl_threshold, tvl_threshold)) .auth_key(auth_key.clone()) .build() .await @@ -242,7 +297,8 @@ pub async fn process_messages( } // Skip balancer pool if it doesn't pass the filter - if !skip_pool && !balancer_pool_filter(&snapshot) { + if !skip_pool && (!balancer_pool_filter(&snapshot) || !curve_pool_filter(&snapshot)) + { skip_pool = true; } @@ -300,7 +356,25 @@ pub async fn process_messages( } } } - _ => panic!("VM snapshot not supported!"), + "vm:curve" => { + match EVMPoolState::try_from_with_block( + snapshot.clone(), + header.clone(), + all_tokens.clone(), + ) + .await + { + Ok(state) => Box::new(state), + Err(e) => { + warn!( + "Failed parsing Curve snapshot for pool {:x?}: {}", + id, e + ); + continue; + } + } + } + _ => panic!("VM snapshot not supported for {}!", protocol.as_str()), }; new_components.insert(id, state); } diff --git a/src/evm/protocol/vm/assets/CurveSwapAdapter.evm.runtime b/src/evm/protocol/vm/assets/CurveSwapAdapter.evm.runtime new file mode 100644 index 0000000000000000000000000000000000000000..ebb4cf818f640711624a3324b371a51b9cec9846 GIT binary patch literal 9842 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 Result<[u8; 32], SimulationError> { Ok(array) } +/// Decodes a JSON-encoded list of hexadecimal strings into a `Vec>`. +/// +/// This function parses a JSON array where each element is a string representing a hexadecimal +/// value. It converts each hex string into a vector of bytes (`Vec`), and aggregates them into +/// a `Vec>`. +/// +/// # Arguments +/// +/// * `input` - A byte slice (`&[u8]`) containing JSON-encoded data. The JSON must be a valid array +/// of hex strings. +/// +/// # Returns +/// +/// * `Ok(Vec>)` - On success, returns a vector of byte vectors. +/// * `Err(SimulationError)` - Returns an error if: +/// - The input is not valid JSON. +/// - The JSON is not an array. +/// - Any array element is not a string. +/// - Any string is not a valid hexadecimal string. +/// +/// # Example +/// ``` +/// use json_deserialize_address_list; +/// +/// let json_input = br#"["0x1234", "0xc0ffee"]"#; +/// match json_deserialize_address_list(json_input) { +/// Ok(result) => println!("Decoded: {:?}", result), +/// Err(e) => eprintln!("Error: {}", e), +/// } +/// ``` +pub fn json_deserialize_address_list(input: &[u8]) -> Result>, SimulationError> { + let json_value: Value = serde_json::from_slice(input) + .map_err(|_| SimulationError::FatalError(format!("Invalid JSON: {:?}", input)))?; + + if let Value::Array(hex_strings) = json_value { + let mut result = Vec::new(); + + for val in hex_strings { + if let Value::String(hexstring) = val { + let bytes = hex::decode(hexstring.trim_start_matches("0x")).map_err(|_| { + SimulationError::FatalError(format!("Invalid hex string: {}", hexstring)) + })?; + result.push(bytes); + } else { + return Err(SimulationError::FatalError("Array contains a non-string value".into())); + } + } + + Ok(result) + } else { + Err(SimulationError::FatalError("Input is not a JSON array".into())) + } +} + +/// Decodes a JSON-encoded list of hexadecimal strings into a `Vec`. +/// +/// This function parses a JSON array where each element is a string representing a hexadecimal +/// value. It converts each hex string into a `BigInt` using big-endian byte interpretation, and +/// aggregates them into a `Vec`. +/// +/// # Arguments +/// +/// * `input` - A byte slice (`&[u8]`) containing JSON-encoded data. The JSON must be a valid array +/// of hex strings. +/// +/// # Returns +/// +/// * `Ok(Vec)` - On success, returns a vector of `BigInt` values. +/// * `Err(SimulationError)` - Returns an error if: +/// - The input is not valid JSON. +/// - The JSON is not an array. +/// - Any array element is not a string. +/// - Any string is not a valid hexadecimal string. +/// +/// # Example +/// ``` +/// use json_deserialize_be_bigint_list; +/// use num_bigint::BigInt; +/// let json_input = br#"["0x1234", "0xdeadbeef"]"#; +/// match json_deserialize_bigint_list(json_input) { +/// Ok(result) => println!("Decoded BigInts: {:?}", result), +/// Err(e) => eprintln!("Error: {}", e), +/// } +/// ``` +pub fn json_deserialize_be_bigint_list(input: &[u8]) -> Result, SimulationError> { + let json_value: Value = serde_json::from_slice(input) + .map_err(|_| SimulationError::FatalError(format!("Invalid JSON: {:?}", input)))?; + + if let Value::Array(hex_strings) = json_value { + let mut result = Vec::new(); + + for val in hex_strings { + if let Value::String(hexstring) = val { + let bytes = hex::decode(hexstring.trim_start_matches("0x")).map_err(|_| { + SimulationError::FatalError(format!("Invalid hex string: {}", hexstring)) + })?; + let bigint = BigInt::from_signed_bytes_be(&bytes); + result.push(bigint); + } else { + return Err(SimulationError::FatalError("Array contains a non-string value".into())); + } + } + + Ok(result) + } else { + Err(SimulationError::FatalError("Input is not a JSON array".into())) + } +} + #[cfg(test)] mod tests { use super::*; @@ -568,4 +679,35 @@ mod tests { panic!("Expected EncodingError"); } } + + #[test] + fn test_json_deserialize_address_list() { + let json_input = r#"["0x1234","0xabcd"]"#.as_bytes(); + let result = json_deserialize_address_list(json_input).unwrap(); + assert_eq!(result, vec![vec![0x12, 0x34], vec![0xab, 0xcd]]); + } + + #[test] + fn test_json_deserialize_bigint_list() { + let json_input = r#"["0x0b1a2bc2ec500000","0x02c68af0bb140000"]"#.as_bytes(); + let result = json_deserialize_be_bigint_list(json_input).unwrap(); + assert_eq!( + result, + vec![BigInt::from(800000000000000000u64), BigInt::from(200000000000000000u64)] + ); + } + + #[test] + fn test_invalid_deserialize_address_list() { + let json_input = r#"["invalid_hex"]"#.as_bytes(); + let result = json_deserialize_address_list(json_input); + assert!(result.is_err()); + } + + #[test] + fn test_invalid_deserialize_bigint_list() { + let json_input = r#"["invalid_hex"]"#.as_bytes(); + let result = json_deserialize_be_bigint_list(json_input); + assert!(result.is_err()); + } }