tycho_execution/encoding/evm/swap_encoder/
swap_encoder_registry.rs1use 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, PRICE_LEVEL_STREAM_KEY, PRICE_LEVEL_STREAM_PREFIX,
10 PROPAMM_FALLBACK_KEY, PROPAMM_FALLBACK_PREFIX, PROTOCOL_SPECIFIC_CONFIG,
11 },
12 swap_encoder::{
13 aerodrome_v1::AerodromeV1SwapEncoder, balancer_v2::BalancerV2SwapEncoder,
14 balancer_v3::BalancerV3SwapEncoder, bebop::BebopSwapEncoder, bopamm::BopAMMSwapEncoder,
15 curve::CurveSwapEncoder, ekubo::EkuboSwapEncoder, ekubo_v3::EkuboV3SwapEncoder,
16 erc_4626::ERC4626SwapEncoder, etherfi::EtherfiSwapEncoder, fermiswap::FermiSwapEncoder,
17 fluid_v1::FluidV1SwapEncoder, hashflow::HashflowSwapEncoder,
18 liquidity_party::LiquidityPartySwapEncoder, liquorice::LiquoriceSwapEncoder,
19 lunarbase::LunarBaseSwapEncoder, maverick_v2::MaverickV2SwapEncoder,
20 metric::MetricSwapEncoder, native_wrap::WrapSwapEncoder, propamm::PropAMMSwapEncoder,
21 ring_swap_v2::RingSwapV2SwapEncoder, rocketpool::RocketpoolSwapEncoder,
22 slipstreams::SlipstreamsSwapEncoder, uniswap_v2::UniswapV2SwapEncoder,
23 uniswap_v3::UniswapV3SwapEncoder, uniswap_v4::UniswapV4SwapEncoder,
24 },
25 },
26 swap_encoder::SwapEncoder,
27};
28
29#[derive(Clone)]
31pub struct SwapEncoderRegistry {
32 chain: Chain,
33 encoders: HashMap<String, Box<dyn SwapEncoder>>,
35}
36
37impl SwapEncoderRegistry {
38 pub fn new(chain: Chain) -> Self {
39 Self { chain, encoders: HashMap::new() }
40 }
41
42 pub fn new_with_defaults(chain: Chain) -> Result<Self, EncodingError> {
44 Self::new(chain).add_default_encoders(None)
45 }
46
47 pub fn add_default_encoders(
50 mut self,
51 executors_addresses: Option<String>,
52 ) -> Result<Self, EncodingError> {
53 let config_str = if let Some(addresses) = executors_addresses {
54 addresses
55 } else {
56 DEFAULT_EXECUTORS_JSON.to_string()
57 };
58 let config: HashMap<Chain, HashMap<String, String>> = serde_json::from_str(&config_str)?;
59 let executors = config
60 .get(&self.chain)
61 .ok_or(EncodingError::FatalError("No executors found for chain".to_string()))?;
62
63 let protocol_specific_config: HashMap<Chain, HashMap<String, HashMap<String, String>>> =
64 serde_json::from_str(PROTOCOL_SPECIFIC_CONFIG)?;
65 let protocol_specific_config = protocol_specific_config
66 .get(&self.chain)
67 .ok_or(EncodingError::FatalError(
68 "No protocol specific config found for chain".to_string(),
69 ))?;
70 for (protocol, executor_address) in executors {
71 let encoder = self.create_encoder(
72 protocol,
73 Bytes::from_str(executor_address).map_err(|_| {
74 EncodingError::FatalError(format!(
75 "Invalid executor address for protocol {}",
76 protocol
77 ))
78 })?,
79 protocol_specific_config
80 .get(protocol)
81 .cloned(),
82 )?;
83 self.encoders
84 .insert(protocol.to_string(), encoder);
85 }
86 Ok(self)
87 }
88
89 pub fn register_encoder(mut self, protocol: &str, encoder: Box<dyn SwapEncoder>) -> Self {
91 self.encoders
92 .insert(protocol.to_string(), encoder);
93 self
94 }
95
96 #[allow(clippy::borrowed_box)]
103 pub fn get_encoder(&self, protocol_system: &str) -> Option<&Box<dyn SwapEncoder>> {
104 if let Some(encoder) = self.encoders.get(protocol_system) {
105 return Some(encoder);
106 }
107 if protocol_system.starts_with(PRICE_LEVEL_STREAM_PREFIX) {
108 return self
109 .encoders
110 .get(PRICE_LEVEL_STREAM_KEY);
111 }
112 if protocol_system.starts_with(PROPAMM_FALLBACK_PREFIX) {
113 return self.encoders.get(PROPAMM_FALLBACK_KEY);
114 }
115 None
116 }
117
118 fn create_encoder(
119 &self,
120 protocol_system: &str,
121 executor_address: Bytes,
122 config: Option<HashMap<String, String>>,
123 ) -> Result<Box<dyn SwapEncoder>, EncodingError> {
124 match protocol_system {
125 "uniswap_v2" | "sushiswap_v2" | "pancakeswap_v2" | "quickswap_v2" => {
126 Ok(Box::new(UniswapV2SwapEncoder::new(executor_address, self.chain, config)?))
127 }
128 "ring_swap_v2" => {
129 Ok(Box::new(RingSwapV2SwapEncoder::new(executor_address, self.chain, config)?))
130 }
131 "aerodrome_v1" => {
132 Ok(Box::new(AerodromeV1SwapEncoder::new(executor_address, self.chain, config)?))
133 }
134 "vm:balancer_v2" => {
135 Ok(Box::new(BalancerV2SwapEncoder::new(executor_address, self.chain, config)?))
136 }
137 "uniswap_v3" | "pancakeswap_v3" => {
138 Ok(Box::new(UniswapV3SwapEncoder::new(executor_address, self.chain, config)?))
139 }
140 "uniswap_v4" => {
141 Ok(Box::new(UniswapV4SwapEncoder::new(executor_address, self.chain, config)?))
142 }
143 "ekubo_v2" => {
144 Ok(Box::new(EkuboSwapEncoder::new(executor_address, self.chain, config)?))
145 }
146 "ekubo_v3" => {
147 Ok(Box::new(EkuboV3SwapEncoder::new(executor_address, self.chain, config)?))
148 }
149 "vm:bopamm" => {
150 Ok(Box::new(BopAMMSwapEncoder::new(executor_address, self.chain, config)?))
151 }
152 "vm:curve" => {
153 Ok(Box::new(CurveSwapEncoder::new(executor_address, self.chain, config)?))
154 }
155 "vm:maverick_v2" => {
156 Ok(Box::new(MaverickV2SwapEncoder::new(executor_address, self.chain, config)?))
157 }
158 "vm:balancer_v3" => {
159 Ok(Box::new(BalancerV3SwapEncoder::new(executor_address, self.chain, config)?))
160 }
161 "rfq:bebop" => {
162 Ok(Box::new(BebopSwapEncoder::new(executor_address, self.chain, config)?))
163 }
164 "rfq:hashflow" => {
165 Ok(Box::new(HashflowSwapEncoder::new(executor_address, self.chain, config)?))
166 }
167 "rfq:liquorice" => {
168 Ok(Box::new(LiquoriceSwapEncoder::new(executor_address, self.chain, config)?))
169 }
170 "rfq:metric" => {
171 Ok(Box::new(MetricSwapEncoder::new(executor_address, self.chain, config)?))
172 }
173 "fluid_v1" => {
174 Ok(Box::new(FluidV1SwapEncoder::new(executor_address, self.chain, config)?))
175 }
176 "vm:fermiswap" => {
177 Ok(Box::new(FermiSwapEncoder::new(executor_address, self.chain, config)?))
178 }
179 "vm:liquidityparty" => {
180 Ok(Box::new(LiquidityPartySwapEncoder::new(executor_address, self.chain, config)?))
181 }
182 "aerodrome_slipstreams" => {
183 Ok(Box::new(SlipstreamsSwapEncoder::new(executor_address, self.chain, config)?))
184 }
185 "rocketpool" => {
186 Ok(Box::new(RocketpoolSwapEncoder::new(executor_address, self.chain, config)?))
187 }
188 "erc4626" => {
189 Ok(Box::new(ERC4626SwapEncoder::new(executor_address, self.chain, config)?))
190 }
191 "lunarbase" => {
192 Ok(Box::new(LunarBaseSwapEncoder::new(executor_address, self.chain, config)?))
193 }
194 "velodrome_slipstreams" => {
195 Ok(Box::new(SlipstreamsSwapEncoder::new(executor_address, self.chain, config)?))
196 }
197 "ramses_v3" => {
212 Ok(Box::new(SlipstreamsSwapEncoder::new(executor_address, self.chain, config)?))
213 }
214 "native_wrapper" => {
215 Ok(Box::new(WrapSwapEncoder::new(executor_address, self.chain, config)?))
216 }
217 "etherfi" => {
218 Ok(Box::new(EtherfiSwapEncoder::new(executor_address, self.chain, config)?))
219 }
220 pls if pls == PRICE_LEVEL_STREAM_KEY ||
227 pls.starts_with(PRICE_LEVEL_STREAM_PREFIX) ||
228 pls == PROPAMM_FALLBACK_KEY ||
229 pls.starts_with(PROPAMM_FALLBACK_PREFIX) =>
230 {
231 Ok(Box::new(PropAMMSwapEncoder::new(executor_address, self.chain, config)?))
232 }
233 _ => Err(EncodingError::FatalError(format!(
234 "Unknown protocol system: {}",
235 protocol_system
236 ))),
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
250 fn test_price_level_stream_protocols_route_to_generic_encoder() {
251 let executors = std::fs::read_to_string("config/test_executor_addresses.json").unwrap();
252 let registry = SwapEncoderRegistry::new(Chain::Ethereum)
253 .add_default_encoders(Some(executors))
254 .unwrap();
255
256 for protocol in [
257 PRICE_LEVEL_STREAM_KEY,
258 "pricelevelstream:fermiswap",
259 "pricelevelstream:kipseli",
260 "pricelevelstream:0x2222222222222222222222222222222222222222",
261 ] {
262 assert!(registry.get_encoder(protocol).is_some(), "no encoder resolved for {protocol}");
263 }
264 assert!(registry
266 .get_encoder("unknown_protocol")
267 .is_none());
268 }
269
270 #[test]
273 fn test_propamm_fallback_protocol_resolution() {
274 let executors = std::fs::read_to_string("config/test_executor_addresses.json").unwrap();
275 let registry = SwapEncoderRegistry::new(Chain::Ethereum)
276 .add_default_encoders(Some(executors))
277 .unwrap();
278
279 for protocol in [
280 PROPAMM_FALLBACK_KEY,
281 "propammfallback:fermiswap",
282 "propammfallback:0x5979458912f80b96d30d4220af8e2e4925a33320",
283 ] {
284 assert!(registry.get_encoder(protocol).is_some(), "no encoder resolved for {protocol}");
285 }
286
287 let direct = registry
288 .get_encoder("pricelevelstream:fermiswap")
289 .unwrap()
290 .executor_address()
291 .clone();
292 let via_router = registry
293 .get_encoder("propammfallback:fermiswap")
294 .unwrap()
295 .executor_address()
296 .clone();
297 assert_ne!(direct, via_router);
298 }
299
300 #[test]
301 fn test_default_encoders_build_for_every_configured_chain() {
302 let chains = [
303 Chain::Ethereum,
304 Chain::Base,
305 Chain::Unichain,
306 Chain::Arbitrum,
307 Chain::Bsc,
308 Chain::Polygon,
309 Chain::Plasma,
310 Chain::Robinhood,
311 ];
312 for chain in chains {
313 let registry = SwapEncoderRegistry::new_with_defaults(chain).unwrap_or_else(|e| {
314 panic!("default encoders failed to build for chain {chain}: {e}")
315 });
316 assert!(
317 registry
318 .get_encoder("uniswap_v3")
319 .is_some(),
320 "chain {chain} is missing the uniswap_v3 encoder"
321 );
322 }
323 }
324}