use alloy::primitives::{U256, U512};
use num_bigint::BigUint;
use num_traits::Zero;
use tycho_client::feed::synchronizer::ComponentWithState;
use tycho_common::{
dto::ProtocolStateDelta,
models::token::Token,
simulation::{
errors::{SimulationError, TransitionError},
protocol_sim::Price,
},
Bytes,
};
use super::reserve_price::spot_price_from_reserves;
use crate::{
evm::protocol::{
safe_math::{safe_add_u256, safe_div_u256, safe_mul_u256, safe_sub_u256, sqrt_u512},
u256_num::{biguint_to_u256, u256_to_biguint},
utils::solidity_math::mul_div,
},
protocol::errors::InvalidSnapshotError,
};
pub fn cpmm_try_from_with_header(
snapshot: ComponentWithState,
) -> Result<(U256, U256), InvalidSnapshotError> {
let reserve0 = U256::from_be_slice(
snapshot
.state
.attributes
.get("reserve0")
.ok_or(InvalidSnapshotError::MissingAttribute("reserve0".to_string()))?,
);
let reserve1 = U256::from_be_slice(
snapshot
.state
.attributes
.get("reserve1")
.ok_or(InvalidSnapshotError::MissingAttribute("reserve1".to_string()))?,
);
Ok((reserve0, reserve1))
}
pub fn cpmm_fee(fee_bps: u32) -> f64 {
fee_bps as f64 / 10000.0
}
pub fn cpmm_spot_price(
base: &Token,
quote: &Token,
reserve0: U256,
reserve1: U256,
) -> Result<f64, SimulationError> {
if base < quote {
spot_price_from_reserves(reserve0, reserve1, base.decimals, quote.decimals)
} else {
spot_price_from_reserves(reserve1, reserve0, base.decimals, quote.decimals)
}
}
pub fn cpmm_get_amount_out(
amount_in: U256,
reserve_in: U256,
reserve_out: U256,
fee: ProtocolFee,
) -> Result<U256, SimulationError> {
if amount_in == U256::from(0u64) {
return Err(SimulationError::InvalidInput("Amount in cannot be zero".to_string(), None));
}
if reserve_in == U256::from(0u64) || reserve_out == U256::from(0u64) {
return Err(SimulationError::RecoverableError("No liquidity".to_string()));
}
let amount_in_with_fee = safe_mul_u256(amount_in, fee.numerator)?;
let numerator = safe_mul_u256(amount_in_with_fee, reserve_out)?;
let denominator = safe_add_u256(safe_mul_u256(reserve_in, fee.precision)?, amount_in_with_fee)?;
safe_div_u256(numerator, denominator)
}
pub fn cpmm_get_limits(
sell_token: Bytes,
buy_token: Bytes,
reserve0: U256,
reserve1: U256,
fee_bps: u32,
) -> Result<(BigUint, BigUint), SimulationError> {
if reserve0 == U256::from(0u64) || reserve1 == U256::from(0u64) {
return Ok((BigUint::zero(), BigUint::zero()));
}
let zero_for_one = sell_token < buy_token;
let (reserve_in, reserve_out) =
if zero_for_one { (reserve0, reserve1) } else { (reserve1, reserve0) };
let amount_in = mul_div(reserve_in, U256::from(2162u128), U256::from(1e3))?;
let fee_multiplier = U256::from(10000 - fee_bps);
let amount_in_with_fee = safe_mul_u256(amount_in, fee_multiplier)?;
let amount_out =
mul_div(reserve_out, amount_in_with_fee, safe_add_u256(reserve_in, amount_in)?)?;
Ok((u256_to_biguint(amount_in), u256_to_biguint(amount_out)))
}
pub fn cpmm_delta_transition(
delta: ProtocolStateDelta,
reserve0_mut: &mut U256,
reserve1_mut: &mut U256,
) -> Result<(), TransitionError> {
let reserve0 = U256::from_be_slice(
delta
.updated_attributes
.get("reserve0")
.ok_or(TransitionError::MissingAttribute("reserve0".to_string()))?,
);
let reserve1 = U256::from_be_slice(
delta
.updated_attributes
.get("reserve1")
.ok_or(TransitionError::MissingAttribute("reserve1".to_string()))?,
);
*reserve0_mut = reserve0;
*reserve1_mut = reserve1;
Ok(())
}
pub struct ProtocolFee {
pub numerator: U256,
pub precision: U256,
}
impl ProtocolFee {
pub fn new(numerator: U256, precision: U256) -> Self {
ProtocolFee { numerator, precision }
}
}
pub fn cpmm_swap_to_price(
reserve_in: U256,
reserve_out: U256,
target_price: &Price,
fee: ProtocolFee,
) -> Result<(BigUint, BigUint), SimulationError> {
if reserve_in == U256::ZERO || reserve_out == U256::ZERO {
return Err(SimulationError::FatalError("Reserves cannot be zero".to_string()));
}
let swap_price_num = biguint_to_u256(&target_price.denominator);
let swap_price_den = biguint_to_u256(&target_price.numerator);
let target_price_cross_mult = U512::from(swap_price_num)
.checked_mul(U512::from(reserve_out))
.and_then(|x| x.checked_mul(U512::from(fee.numerator)))
.ok_or_else(|| SimulationError::FatalError("Overflow in price check".to_string()))?;
let current_price_cross_mult = U512::from(swap_price_den)
.checked_mul(U512::from(reserve_in))
.and_then(|x| x.checked_mul(U512::from(fee.precision)))
.ok_or_else(|| SimulationError::FatalError("Overflow in price check".to_string()))?;
if target_price_cross_mult < current_price_cross_mult {
return Err(SimulationError::InvalidInput(
"Target price is unreachable (already below current spot price)".to_string(),
None,
));
}
let k = U512::from(reserve_in) * U512::from(reserve_out);
let k_times_price = k * U512::from(swap_price_num) * U512::from(fee.numerator) /
(U512::from(swap_price_den) * U512::from(fee.precision));
let x_prime_u512 = sqrt_u512(k_times_price);
let limbs = x_prime_u512.as_limbs();
let x_prime = U256::from_limbs([limbs[0], limbs[1], limbs[2], limbs[3]]);
if x_prime <= reserve_in {
return Ok((BigUint::ZERO, BigUint::ZERO));
}
let amount_in = safe_sub_u256(x_prime, reserve_in)?;
if amount_in == U256::ZERO {
return Ok((BigUint::ZERO, BigUint::ZERO));
}
let implied_amount_out = mul_div(amount_in, swap_price_den, swap_price_num)?;
Ok((u256_to_biguint(amount_in), u256_to_biguint(implied_amount_out)))
}