Platform: Code4rena
Start Date: 27/11/2023
Pot Size: $60,500 USDC
Total HM: 7
Participants: 72
Period: 7 days
Judge: Picodes
Total Solo HM: 2
Id: 309
League: ETH
Rank: 30/72
Findings: 2
Award: $115.49
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: hash
Also found by: 0xCiphky, Neon2835, Topmark, Udsen, critical-or-high, lanrebayode77, ptsanev
104.1662 USDC - $104.17
Underflow Error from the subtraction of "chunkLiquidity" from "removedLiquidity"
function _createLegInAMM( IUniswapV3Pool _univ3pool, uint256 _tokenId, uint256 _leg, uint256 _liquidityChunk, bool _isBurn ) internal returns (int256 _moved, int256 _itmAmounts, int256 _totalCollected) { ... >>> unchecked { ... uint128 startingLiquidity = currentLiquidity.rightSlot(); uint128 removedLiquidity = currentLiquidity.leftSlot(); uint128 chunkLiquidity = _liquidityChunk.liquidity(); if (isLong == 0) { // selling/short: so move from msg.sender *to* uniswap // we're minting more liquidity in uniswap: so add the incoming liquidity chunk to the existing liquidity chunk updatedLiquidity = startingLiquidity + chunkLiquidity; /// @dev If the isLong flag is 0=short but the position was burnt, then this is closing a long position /// @dev so the amount of short liquidity should decrease. if (_isBurn) { >>> removedLiquidity -= chunkLiquidity; } ...
as seen in the code provided above at no point any where in the function was necessary validation to ensure "removedLiquidity" is greater than "chunkLiquidity" before carrying out the subtraction operation. The developers probably expect reversion as solidity helps prevent underflow with reversion by the default but the problem is this subtraction operation was carried out inside an unchecked bracket meaning in any event "chunkLiquidity" is greater than "removedLiquidity" it would not revert instead there would be an underflow and could escalate to wrong code implementation and execution of the _createLegInAMM(...) function. Note: this report should not be overlooked due to it simplicity as the complexity from underflow could be fatal.
Manual Review, Foundry
Necessary Validation should be added to prevent underflow before the subtraction of "chunkLiquidity" from "removedLiquidity" is carried out, as provided in the code below.
function _createLegInAMM( IUniswapV3Pool _univ3pool, uint256 _tokenId, uint256 _leg, uint256 _liquidityChunk, bool _isBurn ) internal returns (int256 _moved, int256 _itmAmounts, int256 _totalCollected) { ... unchecked { ... uint128 startingLiquidity = currentLiquidity.rightSlot(); uint128 removedLiquidity = currentLiquidity.leftSlot(); uint128 chunkLiquidity = _liquidityChunk.liquidity(); if (isLong == 0) { // selling/short: so move from msg.sender *to* uniswap // we're minting more liquidity in uniswap: so add the incoming liquidity chunk to the existing liquidity chunk updatedLiquidity = startingLiquidity + chunkLiquidity; /// @dev If the isLong flag is 0=short but the position was burnt, then this is closing a long position /// @dev so the amount of short liquidity should decrease. if (_isBurn) { +++ require( removedLiquidity > chunkLiquidity , "Underflow Error") removedLiquidity -= chunkLiquidity; } ...
Under/Overflow
#0 - c4-judge
2023-12-14T14:10:33Z
Picodes marked the issue as duplicate of #332
#1 - c4-judge
2023-12-26T21:53:43Z
Picodes changed the severity to 2 (Med Risk)
#2 - c4-judge
2023-12-26T22:02:17Z
Picodes marked the issue as partial-25
#3 - Picodes
2023-12-26T22:02:29Z
No valid scenario or impact is described
🌟 Selected for report: osmanozdemir1
Also found by: 0xCiphky, Audinarey, Banditx0x, CRYP70, Cryptor, D1r3Wolf, KupiaSec, LokiThe5th, Sathish9098, Skylice, ThenPuli, Topmark, Udsen, ZanyBonzy, baice, ether_sky, fatherOfBlocks, foxb868, grearlake, hihen, hubble, hunter_w3b, lanrebayode77, leegh, lsaudit, minhtrng, nocoder, onchain-guardians, ptsanev, ro1sharkm, seaton0x1, sivanesh_808, t4sk, tapir, tpiliposian, ustas
11.3163 USDC - $11.32
--- /// @notice Calculates the amount of liquidity for a given amount of token0 and liquidityChunk +++ /// @notice Calculates the amount of liquidity for a given amount of token1 and liquidityChunk /// @param liquidityChunk variable that efficiently packs the liquidity, tickLower, and tickUpper. /// @param amount1 The amount of token1 /// @return liquidity The calculated amount of liquidity function getLiquidityForAmount1( uint256 liquidityChunk, uint256 amount1 ) internal pure returns (uint128 liquidity) { uint160 lowPriceX96 = getSqrtRatioAtTick(liquidityChunk.tickLower()); uint160 highPriceX96 = getSqrtRatioAtTick(liquidityChunk.tickUpper()); unchecked { return toUint128(mulDiv(amount1, Constants.FP96, highPriceX96 - lowPriceX96)); } }
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { ... --- // Downcast + rounding up to keep is consistent with Uniswap's --- // Downcast + rounding up to keep it consistent with Uniswap's ... sqrtPriceX96 = uint160((sqrtR >> 32) + (sqrtR % (1 << 32) == 0 ? 0 : 1)); } }
```solidity function getAccountPremium( address univ3pool, address owner, uint256 tokenType, int24 tickLower, int24 tickUpper, int24 atTick, uint256 isLong ) external view returns (uint128 premiumToken0, uint128 premiumToken1) { ... --- // Compute the premium up to the current block (ie. after last touch until now). Do not proceed if atTick == type(int24).max = 8388608 +++ // Compute the premium up to the current block (ie. after last touch until now). Do not proceed if atTick == type(int24).max = 8388607 if (atTick < type(int24).max) { ... } ... }
function _burnLiquidity( uint256 liquidityChunk, IUniswapV3Pool univ3pool ) internal returns (int256 movedAmounts) { // burn that option's liquidity in the Uniswap Pool. // This will send the underlying tokens back to the Panoptic Pool (msg.sender) (uint256 amount0, uint256 amount1) = univ3pool.burn( liquidityChunk.tickLower(), liquidityChunk.tickUpper(), liquidityChunk.liquidity() ); // amount0 The amount of token0 that was sent back to the Panoptic Pool // amount1 The amount of token1 that was sent back to the Panoptic Pool // no need to safecast to int from uint here as the max position size is int128 // decrement the amountsOut with burnt amounts. amountsOut = notional value of tokens moved unchecked { movedAmounts = int256(0).toRightSlot(-int128(int256(amount0))).toLeftSlot( -int128(int256(amount1)) ); } }
function burnTokenizedPosition( uint256 tokenId, uint128 positionSize, int24 slippageTickLimitLow, int24 slippageTickLimitHigh ) external ReentrancyLock(tokenId.univ3pool()) returns (int256 totalCollected, int256 totalSwapped, int24 newTick) { +++ require ( positionSize != 0 , "positionSize Error"); // burn this ERC1155 token id _burn(msg.sender, tokenId, positionSize); ... }
#0 - Picodes
2023-12-14T17:01:12Z
#1 - c4-judge
2023-12-14T17:01:17Z
Picodes marked the issue as grade-b