Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/client/trades.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use alloy::primitives::{Address, B256, Bytes, I256, U256};
use alloy::sol_types::SolEvent;

use crate::constants::{MIN_OPENING_MARGIN, TICK_SPACING};
use crate::constants::{MAX_TICK, MIN_OPENING_MARGIN, MIN_TICK, TICK_SPACING};
use crate::contracts::{IERC20, Perp};
use crate::convert::{scale_from_6dec, scale_to_6dec};
use crate::errors::{ContractError, Result, ValidationError};
Expand Down Expand Up @@ -161,7 +161,7 @@ impl PerpClient {
let tick_lower = align_tick_down(price_to_tick(params.price_lower)?, TICK_SPACING);
let tick_upper = align_tick_up(price_to_tick(params.price_upper)?, TICK_SPACING);

if tick_lower >= tick_upper {
if tick_lower < MIN_TICK || tick_upper > MAX_TICK || tick_lower >= tick_upper {
return Err(ValidationError::InvalidTickRange {
lower: tick_lower,
upper: tick_upper,
Expand Down
20 changes: 10 additions & 10 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ pub const MIN_OPENING_MARGIN: u32 = 5_000_000;
/// Funding interval in seconds (1 day).
pub const INTERVAL: u64 = 86_400;

/// sqrt(0.001) * 2^96 — the minimum allowed sqrtPriceX96.
pub const MIN_SQRT_PRICE_X96: U256 = uint!(2505414483750479311864138016_U256);
/// sqrt(1e-6) * 2^96 — the minimum starting sqrtPriceX96 accepted by the factory.
pub const MIN_SQRT_PRICE_X96: U256 = uint!(79228162514264337593543951_U256);

/// sqrt(1000) * 2^96 — the maximum allowed sqrtPriceX96.
pub const MAX_SQRT_PRICE_X96: U256 = uint!(2505414483750479311864138015696_U256);
/// sqrt(1e6) * 2^96 — the maximum starting sqrtPriceX96 accepted by the factory.
pub const MAX_SQRT_PRICE_X96: U256 = uint!(79228162514264337593543950336000_U256);

/// Minimum tick (~= TickMath.getTickAtSqrtPrice(MIN_SQRT_PRICE_X96)).
pub const MIN_TICK: i32 = -69_090;
/// Minimum allowed maker tick, slightly below the factory's minimum starting price.
pub const MIN_TICK: i32 = -138_180;

/// Maximum tick (~= TickMath.getTickAtSqrtPrice(MAX_SQRT_PRICE_X96)).
pub const MAX_TICK: i32 = 69_090;
/// Maximum allowed maker tick, slightly above the factory's maximum starting price.
pub const MAX_TICK: i32 = 138_180;

/// Total supply of the internal accounting token: type(uint120).max.
pub const ACCOUNTING_TOKEN_SUPPLY: U256 = U256::from_limbs([u64::MAX, u64::MAX >> 8, 0, 0]); // 2^120 - 1
Expand Down Expand Up @@ -125,13 +125,13 @@ mod tests {

#[test]
fn min_sqrt_price_x96_matches_contract() {
let expected = U256::from_str_radix("2505414483750479311864138016", 10).unwrap();
let expected = U256::from_str_radix("79228162514264337593543951", 10).unwrap();
assert_eq!(MIN_SQRT_PRICE_X96, expected);
}

#[test]
fn max_sqrt_price_x96_matches_contract() {
let expected = U256::from_str_radix("2505414483750479311864138015696", 10).unwrap();
let expected = U256::from_str_radix("79228162514264337593543950336000", 10).unwrap();
assert_eq!(MAX_SQRT_PRICE_X96, expected);
}
}
30 changes: 10 additions & 20 deletions src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ pub fn sqrt_price_x96_to_price(sqrt_price_x96: U256) -> Result<f64, ValidationEr
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::{MAX_SQRT_PRICE_X96, MIN_SQRT_PRICE_X96, Q96_PRECISION};
use crate::constants::{MAX_SQRT_PRICE_X96, Q96_PRECISION};

// ── scale_to_6dec ──────────────────────────────────────────────

Expand Down Expand Up @@ -587,15 +587,15 @@ mod tests {

#[test]
fn price_very_small_works() {
// 0.001 is at the protocol minimum
let result = price_to_sqrt_price_x96(0.001);
// 1e-6 is the protocol's minimum starting price.
let result = price_to_sqrt_price_x96(1e-6);
assert!(result.is_ok());
}

#[test]
fn price_1000_works() {
// 1000 is at the protocol maximum
let result = price_to_sqrt_price_x96(1000.0);
fn price_1e6_works() {
// 1e6 is the protocol's maximum starting price.
let result = price_to_sqrt_price_x96(1e6);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
assert!(result.is_ok());
}

Expand Down Expand Up @@ -624,23 +624,13 @@ mod tests {
assert!(sqrt_price_x96_to_price(U256::ZERO).is_err());
}

#[test]
fn sqrt_price_x96_protocol_min() {
// MIN_SQRT_PRICE_X96 corresponds to price ≈ 0.001
let price = sqrt_price_x96_to_price(MIN_SQRT_PRICE_X96).unwrap();
assert!(
(price - 0.001).abs() < Q96_PRECISION,
"MIN_SQRT_PRICE_X96 gave price={price}, expected ≈0.001"
);
}

#[test]
fn sqrt_price_x96_protocol_max() {
// MAX_SQRT_PRICE_X96 corresponds to price ≈ 1000
// MAX_SQRT_PRICE_X96 corresponds to price ≈ 1e6.
let price = sqrt_price_x96_to_price(MAX_SQRT_PRICE_X96).unwrap();
assert!(
(price - 1000.0).abs() < Q96_PRECISION,
"MAX_SQRT_PRICE_X96 gave price={price}, expected ≈1000"
(price - 1e6).abs() < Q96_PRECISION,
"MAX_SQRT_PRICE_X96 gave price={price}, expected ≈1e6"
);
}

Expand All @@ -650,7 +640,7 @@ mod tests {
fn price_sqrt_price_x96_roundtrip() {
// Test a range of prices. The 6-decimal intermediate means we
// lose precision at around 1e-6 relative error.
for &price in &[0.01, 0.1, 0.5, 1.0, 2.0, 10.0, 100.0, 500.0, 999.0] {
for &price in &[0.01, 0.1, 0.5, 1.0, 2.0, 10.0, 100.0, 500.0, 1e6] {
let sqrt_px96 = price_to_sqrt_price_x96(price).unwrap();
let recovered = sqrt_price_x96_to_price(sqrt_px96).unwrap();
let rel_error = (recovered - price).abs() / price;
Expand Down
15 changes: 10 additions & 5 deletions src/math/tick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,14 +504,19 @@ mod tests {

#[test]
fn protocol_min_max_ticks_produce_valid_prices() {
// PerpCity uses ±69090 as its bounds.
// PerpCity uses ±138180 as its maker bounds, slightly wider than
// the factory's 1e-6..=1e6 starting-price range.
let min_price = tick_to_price(constants::MIN_TICK).unwrap();
let max_price = tick_to_price(constants::MAX_TICK).unwrap();
assert!(min_price > 0.0);
assert!(max_price > min_price);
// MIN_TICK ≈ -69090 → price ≈ 0.001
assert!((min_price - 0.001).abs() < 0.0005, "min_price={min_price}");
// MAX_TICK ≈ 69090 → price ≈ 1000
assert!((max_price - 1000.0).abs() < 1.0, "max_price={max_price}");
assert!(
(min_price - 1e-6).abs() / 1e-6 < 0.01,
"min_price={min_price}"
);
assert!(
(max_price - 1e6).abs() / 1e6 < 0.01,
"max_price={max_price}"
);
}
}
Loading