1use alloy_primitives::{keccak256, Address, B256, U256};
12use wp_evm_base::evm::sign_extend_i24;
13use wp_evm_ramses_interfaces::periphery::nfpm::IRamsesNonfungiblePositionManager;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct RamsesPositionView {
20 pub token_id: U256,
22 pub token0: Address,
24 pub token1: Address,
26 pub tick_spacing: i32,
29 pub tick_lower: i32,
31 pub tick_upper: i32,
33 pub liquidity: u128,
35 pub fee_growth_inside_0_last_x128: U256,
37 pub fee_growth_inside_1_last_x128: U256,
39 pub tokens_owed_0_stale: u128,
41 pub tokens_owed_1_stale: u128,
43}
44
45impl RamsesPositionView {
46 pub fn from_nfpm_returns(
48 token_id: U256,
49 ret: &IRamsesNonfungiblePositionManager::positionsReturn,
50 ) -> Self {
51 Self {
52 token_id,
53 token0: ret.token0,
54 token1: ret.token1,
55 tick_spacing: sign_extend_i24(ret.tickSpacing),
56 tick_lower: sign_extend_i24(ret.tickLower),
57 tick_upper: sign_extend_i24(ret.tickUpper),
58 liquidity: ret.liquidity,
59 fee_growth_inside_0_last_x128: ret.feeGrowthInside0LastX128,
60 fee_growth_inside_1_last_x128: ret.feeGrowthInside1LastX128,
61 tokens_owed_0_stale: ret.tokensOwed0,
62 tokens_owed_1_stale: ret.tokensOwed1,
63 }
64 }
65}
66
67pub fn position_key(owner: Address, index: U256, tick_lower: i32, tick_upper: i32) -> B256 {
83 assert!(
84 (-8_388_608..=8_388_607).contains(&tick_lower),
85 "tick_lower {tick_lower} outside int24 range",
86 );
87 assert!(
88 (-8_388_608..=8_388_607).contains(&tick_upper),
89 "tick_upper {tick_upper} outside int24 range",
90 );
91
92 let mut buf = [0u8; 58];
93 buf[..20].copy_from_slice(owner.as_slice());
94 buf[20..52].copy_from_slice(&index.to_be_bytes::<32>());
95 buf[52..55].copy_from_slice(&i24_to_be_bytes(tick_lower));
96 buf[55..58].copy_from_slice(&i24_to_be_bytes(tick_upper));
97 keccak256(buf)
98}
99
100fn i24_to_be_bytes(tick: i32) -> [u8; 3] {
101 let bytes = tick.to_be_bytes();
102 [bytes[1], bytes[2], bytes[3]]
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use alloy_primitives::{address, aliases::I24, b256};
109
110 #[test]
124 fn position_key_synthetic_reference_vector() {
125 let key = position_key(
126 address!("000000000000000000000000000000000000dEaD"),
127 U256::ZERO,
128 -887_188,
129 887_188,
130 );
131 assert_eq!(key, b256!("3b5f5df2f36078cb5495dbec069ca73eed9072adad9c8b0dcaced3689b802b73"),);
132 }
133
134 #[test]
142 fn position_key_real_shadow_position_matches_pool() {
143 let nfpm = address!("12E66C8F215DdD5d48d150c8f46aD0c6fB0F4406");
144 let token_id = U256::from(1_156_688u64);
145 let key = position_key(nfpm, token_id, -887_250, 887_250);
146 assert_eq!(key, b256!("4b6f08061b30865c2b49c44f15339f787b09d20d2429d42fbe3ed5de6199776c"),);
147 }
148
149 #[test]
152 fn from_nfpm_returns_sign_extends_ticks_and_preserves_fields() {
153 let ret = IRamsesNonfungiblePositionManager::positionsReturn {
154 token0: address!("0000000000000000000000000000000000000001"),
155 token1: address!("0000000000000000000000000000000000000002"),
156 tickSpacing: I24::try_from(50i32).unwrap(),
157 tickLower: I24::try_from(-120i32).unwrap(),
158 tickUpper: I24::try_from(240i32).unwrap(),
159 liquidity: 42,
160 feeGrowthInside0LastX128: U256::from(7),
161 feeGrowthInside1LastX128: U256::from(8),
162 tokensOwed0: 9,
163 tokensOwed1: 10,
164 };
165 let view = RamsesPositionView::from_nfpm_returns(U256::from(1), &ret);
166 assert_eq!(view.token_id, U256::from(1));
167 assert_eq!(view.token0, address!("0000000000000000000000000000000000000001"));
168 assert_eq!(view.token1, address!("0000000000000000000000000000000000000002"));
169 assert_eq!(view.tick_spacing, 50);
170 assert_eq!(view.tick_lower, -120);
171 assert_eq!(view.tick_upper, 240);
172 assert_eq!(view.liquidity, 42);
173 assert_eq!(view.fee_growth_inside_0_last_x128, U256::from(7));
174 assert_eq!(view.fee_growth_inside_1_last_x128, U256::from(8));
175 assert_eq!(view.tokens_owed_0_stale, 9);
176 assert_eq!(view.tokens_owed_1_stale, 10);
177 }
178
179 #[test]
183 fn from_nfpm_returns_decodes_real_shadow_active_position() {
184 let raw = alloy_primitives::hex!(
185 "000000000000000000000000039e2fb66102314ce7b64ce5ce3e5183bc94ad38"
186 "00000000000000000000000050c42deacd8fc9773493ed674b675be577f2634b"
187 "0000000000000000000000000000000000000000000000000000000000000032"
188 "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2762e"
189 "00000000000000000000000000000000000000000000000000000000000d89d2"
190 "00000000000000000000000000000000000000000000000342c6d2155c8af6b6"
191 "00000000000000000000000000000000003c34f4fcee3e358e2c80986cddcc72"
192 "000000000000000000000000000000000000029cdd807204b5d3d79e9ac52169"
193 "0000000000000000000000000000000000000000000000000000000000000000"
194 "0000000000000000000000000000000000000000000000000000000000000000"
195 );
196 use alloy_sol_types::SolCall;
197 let ret = IRamsesNonfungiblePositionManager::positionsCall::abi_decode_returns(&raw)
198 .expect("fixture decodes");
199
200 let view = RamsesPositionView::from_nfpm_returns(U256::from(1_156_688u64), &ret);
201
202 assert_eq!(view.token_id, U256::from(1_156_688u64));
203 assert_eq!(view.token0, address!("039e2fb66102314ce7b64ce5ce3e5183bc94ad38"));
204 assert_eq!(view.token1, address!("50c42deacd8fc9773493ed674b675be577f2634b"));
205 assert_eq!(view.tick_spacing, 50);
206 assert_eq!(view.tick_lower, -887_250);
207 assert_eq!(view.tick_upper, 887_250);
208 assert_eq!(view.liquidity, 60_151_996_462_209_365_686u128);
209 assert_eq!(
210 view.fee_growth_inside_0_last_x128,
211 U256::from(312_611_906_761_373_619_763_565_392_889_236_594u128),
212 );
213 assert_eq!(
214 view.fee_growth_inside_1_last_x128,
215 U256::from(52_992_964_027_640_673_521_461_442_716_009u128),
216 );
217 assert_eq!(view.tokens_owed_0_stale, 0);
218 assert_eq!(view.tokens_owed_1_stale, 0);
219 }
220
221 #[test]
222 #[should_panic(expected = "tick_lower")]
223 fn position_key_panics_on_oversized_tick_lower() {
224 position_key(Address::ZERO, U256::ZERO, 8_388_608, 0);
225 }
226
227 #[test]
228 #[should_panic(expected = "tick_upper")]
229 fn position_key_panics_on_oversized_tick_upper() {
230 position_key(Address::ZERO, U256::ZERO, 0, -8_388_609);
231 }
232}