use alloy_primitives::{address, Address, Bytes, U256};
use alloy_sol_types::{SolCall, SolValue};
use wp_evm_v4_interfaces::periphery::position_manager::{
Actions, ActionsParams, BurnPositionParams, DecreaseLiquidityParams, IPositionManager,
IncreaseLiquidityParams, MintPositionParams, SettleAllParams, SettlePairParams, SettleParams,
SwapExactInParams, SwapExactInSingleParams, SwapExactOutParams, SwapExactOutSingleParams,
SweepParams, TakeAllParams, TakePairParams, TakeParams, TakePortionParams,
};
pub const MSG_SENDER: Address = address!("0000000000000000000000000000000000000001");
pub const OPEN_DELTA: U256 = U256::ZERO;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct V4Planner {
pub(crate) actions: Vec<u8>,
pub(crate) params: Vec<Bytes>,
}
impl V4Planner {
pub fn new() -> Self {
Self::default()
}
pub fn add_increase_liquidity(&mut self, params: IncreaseLiquidityParams) -> &mut Self {
self.actions.push(Actions::INCREASE_LIQUIDITY);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_decrease_liquidity(&mut self, params: DecreaseLiquidityParams) -> &mut Self {
self.actions.push(Actions::DECREASE_LIQUIDITY);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_mint_position(&mut self, params: MintPositionParams) -> &mut Self {
self.actions.push(Actions::MINT_POSITION);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_burn_position(&mut self, params: BurnPositionParams) -> &mut Self {
self.actions.push(Actions::BURN_POSITION);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_swap_exact_in_single(&mut self, params: SwapExactInSingleParams) -> &mut Self {
self.actions.push(Actions::SWAP_EXACT_IN_SINGLE);
self.params.push(params.abi_encode().into());
self
}
pub fn add_swap_exact_in(&mut self, params: SwapExactInParams) -> &mut Self {
self.actions.push(Actions::SWAP_EXACT_IN);
self.params.push(params.abi_encode().into());
self
}
pub fn add_swap_exact_out_single(&mut self, params: SwapExactOutSingleParams) -> &mut Self {
self.actions.push(Actions::SWAP_EXACT_OUT_SINGLE);
self.params.push(params.abi_encode().into());
self
}
pub fn add_swap_exact_out(&mut self, params: SwapExactOutParams) -> &mut Self {
self.actions.push(Actions::SWAP_EXACT_OUT);
self.params.push(params.abi_encode().into());
self
}
pub fn add_settle(&mut self, params: SettleParams) -> &mut Self {
self.actions.push(Actions::SETTLE);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_settle_all(&mut self, params: SettleAllParams) -> &mut Self {
self.actions.push(Actions::SETTLE_ALL);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_settle_pair(&mut self, params: SettlePairParams) -> &mut Self {
self.actions.push(Actions::SETTLE_PAIR);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_take(&mut self, params: TakeParams) -> &mut Self {
self.actions.push(Actions::TAKE);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_take_all(&mut self, params: TakeAllParams) -> &mut Self {
self.actions.push(Actions::TAKE_ALL);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_take_portion(&mut self, params: TakePortionParams) -> &mut Self {
self.actions.push(Actions::TAKE_PORTION);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_take_pair(&mut self, params: TakePairParams) -> &mut Self {
self.actions.push(Actions::TAKE_PAIR);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_close_currency(&mut self, currency: Address) -> &mut Self {
self.actions.push(Actions::CLOSE_CURRENCY);
self.params.push(currency.abi_encode().into());
self
}
pub fn add_sweep(&mut self, params: SweepParams) -> &mut Self {
self.actions.push(Actions::SWEEP);
self.params.push(params.abi_encode_params().into());
self
}
pub fn add_unwrap(&mut self, amount: U256) -> &mut Self {
self.actions.push(Actions::UNWRAP);
self.params.push(amount.abi_encode().into());
self
}
#[must_use]
pub fn finalize(self) -> Bytes {
ActionsParams { actions: self.actions.into(), params: self.params }
.abi_encode_params()
.into()
}
}
#[must_use]
pub fn encode_modify_liquidities(unlock_data: Bytes, deadline: U256) -> Bytes {
IPositionManager::modifyLiquiditiesCall { unlockData: unlock_data, deadline }
.abi_encode()
.into()
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{
address,
aliases::{I24, U24},
hex, Address, U256,
};
use wp_evm_v4_interfaces::pool::PoolKey;
fn pool_key_native() -> PoolKey {
PoolKey {
currency0: address!("4200000000000000000000000000000000000006"),
currency1: address!("833589fcd6edb6e08f4c7c32d4f71b54bda02913"),
fee: U24::from(500u32),
tickSpacing: I24::try_from(10i32).unwrap(),
hooks: Address::ZERO,
}
}
const HOLDER: Address = address!("00000000219ab540356cBB839Cbe05303d7705Fa");
const TICK_LOWER: i32 = -887_270;
const TICK_UPPER: i32 = 887_270;
#[test]
fn golden_mint_position_then_settle_pair() {
let mut native = V4Planner::new();
native.add_mint_position(MintPositionParams {
poolKey: pool_key_native(),
tickLower: I24::try_from(TICK_LOWER).unwrap(),
tickUpper: I24::try_from(TICK_UPPER).unwrap(),
liquidity: U256::from(1_000_000u128),
amount0Max: 1_000u128,
amount1Max: 2_000u128,
owner: HOLDER,
hookData: Bytes::default(),
});
native.add_settle_pair(SettlePairParams {
currency0: address!("4200000000000000000000000000000000000006"),
currency1: address!("833589fcd6edb6e08f4c7c32d4f71b54bda02913"),
});
assert_eq!(
native.finalize(),
hex!(
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000002"
"020d000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000002"
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000200"
"00000000000000000000000000000000000000000000000000000000000001a0"
"0000000000000000000000004200000000000000000000000000000000000006"
"000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913"
"00000000000000000000000000000000000000000000000000000000000001f4"
"000000000000000000000000000000000000000000000000000000000000000a"
"0000000000000000000000000000000000000000000000000000000000000000"
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff2761a"
"00000000000000000000000000000000000000000000000000000000000d89e6"
"00000000000000000000000000000000000000000000000000000000000f4240"
"00000000000000000000000000000000000000000000000000000000000003e8"
"00000000000000000000000000000000000000000000000000000000000007d0"
"00000000000000000000000000000000219ab540356cbb839cbe05303d7705fa"
"0000000000000000000000000000000000000000000000000000000000000180"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000004200000000000000000000000000000000000006"
"000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913"
)[..],
);
}
#[test]
fn golden_burn_position_then_take_pair() {
let mut native = V4Planner::new();
native.add_burn_position(BurnPositionParams {
tokenId: U256::from(42u64),
amount0Min: 0u128,
amount1Min: 0u128,
hookData: Bytes::default(),
});
native.add_take_pair(TakePairParams {
currency0: address!("4200000000000000000000000000000000000006"),
currency1: address!("833589fcd6edb6e08f4c7c32d4f71b54bda02913"),
recipient: MSG_SENDER,
});
assert_eq!(
native.finalize(),
hex!(
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000002"
"0311000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000002"
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000100"
"00000000000000000000000000000000000000000000000000000000000000a0"
"000000000000000000000000000000000000000000000000000000000000002a"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000060"
"0000000000000000000000004200000000000000000000000000000000000006"
"000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913"
"0000000000000000000000000000000000000000000000000000000000000001"
)[..],
);
}
#[test]
fn golden_decrease_liquidity_then_take_pair() {
let mut native = V4Planner::new();
native.add_decrease_liquidity(DecreaseLiquidityParams {
tokenId: U256::from(99u64),
liquidity: U256::from(500_000u128),
amount0Min: 1u128,
amount1Min: 2u128,
hookData: Bytes::default(),
});
native.add_take_pair(TakePairParams {
currency0: address!("4200000000000000000000000000000000000006"),
currency1: address!("833589fcd6edb6e08f4c7c32d4f71b54bda02913"),
recipient: MSG_SENDER,
});
assert_eq!(
native.finalize(),
hex!(
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000002"
"0111000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000002"
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000120"
"00000000000000000000000000000000000000000000000000000000000000c0"
"0000000000000000000000000000000000000000000000000000000000000063"
"000000000000000000000000000000000000000000000000000000000007a120"
"0000000000000000000000000000000000000000000000000000000000000001"
"0000000000000000000000000000000000000000000000000000000000000002"
"00000000000000000000000000000000000000000000000000000000000000a0"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000060"
"0000000000000000000000004200000000000000000000000000000000000006"
"000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913"
"0000000000000000000000000000000000000000000000000000000000000001"
)[..],
);
}
#[test]
fn golden_swap_exact_in_single_then_settle_all_take_all() {
let mut native = V4Planner::new();
native.add_swap_exact_in_single(SwapExactInSingleParams {
poolKey: pool_key_native(),
zeroForOne: true,
amountIn: 1_000_000u128,
amountOutMinimum: 950_000u128,
hookData: Bytes::default(),
});
native.add_settle_all(SettleAllParams {
currency: address!("4200000000000000000000000000000000000006"),
maxAmount: U256::from(1_000_000u128),
});
native.add_take_all(TakeAllParams {
currency: address!("833589fcd6edb6e08f4c7c32d4f71b54bda02913"),
minAmount: U256::from(950_000u128),
});
assert_eq!(
native.finalize(),
hex!(
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000003"
"060c0f0000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000003"
"0000000000000000000000000000000000000000000000000000000000000060"
"00000000000000000000000000000000000000000000000000000000000001e0"
"0000000000000000000000000000000000000000000000000000000000000240"
"0000000000000000000000000000000000000000000000000000000000000160"
"0000000000000000000000000000000000000000000000000000000000000020"
"0000000000000000000000004200000000000000000000000000000000000006"
"000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913"
"00000000000000000000000000000000000000000000000000000000000001f4"
"000000000000000000000000000000000000000000000000000000000000000a"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000001"
"00000000000000000000000000000000000000000000000000000000000f4240"
"00000000000000000000000000000000000000000000000000000000000e7ef0"
"0000000000000000000000000000000000000000000000000000000000000120"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000004200000000000000000000000000000000000006"
"00000000000000000000000000000000000000000000000000000000000f4240"
"0000000000000000000000000000000000000000000000000000000000000040"
"000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913"
"00000000000000000000000000000000000000000000000000000000000e7ef0"
)[..],
);
}
#[test]
fn golden_encode_modify_liquidities_full_calldata() {
let mut native = V4Planner::new();
native.add_burn_position(BurnPositionParams {
tokenId: U256::from(7u64),
amount0Min: 0u128,
amount1Min: 0u128,
hookData: Bytes::default(),
});
let unlock_native = native.finalize();
let deadline = U256::from(1_800_000_000u64);
let calldata_native = encode_modify_liquidities(unlock_native, deadline);
assert_eq!(
&calldata_native[..],
&hex!(
"dd46508f00000000000000000000000000000000000000000000000000000000"
"0000004000000000000000000000000000000000000000000000000000000000"
"6b49d20000000000000000000000000000000000000000000000000000000000"
"0000018000000000000000000000000000000000000000000000000000000000"
"0000004000000000000000000000000000000000000000000000000000000000"
"0000008000000000000000000000000000000000000000000000000000000000"
"0000000103000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000100000000000000000000000000000000000000000000000000000000"
"0000002000000000000000000000000000000000000000000000000000000000"
"000000a000000000000000000000000000000000000000000000000000000000"
"0000000700000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000008000000000000000000000000000000000000000000000000000000000"
"00000000"
)[..],
);
assert_eq!(&calldata_native[..4], &hex!("dd46508f"));
}
#[test]
fn native_msg_sender_matches_periphery_sentinel() {
assert_eq!(MSG_SENDER, address!("0000000000000000000000000000000000000001"));
}
#[test]
fn golden_empty_planner_envelope() {
let native = V4Planner::new().finalize();
assert_eq!(
&native[..],
&hex!(
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000060"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000000"
)[..],
);
assert_eq!(native.len(), 128);
}
#[test]
fn golden_swap_exact_in_multi_hop() {
let path = vec![
wp_evm_v4_interfaces::periphery::position_manager::PathKey {
intermediateCurrency: address!("833589fcd6edb6e08f4c7c32d4f71b54bda02913"),
fee: U256::from(500u32),
tickSpacing: I24::try_from(10i32).unwrap(),
hooks: Address::ZERO,
hookData: Bytes::default(),
},
wp_evm_v4_interfaces::periphery::position_manager::PathKey {
intermediateCurrency: address!("aaaaaaaa17b8b6e08f4c7c32d4f71b54bda02913"),
fee: U256::from(3_000u32),
tickSpacing: I24::try_from(60i32).unwrap(),
hooks: Address::ZERO,
hookData: Bytes::default(),
},
];
let mut native = V4Planner::new();
native.add_swap_exact_in(SwapExactInParams {
currencyIn: address!("4200000000000000000000000000000000000006"),
path,
maxHopSlippage: vec![U256::from(50u64), U256::from(50u64)],
amountIn: 1_000_000u128,
amountOutMinimum: 950_000u128,
});
assert_eq!(
native.finalize(),
hex!(
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000001"
"0700000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000001"
"0000000000000000000000000000000000000000000000000000000000000020"
"0000000000000000000000000000000000000000000000000000000000000300"
"0000000000000000000000000000000000000000000000000000000000000020"
"0000000000000000000000004200000000000000000000000000000000000006"
"00000000000000000000000000000000000000000000000000000000000000a0"
"0000000000000000000000000000000000000000000000000000000000000280"
"00000000000000000000000000000000000000000000000000000000000f4240"
"00000000000000000000000000000000000000000000000000000000000e7ef0"
"0000000000000000000000000000000000000000000000000000000000000002"
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000100"
"000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913"
"00000000000000000000000000000000000000000000000000000000000001f4"
"000000000000000000000000000000000000000000000000000000000000000a"
"0000000000000000000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000000000000000a0"
"0000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000aaaaaaaa17b8b6e08f4c7c32d4f71b54bda02913"
"0000000000000000000000000000000000000000000000000000000000000bb8"
"000000000000000000000000000000000000000000000000000000000000003c"
"0000000000000000000000000000000000000000000000000000000000000000"
"00000000000000000000000000000000000000000000000000000000000000a0"
"0000000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000002"
"0000000000000000000000000000000000000000000000000000000000000032"
"0000000000000000000000000000000000000000000000000000000000000032"
)[..],
);
}
#[test]
fn golden_close_currency() {
let mut native = V4Planner::new();
native.add_close_currency(address!("4200000000000000000000000000000000000006"));
assert_eq!(
native.finalize(),
hex!(
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000001"
"1200000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000001"
"0000000000000000000000000000000000000000000000000000000000000020"
"0000000000000000000000000000000000000000000000000000000000000020"
"0000000000000000000000004200000000000000000000000000000000000006"
)[..],
);
}
#[test]
fn golden_unwrap() {
let mut native = V4Planner::new();
native.add_unwrap(U256::from(1_500_000u128));
assert_eq!(
native.finalize(),
hex!(
"0000000000000000000000000000000000000000000000000000000000000040"
"0000000000000000000000000000000000000000000000000000000000000080"
"0000000000000000000000000000000000000000000000000000000000000001"
"1600000000000000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000000001"
"0000000000000000000000000000000000000000000000000000000000000020"
"0000000000000000000000000000000000000000000000000000000000000020"
"000000000000000000000000000000000000000000000000000000000016e360"
)[..],
);
}
}