Skip to content

Commit

Permalink
feat(example): add Curve to explorer example
Browse files Browse the repository at this point in the history
  • Loading branch information
zizou0x committed Dec 10, 2024
1 parent 5df95b9 commit 4840265
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 4 deletions.
82 changes: 78 additions & 4 deletions examples/explorer/data_feed/tycho.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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},
},
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
Expand Down
Binary file not shown.
142 changes: 142 additions & 0 deletions src/evm/protocol/vm/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use alloy_primitives::Address;
use num_bigint::BigInt;
use serde_json::Value;
use std::{
collections::HashMap,
env,
Expand Down Expand Up @@ -366,6 +368,115 @@ pub fn string_to_bytes32(pool_id: &str) -> Result<[u8; 32], SimulationError> {
Ok(array)
}

/// Decodes a JSON-encoded list of hexadecimal strings into a `Vec<Vec<u8>>`.
///
/// 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<u8>`), and aggregates them into
/// a `Vec<Vec<u8>>`.
///
/// # Arguments
///
/// * `input` - A byte slice (`&[u8]`) containing JSON-encoded data. The JSON must be a valid array
/// of hex strings.
///
/// # Returns
///
/// * `Ok(Vec<Vec<u8>>)` - 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<Vec<Vec<u8>>, 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<BigInt>`.
///
/// 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<BigInt>`.
///
/// # Arguments
///
/// * `input` - A byte slice (`&[u8]`) containing JSON-encoded data. The JSON must be a valid array
/// of hex strings.
///
/// # Returns
///
/// * `Ok(Vec<BigInt>)` - 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<Vec<BigInt>, 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::*;
Expand Down Expand Up @@ -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());
}
}

0 comments on commit 4840265

Please sign in to comment.