Merge branch 'refs/heads/main' into audit/dc/one-transfer-from-only

# Conflicts:
#	foundry/test/assets/calldata.txt

Took 33 minutes
This commit is contained in:
Diana Carvalho
2025-05-21 09:50:20 +01:00
7 changed files with 77 additions and 104 deletions

View File

@@ -1,3 +1,10 @@
## [0.91.0](https://github.com/propeller-heads/tycho-execution/compare/0.90.0...0.91.0) (2025-05-19)
### Features
* **curve:** Get coin indexes from static attributes ([fcaacf2](https://github.com/propeller-heads/tycho-execution/commit/fcaacf21fbf8a8bc08a395fbb10295bb00e6ab15))
## [0.90.0](https://github.com/propeller-heads/tycho-execution/compare/0.89.0...0.90.0) (2025-05-15) ## [0.90.0](https://github.com/propeller-heads/tycho-execution/compare/0.89.0...0.90.0) (2025-05-15)

2
Cargo.lock generated
View File

@@ -4469,7 +4469,7 @@ dependencies = [
[[package]] [[package]]
name = "tycho-execution" name = "tycho-execution"
version = "0.90.0" version = "0.91.0"
dependencies = [ dependencies = [
"alloy", "alloy",
"alloy-primitives", "alloy-primitives",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "tycho-execution" name = "tycho-execution"
version = "0.90.0" version = "0.91.0"
edition = "2021" edition = "2021"
description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors."
repository = "https://github.com/propeller-heads/tycho-execution" repository = "https://github.com/propeller-heads/tycho-execution"

View File

@@ -4,8 +4,7 @@
"vault_address": "0xba12222222228d8ba445958a75a0704d566bf2c8" "vault_address": "0xba12222222228d8ba445958a75a0704d566bf2c8"
}, },
"vm:curve": { "vm:curve": {
"native_token_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "native_token_address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
"meta_registry_address": "0xF98B45FA17DE75FB1aD0e7aFD971b0ca00e379fC"
} }
}, },
"base": {}, "base": {},

View File

@@ -1,4 +1,4 @@
use std::io; use std::{io, str::Utf8Error};
use thiserror::Error; use thiserror::Error;
@@ -33,3 +33,9 @@ impl From<serde_json::Error> for EncodingError {
EncodingError::FatalError(err.to_string()) EncodingError::FatalError(err.to_string())
} }
} }
impl From<Utf8Error> for EncodingError {
fn from(err: Utf8Error) -> Self {
EncodingError::FatalError(err.to_string())
}
}

View File

