feat: Support Curve ETH

Curve pools use a different address from ETH (native token)

- Pass Chain into the SwapEncoderBuilder and SwapEncoder
- Add native_token_curve_address and native_token_address to CurveSwapEncoder
- Added integration test for this curve case

--- don't change below this line ---
ENG-4306 Took 1 hour 4 minutes


Took 11 seconds
This commit is contained in:
Diana Carvalho
2025-04-07 09:20:38 +01:00
parent 1838ccf8a1
commit 913d677ffb
10 changed files with 270 additions and 71 deletions

View File

@@ -4,7 +4,7 @@ pragma solidity ^0.8.26;
import "@interfaces/IExecutor.sol"; import "@interfaces/IExecutor.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
error CurveExecutor__InvalidAddresses(); error CurveExecutor__AddressZero();
interface CryptoPool { interface CryptoPool {
// slither-disable-next-line naming-convention // slither-disable-next-line naming-convention
@@ -39,7 +39,7 @@ contract CurveExecutor is IExecutor {
constructor(address _nativeToken) { constructor(address _nativeToken) {
if (_nativeToken == address(0)) { if (_nativeToken == address(0)) {
revert CurveExecutor__InvalidAddresses(); revert CurveExecutor__AddressZero();
} }
nativeToken = _nativeToken; nativeToken = _nativeToken;
} }

View File

@@ -30,7 +30,8 @@ contract Constants is Test, BaseConstants {
address UNPAUSER = makeAddr("unpauser"); address UNPAUSER = makeAddr("unpauser");
// Assets // Assets
address ETH_ADDR = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); address ETH_ADDR_FOR_CURVE =
address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
address WETH_ADDR = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); address WETH_ADDR = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
address DAI_ADDR = address(0x6B175474E89094C44Da98b954EedeAC495271d0F); address DAI_ADDR = address(0x6B175474E89094C44Da98b954EedeAC495271d0F);
address BAL_ADDR = address(0xba100000625a3754423978a60c9317c58a424e3D); address BAL_ADDR = address(0xba100000625a3754423978a60c9317c58a424e3D);

View File

@@ -1461,4 +1461,18 @@ contract TychoRouterTest is TychoRouterTestSetup {
vm.stopPrank(); vm.stopPrank();
} }
function testCurveIntegrationStETH() public {
deal(ALICE, 1 ether);
vm.startPrank(ALICE);
// Encoded solution generated using `test_split_encoding_strategy_curve_st_eth`
(bool success,) = tychoRouterAddr.call{value: 1 ether}(
hex"0a83cb080000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe840000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000005b005900010000001d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f67022010001000000000000"
);
assertEq(IERC20(STETH_ADDR).balanceOf(ALICE), 1000754689941529590);
vm.stopPrank();
}
} }

View File

@@ -98,7 +98,7 @@ contract TychoRouterTestSetup is Test, Constants {
new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3); new UniswapV3Executor(factoryPancakeV3, initCodePancakeV3);
balancerv2Executor = new BalancerV2Executor(); balancerv2Executor = new BalancerV2Executor();
ekuboExecutor = new EkuboExecutor(ekuboCore); ekuboExecutor = new EkuboExecutor(ekuboCore);
curveExecutor = new CurveExecutor(ETH_ADDR); curveExecutor = new CurveExecutor(ETH_ADDR_FOR_CURVE);
address[] memory executors = new address[](7); address[] memory executors = new address[](7);
executors[0] = address(usv2Executor); executors[0] = address(usv2Executor);

View File

