#[cfg(feature = "wasm")]
use equanetwork_macros::wasm_expose;
use ethnum::U256;
use super::consts::{PER_1M_DENOMINATOR, Q64_ONE};
use super::error::{
CoreError, ARITHMETIC_OVERFLOW, DIVISION_BY_ZERO, INVALID_MARGIN, INVALID_PRICE,
};
use super::U128;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct PriceBand {
pub lo_q64: u128,
pub hi_q64: u128,
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn default_price_q64() -> U128 {
U128::from(Q64_ONE)
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn ratio_to_q64(numerator: U128, denominator: U128) -> Result<U128, CoreError> {
let numerator: u128 = numerator.into();
let denominator: u128 = denominator.into();
if denominator == 0 {
return Err(DIVISION_BY_ZERO);
}
if numerator == 0 {
return Err(INVALID_PRICE);
}
let q: U256 = (U256::from(numerator) << 64) / U256::from(denominator);
let q = u128::try_from(q).map_err(|_| ARITHMETIC_OVERFLOW)?;
if q == 0 {
return Err(INVALID_PRICE);
}
Ok(U128::from(q))
}
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn combine_prices_q64(price_in_q64: U128, price_out_q64: U128) -> Result<U128, CoreError> {
ratio_to_q64(price_in_q64, price_out_q64)
}
pub fn price_band_with_margin(price_q64: U128, margin_per_1m: u32) -> Result<PriceBand, CoreError> {
let price: u128 = price_q64.into();
if price == 0 {
return Err(INVALID_PRICE);
}
if margin_per_1m > PER_1M_DENOMINATOR as u32 {
return Err(INVALID_MARGIN);
}
let denom = U256::from(PER_1M_DENOMINATOR as u128);
let p = U256::from(price);
let lo_f = U256::from(PER_1M_DENOMINATOR.saturating_sub(margin_per_1m as u64) as u128);
let hi_f = U256::from(PER_1M_DENOMINATOR.saturating_add(margin_per_1m as u64) as u128);
let lo = u128::try_from((p * lo_f) / denom).map_err(|_| ARITHMETIC_OVERFLOW)?;
let hi = u128::try_from((p * hi_f) / denom).map_err(|_| ARITHMETIC_OVERFLOW)?;
if lo == 0 {
return Err(INVALID_PRICE);
}
Ok(PriceBand {
lo_q64: lo,
hi_q64: hi,
})
}
pub fn combine_price_band(
in_lo_q64: U128,
in_hi_q64: U128,
out_lo_q64: U128,
out_hi_q64: U128,
) -> Result<PriceBand, CoreError> {
let lo: u128 = combine_prices_q64(in_lo_q64, out_hi_q64)?.into();
let hi: u128 = combine_prices_q64(in_hi_q64, out_lo_q64)?.into();
if lo == 0 || hi < lo {
return Err(INVALID_PRICE);
}
Ok(PriceBand {
lo_q64: lo,
hi_q64: hi,
})
}
pub fn combine_feed_band(
in_mid_q64: U128,
in_conf_q64: U128,
out_mid_q64: U128,
out_conf_q64: U128,
) -> Result<PriceBand, CoreError> {
let in_mid: u128 = in_mid_q64.into();
let in_conf: u128 = in_conf_q64.into();
let out_mid: u128 = out_mid_q64.into();
let out_conf: u128 = out_conf_q64.into();
let in_lo = in_mid.saturating_sub(in_conf);
let in_hi = in_mid.saturating_add(in_conf);
let out_lo = out_mid.saturating_sub(out_conf);
let out_hi = out_mid.saturating_add(out_conf);
if in_lo == 0 || out_lo == 0 {
return Err(INVALID_PRICE);
}
combine_price_band(
U128::from(in_lo),
U128::from(in_hi),
U128::from(out_lo),
U128::from(out_hi),
)
}
fn ratio_leq(num_a: u128, den_a: u128, num_b: u128, den_b: u128) -> bool {
U256::from(num_a) * U256::from(den_b) <= U256::from(den_a) * U256::from(num_b)
}
#[allow(clippy::too_many_arguments)]
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn assert_synthetic_oracle_within_execution(
source_in_q64: U128,
source_out_q64: U128,
feed_in_mid_q64: U128,
feed_in_conf_q64: U128,
feed_out_mid_q64: U128,
feed_out_conf_q64: U128,
margin_in_per_1m: u32,
margin_out_per_1m: u32,
) -> Result<(), CoreError> {
let source_in: u128 = source_in_q64.into();
let source_out: u128 = source_out_q64.into();
let feed_in_mid: u128 = feed_in_mid_q64.into();
let feed_in_conf: u128 = feed_in_conf_q64.into();
let feed_out_mid: u128 = feed_out_mid_q64.into();
let feed_out_conf: u128 = feed_out_conf_q64.into();
if source_in == 0 || source_out == 0 {
return Err(INVALID_PRICE);
}
let margin_per_1m = core::cmp::min(margin_in_per_1m, margin_out_per_1m);
let source_band = price_band_with_margin(U128::from(source_in), margin_per_1m)?;
let feed_in_lo = feed_in_mid.saturating_sub(feed_in_conf);
let feed_in_hi = feed_in_mid.saturating_add(feed_in_conf);
let feed_out_lo = feed_out_mid.saturating_sub(feed_out_conf);
let feed_out_hi = feed_out_mid.saturating_add(feed_out_conf);
if feed_in_lo == 0 || feed_out_lo == 0 {
return Err(INVALID_PRICE);
}
if !ratio_leq(source_band.lo_q64, source_out, feed_in_lo, feed_out_hi)
|| !ratio_leq(feed_in_hi, feed_out_lo, source_band.hi_q64, source_out)
{
return Err(INVALID_MARGIN);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn combine_prices_cancels_quote_currency() {
let exec: u128 = combine_prices_q64(U128::from(Q64_ONE * 2), U128::from(Q64_ONE))
.unwrap()
.into();
let oracle: u128 = combine_prices_q64(U128::from(Q64_ONE * 200), U128::from(Q64_ONE * 100))
.unwrap()
.into();
assert_eq!(exec, oracle);
assert_eq!(exec, Q64_ONE * 2);
}
#[test]
fn ratio_to_q64_from_integer_amounts() {
let price: u128 = ratio_to_q64(U128::from(2u128), U128::from(1u128))
.unwrap()
.into();
assert_eq!(price, Q64_ONE * 2);
}
#[test]
fn synthetic_oracle_matches_execution() {
assert!(assert_synthetic_oracle_within_execution(
U128::from(Q64_ONE),
U128::from(Q64_ONE),
U128::from(Q64_ONE),
U128::from(0u128),
U128::from(Q64_ONE),
U128::from(0u128),
5_000,
5_000
)
.is_ok());
let near = Q64_ONE + Q64_ONE / 25;
assert!(assert_synthetic_oracle_within_execution(
U128::from(Q64_ONE),
U128::from(Q64_ONE),
U128::from(near),
U128::from(0u128),
U128::from(Q64_ONE),
U128::from(0u128),
50_000,
50_000
)
.is_ok());
let far = Q64_ONE + Q64_ONE / 10;
assert!(assert_synthetic_oracle_within_execution(
U128::from(Q64_ONE),
U128::from(Q64_ONE),
U128::from(far),
U128::from(0u128),
U128::from(Q64_ONE),
U128::from(0u128),
50_000,
50_000
)
.is_err());
let conf = Q64_ONE / 50;
assert!(assert_synthetic_oracle_within_execution(
U128::from(Q64_ONE),
U128::from(Q64_ONE),
U128::from(Q64_ONE),
U128::from(conf),
U128::from(Q64_ONE),
U128::from(0u128),
5_000,
5_000
)
.is_err());
}
#[test]
fn mixed_quote_currencies_cancel_in_combined_ratio() {
let source_in = Q64_ONE * 2;
let feed_in = Q64_ONE * 200;
let feed_out = Q64_ONE * 100;
assert!(assert_synthetic_oracle_within_execution(
U128::from(source_in),
U128::from(Q64_ONE),
U128::from(feed_in),
U128::from(0u128),
U128::from(feed_out),
U128::from(0u128),
5_000,
5_000
)
.is_ok());
let feed_in_far = Q64_ONE * 220;
assert!(assert_synthetic_oracle_within_execution(
U128::from(source_in),
U128::from(Q64_ONE),
U128::from(feed_in_far),
U128::from(0u128),
U128::from(feed_out),
U128::from(0u128),
5_000,
5_000
)
.is_err());
}
}