1use std::{collections::HashMap, str::FromStr};
2
3use tycho_common::{models::Chain, Bytes};
4
5use crate::encoding::{
6 errors::EncodingError,
7 evm::{
8 constants::{
9 DEFAULT_EXECUTORS_JSON, FALLBACK_KEY, FALLBACK_PREFIX, PRICE_LEVEL_STREAM_KEY,
10 PRICE_LEVEL_STREAM_PREFIX, PROPAMM_FALLBACK_KEY, PROPAMM_FALLBACK_PREFIX,
11 PROTOCOL_SPECIFIC_CONFIG, UNISWAP_V2_FORKS, UNISWAP_V3_FORKS,
12 },
13 swap_encoder::{
14 aerodrome_v1::AerodromeV1SwapEncoder, balancer_v2::BalancerV2SwapEncoder,
15 balancer_v3::BalancerV3SwapEncoder, bebop::BebopSwapEncoder, bopamm::BopAMMSwapEncoder,
16 curve::CurveSwapEncoder, ekubo::EkuboSwapEncoder, ekubo_v3::EkuboV3SwapEncoder,
17 erc_4626::ERC4626SwapEncoder, etherfi::EtherfiSwapEncoder,
18 fallback::FallbackSwapEncoder, fermiswap::FermiSwapEncoder,
19 fluid_v1::FluidV1SwapEncoder, hashflow::HashflowSwapEncoder,
20 liquidity_party::LiquidityPartySwapEncoder, liquorice::LiquoriceSwapEncoder,
21 lunarbase::LunarBaseSwapEncoder, maverick_v2::MaverickV2SwapEncoder,
22 metric::MetricSwapEncoder, native::NativeSwapEncoder, native_wrap::WrapSwapEncoder,
23 propamm::PropAMMSwapEncoder, ring_swap_v2::RingSwapV2SwapEncoder,
24 rocketpool::RocketpoolSwapEncoder, sky::SkySwapEncoder,
25 slipstreams::SlipstreamsSwapEncoder, uniswap_v2::UniswapV2SwapEncoder,
26 uniswap_v3::UniswapV3SwapEncoder, uniswap_v4::UniswapV4SwapEncoder,
27 },
28 },
29 swap_encoder::SwapEncoder,
30};
31
32#[derive(Clone)]
34pub struct SwapEncoderRegistry {
35 chain: Chain,
36 encoders: HashMap<String, Box<dyn SwapEncoder>>,
38}
39
40impl SwapEncoderRegistry {
41 pub fn new(chain: Chain) -> Self {
42 Self { chain, encoders: HashMap::new() }
43 }
44
45 pub fn new_with_defaults(chain: Chain) -> Result<Self, EncodingError> {
47 Self::new(chain).add_default_encoders(None)
48 }
49
50 pub fn add_default_encoders(
53 mut self,
54 executors_addresses: Option<String>,
55 ) -> Result<Self, EncodingError> {
56 let config_str = if let Some(addresses) = executors_addresses {
57 addresses
58 } else {
59 DEFAULT_EXECUTORS_JSON.to_string()
60 };
61 let config: HashMap<Chain, HashMap<String, String>> = serde_json::from_str(&config_str)?;
62 let executors = config
63 .get(&self.chain)
64 .ok_or(EncodingError::FatalError("No executors found for chain".to_string()))?;
65
66 let protocol_specific_config: HashMap<Chain, HashMap<String, HashMap<String, String>>> =
67 serde_json::from_str(PROTOCOL_SPECIFIC_CONFIG)?;
68 let protocol_specific_config = protocol_specific_config
69 .get(&self.chain)
70 .ok_or(EncodingError::FatalError(
71 "No protocol specific config found for chain".to_string(),
72 ))?;
73 for (protocol, executor_address) in executors {
74 let encoder = self.create_encoder(
75 protocol,
76 Bytes::from_str(executor_address).map_err(|_| {
77 EncodingError::FatalError(format!(
78 "Invalid executor address for protocol {}",
79 protocol
80 ))
81 })?,
82 protocol_specific_config
83 .get(protocol)
84 .cloned(),
85 )?;
86 self.encoders
87 .insert(protocol.to_string(), encoder);
88 }
89 Ok(self)
90 }
91
92 pub fn register_encoder(mut self, protocol: &str, encoder: Box<dyn SwapEncoder>) -> Self {
94 self.encoders
95 .insert(protocol.to_string(), encoder);
96 self
97 }
98
99 #[allow(clippy::borrowed_box)]
107 pub fn get_encoder(&self, protocol_system: &str) -> Option<&Box<dyn SwapEncoder>> {
108 if let Some(encoder) = self.encoders.get(protocol_system) {
109 return Some(encoder);
110 }
111 if protocol_system.starts_with(PRICE_LEVEL_STREAM_PREFIX) {
112 return self
113 .encoders
114 .get(PRICE_LEVEL_STREAM_KEY);
115 }
116 if protocol_system.starts_with(PROPAMM_FALLBACK_PREFIX) {
117 return self.encoders.get(PROPAMM_FALLBACK_KEY);
118 }
119 if protocol_system.starts_with(FALLBACK_PREFIX) {
120 return self.encoders.get(FALLBACK_KEY);
121 }
122 None
123 }
124
125 pub fn executor_addresses(&self) -> HashMap<String, Bytes> {
130 self.encoders
131 .iter()
132 .map(|(protocol, encoder)| (protocol.clone(), encoder.executor_address().clone()))
133 .collect()
134 }
135
136 fn create_encoder(
137 &self,
138 protocol_system: &str,
139 executor_address: Bytes,
140 config: Option<HashMap<String, String>>,
141 ) -> Result<Box<dyn SwapEncoder>, EncodingError> {
142 match protocol_system {
143 p if UNISWAP_V2_FORKS.contains(&p) => {
144 Ok(Box::new(UniswapV2SwapEncoder::new(executor_address, self.chain, config)?))
145 }
146 "ring_swap_v2" => {
147 Ok(Box::new(RingSwapV2SwapEncoder::new(executor_address, self.chain, config)?))
148 }
149 "aerodrome_v1" => {
150 Ok(Box::new(AerodromeV1SwapEncoder::new(executor_address, self.chain, config)?))
151 }
152 "vm:balancer_v2" => {
153 Ok(Box::new(BalancerV2SwapEncoder::new(executor_address, self.chain, config)?))
154 }
155 p if UNISWAP_V3_FORKS.contains(&p) => {
156 Ok(Box::new(UniswapV3SwapEncoder::new(executor_address, self.chain, config)?))
157 }
158 "uniswap_v4" => {
159 Ok(Box::new(UniswapV4SwapEncoder::new(executor_address, self.chain, config)?))
160 }
161 "ekubo_v2" => {
162 Ok(Box::new(EkuboSwapEncoder::new(executor_address, self.chain, config)?))
163 }
164 "ekubo_v3" => {
165 Ok(Box::new(EkuboV3SwapEncoder::new(executor_address, self.chain, config)?))
166 }
167 "vm:bopamm" => {
168 Ok(Box::new(BopAMMSwapEncoder::new(executor_address, self.chain, config)?))
169 }
170 "vm:curve" => {
171 Ok(Box::new(CurveSwapEncoder::new(executor_address, self.chain, config)?))
172 }
173 "vm:maverick_v2" => {
174 Ok(Box::new(MaverickV2SwapEncoder::new(executor_address, self.chain, config)?))
175 }
176 "vm:balancer_v3" => {
177 Ok(Box::new(BalancerV3SwapEncoder::new(executor_address, self.chain, config)?))
178 }
179 "rfq:bebop" => {
180 Ok(Box::new(BebopSwapEncoder::new(executor_address, self.chain, config)?))
181 }
182 "rfq:hashflow" => {
183 Ok(Box::new(HashflowSwapEncoder::new(executor_address, self.chain, config)?))
184 }
185 "rfq:liquorice" => {
186 Ok(Box::new(LiquoriceSwapEncoder::new(executor_address, self.chain, config)?))
187 }
188 "rfq:metric" => {
189 Ok(Box::new(MetricSwapEncoder::new(executor_address, self.chain, config)?))
190 }
191 "rfq:native" => {
192 Ok(Box::new(NativeSwapEncoder::new(executor_address, self.chain, config)?))
193 }
194 "fluid_v1" => {
195 Ok(Box::new(FluidV1SwapEncoder::new(executor_address, self.chain, config)?))
196 }
197 "vm:fermiswap" => {
198 Ok(Box::new(FermiSwapEncoder::new(executor_address, self.chain, config)?))
199 }
200 "vm:liquidityparty" => {
201 Ok(Box::new(LiquidityPartySwapEncoder::new(executor_address, self.chain, config)?))
202 }
203 "aerodrome_slipstreams" => {
204 Ok(Box::new(SlipstreamsSwapEncoder::new(executor_address, self.chain, config)?))
205 }
206 "rocketpool" => {
207 Ok(Box::new(RocketpoolSwapEncoder::new(executor_address, self.chain, config)?))
208 }
209 "sky" => Ok(Box::new(SkySwapEncoder::new(executor_address, self.chain, config)?)),
210 "erc4626" => {
211 Ok(Box::new(ERC4626SwapEncoder::new(executor_address, self.chain, config)?))
212 }
213 "lunarbase" => {
214 Ok(Box::new(LunarBaseSwapEncoder::new(executor_address, self.chain, config)?))
215 }
216 "velodrome_slipstreams" => {
217 Ok(Box::new(SlipstreamsSwapEncoder::new(executor_address, self.chain, config)?))
218 }
219 "up_v3" => {
222 Ok(Box::new(SlipstreamsSwapEncoder::new(executor_address, self.chain, config)?))
223 }
224 "ramses_v3" => {
239 Ok(Box::new(SlipstreamsSwapEncoder::new(executor_address, self.chain, config)?))
240 }
241 "native_wrapper" => {
242 Ok(Box::new(WrapSwapEncoder::new(executor_address, self.chain, config)?))
243 }
244 "etherfi" => {
245 Ok(Box::new(EtherfiSwapEncoder::new(executor_address, self.chain, config)?))
246 }
247 pls if pls == PRICE_LEVEL_STREAM_KEY ||
255 pls.starts_with(PRICE_LEVEL_STREAM_PREFIX) ||
256 pls == PROPAMM_FALLBACK_KEY ||
257 pls.starts_with(PROPAMM_FALLBACK_PREFIX) =>
258 {
259 Ok(Box::new(PropAMMSwapEncoder::new(executor_address, self.chain, config)?))
260 }
261 f if f == FALLBACK_KEY || f.starts_with(FALLBACK_PREFIX) => {
264 Ok(Box::new(FallbackSwapEncoder::new(executor_address, self.chain, config)?))
265 }
266 _ => Err(EncodingError::FatalError(format!(
267 "Unknown protocol system: {}",
268 protocol_system
269 ))),
270 }
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
283 fn test_price_level_stream_protocols_route_to_generic_encoder() {
284 let executors = std::fs::read_to_string("config/test_executor_addresses.json").unwrap();
285 let registry = SwapEncoderRegistry::new(Chain::Ethereum)
286 .add_default_encoders(Some(executors))
287 .unwrap();
288
289 for protocol in [
290 PRICE_LEVEL_STREAM_KEY,
291 "pricelevelstream:fermiswap",
292 "pricelevelstream:kipseli",
293 "pricelevelstream:0x2222222222222222222222222222222222222222",
294 ] {
295 assert!(registry.get_encoder(protocol).is_some(), "no encoder resolved for {protocol}");
296 }
297 assert!(registry
299 .get_encoder("unknown_protocol")
300 .is_none());
301 }
302
303 #[test]
306 fn test_propamm_fallback_protocol_resolution() {
307 let executors = std::fs::read_to_string("config/test_executor_addresses.json").unwrap();
308 let registry = SwapEncoderRegistry::new(Chain::Ethereum)
309 .add_default_encoders(Some(executors))
310 .unwrap();
311
312 for protocol in [
313 PROPAMM_FALLBACK_KEY,
314 "propammfallback:fermiswap",
315 "propammfallback:0x5979458912f80b96d30d4220af8e2e4925a33320",
316 ] {
317 assert!(registry.get_encoder(protocol).is_some(), "no encoder resolved for {protocol}");
318 }
319
320 let direct = registry
321 .get_encoder("pricelevelstream:fermiswap")
322 .unwrap()
323 .executor_address()
324 .clone();
325 let via_router = registry
326 .get_encoder("propammfallback:fermiswap")
327 .unwrap()
328 .executor_address()
329 .clone();
330 assert_ne!(direct, via_router);
331 }
332
333 #[test]
337 fn test_fallback_protocol_resolution() {
338 let executor_address =
339 Bytes::from_str("0x5c2f5a71f67c01775180adc06909288b4c329308").unwrap();
340 let registry = SwapEncoderRegistry::new(Chain::Ethereum);
341 let config = HashMap::from([(
342 "angstrom_hook_address".to_string(),
343 "0x0000000aa232009084Bd71A5797d089AA4Edfad4".to_string(),
344 )]);
345 let encoder = registry
346 .create_encoder(FALLBACK_KEY, executor_address.clone(), Some(config))
347 .unwrap();
348 let registry = registry.register_encoder(FALLBACK_KEY, encoder);
349
350 for protocol in [
351 FALLBACK_KEY,
352 "fallback:fermiswap",
353 "fallback:0x5979458912f80b96d30d4220af8e2e4925a33320",
354 ] {
355 let resolved = registry
356 .get_encoder(protocol)
357 .unwrap_or_else(|| panic!("no encoder resolved for {protocol}"));
358 assert_eq!(resolved.executor_address(), &executor_address);
359 }
360 assert!(registry
362 .get_encoder("fallbackless_protocol")
363 .is_none());
364 }
365
366 #[test]
367 fn test_default_encoders_build_for_every_configured_chain() {
368 let chains = [
369 Chain::Ethereum,
370 Chain::Base,
371 Chain::Unichain,
372 Chain::Arbitrum,
373 Chain::Bsc,
374 Chain::Polygon,
375 Chain::Plasma,
376 Chain::Robinhood,
377 ];
378 for chain in chains {
379 let registry = SwapEncoderRegistry::new_with_defaults(chain).unwrap_or_else(|e| {
380 panic!("default encoders failed to build for chain {chain}: {e}")
381 });
382 assert!(
383 registry
384 .get_encoder("uniswap_v3")
385 .is_some(),
386 "chain {chain} is missing the uniswap_v3 encoder"
387 );
388 }
389 }
390
391 #[test]
392 fn test_executor_addresses_match_registered_encoders() {
393 let registry = SwapEncoderRegistry::new_with_defaults(Chain::Ethereum).unwrap();
394
395 let executor_addresses = registry.executor_addresses();
396
397 assert!(!executor_addresses.is_empty());
398 for (protocol, executor_address) in executor_addresses {
399 let encoder = registry
400 .get_encoder(&protocol)
401 .unwrap_or_else(|| panic!("no encoder registered for {protocol}"));
402 assert_eq!(encoder.executor_address(), &executor_address);
403 }
404 }
405
406 #[test]
410 fn test_fallback_angstrom_hook_matches_uniswap_v4() {
411 let config: HashMap<Chain, HashMap<String, HashMap<String, String>>> =
412 serde_json::from_str(PROTOCOL_SPECIFIC_CONFIG).unwrap();
413 for (chain, protocols) in config {
414 let Some(fallback) = protocols.get(FALLBACK_KEY) else { continue };
415 assert_eq!(
416 fallback.get("angstrom_hook_address"),
417 protocols
418 .get("uniswap_v4")
419 .and_then(|uniswap_v4| uniswap_v4.get("angstrom_hook_address")),
420 "chain {chain}: the fallback and uniswap_v4 sections of \
421 protocol_specific_addresses.json must name the same Angstrom hook"
422 );
423 }
424 }
425}