diff --git a/Cargo.toml b/Cargo.toml index ab641e9..e2bc544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "pathfinder2" version = "0.1.0" edition = "2021" +default-run = "server" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/bin/cli.rs b/src/bin/cli.rs new file mode 100644 index 0000000..ea12f6d --- /dev/null +++ b/src/bin/cli.rs @@ -0,0 +1,82 @@ +use std::env; +use std::fs::File; +use std::io::Write; + +use pathfinder2::graph; +use pathfinder2::io; +use pathfinder2::types::Address; +use pathfinder2::types::U256; + +const HUB_ADDRESS: &str = "0x29b9a7fBb8995b2423a71cC17cf9810798F6C543"; +const TRANSFER_THROUGH_SIG: &str = "transferThrough(address[],address[],address[],uint256[])"; +const RPC_URL: &str = "https://rpc.gnosischain.com"; + +fn main() { + let (dotfile, args) = if env::args().len() >= 2 && env::args().nth_back(1).unwrap() == "--dot" { + ( + Some(env::args().last().unwrap()), + env::args().rev().skip(2).rev().collect::>(), + ) + } else { + (None, env::args().collect::>()) + }; + + if args.len() < 4 { + println!("Usage: cli [--dot ]"); + println!("Usage: cli [--dot ]"); + println!("Usage: cli [--dot ]"); + return; + } + let mut max_hops = None; + let mut max_flow = U256::MAX; + let (from_str, to_str, edges_file) = (&args[1], &args[2], &args[3]); + if args.len() >= 5 { + max_hops = Some(args[4].parse().unwrap()); + if args.len() >= 6 { + max_flow = args[5].as_str().into(); + } + } + + println!("Computing flow {from_str} -> {to_str} using {edges_file}"); + let edges = io::read_edges_binary(edges_file).expect("Error loading edges."); + println!("Read {} edges", edges.len()); + let (flow, transfers) = graph::compute_flow( + &Address::from(from_str.as_str()), + &Address::from(to_str.as_str()), + &edges, + max_flow, + max_hops, + ); + println!("Max flow: {flow}"); + println!("{:?}", transfers); + + let token_owners = transfers + .iter() + .map(|e| e.token.to_string()) + .collect::>() + .join(","); + let froms = transfers + .iter() + .map(|e| e.from.to_string()) + .collect::>() + .join(","); + let tos = transfers + .iter() + .map(|e| e.to.to_string()) + .collect::>() + .join(","); + let amounts = transfers + .iter() + .map(|e| e.capacity.to_decimal()) + .collect::>() + .join(","); + println!("To check, run the following command (requires foundry):"); + println!("cast call '{HUB_ADDRESS}' '{TRANSFER_THROUGH_SIG}' '[{token_owners}]' '[{froms}]' '[{tos}]' '[{amounts}]' --rpc-url {RPC_URL} --from {}", &transfers[0].from.to_string()); + if let Some(dotfile) = dotfile { + File::create(&dotfile) + .unwrap() + .write_all(graph::transfers_to_dot(&transfers).as_bytes()) + .unwrap(); + println!("Wrote dotfile {dotfile}."); + } +} diff --git a/src/bin/server.rs b/src/bin/server.rs new file mode 100644 index 0000000..cbe0fb1 --- /dev/null +++ b/src/bin/server.rs @@ -0,0 +1,12 @@ +use std::env; + +use pathfinder2::server; + +fn main() { + let port = if env::args().len() == 1 { + 8080 + } else { + env::args().nth(1).unwrap().as_str().parse::().unwrap() + }; + server::start_server(port, 10, 4); +} diff --git a/src/graph/flow.rs b/src/graph/flow.rs index c3215db..78c20ed 100644 --- a/src/graph/flow.rs +++ b/src/graph/flow.rs @@ -65,6 +65,38 @@ pub fn compute_flow( (flow, transfers) } +pub fn transfers_to_dot(edges: &Vec) -> String { + let mut out = String::new(); + writeln!(out, "digraph transfers {{").expect(""); + + for Edge { + from, + to, + token, + capacity, + } in edges + { + let t = if token == from { + "(trust)".to_string() + } else if token == to { + String::new() + } else { + format!(" ({})", token.short()) + }; + writeln!( + out, + " \"{}\" -> \"{}\" [label=\"{}{}\"];", + from.short(), + to.short(), + capacity.to_decimal_fraction(), + t + ) + .expect(""); + } + writeln!(out, "}}").expect(""); + out +} + fn augmenting_path( source: &Address, sink: &Address, diff --git a/src/graph/mod.rs b/src/graph/mod.rs index 204c56b..80f7308 100644 --- a/src/graph/mod.rs +++ b/src/graph/mod.rs @@ -36,3 +36,4 @@ impl Display for Node { } pub use crate::graph::flow::compute_flow; +pub use crate::graph::flow::transfers_to_dot; diff --git a/src/lib.rs b/src/lib.rs index 77ce04b..6e76e80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod graph; pub mod io; +pub mod server; pub mod types; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 3e71dc5..0000000 --- a/src/main.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::env; - -mod graph; -mod io; -mod server; -mod types; - -fn main() { - let port = if env::args().len() == 1 { - 8080 - } else { - env::args().nth(1).unwrap().as_str().parse::().unwrap() - }; - server::start_server(port, 10, 4); - - // let args: Vec = env::args().collect(); - // if args.len() != 4 { - // panic!("Expected three arguments"); - // } - // let (from_str, to_str, edges_file) = (&args[1], &args[2], &args[3]); - - // println!("Computing flow {from_str} -> {to_str} using {edges_file}"); - // let edges = io::read_edges_binary(edges_file).expect("Error loading edges."); - // println!("Read {} edges", edges.len()); - // flow::compute_flow( - // &Address::from(from_str.as_str()), - // &Address::from(to_str.as_str()), - // &edges, - // ); -} diff --git a/src/types/address.rs b/src/types/address.rs index c6dd43c..127701d 100644 --- a/src/types/address.rs +++ b/src/types/address.rs @@ -3,6 +3,12 @@ use std::fmt::{Debug, Display, Formatter}; #[derive(Clone, Copy, Default, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct Address([u8; 20]); +impl Address { + pub fn short(&self) -> String { + format!("{self}")[..8].to_string() + } +} + impl Debug for Address { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { write!(f, "{}", self) diff --git a/src/types/u256.rs b/src/types/u256.rs index 352f0b2..9213300 100644 --- a/src/types/u256.rs +++ b/src/types/u256.rs @@ -1,10 +1,11 @@ +use std::fmt::Debug; use std::fmt::Display; use std::fmt::Formatter; use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; use num_bigint::BigUint; -#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct U256([u128; 2]); impl U256 { @@ -12,11 +13,27 @@ impl U256 { U256([high, low]) } pub const MAX: U256 = U256::new(u128::MAX, u128::MAX); - #[allow(dead_code)] pub fn to_decimal(self) -> String { let value = BigUint::from(self.0[0]) << 128 | BigUint::from(self.0[1]); format!("{}", value) } + pub fn to_decimal_fraction(self) -> String { + let value = BigUint::from(self.0[0]) << 128 | BigUint::from(self.0[1]); + let formatted = format!("{}", value); + match formatted.len() { + 18.. => { + format!( + "{}.{}", + &formatted[..formatted.len() - 18], + &formatted[formatted.len() - 18..formatted.len() - 16] + ) + } + 17 => { + format!(".0{}", &formatted[0..1],) + } + _ => "0+eps".to_string(), + } + } } impl From for U256 { @@ -116,6 +133,12 @@ impl Display for U256 { } } +impl Debug for U256 { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Display::fmt(self, f) + } +} + #[cfg(test)] mod test { use super::U256;