use crate::{settlement_fee, tick_to_price, word_to_u128, word_to_u256, Offer, SimError, U256};
const WAD: u128 = 1_000_000_000_000_000_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum SizingError {
#[error(transparent)]
Sim(#[from] SimError),
#[error("buyer price exceeds WAD; not all buyer assets are reachable")]
PriceGreaterThanOne,
#[error("price is zero; units are unbounded")]
ZeroPrice,
#[error("offer must set exactly one non-zero max-units or max-assets cap")]
InvalidCap,
#[error("uint256 multiplication overflow")]
ArithmeticOverflow,
}
#[inline]
fn wad() -> U256 {
U256::from(WAD)
}
#[inline]
fn mul_div_down(x: U256, y: U256, d: U256) -> Result<U256, SizingError> {
Ok(x.checked_mul(y).ok_or(SizingError::ArithmeticOverflow)? / d)
}
#[inline]
fn mul_div_up(x: U256, y: U256, d: U256) -> Result<U256, SizingError> {
let product = x.checked_mul(y).ok_or(SizingError::ArithmeticOverflow)?;
if product.is_zero() {
return Ok(U256::ZERO);
}
Ok((product - U256::from(1u8)) / d + U256::from(1u8))
}
#[inline]
fn zero_floor_sub(x: U256, y: U256) -> U256 {
if x > y {
x - y
} else {
U256::ZERO
}
}
#[inline]
fn zero_floor_sub_u128(x: u128, y: u128) -> u128 {
x.saturating_sub(y)
}
fn tick_u64(offer: &Offer) -> Result<u64, SimError> {
let t = word_to_u128(&offer.tick).ok_or(SimError::TickNotU64)?;
u64::try_from(t).map_err(|_| SimError::TickNotU64)
}
fn prices(offer: &Offer, now: u64, cbps: [u16; 7]) -> Result<(U256, U256), SizingError> {
let offer_price = tick_to_price(tick_u64(offer)?)?;
let maturity = word_to_u256(&offer.market.maturity);
let ttm = zero_floor_sub(maturity, U256::from(now));
let fee = settlement_fee(cbps, ttm);
let seller_price = if offer.buy {
offer_price
.checked_sub(fee)
.ok_or(SimError::SettlementFeeExceedsPrice)?
} else {
offer_price
};
let buyer_price = seller_price + fee;
Ok((seller_price, buyer_price))
}
pub fn buyer_assets_to_units(
offer: &Offer,
target_buyer_assets: U256,
now: u64,
cbps: [u16; 7],
) -> Result<U256, SizingError> {
let (_seller_price, buyer_price) = prices(offer, now, cbps)?;
if buyer_price > wad() {
return Err(SizingError::PriceGreaterThanOne);
}
if buyer_price.is_zero() {
return Err(SizingError::ZeroPrice);
}
if offer.buy {
mul_div_up(target_buyer_assets, wad(), buyer_price)
} else {
mul_div_down(target_buyer_assets, wad(), buyer_price)
}
}
pub fn seller_assets_to_units(
offer: &Offer,
target_seller_assets: U256,
now: u64,
cbps: [u16; 7],
) -> Result<U256, SizingError> {
let (seller_price, _buyer_price) = prices(offer, now, cbps)?;
if seller_price.is_zero() {
return Err(SizingError::ZeroPrice);
}
if offer.buy {
mul_div_up(target_seller_assets, wad(), seller_price)
} else {
mul_div_down(target_seller_assets, wad(), seller_price)
}
}
pub fn consumable_units(
offer: &Offer,
consumed: u128,
now: u64,
cbps: [u16; 7],
) -> Result<U256, SizingError> {
if offer.max_units > 0 {
return Ok(U256::from(zero_floor_sub_u128(offer.max_units, consumed)));
}
let remaining_assets = U256::from(zero_floor_sub_u128(offer.max_assets, consumed));
if offer.buy {
buyer_assets_to_units(offer, remaining_assets, now, cbps)
} else {
seller_assets_to_units(offer, remaining_assets, now, cbps)
}
}
pub fn get_consumable_units(
offer: &Offer,
consumed: u128,
now: u64,
cbps: [u16; 7],
market_continuous_fee: U256,
) -> Result<U256, SizingError> {
if (offer.max_units == 0) == (offer.max_assets == 0) {
return Err(SizingError::InvalidCap);
}
let now = U256::from(now);
if now < word_to_u256(&offer.start) || now > word_to_u256(&offer.expiry) {
return Ok(U256::ZERO);
}
if word_to_u256(&offer.continuous_fee_cap) < market_continuous_fee {
return Ok(U256::ZERO);
}
if offer.max_units > 0 {
return Ok(U256::from(zero_floor_sub_u128(offer.max_units, consumed)));
}
let remaining_assets = U256::from(zero_floor_sub_u128(offer.max_assets, consumed));
let now = u64::try_from(now).expect("now originated as u64");
let (seller_price, buyer_price) = prices(offer, now, cbps)?;
if offer.buy {
if buyer_price.is_zero() {
return Ok(U256::MAX);
}
Ok(((remaining_assets + U256::from(1u64)) * wad() - U256::from(1u64)) / buyer_price)
} else {
if seller_price.is_zero() {
return Ok(U256::MAX);
}
mul_div_down(remaining_assets, wad(), seller_price)
}
}