use std::collections::HashMap;
use alloy::sol_types::SolValue;
use tycho_common::{models::Chain, Bytes};
use crate::encoding::{
errors::EncodingError,
models::{EncodingContext, Swap},
swap_encoder::SwapEncoder,
};
#[derive(Clone)]
pub struct EtherfiSwapEncoder {
executor_address: Bytes,
eeth_address: Bytes,
weeth_address: Bytes,
eth_address: Bytes,
}
#[repr(u8)]
enum EtherfiDirection {
EethToEth = 0,
EthToEeth = 1,
EethToWeeth = 2,
WeethToEeth = 3,
}
impl SwapEncoder for EtherfiSwapEncoder {
fn new(
executor_address: Bytes,
chain: Chain,
config: Option<HashMap<String, String>>,
) -> Result<Self, EncodingError> {
let config = config
.ok_or_else(|| EncodingError::FatalError("Etherfi config is empty".to_string()))?;
let eeth_address = config
.get("eeth_address")
.map(|a| Bytes::from(a.as_str()))
.ok_or_else(|| {
EncodingError::FatalError("Missing eeth_address in etherfi config".to_string())
})?;
let weeth_address = config
.get("weeth_address")
.map(|a| Bytes::from(a.as_str()))
.ok_or_else(|| {
EncodingError::FatalError("Missing weeth_address in etherfi config".to_string())
})?;
Ok(Self {
executor_address,
eeth_address,
weeth_address,
eth_address: chain.native_token().address,
})
}
fn encode_swap(
&self,
swap: &Swap,
_encoding_context: &EncodingContext,
) -> Result<Vec<u8>, EncodingError> {
let direction = if *swap.token_in() == self.eeth_address &&
*swap.token_out() == self.eth_address
{
EtherfiDirection::EethToEth
} else if *swap.token_in() == self.eth_address && *swap.token_out() == self.eeth_address {
EtherfiDirection::EthToEeth
} else if *swap.token_in() == self.eeth_address && *swap.token_out() == self.weeth_address {
EtherfiDirection::EethToWeeth
} else if *swap.token_in() == self.weeth_address && *swap.token_out() == self.eeth_address {
EtherfiDirection::WeethToEeth
} else {
return Err(EncodingError::InvalidInput("Combination not allowed".to_owned()))
};
let args = (direction as u8).to_be_bytes();
Ok(args.abi_encode_packed())
}
fn executor_address(&self) -> &Bytes {
&self.executor_address
}
fn clone_box(&self) -> Box<dyn SwapEncoder> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use alloy::hex::encode;
use tycho_common::models::protocol::ProtocolComponent;
use super::*;
const EETH_ADDRESS: &str = "0x35fA164735182de50811E8e2E824cFb9B6118ac2";
const WEETH_ADDRESS: &str = "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee";
const REDEMPTION_MANAGER_ADDRESS: &str = "0xDadEf1fFBFeaAB4f68A9fD181395F68b4e4E7Ae0";
fn etherfi_config() -> HashMap<String, String> {
HashMap::from([
("eeth_address".to_string(), EETH_ADDRESS.to_string()),
("weeth_address".to_string(), WEETH_ADDRESS.to_string()),
("redemption_manager_address".to_string(), REDEMPTION_MANAGER_ADDRESS.to_string()),
])
}
fn encoding_context(token_in: &Bytes, token_out: &Bytes) -> EncodingContext {
EncodingContext {
router_address: None,
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
}
}
fn encoder() -> EtherfiSwapEncoder {
EtherfiSwapEncoder::new(
Bytes::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
Chain::Ethereum,
Some(etherfi_config()),
)
.unwrap()
}
#[test]
fn test_encode_etherfi_eeth_to_eth() {
let component = ProtocolComponent {
id: String::from("0x308861a430be4cce5502d0a12724771fc6daf216"),
..Default::default()
};
let token_in = Bytes::from(EETH_ADDRESS);
let token_out = Bytes::from("0x0000000000000000000000000000000000000000");
let swap = Swap::new(component, token_in.clone(), token_out.clone());
let encoded_swap = encoder()
.encode_swap(&swap, &encoding_context(&token_in, &token_out))
.unwrap();
let hex_swap = encode(&encoded_swap);
assert_eq!(hex_swap, String::from("00"));
}
#[test]
fn test_encode_etherfi_eth_to_eeth() {
let component = ProtocolComponent {
id: String::from("0x308861a430be4cce5502d0a12724771fc6daf216"),
..Default::default()
};
let token_in = Bytes::from("0x0000000000000000000000000000000000000000");
let token_out = Bytes::from(EETH_ADDRESS);
let swap = Swap::new(component, token_in.clone(), token_out.clone());
let encoded_swap = encoder()
.encode_swap(&swap, &encoding_context(&token_in, &token_out))
.unwrap();
let hex_swap = encode(&encoded_swap);
assert_eq!(hex_swap, String::from("01",));
}
#[test]
fn test_encode_etherfi_eeth_to_weeth() {
let component = ProtocolComponent {
id: String::from("0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee"),
..Default::default()
};
let token_in = Bytes::from(EETH_ADDRESS);
let token_out = Bytes::from(WEETH_ADDRESS);
let swap = Swap::new(component, token_in.clone(), token_out.clone());
let encoded_swap = encoder()
.encode_swap(&swap, &encoding_context(&token_in, &token_out))
.unwrap();
let hex_swap = encode(&encoded_swap);
assert_eq!(hex_swap, String::from("02",));
}
#[test]
fn test_encode_etherfi_weeth_to_eeth() {
let component = ProtocolComponent {
id: String::from("0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee"),
..Default::default()
};
let token_in = Bytes::from(WEETH_ADDRESS);
let token_out = Bytes::from(EETH_ADDRESS);
let swap = Swap::new(component, token_in.clone(), token_out.clone());
let encoded_swap = encoder()
.encode_swap(&swap, &encoding_context(&token_in, &token_out))
.unwrap();
let hex_swap = encode(&encoded_swap);
assert_eq!(hex_swap, String::from("03"));
}
#[test]
fn test_encode_etherfi_invalid_pair() {
let component = ProtocolComponent {
id: String::from("0x308861a430be4cce5502d0a12724771fc6daf216"),
..Default::default()
};
let token_in = Bytes::from(WEETH_ADDRESS);
let token_out = Bytes::from("0x0000000000000000000000000000000000000000");
let swap = Swap::new(component, token_in.clone(), token_out.clone());
let encoded_swap = encoder().encode_swap(&swap, &encoding_context(&token_in, &token_out));
assert!(encoded_swap.is_err());
}
}