use alloy::primitives::{
aliases::{I24, U160},
Address, Bytes, U256,
};
use alloy_sol_types::SolCall;
use anyhow::{Context, Result};
use uniswap_sdk_core::{
entities::{BaseCurrency, BaseCurrencyCore, FractionBase},
utils::FromBig,
};
use uniswap_v3_sdk::prelude::{encode_multicall, encode_permit, encode_refund_eth};
use waterpump_evm_algebra_client::interfaces;
use crate::{
common::{mint_amounts_utils::calculate_mint_amounts, slippage_utils::ratios_after_slippage},
pool_infusers::common::{
calculate_position_amounts, calculate_slippage_context, AddLiquidityAmounts,
},
pool_swappers::common::MethodParameters,
traits::pool_infuser::{
AddBatchLiquidityParams, AddLiquidityOptions, AddLiquidityParams,
AddLiquiditySpecificOptions, CollectOptions, CollectParams, RemoveBatchLiquidityParams,
RemoveLiquidityOptions, RemoveLiquidityParams,
},
types::quickswap_pool_key::QuickswapPoolKey,
};
fn build_add_liquidity_calldata(
pool_key: &QuickswapPoolKey,
tick_lower: I24,
tick_upper: I24,
amounts: &AddLiquidityAmounts,
specific_opts: AddLiquiditySpecificOptions,
deadline: U256,
) -> Bytes {
match specific_opts {
AddLiquiditySpecificOptions::Mint(opts) => {
interfaces::INonfungiblePositionManager::mintCall {
params: interfaces::INonfungiblePositionManager::MintParams {
token0: pool_key.token_a.address(),
token1: pool_key.token_b.address(),
tickLower: tick_lower,
tickUpper: tick_upper,
amount0Desired: amounts.amount0_desired,
amount1Desired: amounts.amount1_desired,
amount0Min: amounts.amount0_min,
amount1Min: amounts.amount1_min,
recipient: opts.recipient,
deadline,
},
}
.abi_encode()
.into()
}
AddLiquiditySpecificOptions::Increase(opts) => {
interfaces::INonfungiblePositionManager::increaseLiquidityCall {
params: interfaces::INonfungiblePositionManager::IncreaseLiquidityParams {
tokenId: opts.token_id,
amount0Desired: amounts.amount0_desired,
amount1Desired: amounts.amount1_desired,
amount0Min: amounts.amount0_min,
amount1Min: amounts.amount1_min,
deadline,
},
}
.abi_encode()
.into()
}
}
}
pub fn build_add_liquidity_call_parameters(
pool_key: &QuickswapPoolKey,
params: AddLiquidityParams,
options: AddLiquidityOptions,
) -> Result<MethodParameters> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(5);
let deadline = options.deadline;
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,
)?;
if let Some(permit) = options.token0_permit {
calldatas.push(encode_permit(&pool_key.token_a, permit));
}
if let Some(permit) = options.token1_permit {
calldatas.push(encode_permit(&pool_key.token_b, permit));
}
calldatas.push(build_add_liquidity_calldata(
pool_key,
params.tick_lower,
params.tick_upper,
&amounts,
params.specific_opts,
deadline,
));
let mut value = U256::ZERO;
if let Some(ether) = options.use_native {
let wrapped = ether.wrapped();
let wrapped_value = if pool_key.token_a.equals(wrapped) {
amounts.amount0_desired
} else if pool_key.token_b.equals(wrapped) {
amounts.amount1_desired
} else {
return Err(anyhow::anyhow!("NO_WETH: Pool tokens don't match WETH"));
};
if wrapped_value > U256::ZERO {
calldatas.push(encode_refund_eth());
}
value = wrapped_value;
}
Ok(MethodParameters { calldata: encode_multicall(calldatas), value })
}
pub fn build_add_batch_liquidity_call_parameters(
pool_key: &QuickswapPoolKey,
params: AddBatchLiquidityParams,
options: AddLiquidityOptions,
) -> Result<MethodParameters> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(params.items.len() + 4);
let mut total_value = U256::ZERO;
let deadline = options.deadline;
let slippage_ctx =
calculate_slippage_context(¶ms.token0_price, &options.slippage_tolerance)?;
if let Some(permit) = options.token0_permit {
calldatas.push(encode_permit(&pool_key.token_a, permit));
}
if let Some(permit) = options.token1_permit {
calldatas.push(encode_permit(&pool_key.token_b, permit));
}
for item in ¶ms.items {
let amount0_desired = U256::from_big_int(item.amount0.quotient());
let amount1_desired = U256::from_big_int(item.amount1.quotient());
let amounts = calculate_position_amounts(
item.tick_lower,
item.tick_upper,
amount0_desired,
amount1_desired,
&slippage_ctx,
)?;
calldatas.push(build_add_liquidity_calldata(
pool_key,
item.tick_lower,
item.tick_upper,
&amounts,
item.specific_opts,
deadline,
));
if let Some(ref ether) = options.use_native {
let wrapped = ether.wrapped();
let wrapped_value = if pool_key.token_a.equals(wrapped) {
amounts.amount0_desired
} else if pool_key.token_b.equals(wrapped) {
amounts.amount1_desired
} else {
return Err(anyhow::anyhow!("NO_WETH: Pool tokens don't match WETH"));
};
total_value += wrapped_value;
}
}
if total_value > U256::ZERO {
calldatas.push(encode_refund_eth());
}
Ok(MethodParameters { calldata: encode_multicall(calldatas), value: total_value })
}
struct RemovePositionParams {
token_id: U256,
liquidity: U256,
tick_lower: I24,
tick_upper: I24,
recipient: Address,
deadline: U256,
burn_token: bool,
}
fn build_remove_position_calldatas(
pool_key: &QuickswapPoolKey,
params: &RemovePositionParams,
sqrt_ratio_x96_lower_slippage: U160,
sqrt_ratio_x96_upper_slippage: U160,
collect_options: &CollectOptions,
) -> Result<Vec<Bytes>> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(4);
let mint_amounts_upper = calculate_mint_amounts(
params.tick_lower,
params.tick_upper,
params.liquidity.to::<u128>(),
sqrt_ratio_x96_upper_slippage,
)
.context("Failed to calculate mint amounts at upper slippage price")?;
let mint_amounts_lower = calculate_mint_amounts(
params.tick_lower,
params.tick_upper,
params.liquidity.to::<u128>(),
sqrt_ratio_x96_lower_slippage,
)
.context("Failed to calculate mint amounts at lower slippage price")?;
let amount0_min = mint_amounts_upper.amount0;
let amount1_min = mint_amounts_lower.amount1;
tracing::info!("Amount0 min: {:?}", amount0_min);
tracing::info!("Amount1 min: {:?}", amount1_min);
calldatas.push(
interfaces::INonfungiblePositionManager::decreaseLiquidityCall {
params: interfaces::INonfungiblePositionManager::DecreaseLiquidityParams {
tokenId: params.token_id,
liquidity: params.liquidity.to::<u128>(),
amount0Min: amount0_min,
amount1Min: amount1_min,
deadline: params.deadline,
},
}
.abi_encode()
.into(),
);
let collect_params = CollectParams { token_id: params.token_id, recipient: params.recipient };
let collect_calldatas = build_collect_calldatas(pool_key, &collect_params, collect_options)?;
calldatas.extend(collect_calldatas);
if params.burn_token {
calldatas.push(
interfaces::INonfungiblePositionManager::burnCall { tokenId: params.token_id }
.abi_encode()
.into(),
);
}
Ok(calldatas)
}
pub fn build_remove_liquidity_call_parameters(
pool_key: &QuickswapPoolKey,
params: RemoveLiquidityParams,
options: RemoveLiquidityOptions,
) -> Result<MethodParameters> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(6);
let (sqrt_ratio_x96_lower_slippage, sqrt_ratio_x96_upper_slippage) =
ratios_after_slippage(¶ms.token0_price, &options.slippage_tolerance)
.context("Failed to calculate ratios after slippage")?;
let remove_params = RemovePositionParams {
token_id: params.token_id,
liquidity: params.liquidity,
tick_lower: params.tick_lower,
tick_upper: params.tick_upper,
recipient: params.recipient,
deadline: params.deadline,
burn_token: options.burn_token,
};
let position_calldatas = build_remove_position_calldatas(
pool_key,
&remove_params,
sqrt_ratio_x96_lower_slippage,
sqrt_ratio_x96_upper_slippage,
&options.collect_options,
)?;
calldatas.extend(position_calldatas);
Ok(MethodParameters { calldata: encode_multicall(calldatas), value: U256::ZERO })
}
pub fn build_remove_batch_liquidity_call_parameters(
pool_key: &QuickswapPoolKey,
params: RemoveBatchLiquidityParams,
options: RemoveLiquidityOptions,
) -> Result<MethodParameters> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(params.items.len() * 3 + 1);
let (sqrt_ratio_x96_lower_slippage, sqrt_ratio_x96_upper_slippage) =
ratios_after_slippage(¶ms.token0_price, &options.slippage_tolerance)
.context("Failed to calculate ratios after slippage")?;
for item in ¶ms.items {
let remove_params = RemovePositionParams {
token_id: item.token_id,
liquidity: item.liquidity,
tick_lower: item.tick_lower,
tick_upper: item.tick_upper,
recipient: params.recipient,
deadline: params.deadline,
burn_token: item.burn_token,
};
let position_calldatas = build_remove_position_calldatas(
pool_key,
&remove_params,
sqrt_ratio_x96_lower_slippage,
sqrt_ratio_x96_upper_slippage,
&options.collect_options,
)?;
calldatas.extend(position_calldatas);
}
Ok(MethodParameters { calldata: encode_multicall(calldatas), value: U256::ZERO })
}
fn build_collect_calldatas(
pool_key: &QuickswapPoolKey,
params: &CollectParams,
options: &CollectOptions,
) -> Result<Vec<Bytes>> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(3);
let involves_eth = pool_key.token_a.is_native() || pool_key.token_b.is_native();
let recipient = if involves_eth { Address::ZERO } else { params.recipient };
calldatas.push(
interfaces::INonfungiblePositionManager::collectCall {
params: interfaces::INonfungiblePositionManager::CollectParams {
tokenId: params.token_id,
recipient,
amount0Max: u128::MAX,
amount1Max: u128::MAX,
},
}
.abi_encode()
.into(),
);
if involves_eth {
use uniswap_v3_sdk::prelude::{encode_sweep_token, encode_unwrap_weth9};
let token0_min =
if let Some(expected_currency_owed0) = options.expected_currency_owed0.clone() {
U256::from_big_int(expected_currency_owed0.quotient())
} else {
U256::ZERO
};
let token1_min =
if let Some(expected_currency_owed1) = options.expected_currency_owed1.clone() {
U256::from_big_int(expected_currency_owed1.quotient())
} else {
U256::ZERO
};
let (token, token_amount, eth_amount) = if pool_key.token_a.is_native() {
(pool_key.token_a.wrapped(), token1_min, token0_min)
} else if pool_key.token_b.is_native() {
(pool_key.token_b.wrapped(), token0_min, token1_min)
} else {
return Err(anyhow::anyhow!("NO_WETH: Pool tokens don't match WETH"));
};
calldatas.push(encode_unwrap_weth9(eth_amount, params.recipient, None));
calldatas.push(encode_sweep_token(token.address(), token_amount, params.recipient, None));
}
Ok(calldatas)
}
pub fn build_collect_call_parameters(
pool_key: &QuickswapPoolKey,
params: &CollectParams,
options: &CollectOptions,
) -> Result<MethodParameters> {
let calldatas = build_collect_calldatas(pool_key, params, options)?;
Ok(MethodParameters { calldata: encode_multicall(calldatas), value: U256::ZERO })
}