Closed c4-bot-1 closed 9 months ago
0xleastwood marked the issue as primary issue
Wait hold on, in Base.swap
, if the swap amount is zero, it won't revert, instead it will just silently skip the swap step.
https://github.com/code-423n4/2023-12-particle/blob/main/contracts/libraries/Base.sol#L53
Isn't this already the suggested fix? Putting the swap amount to be 0 can liquidate the position directly, no?
I checked again, and I think I made a mistake. When discovering the error, I set the swap amount in data
swap params to 0 but not set amountFrom
to 0. Yes, this is indeed invalid, and it's a mistake on my part.
Understood, not a problem, thank you for the followup!
Seems like this finding has been invalidated by both parties. Closing this.
0xleastwood marked the issue as unsatisfactory: Invalid
Lines of code
https://github.com/code-423n4/2023-12-particle/blob/main/contracts/protocol/ParticlePositionManager.sol#L399-L406
Vulnerability details
Impact
When LP provide uniswap V3 position using
ParticlePositionManager
that have range outside of active price, it can be DoSed by opening position of all the provided liquidity.Proof of Concept
When LPs provide a Uniswap V3 position that is currently outside the active range, the available token to borrow is either all of token0 or all of token1, depending on the current price tick position (below the lower tick or higher than the upper tick). Which means the value of required collateral for the non-zero token is equal to repay amount.
Here is how collateral is calculated :
https://github.com/code-423n4/2023-12-particle/blob/main/contracts/libraries/Base.sol#L153-L161
https://github.com/code-423n4/2023-12-particle/blob/main/contracts/libraries/LiquidityAmounts.sol#L120-L136
And here is how required repay is calculated :
https://github.com/code-423n4/2023-12-particle/blob/main/contracts/libraries/Base.sol#L163-L192
https://github.com/code-423n4/2023-12-particle/blob/main/contracts/libraries/LiquidityAmounts.sol#L146-L162
From these information, if a trader for instance open a position with out of range liquidity position and have a range lower than current price, the borrowed/required pay amount consist of only token1 amount. If trader open long position for this liquidity, the collateral amount will be equal to token1 amount.
The problem is that, due to the collateral amount being equal to the borrowed/required pay amount, the swap is not required when closing this position. However, when closing the position, either via closing or liquidation, it is necessary to perform a swap operation
https://github.com/code-423n4/2023-12-particle/blob/main/contracts/protocol/ParticlePositionManager.sol#L399-L406
If the dex aggregator used is Uniswap, it will not allow providing a 0 swap amount, and the call will always revert. If we provide a dust swap amount (e.g., 1), it will revert due to the following check. Recall that because the collateral amount (
cache.collateralFrom
) is equal to the borrowed/required pay amount (cache.amountFromAdd
), ifcache.amountSpent
is non-zero andcache.tokenFromPremium
is 0, the call will always revert.https://github.com/code-423n4/2023-12-particle/blob/main/contracts/protocol/ParticlePositionManager.sol#L415-L420
Griefer can open the mentioned position with providing dust premium token, and unless the price ever hit that range, liquidation will not be possible and the LP position will stuck.
Coded PoC :
LP provide uniswap V3 liquidity with price tick lower than current price, griefer open long position using all provided liquidity, providing 1 marginFrom so swap not revert. After the operation, the position is created with 0
tokenFromPremium
. Then the LP decide want to claim back the liquidity and trigger reclaim, wait for theLOAN_TERM
(7 days), it still cannot liquidate because of thecache.amountFromAdd > cache.collateralFrom + cache.tokenFromPremium - cache.amountSpent
check.Add this test to
test/OpenPosition.t.sol
, and also addimport "forge-std/console.sol";
to the test contract :Run the test :
Log output :
Tools Used
Manual review
Recommended Mitigation Steps
Modify the all step that required swap operation, if the provided
params.amountSwap
is 0, just skip the swap call.Assessed type
DoS