use alloy_primitives::{aliases::U96, keccak256, Address, B256, U256};
use wp_evm_base::evm::sign_extend_i24;
use wp_evm_ramses_interfaces::periphery::nfpm::IRamsesNonfungiblePositionManager;
use wp_evm_velodrome_interfaces::periphery::nfpm::IVelodromeNonfungiblePositionManager;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RamsesPositionView {
pub token_id: U256,
pub token0: Address,
pub token1: Address,
pub tick_spacing: i32,
pub tick_lower: i32,
pub tick_upper: i32,
pub liquidity: u128,
pub fee_growth_inside_0_last_x128: U256,
pub fee_growth_inside_1_last_x128: U256,
pub tokens_owed_0_stale: u128,
pub tokens_owed_1_stale: u128,
}
impl RamsesPositionView {
pub fn from_nfpm_returns(
token_id: U256,
ret: &IRamsesNonfungiblePositionManager::positionsReturn,
) -> Self {
Self {
token_id,
token0: ret.token0,
token1: ret.token1,
tick_spacing: sign_extend_i24(ret.tickSpacing),
tick_lower: sign_extend_i24(ret.tickLower),
tick_upper: sign_extend_i24(ret.tickUpper),
liquidity: ret.liquidity,
fee_growth_inside_0_last_x128: ret.feeGrowthInside0LastX128,
fee_growth_inside_1_last_x128: ret.feeGrowthInside1LastX128,
tokens_owed_0_stale: ret.tokensOwed0,
tokens_owed_1_stale: ret.tokensOwed1,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VelodromePositionRow {
pub nonce: U96,
pub operator: Address,
pub token0: Address,
pub token1: Address,
pub tick_spacing: i32,
pub tick_lower: i32,
pub tick_upper: i32,
pub liquidity: u128,
pub fee_growth_inside_0_last_x128: U256,
pub fee_growth_inside_1_last_x128: U256,
pub tokens_owed_0_stale: u128,
pub tokens_owed_1_stale: u128,
}
impl VelodromePositionRow {
pub fn from_nfpm_returns(
_token_id: U256,
ret: &IVelodromeNonfungiblePositionManager::positionsReturn,
) -> Self {
Self {
nonce: ret.nonce,
operator: ret.operator,
token0: ret.token0,
token1: ret.token1,
tick_spacing: sign_extend_i24(ret.tickSpacing),
tick_lower: sign_extend_i24(ret.tickLower),
tick_upper: sign_extend_i24(ret.tickUpper),
liquidity: ret.liquidity,
fee_growth_inside_0_last_x128: ret.feeGrowthInside0LastX128,
fee_growth_inside_1_last_x128: ret.feeGrowthInside1LastX128,
tokens_owed_0_stale: ret.tokensOwed0,
tokens_owed_1_stale: ret.tokensOwed1,
}
}
}
pub fn position_key(owner: Address, index: U256, tick_lower: i32, tick_upper: i32) -> B256 {
assert!(
(-8_388_608..=8_388_607).contains(&tick_lower),
"tick_lower {tick_lower} outside int24 range",
);
assert!(
(-8_388_608..=8_388_607).contains(&tick_upper),
"tick_upper {tick_upper} outside int24 range",
);
let mut buf = [0u8; 58];
buf[..20].copy_from_slice(owner.as_slice());
buf[20..52].copy_from_slice(&index.to_be_bytes::<32>());
buf[52..55].copy_from_slice(&i24_to_be_bytes(tick_lower));
buf[55..58].copy_from_slice(&i24_to_be_bytes(tick_upper));
keccak256(buf)
}
fn i24_to_be_bytes(tick: i32) -> [u8; 3] {
let bytes = tick.to_be_bytes();
[bytes[1], bytes[2], bytes[3]]
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{address, aliases::I24, b256};
#[test]
fn position_key_synthetic_reference_vector() {
let key = position_key(
address!("000000000000000000000000000000000000dEaD"),
U256::ZERO,
-887_188,
887_188,
);
assert_eq!(key, b256!("3b5f5df2f36078cb5495dbec069ca73eed9072adad9c8b0dcaced3689b802b73"),);
}
#[test]
fn position_key_real_shadow_position_matches_pool() {
let nfpm = address!("12E66C8F215DdD5d48d150c8f46aD0c6fB0F4406");
let token_id = U256::from(1_156_688u64);
let key = position_key(nfpm, token_id, -887_250, 887_250);
assert_eq!(key, b256!("4b6f08061b30865c2b49c44f15339f787b09d20d2429d42fbe3ed5de6199776c"),);
}
#[test]
fn from_nfpm_returns_sign_extends_ticks_and_preserves_fields() {
let ret = IRamsesNonfungiblePositionManager::positionsReturn {
token0: address!("0000000000000000000000000000000000000001"),
token1: address!("0000000000000000000000000000000000000002"),
tickSpacing: I24::try_from(50i32).unwrap(),
tickLower: I24::try_from(-120i32).unwrap(),
tickUpper: I24::try_from(240i32).unwrap(),
liquidity: 42,
feeGrowthInside0LastX128: U256::from(7),
feeGrowthInside1LastX128: U256::from(8),
tokensOwed0: 9,
tokensOwed1: 10,
};
let view = RamsesPositionView::from_nfpm_returns(U256::from(1), &ret);
assert_eq!(view.token_id, U256::from(1));
assert_eq!(view.token0, address!("0000000000000000000000000000000000000001"));
assert_eq!(view.token1, address!("0000000000000000000000000000000000000002"));
assert_eq!(view.tick_spacing, 50);
assert_eq!(view.tick_lower, -120);
assert_eq!(view.tick_upper, 240);
assert_eq!(view.liquidity, 42);
assert_eq!(view.fee_growth_inside_0_last_x128, U256::from(7));
assert_eq!(view.fee_growth_inside_1_last_x128, U256::from(8));
assert_eq!(view.tokens_owed_0_stale, 9);
assert_eq!(view.tokens_owed_1_stale, 10);
}
#[test]
fn from_nfpm_returns_decodes_real_shadow_active_position() {
let raw = alloy_primitives::hex!(
"000000000000000000000000039e2fb66102314ce7b64ce5ce3e5183bc94ad38"
"00000000000000000000000050c42deacd8fc9773493ed674b675be577f2634b"
"0000000000000000000000000000000000000000000000000000000000000032"
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2762e"
"00000000000000000000000000000000000000000000000000000000000d89d2"
"00000000000000000000000000000000000000000000000342c6d2155c8af6b6"
"00000000000000000000000000000000003c34f4fcee3e358e2c80986cddcc72"
"000000000000000000000000000000000000029cdd807204b5d3d79e9ac52169"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
);
use alloy_sol_types::SolCall;
let ret = IRamsesNonfungiblePositionManager::positionsCall::abi_decode_returns(&raw)
.expect("fixture decodes");
let view = RamsesPositionView::from_nfpm_returns(U256::from(1_156_688u64), &ret);
assert_eq!(view.token_id, U256::from(1_156_688u64));
assert_eq!(view.token0, address!("039e2fb66102314ce7b64ce5ce3e5183bc94ad38"));
assert_eq!(view.token1, address!("50c42deacd8fc9773493ed674b675be577f2634b"));
assert_eq!(view.tick_spacing, 50);
assert_eq!(view.tick_lower, -887_250);
assert_eq!(view.tick_upper, 887_250);
assert_eq!(view.liquidity, 60_151_996_462_209_365_686u128);
assert_eq!(
view.fee_growth_inside_0_last_x128,
U256::from(312_611_906_761_373_619_763_565_392_889_236_594u128),
);
assert_eq!(
view.fee_growth_inside_1_last_x128,
U256::from(52_992_964_027_640_673_521_461_442_716_009u128),
);
assert_eq!(view.tokens_owed_0_stale, 0);
assert_eq!(view.tokens_owed_1_stale, 0);
}
#[test]
#[should_panic(expected = "tick_lower")]
fn position_key_panics_on_oversized_tick_lower() {
position_key(Address::ZERO, U256::ZERO, 8_388_608, 0);
}
#[test]
#[should_panic(expected = "tick_upper")]
fn position_key_panics_on_oversized_tick_upper() {
position_key(Address::ZERO, U256::ZERO, 0, -8_388_609);
}
}