When deployNewPool is called it uses the spot price of the pool, which can be manipulated through a flashloan and thus could return a highly inaccurate result.
The price is used when deciding how much liquidity should be minted for each token, so this can result in an unbalanced pool.
In other parts of the code, this is not an issue as there are oracles that prevent price manipulations, but in case there aren't any checks to avoid so.
Proof of Concept
The spot price is used to calculate the range liquidity for each token:
@> (uint160 currentSqrtPriceX96, , , , , , ) = v3Pool.slot0();
// For full range: L = Δx * sqrt(P) = Δy / sqrt(P)
// We start with fixed token amounts and apply this equation to calculate the liquidity
// Note that for pools with a tickSpacing that is not a power of 2 or greater than 8 (887272 % ts != 0),
// a position at the maximum and minimum allowable ticks will be wide, but not necessarily full-range.
// In this case, the `fullRangeLiquidity` will always be an underestimate in respect to the token amounts required to mint.
uint128 fullRangeLiquidity;
unchecked {
// Since we know one of the tokens is WETH, we simply add 0.1 ETH + worth in tokens
if (token0 == WETH) {
fullRangeLiquidity = uint128(
@> Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_WETH, currentSqrtPriceX96)
);
} else if (token1 == WETH) {
fullRangeLiquidity = uint128(
Math.mulDivRoundingUp(
FULL_RANGE_LIQUIDITY_AMOUNT_WETH,
Constants.FP96,
@> currentSqrtPriceX96
)
);
} else {
// Find the resulting liquidity for providing 1e6 of both tokens
uint128 liquidity0 = uint128(
@> Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN, currentSqrtPriceX96)
);
uint128 liquidity1 = uint128(
Math.mulDivRoundingUp(
FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN,
Constants.FP96,
@> currentSqrtPriceX96
)
);
// Pick the greater of the liquidities - i.e the more "expensive" option
// This ensures that the liquidity added is sufficiently large
fullRangeLiquidity = liquidity0 > liquidity1 ? liquidity0 : liquidity1;
}
}
But unlike other parts of the code, the PanopticFactory doesn't have any checks against the price (it doesn't use any oracles nor the TWAP), so each token liquidity is manipulable through flash loans.
Tools Used
Manual review
Recommended Mitigation Steps
Consider using the TWAP price instead of the spot price.
Lines of code
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticFactory.sol#L341-L374
Vulnerability details
Impact
When
deployNewPool
is called it uses the spot price of the pool, which can be manipulated through a flashloan and thus could return a highly inaccurate result.The price is used when deciding how much liquidity should be minted for each token, so this can result in an unbalanced pool.
In other parts of the code, this is not an issue as there are oracles that prevent price manipulations, but in case there aren't any checks to avoid so.
Proof of Concept
The spot price is used to calculate the range liquidity for each token:
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/PanopticFactory.sol#L341-L374
But unlike other parts of the code, the
PanopticFactory
doesn't have any checks against the price (it doesn't use any oracles nor the TWAP), so each token liquidity is manipulable through flash loans.Tools Used
Manual review
Recommended Mitigation Steps
Consider using the TWAP price instead of the spot price.
Assessed type
Uniswap