#![expect(clippy::expect_used)]
use alloy::primitives::{I256, U256};
use crate::pools::v3_state::{v3_simulate_swap, SimulateSwapError, V3PoolState, V3SwapOutcome};
use crate::pools::v4_state::{v4_simulate_swap, V4PoolState};
pub fn v2_get_amount_out(
amount_in: U256,
reserve_in: U256,
reserve_out: U256,
fee: (u64, u64),
) -> U256 {
let (gamma_num, fee_denom) = fee;
let numerator = amount_in * U256::from(gamma_num) * reserve_out;
let denominator = reserve_in * U256::from(fee_denom) + amount_in * U256::from(gamma_num);
numerator / denominator
}
pub fn cl_exact_in_output(outcome: &V3SwapOutcome, zero_for_one: bool) -> U256 {
if zero_for_one {
outcome.amount1
} else {
outcome.amount0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum OracleOutcome {
Ok(U256),
NotComputable,
MissingTickWord(i32),
}
impl OracleOutcome {
#[must_use]
pub const fn is_ok(&self) -> bool {
matches!(self, Self::Ok(_))
}
}
pub fn v3_hop_output(
state: &V3PoolState,
fee: u32,
tick_spacing: i32,
zero_for_one: bool,
amount_in: U256,
) -> OracleOutcome {
let amount_specified = I256::try_from(amount_in).expect("v3 input fits i256");
let limit = V3PoolState::default_sqrt_price_limit(zero_for_one);
match v3_simulate_swap(
state,
fee,
tick_spacing,
zero_for_one,
amount_specified,
limit,
) {
Ok(o) => OracleOutcome::Ok(cl_exact_in_output(&o, zero_for_one)),
Err(SimulateSwapError::NotComputable) => OracleOutcome::NotComputable,
Err(SimulateSwapError::MissingTickWord(w)) => OracleOutcome::MissingTickWord(w),
}
}
pub fn v4_hop_output(
state: &V4PoolState,
fee: u32,
tick_spacing: i32,
zero_for_one: bool,
amount_in: U256,
) -> OracleOutcome {
let amount_specified = I256::try_from(amount_in)
.expect("v4 input fits i256")
.checked_neg()
.expect("negate input (V4 exact-in is negative)");
let limit = V3PoolState::default_sqrt_price_limit(zero_for_one);
match v4_simulate_swap(
state,
fee,
tick_spacing,
zero_for_one,
amount_specified,
limit,
) {
Ok(o) => OracleOutcome::Ok(cl_exact_in_output(&o, zero_for_one)),
Err(SimulateSwapError::NotComputable) => OracleOutcome::NotComputable,
Err(SimulateSwapError::MissingTickWord(w)) => OracleOutcome::MissingTickWord(w),
}
}
pub fn v4_hop_output_consumed(
state: &V4PoolState,
fee: u32,
tick_spacing: i32,
zero_for_one: bool,
amount_in: U256,
) -> OracleWithConsumed {
let amount_specified = I256::try_from(amount_in)
.expect("v4 input fits i256")
.checked_neg()
.expect("negate input (V4 exact-in is negative)");
let limit = V3PoolState::default_sqrt_price_limit(zero_for_one);
match v4_simulate_swap(
state,
fee,
tick_spacing,
zero_for_one,
amount_specified,
limit,
) {
Ok(o) => OracleWithConsumed {
outcome: OracleOutcome::Ok(cl_exact_in_output(&o, zero_for_one)),
input_consumed: o.input_consumed,
},
Err(SimulateSwapError::NotComputable) => OracleWithConsumed {
outcome: OracleOutcome::NotComputable,
input_consumed: U256::ZERO,
},
Err(SimulateSwapError::MissingTickWord(w)) => OracleWithConsumed {
outcome: OracleOutcome::MissingTickWord(w),
input_consumed: U256::ZERO,
},
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OracleWithConsumed {
pub outcome: OracleOutcome,
pub input_consumed: U256,
}
pub fn display_check(outcome: &OracleOutcome, solver_out: U256) -> String {
match outcome {
OracleOutcome::Ok(o) => {
format!(
"oracle_out={o} solver_out={solver_out} -> {}",
*o == solver_out
)
}
OracleOutcome::NotComputable => "oracle=NotComputable (swap cannot fill)".to_string(),
OracleOutcome::MissingTickWord(w) => format!("oracle=MissingTickWord({w})"),
}
}