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/// Uniswap V4 PoolManager on Base L2.
9pub const POOL_MANAGER: Address = address!("05E73354cFDd6745C338b50BcFDfA3Aa6fA03408");
10
11/// USDC token on Base L2 (6 decimals).
12pub const USDC: Address = address!("C1a5D4E99BB224713dd179eA9CA2Fa6600706210");
13
14/// 2^96 as `U256` — the fixed-point denominator for sqrtPriceX96 values.
15pub const Q96: U256 = U256::from_limbs([0, 0x1_0000_0000, 0, 0]);
16
17/// 2^96 as `u128` for intermediate math that doesn't need full U256.
18pub const Q96_U128: u128 = 1_u128 << 96;
19
20/// 10^6 — the scaling factor for USDC amounts, margin ratios, and fees.
21pub const SCALE_1E6: u32 = 1_000_000;
22
23/// 0.5 scaled by 1e6 (i.e. 500_000).
24pub const ONE_HALF: u32 = 500_000;
25
26/// 10^18 — WAD scaling factor used in some internal math.
27pub const WAD: U256 = U256::from_limbs([1_000_000_000_000_000_000, 0, 0, 0]);
28
29/// 1% scaled by WAD (10^16).
30pub const WAD_ONE_PERCENT: U256 = U256::from_limbs([10_000_000_000_000_000, 0, 0, 0]);
31
32/// Tick spacing for all PerpCity pools.
33pub const TICK_SPACING: i32 = 30;
34
35/// Time-weighted average price window in seconds (1 hour).
36pub const TWAVG_WINDOW: u32 = 3600;
37
38/// Minimum margin required to open a position: 5 USDC (5 * 10^6).
39pub const MIN_OPENING_MARGIN: u32 = 5_000_000;
40
41/// Funding interval in seconds (1 day).
42pub const INTERVAL: u64 = 86_400;
43
44/// sqrt(0.001) * 2^96 — the minimum allowed sqrtPriceX96.
45pub const MIN_SQRT_PRICE_X96: U256 = uint!(2505414483750479311864138016_U256);
46
47/// sqrt(1000) * 2^96 — the maximum allowed sqrtPriceX96.
48pub const MAX_SQRT_PRICE_X96: U256 = uint!(2505414483750479311864138015696_U256);
49
50/// Minimum tick (~= TickMath.getTickAtSqrtPrice(MIN_SQRT_PRICE_X96)).
51pub const MIN_TICK: i32 = -69_090;
52
53/// Maximum tick (~= TickMath.getTickAtSqrtPrice(MAX_SQRT_PRICE_X96)).
54pub const MAX_TICK: i32 = 69_090;
55
56/// Total supply of the internal accounting token: type(uint120).max.
57pub const ACCOUNTING_TOKEN_SUPPLY: U256 = U256::from_limbs([u64::MAX, u64::MAX >> 8, 0, 0]); // 2^120 - 1
58
59/// Maximum protocol fee: 5% scaled by 1e6 (50_000).
60pub const MAX_PROTOCOL_FEE: u32 = 50_000;
61
62/// ERC721 name for PerpCity position NFTs.
63pub const ERC721_NAME: &str = "Perp City Positions";
64
65/// ERC721 symbol for PerpCity position NFTs.
66pub const ERC721_SYMBOL: &str = "PERPCITY";
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    // Verify hand-crafted `from_limbs` constants match computed values.
73
74    #[test]
75    fn q96_equals_two_pow_96() {
76        assert_eq!(Q96, U256::from(1u64) << 96);
77    }
78
79    #[test]
80    fn q96_u128_equals_two_pow_96() {
81        assert_eq!(Q96_U128, 79_228_162_514_264_337_593_543_950_336);
82    }
83
84    #[test]
85    fn one_half_is_half_of_scale() {
86        assert_eq!(ONE_HALF, SCALE_1E6 / 2);
87    }
88
89    #[test]
90    fn wad_value() {
91        assert_eq!(WAD, U256::from(10u64).pow(U256::from(18)));
92    }
93
94    #[test]
95    fn wad_one_percent_value() {
96        assert_eq!(WAD_ONE_PERCENT, U256::from(10u64).pow(U256::from(16)));
97    }
98
99    #[test]
100    fn tick_range_symmetric() {
101        assert_eq!(MIN_TICK, -MAX_TICK);
102    }
103
104    #[test]
105    fn accounting_token_supply_is_uint120_max() {
106        assert_eq!(
107            ACCOUNTING_TOKEN_SUPPLY,
108            (U256::from(1u64) << 120) - U256::from(1u64)
109        );
110    }
111
112    #[test]
113    fn min_sqrt_price_less_than_max() {
114        assert!(MIN_SQRT_PRICE_X96 < MAX_SQRT_PRICE_X96);
115    }
116
117    // Verify uint! macro values match the Constants.sol source strings.
118
119    #[test]
120    fn min_sqrt_price_x96_matches_contract() {
121        let expected = U256::from_str_radix("2505414483750479311864138016", 10).unwrap();
122        assert_eq!(MIN_SQRT_PRICE_X96, expected);
123    }
124
125    #[test]
126    fn max_sqrt_price_x96_matches_contract() {
127        let expected = U256::from_str_radix("2505414483750479311864138015696", 10).unwrap();
128        assert_eq!(MAX_SQRT_PRICE_X96, expected);
129    }
130
131    // Verify address! macros match checksummed strings.
132
133    #[test]
134    fn usdc_address() {
135        assert_eq!(
136            USDC,
137            "0xC1a5D4E99BB224713dd179eA9CA2Fa6600706210"
138                .parse::<Address>()
139                .unwrap()
140        );
141    }
142
143    #[test]
144    fn pool_manager_address() {
145        assert_eq!(
146            POOL_MANAGER,
147            "0x05E73354cFDd6745C338b50BcFDfA3Aa6fA03408"
148                .parse::<Address>()
149                .unwrap()
150        );
151    }
152}