@@ -50,7 +50,7 @@ contract CurveExecutorTest is Test, Constants {
function setUp() public { function setUp() public {
uint256 forkBlock = 22031795; uint256 forkBlock = 22031795;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
curveExecutorExposed = new CurveExecutorExposed(ETH_ADDR); curveExecutorExposed = new CurveExecutorExposed(ETH_ADDR_FOR_CURVE);
metaRegistry = MetaRegistry(CURVE_META_REGISTRY); metaRegistry = MetaRegistry(CURVE_META_REGISTRY);
} }
@@ -105,7 +105,8 @@ contract CurveExecutorTest is Test, Constants {
uint256 amountIn = 1 ether; uint256 amountIn = 1 ether;
deal(address(curveExecutorExposed), amountIn); deal(address(curveExecutorExposed), amountIn);
bytes memory data = _getData(ETH_ADDR, STETH_ADDR, STETH_POOL, 1); bytes memory data =
_getData(ETH_ADDR_FOR_CURVE, STETH_ADDR, STETH_POOL, 1);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data); uint256 amountOut = curveExecutorExposed.swap(amountIn, data);
@@ -203,7 +204,8 @@ contract CurveExecutorTest is Test, Constants {
uint256 initialBalance = address(curveExecutorExposed).balance; // this address already has some ETH assigned to it uint256 initialBalance = address(curveExecutorExposed).balance; // this address already has some ETH assigned to it
deal(XYO_ADDR, address(curveExecutorExposed), amountIn); deal(XYO_ADDR, address(curveExecutorExposed), amountIn);
bytes memory data = _getData(XYO_ADDR, ETH_ADDR, ETH_XYO_POOL, 2); bytes memory data =
_getData(XYO_ADDR, ETH_ADDR_FOR_CURVE, ETH_XYO_POOL, 2);
uint256 amountOut = curveExecutorExposed.swap(amountIn, data); uint256 amountOut = curveExecutorExposed.swap(amountIn, data);

View File

@@ -1831,4 +1831,59 @@ mod tests {
let hex_calldata = encode(&calldata); let hex_calldata = encode(&calldata);
println!("{}", hex_calldata); println!("{}", hex_calldata);
} }
#[test]
fn test_split_encoding_strategy_curve_st_eth() {
// ETH ──(curve stETH pool)──> STETH
let token_in = Bytes::from("0x0000000000000000000000000000000000000000"); // ETH
let token_out = Bytes::from("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"); // STETH
let static_attributes = HashMap::from([("factory".to_string(), Bytes::from(vec![]))]);
let component = ProtocolComponent {
id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"),
protocol_system: String::from("vm:curve"),
static_attributes,
..Default::default()
};
let swap = Swap {
component,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
};
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SplitSwapStrategyEncoder::new(
eth_chain(),
swap_encoder_registry,
None,
Some(Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap()),
)
.unwrap();
let solution = Solution {
exact_out: false,
given_token: token_in,
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
checked_token: token_out,
expected_amount: None,
checked_amount: Some(BigUint::from_str("1").unwrap()),
slippage: None,
// Alice
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
swaps: vec![swap],
..Default::default()
};
let (calldata, _) = encoder
.encode_strategy(solution)
.unwrap();
let hex_calldata = encode(&calldata);
println!("{}", hex_calldata);
}
} }

View File

