Skip to main content

perpcity_sdk/
constants.rs

1//! Protocol constants from `perpcity-contracts/src/libraries/Constants.sol`.
2//!
3//! All values are exact mirrors of the on-chain constants. Scaling factors
4//! use the same names as the Solidity source to eliminate any ambiguity.
5
6use alloy::primitives::{Address, U256, address, uint};
7
8/// Multicall3 contract address — identical on all EVM chains.
9///
10/// See <https://www.multicall3.com> for deployment details.
11pub const MULTICALL3: Address = address!("cA11bde05977b3631167028862bE2a173976CA11");
12
13/// 2^96 as `U256` — the fixed-point denominator for sqrtPriceX96 values.
14pub const Q96: U256 = U256::from_limbs([0, 0x1_0000_0000, 0, 0]);
15
16/// 2^96 as `u128` for intermediate math that doesn't need full U256.
17pub const Q96_U128: u128 = 1_u128 << 96;
18
19/// 10^6 — the scaling factor for USDC amounts, margin ratios, and fees.
20pub const SCALE_1E6: u32 = 1_000_000;
21
22/// 0.5 scaled by 1e6 (i.e. 500_000).
23pub const ONE_HALF: u32 = 500_000;
24
25/// 10^18 — WAD scaling factor used in some internal math.
26pub const WAD: U256 = U256::from_limbs([1_000_000_000_000_000_000, 0, 0, 0]);
27
28/// 1% scaled by WAD (10^16).
29pub const WAD_ONE_PERCENT: U256 = U256::from_limbs([10_000_000_000_000_000, 0, 0, 0]);
30
31/// Tick spacing for all PerpCity pools.
32pub const TICK_SPACING: i32 = 30;
33
34/// Time-weighted average price window in seconds (1 hour).
35pub const TWAVG_WINDOW: u32 = 3600;
36
37/// Minimum margin required to open a position: 5 USDC (5 * 10^6).
38pub const MIN_OPENING_MARGIN: u32 = 5_000_000;
39
40/// Funding interval in seconds (1 day).
41pub const INTERVAL: u64 = 86_400;
42
43/// sqrt(0.001) * 2^96 — the minimum allowed sqrtPriceX96.
44pub const MIN_SQRT_PRICE_X96: U256 = uint!(2505414483750479311864138016_U256);
45
46/// sqrt(1000) * 2^96 — the maximum allowed sqrtPriceX96.
47pub const MAX_SQRT_PRICE_X96: U256 = uint!(2505414483750479311864138015696_U256);
48
49/// Minimum tick (~= TickMath.getTickAtSqrtPrice(MIN_SQRT_PRICE_X96)).
50pub const MIN_TICK: i32 = -69_090;
51
52/// Maximum tick (~= TickMath.getTickAtSqrtPrice(MAX_SQRT_PRICE_X96)).
53pub const MAX_TICK: i32 = 69_090;
54
55/// Total supply of the internal accounting token: type(uint120).max.
56pub const ACCOUNTING_TOKEN_SUPPLY: U256 = U256::from_limbs([u64::MAX, u64::MAX >> 8, 0, 0]); // 2^120 - 1
57
58/// Maximum protocol fee: 5% scaled by 1e6 (50_000).
59pub const MAX_PROTOCOL_FEE: u32 = 50_000;
60
61/// Maximum absolute error when decoding a Q96 fixed-point value to f64.
62///
63/// The conversion `(value * 1e6) / Q96` uses integer division, which
64/// truncates the remainder. The truncation loses at most 1 unit in the
65/// intermediate integer, mapping to `1 / 1e6 = 0.000001` in the final
66/// f64. This bound holds regardless of price magnitude.
67pub const Q96_PRECISION: f64 = 0.000001;
68
69/// ERC721 name for PerpCity position NFTs.
70pub const ERC721_NAME: &str = "Perp City Positions";
71
72/// ERC721 symbol for PerpCity position NFTs.
73pub const ERC721_SYMBOL: &str = "PERPCITY";
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    // Verify hand-crafted `from_limbs` constants match computed values.
80
81    #[test]
82    fn q96_equals_two_pow_96() {
83        assert_eq!(Q96, U256::from(1u64) << 96);
84    }
85
86    #[test]
87    fn q96_u128_equals_two_pow_96() {
88        assert_eq!(Q96_U128, 79_228_162_514_264_337_593_543_950_336);
89    }
90
91    #[test]
92    fn one_half_is_half_of_scale() {
93        assert_eq!(ONE_HALF, SCALE_1E6 / 2);
94    }
95
96    #[test]
97    fn wad_value() {
98        assert_eq!(WAD, U256::from(10u64).pow(U256::from(18)));
99    }
100
101    #[test]
102    fn wad_one_percent_value() {
103        assert_eq!(WAD_ONE_PERCENT, U256::from(10u64).pow(U256::from(16)));
104    }
105
106    #[test]
107    fn tick_range_symmetric() {
108        assert_eq!(MIN_TICK, -MAX_TICK);
109    }
110
111    #[test]
112    fn accounting_token_supply_is_uint120_max() {
113        assert_eq!(
114            ACCOUNTING_TOKEN_SUPPLY,
115            (U256::from(1u64) << 120) - U256::from(1u64)
116        );
117    }
118
119    #[test]
120    fn min_sqrt_price_less_than_max() {
121        assert!(MIN_SQRT_PRICE_X96 < MAX_SQRT_PRICE_X96);
122    }
123
124    // Verify uint! macro values match the Constants.sol source strings.
125
126    #[test]
127    fn min_sqrt_price_x96_matches_contract() {
128        let expected = U256::from_str_radix("2505414483750479311864138016", 10).unwrap();
129        assert_eq!(MIN_SQRT_PRICE_X96, expected);
130    }
131
132    #[test]
133    fn max_sqrt_price_x96_matches_contract() {
134        let expected = U256::from_str_radix("2505414483750479311864138015696", 10).unwrap();
135        assert_eq!(MAX_SQRT_PRICE_X96, expected);
136    }
137}