use alloy::primitives::{
address,
aliases::{I24, U160},
Address, Bytes, U256,
};
use anyhow::{Context, Result};
use uniswap_sdk_core::{
entities::{BaseCurrency, BaseCurrencyCore, FractionBase},
prelude::{Currency, Percent, Price},
utils::FromBig,
};
use uniswap_v3_sdk::prelude::encode_multicall;
use uniswap_v4_sdk::position_manager::{
encode_modify_liquidities, encode_permit_batch, AddLiquidityOptions as V4AddLiquidityOptions,
AddLiquiditySpecificOptions as V4AddLiquiditySpecificOptions, CommonOptions,
MintSpecificOptions, ModifyPositionSpecificOptions, OPEN_DELTA,
};
use crate::{
common::{
mint_amounts_utils::{calculate_mint_amounts, MintAmounts},
slippage_utils::ratios_after_slippage,
},
pool_infusers::common::{
calculate_position_amounts, calculate_slippage_context, V4PositionPlanner,
},
pool_swappers::common::MethodParameters,
traits::pool_infuser::{
AddBatchLiquidityParams, AddLiquidityOptions, AddLiquidityParams,
AddLiquiditySpecificOptions, CollectOptions, CollectParams, RemoveBatchLiquidityParams,
RemoveLiquidityOptions, RemoveLiquidityParams,
},
types::v4_pool_key::V4PoolKey,
};
const MSG_SENDER: Address = address!("0000000000000000000000000000000000000001");
#[derive(Debug, Clone)]
pub struct AddLiquidityCallResult {
pub method_parameters: MethodParameters,
pub amount0_max: U256,
pub amount1_max: U256,
pub liquidity: u128,
}
#[derive(Debug, Clone)]
pub struct RemoveLiquidityCallResult {
pub method_parameters: MethodParameters,
pub amount0_min: U256,
pub amount1_min: U256,
pub liquidity: u128,
}
fn convert_add_liquidity_specific_options(
opts: AddLiquiditySpecificOptions,
sqrt_price_x96: U160,
) -> V4AddLiquiditySpecificOptions {
match opts {
AddLiquiditySpecificOptions::Mint(mint_opts) => {
V4AddLiquiditySpecificOptions::Mint(MintSpecificOptions {
recipient: mint_opts.recipient,
create_pool: mint_opts.create_pool,
sqrt_price_x96: if mint_opts.create_pool { Some(sqrt_price_x96) } else { None },
migrate: false,
})
}
AddLiquiditySpecificOptions::Increase(increase_opts) => {
V4AddLiquiditySpecificOptions::Increase(ModifyPositionSpecificOptions {
token_id: increase_opts.token_id,
})
}
}
}
fn convert_add_liquidity_options(
pool_key: &V4PoolKey,
options: AddLiquidityOptions,
specific_opts: AddLiquiditySpecificOptions,
sqrt_price_x96: U160,
) -> V4AddLiquidityOptions {
V4AddLiquidityOptions {
common_opts: CommonOptions {
slippage_tolerance: options.slippage_tolerance,
deadline: options.deadline,
hook_data: Bytes::from(pool_key.hooks.into_word()),
},
use_native: options.use_native,
batch_permit: None, specific_opts: convert_add_liquidity_specific_options(specific_opts, sqrt_price_x96),
}
}
pub fn build_add_liquidity_call_parameters(
pool_key: &V4PoolKey,
params: AddLiquidityParams,
options: AddLiquidityOptions,
) -> Result<AddLiquidityCallResult> {
let sqrt_price_x96 = encode_sqrt_price_from_price(¶ms.token0_price)?;
let amount0_desired = U256::from_big_int(params.amount0.quotient());
let amount1_desired = U256::from_big_int(params.amount1.quotient());
let slippage_ctx =
calculate_slippage_context(¶ms.token0_price, &options.slippage_tolerance)?;
let amounts = calculate_position_amounts(
params.tick_lower,
params.tick_upper,
amount0_desired,
amount1_desired,
&slippage_ctx,
)?;
assert!(amounts.liquidity > 0, "ZERO_LIQUIDITY");
let v4_options = convert_add_liquidity_options(
pool_key,
options.clone(),
params.specific_opts,
sqrt_price_x96,
);
let mut calldatas: Vec<Bytes> = Vec::with_capacity(3);
let mut planner = V4PositionPlanner::default();
assert!(
if let Some(ether) = &v4_options.use_native {
pool_key.token_a.equals(ether)
} else {
!pool_key.token_a.is_native()
},
"NATIVE_NOT_SET"
);
if let Some(batch_permit) = v4_options.batch_permit {
calldatas.push(encode_permit_batch(
batch_permit.owner,
batch_permit.permit_batch,
batch_permit.signature,
));
}
let mint_amounts = mint_amounts_with_slippage(
amounts.liquidity,
params.tick_lower,
params.tick_upper,
¶ms.token0_price,
&options.slippage_tolerance,
)?;
let amount0_max = mint_amounts.amount0.to::<u128>();
let amount1_max = mint_amounts.amount1.to::<u128>();
match v4_options.specific_opts {
V4AddLiquiditySpecificOptions::Mint(mint_opts) => {
planner.add_mint(
pool_key,
params.tick_lower,
params.tick_upper,
U256::from(amounts.liquidity),
amount0_max,
amount1_max,
mint_opts.recipient,
)?;
}
V4AddLiquiditySpecificOptions::Increase(increase_opts) => {
planner.add_increase(
increase_opts.token_id,
U256::from(amounts.liquidity),
amount0_max,
amount1_max,
v4_options.common_opts.hook_data,
);
}
}
let mut value = U256::ZERO;
match v4_options.specific_opts {
V4AddLiquiditySpecificOptions::Mint(mint_opts) if mint_opts.migrate => {
if v4_options.use_native.is_some() {
planner.add_unwrap(OPEN_DELTA);
planner.add_settle(&pool_key.token_a, false, None);
planner.add_settle(&pool_key.token_b, false, None);
planner.add_sweep(pool_key.token_a.wrapped(), mint_opts.recipient);
planner.add_sweep(&pool_key.token_b, mint_opts.recipient);
} else {
planner.add_settle(&pool_key.token_a, false, None);
planner.add_settle(&pool_key.token_b, false, None);
planner.add_sweep(&pool_key.token_a, mint_opts.recipient);
planner.add_sweep(&pool_key.token_b, mint_opts.recipient);
}
}
_ => {
planner.add_settle_pair(&pool_key.token_a, &pool_key.token_b);
if v4_options.use_native.is_some() {
value = U256::from(amount0_max);
planner.add_sweep(&pool_key.token_a, MSG_SENDER);
}
}
}
calldatas
.push(encode_modify_liquidities(planner.0.finalize(), v4_options.common_opts.deadline));
Ok(AddLiquidityCallResult {
method_parameters: MethodParameters { calldata: encode_multicall(calldatas), value },
amount0_max: mint_amounts.amount0,
amount1_max: mint_amounts.amount1,
liquidity: amounts.liquidity,
})
}
fn mint_amounts_with_slippage(
liquidity: u128,
tick_lower: I24,
tick_upper: I24,
token0_price: &Price<Currency, Currency>,
slippage_tolerance: &Percent,
) -> Result<MintAmounts> {
let (sqrt_ratio_x96_lower, sqrt_ratio_x96_upper) =
ratios_after_slippage(token0_price, slippage_tolerance)
.context("Failed to calculate ratios after slippage")?;
let mint_amounts_upper =
calculate_mint_amounts(tick_lower, tick_upper, liquidity, sqrt_ratio_x96_upper)
.context("Failed to calculate mint amounts at upper price")?;
let mint_amounts_lower =
calculate_mint_amounts(tick_lower, tick_upper, liquidity, sqrt_ratio_x96_lower)
.context("Failed to calculate mint amounts at lower price")?;
Ok(MintAmounts { amount0: mint_amounts_upper.amount0, amount1: mint_amounts_lower.amount1 })
}
fn burn_amounts_with_slippage(
liquidity: u128,
tick_lower: I24,
tick_upper: I24,
token0_price: &Price<Currency, Currency>,
slippage_tolerance: &Percent,
) -> Result<(U256, U256)> {
let (sqrt_ratio_x96_lower, sqrt_ratio_x96_upper) =
ratios_after_slippage(token0_price, slippage_tolerance)
.context("Failed to calculate ratios after slippage")?;
let mint_amounts_lower =
calculate_mint_amounts(tick_lower, tick_upper, liquidity, sqrt_ratio_x96_lower)
.context("Failed to calculate mint amounts at lower price")?;
let mint_amounts_upper =
calculate_mint_amounts(tick_lower, tick_upper, liquidity, sqrt_ratio_x96_upper)
.context("Failed to calculate mint amounts at upper price")?;
Ok((mint_amounts_lower.amount0, mint_amounts_upper.amount1))
}
fn encode_sqrt_price_from_price(price: &Price<Currency, Currency>) -> Result<U160> {
use uniswap_sdk_core::utils::FromBig;
use waterpump_evm_amm_math::encode_sqrt_ratio::encode_sqrt_ratio_x96;
let fraction = price.as_fraction();
let num = U256::from_big_int(fraction.numerator);
let den = U256::from_big_int(fraction.denominator);
let result = encode_sqrt_ratio_x96(num, den)?;
Ok(U160::from(result))
}
pub fn build_add_batch_liquidity_call_parameters(
_pool_key: &V4PoolKey,
_params: AddBatchLiquidityParams,
_options: AddLiquidityOptions,
) -> Result<MethodParameters> {
unimplemented!()
}
pub fn build_remove_liquidity_call_parameters(
pool_key: &V4PoolKey,
params: RemoveLiquidityParams,
options: RemoveLiquidityOptions,
) -> Result<RemoveLiquidityCallResult> {
let liquidity_u128 = params.liquidity.to::<u128>();
let mut calldatas: Vec<Bytes> = Vec::with_capacity(2);
let mut planner = V4PositionPlanner::default();
let token_id = params.token_id;
let hook_data = Bytes::from(pool_key.hooks.into_word());
if options.burn_token {
if let Some(permit) = options.permit {
use uniswap_v4_sdk::position_manager::encode_erc721_permit;
calldatas.push(encode_erc721_permit(
permit.spender,
token_id,
permit.deadline,
permit.nonce,
permit.signature.as_bytes().into(),
));
}
let (amount0_min, amount1_min) = burn_amounts_with_slippage(
liquidity_u128,
params.tick_lower,
params.tick_upper,
¶ms.token0_price,
&options.slippage_tolerance,
)?;
planner.add_burn(token_id, amount0_min.to::<u128>(), amount1_min.to::<u128>(), hook_data);
} else {
assert!(liquidity_u128 > 0, "ZERO_LIQUIDITY");
let (amount0_min, amount1_min) = burn_amounts_with_slippage(
liquidity_u128,
params.tick_lower,
params.tick_upper,
¶ms.token0_price,
&options.slippage_tolerance,
)?;
planner.add_decrease(
token_id,
U256::from(liquidity_u128),
amount0_min.to::<u128>(),
amount1_min.to::<u128>(),
hook_data,
);
}
planner.add_take_pair(&pool_key.token_a, &pool_key.token_b, params.recipient);
calldatas.push(encode_modify_liquidities(planner.0.finalize(), params.deadline));
let (amount0_min, amount1_min) = burn_amounts_with_slippage(
liquidity_u128,
params.tick_lower,
params.tick_upper,
¶ms.token0_price,
&options.slippage_tolerance,
)?;
Ok(RemoveLiquidityCallResult {
method_parameters: MethodParameters {
calldata: encode_multicall(calldatas),
value: U256::ZERO,
},
amount0_min,
amount1_min,
liquidity: liquidity_u128,
})
}
pub fn build_remove_batch_liquidity_call_parameters(
_pool_key: &V4PoolKey,
_params: RemoveBatchLiquidityParams,
_options: RemoveLiquidityOptions,
) -> Result<MethodParameters> {
unimplemented!()
}
pub fn build_collect_call_parameters(
pool_key: &V4PoolKey,
params: &CollectParams,
_options: &CollectOptions,
) -> Result<MethodParameters> {
let mut planner = V4PositionPlanner::default();
planner.add_decrease(
params.token_id,
U256::ZERO,
0,
0,
Bytes::from(pool_key.hooks.into_word()),
);
planner.add_take_pair(&pool_key.token_a, &pool_key.token_b, params.recipient);
let deadline = chrono::Utc::now().timestamp() + 1200;
Ok(MethodParameters {
calldata: encode_modify_liquidities(planner.0.finalize(), U256::from(deadline)),
value: U256::ZERO,
})
}