@@ -4,6 +4,7 @@ use crate::encoding::{
BalancerV2SwapEncoder, CurveSwapEncoder, EkuboSwapEncoder, UniswapV2SwapEncoder, BalancerV2SwapEncoder, CurveSwapEncoder, EkuboSwapEncoder, UniswapV2SwapEncoder,
UniswapV3SwapEncoder, UniswapV4SwapEncoder, UniswapV3SwapEncoder, UniswapV4SwapEncoder,
}, },
models::Chain,
swap_encoder::SwapEncoder, swap_encoder::SwapEncoder,
}; };
@@ -11,27 +12,43 @@ use crate::encoding::{
pub struct SwapEncoderBuilder { pub struct SwapEncoderBuilder {
protocol_system: String, protocol_system: String,
executor_address: String, executor_address: String,
chain: Chain,
} }
impl SwapEncoderBuilder { impl SwapEncoderBuilder {
pub fn new(protocol_system: &str, executor_address: &str) -> Self { pub fn new(protocol_system: &str, executor_address: &str, chain: Chain) -> Self {
SwapEncoderBuilder { SwapEncoderBuilder {
protocol_system: protocol_system.to_string(), protocol_system: protocol_system.to_string(),
executor_address: executor_address.to_string(), executor_address: executor_address.to_string(),
chain,
} }
} }
pub fn build(self) -> Result<Box<dyn SwapEncoder>, EncodingError> { pub fn build(self) -> Result<Box<dyn SwapEncoder>, EncodingError> {
match self.protocol_system.as_str() { match self.protocol_system.as_str() {
"uniswap_v2" => Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address))), "uniswap_v2" => {
"sushiswap_v2" => Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address))), Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address, self.chain)?))
"pancakeswap_v2" => Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address))), }
"vm:balancer_v2" => Ok(Box::new(BalancerV2SwapEncoder::new(self.executor_address))), "sushiswap_v2" => {
"uniswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))), Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address, self.chain)?))
"pancakeswap_v3" => Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address))), }
"uniswap_v4" => Ok(Box::new(UniswapV4SwapEncoder::new(self.executor_address))), "pancakeswap_v2" => {
"ekubo_v2" => Ok(Box::new(EkuboSwapEncoder::new(self.executor_address))), Ok(Box::new(UniswapV2SwapEncoder::new(self.executor_address, self.chain)?))
"vm:curve" => Ok(Box::new(CurveSwapEncoder::new(self.executor_address))), }
"vm:balancer_v2" => {
Ok(Box::new(BalancerV2SwapEncoder::new(self.executor_address, self.chain)?))
}
"uniswap_v3" => {
Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address, self.chain)?))
}
"pancakeswap_v3" => {
Ok(Box::new(UniswapV3SwapEncoder::new(self.executor_address, self.chain)?))
}
"uniswap_v4" => {
Ok(Box::new(UniswapV4SwapEncoder::new(self.executor_address, self.chain)?))
}
"ekubo_v2" => Ok(Box::new(EkuboSwapEncoder::new(self.executor_address, self.chain)?)),
"vm:curve" => Ok(Box::new(CurveSwapEncoder::new(self.executor_address, self.chain)?)),
_ => Err(EncodingError::FatalError(format!( _ => Err(EncodingError::FatalError(format!(
"Unknown protocol system: {}", "Unknown protocol system: {}",
self.protocol_system self.protocol_system

View File

@@ -38,7 +38,7 @@ impl SwapEncoderRegistry {
.get(&chain.name) .get(&chain.name)
.ok_or(EncodingError::FatalError("No executors found for chain".to_string()))?; .ok_or(EncodingError::FatalError("No executors found for chain".to_string()))?;
for (protocol, executor_address) in executors { for (protocol, executor_address) in executors {
let builder = SwapEncoderBuilder::new(protocol, executor_address); let builder = SwapEncoderBuilder::new(protocol, executor_address, chain.clone());
let encoder = builder.build()?; let encoder = builder.build()?;
encoders.insert(protocol.to_string(), encoder); encoders.insert(protocol.to_string(), encoder);
} }

View File

@@ -18,7 +18,7 @@ use crate::encoding::{
bytes_to_address, encode_input, get_runtime, get_static_attribute, pad_to_fixed_size, bytes_to_address, encode_input, get_runtime, get_static_attribute, pad_to_fixed_size,
}, },
}, },
models::{EncodingContext, Swap}, models::{Chain, EncodingContext, Swap},
swap_encoder::SwapEncoder, swap_encoder::SwapEncoder,
}; };
@@ -39,8 +39,8 @@ impl UniswapV2SwapEncoder {
} }
impl SwapEncoder for UniswapV2SwapEncoder { impl SwapEncoder for UniswapV2SwapEncoder {
fn new(executor_address: String) -> Self { fn new(executor_address: String, _chain: Chain) -> Result<Self, EncodingError> {
Self { executor_address } Ok(Self { executor_address })
} }
fn encode_swap( fn encode_swap(
@@ -93,8 +93,8 @@ impl UniswapV3SwapEncoder {
} }
impl SwapEncoder for UniswapV3SwapEncoder { impl SwapEncoder for UniswapV3SwapEncoder {
fn new(executor_address: String) -> Self { fn new(executor_address: String, _chain: Chain) -> Result<Self, EncodingError> {
Self { executor_address } Ok(Self { executor_address })
} }
fn encode_swap( fn encode_swap(
@@ -151,8 +151,8 @@ impl UniswapV4SwapEncoder {
} }
impl SwapEncoder for UniswapV4SwapEncoder { impl SwapEncoder for UniswapV4SwapEncoder {
fn new(executor_address: String) -> Self { fn new(executor_address: String, _chain: Chain) -> Result<Self, EncodingError> {
Self { executor_address } Ok(Self { executor_address })
} }
fn encode_swap( fn encode_swap(
@@ -224,11 +224,11 @@ pub struct BalancerV2SwapEncoder {
} }
impl SwapEncoder for BalancerV2SwapEncoder { impl SwapEncoder for BalancerV2SwapEncoder {
fn new(executor_address: String) -> Self { fn new(executor_address: String, _chain: Chain) -> Result<Self, EncodingError> {
Self { Ok(Self {
executor_address, executor_address,
vault_address: "0xba12222222228d8ba445958a75a0704d566bf2c8".to_string(), vault_address: "0xba12222222228d8ba445958a75a0704d566bf2c8".to_string(),
} })
} }
fn encode_swap( fn encode_swap(
&self, &self,
@@ -282,8 +282,8 @@ pub struct EkuboSwapEncoder {
} }
impl SwapEncoder for EkuboSwapEncoder { impl SwapEncoder for EkuboSwapEncoder {
fn new(executor_address: String) -> Self { fn new(executor_address: String, _chain: Chain) -> Result<Self, EncodingError> {
Self { executor_address } Ok(Self { executor_address })
} }
fn encode_swap( fn encode_swap(
@@ -340,11 +340,16 @@ impl SwapEncoder for EkuboSwapEncoder {
/// ///
/// # Fields /// # Fields
/// * `executor_address` - The address of the executor contract that will perform the swap. /// * `executor_address` - The address of the executor contract that will perform the swap.
/// * `vault_address` - The address of the vault contract that will perform the swap. /// * `meta_registry_address` - The address of the Curve meta registry contract. Used to get coin
/// indexes.
/// * `native_token_curve_address` - The address used as native token in curve pools.
/// * `native_token_address` - The address of the native token.
#[derive(Clone)] #[derive(Clone)]
pub struct CurveSwapEncoder { pub struct CurveSwapEncoder {
executor_address: String, executor_address: String,
meta_registry_address: String, meta_registry_address: String,
native_token_curve_address: Address,
native_token_address: Bytes,
} }
impl CurveSwapEncoder { impl CurveSwapEncoder {
@@ -416,7 +421,8 @@ impl CurveSwapEncoder {
let (i_256, j_256, _): ResponseType = ResponseType::abi_decode(&response, true) let (i_256, j_256, _): ResponseType = ResponseType::abi_decode(&response, true)
.map_err(|_| { .map_err(|_| {
EncodingError::FatalError( EncodingError::FatalError(
"Failed to decode response for allowance".to_string(), "Failed to decode response when getting coin indexes on a curve pool"
.to_string(),
) )
})?; })?;
let i = U8::from(i_256); let i = U8::from(i_256);
@@ -432,11 +438,17 @@ impl CurveSwapEncoder {
} }
impl SwapEncoder for CurveSwapEncoder { impl SwapEncoder for CurveSwapEncoder {
fn new(executor_address: String) -> Self { fn new(executor_address: String, chain: Chain) -> Result<Self, EncodingError> {
Self { let native_token_curve_address =
Address::from_str("0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE").map_err(|_| {
EncodingError::FatalError("Invalid Curve native token address".to_string())
})?;
Ok(Self {
executor_address, executor_address,
meta_registry_address: "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC".to_string(), meta_registry_address: "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC".to_string(),
} native_token_address: chain.native_token()?,
native_token_curve_address,
})
} }
fn encode_swap( fn encode_swap(
&self, &self,
@@ -444,18 +456,31 @@ impl SwapEncoder for CurveSwapEncoder {
encoding_context: EncodingContext, encoding_context: EncodingContext,
) -> Result<Vec<u8>, EncodingError> { ) -> Result<Vec<u8>, EncodingError> {
let token_approvals_manager = ProtocolApprovalsManager::new()?; let token_approvals_manager = ProtocolApprovalsManager::new()?;
let token = bytes_to_address(&swap.token_in)?; let token_in = if swap.token_in == self.native_token_address {
self.native_token_curve_address
} else {
bytes_to_address(&swap.token_in)?
};
let token_out = if swap.token_out == self.native_token_address {
self.native_token_curve_address
} else {
bytes_to_address(&swap.token_out)?
};
let approval_needed: bool; let approval_needed: bool;
let component_address = Address::from_str(&swap.component.id) let component_address = Address::from_str(&swap.component.id)
.map_err(|_| EncodingError::FatalError("Invalid curve pool address".to_string()))?; .map_err(|_| EncodingError::FatalError("Invalid curve pool address".to_string()))?;
if let Some(router_address) = encoding_context.router_address { if let Some(router_address) = encoding_context.router_address {
if token_in != self.native_token_curve_address {
let tycho_router_address = bytes_to_address(&router_address)?; let tycho_router_address = bytes_to_address(&router_address)?;
approval_needed = token_approvals_manager.approval_needed( approval_needed = token_approvals_manager.approval_needed(
token, token_in,
tycho_router_address, tycho_router_address,
component_address, component_address,
)?; )?;
} else {
approval_needed = false;
}
} else { } else {
approval_needed = true; approval_needed = true;
} }
@@ -469,15 +494,11 @@ impl SwapEncoder for CurveSwapEncoder {
let pool_type = self.get_pool_type(&swap.component.id, factory.as_deref())?; let pool_type = self.get_pool_type(&swap.component.id, factory.as_deref())?;
let (i, j) = self.get_coin_indexes( let (i, j) = self.get_coin_indexes(component_address, token_in, token_out)?;
component_address,
bytes_to_address(&swap.token_in)?,
bytes_to_address(&swap.token_out)?,
)?;
let args = ( let args = (
bytes_to_address(&swap.token_in)?, token_in,
bytes_to_address(&swap.token_out)?, token_out,
component_address, component_address,
pool_type.to_be_bytes::<1>(), pool_type.to_be_bytes::<1>(),
i.to_be_bytes::<1>(), i.to_be_bytes::<1>(),
@@ -502,7 +523,10 @@ mod tests {
use alloy::hex::encode; use alloy::hex::encode;
use num_bigint::BigInt; use num_bigint::BigInt;
use tycho_common::{models::protocol::ProtocolComponent, Bytes}; use tycho_common::{
models::{protocol::ProtocolComponent, Chain as TychoCoreChain},
Bytes,
};
use super::*; use super::*;
@@ -528,8 +552,11 @@ mod tests {
group_token_in: token_in.clone(), group_token_in: token_in.clone(),
group_token_out: token_out.clone(), group_token_out: token_out.clone(),
}; };
let encoder = let encoder = UniswapV2SwapEncoder::new(
UniswapV2SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let encoded_swap = encoder let encoded_swap = encoder
.encode_swap(swap, encoding_context) .encode_swap(swap, encoding_context)
.unwrap(); .unwrap();
@@ -575,8 +602,11 @@ mod tests {
group_token_in: token_in.clone(), group_token_in: token_in.clone(),
group_token_out: token_out.clone(), group_token_out: token_out.clone(),
}; };
let encoder = let encoder = UniswapV3SwapEncoder::new(
UniswapV3SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let encoded_swap = encoder let encoded_swap = encoder
.encode_swap(swap, encoding_context) .encode_swap(swap, encoding_context)
.unwrap(); .unwrap();
@@ -623,8 +653,11 @@ mod tests {
group_token_in: token_in.clone(), group_token_in: token_in.clone(),
group_token_out: token_out.clone(), group_token_out: token_out.clone(),
}; };
let encoder = let encoder = BalancerV2SwapEncoder::new(
BalancerV2SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let encoded_swap = encoder let encoded_swap = encoder
.encode_swap(swap, encoding_context) .encode_swap(swap, encoding_context)
.unwrap(); .unwrap();
@@ -682,8 +715,11 @@ mod tests {
group_token_in: token_in.clone(), group_token_in: token_in.clone(),
group_token_out: token_out.clone(), group_token_out: token_out.clone(),
}; };
let encoder = let encoder = UniswapV4SwapEncoder::new(
UniswapV4SwapEncoder::new(String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a")); String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let encoded_swap = encoder let encoded_swap = encoder
.encode_swap(swap, encoding_context) .encode_swap(swap, encoding_context)
.unwrap(); .unwrap();
@@ -747,8 +783,11 @@ mod tests {
group_token_out: token_out.clone(), group_token_out: token_out.clone(),
}; };
let encoder = let encoder = UniswapV4SwapEncoder::new(
UniswapV4SwapEncoder::new(String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4")); String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let encoded_swap = encoder let encoded_swap = encoder
.encode_swap(swap, encoding_context) .encode_swap(swap, encoding_context)
.unwrap(); .unwrap();
@@ -835,8 +874,11 @@ mod tests {
split: 0f64, split: 0f64,
}; };
let encoder = let encoder = UniswapV4SwapEncoder::new(
UniswapV4SwapEncoder::new(String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a")); String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let initial_encoded_swap = encoder let initial_encoded_swap = encoder
.encode_swap(initial_swap, context.clone()) .encode_swap(initial_swap, context.clone())
.unwrap(); .unwrap();
@@ -911,7 +953,8 @@ mod tests {
router_address: Some(Bytes::default()), router_address: Some(Bytes::default()),
}; };
let encoder = EkuboSwapEncoder::new(String::default()); let encoder =
EkuboSwapEncoder::new(String::default(), TychoCoreChain::Ethereum.into()).unwrap();
let encoded_swap = encoder let encoded_swap = encoder
.encode_swap(swap, encoding_context) .encode_swap(swap, encoding_context)
@@ -939,7 +982,8 @@ mod tests {
let group_token_out = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT let group_token_out = Bytes::from("0xdAC17F958D2ee523a2206206994597C13D831ec7"); // USDT
let intermediary_token = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC let intermediary_token = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
let encoder = EkuboSwapEncoder::new(String::default()); let encoder =
EkuboSwapEncoder::new(String::default(), TychoCoreChain::Ethereum.into()).unwrap();
let encoding_context = EncodingContext { let encoding_context = EncodingContext {
receiver: RECEIVER.into(), receiver: RECEIVER.into(),
@@ -1061,7 +1105,8 @@ mod tests {
#[case] expected_i: u64, #[case] expected_i: u64,
#[case] expected_j: u64, #[case] expected_j: u64,
) { ) {
let encoder = CurveSwapEncoder::new(String::default()); let encoder =
CurveSwapEncoder::new(String::default(), TychoCoreChain::Ethereum.into()).unwrap();
let (i, j) = encoder let (i, j) = encoder
.get_coin_indexes( .get_coin_indexes(
Address::from_str(pool).unwrap(), Address::from_str(pool).unwrap(),
@@ -1100,8 +1145,11 @@ mod tests {
group_token_in: token_in.clone(), group_token_in: token_in.clone(),
group_token_out: token_out.clone(), group_token_out: token_out.clone(),
}; };
let encoder = let encoder = CurveSwapEncoder::new(
CurveSwapEncoder::new(String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f")); String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let encoded_swap = encoder let encoded_swap = encoder
.encode_swap(swap, encoding_context) .encode_swap(swap, encoding_context)
.unwrap(); .unwrap();
@@ -1155,8 +1203,11 @@ mod tests {
group_token_in: token_in.clone(), group_token_in: token_in.clone(),
group_token_out: token_out.clone(), group_token_out: token_out.clone(),
}; };
let encoder = let encoder = CurveSwapEncoder::new(
CurveSwapEncoder::new(String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f")); String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let encoded_swap = encoder let encoded_swap = encoder
.encode_swap(swap, encoding_context) .encode_swap(swap, encoding_context)
.unwrap(); .unwrap();
@@ -1182,4 +1233,62 @@ mod tests {
)) ))
); );
} }
#[test]
fn test_curve_encode_st_eth() {
// This test is for the stETH pool, which is a special case in Curve
// where the token in is ETH but not as the zero address.
let mut static_attributes: HashMap<String, Bytes> = HashMap::new();
static_attributes.insert("factory".into(), Bytes::from(vec![]));
let curve_pool = ProtocolComponent {
id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"),
protocol_system: String::from("vm:curve"),
static_attributes,
..Default::default()
};
let token_in = Bytes::from("0x0000000000000000000000000000000000000000");
let token_out = Bytes::from("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84");
let swap = Swap {
component: curve_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
};
let encoding_context = EncodingContext {
// The receiver was generated with `makeAddr("bob") using forge`
receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"),
exact_out: false,
router_address: None,
group_token_in: token_in.clone(),
group_token_out: token_out.clone(),
};
let encoder = CurveSwapEncoder::new(
String::from("0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f"),
TychoCoreChain::Ethereum.into(),
)
.unwrap();
let encoded_swap = encoder
.encode_swap(swap, encoding_context)
.unwrap();
let hex_swap = encode(&encoded_swap);
assert_eq!(
hex_swap,
String::from(concat!(
// token in
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
// token out
"ae7ab96520de3a18e5e111b5eaab095312d7fe84",
// pool address
"dc24316b9ae028f1497c275eb9192a3ea0f67022",
// pool type 1
"01",
// i index
"00",
// j index
"01",
// approval needed
"01",
))
);
}
} }

View File

@@ -1,6 +1,6 @@
use crate::encoding::{ use crate::encoding::{
errors::EncodingError, errors::EncodingError,
models::{EncodingContext, Swap}, models::{Chain, EncodingContext, Swap},
}; };
/// A trait for protocol-specific swap encoding, where each implementation should handle the /// A trait for protocol-specific swap encoding, where each implementation should handle the
@@ -10,7 +10,8 @@ pub trait SwapEncoder: Sync + Send {
/// ///
/// # Arguments /// # Arguments
/// * `executor_address` - The address of the contract that will execute the swap /// * `executor_address` - The address of the contract that will execute the swap
fn new(executor_address: String) -> Self /// * `chain` - The chain on which the swap will be executed
fn new(executor_address: String, chain: Chain) -> Result<Self, EncodingError>
where where
Self: Sized; Self: Sized;