use alloy::{
network::Ethereum,
primitives::{address, Address, TxKind},
providers::{Provider, ProviderBuilder, RootProvider},
rpc::types::TransactionRequest,
sol,
sol_types::SolCall,
};
use tycho_common::Bytes;
pub const FALLBACK_ROUTER_ADDRESS: Address = address!("4DdF368080CD7946db5b459aD591c350158175e1");
sol! {
function getWhitelistedVenues() external view returns (address[] memory venues);
}
#[derive(Debug, thiserror::Error)]
pub enum FetchVenuesError {
#[error("invalid RPC URL {url:?}: {reason}")]
InvalidUrl {
url: String,
reason: String,
},
#[error("getWhitelistedVenues call to the PropAMMRouter failed: {reason}")]
Call {
reason: String,
},
}
pub async fn fetch_fallback_router_venues(rpc_url: &str) -> Result<Vec<Bytes>, FetchVenuesError> {
let url: reqwest::Url = rpc_url
.parse()
.map_err(|e| FetchVenuesError::InvalidUrl {
url: rpc_url.to_string(),
reason: format!("{e}"),
})?;
let provider: RootProvider<Ethereum> = ProviderBuilder::default().connect_http(url);
let response = provider
.call(TransactionRequest {
to: Some(TxKind::Call(FALLBACK_ROUTER_ADDRESS)),
input: getWhitelistedVenuesCall {}
.abi_encode()
.into(),
..Default::default()
})
.await
.map_err(|e| FetchVenuesError::Call { reason: e.to_string() })?;
let venues = getWhitelistedVenuesCall::abi_decode_returns(&response).map_err(|e| {
FetchVenuesError::Call { reason: format!("failed to decode response: {e}") }
})?;
Ok(venues
.into_iter()
.map(|venue| Bytes::from(venue.as_slice().to_vec()))
.collect())
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
#[tokio::test]
#[ignore = "Requires RPC_URL to be set in environment variables or .env file"]
async fn test_fetch_fallback_router_venues_against_mainnet() {
let rpc_url = std::env::var("RPC_URL").expect("RPC_URL must be set for network tests");
let venues = fetch_fallback_router_venues(&rpc_url)
.await
.expect("whitelist read should succeed");
let fermiswap =
Bytes::from_str("0x5979458912f80b96d30d4220af8e2e4925a33320").expect("valid address");
assert!(venues.contains(&fermiswap), "expected FermiSwap in {venues:?}");
}
#[tokio::test]
async fn test_fetch_fallback_router_venues_invalid_url() {
let result = fetch_fallback_router_venues("not a url").await;
assert!(matches!(result, Err(FetchVenuesError::InvalidUrl { .. })));
}
#[test]
fn test_router_address_matches_the_executor() {
let executor = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../tycho-execution/contracts/src/executors/PropAMMFallbackExecutor.sol");
let source = std::fs::read_to_string(&executor)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", executor.display()));
let address = FALLBACK_ROUTER_ADDRESS.to_string();
assert!(source.contains(&address), "PropAMMFallbackExecutor.sol does not use {address}");
}
}