use alloy::primitives::{
aliases::{I24, U160},
Address, Bytes, U256,
};
use alloy_sol_types::SolCall;
use anyhow::{Context, Result};
use uniswap_sdk_core::{
entities::{BaseCurrency, FractionBase},
prelude::ToBig,
utils::FromBig,
};
use uniswap_v3_sdk::prelude::{encode_multicall, encode_permit, encode_refund_eth};
use waterpump_evm_core::pool_key::SlipstreamPoolKey;
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,
},
};
alloy_sol_types::sol! {
interface INonfungiblePositionManager {
struct MintParams {
address token0;
address token1;
int24 tickSpacing;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
uint160 sqrtPriceX96;
}
function mint(MintParams calldata params)
external
payable
returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
struct IncreaseLiquidityParams {
uint256 tokenId;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
function increaseLiquidity(IncreaseLiquidityParams calldata params)
external
payable
returns (uint128 liquidity, uint256 amount0, uint256 amount1);
struct DecreaseLiquidityParams {
uint256 tokenId;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1);
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);
function burn(uint256 tokenId) external payable;
}
}
fn build_add_liquidity_calldata(
pool_key: &SlipstreamPoolKey,
tick_lower: I24,
tick_upper: I24,
amounts: &AddLiquidityAmounts,
specific_opts: AddLiquiditySpecificOptions,
deadline: U256,
sqrt_ratio_x96_current: U160,
) -> Bytes {
match specific_opts {
AddLiquiditySpecificOptions::Mint(opts) => INonfungiblePositionManager::mintCall {
params: INonfungiblePositionManager::MintParams {
token0: pool_key.token_a.address(),
token1: pool_key.token_b.address(),
tickSpacing: pool_key.tick_spacing,
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,
sqrtPriceX96: sqrt_ratio_x96_current,
},
}
.abi_encode()
.into(),
AddLiquiditySpecificOptions::Increase(opts) => {
INonfungiblePositionManager::increaseLiquidityCall {
params: 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: &SlipstreamPoolKey,
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 AddLiquiditySpecificOptions::Mint(opts) = params.specific_opts {
if opts.create_pool {
}
}
calldatas.push(build_add_liquidity_calldata(
pool_key,
params.tick_lower,
params.tick_upper,
&amounts,
params.specific_opts,
deadline,
slippage_ctx.sqrt_ratio_x96_current,
));
if let Some(token0_permit) = options.token0_permit {
calldatas.push(encode_permit(&pool_key.token_a, token0_permit));
}
if let Some(token1_permit) = options.token1_permit {
calldatas.push(encode_permit(&pool_key.token_b, token1_permit));
}
if options.use_native.is_some() {
calldatas.push(encode_refund_eth());
}
Ok(MethodParameters { calldata: encode_multicall(calldatas), value: U256::ZERO })
}
pub fn build_add_batch_liquidity_call_parameters(
pool_key: &SlipstreamPoolKey,
params: AddBatchLiquidityParams,
options: AddLiquidityOptions,
) -> Result<MethodParameters> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(params.items.len() + 4);
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,
slippage_ctx.sqrt_ratio_x96_current,
));
}
if options.use_native.is_some() {
calldatas.push(encode_refund_eth());
}
Ok(MethodParameters { calldata: encode_multicall(calldatas), value: U256::ZERO })
}
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: &SlipstreamPoolKey,
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;
calldatas.push(
INonfungiblePositionManager::decreaseLiquidityCall {
params: INonfungiblePositionManager::DecreaseLiquidityParams {
tokenId: params.token_id,
liquidity: params.liquidity.to::<u128>(),
amount0Min: U256::from(amount0_min),
amount1Min: U256::from(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(
INonfungiblePositionManager::burnCall { tokenId: params.token_id }.abi_encode().into(),
);
}
Ok(calldatas)
}
pub fn build_remove_liquidity_call_parameters(
pool_key: &SlipstreamPoolKey,
params: RemoveLiquidityParams,
options: RemoveLiquidityOptions,
) -> Result<MethodParameters> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(6);
if let Some(permit) = options.permit {
alloy_sol_types::sol! {
interface IERC721Permit {
function permit(
address spender,
uint256 tokenId,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
}
calldatas.push(
IERC721Permit::permitCall {
spender: permit.spender,
tokenId: params.token_id,
deadline: permit.deadline,
v: permit.signature.v() as u8 + 27,
r: permit.signature.r().into(),
s: permit.signature.s().into(),
}
.abi_encode()
.into(),
);
}
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: &SlipstreamPoolKey,
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: &SlipstreamPoolKey,
params: &CollectParams,
options: &CollectOptions,
) -> Result<Vec<Bytes>> {
let mut calldatas: Vec<Bytes> = Vec::with_capacity(1);
let amount0_max = options
.expected_currency_owed0
.as_ref()
.map(|c| c.quotient().to_big_uint())
.unwrap_or_else(|| U256::from(u128::MAX).to_big_uint());
let amount1_max = options
.expected_currency_owed1
.as_ref()
.map(|c| c.quotient().to_big_uint())
.unwrap_or_else(|| U256::from(u128::MAX).to_big_uint());
let amount0_max_u128 = U256::from_big_uint(amount0_max).to::<u128>();
let amount1_max_u128 = U256::from_big_uint(amount1_max).to::<u128>();
calldatas.push(
INonfungiblePositionManager::collectCall {
params: INonfungiblePositionManager::CollectParams {
tokenId: params.token_id,
recipient: params.recipient,
amount0Max: amount0_max_u128,
amount1Max: amount1_max_u128,
},
}
.abi_encode()
.into(),
);
Ok(calldatas)
}
pub fn build_collect_call_parameters(
pool_key: &SlipstreamPoolKey,
params: &CollectParams,
options: &CollectOptions,
) -> Result<MethodParameters> {
let calldatas = build_collect_calldatas(pool_key, params, options)?;
Ok(MethodParameters { calldata: encode_multicall(calldatas), value: U256::ZERO })
}