use alloy::primitives::{U256, U512};
use tycho_common::simulation::errors::SimulationError;
use crate::evm::protocol::safe_math::{div_mod_u256, div_mod_u512, safe_div_u256, safe_div_u512};
pub(crate) fn mul_div_rounding_up(a: U256, b: U256, denom: U256) -> Result<U256, SimulationError> {
let product: U512 = a.widening_mul(b);
if let Some(product) = to_u256_if_fits(&product) {
let (result, rest) = div_mod_u256(product, denom)?;
return Ok(if rest.is_zero() { result } else { result + U256::from(1u64) });
}
let (mut result, rest) = div_mod_u512(product, U512::from(denom))?;
if !rest.is_zero() {
result = result
.checked_add(U512::from(1u64))
.ok_or_else(|| SimulationError::FatalError("Overflow when rounding up".to_string()))?;
}
truncate_to_u256(result)
}
pub(crate) fn mul_div(a: U256, b: U256, denom: U256) -> Result<U256, SimulationError> {
let product: U512 = a.widening_mul(b);
if let Some(product) = to_u256_if_fits(&product) {
return safe_div_u256(product, denom);
}
let result = safe_div_u512(product, U512::from(denom))?;
truncate_to_u256(result)
}
pub(crate) fn mul_div_q96_rounding_up(a: U256, b: U256) -> Result<U256, SimulationError> {
let product: U512 = a.widening_mul(b);
let limbs = product.as_limbs();
let has_remainder = limbs[0] != 0 || limbs[1] & u64::from(u32::MAX) != 0;
let mut result = product >> 96;
if has_remainder {
result += U512::from(1u64);
}
truncate_to_u256(result)
}
fn to_u256_if_fits(value: &U512) -> Option<U256> {
let limbs = value.as_limbs();
(limbs[4] | limbs[5] | limbs[6] | limbs[7] == 0)
.then(|| U256::from_limbs([limbs[0], limbs[1], limbs[2], limbs[3]]))
}
fn truncate_to_u256(value: U512) -> Result<U256, SimulationError> {
to_u256_if_fits(&value)
.ok_or_else(|| SimulationError::FatalError("Overflow: Value exceeds 256 bits".to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
fn reference_mul_div(
a: U256,
b: U256,
denom: U256,
round_up: bool,
) -> Result<U256, SimulationError> {
if denom.is_zero() {
return Err(SimulationError::FatalError("Division by zero".to_string()));
}
let (mut result, rest) = (U512::from(a) * U512::from(b)).div_rem(U512::from(denom));
if round_up && !rest.is_zero() {
result += U512::from(1u64);
}
truncate_to_u256(result)
}
#[test]
fn test_mul_div_matches_the_512_bit_reference() {
let values = [
U256::ZERO,
U256::from(1u64),
U256::from(2u64),
U256::from(3u64),
U256::from(1_000_000u64),
U256::from(u128::MAX),
U256::from(1u64) << 128,
(U256::from(1u64) << 160) - U256::from(1u64),
U256::MAX / U256::from(2u64),
U256::MAX - U256::from(1u64),
U256::MAX,
];
for a in values {
for b in values {
for denom in values {
assert_eq!(
format!("{:?}", mul_div(a, b, denom)),
format!("{:?}", reference_mul_div(a, b, denom, false)),
"mul_div({a}, {b}, {denom})"
);
assert_eq!(
format!("{:?}", mul_div_rounding_up(a, b, denom)),
format!("{:?}", reference_mul_div(a, b, denom, true)),
"mul_div_rounding_up({a}, {b}, {denom})"
);
}
}
}
}
#[test]
fn test_mul_div_rounding_up() {
let a = U256::from(23);
let b = U256::from(10);
let denom = U256::from(50);
let res = mul_div_rounding_up(a, b, denom).unwrap();
assert_eq!(res, U256::from(5));
}
#[test]
fn test_mul_div_rounding_up_overflow_u256() {
let (a, b) = (U256::MAX, U256::MAX);
let denom = U256::from(1);
let result = mul_div_rounding_up(a, b, denom);
assert!(matches!(result, Err(SimulationError::FatalError(_))));
}
#[test]
fn test_mul_div_q96_rounding_up_matches_mul_div_rounding_up() {
let q96 = U256::from(1u64) << 96;
let values = [
U256::ZERO,
U256::from(1u64),
U256::from(23u64),
q96 - U256::from(1u64),
q96,
q96 + U256::from(1u64),
U256::from(u128::MAX),
(U256::from(1u64) << 160) - U256::from(1u64),
U256::from(1u64) << 200,
U256::MAX,
];
for a in values {
for b in values {
assert_eq!(
format!("{:?}", mul_div_q96_rounding_up(a, b)),
format!("{:?}", mul_div_rounding_up(a, b, q96)),
"a = {a}, b = {b}"
);
}
}
}
#[test]
fn test_mul_div() {
let a = U256::from(23);
let b = U256::from(10);
let denom = U256::from(50);
let res = mul_div(a, b, denom).unwrap();
assert_eq!(res, U256::from(4));
}
#[test]
fn test_mul_div_overflow_u256() {
let (a, b) = (U256::MAX, U256::MAX);
let denom = U256::from(1);
let result = mul_div(a, b, denom);
assert!(matches!(result, Err(SimulationError::FatalError(_))));
}
}