@@ -1573,6 +1573,11 @@ mod tests {
.to_vec(), .to_vec(),
), ),
); );
attrs.insert(
"coins".into(),
Bytes::from_str("0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307863303261616133396232323366653864306130653563346632376561643930383363373536636332225d")
.unwrap(),
);
attrs attrs
}, },
..Default::default() ..Default::default()
@@ -1737,6 +1742,11 @@ mod tests {
.to_vec(), .to_vec(),
), ),
); );
attrs.insert(
"coins".into(),
Bytes::from_str("0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307863303261616133396232323366653864306130653563346632376561643930383363373536636332225d")
.unwrap(),
);
attrs attrs
}, },
..Default::default() ..Default::default()
@@ -2662,8 +2672,9 @@ mod tests {
"0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f" "0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f"
.as_bytes() .as_bytes()
.to_vec(), .to_vec(),
), )),
)]); ("coins".to_string(), Bytes::from_str("0x5b22307863303261616133396232323366653864306130653563346632376561643930383363373536636332222c22307835356330386361353234393765326631353334623539653239313762663532346434373635323537225d").unwrap()),
]);
let component = ProtocolComponent { let component = ProtocolComponent {
id: String::from("0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71"), id: String::from("0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71"),
@@ -2726,7 +2737,8 @@ mod tests {
.as_bytes() .as_bytes()
.to_vec(), .to_vec(),
), ),
)]); ),
("coins".to_string(), Bytes::from_str("0x5b22307865656565656565656565656565656565656565656565656565656565656565656565656565656565222c22307861653761623936353230646533613138653565313131623565616162303935333132643766653834225d").unwrap()),]);
let component = ProtocolComponent { let component = ProtocolComponent {
id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"), id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"),

View File

@@ -1,22 +1,15 @@
use std::{collections::HashMap, str::FromStr}; use std::{collections::HashMap, str::FromStr};
use alloy::{ use alloy_primitives::{Address, Bytes as AlloyBytes, U8};
providers::Provider,
rpc::types::{TransactionInput, TransactionRequest},
};
use alloy_primitives::{Address, Bytes as AlloyBytes, TxKind, U256, U8};
use alloy_sol_types::SolValue; use alloy_sol_types::SolValue;
use tokio::task::block_in_place; use serde_json::from_str;
use tycho_common::Bytes; use tycho_common::Bytes;
use crate::encoding::{ use crate::encoding::{
errors::EncodingError, errors::EncodingError,
evm::{ evm::{
approvals::protocol_approvals_manager::ProtocolApprovalsManager, approvals::protocol_approvals_manager::ProtocolApprovalsManager,
utils, utils::{bytes_to_address, get_static_attribute, pad_to_fixed_size},
utils::{
bytes_to_address, encode_input, get_runtime, get_static_attribute, pad_to_fixed_size,
},
}, },
models::{Chain, EncodingContext, Swap}, models::{Chain, EncodingContext, Swap},
swap_encoder::SwapEncoder, swap_encoder::SwapEncoder,
@@ -375,10 +368,8 @@ impl SwapEncoder for EkuboSwapEncoder {
#[derive(Clone)] #[derive(Clone)]
pub struct CurveSwapEncoder { pub struct CurveSwapEncoder {
executor_address: String, executor_address: String,
meta_registry_address: String,
native_token_curve_address: String, native_token_curve_address: String,
native_token_address: Bytes, native_token_address: Bytes,
wrapped_native_token_address: Bytes,
} }
impl CurveSwapEncoder { impl CurveSwapEncoder {
@@ -416,68 +407,25 @@ impl CurveSwapEncoder {
fn get_coin_indexes( fn get_coin_indexes(
&self, &self,
pool_id: Address, swap: Swap,
token_in: Address, token_in: Address,
token_out: Address, token_out: Address,
) -> Result<(U8, U8), EncodingError> { ) -> Result<(U8, U8), EncodingError> {
let (handle, _runtime) = get_runtime()?; let coins_bytes = get_static_attribute(&swap, "coins")?;
let client = block_in_place(|| handle.block_on(utils::get_client()))?; let coins: Vec<Address> = from_str(std::str::from_utf8(&coins_bytes)?)?;
let args = (pool_id, token_in, token_out); let i = coins
let data = encode_input("get_coin_indices(address,address,address)", args.abi_encode()); .iter()
let tx = TransactionRequest { .position(|&addr| addr == token_in)
to: Some(TxKind::from(Address::from_str(&self.meta_registry_address).map_err( .ok_or(EncodingError::FatalError(format!(
|_| EncodingError::FatalError("Invalid Curve meta registry address".to_string()), "Token in address {token_in} not found in curve pool coins"
)?)), )))?;
input: TransactionInput { let j = coins
input: Some(alloy_primitives::Bytes::from(data)), .iter()
data: None, .position(|&addr| addr == token_out)
}, .ok_or(EncodingError::FatalError(format!(
..Default::default() "Token in address {token_in} not found in curve pool coins"
}; )))?;
let output = block_in_place(|| handle.block_on(async { client.call(&tx).await })); Ok((U8::from(i), U8::from(j)))
type ResponseType = (U256, U256, bool);
match output {
Ok(response) => {
let (i_256, j_256, _): ResponseType = ResponseType::abi_decode(&response, true)
.map_err(|_| {
EncodingError::FatalError(
"Failed to decode response when getting coin indexes on a curve pool"
.to_string(),
)
})?;
let i = U8::from(i_256);
let j = U8::from(j_256);
Ok((i, j))
}
Err(err) => {
// Temporary until we get the coin indexes from the indexer
// This is because some curve pools hold ETH but the coin is defined as WETH
// Our indexer reports this pool as holding ETH but then here we need to use WETH
// This is valid only for some pools, that's why we are doing the trial and error
// approach
let native_token_curve_address =
Address::from_str(&self.native_token_curve_address).map_err(|_| {
EncodingError::FatalError(
"Invalid Curve native token curve address".to_string(),
)
})?;
if token_in != native_token_curve_address && token_out != native_token_curve_address
{
Err(EncodingError::RecoverableError(format!(
"Curve meta registry call failed with error: {err}"
)))
} else {
let wrapped_token = bytes_to_address(&self.wrapped_native_token_address)?;
let (i, j) = if token_in == native_token_curve_address {
self.get_coin_indexes(pool_id, wrapped_token, token_out)?
} else {
self.get_coin_indexes(pool_id, token_in, wrapped_token)?
};
Ok((i, j))
}
}
}
} }
} }
@@ -496,17 +444,9 @@ impl SwapEncoder for CurveSwapEncoder {
"Missing native token curve address in config".to_string(), "Missing native token curve address in config".to_string(),
))? ))?
.to_string(); .to_string();
let meta_registry_address = config
.get("meta_registry_address")
.ok_or(EncodingError::FatalError(
"Missing meta registry address in config".to_string(),
))?
.to_string();
Ok(Self { Ok(Self {
executor_address, executor_address,
meta_registry_address,
native_token_address: chain.native_token()?, native_token_address: chain.native_token()?,
wrapped_native_token_address: chain.wrapped_token()?,
native_token_curve_address, native_token_curve_address,
}) })
} }
@@ -565,7 +505,7 @@ impl SwapEncoder for CurveSwapEncoder {
let pool_type = let pool_type =
self.get_pool_type(&pool_address.to_string(), &factory_address.to_string())?; self.get_pool_type(&pool_address.to_string(), &factory_address.to_string())?;
let (i, j) = self.get_coin_indexes(component_address, token_in, token_out)?; let (i, j) = self.get_coin_indexes(swap, token_in, token_out)?;
let args = ( let args = (
token_in, token_in,
@@ -1250,43 +1190,36 @@ mod tests {
#[rstest] #[rstest]
#[case( #[case(
"0x5500307Bcf134E5851FB4D7D8D1Dc556dCdB84B4", "0x5b22307838363533373733363730353435313665313730313463636465643165376438313465646339636534222c22307861353538386637636466353630383131373130613264383264336339633939373639646231646362225d",
"0xdA16Cf041E2780618c49Dbae5d734B89a6Bac9b3",
"0xdAC17F958D2ee523a2206206994597C13D831ec7",
1,
0
)]
#[case(
"0xef484de8C07B6e2d732A92B5F78e81B38f99f95E",
"0x865377367054516e17014CcdED1e7d814EDC9ce4", "0x865377367054516e17014CcdED1e7d814EDC9ce4",
"0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb", "0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb",
0, 0,
1 1
)] )]
#[case( #[case(
"0xA5407eAE9Ba41422680e2e00537571bcC53efBfD", "0x5b22307836623137353437346538393039346334346461393862393534656564656163343935323731643066222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307835376162316563323864313239373037303532646634646634313864353861326434366435663531225d",
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"0x57Ab1ec28D129707052df4dF418D58a2D46d5f51", "0x57Ab1ec28D129707052df4dF418D58a2D46d5f51",
1, 1,
3 3
)] )]
#[case( #[case(
"0xD51a44d3FaE010294C616388b506AcdA1bfAAE46", "0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307863303261616133396232323366653864306130653563346632376561643930383363373536636332225d",
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
2, 2,
1 1
)] )]
#[case( #[case(
"0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B", "0x5b22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307865656565656565656565656565656565656565656565656565656565656565656565656565656565225d",
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2, 2,
0 0
)] )]
// Pool that holds ETH but coin is WETH // Pool that holds ETH but coin is WETH
#[case( #[case(
"0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B", "0x5b22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307865656565656565656565656565656565656565656565656565656565656565656565656565656565225d",
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2, 2,
@@ -1294,19 +1227,32 @@ mod tests {
)] )]
// Pool that holds ETH but coin is WETH // Pool that holds ETH but coin is WETH
#[case( #[case(
"0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B", "0x5b22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307865656565656565656565656565656565656565656565656565656565656565656565656565656565225d",
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
0, 0,
2 2
)] )]
fn test_curve_get_coin_indexes( fn test_curve_get_coin_indexes(
#[case] pool: &str, #[case] coins: &str,
#[case] token_in: &str, #[case] token_in: &str,
#[case] token_out: &str, #[case] token_out: &str,
#[case] expected_i: u64, #[case] expected_i: u64,
#[case] expected_j: u64, #[case] expected_j: u64,
) { ) {
let mut static_attributes: HashMap<String, Bytes> = HashMap::new();
static_attributes.insert("coins".into(), Bytes::from_str(coins).unwrap());
let swap = Swap {
component: ProtocolComponent {
id: "pool-id".into(),
protocol_system: String::from("vm:curve"),
static_attributes,
..Default::default()
},
token_in: Bytes::from(token_in),
token_out: Bytes::from(token_out),
split: 0f64,
};
let encoder = CurveSwapEncoder::new( let encoder = CurveSwapEncoder::new(
String::default(), String::default(),
TychoCoreChain::Ethereum.into(), TychoCoreChain::Ethereum.into(),
@@ -1315,7 +1261,7 @@ mod tests {
.unwrap(); .unwrap();
let (i, j) = encoder let (i, j) = encoder
.get_coin_indexes( .get_coin_indexes(
Address::from_str(pool).unwrap(), swap,
Address::from_str(token_in).unwrap(), Address::from_str(token_in).unwrap(),
Address::from_str(token_out).unwrap(), Address::from_str(token_out).unwrap(),
) )
@@ -1335,6 +1281,7 @@ mod tests {
.to_vec(), .to_vec(),
), ),
); );
static_attributes.insert("coins".into(), Bytes::from_str("0x5b22307836623137353437346538393039346334346461393862393534656564656163343935323731643066222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438222c22307864616331376639353864326565353233613232303632303639393435393763313364383331656337225d").unwrap());
let curve_tri_pool = ProtocolComponent { let curve_tri_pool = ProtocolComponent {
id: String::from("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"), id: String::from("0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7"),
protocol_system: String::from("vm:curve"), protocol_system: String::from("vm:curve"),
@@ -1405,6 +1352,7 @@ mod tests {
.to_vec(), .to_vec(),
), ),
); );
static_attributes.insert("coins".into(), Bytes::from_str("0x5b22307834633965646435383532636439303566303836633735396538333833653039626666316536386233222c22307861306238363939316336323138623336633164313964346132653965623063653336303665623438225d").unwrap());
let curve_pool = ProtocolComponent { let curve_pool = ProtocolComponent {
id: String::from("0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72"), id: String::from("0x02950460E2b9529D0E00284A5fA2d7bDF3fA4d72"),
protocol_system: String::from("vm:curve"), protocol_system: String::from("vm:curve"),
@@ -1476,6 +1424,7 @@ mod tests {
.to_vec(), .to_vec(),
), ),
); );
static_attributes.insert("coins".into(), Bytes::from_str("0x5b22307865656565656565656565656565656565656565656565656565656565656565656565656565656565222c22307861653761623936353230646533613138653565313131623565616162303935333132643766653834225d").unwrap());
let curve_pool = ProtocolComponent { let curve_pool = ProtocolComponent {
id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"), id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"),
protocol_system: String::from("vm:curve"), protocol_system: String::from("vm:curve"),