-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Loss of precision in Math.getLiquidityForAmount1()
function allows minting dust amounts of SFPM
tokens unlinked from liquidity
#134
Comments
dyedm1 (sponsor) disputed |
It's true that you can have ghost tokens, but this is natural and expected inherent to the way we size positions. 1 position size unit does not correspond to exactly 1 liquidity unit because it's an amount of token0 or token1. The key is that those ghost tokens can't be exploited because of our strict liquidity accounting. Protocols who use Panoptic should consider this and ensure that their accounting cannot be gamed by accumulating ghost tokens - in Panoptic, for example, users are only allowed to mint and burn their entire token balance at once, so there is no opportunity for them to accumulate non-negligible ghost tokens that would affect any of the calculations. The tokens aren't meant to be transferred as part of a protocol's functions anyway and they otherwise have control over what can and cannot be minted and how the calculations are performed. |
Picodes changed the severity to QA (Quality Assurance) |
Downgrading to QA as this more a warning for future integrators than a broken functionality in the current system. |
I believe this issue can be considered as a duplicate of #256 because the root cause (unchecked transfer) is the same and the proposed changes would have fixed both issues. Mitigation steps from the #256:
Mitigation steps from the current issue:
|
Not really. The key issue in 256 is that we're not checking the left slot of the liquidity being transferred. We are mitigating 256 by checking the removed liquidity as well during transfers, so you'll still be able to transfer ghost tokens (the key thing we look at is the liquidity, not the absolute balance). As stated in my reasoning above, ghost tokens are expected behavior and they can be accumulated without doing any transfers. |
I agree with the sponsor on this one. |
Lines of code
https://github.com/code-423n4/2023-11-panoptic/blob/f75d07c345fd795f907385868c39bafcd6a56624/contracts/libraries/Math.sol#L150-L163
https://github.com/code-423n4/2023-11-panoptic/blob/f75d07c345fd795f907385868c39bafcd6a56624/contracts/SemiFungiblePositionManager.sol#L586-L591
https://github.com/code-423n4/2023-11-panoptic/blob/f75d07c345fd795f907385868c39bafcd6a56624/contracts/SemiFungiblePositionManager.sol#L620-L630
Vulnerability details
Impact
Users can mint varying amounts of
SFPM
tokens for the same quantity of input tokens (token0
andtoken1
) due to a lack of precision inMath.getLiquidityForAmount1()
.The main issue in the system is a mismatch between user balances and liquidity. Although user balances are only used for transfers and do not affect their funds, it can pose a problem if
SFPM
is integrated with external protocols that rely on user balances to determine the amount of liquidity held.Proof of Concept
Check out the PoC: https://gist.github.com/ustas-eth/e56c5cd27e91e0d02a06bcd0d495462a
The root of the problem is
Math.getLiquidityForAmount1()
. It acts in the same way as in Uniswap'sNonfungiblePositionManager
, but Uniswap doesn't use fungible balances for users (the reason whySemiFungiblePositionManager
exists).You can try to run the following contract in Remix with the Uniswap file, or you can use
./contracts/libraries/Math.sol
with the same success:E.g., if you put
1057963989656675351166795586135635
(tick190_000
) aslowPriceX96
and2875700509213056433999819962028363
(tick210_000
) ashighPriceX96
it'll return liquidity with certain loss of precision:This allows users to call
SFPM.mintTokenizedPosition()
to get somex
amount ofSFPM
tokens and do two things:SFPM.burnTokenizedPosition()
withx - y
, wherey
represents the portion of ghost tokens, get back all the tokens spent on the position, and leavey
on the balance.SFPM.safeTransferFrom()
withx - y
, send all the liquidity to another address and leavey
on the balance.Users will only pay for
x - y
, the ghost tokens are free.These leftover tokens then can be consolidated together to form
y * 100
, for example, but users won't be able to burn them since there's no liquidity underlying.Tools Used
Manual review
Recommended Mitigation Steps
registerTokenTransfer()
SFPM
tokens based on the minted liquidity instead of users' input inmintTokenizedPosition()
.Assessed type
Math
The text was updated successfully, but these errors were encountered: