Files
tycho-protocol-sdk/protocol-testing/src/tycho_runner.rs
TAMARA LIPOWSKI 12e6e48a78 feat: Add test for UniswapV2
- Used a random post-Shanghai-created USV2 pool state with enough liquidity for the test

Necessary fixes:
- Don't hardcode to EVMPoolState
- Don't expect adapter to always be set.
- UniswapV2 names its module differently for some reason... this seems like a special case so instead of updating all integration test yamls, I'm just hardcoding it in test_runner.rs. Perhaps not the best decision?
- Log the static attributes if not found.
- Get the substreams yaml path from config instead of hardcoding it.
2025-09-23 00:42:23 -04:00

183 lines
5.6 KiB
Rust

use std::{
io::{BufRead, BufReader},
process::{Child, Command, Stdio},
sync::mpsc::{self, Receiver, Sender},
thread,
time::Duration,
};
use miette::{IntoDiagnostic, WrapErr};
use tracing::{debug, info};
use crate::config::ProtocolComponentWithTestConfig;
pub struct TychoRunner {
db_url: String,
initialized_accounts: Vec<String>,
}
// TODO: Currently Tycho-Indexer cannot be run as a lib. We need to expose the entrypoints to allow
// running it as a lib
impl TychoRunner {
pub fn new(db_url: String, initialized_accounts: Vec<String>) -> Self {
Self { db_url, initialized_accounts }
}
pub fn run_tycho(
&self,
spkg_path: &str,
start_block: u64,
end_block: u64,
protocol_type_names: &[String],
protocol_system: &str,
) -> miette::Result<()> {
info!("Running Tycho indexer from block {start_block} to {end_block}...");
let mut cmd = Command::new("tycho-indexer");
cmd.env("RUST_LOG", std::env::var("RUST_LOG").unwrap_or("tycho_indexer=info".to_string()))
.env("AUTH_API_KEY", "dummy");
let all_accounts = self.initialized_accounts.clone();
// Determine the correct module name based on protocol system
let module_name = match protocol_system {
"uniswap_v2" => "map_pool_events",
_ => "map_protocol_changes",
};
cmd.args([
"--database-url",
self.db_url.as_str(),
"run",
"--spkg",
spkg_path,
"--module",
module_name,
"--protocol-type-names",
&protocol_type_names.join(","),
"--protocol-system",
protocol_system,
"--start-block",
&start_block.to_string(),
"--stop-block",
&(end_block + 2).to_string(), // +2 is to make up for the cache in the index side
"--dci-plugin",
"rpc",
]);
if !all_accounts.is_empty() {
cmd.args([
"--initialized-accounts",
&all_accounts.join(","),
"--initialization-block",
&start_block.to_string(),
]);
}
cmd.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut process = cmd
.spawn()
.into_diagnostic()
.wrap_err("Error running Tycho indexer")?;
Self::handle_process_output(&mut process);
let status = process
.wait()
.into_diagnostic()
.wrap_err("Failed to wait on Tycho indexer process")?;
// Note: tycho-indexer may exit with non-zero status when stream ends normally
// This is expected behavior and should not be treated as an error
if !status.success() {
debug!("Tycho indexer process exited with status: {status}");
}
Ok(())
}
pub fn run_with_rpc_server<F, R>(
&self,
func: F,
expected_components: &Vec<ProtocolComponentWithTestConfig>,
start_block: u64,
stop_block: u64,
) -> miette::Result<R>
where
F: FnOnce(&Vec<ProtocolComponentWithTestConfig>, u64, u64) -> R,
{
let (tx, rx): (Sender<bool>, Receiver<bool>) = mpsc::channel();
let db_url = self.db_url.clone();
// Start the RPC server in a separate thread
let rpc_thread = thread::spawn(move || {
let mut cmd = Command::new("tycho-indexer")
.args(["--database-url", db_url.as_str(), "rpc"])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.env(
"RUST_LOG",
std::env::var("RUST_LOG").unwrap_or("tycho_indexer=info".to_string()),
)
.env("AUTH_API_KEY", "dummy")
.spawn()
.expect("Failed to start RPC server");
Self::handle_process_output(&mut cmd);
match rx.recv() {
Ok(_) => {
debug!("Received termination message, stopping RPC server...");
cmd.kill()
.expect("Failed to kill RPC server");
let _ = cmd.wait();
}
Err(_) => {
// Channel closed, terminate anyway
let _ = cmd.kill();
let _ = cmd.wait();
}
}
});
// Give the RPC server time to start
thread::sleep(Duration::from_secs(3));
// Run the provided function
let result = func(expected_components, start_block, stop_block);
tx.send(true)
.expect("Failed to send termination message");
// Wait for the RPC thread to finish
if rpc_thread.join().is_err() {
eprintln!("Failed to join RPC thread");
}
Ok(result)
}
// Helper method to handle process output in separate threads
fn handle_process_output(child: &mut Child) {
if let Some(stdout) = child.stdout.take() {
thread::spawn(move || {
let reader = BufReader::new(stdout);
for line in reader.lines().map_while(Result::ok) {
println!("{line}");
}
});
}
if let Some(stderr) = child.stderr.take() {
thread::spawn(move || {
let reader = BufReader::new(stderr);
for line in reader.lines().map_while(Result::ok) {
eprintln!("{line}");
}
});
}
}
}