use std::collections::HashMap;
use tracing::{debug, info};
use tycho_client::feed::synchronizer::ComponentWithState;
use tycho_common::Bytes;
pub use crate::evm::protocol::ekubo_v3::{
filter_fn as ekubo_v3_extension_filter,
filter_fn_with_signed_exclusive_swap as ekubo_v3_extension_filter_with_signed_exclusive_swap,
};
pub fn balancer_v2_pool_filter(component: &ComponentWithState) -> bool {
const UNSUPPORTED_COMPONENT_IDS: [&str; 6] = [
"0x848a5564158d84b8a8fb68ab5d004fae11619a5400000000000000000000066a",
"0x596192bb6e41802428ac943d2f1476c1af25cc0e000000000000000000000659",
"0x05ff47afada98a98982113758878f9a8b9fdda0a000000000000000000000645",
"0x265b6d1a6c12873a423c177eba6dd2470f40a3b50001000000000000000003fd",
"0x9f9d900462492d4c21e9523ca95a7cd86142f298000200000000000000000462",
"0x42ed016f826165c2e5976fe5bc3df540c5ad0af700000000000000000000058b",
];
if UNSUPPORTED_COMPONENT_IDS.contains(
&component
.component
.id
.to_lowercase()
.as_str(),
) {
debug!(
"Filtering out Balancer pools {} that have missing Accounts after DCI update.",
component.component.id
);
return false;
}
true
}
pub fn uniswap_v4_euler_hook_pool_filter(component: &ComponentWithState) -> bool {
component
.component
.static_attributes
.get("hook_identifier")
.and_then(|bytes| std::str::from_utf8(bytes).ok())
.is_some_and(|s| s == "euler_v1")
}
pub fn uniswap_v4_angstrom_hook_pool_filter(component: &ComponentWithState) -> bool {
component
.component
.static_attributes
.get("hook_identifier")
.and_then(|bytes| std::str::from_utf8(bytes).ok())
.is_some_and(|s| s == "angstrom_v1")
}
pub fn uniswap_v4_non_angstrom_hook_pool_filter(component: &ComponentWithState) -> bool {
!uniswap_v4_angstrom_hook_pool_filter(component)
}
pub fn balancer_v3_pool_filter(component: &ComponentWithState) -> bool {
if let Some(erc4626) = component
.component
.static_attributes
.get("erc4626")
{
if erc4626.to_vec() == [1u8] {
info!(
"Filtering out Balancer V3 pool {} because it uses ERC4626",
component.component.id
);
return false;
}
}
true
}
pub fn fluid_v1_paused_pools_filter(component: &ComponentWithState) -> bool {
const PAUSED_POOLS: [&str; 5] = [
"0x97479d9c09c7fd333bbfd07e93d4c8a669698ebc",
"0xd0810e5cf08dcde266ecebef40cad806c7768d72",
"0xf507a38aaf37339cc3beac4c7a58b17401bdf6bc",
"0x2886a01a0645390872a9eb99dae1283664b0c524",
"0x276084527b801e00db8e4410504f9baf93f72c67",
];
if PAUSED_POOLS.contains(
&component
.component
.id
.to_lowercase()
.as_str(),
) {
return false;
}
true
}
pub fn curve_filter(component: &ComponentWithState) -> bool {
let attrs = &component.component.static_attributes;
if attr_json_list_non_empty(attrs, "rebase_tokens") || has_unsupported_asset_type(attrs) {
debug!(
"Filtering out curve pool {} with rate-bearing/rebasing coins (unsupported by hybrid)",
component.component.id
);
return false;
}
true
}
pub fn liquidityparty_killed_pools_filter(component: &ComponentWithState) -> bool {
if let Some(killed) = component.state.attributes.get("killed") {
if killed.to_vec() == [1u8] {
debug!(
"Filtering out LiquidityParty pool {} because it is killed",
component.component.id
);
return false;
}
}
true
}
fn attr_json_list(attrs: &HashMap<String, Bytes>, key: &str) -> Option<Vec<String>> {
let text = std::str::from_utf8(attrs.get(key)?.as_ref()).ok()?;
serde_json::from_str::<Vec<String>>(text).ok()
}
fn attr_json_list_non_empty(attrs: &HashMap<String, Bytes>, key: &str) -> bool {
attr_json_list(attrs, key).is_some_and(|list| !list.is_empty())
}
fn has_unsupported_asset_type(attrs: &HashMap<String, Bytes>) -> bool {
const TRUSTED_RATE_ORACLES: [(&str, &str); 1] = [
("0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee", "0x679aefce"),
];
let Some(types) = attr_json_list(attrs, "asset_types") else {
return false;
};
let oracles = attr_json_list(attrs, "oracles").unwrap_or_default();
let method_ids = attr_json_list(attrs, "method_ids").unwrap_or_default();
for (index, entry) in types.iter().enumerate() {
let asset_type = entry
.trim_start_matches("0x")
.trim_start_matches('0');
if asset_type.is_empty() {
continue;
}
let (Some(oracle), Some(method_id)) = (oracles.get(index), method_ids.get(index)) else {
return true;
};
let trusted = TRUSTED_RATE_ORACLES
.iter()
.any(|(trusted_oracle, trusted_method_id)| {
oracle.eq_ignore_ascii_case(trusted_oracle) &&
method_id.eq_ignore_ascii_case(trusted_method_id)
});
if asset_type != "1" || !trusted {
return true;
}
}
false
}
pub fn erc4626_filter(component: &ComponentWithState) -> bool {
const UNSUPPORTED_POOLS: [&str; 3] = [
"0x28b3a8fb53b741a8fd78c0fb9a6b2393d896a43d",
"0xe2e7a17dff93280dec073c995595155283e3c372",
"0x83f20f44975d03b1b09e64809b757c47f942beea",
];
if UNSUPPORTED_POOLS.contains(
&component
.component
.id
.to_lowercase()
.as_str(),
) {
return false;
}
true
}
#[cfg(test)]
mod tests {
use tycho_common::models::protocol::{ProtocolComponent, ProtocolComponentState};
use super::*;
fn attrs(pairs: &[(&str, &str)]) -> HashMap<String, Bytes> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), Bytes::from(v.as_bytes().to_vec())))
.collect()
}
fn hooks_component(hook_identifier: Option<&str>) -> ComponentWithState {
let static_attributes = match hook_identifier {
Some(id) => attrs(&[("hook_identifier", id)]),
None => HashMap::new(),
};
ComponentWithState {
state: ProtocolComponentState::new("test_pool", HashMap::new(), HashMap::new()),
component: ProtocolComponent { static_attributes, ..Default::default() },
component_tvl: None,
entrypoints: Vec::new(),
}
}
fn curve_component(static_attributes: &[(&str, &str)]) -> ComponentWithState {
ComponentWithState {
state: ProtocolComponentState::new("curve_pool", HashMap::new(), HashMap::new()),
component: ProtocolComponent {
static_attributes: attrs(static_attributes),
..Default::default()
},
component_tvl: None,
entrypoints: Vec::new(),
}
}
fn erc4626_component(id: &str) -> ComponentWithState {
ComponentWithState {
state: ProtocolComponentState::new(id, HashMap::new(), HashMap::new()),
component: ProtocolComponent { id: id.to_string(), ..Default::default() },
component_tvl: None,
entrypoints: Vec::new(),
}
}
#[test]
fn non_angstrom_filter_excludes_angstrom_pools() {
assert!(!uniswap_v4_non_angstrom_hook_pool_filter(&hooks_component(Some("angstrom_v1"))));
assert!(uniswap_v4_non_angstrom_hook_pool_filter(&hooks_component(Some("euler_v1"))));
assert!(uniswap_v4_non_angstrom_hook_pool_filter(&hooks_component(None)));
}
#[test]
fn curve_keeps_standard_pool() {
assert!(curve_filter(&curve_component(&[])));
assert!(curve_filter(&curve_component(&[("asset_types", r#"["0x00","0x00"]"#)])));
}
fn oracle_pool(asset_type: &str, oracle: &str, method_id: &str) -> ComponentWithState {
let asset_types = format!(r#"["0x00","{asset_type}"]"#);
let oracles = format!(r#"["0x0000000000000000000000000000000000000000","{oracle}"]"#);
let method_ids = format!(r#"["0x00000000","{method_id}"]"#);
curve_component(&[
("asset_types", asset_types.as_str()),
("oracles", oracles.as_str()),
("method_ids", method_ids.as_str()),
])
}
#[test]
fn curve_keeps_trusted_oracle_asset_type() {
assert!(curve_filter(&oracle_pool(
"0x01",
"0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee",
"0x679aefce"
)));
}
#[test]
fn curve_excludes_untrusted_rate_coins() {
let weeth = "0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee";
assert!(!curve_filter(&oracle_pool(
"0x01",
"0x1111111111111111111111111111111111111111",
"0x679aefce"
)));
assert!(!curve_filter(&oracle_pool("0x01", weeth, "0x12345678")));
assert!(!curve_filter(&oracle_pool("0x03", weeth, "0x679aefce")));
assert!(!curve_filter(&curve_component(&[("asset_types", r#"["0x00","0x01"]"#)])));
}
#[test]
fn curve_excludes_rebase_tokens() {
assert!(!curve_filter(&curve_component(&[(
"rebase_tokens",
r#"["0xa35b1b31ce002fbf2058d22f30f95d405200a15b"]"#
)])));
}
#[test]
fn erc4626_excludes_unsupported_pools_regardless_of_id_case() {
assert!(!erc4626_filter(&erc4626_component("0x28B3a8fb53B741A8Fd78c0fb9A6B2393d896a43d")));
assert!(!erc4626_filter(&erc4626_component("0x28b3a8fb53b741a8fd78c0fb9a6b2393d896a43d")));
assert!(erc4626_filter(&erc4626_component("0xa3931d71877c0e7a3148cb7eb4463524fec27fbd")));
}
#[test]
fn curve_zero_encoded_as_empty_hex_is_standard() {
assert!(curve_filter(&curve_component(&[("asset_types", r#"["0x","0x"]"#)])));
}
}