use alloy::primitives::{Sign, I256, U256};
use num_bigint::BigUint;
use tycho_common::{
simulation::{errors::SimulationError, protocol_sim::Price},
Bytes,
};
use crate::evm::protocol::{
u256_num::u256_to_biguint,
utils::uniswap::{sqrt_price_math::get_sqrt_price_limit, SwapResults},
};
const U160_MAX: U256 = U256::from_limbs([u64::MAX, u64::MAX, u64::MAX >> 32, 0]);
#[allow(clippy::too_many_arguments)]
pub fn clmm_swap_to_price<F>(
sqrt_price: U256,
token_in: &Bytes,
token_out: &Bytes,
target_price: &Price,
fee_pips: u32,
amount_sign: Sign,
swap_fn: F,
) -> Result<(BigUint, BigUint, SwapResults), SimulationError>
where
F: FnOnce(bool, I256, U256) -> Result<SwapResults, SimulationError>,
{
let zero_for_one = token_in < token_out;
let sqrt_price_limit =
get_sqrt_price_limit(token_in, token_out, target_price, U256::from(fee_pips))?;
if zero_for_one && sqrt_price_limit >= sqrt_price {
return Err(SimulationError::InvalidInput(
"Target price is unreachable (already below current spot price)".to_string(),
None,
));
}
if !zero_for_one && sqrt_price_limit <= sqrt_price {
return Err(SimulationError::InvalidInput(
"Target price is unreachable (already below current spot price)".to_string(),
None,
));
}
let amount_specified =
I256::checked_from_sign_and_abs(amount_sign, U160_MAX).ok_or_else(|| {
SimulationError::InvalidInput("I256 overflow: U160_MAX".to_string(), None)
})?;
let result = swap_fn(zero_for_one, amount_specified, sqrt_price_limit)?;
let amount_in = (result.amount_specified - result.amount_remaining)
.abs()
.into_raw();
if amount_in == U256::ZERO {
return Ok((BigUint::ZERO, BigUint::ZERO, SwapResults::default()));
}
let amount_out = result
.amount_calculated
.abs()
.into_raw();
Ok((u256_to_biguint(amount_in), u256_to_biguint(amount_out), result))
}