2024-04-panoptic
Table of Contents
-
Summary
-
Low Risk Issues
-
Non-Critical Issues
-
Details
-
Low Risk Issues
-
Non-Critical Issues
<a id="summary"></a> Summary
<a id="summary-low-risk-issues"></a> Low Risk Issues
There are 425 instances over 17 issues.
<a id="summary-non-critical-issues"></a> Non-Critical Issues
There are 1233 instances over 63 issues.
<a id="details"></a> Details
<a id="details-low-risk-issues"></a> Low Risk Issues
<a id="l-01"></a> [L-01] Consider disallowing minting/transfers to address(this)
Description:
A transfer to the token contract itself is unlikely to be correct and more likely to be a copy and paste error. Proceeding with such a transfer will result in the permanent loss of user tokens.
Instances:
There is 1 instance of this issue.
File: contracts/CollateralTracker.sol
477: function mint(uint256 shares, address receiver) external returns (uint256 assets) {
<a id="l-02"></a> [L-02] constructor
/initialize
function lacks parameter validation
Description:
Constructors and initialization functions play a critical role in contracts by setting important initial states when the contract is first deployed before the system starts. The parameters passed to the constructor and initialization functions directly affect the behavior of the contract / protocol. If incorrect parameters are provided, the system may fail to run, behave abnormally, be unstable, or lack security. Therefore, it is crucial to carefully check each parameter in the constructor and initialization functions. If an exception is found, the transaction should be rolled back.
Recommendation:
Consider whether reasonable bounds checks for variables would be useful.
Instances:
There is 1 instance of this issue.
File: contracts/CollateralTracker.sol
/// @audit parameters not validated: _commissionFee, _sellerCollateralRatio, _buyerCollateralRatio, _forceExerciseCost, _targetPoolUtilization, _saturatedPoolUtilization, _ITMSpreadMultiplier
178: constructor(
179: uint256 _commissionFee,
180: uint256 _sellerCollateralRatio,
181: uint256 _buyerCollateralRatio,
182: int256 _forceExerciseCost,
183: uint256 _targetPoolUtilization,
184: uint256 _saturatedPoolUtilization,
185: uint256 _ITMSpreadMultiplier
186: ) {
<a id="l-03"></a> [L-03] Contracts use infinite approvals with no means to revoke
Description:
Infinite approvals on external contracts can be dangerous if the target becomes compromised. The following contracts are vulnerable to such attacks since they have no functionality to revoke the approval (call approve
with amount 0).
Recommendation:
Consider adding the ability to revoke approval in emergency situations.
Instances:
There are 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol
32: IERC20Partial(token0).approve(address(sfpm), type(uint256).max);
33: IERC20Partial(token1).approve(address(sfpm), type(uint256).max);
36: IERC20Partial(token0).approve(address(ct0), type(uint256).max);
37: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
<a id="l-04"></a> [L-04] Fee-on-transfer and rebasing tokens will have problems when swapping
Description:
Uniswap v3 does not support rebasing or fee-on-transfer tokens so using these tokens with Uniswap will result in transfers after the swap failing due to insufficient tokens, or with the contract having a remaining balance after the transfer.
Instances:
There is 1 instance of this issue.
File: contracts/SemiFungiblePositionManager.sol
837: (int256 swap0, int256 swap1) = _univ3pool.swap(
<a id="l-05"></a> [L-05] File allows a version of Solidity that is susceptible to .selector
-related optimizer bug
Description:
In Solidity versions starting with 0.6.2 and before 0.8.21, the legacy code generator was not evaluating complex expressions, like assignments, function calls, or conditionals, whose .selector
was being accessed. For example, in an expression like f().g.selector
, the result will always be the same regardless of what f()
returns. It is listed as low severity because projects usually use the contract name rather than a function call to look up the selector. All files using .selector
where the Solidity version is vulnerable have been reported.
Instances:
There is 1 instance of this issue.
File: contracts/tokens/ERC1155Minimal.sol
2: pragma solidity ^0.8.0;
<a id="l-06"></a> [L-06] for
and while
loops in public
or external
functions should be avoided due to high gas costs and possible DOS
Description:
In Solidity, for
and while
loops can potentially cause denial of service (DOS) attacks if not handled carefully. DOS attacks can occur when an attacker intentionally exploits the gas cost of a function, causing it to run out of gas or making it too expensive for other users to call. Below are some scenarios where for
or while
loops can lead to DOS attacks: Nested for
and while
loops can become exceptionally gas expensive and should be used sparingly.
Instances:
There are 18 instances of this issue.
<details><summary>View 18 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit external function exerciseCost()
662: for (uint256 leg = 0; leg < positionId.countLegs(); ++leg) {
File: contracts/PanopticFactory.sol
/// @audit external function minePoolAddress()
303: for (; uint256(salt) < maxSalt; ) {
File: contracts/SemiFungiblePositionManager.sol
/// @audit external function initializeAMMPool()
372: while (address(s_poolContext[poolId].pool) != address(0)) {
/// @audit public function safeBatchTransferFrom()
575: for (uint256 i = 0; i < ids.length; ) {
File: contracts/libraries/FeesCalc.sol
/// @audit external function getPortfolioValue()
51: for (uint256 k = 0; k < positionIdList.length; ) {
/// @audit external function getPortfolioValue()
55: for (uint256 leg = 0; leg < numLegs; ) {
File: contracts/libraries/PanopticMath.sol
/// @audit external function computeMedianObservedPrice()
137: for (uint256 i = 0; i < cardinality + 1; ++i) {
/// @audit external function computeMedianObservedPrice()
148: for (uint256 i = 0; i < cardinality; ++i) {
/// @audit external function computeInternalMedian()
207: for (uint8 i; i < 8; ++i) {
/// @audit external function twapFilter()
248: for (uint256 i = 0; i < 20; ++i) {
/// @audit external function twapFilter()
256: for (uint256 i = 0; i < 19; ++i) {
/// @audit external function haircutPremia()
781: for (uint256 i = 0; i < positionIdList.length; ++i) {
/// @audit external function haircutPremia()
784: for (uint256 leg = 0; leg < numLegs; ++leg) {
/// @audit external function haircutPremia()
860: for (uint256 i = 0; i < positionIdList.length; i++) {
/// @audit external function haircutPremia()
863: for (uint256 leg = 0; leg < tokenId.countLegs(); ++leg) {
File: contracts/multicall/Multicall.sol
/// @audit public function multicall()
14: for (uint256 i = 0; i < data.length; ) {
File: contracts/tokens/ERC1155Minimal.sol
/// @audit public function safeBatchTransferFrom()
143: for (uint256 i = 0; i < ids.length; ) {
/// @audit public function balanceOfBatch()
187: for (uint256 i = 0; i < owners.length; ++i) {
</details>
<a id="l-07"></a> [L-07] Functions calling contracts/addresses with transfer hooks are missing reentrancy guards
Description:
Even if the function follows the best practice of the check-effects-interactions pattern, not using a reentrancy guard when there may be transfer hooks will open the users of this protocol up to read-only reentrancies with no way to protect against it other than by block-listing the whole protocol.
Recommendation:
Add a reentrancy guard to any function using a transfer hook.
Instances:
There are 3 instances of this issue.
File: contracts/PanopticFactory.sol
/// @audit function: `uniswapV3MintCallback()`
182: SafeTransferLib.safeTransferFrom(
183: decoded.poolFeatures.token0,
184: decoded.payer,
185: msg.sender,
186: amount0Owed
187: );
File: contracts/SemiFungiblePositionManager.sol
/// @audit function: `uniswapV3MintCallback()`
413: SafeTransferLib.safeTransferFrom(
414: decoded.poolFeatures.token0,
415: decoded.payer,
416: msg.sender,
417: amount0Owed
418: );
/// @audit function: `uniswapV3SwapCallback()`
456: SafeTransferLib.safeTransferFrom(token, decoded.payer, msg.sender, amountToPay);
<a id="l-08"></a> [L-08] internal
function calls within loops
Description:
Making internal function calls within loops in Solidity can lead to inefficient gas usage, potential bottlenecks, and increased vulnerability to denial of service (DOS) attacks. Each function call consumes gas, and when executed within a loop, the gas cost multiplies, potentially causing the transaction to run out of gas or exceed block gas limits. This can result in transaction failure or unpredictable behavior.
Instances:
There are 35 instances of this issue.
<details><summary>View 35 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit _getRequiredCollateralAtTickSinglePosition()
1219: uint256 _tokenRequired = _getRequiredCollateralAtTickSinglePosition(
1220: tokenId,
1221: positionSize,
1222: atTick,
1223: poolUtilization
1224: );
/// @audit _getRequiredCollateralSingleLeg()
1259: tokenRequired += _getRequiredCollateralSingleLeg(
1260: tokenId,
1261: index,
1262: positionSize,
1263: atTick,
1264: poolUtilization
1265: );
File: contracts/PanopticPool.sol
/// @audit _getPremia()
450: ) = _getPremia(
451: tokenId,
452: LeftRightUnsigned.wrap(balances[k][1]).rightSlot(),
453: c_user,
454: computeAllPremia,
455: atTick
456: );
/// @audit _getAvailablePremium()
469: LeftRightUnsigned availablePremium = _getAvailablePremium(
470: _getTotalLiquidity(tokenId, leg),
471: s_settledTokens[chunkKey],
472: s_grossPremiumLast[chunkKey],
473: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))),
474: premiumAccumulatorsByLeg[leg]
475: );
/// @audit _getAvailablePremium()
469: LeftRightUnsigned availablePremium = _getAvailablePremium(
470: _getTotalLiquidity(tokenId, leg),
471: s_settledTokens[chunkKey],
472: s_grossPremiumLast[chunkKey],
473: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))),
474: premiumAccumulatorsByLeg[leg]
475: );
/// @audit _getTotalLiquidity()
470: _getTotalLiquidity(tokenId, leg),
/// @audit _getTotalLiquidity()
470: _getTotalLiquidity(tokenId, leg),
/// @audit _checkLiquiditySpread()
770: _checkLiquiditySpread(
771: tokenId,
772: leg,
773: tickLower,
774: tickUpper,
775: uint64(Math.min(effectiveLiquidityLimitX32, MAX_SPREAD))
776: );
/// @audit _burnOptions()
804: (paidAmounts, premiasByLeg[i]) = _burnOptions(
805: commitLongSettled,
806: positionIdList[i],
807: owner,
808: tickLimitLow,
809: tickLimitHigh
810: );
/// @audit _checkLiquiditySpread()
868: _checkLiquiditySpread(tokenId, leg, tickLower, tickUpper, MAX_SPREAD);
/// @audit _getTotalLiquidity()
1687: uint256 totalLiquidity = _getTotalLiquidity(tokenId, leg);
/// @audit _getTotalLiquidity()
1882: uint256 totalLiquidity = _getTotalLiquidity(tokenId, leg);
/// @audit _getAvailablePremium()
1888: LeftRightUnsigned availablePremium = _getAvailablePremium(
1889: totalLiquidity + positionLiquidity,
1890: settledTokens,
1891: grossPremiumLast,
1892: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(legPremia))),
1893: premiumAccumulatorsByLeg[leg]
1894: );
File: contracts/SemiFungiblePositionManager.sol
/// @audit registerTokenTransfer()
577: registerTokenTransfer(from, to, TokenId.wrap(ids[i]), amounts[i]);
/// @audit _createLegInAMM()
910: (_moved, _itmAmounts, _collectedSingleLeg) = _createLegInAMM(
911: _univ3pool,
912: _tokenId,
913: _leg,
914: liquidityChunk,
915: _isBurn
916: );
File: contracts/libraries/PanopticMath.sol
/// @audit _calculateIOAmounts()
397: (LeftRightSigned longs, LeftRightSigned shorts) = _calculateIOAmounts(
398: tokenId,
399: positionSize,
400: leg
401: );
File: contracts/types/TokenId.sol
/// @audit optionRatio()
508: if (self.optionRatio(i) == 0) {
/// @audit countLegs()
519: uint256 numLegs = self.countLegs();
/// @audit width()
528: if ((self.width(i) == 0)) revert Errors.InvalidTokenIdParameter(5);
/// @audit strike()
531: (self.strike(i) == Constants.MIN_V3POOL_TICK) ||
/// @audit strike()
532: (self.strike(i) == Constants.MAX_V3POOL_TICK)
/// @audit riskPartner()
538: uint256 riskPartnerIndex = self.riskPartner(i);
/// @audit riskPartner()
541: if (self.riskPartner(riskPartnerIndex) != i)
/// @audit asset()
546: (self.asset(riskPartnerIndex) != self.asset(i)) ||
/// @audit asset()
546: (self.asset(riskPartnerIndex) != self.asset(i)) ||
/// @audit optionRatio()
547: (self.optionRatio(riskPartnerIndex) != self.optionRatio(i))
/// @audit optionRatio()
547: (self.optionRatio(riskPartnerIndex) != self.optionRatio(i))
/// @audit isLong()
551: uint256 _isLong = self.isLong(i);
/// @audit isLong()
552: uint256 isLongP = self.isLong(riskPartnerIndex);
/// @audit tokenType()
555: uint256 _tokenType = self.tokenType(i);
/// @audit tokenType()
556: uint256 tokenTypeP = self.tokenType(riskPartnerIndex);
/// @audit width()
583: self.width(i),
/// @audit tickSpacing()
584: self.tickSpacing()
/// @audit strike()
587: int24 _strike = self.strike(i);
/// @audit isLong()
592: if (self.isLong(i) == 1) return; // validated
</details>
<a id="l-09"></a> [L-09] Large approvals may not work with some ERC20
tokens
Description:
Not all IERC20
implementations are totally compliant, and some (e.g., UNI
, COMP
) may fail if the valued passed to approve
is larger than uint96
. If the approval amount is type(uint256).max
, this may cause issues with systems that expect the value passed to approve to be reflected in the allowances mapping.
Instances:
There are 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol
32: IERC20Partial(token0).approve(address(sfpm), type(uint256).max);
33: IERC20Partial(token1).approve(address(sfpm), type(uint256).max);
36: IERC20Partial(token0).approve(address(ct0), type(uint256).max);
37: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
<a id="l-10"></a> [L-10] Large transfers may not work with some ERC20
tokens
Description:
Some IERC20
implementations (e.g., UNI
, COMP
) may fail if the valued transferred is larger than uint96
. Source.
Recommendation:
Check the actual balance before and after vs. the expected balance.
Instances:
There are 2 instances of this issue.
File: contracts/CollateralTracker.sol
/// @audit uint256 amount
333: return ERC20Minimal.transfer(recipient, amount);
/// @audit uint256 amount
352: return ERC20Minimal.transferFrom(from, to, amount);
<a id="l-11"></a> [L-11] Missing contract-existence checks before yul call()
Description:
Low-level calls return success if there is no code present at the specified address. In addition to the zero-address checks, add a check to verify that extcodesize()
is non-zero.
Instances:
There are 2 instances of this issue.
File: contracts/libraries/SafeTransferLib.sol
/// @audit token
24: assembly ("memory-safe") {
25: // Get free memory pointer - we will store our calldata in scratch space starting at the offset specified here.
26: let p := mload(0x40)
27:
28: // Write the abi-encoded calldata into memory, beginning with the function selector.
29: mstore(p, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
30: mstore(add(4, p), from) // Append the "from" argument.
31: mstore(add(36, p), to) // Append the "to" argument.
32: mstore(add(68, p), amount) // Append the "amount" argument.
33:
34: success := and(
35: // Set success to whether the call reverted, if not we check it either
36: // returned exactly 1 (can't just be non-zero data), or had no return data.
37: or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
38: // We use 100 because that's the total length of our calldata (4 + 32 * 3)
39: // Counterintuitively, this call() must be positioned after the or() in the
40: // surrounding and() because and() evaluates its arguments from right to left.
41: call(gas(), token, 0, p, 100, 0, 32)
42: )
43: }
/// @audit token
55: assembly ("memory-safe") {
56: // Get free memory pointer - we will store our calldata in scratch space starting at the offset specified here.
57: let p := mload(0x40)
58:
59: // Write the abi-encoded calldata into memory, beginning with the function selector.
60: mstore(p, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
61: mstore(add(4, p), to) // Append the "to" argument.
62: mstore(add(36, p), amount) // Append the "amount" argument.
63:
64: success := and(
65: // Set success to whether the call reverted, if not we check it either
66: // returned exactly 1 (can't just be non-zero data), or had no return data.
67: or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
68: // We use 68 because that's the total length of our calldata (4 + 32 * 2)
69: // Counterintuitively, this call() must be positioned after the or() in the
70: // surrounding and() because and() evaluates its arguments from right to left.
71: call(gas(), token, 0, p, 68, 0, 32)
72: )
73: }
<a id="l-12"></a> [L-12] Missing zero address check in constructor/initializer
Description:
Constructors and initializer functions often take address parameters to initialize important components of a contract, such as the owner or linked contracts. However, without validation, there is a risk that an address parameter could be mistakenly set to the zero address (0x0) due to an error or an oversight during contract deployment. A zero address in a crucial role can cause serious issues, as it cannot perform actions like a normal address. Any funds sent to it will be irretrievable. It is therefore crucial to include a zero address check in constructors to prevent such problems. If a zero address is detected, the constructor should revert the transaction.
Recommendation:
Consider adding explicit zero-address validation prior to use of address
parameters in constructors.
Instances:
There are 4 instances of this issue.
File: contracts/PanopticFactory.sol
/// @audit Not checked against address zero: _WETH9, _SFPM, _univ3Factory, _donorNFT, _poolReference, _collateralReference
115: constructor(
116: address _WETH9,
117: SemiFungiblePositionManager _SFPM,
118: IUniswapV3Factory _univ3Factory,
119: IDonorNFT _donorNFT,
120: address _poolReference,
121: address _collateralReference
122: ) {
/// @audit Not checked against address zero: _owner
134: function initialize(address _owner) public {
File: contracts/PanopticPool.sol
/// @audit Not checked against address zero: _sfpm
280: constructor(SemiFungiblePositionManager _sfpm) {
File: contracts/SemiFungiblePositionManager.sol
/// @audit Not checked against address zero: _factory
341: constructor(IUniswapV3Factory _factory) {
<a id="l-13"></a> [L-13] Missing zero address check in functions with address parameters
Description:
A zero address in a crucial role can cause serious issues, as it cannot perform actions like a normal address. Any funds sent to it will be irretrievable. Even for protected functions, an explicit zero address check can prevent user errors due to typos, copy/paste errors, and similar.
Recommendation:
Consider adding explicit zero-address validation prior to use of address
parameters in functions.
Instances:
There are 83 instances of this issue.
<details><summary>View 83 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit Not checked against address zero: token0, token1, panopticPool
221: function startToken(
222: bool underlyingIsToken0,
223: address token0,
224: address token1,
225: uint24 fee,
226: PanopticPool panopticPool
227: ) external {
/// @audit Not checked against address zero: recipient
323: function transfer(
324: address recipient,
325: uint256 amount
326: ) public override(ERC20Minimal) returns (bool) {
/// @audit Not checked against address zero: from, to
341: function transferFrom(
342: address from,
343: address to,
344: uint256 amount
345: ) public override(ERC20Minimal) returns (bool) {
/// @audit Not checked against address zero:
392: function maxDeposit(address) external pure returns (uint256 maxAssets) {
/// @audit Not checked against address zero: receiver
417: function deposit(uint256 assets, address receiver) external returns (uint256 shares) {
/// @audit Not checked against address zero:
444: function maxMint(address) external view returns (uint256 maxShares) {
/// @audit Not checked against address zero: receiver
477: function mint(uint256 shares, address receiver) external returns (uint256 assets) {
/// @audit Not checked against address zero: owner
507: function maxWithdraw(address owner) public view returns (uint256 maxAssets) {
/// @audit Not checked against address zero: receiver, owner
531: function withdraw(
532: uint256 assets,
533: address receiver,
534: address owner
535: ) external returns (uint256 shares) {
/// @audit Not checked against address zero: owner
572: function maxRedeem(address owner) public view returns (uint256 maxShares) {
/// @audit Not checked against address zero: receiver, owner
591: function redeem(
592: uint256 shares,
593: address receiver,
594: address owner
595: ) external returns (uint256 assets) {
/// @audit Not checked against address zero: delegator, delegatee
864: function delegate(
865: address delegator,
866: address delegatee,
867: uint256 assets
868: ) external onlyPanopticPool {
/// @audit Not checked against address zero: delegatee
894: function delegate(address delegatee, uint256 assets) external onlyPanopticPool {
/// @audit Not checked against address zero: delegatee
903: function refund(address delegatee, uint256 assets) external onlyPanopticPool {
/// @audit Not checked against address zero: delegator, delegatee
911: function revoke(
912: address delegator,
913: address delegatee,
914: uint256 assets
915: ) external onlyPanopticPool {
/// @audit Not checked against address zero: refunder, refundee
975: function refund(address refunder, address refundee, int256 assets) external onlyPanopticPool {
/// @audit Not checked against address zero: optionOwner
995: function takeCommissionAddData(
996: address optionOwner,
997: int128 longAmount,
998: int128 shortAmount,
999: int128 swappedAmount
1000: ) external onlyPanopticPool returns (int256 utilization) {
/// @audit Not checked against address zero: optionOwner
1043: function exercise(
1044: address optionOwner,
1045: int128 longAmount,
1046: int128 shortAmount,
1047: int128 swappedAmount,
1048: int128 realizedPremium
1049: ) external onlyPanopticPool returns (int128) {
/// @audit Not checked against address zero: user
1141: function getAccountMarginDetails(
1142: address user,
1143: int24 currentTick,
1144: uint256[2][] memory positionBalanceArray,
1145: int128 premiumAllPositions
1146: ) public view returns (LeftRightUnsigned tokenData) {
/// @audit Not checked against address zero: user
1159: function _getAccountMargin(
1160: address user,
1161: int24 atTick,
1162: uint256[2][] memory positionBalanceArray,
1163: int128 premiumAllPositions
1164: ) internal view returns (LeftRightUnsigned tokenData) {
File: contracts/PanopticFactory.sol
/// @audit Not checked against address zero: newOwner
147: function transferOwnership(address newOwner) external {
/// @audit Not checked against address zero: token0, token1
210: function deployNewPool(
211: address token0,
212: address token1,
213: uint24 fee,
214: bytes32 salt
215: ) external returns (PanopticPool newPoolContract) {
/// @audit Not checked against address zero: v3Pool, token0, token1
335: function _mintFullRange(
336: IUniswapV3Pool v3Pool,
337: address token0,
338: address token1,
339: uint24 fee
340: ) internal returns (uint256, uint256) {
/// @audit Not checked against address zero: univ3pool
420: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
File: contracts/PanopticPool.sol
/// @audit Not checked against address zero: _univ3pool, token0, token1, collateralTracker0, collateralTracker1
291: function startPool(
292: IUniswapV3Pool _univ3pool,
293: address token0,
294: address token1,
295: CollateralTracker collateralTracker0,
296: CollateralTracker collateralTracker1
297: ) external {
/// @audit Not checked against address zero: user
352: function optionPositionBalance(
353: address user,
354: TokenId tokenId
355: ) external view returns (uint128 balance, uint64 poolUtilization0, uint64 poolUtilization1) {
/// @audit Not checked against address zero: user
381: function calculateAccumulatedFeesBatch(
382: address user,
383: bool includePendingPremium,
384: TokenId[] calldata positionIdList
385: ) external view returns (int128 premium0, int128 premium1, uint256[2][] memory) {
/// @audit Not checked against address zero: user
410: function calculatePortfolioValue(
411: address user,
412: int24 atTick,
413: TokenId[] calldata positionIdList
414: ) external view returns (int256 value0, int256 value1) {
/// @audit Not checked against address zero: user
429: function _calculateAccumulatedPremia(
430: address user,
431: TokenId[] calldata positionIdList,
432: bool computeAllPremia,
433: bool includePendingPremium,
434: int24 atTick
435: ) internal view returns (LeftRightSigned portfolioPremium, uint256[2][] memory balances) {
/// @audit Not checked against address zero: owner
794: function _burnAllOptionsFrom(
795: address owner,
796: int24 tickLimitLow,
797: int24 tickLimitHigh,
798: bool commitLongSettled,
799: TokenId[] calldata positionIdList
800: ) internal returns (LeftRightSigned netPaid, LeftRightSigned[4][] memory premiasByLeg) {
/// @audit Not checked against address zero: owner
826: function _burnOptions(
827: bool commitLongSettled,
828: TokenId tokenId,
829: address owner,
830: int24 tickLimitLow,
831: int24 tickLimitHigh
832: ) internal returns (LeftRightSigned paidAmounts, LeftRightSigned[4] memory premiaByLeg) {
/// @audit Not checked against address zero: owner
859: function _updatePositionDataBurn(address owner, TokenId tokenId) internal {
/// @audit Not checked against address zero: user
887: function _validateSolvency(
888: address user,
889: TokenId[] calldata positionIdList,
890: uint256 buffer
891: ) internal view returns (uint256 medianData) {
/// @audit Not checked against address zero: owner
955: function _burnAndHandleExercise(
956: bool commitLongSettled,
957: int24 tickLimitLow,
958: int24 tickLimitHigh,
959: TokenId tokenId,
960: uint128 positionSize,
961: address owner
962: )
963: internal
964: returns (
965: LeftRightSigned realizedPremia,
966: LeftRightSigned[4] memory premiaByLeg,
967: LeftRightSigned paidAmounts
968: )
969: {
/// @audit Not checked against address zero: liquidatee
1017: function liquidate(
1018: TokenId[] calldata positionIdListLiquidator,
1019: address liquidatee,
1020: LeftRightUnsigned delegations,
1021: TokenId[] calldata positionIdList
1022: ) external {
/// @audit Not checked against address zero: account
1179: function forceExercise(
1180: address account,
1181: TokenId[] calldata touchedId,
1182: TokenId[] calldata positionIdListExercisee,
1183: TokenId[] calldata positionIdListExercisor
1184: ) external {
/// @audit Not checked against address zero: account
1290: function _checkSolvencyAtTick(
1291: address account,
1292: TokenId[] calldata positionIdList,
1293: int24 currentTick,
1294: int24 atTick,
1295: uint256 buffer
1296: ) internal view returns (bool) {
/// @audit Not checked against address zero: account
1367: function _validatePositionList(
1368: address account,
1369: TokenId[] calldata positionIdList,
1370: uint256 offset
1371: ) internal view {
/// @audit Not checked against address zero: account
1405: function _updatePositionsHash(address account, TokenId tokenId, bool addFlag) internal {
/// @audit Not checked against address zero: user
1444: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) {
/// @audit Not checked against address zero: owner
1503: function _getPremia(
1504: TokenId tokenId,
1505: uint128 positionSize,
1506: address owner,
1507: bool computeAllPremia,
1508: int24 atTick
1509: )
1510: internal
1511: view
1512: returns (
1513: LeftRightSigned[4] memory premiaByLeg,
1514: uint256[2][4] memory premiumAccumulatorsByLeg
1515: )
1516: {
/// @audit Not checked against address zero: owner
1587: function settleLongPremium(
1588: TokenId[] calldata positionIdList,
1589: address owner,
1590: uint256 legIndex
1591: ) external {
/// @audit Not checked against address zero: owner
1833: function _updateSettlementPostBurn(
1834: address owner,
1835: TokenId tokenId,
1836: LeftRightUnsigned[4] memory collectedByLeg,
1837: uint128 positionSize,
1838: bool commitLongSettled
1839: ) internal returns (LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg) {
File: contracts/SemiFungiblePositionManager.sol
/// @audit Not checked against address zero: token0, token1
350: function initializeAMMPool(address token0, address token1, uint24 fee) external {
/// @audit Not checked against address zero: from, to
540: function safeTransferFrom(
541: address from,
542: address to,
543: uint256 id,
544: uint256 amount,
545: bytes calldata data
546: ) public override {
/// @audit Not checked against address zero: from, to
566: function safeBatchTransferFrom(
567: address from,
568: address to,
569: uint256[] calldata ids,
570: uint256[] calldata amounts,
571: bytes calldata data
572: ) public override {
/// @audit Not checked against address zero: from, to
593: function registerTokenTransfer(address from, address to, TokenId id, uint256 amount) internal {
/// @audit Not checked against address zero: univ3pool
756: function swapInAMM(
757: IUniswapV3Pool univ3pool,
758: LeftRightSigned itmAmounts
759: ) internal returns (LeftRightSigned totalSwapped) {
/// @audit Not checked against address zero: univ3pool
863: function _createPositionInAMM(
864: IUniswapV3Pool univ3pool,
865: TokenId tokenId,
866: uint128 positionSize,
867: bool isBurn
868: )
869: internal
870: returns (
871: LeftRightSigned totalMoved,
872: LeftRightUnsigned[4] memory collectedByLeg,
873: LeftRightSigned itmAmounts
874: )
875: {
/// @audit Not checked against address zero: univ3pool
1138: function _getFeesBase(
1139: IUniswapV3Pool univ3pool,
1140: uint128 liquidity,
1141: LiquidityChunk liquidityChunk,
1142: bool roundUp
1143: ) private view returns (LeftRightSigned feesBase) {
/// @audit Not checked against address zero: univ3pool
1185: function _mintLiquidity(
1186: LiquidityChunk liquidityChunk,
1187: IUniswapV3Pool univ3pool
1188: ) internal returns (LeftRightSigned movedAmounts) {
/// @audit Not checked against address zero: univ3pool
1224: function _burnLiquidity(
1225: LiquidityChunk liquidityChunk,
1226: IUniswapV3Pool univ3pool
1227: ) internal returns (LeftRightSigned movedAmounts) {
/// @audit Not checked against address zero: univ3pool
1255: function _collectAndWritePositionData(
1256: LiquidityChunk liquidityChunk,
1257: IUniswapV3Pool univ3pool,
1258: LeftRightUnsigned currentLiquidity,
1259: bytes32 positionKey,
1260: LeftRightSigned movedInLeg,
1261: uint256 isLong
1262: ) internal returns (LeftRightUnsigned collectedChunk) {
/// @audit Not checked against address zero: univ3pool, owner
1421: function getAccountLiquidity(
1422: address univ3pool,
1423: address owner,
1424: uint256 tokenType,
1425: int24 tickLower,
1426: int24 tickUpper
1427: ) external view returns (LeftRightUnsigned accountLiquidities) {
/// @audit Not checked against address zero: univ3pool, owner
1449: function getAccountPremium(
1450: address univ3pool,
1451: address owner,
1452: uint256 tokenType,
1453: int24 tickLower,
1454: int24 tickUpper,
1455: int24 atTick,
1456: uint256 isLong
1457: ) external view returns (uint128, uint128) {
/// @audit Not checked against address zero: univ3pool, owner
1535: function getAccountFeesBase(
1536: address univ3pool,
1537: address owner,
1538: uint256 tokenType,
1539: int24 tickLower,
1540: int24 tickUpper
1541: ) external view returns (int128 feesBase0, int128 feesBase1) {
/// @audit Not checked against address zero: univ3pool
1566: function getPoolId(address univ3pool) external view returns (uint64 poolId) {
File: contracts/libraries/CallbackLib.sol
/// @audit Not checked against address zero: sender, factory
30: function validateCallback(
31: address sender,
32: IUniswapV3Factory factory,
33: PoolFeatures memory features
34: ) internal view {
File: contracts/libraries/FeesCalc.sol
/// @audit Not checked against address zero: univ3pool
97: function calculateAMMSwapFees(
98: IUniswapV3Pool univ3pool,
99: int24 currentTick,
100: int24 tickLower,
101: int24 tickUpper,
102: uint128 liquidity
103: ) public view returns (LeftRightSigned) {
/// @audit Not checked against address zero: univ3pool
130: function _getAMMSwapFeesPerLiquidityCollected(
131: IUniswapV3Pool univ3pool,
132: int24 currentTick,
133: int24 tickLower,
134: int24 tickUpper
135: ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) {
File: contracts/libraries/InteractionHelper.sol
/// @audit Not checked against address zero: token0, token1
24: function doApprovals(
25: SemiFungiblePositionManager sfpm,
26: CollateralTracker ct0,
27: CollateralTracker ct1,
28: address token0,
29: address token1
30: ) external {
/// @audit Not checked against address zero: token0, token1
48: function computeName(
49: address token0,
50: address token1,
51: bool isToken0,
52: uint24 fee,
53: string memory prefix
54: ) external view returns (string memory) {
/// @audit Not checked against address zero: token
91: function computeSymbol(
92: address token,
93: string memory prefix
94: ) external view returns (string memory) {
/// @audit Not checked against address zero: token
107: function computeDecimals(address token) external view returns (uint8) {
File: contracts/libraries/PanopticMath.sol
/// @audit Not checked against address zero: univ3pool
47: function getPoolId(address univ3pool) internal view returns (uint64) {
/// @audit Not checked against address zero: univ3pool
125: function computeMedianObservedPrice(
126: IUniswapV3Pool univ3pool,
127: uint256 observationIndex,
128: uint256 observationCardinality,
129: uint256 cardinality,
130: uint256 period
131: ) external view returns (int24) {
/// @audit Not checked against address zero: univ3pool
168: function computeInternalMedian(
169: uint256 observationIndex,
170: uint256 observationCardinality,
171: uint256 period,
172: uint256 medianData,
173: IUniswapV3Pool univ3pool
174: ) external view returns (int24 medianTick, uint256 updatedMedianData) {
/// @audit Not checked against address zero: univ3pool
241: function twapFilter(IUniswapV3Pool univ3pool, uint32 twapWindow) external view returns (int24) {
/// @audit Not checked against address zero: liquidatee, collateral0, collateral1
768: function haircutPremia(
769: address liquidatee,
770: TokenId[] memory positionIdList,
771: LeftRightSigned[4][] memory premiasByLeg,
772: LeftRightSigned collateralRemaining,
773: CollateralTracker collateral0,
774: CollateralTracker collateral1,
775: uint160 sqrtPriceX96Final,
776: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens
777: ) external returns (int256, int256) {
/// @audit Not checked against address zero: refunder, collateral0, collateral1
917: function getRefundAmounts(
918: address refunder,
919: LeftRightSigned refundValues,
920: int24 atTick,
921: CollateralTracker collateral0,
922: CollateralTracker collateral1
923: ) external view returns (LeftRightSigned) {
File: contracts/libraries/SafeTransferLib.sol
/// @audit Not checked against address zero: token, from, to
21: function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @audit Not checked against address zero: token, to
52: function safeTransfer(address token, address to, uint256 amount) internal {
File: contracts/tokens/ERC1155Minimal.sol
/// @audit Not checked against address zero: operator
81: function setApprovalForAll(address operator, bool approved) public {
/// @audit Not checked against address zero: from, to
94: function safeTransferFrom(
95: address from,
96: address to,
97: uint256 id,
98: uint256 amount,
99: bytes calldata data
100: ) public virtual {
/// @audit Not checked against address zero: from, to
130: function safeBatchTransferFrom(
131: address from,
132: address to,
133: uint256[] calldata ids,
134: uint256[] calldata amounts,
135: bytes calldata data
136: ) public virtual {
/// @audit Not checked against address zero: to
214: function _mint(address to, uint256 id, uint256 amount) internal {
/// @audit Not checked against address zero: from
236: function _burn(address from, uint256 id, uint256 amount) internal {
File: contracts/tokens/ERC20Minimal.sol
/// @audit Not checked against address zero: spender
49: function approve(address spender, uint256 amount) public returns (bool) {
/// @audit Not checked against address zero: to
61: function transfer(address to, uint256 amount) public virtual returns (bool) {
/// @audit Not checked against address zero: from, to
81: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
/// @audit Not checked against address zero: from, to
103: function _transferFrom(address from, address to, uint256 amount) internal {
/// @audit Not checked against address zero: to
122: function _mint(address to, uint256 amount) internal {
/// @audit Not checked against address zero: from
136: function _burn(address from, uint256 amount) internal {
</details>
<a id="l-14"></a> [L-14] Some tokens may not consider type(uint256).max
as an infinite approval
Description:
Some tokens such as COMP downcast approval amounts of type(uint256).max
to uint96
and use that as a the amount rather than interpreting it as an infinite approval. Eventually these approvals will reach zero, at which point the calling contract will no longer function properly.
Instances:
There are 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol
32: IERC20Partial(token0).approve(address(sfpm), type(uint256).max);
33: IERC20Partial(token1).approve(address(sfpm), type(uint256).max);
36: IERC20Partial(token0).approve(address(ct0), type(uint256).max);
37: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
<a id="l-15"></a> [L-15] Unsafe addition/multiplication in unchecked
block
Description:
These additions/multiplications may silently overflow because they are in unchecked blocks with no preceding value checks, which may lead to unexpected results.
Instances:
There are 177 instances of this issue.
<details><summary>View 177 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit unchecked block starts at line 197
202: 2230 +
203: (12500 * ratioTick) /
204: 10_000 +
205: (7812 * ratioTick ** 2) /
206: 10_000 ** 2 +
207: (6510 * ratioTick ** 3) /
208: 10_000 ** 3
/// @audit unchecked block starts at line 197
203: (12500 * ratioTick) /
/// @audit unchecked block starts at line 197
205: (7812 * ratioTick ** 2) /
/// @audit unchecked block starts at line 197
207: (6510 * ratioTick ** 3) /
/// @audit unchecked block starts at line 261
262: s_ITMSpreadFee = uint128((ITM_SPREAD_MULTIPLIER * _poolFee) / DECIMALS);
/// @audit unchecked block starts at line 371
372: return s_poolAssets + s_inAMM;
/// @audit unchecked block starts at line 401
403: assets * (DECIMALS - COMMISSION_FEE),
/// @audit unchecked block starts at line 401
405: totalAssets() * DECIMALS
/// @audit unchecked block starts at line 445
446: return (convertToShares(type(uint104).max) * DECIMALS) / (DECIMALS + COMMISSION_FEE);
/// @audit unchecked block starts at line 461
463: shares * DECIMALS,
/// @audit unchecked block starts at line 461
465: totalSupply * (DECIMALS - COMMISSION_FEE)
/// @audit unchecked block starts at line 661
670: uint24(positionId.width(leg) * positionId.tickSpacing()),
/// @audit unchecked block starts at line 661
731: .toRightSlot(int128((longAmounts.rightSlot() * fee) / DECIMALS_128))
/// @audit unchecked block starts at line 661
732: .toLeftSlot(int128((longAmounts.leftSlot() * fee) / DECIMALS_128));
/// @audit unchecked block starts at line 742
743: return int256((s_inAMM * DECIMALS) / totalAssets());
/// @audit unchecked block starts at line 794
796: min_sell_ratio +
797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) /
798: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL);
/// @audit unchecked block starts at line 794
797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) /
/// @audit unchecked block starts at line 847
849: (BUYER_COLLATERAL_RATIO +
850: (BUYER_COLLATERAL_RATIO * (SATURATED_POOL_UTIL - utilization)) /
851: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL)) / 2; // do the division by 2 at the end after all addition and multiplication; b/c y1 = buyCollateralRatio / 2
/// @audit unchecked block starts at line 847
850: (BUYER_COLLATERAL_RATIO * (SATURATED_POOL_UTIL - utilization)) /
/// @audit unchecked block starts at line 1001
1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount)));
/// @audit unchecked block starts at line 1050
1084: s_poolAssets = uint128(uint256(updatedAssets + realizedPremium));
/// @audit unchecked block starts at line 1103
1110: s_ITMSpreadFee * uint256(Math.abs(intrinsicValue)),
/// @audit unchecked block starts at line 1103
1115: exchangedAmount = intrinsicValue + int256(swapCommission);
/// @audit unchecked block starts at line 1103
1121: uint256(uint128(shortAmount + longAmount)) * COMMISSION_FEE,
/// @audit unchecked block starts at line 1338
1364: Math.max24(2 * (atTick - strike), Constants.MIN_V3POOL_TICK)
/// @audit unchecked block starts at line 1338
1367: Math.max24(2 * (strike - atTick), Constants.MIN_V3POOL_TICK)
/// @audit unchecked block starts at line 1338
1409: (tickUpper - strike) + (strike - tickLower)
/// @audit unchecked block starts at line 1338
1414: scaleFactor + Constants.FP96
/// @audit unchecked block starts at line 1485
1486: required = Math.unsafeDivRoundingUp(amount * sellCollateral, DECIMALS);
/// @audit unchecked block starts at line 1495
1496: required = Math.unsafeDivRoundingUp(amount * buyCollateral, DECIMALS);
/// @audit unchecked block starts at line 1555
1571: ? Math.unsafeDivRoundingUp((notionalP - notional) * contracts, notional)
/// @audit unchecked block starts at line 1555
1572: : Math.unsafeDivRoundingUp((notional - notionalP) * contracts, notionalP);
/// @audit unchecked block starts at line 1627
1637: uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) +
1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
File: contracts/PanopticFactory.sol
/// @audit unchecked block starts at line 299
300: maxSalt = uint256(salt) + loops;
/// @audit unchecked block starts at line 321
323: salt = bytes32(uint256(salt) + 1);
/// @audit unchecked block starts at line 390
392: tickLower = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing;
File: contracts/PanopticPool.sol
/// @audit unchecked block starts at line 307
309: (uint256(block.timestamp) << 216) +
310: // magic number which adds (7,5,3,1,0,2,4,6) order and minTick in positions 7, 5, 3 and maxTick in 6, 4, 2
311: // see comment on s_miniMedian initialization for format of this magic number
312: (uint256(0xF590A6F276170D89E9F276170D89E9F276170D89E9000000000000)) +
313: (uint256(uint24(currentTick)) << 24) + // add to slot 4
314: (uint256(uint24(currentTick))); // add to slot 3
/// @audit unchecked block starts at line 729
730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64));
/// @audit unchecked block starts at line 1328
1329: return balanceCross >= Math.unsafeDivRoundingUp(thresholdCross * buffer, 10_000);
/// @audit unchecked block starts at line 1344
1348: Math.mulDiv(uint256(tokenData1.rightSlot()), 2 ** 96, sqrtPriceX96) +
1349: Math.mulDiv96(tokenData0.rightSlot(), sqrtPriceX96);
/// @audit unchecked block starts at line 1344
1353: Math.mulDivRoundingUp(uint256(tokenData1.leftSlot()), 2 ** 96, sqrtPriceX96) +
1354: Math.mulDiv96RoundingUp(tokenData0.leftSlot(), sqrtPriceX96);
/// @audit unchecked block starts at line 1486
1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity;
/// @audit unchecked block starts at line 1539
1550: ((premiumAccumulatorsByLeg[leg][0] -
1551: premiumAccumulatorLast.rightSlot()) *
1552: (liquidityChunk.liquidity())) / 2 ** 64
/// @audit unchecked block starts at line 1539
1559: ((premiumAccumulatorsByLeg[leg][1] -
1560: premiumAccumulatorLast.leftSlot()) *
1561: (liquidityChunk.liquidity())) / 2 ** 64
/// @audit unchecked block starts at line 1631
1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64)))
/// @audit unchecked block starts at line 1631
1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));
/// @audit unchecked block starts at line 1717
1729: (grossCurrent[0] *
1730: positionLiquidity +
1731: grossPremiumLast.rightSlot() *
1732: totalLiquidityBefore) / (totalLiquidity)
/// @audit unchecked block starts at line 1717
1731: grossPremiumLast.rightSlot() *
1732: totalLiquidityBefore) / (totalLiquidity)
/// @audit unchecked block starts at line 1717
1737: (grossCurrent[1] *
1738: positionLiquidity +
1739: grossPremiumLast.leftSlot() *
1740: totalLiquidityBefore) / (totalLiquidity)
/// @audit unchecked block starts at line 1717
1739: grossPremiumLast.leftSlot() *
1740: totalLiquidityBefore) / (totalLiquidity)
/// @audit unchecked block starts at line 1764
1768: uint256 accumulated0 = ((premiumAccumulators[0] - grossPremiumLast.rightSlot()) *
1769: totalLiquidity) / 2 ** 64;
/// @audit unchecked block starts at line 1764
1770: uint256 accumulated1 = ((premiumAccumulators[1] - grossPremiumLast.leftSlot()) *
1771: totalLiquidity) / 2 ** 64;
/// @audit unchecked block starts at line 1764
1779: (uint256(premiumOwed.rightSlot()) * settledTokens.rightSlot()) /
/// @audit unchecked block starts at line 1764
1788: (uint256(premiumOwed.leftSlot()) * settledTokens.leftSlot()) /
/// @audit unchecked block starts at line 1806
1820: totalLiquidity = accountLiquidities.rightSlot() + accountLiquidities.leftSlot();
/// @audit unchecked block starts at line 1922
1935: (int256(
1936: grossPremiumLast.rightSlot() *
1937: totalLiquidityBefore
1938: ) -
1939: int256(
1940: _premiumAccumulatorsByLeg[_leg][0] *
1941: positionLiquidity
1942: )) + int256(legPremia.rightSlot() * 2 ** 64),
/// @audit unchecked block starts at line 1922
1936: grossPremiumLast.rightSlot() *
1937: totalLiquidityBefore
/// @audit unchecked block starts at line 1922
1940: _premiumAccumulatorsByLeg[_leg][0] *
1941: positionLiquidity
/// @audit unchecked block starts at line 1922
1942: )) + int256(legPremia.rightSlot() * 2 ** 64),
/// @audit unchecked block starts at line 1922
1952: (int256(
1953: grossPremiumLast.leftSlot() *
1954: totalLiquidityBefore
1955: ) -
1956: int256(
1957: _premiumAccumulatorsByLeg[_leg][1] *
1958: positionLiquidity
1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
/// @audit unchecked block starts at line 1922
1953: grossPremiumLast.leftSlot() *
1954: totalLiquidityBefore
/// @audit unchecked block starts at line 1922
1957: _premiumAccumulatorsByLeg[_leg][1] *
1958: positionLiquidity
/// @audit unchecked block starts at line 1922
1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
File: contracts/SemiFungiblePositionManager.sol
/// @audit unchecked block starts at line 387
388: s_AddrToPoolIdData[univ3pool] = uint256(poolId) + 2 ** 255;
/// @audit unchecked block starts at line 1339
1340: uint256 totalLiquidity = netLiquidity + removedLiquidity;
/// @audit unchecked block starts at line 1339
1352: totalLiquidity * 2 ** 64,
/// @audit unchecked block starts at line 1339
1357: totalLiquidity * 2 ** 64,
/// @audit unchecked block starts at line 1339
1367: uint256 numerator = netLiquidity + (removedLiquidity / 2 ** VEGOID);
/// @audit unchecked block starts at line 1339
1388: uint256 numerator = totalLiquidity ** 2 -
1389: totalLiquidity *
1390: removedLiquidity +
1391: ((removedLiquidity ** 2) / 2 ** (VEGOID));
/// @audit unchecked block starts at line 1339
1389: totalLiquidity *
1390: removedLiquidity +
File: contracts/libraries/Math.sol
/// @audit unchecked block starts at line 129
137: if (absTick & 0x2 != 0) sqrtR = (sqrtR * 0xfff97272373d413259a46990580e213a) >> 128;
/// @audit unchecked block starts at line 129
139: if (absTick & 0x4 != 0) sqrtR = (sqrtR * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
/// @audit unchecked block starts at line 129
141: if (absTick & 0x8 != 0) sqrtR = (sqrtR * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
/// @audit unchecked block starts at line 129
143: if (absTick & 0x10 != 0) sqrtR = (sqrtR * 0xffcb9843d60f6159c9db58835c926644) >> 128;
/// @audit unchecked block starts at line 129
145: if (absTick & 0x20 != 0) sqrtR = (sqrtR * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
/// @audit unchecked block starts at line 129
147: if (absTick & 0x40 != 0) sqrtR = (sqrtR * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
/// @audit unchecked block starts at line 129
149: if (absTick & 0x80 != 0) sqrtR = (sqrtR * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
/// @audit unchecked block starts at line 129
151: if (absTick & 0x100 != 0) sqrtR = (sqrtR * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
/// @audit unchecked block starts at line 129
153: if (absTick & 0x200 != 0) sqrtR = (sqrtR * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
/// @audit unchecked block starts at line 129
155: if (absTick & 0x400 != 0) sqrtR = (sqrtR * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
/// @audit unchecked block starts at line 129
157: if (absTick & 0x800 != 0) sqrtR = (sqrtR * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
/// @audit unchecked block starts at line 129
159: if (absTick & 0x1000 != 0) sqrtR = (sqrtR * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
/// @audit unchecked block starts at line 129
161: if (absTick & 0x2000 != 0) sqrtR = (sqrtR * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
/// @audit unchecked block starts at line 129
163: if (absTick & 0x4000 != 0) sqrtR = (sqrtR * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
/// @audit unchecked block starts at line 129
165: if (absTick & 0x8000 != 0) sqrtR = (sqrtR * 0x31be135f97d08fd981231505542fcfa6) >> 128;
/// @audit unchecked block starts at line 129
167: if (absTick & 0x10000 != 0) sqrtR = (sqrtR * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
/// @audit unchecked block starts at line 129
169: if (absTick & 0x20000 != 0) sqrtR = (sqrtR * 0x5d6af8dedb81196699c329225ee604) >> 128;
/// @audit unchecked block starts at line 129
171: if (absTick & 0x40000 != 0) sqrtR = (sqrtR * 0x2216e584f5fa1ea926041bedfe98) >> 128;
/// @audit unchecked block starts at line 129
173: if (absTick & 0x80000 != 0) sqrtR = (sqrtR * 0x48a170391f7dc42444e8fa2) >> 128;
/// @audit unchecked block starts at line 129
179: return uint160((sqrtR >> 32) + (sqrtR % (1 << 32) == 0 ? 0 : 1));
/// @audit unchecked block starts at line 345
407: prod0 |= prod1 * twos;
/// @audit unchecked block starts at line 345
414: uint256 inv = (3 * denominator) ^ 2;
/// @audit unchecked block starts at line 345
418: inv *= 2 - denominator * inv; // inverse mod 2**8
/// @audit unchecked block starts at line 345
419: inv *= 2 - denominator * inv; // inverse mod 2**16
/// @audit unchecked block starts at line 345
420: inv *= 2 - denominator * inv; // inverse mod 2**32
/// @audit unchecked block starts at line 345
421: inv *= 2 - denominator * inv; // inverse mod 2**64
/// @audit unchecked block starts at line 345
422: inv *= 2 - denominator * inv; // inverse mod 2**128
/// @audit unchecked block starts at line 345
423: inv *= 2 - denominator * inv; // inverse mod 2**256
/// @audit unchecked block starts at line 345
431: result = prod0 * inv;
/// @audit unchecked block starts at line 459
511: prod0 |= prod1 * 2 ** 192;
/// @audit unchecked block starts at line 522
574: prod0 |= prod1 * 2 ** 160;
/// @audit unchecked block starts at line 676
728: prod0 |= prod1 * 2 ** 64;
/// @audit unchecked block starts at line 754
758: int256 pivot = arr[uint256(left + (right - left) / 2)];
File: contracts/libraries/PanopticMath.sol
/// @audit unchecked block starts at line 60
62: return (poolId & TICKSPACING_MASK) + (uint48(poolId) + 1);
/// @audit unchecked block starts at line 100
107: ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248)
/// @audit unchecked block starts at line 100
108: : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248);
/// @audit unchecked block starts at line 132
133: int256[] memory tickCumulatives = new int256[](cardinality + 1);
/// @audit unchecked block starts at line 132
135: uint256[] memory timestamps = new uint256[](cardinality + 1);
/// @audit unchecked block starts at line 132
137: for (uint256 i = 0; i < cardinality + 1; ++i) {
/// @audit unchecked block starts at line 132
140: (int256(observationIndex) - int256(i * period)) +
141: int256(observationCardinality)
/// @audit unchecked block starts at line 132
150: (tickCumulatives[i] - tickCumulatives[i + 1]) /
/// @audit unchecked block starts at line 132
151: int256(timestamps[i] - timestamps[i + 1]);
/// @audit unchecked block starts at line 175
178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) +
179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) /
/// @audit unchecked block starts at line 175
179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) /
/// @audit unchecked block starts at line 175
183: if (block.timestamp >= uint256(uint40(medianData >> 216)) + period) {
/// @audit unchecked block starts at line 175
188: int256(observationIndex) - int256(1) + int256(observationCardinality)
/// @audit unchecked block starts at line 175
209: rank = (orderMap >> (3 * i)) % 8;
/// @audit unchecked block starts at line 175
217: entry = int24(uint24(medianData >> (rank * 24)));
/// @audit unchecked block starts at line 175
223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1)));
/// @audit unchecked block starts at line 175
227: (block.timestamp << 216) +
228: (uint256(newOrderMap) << 192) +
229: uint256(uint192(medianData << 24)) +
230: uint256(uint24(lastObservedTick));
/// @audit unchecked block starts at line 246
249: secondsAgos[i] = uint32(((i + 1) * twapWindow) / 20);
/// @audit unchecked block starts at line 246
258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20))
/// @audit unchecked block starts at line 343
346: int24 minTick = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing;
/// @audit unchecked block starts at line 343
347: int24 maxTick = (Constants.MAX_V3POOL_TICK / tickSpacing) * tickSpacing;
/// @audit unchecked block starts at line 343
351: (tickLower, tickUpper) = (strike - rangeDown, strike + rangeUp);
/// @audit unchecked block starts at line 474
476: ? convert0to1(contractSize, Math.getSqrtRatioAtTick((tickUpper + tickLower) / 2))
/// @audit unchecked block starts at line 474
477: : convert1to0(contractSize, Math.getSqrtRatioAtTick((tickUpper + tickLower) / 2));
/// @audit unchecked block starts at line 659
669: uint256 requiredRatioX128 = (required0 << 128) / (required0 + required1);
/// @audit unchecked block starts at line 659
698: int256 paid0 = bonus0 + int256(netExchanged.rightSlot());
/// @audit unchecked block starts at line 659
699: int256 paid1 = bonus1 + int256(netExchanged.leftSlot());
/// @audit unchecked block starts at line 659
744: paid0 = bonus0 + int256(netExchanged.rightSlot());
/// @audit unchecked block starts at line 659
745: paid1 = bonus1 + int256(netExchanged.leftSlot());
/// @audit unchecked block starts at line 778
820: haircut1 = protocolLoss1 + collateralDelta1;
/// @audit unchecked block starts at line 778
843: haircut0 = collateralDelta0 + protocolLoss0;
/// @audit unchecked block starts at line 778
870: uint128(-_premiasByLeg[i][leg].rightSlot()) * uint256(haircut0),
/// @audit unchecked block starts at line 778
874: uint128(-_premiasByLeg[i][leg].leftSlot()) * uint256(haircut1),
/// @audit unchecked block starts at line 925
938: int256(
939: PanopticMath.convert0to1(uint256(balanceShortage), sqrtPriceX96)
940: ) + refundValues.leftSlot()
/// @audit unchecked block starts at line 925
956: int256(
957: PanopticMath.convert1to0(uint256(balanceShortage), sqrtPriceX96)
958: ) + refundValues.rightSlot()
File: contracts/types/LeftRight.sol
/// @audit unchecked block starts at line 63
68: (LeftRightUnsigned.unwrap(self) & LEFT_HALF_BIT_MASK) +
69: uint256(uint128(LeftRightUnsigned.unwrap(self)) + right)
/// @audit unchecked block starts at line 63
69: uint256(uint128(LeftRightUnsigned.unwrap(self)) + right)
/// @audit unchecked block starts at line 83
88: (LeftRightSigned.unwrap(self) & LEFT_HALF_BIT_MASK_INT) +
89: (int256(int128(LeftRightSigned.unwrap(self)) + right) & RIGHT_HALF_BIT_MASK)
/// @audit unchecked block starts at line 83
89: (int256(int128(LeftRightSigned.unwrap(self)) + right) & RIGHT_HALF_BIT_MASK)
/// @audit unchecked block starts at line 125
126: return LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(self) + (uint256(left) << 128));
/// @audit unchecked block starts at line 135
136: return LeftRightSigned.wrap(LeftRightSigned.unwrap(self) + (int256(left) << 128));
/// @audit unchecked block starts at line 152
155: z = LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(x) + LeftRightUnsigned.unwrap(y));
/// @audit unchecked block starts at line 195
196: int256 left = int256(uint256(x.leftSlot())) + y.leftSlot();
/// @audit unchecked block starts at line 195
201: int256 right = int256(uint256(x.rightSlot())) + y.rightSlot();
/// @audit unchecked block starts at line 215
216: int256 left256 = int256(x.leftSlot()) + y.leftSlot();
/// @audit unchecked block starts at line 215
219: int256 right256 = int256(x.rightSlot()) + y.rightSlot();
File: contracts/types/LiquidityChunk.sol
/// @audit unchecked block starts at line 75
78: (uint256(uint24(_tickLower)) << 232) +
79: (uint256(uint24(_tickUpper)) << 208) +
80: uint256(amount)
/// @audit unchecked block starts at line 93
94: return LiquidityChunk.wrap(LiquidityChunk.unwrap(self) + amount);
/// @audit unchecked block starts at line 106
109: LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232)
/// @audit unchecked block starts at line 122
126: LiquidityChunk.unwrap(self) + ((uint256(uint24(_tickUpper))) << 208)
File: contracts/types/TokenId.sol
/// @audit unchecked block starts at line 109
110: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48)) % 2);
/// @audit unchecked block starts at line 119
120: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 1)) % 128);
/// @audit unchecked block starts at line 129
130: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 8)) % 2);
/// @audit unchecked block starts at line 139
140: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 9)) % 2);
/// @audit unchecked block starts at line 149
150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4);
/// @audit unchecked block starts at line 159
160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12)));
/// @audit unchecked block starts at line 170
171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096));
/// @audit unchecked block starts at line 184
185: return TokenId.wrap(TokenId.unwrap(self) + _poolId);
/// @audit unchecked block starts at line 194
195: return TokenId.wrap(TokenId.unwrap(self) + (uint256(uint24(_tickSpacing)) << 48));
/// @audit unchecked block starts at line 210
212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48)));
/// @audit unchecked block starts at line 226
229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1))
/// @audit unchecked block starts at line 245
246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8)));
/// @audit unchecked block starts at line 260
263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9))
/// @audit unchecked block starts at line 278
281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10))
/// @audit unchecked block starts at line 296
299: TokenId.unwrap(self) +
300: uint256((int256(_strike) & BITMASK_INT24) << (64 + legIndex * 48 + 12))
/// @audit unchecked block starts at line 296
300: uint256((int256(_strike) & BITMASK_INT24) << (64 + legIndex * 48 + 12))
/// @audit unchecked block starts at line 316
319: TokenId.unwrap(self) +
320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36))
/// @audit unchecked block starts at line 316
320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36))
/// @audit unchecked block starts at line 367
395: ((LONG_MASK >> (48 * (4 - optionRatios))) & CLEAR_POOLID_MASK)
/// @audit unchecked block starts at line 405
406: return self.isLong(0) + self.isLong(1) + self.isLong(2) + self.isLong(3);
/// @audit unchecked block starts at line 504
512: if ((TokenId.unwrap(self) >> (64 + 48 * i)) != 0)
/// @audit unchecked block starts at line 504
520: for (uint256 j = i + 1; j < numLegs; ++j) {
/// @audit unchecked block starts at line 504
521: if (uint48(chunkData >> (48 * i)) == uint48(chunkData >> (48 * j))) {
/// @audit unchecked block starts at line 579
589: if ((currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown)) {
</details>
<a id="l-16"></a> [L-16] Unsafe subtraction in unchecked
block
Description:
The subtraction may silently underflow because it is in an unchecked
block with no preceding or subsequent value checks, which may lead to unexpected results.
Instances:
There are 82 instances of this issue.
<details><summary>View 82 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit unchecked block starts at line 197
200: int256 ratioTick = (int256(_sellerCollateralRatio) - 2000);
/// @audit unchecked block starts at line 401
403: assets * (DECIMALS - COMMISSION_FEE),
/// @audit unchecked block starts at line 461
465: totalSupply * (DECIMALS - COMMISSION_FEE)
/// @audit unchecked block starts at line 661
677: uint256(Math.abs(currentTick - positionId.strike(leg)) / range)
/// @audit unchecked block starts at line 661
715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0))
/// @audit unchecked block starts at line 661
718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1))
/// @audit unchecked block starts at line 661
727: int256 fee = (FORCE_EXERCISE_COST >> (maxNumRangesFromStrike - 1)); // exponential decay of fee based on number of half ranges away from the price
/// @audit unchecked block starts at line 794
797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) /
/// @audit unchecked block starts at line 794
798: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL);
/// @audit unchecked block starts at line 847
851: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL)) / 2; // do the division by 2 at the end after all addition and multiplication; b/c y1 = buyCollateralRatio / 2
/// @audit unchecked block starts at line 1001
1003: int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;
/// @audit unchecked block starts at line 1001
1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount)));
/// @audit unchecked block starts at line 1050
1052: int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;
/// @audit unchecked block starts at line 1050
1058: int256 intrinsicValue = swappedAmount - (longAmount - shortAmount);
/// @audit unchecked block starts at line 1050
1085: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount)));
/// @audit unchecked block starts at line 1103
1105: int256 intrinsicValue = swappedAmount - (shortAmount - longAmount);
/// @audit unchecked block starts at line 1338
1364: Math.max24(2 * (atTick - strike), Constants.MIN_V3POOL_TICK)
/// @audit unchecked block starts at line 1338
1367: Math.max24(2 * (strike - atTick), Constants.MIN_V3POOL_TICK)
/// @audit unchecked block starts at line 1338
1397: uint256 c2 = Constants.FP96 - ratio;
/// @audit unchecked block starts at line 1338
1409: (tickUpper - strike) + (strike - tickLower)
/// @audit unchecked block starts at line 1338
1413: scaleFactor - ratio,
File: contracts/PanopticPool.sol
/// @audit unchecked block starts at line 623
624: tokenId = positionIdList[positionIdList.length - 1];
/// @audit unchecked block starts at line 1250
1255: refundAmounts.rightSlot() - delegatedAmounts.rightSlot()
/// @audit unchecked block starts at line 1250
1260: refundAmounts.leftSlot() - delegatedAmounts.leftSlot()
/// @audit unchecked block starts at line 1375
1376: pLength = positionIdList.length - offset;
/// @audit unchecked block starts at line 1539
1550: ((premiumAccumulatorsByLeg[leg][0] -
1551: premiumAccumulatorLast.rightSlot()) *
/// @audit unchecked block starts at line 1539
1559: ((premiumAccumulatorsByLeg[leg][1] -
1560: premiumAccumulatorLast.leftSlot()) *
/// @audit unchecked block starts at line 1717
1723: uint256 totalLiquidityBefore = totalLiquidity - positionLiquidity;
/// @audit unchecked block starts at line 1764
1768: uint256 accumulated0 = ((premiumAccumulators[0] - grossPremiumLast.rightSlot()) *
/// @audit unchecked block starts at line 1764
1770: uint256 accumulated1 = ((premiumAccumulators[1] - grossPremiumLast.leftSlot()) *
/// @audit unchecked block starts at line 1922
1935: (int256(
1936: grossPremiumLast.rightSlot() *
1937: totalLiquidityBefore
1938: ) -
1939: int256(
1940: _premiumAccumulatorsByLeg[_leg][0] *
1941: positionLiquidity
1942: )) + int256(legPremia.rightSlot() * 2 ** 64),
/// @audit unchecked block starts at line 1922
1952: (int256(
1953: grossPremiumLast.leftSlot() *
1954: totalLiquidityBefore
1955: ) -
1956: int256(
1957: _premiumAccumulatorsByLeg[_leg][1] *
1958: positionLiquidity
1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
File: contracts/SemiFungiblePositionManager.sol
/// @audit unchecked block starts at line 895
899: _leg = _isBurn ? numLegs - leg - 1 : leg;
/// @audit unchecked block starts at line 1295
1297: ? receivedAmount0 - uint128(-movedInLeg.rightSlot())
/// @audit unchecked block starts at line 1295
1300: ? receivedAmount1 - uint128(-movedInLeg.leftSlot())
/// @audit unchecked block starts at line 1339
1388: uint256 numerator = totalLiquidity ** 2 -
1389: totalLiquidity *
1390: removedLiquidity +
File: contracts/libraries/FeesCalc.sol
/// @audit unchecked block starts at line 146
166: feeGrowthInside0X128 = lowerOut0 - upperOut0; // fee growth inside the chunk
/// @audit unchecked block starts at line 146
167: feeGrowthInside1X128 = lowerOut1 - upperOut1;
/// @audit unchecked block starts at line 146
183: feeGrowthInside0X128 = upperOut0 - lowerOut0;
/// @audit unchecked block starts at line 146
184: feeGrowthInside1X128 = upperOut1 - lowerOut1;
/// @audit unchecked block starts at line 146
204: feeGrowthInside0X128 = univ3pool.feeGrowthGlobal0X128() - lowerOut0 - upperOut0;
/// @audit unchecked block starts at line 146
205: feeGrowthInside1X128 = univ3pool.feeGrowthGlobal1X128() - lowerOut1 - upperOut1;
File: contracts/libraries/Math.sol
/// @audit unchecked block starts at line 194
198: highPriceX96 - lowPriceX96,
/// @audit unchecked block starts at line 211
212: return mulDiv96(liquidityChunk.liquidity(), highPriceX96 - lowPriceX96);
/// @audit unchecked block starts at line 249
258: highPriceX96 - lowPriceX96
/// @audit unchecked block starts at line 279
284: toUint128(mulDiv(amount1, Constants.FP96, highPriceX96 - lowPriceX96))
/// @audit unchecked block starts at line 345
418: inv *= 2 - denominator * inv; // inverse mod 2**8
/// @audit unchecked block starts at line 345
419: inv *= 2 - denominator * inv; // inverse mod 2**16
/// @audit unchecked block starts at line 345
420: inv *= 2 - denominator * inv; // inverse mod 2**32
/// @audit unchecked block starts at line 345
421: inv *= 2 - denominator * inv; // inverse mod 2**64
/// @audit unchecked block starts at line 345
422: inv *= 2 - denominator * inv; // inverse mod 2**128
/// @audit unchecked block starts at line 345
423: inv *= 2 - denominator * inv; // inverse mod 2**256
/// @audit unchecked block starts at line 754
758: int256 pivot = arr[uint256(left + (right - left) / 2)];
/// @audit unchecked block starts at line 777
778: quickSort(data, int256(0), int256(data.length - 1));
File: contracts/libraries/PanopticMath.sol
/// @audit unchecked block starts at line 76
77: return addr == address(0) ? 40 : 39 - Math.mostSignificantNibble(uint160(addr));
/// @audit unchecked block starts at line 100
108: : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248);
/// @audit unchecked block starts at line 132
140: (int256(observationIndex) - int256(i * period)) +
/// @audit unchecked block starts at line 132
150: (tickCumulatives[i] - tickCumulatives[i + 1]) /
/// @audit unchecked block starts at line 132
151: int256(timestamps[i] - timestamps[i + 1]);
/// @audit unchecked block starts at line 175
188: int256(observationIndex) - int256(1) + int256(observationCardinality)
/// @audit unchecked block starts at line 175
195: (tickCumulative_last - tickCumulative_old) /
/// @audit unchecked block starts at line 175
196: int256(timestamp_last - timestamp_old)
/// @audit unchecked block starts at line 175
223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1)));
/// @audit unchecked block starts at line 246
258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20))
/// @audit unchecked block starts at line 343
351: (tickLower, tickUpper) = (strike - rangeDown, strike + rangeUp);
/// @audit unchecked block starts at line 659
678: uint256 bonusCross = Math.min(balanceCross / 2, thresholdCross - balanceCross);
/// @audit unchecked block starts at line 659
685: Math.mulDiv128(bonusCross, 2 ** 128 - requiredRatioX128),
/// @audit unchecked block starts at line 659
693: int256 balance0 = int256(uint256(tokenData0.rightSlot())) -
694: Math.max(premia.rightSlot(), 0);
/// @audit unchecked block starts at line 659
695: int256 balance1 = int256(uint256(tokenData1.rightSlot())) -
696: Math.max(premia.leftSlot(), 0);
/// @audit unchecked block starts at line 778
890: uint128(-_premiasByLeg[i][leg].rightSlot()) - settled0
/// @audit unchecked block starts at line 778
894: uint128(-_premiasByLeg[i][leg].leftSlot()) - settled1
/// @audit unchecked block starts at line 925
928: int256 balanceShortage = refundValues.rightSlot() -
929: int256(collateral0.convertToAssets(collateral0.balanceOf(refunder)));
/// @audit unchecked block starts at line 925
935: .toRightSlot(int128(refundValues.rightSlot() - balanceShortage))
/// @audit unchecked block starts at line 925
946: refundValues.leftSlot() -
947: int256(collateral1.convertToAssets(collateral1.balanceOf(refunder)));
/// @audit unchecked block starts at line 925
953: .toLeftSlot(int128(refundValues.leftSlot() - balanceShortage))
File: contracts/types/LeftRight.sol
/// @audit unchecked block starts at line 175
178: z = LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(x) - LeftRightUnsigned.unwrap(y));
/// @audit unchecked block starts at line 233
234: int256 left256 = int256(x.leftSlot()) - y.leftSlot();
/// @audit unchecked block starts at line 233
237: int256 right256 = int256(x.rightSlot()) - y.rightSlot();
/// @audit unchecked block starts at line 255
256: int256 left256 = int256(x.leftSlot()) - y.leftSlot();
/// @audit unchecked block starts at line 255
259: int256 right256 = int256(x.rightSlot()) - y.rightSlot();
File: contracts/types/TokenId.sol
/// @audit unchecked block starts at line 367
395: ((LONG_MASK >> (48 * (4 - optionRatios))) & CLEAR_POOLID_MASK)
/// @audit unchecked block starts at line 579
589: if ((currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown)) {
</details>
<a id="l-17"></a> [L-17] Vulnerable versions of packages are being used
Description:
This project's dependencies are vulnerable to the Common Vulnerabilities and Exposures (CVEs) listed below.
Recommendation:
Although the CVEs may involve code not in use by this project, consider switching to more recent versions of these packages that do not have these vulnerabilities, to avoid trying to determine whether there is vulnerable code from these packages in use.
Instances:
There are 3 instances of this issue.
-
CVE-2023-34234 - Severity: <span style="color:yellow">Medium</span> - OpenZeppelin Contracts is a library for smart contract development. By frontrunning the creation of a proposal, an attacker can become the proposer and gain the ability to cancel it. The attacker can do this repeatedly to try to prevent a proposal from being proposed at all. This impacts the Governor
contract in v4.9.0 only, and the GovernorCompatibilityBravo
contract since v4.3.0. This problem has been patched in 4.9.1 by introducing opt-in frontrunning protection. Users are advised to upgrade. Users unable to upgrade may submit the proposal creation transaction to an endpoint with frontrunning protection as a workaround.
-
CVE-2023-34459 - Severity: <span style="color:yellow">Medium</span> - OpenZeppelin Contracts is a library for smart contract development. Starting in version 4.7.0 and prior to version 4.9.2, when the verifyMultiProof
, verifyMultiProofCalldata
, procesprocessMultiProof
, or processMultiProofCalldat
functions are in use, it is possible to construct merkle trees that allow forging a valid multiproof for an arbitrary set of leaves. A contract may be vulnerable if it uses multiproofs for verification and the merkle tree that is processed includes a node with value 0 at depth 1 (just under the root). This could happen inadvertedly for balanced trees with 3 leaves or less, if the leaves are not hashed. This could happen deliberately if a malicious tree builder includes such a node in the tree. A contract is not vulnerable if it uses single-leaf proving (verify
, verifyCalldata
, processProof
, or processProofCalldata
), or if it uses multiproofs with a known tree that has hashed leaves. Standard merkle trees produced or validated with the @openzeppelin/merkle-tree library are safe. The problem has been patched in version 4.9.2. Some workarounds are available. For those using multiproofs: When constructing merkle trees hash the leaves and do not insert empty nodes in your trees. Using the @openzeppelin/merkle-tree package eliminates this issue. Do not accept user-provided merkle roots without reconstructing at least the first level of the tree. Verify the merkle tree structure by reconstructing it from the leaves.
-
CVE-2023-40014 - Severity: <span style="color:yellow">Medium</span> - OpenZeppelin Contracts is a library for secure smart contract development. Starting in version 4.0.0 and prior to version 4.9.3, contracts using ERC2771Context
along with a custom trusted forwarder may see _msgSender
return address(0)
in calls that originate from the forwarder with calldata shorter than 20 bytes. This combination of circumstances does not appear to be common, in particular it is not the case for MinimalForwarder
from OpenZeppelin Contracts, or any deployed forwarder the team is aware of, given that the signer address is appended to all calls that originate from these forwarders. The problem has been patched in v4.9.3.
<a id="details-non-critical-issues"></a> Non-Critical Issues
<a id="n-01"></a> [N-01] Add inline comments for unnamed variables
Description:
Unnamed function argument variables should have have an inline comment providing a name that will indicate the meaning. For example:
// Before:
function approve(address token, address)
// After:
function approve(address token, address /* spender */)
Instances:
There are 2 instances of this issue.
File: contracts/CollateralTracker.sol
/// @audit parameters: 1st
392: function maxDeposit(address) external pure returns (uint256 maxAssets) {
/// @audit parameters: 1st
444: function maxMint(address) external view returns (uint256 maxShares) {
<a id="n-02"></a> [N-02] Array parameter lengths not validated
Description:
A function has two or more arrays passed into it, but the length of the arrays are not validated to be of equal length.
Recommendation:
When two or more array parameters are passed to a function, if their lengths need to match so that the function logic executes successfully, the array lengths should be validated to confirm they are matching prior to use of the arrays.
Instances:
There are 7 instances of this issue.
<details><summary>View 7 Instances</summary>
File: contracts/PanopticPool.sol
586: function burnOptions(
587: TokenId[] calldata positionIdList,
588: TokenId[] calldata newPositionIdList,
589: int24 tickLimitLow,
590: int24 tickLimitHigh
591: ) external {
1017: function liquidate(
1018: TokenId[] calldata positionIdListLiquidator,
1019: address liquidatee,
1020: LeftRightUnsigned delegations,
1021: TokenId[] calldata positionIdList
1022: ) external {
1179: function forceExercise(
1180: address account,
1181: TokenId[] calldata touchedId,
1182: TokenId[] calldata positionIdListExercisee,
1183: TokenId[] calldata positionIdListExercisor
1184: ) external {
File: contracts/SemiFungiblePositionManager.sol
566: function safeBatchTransferFrom(
567: address from,
568: address to,
569: uint256[] calldata ids,
570: uint256[] calldata amounts,
571: bytes calldata data
572: ) public override {
File: contracts/libraries/PanopticMath.sol
768: function haircutPremia(
769: address liquidatee,
770: TokenId[] memory positionIdList,
771: LeftRightSigned[4][] memory premiasByLeg,
772: LeftRightSigned collateralRemaining,
773: CollateralTracker collateral0,
774: CollateralTracker collateral1,
775: uint160 sqrtPriceX96Final,
776: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens
777: ) external returns (int256, int256) {
File: contracts/tokens/ERC1155Minimal.sol
130: function safeBatchTransferFrom(
131: address from,
132: address to,
133: uint256[] calldata ids,
134: uint256[] calldata amounts,
135: bytes calldata data
136: ) public virtual {
178: function balanceOfBatch(
179: address[] calldata owners,
180: uint256[] calldata ids
181: ) public view returns (uint256[] memory balances) {
</details>
<a id="n-03"></a> [N-03] Assembly blocks should have extensive comments
Description:
Assembly code, though generally more efficient than its Solidity equivalent, can be more difficult to understand and audit than normal Solidity code.
Recommendation:
To make code comprehension and maintenance easier, consider adding comments explaining what is being done in the assembly code block.
Instances:
There are 21 instances of this issue.
<details><summary>View 21 Instances</summary>
File: contracts/libraries/Math.sol
353: assembly ("memory-safe") {
354: let mm := mulmod(a, b, not(0))
355: prod0 := mul(a, b)
356: prod1 := sub(sub(mm, prod0), lt(mm, prod0))
357: }
362: assembly ("memory-safe") {
363: result := div(prod0, denominator)
364: }
379: assembly ("memory-safe") {
380: remainder := mulmod(a, b, denominator)
381: }
383: assembly ("memory-safe") {
384: prod1 := sub(prod1, gt(remainder, prod0))
385: prod0 := sub(prod0, remainder)
386: }
393: assembly ("memory-safe") {
394: denominator := div(denominator, twos)
395: }
398: assembly ("memory-safe") {
399: prod0 := div(prod0, twos)
400: }
404: assembly ("memory-safe") {
405: twos := add(div(sub(0, twos), twos), 1)
406: }
467: assembly ("memory-safe") {
468: let mm := mulmod(a, b, not(0))
469: prod0 := mul(a, b)
470: prod1 := sub(sub(mm, prod0), lt(mm, prod0))
471: }
493: assembly ("memory-safe") {
494: remainder := mulmod(a, b, 0x10000000000000000)
495: }
497: assembly ("memory-safe") {
498: prod1 := sub(prod1, gt(remainder, prod0))
499: prod0 := sub(prod0, remainder)
500: }
530: assembly ("memory-safe") {
531: let mm := mulmod(a, b, not(0))
532: prod0 := mul(a, b)
533: prod1 := sub(sub(mm, prod0), lt(mm, prod0))
534: }
556: assembly ("memory-safe") {
557: remainder := mulmod(a, b, 0x1000000000000000000000000)
558: }
560: assembly ("memory-safe") {
561: prod1 := sub(prod1, gt(remainder, prod0))
562: prod0 := sub(prod0, remainder)
563: }
607: assembly ("memory-safe") {
608: let mm := mulmod(a, b, not(0))
609: prod0 := mul(a, b)
610: prod1 := sub(sub(mm, prod0), lt(mm, prod0))
611: }
633: assembly ("memory-safe") {
634: remainder := mulmod(a, b, 0x100000000000000000000000000000000)
635: }
637: assembly ("memory-safe") {
638: prod1 := sub(prod1, gt(remainder, prod0))
639: prod0 := sub(prod0, remainder)
640: }
684: assembly ("memory-safe") {
685: let mm := mulmod(a, b, not(0))
686: prod0 := mul(a, b)
687: prod1 := sub(sub(mm, prod0), lt(mm, prod0))
688: }
710: assembly ("memory-safe") {
711: remainder := mulmod(a, b, 0x1000000000000000000000000000000000000000000000000)
712: }
714: assembly ("memory-safe") {
715: prod1 := sub(prod1, gt(remainder, prod0))
716: prod0 := sub(prod0, remainder)
717: }
739: assembly ("memory-safe") {
740: result := add(div(a, b), gt(mod(a, b), 0))
741: }
File: contracts/multicall/Multicall.sol
25: assembly ("memory-safe") {
26: revert(add(result, 32), mload(result))
27: }
</details>
<a id="n-04"></a> [N-04] Complex arithmetic
Description:
The longer a string of operations is, the harder it is to understand it. To increase readability and maintainability, particularly in Solidity which often involves complex mathematical operations, it is recommended to limit the number of arithmetic operations to two to three per statement. Too many arithmetic operations in a single statement can make the code difficult to comprehend, increase the likelihood of mistakes, and complicate the process of debugging and reviewing the code.
Recommendation:
Consider splitting complex arithmetic operations into more steps, with more descriptive temporary variable names, and add extensive comments.
Instances:
There are 40 instances of this issue.
<details><summary>View 40 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit arithmetic operator count: 9
201: TICK_DEVIATION = uint256(
202: 2230 +
203: (12500 * ratioTick) /
204: 10_000 +
205: (7812 * ratioTick ** 2) /
206: 10_000 ** 2 +
207: (6510 * ratioTick ** 3) /
208: 10_000 ** 3
209: );
/// @audit arithmetic operator count: 4
730: exerciseFees = exerciseFees
731: .toRightSlot(int128((longAmounts.rightSlot() * fee) / DECIMALS_128))
732: .toLeftSlot(int128((longAmounts.leftSlot() * fee) / DECIMALS_128));
/// @audit arithmetic operator count: 6
795: return
796: min_sell_ratio +
797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) /
798: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL);
/// @audit arithmetic operator count: 6
848: return
849: (BUYER_COLLATERAL_RATIO +
850: (BUYER_COLLATERAL_RATIO * (SATURATED_POOL_UTIL - utilization)) /
851: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL)) / 2; // do the division by 2 at the end after all addition and multiplication; b/c y1 = buyCollateralRatio / 2
/// @audit arithmetic operator count: 4
1362: uint160 ratio = tokenType == 1 // tokenType
1363: ? Math.getSqrtRatioAtTick(
1364: Math.max24(2 * (atTick - strike), Constants.MIN_V3POOL_TICK)
1365: ) // puts -> price/strike
1366: : Math.getSqrtRatioAtTick(
1367: Math.max24(2 * (strike - atTick), Constants.MIN_V3POOL_TICK)
1368: ); // calls -> strike/price
/// @audit arithmetic operator count: 4
1570: spreadRequirement = (notional < notionalP)
1571: ? Math.unsafeDivRoundingUp((notionalP - notional) * contracts, notional)
1572: : Math.unsafeDivRoundingUp((notional - notionalP) * contracts, notionalP);
File: contracts/PanopticPool.sol
/// @audit arithmetic operator count: 5
308: s_miniMedian =
309: (uint256(block.timestamp) << 216) +
310: // magic number which adds (7,5,3,1,0,2,4,6) order and minTick in positions 7, 5, 3 and maxTick in 6, 4, 2
311: // see comment on s_miniMedian initialization for format of this magic number
312: (uint256(0xF590A6F276170D89E9F276170D89E9F276170D89E9000000000000)) +
313: (uint256(uint24(currentTick)) << 24) + // add to slot 4
314: (uint256(uint24(currentTick))); // add to slot 3
/// @audit arithmetic operator count: 6
1545: premiaByLeg[leg] = LeftRightSigned
1546: .wrap(0)
1547: .toRightSlot(
1548: int128(
1549: int256(
1550: ((premiumAccumulatorsByLeg[leg][0] -
1551: premiumAccumulatorLast.rightSlot()) *
1552: (liquidityChunk.liquidity())) / 2 ** 64
1553: )
1554: )
1555: )
1556: .toLeftSlot(
1557: int128(
1558: int256(
1559: ((premiumAccumulatorsByLeg[leg][1] -
1560: premiumAccumulatorLast.leftSlot()) *
1561: (liquidityChunk.liquidity())) / 2 ** 64
1562: )
1563: )
1564: );
/// @audit arithmetic operator count: 4
1633: LeftRightSigned realizedPremia = LeftRightSigned
1634: .wrap(0)
1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64)))
1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));
/// @audit arithmetic operator count: 8
1725: s_grossPremiumLast[chunkKey] = LeftRightUnsigned
1726: .wrap(0)
1727: .toRightSlot(
1728: uint128(
1729: (grossCurrent[0] *
1730: positionLiquidity +
1731: grossPremiumLast.rightSlot() *
1732: totalLiquidityBefore) / (totalLiquidity)
1733: )
1734: )
1735: .toLeftSlot(
1736: uint128(
1737: (grossCurrent[1] *
1738: positionLiquidity +
1739: grossPremiumLast.leftSlot() *
1740: totalLiquidityBefore) / (totalLiquidity)
1741: )
1742: );
/// @audit arithmetic operator count: 4
1773: return (
1774: LeftRightUnsigned
1775: .wrap(0)
1776: .toRightSlot(
1777: uint128(
1778: Math.min(
1779: (uint256(premiumOwed.rightSlot()) * settledTokens.rightSlot()) /
1780: (accumulated0 == 0 ? type(uint256).max : accumulated0),
1781: premiumOwed.rightSlot()
1782: )
1783: )
1784: )
1785: .toLeftSlot(
1786: uint128(
1787: Math.min(
1788: (uint256(premiumOwed.leftSlot()) * settledTokens.leftSlot()) /
1789: (accumulated1 == 0 ? type(uint256).max : accumulated1),
1790: premiumOwed.leftSlot()
1791: )
1792: )
1793: )
1794: );
/// @audit arithmetic operator count: 12
1928: s_grossPremiumLast[chunkKey] = totalLiquidity != 0
1929: ? LeftRightUnsigned
1930: .wrap(0)
1931: .toRightSlot(
1932: uint128(
1933: uint256(
1934: Math.max(
1935: (int256(
1936: grossPremiumLast.rightSlot() *
1937: totalLiquidityBefore
1938: ) -
1939: int256(
1940: _premiumAccumulatorsByLeg[_leg][0] *
1941: positionLiquidity
1942: )) + int256(legPremia.rightSlot() * 2 ** 64),
1943: 0
1944: )
1945: ) / totalLiquidity
1946: )
1947: )
1948: .toLeftSlot(
1949: uint128(
1950: uint256(
1951: Math.max(
1952: (int256(
1953: grossPremiumLast.leftSlot() *
1954: totalLiquidityBefore
1955: ) -
1956: int256(
1957: _premiumAccumulatorsByLeg[_leg][1] *
1958: positionLiquidity
1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
1960: 0
1961: )
1962: ) / totalLiquidity
1963: )
1964: )
1965: : LeftRightUnsigned
1966: .wrap(0)
1967: .toRightSlot(uint128(premiumAccumulatorsByLeg[_leg][0]))
1968: .toLeftSlot(uint128(premiumAccumulatorsByLeg[_leg][1]));
File: contracts/SemiFungiblePositionManager.sol
/// @audit arithmetic operator count: 4
1388: uint256 numerator = totalLiquidity ** 2 -
1389: totalLiquidity *
1390: removedLiquidity +
1391: ((removedLiquidity ** 2) / 2 ** (VEGOID));
File: contracts/libraries/Math.sol
/// @audit arithmetic operator count: 4
179: return uint160((sqrtR >> 32) + (sqrtR % (1 << 32) == 0 ? 0 : 1));
File: contracts/libraries/PanopticMath.sol
/// @audit arithmetic operator count: 8
105: return
106: addFlag
107: ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248)
108: : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248);
/// @audit arithmetic operator count: 4
138: (timestamps[i], tickCumulatives[i], , ) = univ3pool.observations(
139: uint256(
140: (int256(observationIndex) - int256(i * period)) +
141: int256(observationCardinality)
142: ) % observationCardinality
143: );
/// @audit arithmetic operator count: 5
149: ticks[i] =
150: (tickCumulatives[i] - tickCumulatives[i + 1]) /
151: int256(timestamps[i] - timestamps[i + 1]);
/// @audit arithmetic operator count: 14
177: medianTick =
178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) +
179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) /
180: 2;
/// @audit arithmetic operator count: 6
223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1)));
/// @audit arithmetic operator count: 6
226: updatedMedianData =
227: (block.timestamp << 216) +
228: (uint256(newOrderMap) << 192) +
229: uint256(uint192(medianData << 24)) +
230: uint256(uint24(lastObservedTick));
/// @audit arithmetic operator count: 4
257: twapMeasurement[i] = int24(
258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20))
259: );
/// @audit arithmetic operator count: 4
475: uint256 notional = asset == 0
476: ? convert0to1(contractSize, Math.getSqrtRatioAtTick((tickUpper + tickLower) / 2))
477: : convert1to0(contractSize, Math.getSqrtRatioAtTick((tickUpper + tickLower) / 2));
/// @audit arithmetic operator count: 4
802: (collateralDelta0, collateralDelta1) = (
803: -Math.min(
804: collateralDelta0 - longPremium.rightSlot(),
805: PanopticMath.convert1to0(
806: longPremium.leftSlot() - collateralDelta1,
807: sqrtPriceX96Final
808: )
809: ),
810: Math.min(
811: longPremium.leftSlot() - collateralDelta1,
812: PanopticMath.convert0to1(
813: collateralDelta0 - longPremium.rightSlot(),
814: sqrtPriceX96Final
815: )
816: )
817: );
/// @audit arithmetic operator count: 4
826: (collateralDelta0, collateralDelta1) = (
827: Math.min(
828: longPremium.rightSlot() - collateralDelta0,
829: PanopticMath.convert1to0(
830: collateralDelta1 - longPremium.leftSlot(),
831: sqrtPriceX96Final
832: )
833: ),
834: -Math.min(
835: collateralDelta1 - longPremium.leftSlot(),
836: PanopticMath.convert0to1(
837: longPremium.rightSlot() - collateralDelta0,
838: sqrtPriceX96Final
839: )
840: )
841: );
File: contracts/types/LiquidityChunk.sol
/// @audit arithmetic operator count: 4
76: return
77: LiquidityChunk.wrap(
78: (uint256(uint24(_tickLower)) << 232) +
79: (uint256(uint24(_tickUpper)) << 208) +
80: uint256(amount)
81: );
File: contracts/types/TokenId.sol
/// @audit arithmetic operator count: 4
110: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48)) % 2);
/// @audit arithmetic operator count: 5
120: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 1)) % 128);
/// @audit arithmetic operator count: 5
130: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 8)) % 2);
/// @audit arithmetic operator count: 5
140: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 9)) % 2);
/// @audit arithmetic operator count: 5
150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4);
/// @audit arithmetic operator count: 4
160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12)));
/// @audit arithmetic operator count: 5
171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096));
/// @audit arithmetic operator count: 5
211: return
212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48)));
/// @audit arithmetic operator count: 6
227: return
228: TokenId.wrap(
229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1))
230: );
/// @audit arithmetic operator count: 6
246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8)));
/// @audit arithmetic operator count: 6
261: return
262: TokenId.wrap(
263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9))
264: );
/// @audit arithmetic operator count: 6
279: return
280: TokenId.wrap(
281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10))
282: );
/// @audit arithmetic operator count: 5
297: return
298: TokenId.wrap(
299: TokenId.unwrap(self) +
300: uint256((int256(_strike) & BITMASK_INT24) << (64 + legIndex * 48 + 12))
301: );
/// @audit arithmetic operator count: 6
317: return
318: TokenId.wrap(
319: TokenId.unwrap(self) +
320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36))
321: );
/// @audit arithmetic operator count: 4
392: return
393: TokenId.wrap(
394: TokenId.unwrap(self) ^
395: ((LONG_MASK >> (48 * (4 - optionRatios))) & CLEAR_POOLID_MASK)
396: );
</details>
<a id="n-05"></a> [N-05] Complex casting
Description:
Consider whether the number of casts is really necessary, or whether using a different type would be more appropriate. Alternatively, add comments to explain in detail why the casts are necessary, and any implicit reasons why the cast does not introduce an overflow.
Instances:
There are 101 instances of this issue.
<details><summary>View 101 Instances</summary>
File: contracts/CollateralTracker.sol
667: int24 range = int24(
668: int256(
669: Math.unsafeDivRoundingUp(
670: uint24(positionId.width(leg) * positionId.tickSpacing()),
671: 2
672: )
673: )
674: );
668: int256(
669: Math.unsafeDivRoundingUp(
670: uint24(positionId.width(leg) * positionId.tickSpacing()),
671: 2
672: )
673: )
715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0))
715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0))
718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1))
718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1))
959: uint256(Math.max(1, int256(totalAssets()) - int256(assets)))
1003: int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;
1028: s_poolAssets = uint128(uint256(updatedAssets));
1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount)));
1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount)));
1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount)));
1052: int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;
1084: s_poolAssets = uint128(uint256(updatedAssets + realizedPremium));
1085: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount)));
1085: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount)));
1085: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount)));
1119: exchangedAmount += int256(
1120: Math.unsafeDivRoundingUp(
1121: uint256(uint128(shortAmount + longAmount)) * COMMISSION_FEE,
1122: DECIMALS
1123: )
1124: );
1121: uint256(uint128(shortAmount + longAmount)) * COMMISSION_FEE,
1184: netBalance += uint256(uint128(premiumAllPositions));
1329: ? int64(uint64(poolUtilization))
1330: : int64(uint64(poolUtilization >> 64));
1585: ? int64(uint64(poolUtilization))
1586: : int64(uint64(poolUtilization >> 64))
1637: uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) +
1637: uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) +
1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
File: contracts/PanopticPool.sol
313: (uint256(uint24(currentTick)) << 24) + // add to slot 4
314: (uint256(uint24(currentTick))); // add to slot 3
730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64));
730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64));
1144: uint256(int256(uint256(_delegations.rightSlot())) + liquidationBonus0)
1144: uint256(int256(uint256(_delegations.rightSlot())) + liquidationBonus0)
1149: uint256(int256(uint256(_delegations.leftSlot())) + liquidationBonus1)
1149: uint256(int256(uint256(_delegations.leftSlot())) + liquidationBonus1)
1548: int128(
1549: int256(
1550: ((premiumAccumulatorsByLeg[leg][0] -
1551: premiumAccumulatorLast.rightSlot()) *
1552: (liquidityChunk.liquidity())) / 2 ** 64
1553: )
1554: )
1557: int128(
1558: int256(
1559: ((premiumAccumulatorsByLeg[leg][1] -
1560: premiumAccumulatorLast.leftSlot()) *
1561: (liquidityChunk.liquidity())) / 2 ** 64
1562: )
1563: )
1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64)))
1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));
1777: uint128(
1778: Math.min(
1779: (uint256(premiumOwed.rightSlot()) * settledTokens.rightSlot()) /
1780: (accumulated0 == 0 ? type(uint256).max : accumulated0),
1781: premiumOwed.rightSlot()
1782: )
1783: )
1786: uint128(
1787: Math.min(
1788: (uint256(premiumOwed.leftSlot()) * settledTokens.leftSlot()) /
1789: (accumulated1 == 0 ? type(uint256).max : accumulated1),
1790: premiumOwed.leftSlot()
1791: )
1792: )
1867: uint256(
1868: LeftRightSigned.unwrap(
1869: LeftRightSigned
1870: .wrap(int256(LeftRightUnsigned.unwrap(settledTokens)))
1871: .sub(legPremia)
1872: )
1873: )
1932: uint128(
1933: uint256(
1934: Math.max(
1935: (int256(
1936: grossPremiumLast.rightSlot() *
1937: totalLiquidityBefore
1938: ) -
1939: int256(
1940: _premiumAccumulatorsByLeg[_leg][0] *
1941: positionLiquidity
1942: )) + int256(legPremia.rightSlot() * 2 ** 64),
1943: 0
1944: )
1945: ) / totalLiquidity
1946: )
1933: uint256(
1934: Math.max(
1935: (int256(
1936: grossPremiumLast.rightSlot() *
1937: totalLiquidityBefore
1938: ) -
1939: int256(
1940: _premiumAccumulatorsByLeg[_leg][0] *
1941: positionLiquidity
1942: )) + int256(legPremia.rightSlot() * 2 ** 64),
1943: 0
1944: )
1945: ) / totalLiquidity
1949: uint128(
1950: uint256(
1951: Math.max(
1952: (int256(
1953: grossPremiumLast.leftSlot() *
1954: totalLiquidityBefore
1955: ) -
1956: int256(
1957: _premiumAccumulatorsByLeg[_leg][1] *
1958: positionLiquidity
1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
1960: 0
1961: )
1962: ) / totalLiquidity
1963: )
1950: uint256(
1951: Math.max(
1952: (int256(
1953: grossPremiumLast.leftSlot() *
1954: totalLiquidityBefore
1955: ) -
1956: int256(
1957: _premiumAccumulatorsByLeg[_leg][1] *
1958: positionLiquidity
1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
1960: 0
1961: )
1962: ) / totalLiquidity
File: contracts/SemiFungiblePositionManager.sol
1169: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside0LastX128, liquidity)))
1172: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside1LastX128, liquidity)))
1176: .toRightSlot(int128(int256(Math.mulDiv128(feeGrowthInside0LastX128, liquidity))))
1177: .toLeftSlot(int128(int256(Math.mulDiv128(feeGrowthInside1LastX128, liquidity))));
1214: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(int128(int256(amount0))).toLeftSlot(
1215: int128(int256(amount1))
1241: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(-int128(int256(amount0))).toLeftSlot(
1242: -int128(int256(amount1))
File: contracts/libraries/FeesCalc.sol
117: .toRightSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken0X128, liquidity))))
118: .toLeftSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken1X128, liquidity))));
File: contracts/libraries/Math.sol
130: uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
130: uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
131: if (absTick > uint256(int256(Constants.MAX_V3POOL_TICK))) revert Errors.InvalidTick();
File: contracts/libraries/PanopticMath.sol
50: uint64 poolId = uint64(uint160(univ3pool) >> 112);
51: poolId += uint64(uint24(tickSpacing)) << 48;
103: (uint248(uint256(keccak256(abi.encode(tokenId)))));
139: uint256(
140: (int256(observationIndex) - int256(i * period)) +
141: int256(observationCardinality)
142: ) % observationCardinality
178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) +
178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) +
179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) /
179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) /
183: if (block.timestamp >= uint256(uint40(medianData >> 216)) + period) {
187: uint256(
188: int256(observationIndex) - int256(1) + int256(observationCardinality)
189: ) % observationCardinality
194: lastObservedTick = int24(
195: (tickCumulative_last - tickCumulative_old) /
196: int256(timestamp_last - timestamp_old)
197: );
217: entry = int24(uint24(medianData >> (rank * 24)));
229: uint256(uint192(medianData << 24)) +
230: uint256(uint24(lastObservedTick));
257: twapMeasurement[i] = int24(
258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20))
259: );
258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20))
377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2)))
377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2)))
693: int256 balance0 = int256(uint256(tokenData0.rightSlot())) -
695: int256 balance1 = int256(uint256(tokenData1.rightSlot())) -
937: int128(
938: int256(
939: PanopticMath.convert0to1(uint256(balanceShortage), sqrtPriceX96)
940: ) + refundValues.leftSlot()
941: )
938: int256(
939: PanopticMath.convert0to1(uint256(balanceShortage), sqrtPriceX96)
940: ) + refundValues.leftSlot()
955: int128(
956: int256(
957: PanopticMath.convert1to0(uint256(balanceShortage), sqrtPriceX96)
958: ) + refundValues.rightSlot()
959: )
956: int256(
957: PanopticMath.convert1to0(uint256(balanceShortage), sqrtPriceX96)
958: ) + refundValues.rightSlot()
File: contracts/types/LeftRight.sol
27: int256(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000));
69: uint256(uint128(LeftRightUnsigned.unwrap(self)) + right)
89: (int256(int128(LeftRightSigned.unwrap(self)) + right) & RIGHT_HALF_BIT_MASK)
196: int256 left = int256(uint256(x.leftSlot())) + y.leftSlot();
201: int256 right = int256(uint256(x.rightSlot())) + y.rightSlot();
File: contracts/types/LiquidityChunk.sol
78: (uint256(uint24(_tickLower)) << 232) +
79: (uint256(uint24(_tickUpper)) << 208) +
109: LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232)
126: LiquidityChunk.unwrap(self) + ((uint256(uint24(_tickUpper))) << 208)
173: return int24(int256(LiquidityChunk.unwrap(self) >> 232));
182: return int24(int256(LiquidityChunk.unwrap(self) >> 208));
File: contracts/types/TokenId.sol
98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16));
160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12)));
171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096));
195: return TokenId.wrap(TokenId.unwrap(self) + (uint256(uint24(_tickSpacing)) << 48));
300: uint256((int256(_strike) & BITMASK_INT24) << (64 + legIndex * 48 + 12))
320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36))
</details>
<a id="n-06"></a> [N-06] Consider adding a block/deny-list
Description:
Adding a block/deny list increases centralization, but will help to prevent hackers from using stolen tokens.
Instances:
There are 6 instances of this issue.
<details><summary>View 6 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit ERC20 handles tokens
36: contract CollateralTracker is ERC20Minimal, Multicall {
File: contracts/PanopticPool.sol
/// @audit ERC1155 handles tokens
27: contract PanopticPool is ERC1155Holder, Multicall {
File: contracts/SemiFungiblePositionManager.sol
/// @audit ERC1155 handles tokens
72: contract SemiFungiblePositionManager is ERC1155, Multicall {
File: contracts/tokens/ERC1155Minimal.sol
/// @audit ERC1155 handles tokens
11: abstract contract ERC1155 {
File: contracts/tokens/ERC20Minimal.sol
/// @audit ERC20 handles tokens
9: abstract contract ERC20Minimal {
File: contracts/tokens/interfaces/IERC20Partial.sol
/// @audit ERC20 handles tokens
11: interface IERC20Partial {
</details>
<a id="n-07"></a> [N-07] Consider adding formal verification proofs
Description:
Consider using formal verification to mathematically prove that your code does what is intended, and does not have any edge cases with unexpected behavior. The Solidity compiler has this functionality built in. See SMTChecker and Formal Verification for more information. Use of the SMTChecker module was not detected based on the project configuration.
Instances:
There is 1 instance of this issue.
<a id="n-08"></a> [N-08] Consider adding validation of user inputs
Description:
There are no validations done on the arguments below. Input validation helps to ensure that the function receives valid and expected inputs. Without proper validation, malicious users or even accidental errors could pass invalid data, leading to unexpected behavior or vulnerabilities in the contract.
Recommendation:
Add validation to prevent use of an invalid/undesirable value.
Instances:
There are 28 instances of this issue.
<details><summary>View 28 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit not validated: recipient, amount
323: function transfer(
324: address recipient,
325: uint256 amount
326: ) public override(ERC20Minimal) returns (bool) {
/// @audit not validated: to, amount
341: function transferFrom(
342: address from,
343: address to,
344: uint256 amount
345: ) public override(ERC20Minimal) returns (bool) {
/// @audit not validated: receiver
417: function deposit(uint256 assets, address receiver) external returns (uint256 shares) {
/// @audit not validated: shares, receiver
477: function mint(uint256 shares, address receiver) external returns (uint256 assets) {
/// @audit not validated: assets, receiver, owner
531: function withdraw(
532: uint256 assets,
533: address receiver,
534: address owner
535: ) external returns (uint256 shares) {
/// @audit not validated: shares, receiver, owner
591: function redeem(
592: uint256 shares,
593: address receiver,
594: address owner
595: ) external returns (uint256 assets) {
/// @audit not validated: currentTick, oracleTick, positionId, positionBalance, longAmounts
650: function exerciseCost(
651: int24 currentTick,
652: int24 oracleTick,
653: TokenId positionId,
654: uint128 positionBalance,
655: LeftRightSigned longAmounts
656: ) external view returns (LeftRightSigned exerciseFees) {
/// @audit not validated: delegator, delegatee, assets
911: function revoke(
912: address delegator,
913: address delegatee,
914: uint256 assets
915: ) external onlyPanopticPool {
/// @audit not validated: refunder, refundee
975: function refund(address refunder, address refundee, int256 assets) external onlyPanopticPool {
/// @audit not validated: optionOwner, longAmount, shortAmount, swappedAmount
995: function takeCommissionAddData(
996: address optionOwner,
997: int128 longAmount,
998: int128 shortAmount,
999: int128 swappedAmount
1000: ) external onlyPanopticPool returns (int256 utilization) {
/// @audit not validated: optionOwner, longAmount, shortAmount, swappedAmount, realizedPremium
1043: function exercise(
1044: address optionOwner,
1045: int128 longAmount,
1046: int128 shortAmount,
1047: int128 swappedAmount,
1048: int128 realizedPremium
1049: ) external onlyPanopticPool returns (int128) {
File: contracts/PanopticFactory.sol
/// @audit not validated: newOwner
147: function transferOwnership(address newOwner) external {
/// @audit not validated: amount0Owed, amount1Owed, data
172: function uniswapV3MintCallback(
173: uint256 amount0Owed,
174: uint256 amount1Owed,
175: bytes calldata data
176: ) external {
/// @audit not validated: token0, token1, fee, salt
210: function deployNewPool(
211: address token0,
212: address token1,
213: uint24 fee,
214: bytes32 salt
215: ) external returns (PanopticPool newPoolContract) {
/// @audit not validated: salt, loops, minTargetRarity
290: function minePoolAddress(
291: bytes32 salt,
292: uint256 loops,
293: uint256 minTargetRarity
294: ) external view returns (bytes32 bestSalt, uint256 highestRarity) {
File: contracts/PanopticPool.sol
/// @audit not validated: _univ3pool, token0, token1, collateralTracker0, collateralTracker1
291: function startPool(
292: IUniswapV3Pool _univ3pool,
293: address token0,
294: address token1,
295: CollateralTracker collateralTracker0,
296: CollateralTracker collateralTracker1
297: ) external {
/// @audit not validated: sqrtLowerBound, sqrtUpperBound
338: function assertPriceWithinBounds(uint160 sqrtLowerBound, uint160 sqrtUpperBound) external view {
/// @audit not validated: positionIdListLiquidator, liquidatee, delegations, positionIdList
1017: function liquidate(
1018: TokenId[] calldata positionIdListLiquidator,
1019: address liquidatee,
1020: LeftRightUnsigned delegations,
1021: TokenId[] calldata positionIdList
1022: ) external {
/// @audit not validated: account, touchedId, positionIdListExercisee, positionIdListExercisor
1179: function forceExercise(
1180: address account,
1181: TokenId[] calldata touchedId,
1182: TokenId[] calldata positionIdListExercisee,
1183: TokenId[] calldata positionIdListExercisor
1184: ) external {
File: contracts/SemiFungiblePositionManager.sol
/// @audit not validated: token0, token1, fee
350: function initializeAMMPool(address token0, address token1, uint24 fee) external {
/// @audit not validated: amount0Owed, amount1Owed, data
402: function uniswapV3MintCallback(
403: uint256 amount0Owed,
404: uint256 amount1Owed,
405: bytes calldata data
406: ) external {
/// @audit not validated: amount1Delta, data
435: function uniswapV3SwapCallback(
436: int256 amount0Delta,
437: int256 amount1Delta,
438: bytes calldata data
439: ) external {
/// @audit not validated: from, to, amounts, data
566: function safeBatchTransferFrom(
567: address from,
568: address to,
569: uint256[] calldata ids,
570: uint256[] calldata amounts,
571: bytes calldata data
572: ) public override {
/// @audit not validated: univ3pool, owner, tokenType, tickLower, tickUpper, atTick, isLong
1449: function getAccountPremium(
1450: address univ3pool,
1451: address owner,
1452: uint256 tokenType,
1453: int24 tickLower,
1454: int24 tickUpper,
1455: int24 atTick,
1456: uint256 isLong
1457: ) external view returns (uint128, uint128) {
File: contracts/tokens/ERC1155Minimal.sol
/// @audit not validated: from, to, id, amount, data
94: function safeTransferFrom(
95: address from,
96: address to,
97: uint256 id,
98: uint256 amount,
99: bytes calldata data
100: ) public virtual {
/// @audit not validated: from, to, ids, amounts, data
130: function safeBatchTransferFrom(
131: address from,
132: address to,
133: uint256[] calldata ids,
134: uint256[] calldata amounts,
135: bytes calldata data
136: ) public virtual {
/// @audit not validated: ids
178: function balanceOfBatch(
179: address[] calldata owners,
180: uint256[] calldata ids
181: ) public view returns (uint256[] memory balances) {
File: contracts/tokens/ERC20Minimal.sol
/// @audit not validated: from, to, amount
81: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
</details>
<a id="n-09"></a> [N-09] Consider bounding input array length
Description:
The functions below take in an unbounded array, and make function calls for entries in the array. While the function will revert if it eventually runs out of gas, it may be a better user experience to require()
that the length of the array is below some reasonable maximum, so that the user does not have to use up a full transaction's gas only to see that the transaction reverts.
Instances:
There are 4 instances of this issue.
File: contracts/PanopticPool.sol
/// @audit function: _burnAllOptionsFrom(), array parameter: positionIdList[]
802: for (uint256 i = 0; i < positionIdList.length; ) {
803: LeftRightSigned paidAmounts;
804: (paidAmounts, premiasByLeg[i]) = _burnOptions(
805: commitLongSettled,
806: positionIdList[i],
807: owner,
808: tickLimitLow,
809: tickLimitHigh
810: );
811: netPaid = netPaid.add(paidAmounts);
812: unchecked {
813: ++i;
814: }
815: }
File: contracts/SemiFungiblePositionManager.sol
/// @audit function: safeBatchTransferFrom(), array parameter: ids[]
575: for (uint256 i = 0; i < ids.length; ) {
576: if (s_poolContext[TokenId.wrap(ids[i]).poolId()].locked) revert Errors.ReentrantCall();
577: registerTokenTransfer(from, to, TokenId.wrap(ids[i]), amounts[i]);
578: unchecked {
579: ++i;
580: }
581: }
File: contracts/libraries/PanopticMath.sol
/// @audit function: haircutPremia(), array parameter: positionIdList[]
860: for (uint256 i = 0; i < positionIdList.length; i++) {
861: TokenId tokenId = positionIdList[i];
862: LeftRightSigned[4][] memory _premiasByLeg = premiasByLeg;
863: for (uint256 leg = 0; leg < tokenId.countLegs(); ++leg) {
864: if (tokenId.isLong(leg) == 1) {
865: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens)
866: storage _settledTokens = settledTokens;
867:
868: // calculate amounts to revoke from settled and subtract from haircut req
869: uint256 settled0 = Math.unsafeDivRoundingUp(
870: uint128(-_premiasByLeg[i][leg].rightSlot()) * uint256(haircut0),
871: uint128(longPremium.rightSlot())
872: );
873: uint256 settled1 = Math.unsafeDivRoundingUp(
874: uint128(-_premiasByLeg[i][leg].leftSlot()) * uint256(haircut1),
875: uint128(longPremium.leftSlot())
876: );
877:
878: bytes32 chunkKey = keccak256(
879: abi.encodePacked(
880: tokenId.strike(0),
881: tokenId.width(0),
882: tokenId.tokenType(0)
883: )
884: );
885:
886: // The long premium is not commited to storage during the liquidation, so we add the entire adjusted amount
887: // for the haircut directly to the accumulator
888: settled0 = Math.max(
889: 0,
890: uint128(-_premiasByLeg[i][leg].rightSlot()) - settled0
891: );
892: settled1 = Math.max(
893: 0,
894: uint128(-_premiasByLeg[i][leg].leftSlot()) - settled1
895: );
896:
897: _settledTokens[chunkKey] = _settledTokens[chunkKey].add(
898: LeftRightUnsigned.wrap(0).toRightSlot(uint128(settled0)).toLeftSlot(
899: uint128(settled1)
900: )
901: );
902: }
903: }
904: }
File: contracts/multicall/Multicall.sol
/// @audit function: multicall(), array parameter: data[]
14: for (uint256 i = 0; i < data.length; ) {
15: (bool success, bytes memory result) = address(this).delegatecall(data[i]);
16:
17: if (!success) {
18: // Bubble up the revert reason
19: // The bytes type is ABI encoded as a length-prefixed byte array
20: // So we simply need to add 32 to the pointer to get the start of the data
21: // And then revert with the size loaded from the first 32 bytes
22: // Other solutions will do work to differentiate the revert reasons and provide paranthetical information
23: // However, we have chosen to simply replicate the the normal behavior of the call
24: // NOTE: memory-safe because it reads from memory already allocated by solidity (the bytes memory result)
25: assembly ("memory-safe") {
26: revert(add(result, 32), mload(result))
27: }
28: }
29:
30: results[i] = result;
31:
32: unchecked {
33: ++i;
34: }
35: }
<a id="n-10"></a> [N-10] Consider emitting an event at the end of the constructor
Description:
Consider emitting an event when the contract is constructed to make it easier for off-chain tools to track when and by whom the contract was constructed.
Instances:
There are 4 instances of this issue.
File: contracts/CollateralTracker.sol
178: constructor(
179: uint256 _commissionFee,
180: uint256 _sellerCollateralRatio,
181: uint256 _buyerCollateralRatio,
182: int256 _forceExerciseCost,
183: uint256 _targetPoolUtilization,
184: uint256 _saturatedPoolUtilization,
185: uint256 _ITMSpreadMultiplier
186: ) {
File: contracts/PanopticFactory.sol
115: constructor(
116: address _WETH9,
117: SemiFungiblePositionManager _SFPM,
118: IUniswapV3Factory _univ3Factory,
119: IDonorNFT _donorNFT,
120: address _poolReference,
121: address _collateralReference
122: ) {
File: contracts/PanopticPool.sol
280: constructor(SemiFungiblePositionManager _sfpm) {
File: contracts/SemiFungiblePositionManager.sol
341: constructor(IUniswapV3Factory _factory) {
<a id="n-11"></a> [N-11] Consider making contracts Upgradeable
Description:
Making a contract Upgradeable
allows for bugs to be fixed in production, at the expense of significantly increasing centralization.
Instances:
There are 4 instances of this issue.
File: contracts/CollateralTracker.sol
36: contract CollateralTracker is ERC20Minimal, Multicall {
File: contracts/PanopticFactory.sol
26: contract PanopticFactory is Multicall {
File: contracts/PanopticPool.sol
27: contract PanopticPool is ERC1155Holder, Multicall {
File: contracts/SemiFungiblePositionManager.sol
72: contract SemiFungiblePositionManager is ERC1155, Multicall {
<a id="n-12"></a> [N-12] Consider returning a struct
rather than having multiple return
values
Description:
To increase readability and maintainability, it may be more efficient to return a struct
rather than having multiple return
values.
Instances:
There are 7 instances of this issue.
<details><summary>View 7 Instances</summary>
File: contracts/CollateralTracker.sol
277: function getPoolData()
278: external
279: view
280: returns (uint256 poolAssets, uint256 insideAMM, int256 currentPoolUtilization)
281: {
File: contracts/PanopticPool.sol
352: function optionPositionBalance(
353: address user,
354: TokenId tokenId
355: ) external view returns (uint128 balance, uint64 poolUtilization0, uint64 poolUtilization1) {
381: function calculateAccumulatedFeesBatch(
382: address user,
383: bool includePendingPremium,
384: TokenId[] calldata positionIdList
385: ) external view returns (int128 premium0, int128 premium1, uint256[2][] memory) {
955: function _burnAndHandleExercise(
956: bool commitLongSettled,
957: int24 tickLimitLow,
958: int24 tickLimitHigh,
959: TokenId tokenId,
960: uint128 positionSize,
961: address owner
962: )
963: internal
964: returns (
965: LeftRightSigned realizedPremia,
966: LeftRightSigned[4] memory premiaByLeg,
967: LeftRightSigned paidAmounts
968: )
969: {
File: contracts/SemiFungiblePositionManager.sol
863: function _createPositionInAMM(
864: IUniswapV3Pool univ3pool,
865: TokenId tokenId,
866: uint128 positionSize,
867: bool isBurn
868: )
869: internal
870: returns (
871: LeftRightSigned totalMoved,
872: LeftRightUnsigned[4] memory collectedByLeg,
873: LeftRightSigned itmAmounts
874: )
875: {
958: function _createLegInAMM(
959: IUniswapV3Pool univ3pool,
960: TokenId tokenId,
961: uint256 leg,
962: LiquidityChunk liquidityChunk,
963: bool isBurn
964: )
965: internal
966: returns (
967: LeftRightSigned moved,
968: LeftRightSigned itmAmounts,
969: LeftRightUnsigned collectedSingleLeg
970: )
971: {
File: contracts/libraries/PanopticMath.sol
651: function getLiquidationBonus(
652: LeftRightUnsigned tokenData0,
653: LeftRightUnsigned tokenData1,
654: uint160 sqrtPriceX96Twap,
655: uint160 sqrtPriceX96Final,
656: LeftRightSigned netExchanged,
657: LeftRightSigned premia
658: ) external pure returns (int256 bonus0, int256 bonus1, LeftRightSigned) {
</details>
<a id="n-13"></a> [N-13] Consider splitting complex checks into multiple steps
Description:
The longer a string of operations is, the harder it is to understand it. To increase readability and maintainability, particularly in Solidity which often involves complex mathematical operations, it is recommended to limit the number of comparison operations to two to three per statement. Too many comparison operations in a single statement can make the code difficult to comprehend, increase the likelihood of mistakes, and complicate the process of debugging and reviewing the code.
Instances:
There are 21 instances of this issue.
<details><summary>View 21 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit comparison operator count: 9
662: for (uint256 leg = 0; leg < positionId.countLegs(); ++leg) {
663: // short legs are not counted - exercise is intended to be based on long legs
664: if (positionId.isLong(leg) == 0) continue;
665:
666: {
667: int24 range = int24(
668: int256(
669: Math.unsafeDivRoundingUp(
670: uint24(positionId.width(leg) * positionId.tickSpacing()),
671: 2
672: )
673: )
674: );
675: maxNumRangesFromStrike = Math.max(
676: maxNumRangesFromStrike,
677: uint256(Math.abs(currentTick - positionId.strike(leg)) / range)
678: );
679: }
680:
681: uint256 currentValue0;
682: uint256 currentValue1;
683: uint256 oracleValue0;
684: uint256 oracleValue1;
685:
686: {
687: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk(
688: positionId,
689: leg,
690: positionBalance
691: );
692:
693: (currentValue0, currentValue1) = Math.getAmountsForLiquidity(
694: currentTick,
695: liquidityChunk
696: );
697:
698: (oracleValue0, oracleValue1) = Math.getAmountsForLiquidity(
699: oracleTick,
700: liquidityChunk
701: );
702: }
703:
704: uint256 tokenType = positionId.tokenType(leg);
705: // compensate user for loss in value if chunk has lost money between current and median tick
706: // note: the delta for one token will be positive and the other will be negative. This cancels out any moves in their positions
707: if (
708: (tokenType == 0 && currentValue1 < oracleValue1) ||
709: (tokenType == 1 && currentValue0 < oracleValue0)
710: )
711: exerciseFees = exerciseFees.sub(
712: LeftRightSigned
713: .wrap(0)
714: .toRightSlot(
715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0))
716: )
717: .toLeftSlot(
718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1))
719: )
720: );
721: }
/// @audit comparison operator count: 7
707: if (
708: (tokenType == 0 && currentValue1 < oracleValue1) ||
709: (tokenType == 1 && currentValue0 < oracleValue0)
710: )
711: exerciseFees = exerciseFees.sub(
712: LeftRightSigned
713: .wrap(0)
714: .toRightSlot(
715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0))
716: )
717: .toLeftSlot(
718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1))
719: )
720: );
/// @audit comparison operator count: 16
1339: if (isLong == 0) {
1340: // if position is short, check whether the position is out-the-money
1341:
1342: (int24 tickLower, int24 tickUpper) = tokenId.asTicks(index);
1343:
1344: // compute the collateral requirement as a fixed amount that doesn't depend on price
1345: if (
1346: ((atTick >= tickUpper) && (tokenType == 1)) || // strike OTM when price >= upperTick for tokenType=1
1347: ((atTick < tickLower) && (tokenType == 0)) // strike OTM when price < lowerTick for tokenType=0
1348: ) {
1349: // position is out-the-money, collateral requirement = SCR * amountMoved
1350: required;
1351: } else {
1352: int24 strike = tokenId.strike(index);
1353: // if position is ITM or ATM, then the collateral requirement depends on price:
1354:
1355: // compute the ratio of strike to price for calls (or price to strike for puts)
1356: // (- and * 2 in tick space are / and ^ 2 in price space so sqrtRatioAtTick(2 *(a - b)) = a/b (*2^96)
1357: // both of these ratios decrease as the position becomes deeper ITM, and it is possible
1358: // for the ratio of the prices to go under the minimum price
1359: // (which is the limit of what getSqrtRatioAtTick supports)
1360: // so instead we cap it at the minimum price, which is acceptable because
1361: // a higher ratio will result in an increased slope for the collateral requirement
1362: uint160 ratio = tokenType == 1 // tokenType
1363: ? Math.getSqrtRatioAtTick(
1364: Math.max24(2 * (atTick - strike), Constants.MIN_V3POOL_TICK)
1365: ) // puts -> price/strike
1366: : Math.getSqrtRatioAtTick(
1367: Math.max24(2 * (strike - atTick), Constants.MIN_V3POOL_TICK)
1368: ); // calls -> strike/price
1369:
1370: // compute the collateral requirement depending on whether the position is ITM & out-of-range or ITM and in-range:
1371:
1372: /// ITM and out-of-range
1373: if (
1374: ((atTick < tickLower) && (tokenType == 1)) || // strike ITM but out of range price < lowerTick for tokenType=1
1375: ((atTick >= tickUpper) && (tokenType == 0)) // strike ITM but out of range when price >= upperTick for tokenType=0
1376: ) {
1377: /**
1378: Short put BPR = 100% - (price/strike) + SCR
1379:
1380: BUYING
1381: POWER
1382: REQUIREMENT
1383:
1384: ^ . .
1385: | <- ITM . <-ATM-> . OTM ->
1386: 100% + SCR% - |--__ . . .
1387: 100% - | . .--__ . . .
1388: | . --__ . .
1389: SCR - | . .--__________
1390: | . . . .
1391: +----+----------+----+----+---> current
1392: 0 Liqui- Pa strike Pb price
1393: dation
1394: price = SCR*strike
1395: */
1396:
1397: uint256 c2 = Constants.FP96 - ratio;
1398:
1399: // compute the tokens required
1400: // position is in-the-money, collateral requirement = amountMoved*(1-ratio) + SCR*amountMoved
1401: required += Math.mulDiv96RoundingUp(amountMoved, c2);
1402: } else {
1403: // position is in-range (ie. current tick is between upper+lower tick): we draw a line between the
1404: // collateral requirement at the lowerTick and the one at the upperTick. We use that interpolation as
1405: // the collateral requirement when in-range, which always over-estimates the amount of token required
1406: // Specifically:
1407: // required = amountMoved * (scaleFactor - ratio) / (scaleFactor + 1) + sellCollateralRatio*amountMoved
1408: uint160 scaleFactor = Math.getSqrtRatioAtTick(
1409: (tickUpper - strike) + (strike - tickLower)
1410: );
1411: uint256 c3 = Math.mulDivRoundingUp(
1412: amountMoved,
1413: scaleFactor - ratio,
1414: scaleFactor + Constants.FP96
1415: );
1416: // position is in-the-money, collateral requirement = amountMoved*(1-SRC)*(scaleFactor-ratio)/(scaleFactor+1) + SCR*amountMoved
1417: required += c3;
1418: }
1419: }
1420: }
/// @audit comparison operator count: 15
1345: if (
1346: ((atTick >= tickUpper) && (tokenType == 1)) || // strike OTM when price >= upperTick for tokenType=1
1347: ((atTick < tickLower) && (tokenType == 0)) // strike OTM when price < lowerTick for tokenType=0
1348: ) {
1349: // position is out-the-money, collateral requirement = SCR * amountMoved
1350: required;
1351: } else {
1352: int24 strike = tokenId.strike(index);
1353: // if position is ITM or ATM, then the collateral requirement depends on price:
1354:
1355: // compute the ratio of strike to price for calls (or price to strike for puts)
1356: // (- and * 2 in tick space are / and ^ 2 in price space so sqrtRatioAtTick(2 *(a - b)) = a/b (*2^96)
1357: // both of these ratios decrease as the position becomes deeper ITM, and it is possible
1358: // for the ratio of the prices to go under the minimum price
1359: // (which is the limit of what getSqrtRatioAtTick supports)
1360: // so instead we cap it at the minimum price, which is acceptable because
1361: // a higher ratio will result in an increased slope for the collateral requirement
1362: uint160 ratio = tokenType == 1 // tokenType
1363: ? Math.getSqrtRatioAtTick(
1364: Math.max24(2 * (atTick - strike), Constants.MIN_V3POOL_TICK)
1365: ) // puts -> price/strike
1366: : Math.getSqrtRatioAtTick(
1367: Math.max24(2 * (strike - atTick), Constants.MIN_V3POOL_TICK)
1368: ); // calls -> strike/price
1369:
1370: // compute the collateral requirement depending on whether the position is ITM & out-of-range or ITM and in-range:
1371:
1372: /// ITM and out-of-range
1373: if (
1374: ((atTick < tickLower) && (tokenType == 1)) || // strike ITM but out of range price < lowerTick for tokenType=1
1375: ((atTick >= tickUpper) && (tokenType == 0)) // strike ITM but out of range when price >= upperTick for tokenType=0
1376: ) {
1377: /**
1378: Short put BPR = 100% - (price/strike) + SCR
1379:
1380: BUYING
1381: POWER
1382: REQUIREMENT
1383:
1384: ^ . .
1385: | <- ITM . <-ATM-> . OTM ->
1386: 100% + SCR% - |--__ . . .
1387: 100% - | . .--__ . . .
1388: | . --__ . .
1389: SCR - | . .--__________
1390: | . . . .
1391: +----+----------+----+----+---> current
1392: 0 Liqui- Pa strike Pb price
1393: dation
1394: price = SCR*strike
1395: */
1396:
1397: uint256 c2 = Constants.FP96 - ratio;
1398:
1399: // compute the tokens required
1400: // position is in-the-money, collateral requirement = amountMoved*(1-ratio) + SCR*amountMoved
1401: required += Math.mulDiv96RoundingUp(amountMoved, c2);
1402: } else {
1403: // position is in-range (ie. current tick is between upper+lower tick): we draw a line between the
1404: // collateral requirement at the lowerTick and the one at the upperTick. We use that interpolation as
1405: // the collateral requirement when in-range, which always over-estimates the amount of token required
1406: // Specifically:
1407: // required = amountMoved * (scaleFactor - ratio) / (scaleFactor + 1) + sellCollateralRatio*amountMoved
1408: uint160 scaleFactor = Math.getSqrtRatioAtTick(
1409: (tickUpper - strike) + (strike - tickLower)
1410: );
1411: uint256 c3 = Math.mulDivRoundingUp(
1412: amountMoved,
1413: scaleFactor - ratio,
1414: scaleFactor + Constants.FP96
1415: );
1416: // position is in-the-money, collateral requirement = amountMoved*(1-SRC)*(scaleFactor-ratio)/(scaleFactor+1) + SCR*amountMoved
1417: required += c3;
1418: }
1419: }
/// @audit comparison operator count: 7
1373: if (
1374: ((atTick < tickLower) && (tokenType == 1)) || // strike ITM but out of range price < lowerTick for tokenType=1
1375: ((atTick >= tickUpper) && (tokenType == 0)) // strike ITM but out of range when price >= upperTick for tokenType=0
1376: ) {
1377: /**
1378: Short put BPR = 100% - (price/strike) + SCR
1379:
1380: BUYING
1381: POWER
1382: REQUIREMENT
1383:
1384: ^ . .
1385: | <- ITM . <-ATM-> . OTM ->
1386: 100% + SCR% - |--__ . . .
1387: 100% - | . .--__ . . .
1388: | . --__ . .
1389: SCR - | . .--__________
1390: | . . . .
1391: +----+----------+----+----+---> current
1392: 0 Liqui- Pa strike Pb price
1393: dation
1394: price = SCR*strike
1395: */
1396:
1397: uint256 c2 = Constants.FP96 - ratio;
1398:
1399: // compute the tokens required
1400: // position is in-the-money, collateral requirement = amountMoved*(1-ratio) + SCR*amountMoved
1401: required += Math.mulDiv96RoundingUp(amountMoved, c2);
1402: } else {
1403: // position is in-range (ie. current tick is between upper+lower tick): we draw a line between the
1404: // collateral requirement at the lowerTick and the one at the upperTick. We use that interpolation as
1405: // the collateral requirement when in-range, which always over-estimates the amount of token required
1406: // Specifically:
1407: // required = amountMoved * (scaleFactor - ratio) / (scaleFactor + 1) + sellCollateralRatio*amountMoved
1408: uint160 scaleFactor = Math.getSqrtRatioAtTick(
1409: (tickUpper - strike) + (strike - tickLower)
1410: );
1411: uint256 c3 = Math.mulDivRoundingUp(
1412: amountMoved,
1413: scaleFactor - ratio,
1414: scaleFactor + Constants.FP96
1415: );
1416: // position is in-the-money, collateral requirement = amountMoved*(1-SRC)*(scaleFactor-ratio)/(scaleFactor+1) + SCR*amountMoved
1417: required += c3;
1418: }
/// @audit comparison operator count: 5
1541: if (tokenId.asset(index) != tokenType) {
1542: unchecked {
1543: // always take the absolute values of the difference of amounts moved
1544: if (tokenType == 0) {
1545: spreadRequirement = movedRight < movedPartnerRight
1546: ? movedPartnerRight - movedRight
1547: : movedRight - movedPartnerRight;
1548: } else {
1549: spreadRequirement = movedLeft < movedPartnerLeft
1550: ? movedPartnerLeft - movedLeft
1551: : movedLeft - movedPartnerLeft;
1552: }
1553: }
1554: } else {
1555: unchecked {
1556: uint256 notional;
1557: uint256 notionalP;
1558: uint128 contracts;
1559: if (tokenType == 1) {
1560: notional = movedRight;
1561: notionalP = movedPartnerRight;
1562: contracts = movedLeft;
1563: } else {
1564: notional = movedLeft;
1565: notionalP = movedPartnerLeft;
1566: contracts = movedRight;
1567: }
1568: // the required amount is the amount of contracts multiplied by (notional1 - notional2)/min(notional1, notional2)
1569: // can use unsafe because denominator is always nonzero
1570: spreadRequirement = (notional < notionalP)
1571: ? Math.unsafeDivRoundingUp((notionalP - notional) * contracts, notional)
1572: : Math.unsafeDivRoundingUp((notional - notionalP) * contracts, notionalP);
1573: }
1574: }
File: contracts/PanopticPool.sol
/// @audit comparison operator count: 4
441: for (uint256 k = 0; k < pLength; ) {
442: TokenId tokenId = positionIdList[k];
443:
444: balances[k][0] = TokenId.unwrap(tokenId);
445: balances[k][1] = LeftRightUnsigned.unwrap(s_positionBalance[c_user][tokenId]);
446:
447: (
448: LeftRightSigned[4] memory premiaByLeg,
449: uint256[2][4] memory premiumAccumulatorsByLeg
450: ) = _getPremia(
451: tokenId,
452: LeftRightUnsigned.wrap(balances[k][1]).rightSlot(),
453: c_user,
454: computeAllPremia,
455: atTick
456: );
457:
458: uint256 numLegs = tokenId.countLegs();
459: for (uint256 leg = 0; leg < numLegs; ) {
460: if (tokenId.isLong(leg) == 0 && !includePendingPremium) {
461: bytes32 chunkKey = keccak256(
462: abi.encodePacked(
463: tokenId.strike(leg),
464: tokenId.width(leg),
465: tokenId.tokenType(leg)
466: )
467: );
468:
469: LeftRightUnsigned availablePremium = _getAvailablePremium(
470: _getTotalLiquidity(tokenId, leg),
471: s_settledTokens[chunkKey],
472: s_grossPremiumLast[chunkKey],
473: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))),
474: premiumAccumulatorsByLeg[leg]
475: );
476: portfolioPremium = portfolioPremium.add(
477: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium)))
478: );
479: } else {
480: portfolioPremium = portfolioPremium.add(premiaByLeg[leg]);
481: }
482: unchecked {
483: ++leg;
484: }
485: }
486:
487: unchecked {
488: ++k;
489: }
490: }
/// @audit comparison operator count: 4
1518: for (uint256 leg = 0; leg < numLegs; ) {
1519: uint256 isLong = tokenId.isLong(leg);
1520: if ((isLong == 1) || computeAllPremia) {
1521: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk(
1522: tokenId,
1523: leg,
1524: positionSize
1525: );
1526: uint256 tokenType = tokenId.tokenType(leg);
1527:
1528: (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM
1529: .getAccountPremium(
1530: address(s_univ3pool),
1531: address(this),
1532: tokenType,
1533: liquidityChunk.tickLower(),
1534: liquidityChunk.tickUpper(),
1535: atTick,
1536: isLong
1537: );
1538:
1539: unchecked {
1540: LeftRightUnsigned premiumAccumulatorLast = s_options[owner][tokenId][leg];
1541:
1542: // if the premium accumulatorLast is higher than current, it means the premium accumulator has overflowed and rolled over at least once
1543: // we can account for one rollover by doing (acc_cur + (acc_max - acc_last))
1544: // if there are multiple rollovers or the rollover goes past the last accumulator, rolled over fees will just remain unclaimed
1545: premiaByLeg[leg] = LeftRightSigned
1546: .wrap(0)
1547: .toRightSlot(
1548: int128(
1549: int256(
1550: ((premiumAccumulatorsByLeg[leg][0] -
1551: premiumAccumulatorLast.rightSlot()) *
1552: (liquidityChunk.liquidity())) / 2 ** 64
1553: )
1554: )
1555: )
1556: .toLeftSlot(
1557: int128(
1558: int256(
1559: ((premiumAccumulatorsByLeg[leg][1] -
1560: premiumAccumulatorLast.leftSlot()) *
1561: (liquidityChunk.liquidity())) / 2 ** 64
1562: )
1563: )
1564: );
1565:
1566: if (isLong == 1) {
1567: premiaByLeg[leg] = LeftRightSigned.wrap(0).sub(premiaByLeg[leg]);
1568: }
1569: }
1570: }
1571: unchecked {
1572: ++leg;
1573: }
1574: }
File: contracts/SemiFungiblePositionManager.sol
/// @audit comparison operator count: 4
787: if ((itm0 != 0) && (itm1 != 0)) {
788: (uint160 sqrtPriceX96, , , , , , ) = _univ3pool.slot0();
789:
790: // implement a single "netting" swap. Thank you @danrobinson for this puzzle/idea
791: // note: negative ITM amounts denote a surplus of tokens (burning liquidity), while positive amounts denote a shortage of tokens (minting liquidity)
792: // compute the approximate delta of token0 that should be resolved in the swap at the current tick
793: // we do this by flipping the signs on the token1 ITM amount converting+deducting it against the token0 ITM amount
794: // couple examples (price = 2 1/0):
795: // - 100 surplus 0, 100 surplus 1 (itm0 = -100, itm1 = -100)
796: // normal swap 0: 100 0 => 200 1
797: // normal swap 1: 100 1 => 50 0
798: // final swap amounts: 50 0 => 100 1
799: // netting swap: net0 = -100 - (-100/2) = -50, ZF1 = true, 50 0 => 100 1
800: // - 100 surplus 0, 100 shortage 1 (itm0 = -100, itm1 = 100)
801: // normal swap 0: 100 0 => 200 1
802: // normal swap 1: 50 0 => 100 1
803: // final swap amounts: 150 0 => 300 1
804: // netting swap: net0 = -100 - (100/2) = -150, ZF1 = true, 150 0 => 300 1
805: // - 100 shortage 0, 100 surplus 1 (itm0 = 100, itm1 = -100)
806: // normal swap 0: 200 1 => 100 0
807: // normal swap 1: 100 1 => 50 0
808: // final swap amounts: 300 1 => 150 0
809: // netting swap: net0 = 100 - (-100/2) = 150, ZF1 = false, 300 1 => 150 0
810: // - 100 shortage 0, 100 shortage 1 (itm0 = 100, itm1 = 100)
811: // normal swap 0: 200 1 => 100 0
812: // normal swap 1: 50 0 => 100 1
813: // final swap amounts: 100 1 => 50 0
814: // netting swap: net0 = 100 - (100/2) = 50, ZF1 = false, 100 1 => 50 0
815: // - = Net surplus of token0
816: // + = Net shortage of token0
817: int256 net0 = itm0 - PanopticMath.convert1to0(itm1, sqrtPriceX96);
818:
819: zeroForOne = net0 < 0;
820:
821: //compute the swap amount, set as positive (exact input)
822: swapAmount = -net0;
823: } else if (itm0 != 0) {
824: zeroForOne = itm0 < 0;
825: swapAmount = -itm0;
826: } else {
827: zeroForOne = itm1 > 0;
828: swapAmount = -itm1;
829: }
File: contracts/libraries/Math.sol
/// @audit comparison operator count: 4
759: while (i < j) {
760: while (arr[uint256(i)] < pivot) i++;
761: while (pivot < arr[uint256(j)]) j--;
762: if (i <= j) {
763: (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]);
764: i++;
765: j--;
766: }
767: }
File: contracts/libraries/PanopticMath.sol
/// @audit comparison operator count: 5
183: if (block.timestamp >= uint256(uint40(medianData >> 216)) + period) {
184: int24 lastObservedTick;
185: {
186: (uint256 timestamp_old, int56 tickCumulative_old, , ) = univ3pool.observations(
187: uint256(
188: int256(observationIndex) - int256(1) + int256(observationCardinality)
189: ) % observationCardinality
190: );
191:
192: (uint256 timestamp_last, int56 tickCumulative_last, , ) = univ3pool
193: .observations(observationIndex);
194: lastObservedTick = int24(
195: (tickCumulative_last - tickCumulative_old) /
196: int256(timestamp_last - timestamp_old)
197: );
198: }
199:
200: uint24 orderMap = uint24(medianData >> 192);
201:
202: uint24 newOrderMap;
203: uint24 shift = 1;
204: bool below = true;
205: uint24 rank;
206: int24 entry;
207: for (uint8 i; i < 8; ++i) {
208: // read the rank from the existing ordering
209: rank = (orderMap >> (3 * i)) % 8;
210:
211: if (rank == 7) {
212: shift -= 1;
213: continue;
214: }
215:
216: // read the corresponding entry
217: entry = int24(uint24(medianData >> (rank * 24)));
218: if ((below) && (lastObservedTick > entry)) {
219: shift += 1;
220: below = false;
221: }
222:
223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1)));
224: }
225:
226: updatedMedianData =
227: (block.timestamp << 216) +
228: (uint256(newOrderMap) << 192) +
229: uint256(uint192(medianData << 24)) +
230: uint256(uint24(lastObservedTick));
231: }
/// @audit comparison operator count: 4
207: for (uint8 i; i < 8; ++i) {
208: // read the rank from the existing ordering
209: rank = (orderMap >> (3 * i)) % 8;
210:
211: if (rank == 7) {
212: shift -= 1;
213: continue;
214: }
215:
216: // read the corresponding entry
217: entry = int24(uint24(medianData >> (rank * 24)));
218: if ((below) && (lastObservedTick > entry)) {
219: shift += 1;
220: below = false;
221: }
222:
223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1)));
224: }
/// @audit comparison operator count: 5
356: if (
357: tickLower % tickSpacing != 0 ||
358: tickUpper % tickSpacing != 0 ||
359: tickLower < minTick ||
360: tickUpper > maxTick
361: ) revert Errors.TicksNotInitializable();
/// @audit comparison operator count: 5
704: if (!(paid0 > balance0 && paid1 > balance1)) {
705: // liquidatee cannot pay back the liquidator fully in either token, so no protocol loss can be avoided
706: if ((paid0 > balance0)) {
707: // liquidatee has insufficient token0 but some token1 left over, so we use what they have left to mitigate token0 losses
708: // we do this by substituting an equivalent value of token1 in our refund to the liquidator, plus a bonus, for the token0 we convert
709: // we want to convert the minimum amount of tokens required to achieve the lowest possible protocol loss (to avoid overpaying on the conversion bonus)
710: // the maximum level of protocol loss mitigation that can be achieved is the liquidatee's excess token1 balance: balance1 - paid1
711: // and paid0 - balance0 is the amount of token0 that the liquidatee is missing, i.e the protocol loss
712: // if the protocol loss is lower than the excess token1 balance, then we can fully mitigate the loss and we should only convert the loss amount
713: // if the protocol loss is higher than the excess token1 balance, we can only mitigate part of the loss, so we should convert only the excess token1 balance
714: // thus, the value converted should be min(balance1 - paid1, paid0 - balance0)
715: bonus1 += Math.min(
716: balance1 - paid1,
717: PanopticMath.convert0to1(paid0 - balance0, sqrtPriceX96Final)
718: );
719: bonus0 -= Math.min(
720: PanopticMath.convert1to0(balance1 - paid1, sqrtPriceX96Final),
721: paid0 - balance0
722: );
723: }
724: if ((paid1 > balance1)) {
725: // liquidatee has insufficient token1 but some token0 left over, so we use what they have left to mitigate token1 losses
726: // we do this by substituting an equivalent value of token0 in our refund to the liquidator, plus a bonus, for the token1 we convert
727: // we want to convert the minimum amount of tokens required to achieve the lowest possible protocol loss (to avoid overpaying on the conversion bonus)
728: // the maximum level of protocol loss mitigation that can be achieved is the liquidatee's excess token0 balance: balance0 - paid0
729: // and paid1 - balance1 is the amount of token1 that the liquidatee is missing, i.e the protocol loss
730: // if the protocol loss is lower than the excess token0 balance, then we can fully mitigate the loss and we should only convert the loss amount
731: // if the protocol loss is higher than the excess token0 balance, we can only mitigate part of the loss, so we should convert only the excess token0 balance
732: // thus, the value converted should be min(balance0 - paid0, paid1 - balance1)
733: bonus0 += Math.min(
734: balance0 - paid0,
735: PanopticMath.convert1to0(paid1 - balance1, sqrtPriceX96Final)
736: );
737: bonus1 -= Math.min(
738: PanopticMath.convert0to1(balance0 - paid0, sqrtPriceX96Final),
739: paid1 - balance1
740: );
741: }
742: }
/// @audit comparison operator count: 6
797: if (
798: longPremium.rightSlot() < collateralDelta0 &&
799: longPremium.leftSlot() > collateralDelta1
800: ) {
801: int256 protocolLoss1 = collateralDelta1;
802: (collateralDelta0, collateralDelta1) = (
803: -Math.min(
804: collateralDelta0 - longPremium.rightSlot(),
805: PanopticMath.convert1to0(
806: longPremium.leftSlot() - collateralDelta1,
807: sqrtPriceX96Final
808: )
809: ),
810: Math.min(
811: longPremium.leftSlot() - collateralDelta1,
812: PanopticMath.convert0to1(
813: collateralDelta0 - longPremium.rightSlot(),
814: sqrtPriceX96Final
815: )
816: )
817: );
818:
819: haircut0 = longPremium.rightSlot();
820: haircut1 = protocolLoss1 + collateralDelta1;
821: } else if (
822: longPremium.leftSlot() < collateralDelta1 &&
823: longPremium.rightSlot() > collateralDelta0
824: ) {
825: int256 protocolLoss0 = collateralDelta0;
826: (collateralDelta0, collateralDelta1) = (
827: Math.min(
828: longPremium.rightSlot() - collateralDelta0,
829: PanopticMath.convert1to0(
830: collateralDelta1 - longPremium.leftSlot(),
831: sqrtPriceX96Final
832: )
833: ),
834: -Math.min(
835: collateralDelta1 - longPremium.leftSlot(),
836: PanopticMath.convert0to1(
837: longPremium.rightSlot() - collateralDelta0,
838: sqrtPriceX96Final
839: )
840: )
841: );
842:
843: haircut0 = collateralDelta0 + protocolLoss0;
844: haircut1 = longPremium.leftSlot();
845: } else {
846: // for each token, haircut until the protocol loss is mitigated or the premium paid is exhausted
847: haircut0 = Math.min(collateralDelta0, longPremium.rightSlot());
848: haircut1 = Math.min(collateralDelta1, longPremium.leftSlot());
849:
850: collateralDelta0 = 0;
851: collateralDelta1 = 0;
852: }
File: contracts/types/TokenId.sol
/// @audit comparison operator count: 4
376: if (optionRatios < 2 ** 64) {
377: optionRatios = 0;
378: } else if (optionRatios < 2 ** 112) {
379: optionRatios = 1;
380: } else if (optionRatios < 2 ** 160) {
381: optionRatios = 2;
382: } else if (optionRatios < 2 ** 208) {
383: optionRatios = 3;
384: } else {
385: optionRatios = 4;
386: }
/// @audit comparison operator count: 4
439: if (optionRatios < 2 ** 64) {
440: return 0;
441: } else if (optionRatios < 2 ** 112) {
442: return 1;
443: } else if (optionRatios < 2 ** 160) {
444: return 2;
445: } else if (optionRatios < 2 ** 208) {
446: return 3;
447: }
/// @audit comparison operator count: 15
507: for (uint256 i = 0; i < 4; ++i) {
508: if (self.optionRatio(i) == 0) {
509: // final leg in this position identified;
510: // make sure any leg above this are zero as well
511: // (we don't allow gaps eg having legs 1 and 4 active without 2 and 3 is not allowed)
512: if ((TokenId.unwrap(self) >> (64 + 48 * i)) != 0)
513: revert Errors.InvalidTokenIdParameter(1);
514:
515: break; // we are done iterating over potential legs
516: }
517:
518: // prevent legs touching the same chunks - all chunks in the position must be discrete
519: uint256 numLegs = self.countLegs();
520: for (uint256 j = i + 1; j < numLegs; ++j) {
521: if (uint48(chunkData >> (48 * i)) == uint48(chunkData >> (48 * j))) {
522: revert Errors.InvalidTokenIdParameter(6);
523: }
524: }
525: // now validate this ith leg in the position:
526:
527: // The width cannot be 0; the minimum is 1
528: if ((self.width(i) == 0)) revert Errors.InvalidTokenIdParameter(5);
529: // Strike cannot be MIN_TICK or MAX_TICK
530: if (
531: (self.strike(i) == Constants.MIN_V3POOL_TICK) ||
532: (self.strike(i) == Constants.MAX_V3POOL_TICK)
533: ) revert Errors.InvalidTokenIdParameter(4);
534:
535: // In the following, we check whether the risk partner of this leg is itself
536: // or another leg in this position.
537: // Handles case where riskPartner(i) != i ==> leg i has a risk partner that is another leg
538: uint256 riskPartnerIndex = self.riskPartner(i);
539: if (riskPartnerIndex != i) {
540: // Ensures that risk partners are mutual
541: if (self.riskPartner(riskPartnerIndex) != i)
542: revert Errors.InvalidTokenIdParameter(3);
543:
544: // Ensures that risk partners have 1) the same asset, and 2) the same ratio
545: if (
546: (self.asset(riskPartnerIndex) != self.asset(i)) ||
547: (self.optionRatio(riskPartnerIndex) != self.optionRatio(i))
548: ) revert Errors.InvalidTokenIdParameter(3);
549:
550: // long/short status of associated legs
551: uint256 _isLong = self.isLong(i);
552: uint256 isLongP = self.isLong(riskPartnerIndex);
553:
554: // token type status of associated legs (call/put)
555: uint256 _tokenType = self.tokenType(i);
556: uint256 tokenTypeP = self.tokenType(riskPartnerIndex);
557:
558: // if the position is the same i.e both long calls, short put's etc.
559: // then this is a regular position, not a defined risk position
560: if ((_isLong == isLongP) && (_tokenType == tokenTypeP))
561: revert Errors.InvalidTokenIdParameter(4);
562:
563: // if the two token long-types and the tokenTypes are both different (one is a short call, the other a long put, e.g.), this is a synthetic position
564: // A synthetic long or short is more capital efficient than each leg separated because the long+short premia accumulate proportionally
565: // unlike short stranlges, long strangles also cannot be partnered, because there is no reduction in risk (both legs can earn premia simultaneously)
566: if (((_isLong != isLongP) || _isLong == 1) && (_tokenType != tokenTypeP))
567: revert Errors.InvalidTokenIdParameter(5);
568: }
569: } // end for loop over legs
/// @audit comparison operator count: 7
539: if (riskPartnerIndex != i) {
540: // Ensures that risk partners are mutual
541: if (self.riskPartner(riskPartnerIndex) != i)
542: revert Errors.InvalidTokenIdParameter(3);
543:
544: // Ensures that risk partners have 1) the same asset, and 2) the same ratio
545: if (
546: (self.asset(riskPartnerIndex) != self.asset(i)) ||
547: (self.optionRatio(riskPartnerIndex) != self.optionRatio(i))
548: ) revert Errors.InvalidTokenIdParameter(3);
549:
550: // long/short status of associated legs
551: uint256 _isLong = self.isLong(i);
552: uint256 isLongP = self.isLong(riskPartnerIndex);
553:
554: // token type status of associated legs (call/put)
555: uint256 _tokenType = self.tokenType(i);
556: uint256 tokenTypeP = self.tokenType(riskPartnerIndex);
557:
558: // if the position is the same i.e both long calls, short put's etc.
559: // then this is a regular position, not a defined risk position
560: if ((_isLong == isLongP) && (_tokenType == tokenTypeP))
561: revert Errors.InvalidTokenIdParameter(4);
562:
563: // if the two token long-types and the tokenTypes are both different (one is a short call, the other a long put, e.g.), this is a synthetic position
564: // A synthetic long or short is more capital efficient than each leg separated because the long+short premia accumulate proportionally
565: // unlike short stranlges, long strangles also cannot be partnered, because there is no reduction in risk (both legs can earn premia simultaneously)
566: if (((_isLong != isLongP) || _isLong == 1) && (_tokenType != tokenTypeP))
567: revert Errors.InvalidTokenIdParameter(5);
568: }
/// @audit comparison operator count: 5
581: for (uint256 i = 0; i < numLegs; ++i) {
582: (int24 rangeDown, int24 rangeUp) = PanopticMath.getRangesFromStrike(
583: self.width(i),
584: self.tickSpacing()
585: );
586:
587: int24 _strike = self.strike(i);
588: // check if the price is outside this chunk
589: if ((currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown)) {
590: // if this leg is long and the price beyond the leg's range:
591: // this exercised ID, `self`, appears valid
592: if (self.isLong(i) == 1) return; // validated
593: }
594: }
/// @audit comparison operator count: 4
589: if ((currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown)) {
590: // if this leg is long and the price beyond the leg's range:
591: // this exercised ID, `self`, appears valid
592: if (self.isLong(i) == 1) return; // validated
593: }
</details>
<a id="n-14"></a> [N-14] Consider using a struct
rather than having many function input parameters
Description:
To increase readability and maintainability, it may be more efficient to pass in a struct
rather than having many function input parameters.
Instances:
There are 40 instances of this issue.
<details><summary>View 40 Instances</summary>
File: contracts/CollateralTracker.sol
178: constructor(
179: uint256 _commissionFee,
180: uint256 _sellerCollateralRatio,
181: uint256 _buyerCollateralRatio,
182: int256 _forceExerciseCost,
183: uint256 _targetPoolUtilization,
184: uint256 _saturatedPoolUtilization,
185: uint256 _ITMSpreadMultiplier
186: ) {
221: function startToken(
222: bool underlyingIsToken0,
223: address token0,
224: address token1,
225: uint24 fee,
226: PanopticPool panopticPool
227: ) external {
650: function exerciseCost(
651: int24 currentTick,
652: int24 oracleTick,
653: TokenId positionId,
654: uint128 positionBalance,
655: LeftRightSigned longAmounts
656: ) external view returns (LeftRightSigned exerciseFees) {
1043: function exercise(
1044: address optionOwner,
1045: int128 longAmount,
1046: int128 shortAmount,
1047: int128 swappedAmount,
1048: int128 realizedPremium
1049: ) external onlyPanopticPool returns (int128) {
1278: function _getRequiredCollateralSingleLeg(
1279: TokenId tokenId,
1280: uint256 index,
1281: uint128 positionSize,
1282: int24 atTick,
1283: uint128 poolUtilization
1284: ) internal view returns (uint256 required) {
1311: function _getRequiredCollateralSingleLegNoPartner(
1312: TokenId tokenId,
1313: uint256 index,
1314: uint128 positionSize,
1315: int24 atTick,
1316: uint128 poolUtilization
1317: ) internal view returns (uint256 required) {
1439: function _getRequiredCollateralSingleLegPartner(
1440: TokenId tokenId,
1441: uint256 index,
1442: uint128 positionSize,
1443: int24 atTick,
1444: uint128 poolUtilization
1445: ) internal view returns (uint256 required) {
1510: function _computeSpread(
1511: TokenId tokenId,
1512: uint128 positionSize,
1513: uint256 index,
1514: uint256 partnerIndex,
1515: uint128 poolUtilization
1516: ) internal view returns (uint256 spreadRequirement) {
1600: function _computeStrangle(
1601: TokenId tokenId,
1602: uint256 index,
1603: uint128 positionSize,
1604: int24 atTick,
1605: uint128 poolUtilization
1606: ) internal view returns (uint256 strangleRequired) {
File: contracts/PanopticFactory.sol
115: constructor(
116: address _WETH9,
117: SemiFungiblePositionManager _SFPM,
118: IUniswapV3Factory _univ3Factory,
119: IDonorNFT _donorNFT,
120: address _poolReference,
121: address _collateralReference
122: ) {
File: contracts/PanopticPool.sol
291: function startPool(
292: IUniswapV3Pool _univ3pool,
293: address token0,
294: address token1,
295: CollateralTracker collateralTracker0,
296: CollateralTracker collateralTracker1
297: ) external {
429: function _calculateAccumulatedPremia(
430: address user,
431: TokenId[] calldata positionIdList,
432: bool computeAllPremia,
433: bool includePendingPremium,
434: int24 atTick
435: ) internal view returns (LeftRightSigned portfolioPremium, uint256[2][] memory balances) {
547: function mintOptions(
548: TokenId[] calldata positionIdList,
549: uint128 positionSize,
550: uint64 effectiveLiquidityLimitX32,
551: int24 tickLimitLow,
552: int24 tickLimitHigh
553: ) external {
614: function _mintOptions(
615: TokenId[] calldata positionIdList,
616: uint128 positionSize,
617: uint64 effectiveLiquidityLimitX32,
618: int24 tickLimitLow,
619: int24 tickLimitHigh
620: ) internal {
794: function _burnAllOptionsFrom(
795: address owner,
796: int24 tickLimitLow,
797: int24 tickLimitHigh,
798: bool commitLongSettled,
799: TokenId[] calldata positionIdList
800: ) internal returns (LeftRightSigned netPaid, LeftRightSigned[4][] memory premiasByLeg) {
826: function _burnOptions(
827: bool commitLongSettled,
828: TokenId tokenId,
829: address owner,
830: int24 tickLimitLow,
831: int24 tickLimitHigh
832: ) internal returns (LeftRightSigned paidAmounts, LeftRightSigned[4] memory premiaByLeg) {
955: function _burnAndHandleExercise(
956: bool commitLongSettled,
957: int24 tickLimitLow,
958: int24 tickLimitHigh,
959: TokenId tokenId,
960: uint128 positionSize,
961: address owner
962: )
963: internal
964: returns (
965: LeftRightSigned realizedPremia,
966: LeftRightSigned[4] memory premiaByLeg,
967: LeftRightSigned paidAmounts
968: )
969: {
1290: function _checkSolvencyAtTick(
1291: address account,
1292: TokenId[] calldata positionIdList,
1293: int24 currentTick,
1294: int24 atTick,
1295: uint256 buffer
1296: ) internal view returns (bool) {
1465: function _checkLiquiditySpread(
1466: TokenId tokenId,
1467: uint256 leg,
1468: int24 tickLower,
1469: int24 tickUpper,
1470: uint64 effectiveLiquidityLimitX32
1471: ) internal view {
1503: function _getPremia(
1504: TokenId tokenId,
1505: uint128 positionSize,
1506: address owner,
1507: bool computeAllPremia,
1508: int24 atTick
1509: )
1510: internal
1511: view
1512: returns (
1513: LeftRightSigned[4] memory premiaByLeg,
1514: uint256[2][4] memory premiumAccumulatorsByLeg
1515: )
1516: {
1757: function _getAvailablePremium(
1758: uint256 totalLiquidity,
1759: LeftRightUnsigned settledTokens,
1760: LeftRightUnsigned grossPremiumLast,
1761: LeftRightUnsigned premiumOwed,
1762: uint256[2] memory premiumAccumulators
1763: ) internal pure returns (LeftRightUnsigned) {
1833: function _updateSettlementPostBurn(
1834: address owner,
1835: TokenId tokenId,
1836: LeftRightUnsigned[4] memory collectedByLeg,
1837: uint128 positionSize,
1838: bool commitLongSettled
1839: ) internal returns (LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg) {
File: contracts/SemiFungiblePositionManager.sol
680: function _validateAndForwardToAMM(
681: TokenId tokenId,
682: uint128 positionSize,
683: int24 tickLimitLow,
684: int24 tickLimitHigh,
685: bool isBurn
686: ) internal returns (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalMoved) {
958: function _createLegInAMM(
959: IUniswapV3Pool univ3pool,
960: TokenId tokenId,
961: uint256 leg,
962: LiquidityChunk liquidityChunk,
963: bool isBurn
964: )
965: internal
966: returns (
967: LeftRightSigned moved,
968: LeftRightSigned itmAmounts,
969: LeftRightUnsigned collectedSingleLeg
970: )
971: {
1255: function _collectAndWritePositionData(
1256: LiquidityChunk liquidityChunk,
1257: IUniswapV3Pool univ3pool,
1258: LeftRightUnsigned currentLiquidity,
1259: bytes32 positionKey,
1260: LeftRightSigned movedInLeg,
1261: uint256 isLong
1262: ) internal returns (LeftRightUnsigned collectedChunk) {
1421: function getAccountLiquidity(
1422: address univ3pool,
1423: address owner,
1424: uint256 tokenType,
1425: int24 tickLower,
1426: int24 tickUpper
1427: ) external view returns (LeftRightUnsigned accountLiquidities) {
1449: function getAccountPremium(
1450: address univ3pool,
1451: address owner,
1452: uint256 tokenType,
1453: int24 tickLower,
1454: int24 tickUpper,
1455: int24 atTick,
1456: uint256 isLong
1457: ) external view returns (uint128, uint128) {
1535: function getAccountFeesBase(
1536: address univ3pool,
1537: address owner,
1538: uint256 tokenType,
1539: int24 tickLower,
1540: int24 tickUpper
1541: ) external view returns (int128 feesBase0, int128 feesBase1) {
File: contracts/libraries/FeesCalc.sol
97: function calculateAMMSwapFees(
98: IUniswapV3Pool univ3pool,
99: int24 currentTick,
100: int24 tickLower,
101: int24 tickUpper,
102: uint128 liquidity
103: ) public view returns (LeftRightSigned) {
File: contracts/libraries/InteractionHelper.sol
24: function doApprovals(
25: SemiFungiblePositionManager sfpm,
26: CollateralTracker ct0,
27: CollateralTracker ct1,
28: address token0,
29: address token1
30: ) external {
48: function computeName(
49: address token0,
50: address token1,
51: bool isToken0,
52: uint24 fee,
53: string memory prefix
54: ) external view returns (string memory) {
File: contracts/libraries/PanopticMath.sol
125: function computeMedianObservedPrice(
126: IUniswapV3Pool univ3pool,
127: uint256 observationIndex,
128: uint256 observationCardinality,
129: uint256 cardinality,
130: uint256 period
131: ) external view returns (int24) {
168: function computeInternalMedian(
169: uint256 observationIndex,
170: uint256 observationCardinality,
171: uint256 period,
172: uint256 medianData,
173: IUniswapV3Pool univ3pool
174: ) external view returns (int24 medianTick, uint256 updatedMedianData) {
651: function getLiquidationBonus(
652: LeftRightUnsigned tokenData0,
653: LeftRightUnsigned tokenData1,
654: uint160 sqrtPriceX96Twap,
655: uint160 sqrtPriceX96Final,
656: LeftRightSigned netExchanged,
657: LeftRightSigned premia
658: ) external pure returns (int256 bonus0, int256 bonus1, LeftRightSigned) {
768: function haircutPremia(
769: address liquidatee,
770: TokenId[] memory positionIdList,
771: LeftRightSigned[4][] memory premiasByLeg,
772: LeftRightSigned collateralRemaining,
773: CollateralTracker collateral0,
774: CollateralTracker collateral1,
775: uint160 sqrtPriceX96Final,
776: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens
777: ) external returns (int256, int256) {
917: function getRefundAmounts(
918: address refunder,
919: LeftRightSigned refundValues,
920: int24 atTick,
921: CollateralTracker collateral0,
922: CollateralTracker collateral1
923: ) external view returns (LeftRightSigned) {
File: contracts/tokens/ERC1155Minimal.sol
94: function safeTransferFrom(
95: address from,
96: address to,
97: uint256 id,
98: uint256 amount,
99: bytes calldata data
100: ) public virtual {
130: function safeBatchTransferFrom(
131: address from,
132: address to,
133: uint256[] calldata ids,
134: uint256[] calldata amounts,
135: bytes calldata data
136: ) public virtual {
File: contracts/tokens/interfaces/IDonorNFT.sol
13: function issueNFT(
14: address deployer,
15: PanopticPool newPoolContract,
16: address token0,
17: address token1,
18: uint24 fee
19: ) external;
File: contracts/types/TokenId.sol
336: function addLeg(
337: TokenId self,
338: uint256 legIndex,
339: uint256 _optionRatio,
340: uint256 _asset,
341: uint256 _isLong,
342: uint256 _tokenType,
343: uint256 _riskPartner,
344: int24 _strike,
345: int24 _width
346: ) internal pure returns (TokenId tokenId) {
</details>
<a id="n-15"></a> [N-15] Consider using descriptive constant
s when passing zero as a function argument
Description:
Passing zero as a function argument can sometimes result in a security issue (e.g., passing zero as the slippage parameter).
Recommendation:
Consider using a constant
with a descriptive name, so it is clear what the argument represents and how it is being used.
Instances:
There are 62 instances of this issue.
<details><summary>View 62 Instances</summary>
File: contracts/CollateralTracker.sol
712: LeftRightSigned
713: .wrap(0)
File: contracts/PanopticPool.sol
634: revert Errors.InvalidTokenIdParameter(0);
654: s_positionBalance[msg.sender][tokenId] = LeftRightUnsigned
655: .wrap(0)
762: s_options[msg.sender][tokenId][leg] = LeftRightUnsigned
763: .wrap(0)
861: s_positionBalance[owner][tokenId] = LeftRightUnsigned.wrap(0);
870: s_options[owner][tokenId][leg] = LeftRightUnsigned.wrap(0);
893: _validatePositionList(user, positionIdList, 0);
1023: _validatePositionList(liquidatee, positionIdList, 0);
1153: _validatePositionList(msg.sender, positionIdListLiquidator, 0);
1165: LeftRightSigned bonusAmounts = LeftRightSigned
1166: .wrap(0)
1191: _validatePositionList(msg.sender, positionIdListExercisor, 0);
1227: _burnAllOptionsFrom(account, 0, 0, COMMIT_LONG_SETTLED, touchedId);
1545: premiaByLeg[leg] = LeftRightSigned
1546: .wrap(0)
1567: premiaByLeg[leg] = LeftRightSigned.wrap(0).sub(premiaByLeg[leg]);
1592: _validatePositionList(owner, positionIdList, 0);
1614: accumulatedPremium = LeftRightUnsigned
1615: .wrap(0)
1633: LeftRightSigned realizedPremia = LeftRightSigned
1634: .wrap(0)
1639: s_collateralToken0.exercise(owner, 0, 0, 0, realizedPremia.rightSlot());
1640: s_collateralToken1.exercise(owner, 0, 0, 0, realizedPremia.leftSlot());
1707: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium(
1708: address(s_univ3pool),
1709: address(this),
1710: tokenId.tokenType(leg),
1711: liquidityChunk.tickLower(),
1712: liquidityChunk.tickUpper(),
1713: type(int24).max,
1714: 0
1715: );
1725: s_grossPremiumLast[chunkKey] = LeftRightUnsigned
1726: .wrap(0)
1774: LeftRightUnsigned
1775: .wrap(0)
1929: ? LeftRightUnsigned
1930: .wrap(0)
1934: Math.max(
1935: (int256(
1936: grossPremiumLast.rightSlot() *
1937: totalLiquidityBefore
1938: ) -
1939: int256(
1940: _premiumAccumulatorsByLeg[_leg][0] *
1941: positionLiquidity
1942: )) + int256(legPremia.rightSlot() * 2 ** 64),
1943: 0
1944: )
1951: Math.max(
1952: (int256(
1953: grossPremiumLast.leftSlot() *
1954: totalLiquidityBefore
1955: ) -
1956: int256(
1957: _premiumAccumulatorsByLeg[_leg][1] *
1958: positionLiquidity
1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
1960: 0
1961: )
1965: : LeftRightUnsigned
1966: .wrap(0)
File: contracts/SemiFungiblePositionManager.sol
645: s_accountLiquidity[positionKey_from] = LeftRightUnsigned.wrap(0);
648: s_accountFeesBase[positionKey_from] = LeftRightSigned.wrap(0);
833: if (swapAmount == 0) return LeftRightSigned.wrap(0);
848: totalSwapped = LeftRightSigned.wrap(0).toRightSlot(swap0.toInt128()).toLeftSlot(
1038: s_accountLiquidity[positionKey] = LeftRightUnsigned
1039: .wrap(0)
1166: ? LeftRightSigned
1167: .wrap(0)
1174: : LeftRightSigned
1175: .wrap(0)
1214: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(int128(int256(amount0))).toLeftSlot(
1241: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(-int128(int256(amount0))).toLeftSlot(
1306: collectedChunk = LeftRightUnsigned.wrap(0).toRightSlot(collected0).toLeftSlot(
1376: deltaPremiumOwed = LeftRightUnsigned
1377: .wrap(0)
1400: deltaPremiumGross = LeftRightUnsigned
1401: .wrap(0)
File: contracts/libraries/FeesCalc.sol
115: LeftRightSigned
116: .wrap(0)
File: contracts/libraries/PanopticMath.sol
598: return LeftRightUnsigned.wrap(0).toRightSlot(amount0).toLeftSlot(amount1);
671: (uint256 balanceCross, uint256 thresholdCross) = PanopticMath.convertCollateralData(
672: tokenData0,
673: tokenData1,
674: 0,
675: sqrtPriceX96Twap
676: );
694: Math.max(premia.rightSlot(), 0);
696: Math.max(premia.leftSlot(), 0);
749: LeftRightSigned.wrap(0).toRightSlot(int128(balance0 - paid0)).toLeftSlot(
791: int256 collateralDelta0 = -Math.min(collateralRemaining.rightSlot(), 0);
792: int256 collateralDelta1 = -Math.min(collateralRemaining.leftSlot(), 0);
856: if (haircut0 != 0) collateral0.exercise(_liquidatee, 0, 0, 0, int128(haircut0));
857: if (haircut1 != 0) collateral1.exercise(_liquidatee, 0, 0, 0, int128(haircut1));
880: tokenId.strike(0),
881: tokenId.width(0),
882: tokenId.tokenType(0)
888: settled0 = Math.max(
889: 0,
890: uint128(-_premiasByLeg[i][leg].rightSlot()) - settled0
891: );
892: settled1 = Math.max(
893: 0,
894: uint128(-_premiasByLeg[i][leg].leftSlot()) - settled1
895: );
898: LeftRightUnsigned.wrap(0).toRightSlot(uint128(settled0)).toLeftSlot(
933: LeftRightSigned
934: .wrap(0)
951: LeftRightSigned
952: .wrap(0)
File: contracts/types/LeftRight.sol
265: z.toRightSlot(int128(Math.max(right128, 0))).toLeftSlot(
266: int128(Math.max(left128, 0))
294: LeftRightUnsigned.wrap(0).toRightSlot(r_Enabled ? z_xR : x.rightSlot()).toLeftSlot(
297: LeftRightUnsigned.wrap(0).toRightSlot(r_Enabled ? z_yR : y.rightSlot()).toLeftSlot(
File: contracts/types/TokenId.sol
406: return self.isLong(0) + self.isLong(1) + self.isLong(2) + self.isLong(3);
501: if (self.optionRatio(0) == 0) revert Errors.InvalidTokenIdParameter(1);
</details>
<a id="n-16"></a> [N-16] Consider using named function arguments
Description:
Function call arguments can be given by name, in any order, if they are enclosed in {
and }
. The argument list has to coincide by name with the list of parameters from the function declaration, but can be in arbitrary order. Using named function parameters can improve code readability and maintainability by explicitly mapping arguments to their respective parameter names, reducing potential errors due to out of order arguments. The following findings are for function calls with 4 or more parameters.
Instances:
There are 117 instances of this issue.
<details><summary>View 117 Instances</summary>
File: contracts/CollateralTracker.sol
292: InteractionHelper.computeName(
293: s_univ3token0,
294: s_univ3token1,
295: s_underlyingIsToken0,
296: s_poolFee,
297: NAME_PREFIX
298: );
424: SafeTransferLib.safeTransferFrom(
425: s_underlyingToken,
426: msg.sender,
427: address(s_panopticPool),
428: assets
429: );
439: emit Deposit(msg.sender, receiver, assets, shares);
484: SafeTransferLib.safeTransferFrom(
485: s_underlyingToken,
486: msg.sender,
487: address(s_panopticPool),
488: assets
489: );
499: emit Deposit(msg.sender, receiver, assets, shares);
556: SafeTransferLib.safeTransferFrom(
557: s_underlyingToken,
558: address(s_panopticPool),
559: receiver,
560: assets
561: );
563: emit Withdraw(msg.sender, receiver, owner, assets, shares);
616: SafeTransferLib.safeTransferFrom(
617: s_underlyingToken,
618: address(s_panopticPool),
619: receiver,
620: assets
621: );
623: emit Withdraw(msg.sender, receiver, owner, assets, shares);
1147: tokenData = _getAccountMargin(user, currentTick, positionBalanceArray, premiumAllPositions);
1219: uint256 _tokenRequired = _getRequiredCollateralAtTickSinglePosition(
1220: tokenId,
1221: positionSize,
1222: atTick,
1223: poolUtilization
1224: );
1259: tokenRequired += _getRequiredCollateralSingleLeg(
1260: tokenId,
1261: index,
1262: positionSize,
1263: atTick,
1264: poolUtilization
1265: );
1287: ? _getRequiredCollateralSingleLegNoPartner(
1288: tokenId,
1289: index,
1290: positionSize,
1291: atTick,
1292: poolUtilization
1293: )
1294: : _getRequiredCollateralSingleLegPartner(
1295: tokenId,
1296: index,
1297: positionSize,
1298: atTick,
1299: poolUtilization
1300: );
1453: required = _computeSpread(
1454: tokenId,
1455: positionSize,
1456: index,
1457: partnerIndex,
1458: poolUtilization
1459: );
1462: required = _computeStrangle(tokenId, index, positionSize, atTick, poolUtilization);
1641: strangleRequired = _getRequiredCollateralSingleLegNoPartner(
1642: tokenId,
1643: index,
1644: positionSize,
1645: atTick,
1646: poolUtilization
1647: );
File: contracts/PanopticFactory.sol
182: SafeTransferLib.safeTransferFrom(
183: decoded.poolFeatures.token0,
184: decoded.payer,
185: msg.sender,
186: amount0Owed
187: );
189: SafeTransferLib.safeTransferFrom(
190: decoded.poolFeatures.token1,
191: decoded.payer,
192: msg.sender,
193: amount1Owed
194: );
248: collateralTracker0.startToken(true, token0, token1, fee, newPoolContract);
249: collateralTracker1.startToken(false, token0, token1, fee, newPoolContract);
251: newPoolContract.startPool(v3Pool, token0, token1, collateralTracker0, collateralTracker1);
263: (uint256 amount0, uint256 amount1) = _mintFullRange(v3Pool, token0, token1, fee);
266: DONOR_NFT.issueNFT(msg.sender, newPoolContract, token0, token1, fee);
268: emit PoolDeployed(
269: newPoolContract,
270: v3Pool,
271: collateralTracker0,
272: collateralTracker1,
273: amount0,
274: amount1
275: );
404: IUniswapV3Pool(v3Pool).mint(
405: address(this),
406: tickLower,
407: tickUpper,
408: fullRangeLiquidity,
409: mintCallback
410: );
File: contracts/PanopticPool.sol
326: InteractionHelper.doApprovals(SFPM, collateralTracker0, collateralTracker1, token0, token1);
390: (LeftRightSigned premia, uint256[2][] memory balances) = _calculateAccumulatedPremia(
391: user,
392: positionIdList,
393: COMPUTE_ALL_PREMIA,
394: includePendingPremium,
395: currentTick
396: );
450: ) = _getPremia(
451: tokenId,
452: LeftRightUnsigned.wrap(balances[k][1]).rightSlot(),
453: c_user,
454: computeAllPremia,
455: atTick
456: );
469: LeftRightUnsigned availablePremium = _getAvailablePremium(
470: _getTotalLiquidity(tokenId, leg),
471: s_settledTokens[chunkKey],
472: s_grossPremiumLast[chunkKey],
473: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))),
474: premiumAccumulatorsByLeg[leg]
475: );
525: (, uint256 medianData) = PanopticMath.computeInternalMedian(
526: observationIndex,
527: observationCardinality,
528: MEDIAN_PERIOD,
529: s_miniMedian,
530: s_univ3pool
531: );
554: _mintOptions(
555: positionIdList,
556: positionSize,
557: effectiveLiquidityLimitX32,
558: tickLimitLow,
559: tickLimitHigh
560: );
575: _burnOptions(COMMIT_LONG_SETTLED, tokenId, msg.sender, tickLimitLow, tickLimitHigh);
592: _burnAllOptionsFrom(
593: msg.sender,
594: tickLimitLow,
595: tickLimitHigh,
596: COMMIT_LONG_SETTLED,
597: positionIdList
598: );
642: uint128 poolUtilizations = _mintInSFPMAndUpdateCollateral(
643: tokenId,
644: positionSize,
645: tickLimitLow,
646: tickLimitHigh
647: );
666: emit OptionMinted(msg.sender, positionSize, tokenId, poolUtilizations);
685: (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) = SFPM
686: .mintTokenizedPosition(tokenId, positionSize, tickLimitLow, tickLimitHigh);
715: int256 utilization0 = s_collateralToken0.takeCommissionAddData(
716: msg.sender,
717: longAmounts.rightSlot(),
718: shortAmounts.rightSlot(),
719: totalSwapped.rightSlot()
720: );
721: int256 utilization1 = s_collateralToken1.takeCommissionAddData(
722: msg.sender,
723: longAmounts.leftSlot(),
724: shortAmounts.leftSlot(),
725: totalSwapped.leftSlot()
726: );
751: (uint128 premiumAccumulator0, uint128 premiumAccumulator1) = SFPM.getAccountPremium(
752: address(s_univ3pool),
753: address(this),
754: tokenId.tokenType(leg),
755: tickLower,
756: tickUpper,
757: type(int24).max,
758: isLong
759: );
770: _checkLiquiditySpread(
771: tokenId,
772: leg,
773: tickLower,
774: tickUpper,
775: uint64(Math.min(effectiveLiquidityLimitX32, MAX_SPREAD))
776: );
804: (paidAmounts, premiasByLeg[i]) = _burnOptions(
805: commitLongSettled,
806: positionIdList[i],
807: owner,
808: tickLimitLow,
809: tickLimitHigh
810: );
840: (premiaOwed, premiaByLeg, paidAmounts) = _burnAndHandleExercise(
841: commitLongSettled,
842: tickLimitLow,
843: tickLimitHigh,
844: tokenId,
845: positionSize,
846: owner
847: );
853: emit OptionBurnt(owner, positionSize, tokenId, premiaOwed);
868: _checkLiquiditySpread(tokenId, leg, tickLower, tickUpper, MAX_SPREAD);
905: int24 fastOracleTick = PanopticMath.computeMedianObservedPrice(
906: _univ3pool,
907: observationIndex,
908: observationCardinality,
909: FAST_ORACLE_CARDINALITY,
910: FAST_ORACLE_PERIOD
911: );
915: slowOracleTick = PanopticMath.computeMedianObservedPrice(
916: _univ3pool,
917: observationIndex,
918: observationCardinality,
919: SLOW_ORACLE_CARDINALITY,
920: SLOW_ORACLE_PERIOD
921: );
923: (slowOracleTick, medianData) = PanopticMath.computeInternalMedian(
924: observationIndex,
925: observationCardinality,
926: MEDIAN_PERIOD,
927: s_miniMedian,
928: _univ3pool
929: );
933: bool solventAtFast = _checkSolvencyAtTick(
934: user,
935: positionIdList,
936: currentTick,
937: fastOracleTick,
938: buffer
939: );
944: if (!_checkSolvencyAtTick(user, positionIdList, currentTick, slowOracleTick, buffer))
970: (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) = SFPM
971: .burnTokenizedPosition(tokenId, positionSize, tickLimitLow, tickLimitHigh);
973: (realizedPremia, premiaByLeg) = _updateSettlementPostBurn(
974: owner,
975: tokenId,
976: collectedByLeg,
977: positionSize,
978: commitLongSettled
979: );
985: int128 paid0 = s_collateralToken0.exercise(
986: owner,
987: longAmounts.rightSlot(),
988: shortAmounts.rightSlot(),
989: totalSwapped.rightSlot(),
990: realizedPremia.rightSlot()
991: );
996: int128 paid1 = s_collateralToken1.exercise(
997: owner,
998: longAmounts.leftSlot(),
999: shortAmounts.leftSlot(),
1000: totalSwapped.leftSlot(),
1001: realizedPremia.leftSlot()
1002: );
1039: (premia, positionBalanceArray) = _calculateAccumulatedPremia(
1040: liquidatee,
1041: positionIdList,
1042: COMPUTE_ALL_PREMIA,
1043: ONLY_AVAILABLE_PREMIUM,
1044: currentTick
1045: );
1046: tokenData0 = s_collateralToken0.getAccountMarginDetails(
1047: liquidatee,
1048: twapTick,
1049: positionBalanceArray,
1050: premia.rightSlot()
1051: );
1053: tokenData1 = s_collateralToken1.getAccountMarginDetails(
1054: liquidatee,
1055: twapTick,
1056: positionBalanceArray,
1057: premia.leftSlot()
1058: );
1086: (netExchanged, premiasByLeg) = _burnAllOptionsFrom(
1087: liquidatee,
1088: Constants.MIN_V3POOL_TICK,
1089: Constants.MAX_V3POOL_TICK,
1090: DONOT_COMMIT_LONG_SETTLED,
1091: positionIdList
1092: );
1098: (liquidationBonus0, liquidationBonus1, collateralRemaining) = PanopticMath
1099: .getLiquidationBonus(
1100: tokenData0,
1101: tokenData1,
1102: Math.getSqrtRatioAtTick(twapTick),
1103: Math.getSqrtRatioAtTick(finalTick),
1104: netExchanged,
1105: premia
1106: );
1122: (deltaBonus0, deltaBonus1) = PanopticMath.haircutPremia(
1123: _liquidatee,
1124: _positionIdList,
1125: premiasByLeg,
1126: collateralRemaining,
1127: s_collateralToken0,
1128: s_collateralToken1,
1129: Math.getSqrtRatioAtTick(_finalTick),
1130: s_settledTokens
1131: );
1156: !_checkSolvencyAtTick(
1157: msg.sender,
1158: positionIdListLiquidator,
1159: finalTick,
1160: finalTick,
1161: BP_DECREASE_BUFFER
1162: )
1206: (LeftRightSigned positionPremia, ) = _calculateAccumulatedPremia(
1207: account,
1208: touchedId,
1209: COMPUTE_LONG_PREMIA,
1210: ONLY_AVAILABLE_PREMIUM,
1211: currentTick
1212: );
1227: _burnAllOptionsFrom(account, 0, 0, COMMIT_LONG_SETTLED, touchedId);
1231: LeftRightSigned exerciseFees = s_collateralToken0.exerciseCost(
1232: currentTick,
1233: twapTick,
1234: touchedId[0],
1235: positionBalance,
1236: longAmounts
1237: );
1242: refundAmounts = PanopticMath.getRefundAmounts(
1243: account,
1244: refundAmounts,
1245: twapTick,
1246: s_collateralToken0,
1247: s_collateralToken1
1248: );
1277: emit ForcedExercised(msg.sender, account, touchedId[0], exerciseFees);
1300: ) = _calculateAccumulatedPremia(
1301: account,
1302: positionIdList,
1303: COMPUTE_ALL_PREMIA,
1304: ONLY_AVAILABLE_PREMIUM,
1305: currentTick
1306: );
1308: LeftRightUnsigned tokenData0 = s_collateralToken0.getAccountMarginDetails(
1309: account,
1310: atTick,
1311: positionBalanceArray,
1312: portfolioPremium.rightSlot()
1313: );
1314: LeftRightUnsigned tokenData1 = s_collateralToken1.getAccountMarginDetails(
1315: account,
1316: atTick,
1317: positionBalanceArray,
1318: portfolioPremium.leftSlot()
1319: );
1472: LeftRightUnsigned accountLiquidities = SFPM.getAccountLiquidity(
1473: address(s_univ3pool),
1474: address(this),
1475: tokenId.tokenType(leg),
1476: tickLower,
1477: tickUpper
1478: );
1528: (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM
1529: .getAccountPremium(
1530: address(s_univ3pool),
1531: address(this),
1532: tokenType,
1533: liquidityChunk.tickLower(),
1534: liquidityChunk.tickUpper(),
1535: atTick,
1536: isLong
1537: );
1605: (uint128 premiumAccumulator0, uint128 premiumAccumulator1) = SFPM.getAccountPremium(
1606: address(s_univ3pool),
1607: address(this),
1608: tokenType,
1609: tickLower,
1610: tickUpper,
1611: currentTick,
1612: 1
1613: );
1639: s_collateralToken0.exercise(owner, 0, 0, 0, realizedPremia.rightSlot());
1640: s_collateralToken1.exercise(owner, 0, 0, 0, realizedPremia.leftSlot());
1707: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium(
1708: address(s_univ3pool),
1709: address(this),
1710: tokenId.tokenType(leg),
1711: liquidityChunk.tickLower(),
1712: liquidityChunk.tickUpper(),
1713: type(int24).max,
1714: 0
1715: );
1811: LeftRightUnsigned accountLiquidities = SFPM.getAccountLiquidity(
1812: address(s_univ3pool),
1813: address(this),
1814: tokenType,
1815: tickLower,
1816: tickUpper
1817: );
1844: (premiaByLeg, premiumAccumulatorsByLeg) = _getPremia(
1845: tokenId,
1846: positionSize,
1847: owner,
1848: COMPUTE_ALL_PREMIA,
1849: type(int24).max
1850: );
1888: LeftRightUnsigned availablePremium = _getAvailablePremium(
1889: totalLiquidity + positionLiquidity,
1890: settledTokens,
1891: grossPremiumLast,
1892: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(legPremia))),
1893: premiumAccumulatorsByLeg[leg]
1894: );
File: contracts/SemiFungiblePositionManager.sol
413: SafeTransferLib.safeTransferFrom(
414: decoded.poolFeatures.token0,
415: decoded.payer,
416: msg.sender,
417: amount0Owed
418: );
420: SafeTransferLib.safeTransferFrom(
421: decoded.poolFeatures.token1,
422: decoded.payer,
423: msg.sender,
424: amount1Owed
425: );
456: SafeTransferLib.safeTransferFrom(token, decoded.payer, msg.sender, amountToPay);
488: (collectedByLeg, totalSwapped) = _validateAndForwardToAMM(
489: tokenId,
490: positionSize,
491: slippageTickLimitLow,
492: slippageTickLimitHigh,
493: BURN
494: );
520: (collectedByLeg, totalSwapped) = _validateAndForwardToAMM(
521: tokenId,
522: positionSize,
523: slippageTickLimitLow,
524: slippageTickLimitHigh,
525: MINT
526: );
552: registerTokenTransfer(from, to, TokenId.wrap(id), amount);
555: super.safeTransferFrom(from, to, id, amount, data);
577: registerTokenTransfer(from, to, TokenId.wrap(ids[i]), amounts[i]);
584: super.safeBatchTransferFrom(from, to, ids, amounts, data);
612: abi.encodePacked(
613: address(univ3pool),
614: from,
615: id.tokenType(leg),
616: liquidityChunk.tickLower(),
617: liquidityChunk.tickUpper()
618: )
621: abi.encodePacked(
622: address(univ3pool),
623: to,
624: id.tokenType(leg),
625: liquidityChunk.tickLower(),
626: liquidityChunk.tickUpper()
627: )
708: (totalMoved, collectedByLeg, itmAmounts) = _createPositionInAMM(
709: univ3pool,
710: tokenId,
711: positionSize,
712: isBurn
713: );
837: (int256 swap0, int256 swap1) = _univ3pool.swap(
838: msg.sender,
839: zeroForOne,
840: swapAmount,
841: zeroForOne
842: ? Constants.MIN_V3POOL_SQRT_RATIO + 1
843: : Constants.MAX_V3POOL_SQRT_RATIO - 1,
844: data
845: );
910: (_moved, _itmAmounts, _collectedSingleLeg) = _createLegInAMM(
911: _univ3pool,
912: _tokenId,
913: _leg,
914: liquidityChunk,
915: _isBurn
916: );
975: abi.encodePacked(
976: address(univ3pool),
977: msg.sender,
978: tokenType,
979: liquidityChunk.tickLower(),
980: liquidityChunk.tickUpper()
981: )
1086: collectedSingleLeg = _collectAndWritePositionData(
1087: liquidityChunk,
1088: univ3pool,
1089: currentLiquidity,
1090: positionKey,
1091: moved,
1092: isLong
1093: );
1098: s_accountFeesBase[positionKey] = _getFeesBase(
1099: univ3pool,
1100: updatedLiquidity,
1101: liquidityChunk,
1102: true
1103: );
1123: (s_accountPremiumOwed[positionKey], s_accountPremiumGross[positionKey]) = LeftRightLibrary
1124: .addCapped(
1125: s_accountPremiumOwed[positionKey],
1126: deltaPremiumOwed,
1127: s_accountPremiumGross[positionKey],
1128: deltaPremiumGross
1129: );
1203: (uint256 amount0, uint256 amount1) = univ3pool.mint(
1204: address(this),
1205: liquidityChunk.tickLower(),
1206: liquidityChunk.tickUpper(),
1207: liquidityChunk.liquidity(),
1208: mintdata
1209: );
1268: LeftRightSigned amountToCollect = _getFeesBase(
1269: univ3pool,
1270: startingLiquidity,
1271: liquidityChunk,
1272: false
1273: ).subRect(s_accountFeesBase[positionKey]);
1284: (uint128 receivedAmount0, uint128 receivedAmount1) = univ3pool.collect(
1285: msg.sender,
1286: liquidityChunk.tickLower(),
1287: liquidityChunk.tickUpper(),
1288: uint128(amountToCollect.rightSlot()),
1289: uint128(amountToCollect.leftSlot())
1290: );
1431: keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper))
1459: abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper)
1480: LeftRightSigned feesBase = FeesCalc.calculateAMMSwapFees(
1481: _univ3pool,
1482: atTick,
1483: _tickLower,
1484: _tickUpper,
1485: netLiquidity
1486: );
1507: (premiumOwed, premiumGross) = LeftRightLibrary.addCapped(
1508: s_accountPremiumOwed[positionKey],
1509: premiumOwed,
1510: s_accountPremiumGross[positionKey],
1511: premiumGross
1512: );
1544: keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper))
File: contracts/libraries/FeesCalc.sol
109: ) = _getAMMSwapFeesPerLiquidityCollected(univ3pool, currentTick, tickLower, tickUpper);
File: contracts/libraries/InteractionHelper.sol
72: string.concat(
73: prefix,
74: " ",
75: isToken0 ? symbol0 : symbol1,
76: " LP on ",
77: symbol0,
78: "/",
79: symbol1,
80: " ",
81: Strings.toString(fee),
82: "bps"
83: );
File: contracts/libraries/PanopticMath.sol
452: convertCollateralData(tokenData0, tokenData1, tokenType, Math.getSqrtRatioAtTick(tick));
671: (uint256 balanceCross, uint256 thresholdCross) = PanopticMath.convertCollateralData(
672: tokenData0,
673: tokenData1,
674: 0,
675: sqrtPriceX96Twap
676: );
856: if (haircut0 != 0) collateral0.exercise(_liquidatee, 0, 0, 0, int128(haircut0));
857: if (haircut1 != 0) collateral1.exercise(_liquidatee, 0, 0, 0, int128(haircut1));
File: contracts/tokens/ERC1155Minimal.sol
110: emit TransferSingle(msg.sender, from, to, id, amount);
114: ERC1155Holder(to).onERC1155Received(msg.sender, from, id, amount, data) !=
161: emit TransferBatch(msg.sender, from, to, ids, amounts);
165: ERC1155Holder(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) !=
220: emit TransferSingle(msg.sender, address(0), to, id, amount);
224: ERC1155Holder(to).onERC1155Received(msg.sender, address(0), id, amount, "") !=
239: emit TransferSingle(msg.sender, from, address(0), id, amount);
</details>
<a id="n-17"></a> [N-17] Constants in comparisons should appear on the left side
Description:
Constants or literal values in comparisons should appear on the left side of the comparison operator in order to prevent typo bugs. If an operator character is missing, an unintentional variable assignment can occur. For example:
// Intent - compare value to ten
if (voteCount == 10) {
// do something
}
// Typo - forgot one equal sign causing variable assignment
if (voteCount = 10) {
// now voteCount equals ten
}
// Preventative style - a missing equal sign will not compile
if (10 == voteCount) {
// do something
}
Recommendation:
Place constants and literal values on the left side of the comparison operator.
Instances:
There are 182 instances of this issue.
<details><summary>View 182 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit literal: 0
331: if (s_panopticPool.numberOfPositions(msg.sender) != 0) revert Errors.PositionCountNotZero();
/// @audit literal: 0
350: if (s_panopticPool.numberOfPositions(from) != 0) revert Errors.PositionCountNotZero();
/// @audit literal: 0
512: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0;
/// @audit literal: 0
575: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0;
/// @audit literal: 0
664: if (positionId.isLong(leg) == 0) continue;
/// @audit literal: 0
708: (tokenType == 0 && currentValue1 < oracleValue1) ||
/// @audit literal: 1
709: (tokenType == 1 && currentValue0 < oracleValue0)
/// @audit literal: 0
776: if (utilization < 0) {
/// @audit literal: 0
976: if (assets > 0) {
/// @audit literal: 0
1010: if (tokenToPay > 0) {
/// @audit literal: 0
1018: } else if (tokenToPay < 0) {
/// @audit literal: 0
1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) {
/// @audit literal: 0
1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) {
/// @audit literal: 0
1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) {
/// @audit literal: 0
1067: if (tokenToPay > 0) {
/// @audit literal: 0
1075: } else if (tokenToPay < 0) {
/// @audit literal: 0
1107: if (intrinsicValue != 0) {
/// @audit literal: 0
1168: if (positionBalanceArray.length > 0) {
/// @audit literal: 0
1173: if (premiumAllPositions < 0) {
/// @audit literal: 0
1182: if (premiumAllPositions > 0) {
/// @audit literal: 0
1325: uint128 amountMoved = tokenType == 0 ? amountsMoved.rightSlot() : amountsMoved.leftSlot();
/// @audit literal: 0
1328: int64 utilization = tokenType == 0
/// @audit literal: 0
1339: if (isLong == 0) {
/// @audit literal: 1
1346: ((atTick >= tickUpper) && (tokenType == 1)) || // strike OTM when price >= upperTick for tokenType=1
/// @audit literal: 0
1347: ((atTick < tickLower) && (tokenType == 0)) // strike OTM when price < lowerTick for tokenType=0
/// @audit literal: 1
1362: uint160 ratio = tokenType == 1 // tokenType
/// @audit literal: 1
1374: ((atTick < tickLower) && (tokenType == 1)) || // strike ITM but out of range price < lowerTick for tokenType=1
/// @audit literal: 0
1375: ((atTick >= tickUpper) && (tokenType == 0)) // strike ITM but out of range when price >= upperTick for tokenType=0
/// @audit literal: 1
1451: if (isLong == 1) {
/// @audit literal: 0
1479: if (isLong == 0) {
/// @audit literal: 1
1488: } else if (isLong == 1) {
/// @audit literal: 0
1544: if (tokenType == 0) {
/// @audit literal: 1
1559: if (tokenType == 1) {
/// @audit literal: 0
1582: tokenType == 0 ? movedRight : movedLeft,
/// @audit literal: 0
1584: tokenType == 0
/// @audit literal: 0
1637: uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) +
/// @audit literal: 0
1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
File: contracts/PanopticFactory.sol
/// @audit literal: 0
181: if (amount0Owed > 0)
/// @audit literal: 0
188: if (amount1Owed > 0)
File: contracts/PanopticPool.sol
/// @audit literal: 0
460: if (tokenId.isLong(leg) == 0 && !includePendingPremium) {
/// @audit literal: 0
533: if (medianData != 0) s_miniMedian = medianData;
/// @audit literal: 0
638: if (LeftRightUnsigned.unwrap(s_positionBalance[msg.sender][tokenId]) != 0)
/// @audit literal: 0
664: if (medianData != 0) s_miniMedian = medianData;
/// @audit literal: 1
768: if (isLong == 1) {
/// @audit literal: 0
865: if (tokenId.isLong(leg) == 0) {
/// @audit constant: MAX_SLOW_FAST_DELTA
943: if (Math.abs(int256(fastOracleTick) - slowOracleTick) > MAX_SLOW_FAST_DELTA)
/// @audit constant: MAX_TWAP_DELTA_LIQUIDATION
1035: if (Math.abs(currentTick - twapTick) > MAX_TWAP_DELTA_LIQUIDATION)
/// @audit literal: 1
1188: if (touchedId.length != 1) revert Errors.InputListFail();
/// @audit literal: 0
1274: if (positionIdListExercisor.length > 0)
/// @audit constant: MAX_POSITIONS
1415: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen();
/// @audit literal: 0
1483: if (netLiquidity == 0) return;
/// @audit literal: 1
1520: if ((isLong == 1) || computeAllPremia) {
/// @audit literal: 1
1566: if (isLong == 1) {
/// @audit literal: 0
1596: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg();
/// @audit literal: 3
1596: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg();
/// @audit literal: 0
1679: if (tokenId.isLong(leg) == 0) {
/// @audit literal: 0
1780: (accumulated0 == 0 ? type(uint256).max : accumulated0),
/// @audit literal: 0
1789: (accumulated1 == 0 ? type(uint256).max : accumulated1),
/// @audit literal: 0
1862: if (LeftRightSigned.unwrap(legPremia) != 0) {
/// @audit literal: 1
1864: if (tokenId.isLong(leg) == 1) {
/// @audit literal: 0
1928: s_grossPremiumLast[chunkKey] = totalLiquidity != 0
File: contracts/SemiFungiblePositionManager.sol
/// @audit literal: 0
362: if (s_AddrToPoolIdData[univ3pool] != 0) return;
/// @audit literal: 0
412: if (amount0Owed > 0)
/// @audit literal: 0
419: if (amount1Owed > 0)
/// @audit literal: 0
446: address token = amount0Delta > 0
/// @audit literal: 0
453: uint256 amountToPay = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta);
/// @audit literal: 0
632: (LeftRightUnsigned.unwrap(s_accountLiquidity[positionKey_to]) != 0) ||
/// @audit literal: 0
633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0)
/// @audit literal: 0
688: if (positionSize == 0) revert Errors.OptionsBalanceZero();
/// @audit constant: IUniswapV3Pool(address(0))
702: if (univ3pool == IUniswapV3Pool(address(0))) revert Errors.UniswapPoolNotInitialized();
/// @audit literal: 0
717: if ((LeftRightSigned.unwrap(itmAmounts) != 0)) {
/// @audit literal: 0
787: if ((itm0 != 0) && (itm1 != 0)) {
/// @audit literal: 0
787: if ((itm0 != 0) && (itm1 != 0)) {
/// @audit literal: 0
819: zeroForOne = net0 < 0;
/// @audit literal: 0
823: } else if (itm0 != 0) {
/// @audit literal: 0
824: zeroForOne = itm0 < 0;
/// @audit literal: 0
827: zeroForOne = itm1 > 0;
/// @audit literal: 0
833: if (swapAmount == 0) return LeftRightSigned.wrap(0);
/// @audit literal: 0
999: if (isLong == 0) {
/// @audit literal: 0
1066: moved = isLong == 0
/// @audit literal: 1
1073: if (tokenType == 1) {
/// @audit literal: 0
1078: if (tokenType == 0) {
/// @audit literal: 0
1085: if (currentLiquidity.rightSlot() > 0) {
/// @audit literal: 1
1275: if (isLong == 1) {
/// @audit literal: 0
1279: if (LeftRightSigned.unwrap(amountToCollect) != 0) {
/// @audit literal: 0
1296: collected0 = movedInLeg.rightSlot() < 0
/// @audit literal: 0
1299: collected1 = movedInLeg.leftSlot() < 0
/// @audit literal: 0
1469: if (netLiquidity != 0) {
/// @audit literal: 1
1514: acctPremia = isLong == 1 ? premiumOwed : premiumGross;
/// @audit literal: 1
1518: acctPremia = isLong == 1
File: contracts/libraries/FeesCalc.sol
/// @audit literal: 0
67: if (tokenId.isLong(leg) == 0) {
File: contracts/libraries/Math.sol
/// @audit literal: 0
74: return x > 0 ? x : -x;
/// @audit literal: 0
83: return x > 0 ? uint256(x) : uint256(-x);
/// @audit literal: 0x100000000000000000000000000000000
93: if (x >= 0x100000000000000000000000000000000) {
/// @audit literal: 0x10000000000000000
97: if (x >= 0x10000000000000000) {
/// @audit literal: 0x100000000
101: if (x >= 0x100000000) {
/// @audit literal: 0x10000
105: if (x >= 0x10000) {
/// @audit literal: 0x100
109: if (x >= 0x100) {
/// @audit literal: 0x10
113: if (x >= 0x10) {
/// @audit literal: 0
130: uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
/// @audit constant: uint256(int256(Constants.MAX_V3POOL_TICK))
131: if (absTick > uint256(int256(Constants.MAX_V3POOL_TICK))) revert Errors.InvalidTick();
/// @audit literal: 0
133: uint256 sqrtR = absTick & 0x1 != 0
/// @audit literal: 0
137: if (absTick & 0x2 != 0) sqrtR = (sqrtR * 0xfff97272373d413259a46990580e213a) >> 128;
/// @audit literal: 0
139: if (absTick & 0x4 != 0) sqrtR = (sqrtR * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
/// @audit literal: 0
141: if (absTick & 0x8 != 0) sqrtR = (sqrtR * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
/// @audit literal: 0
143: if (absTick & 0x10 != 0) sqrtR = (sqrtR * 0xffcb9843d60f6159c9db58835c926644) >> 128;
/// @audit literal: 0
145: if (absTick & 0x20 != 0) sqrtR = (sqrtR * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
/// @audit literal: 0
147: if (absTick & 0x40 != 0) sqrtR = (sqrtR * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
/// @audit literal: 0
149: if (absTick & 0x80 != 0) sqrtR = (sqrtR * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
/// @audit literal: 0
151: if (absTick & 0x100 != 0) sqrtR = (sqrtR * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
/// @audit literal: 0
153: if (absTick & 0x200 != 0) sqrtR = (sqrtR * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
/// @audit literal: 0
155: if (absTick & 0x400 != 0) sqrtR = (sqrtR * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
/// @audit literal: 0
157: if (absTick & 0x800 != 0) sqrtR = (sqrtR * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
/// @audit literal: 0
159: if (absTick & 0x1000 != 0) sqrtR = (sqrtR * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
/// @audit literal: 0
161: if (absTick & 0x2000 != 0) sqrtR = (sqrtR * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
/// @audit literal: 0
163: if (absTick & 0x4000 != 0) sqrtR = (sqrtR * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
/// @audit literal: 0
165: if (absTick & 0x8000 != 0) sqrtR = (sqrtR * 0x31be135f97d08fd981231505542fcfa6) >> 128;
/// @audit literal: 0
167: if (absTick & 0x10000 != 0) sqrtR = (sqrtR * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
/// @audit literal: 0
169: if (absTick & 0x20000 != 0) sqrtR = (sqrtR * 0x5d6af8dedb81196699c329225ee604) >> 128;
/// @audit literal: 0
171: if (absTick & 0x40000 != 0) sqrtR = (sqrtR * 0x2216e584f5fa1ea926041bedfe98) >> 128;
/// @audit literal: 0
173: if (absTick & 0x80000 != 0) sqrtR = (sqrtR * 0x48a170391f7dc42444e8fa2) >> 128;
/// @audit literal: 0
176: if (tick > 0) sqrtR = type(uint256).max / sqrtR;
/// @audit literal: 0
179: return uint160((sqrtR >> 32) + (sqrtR % (1 << 32) == 0 ? 0 : 1));
/// @audit literal: 0
312: if ((downcastedInt = int128(toCast)) < 0) revert Errors.CastingError();
/// @audit literal: 0
360: if (prod1 == 0) {
/// @audit literal: 0
361: require(denominator > 0);
/// @audit literal: 0
447: if (mulmod(a, b, denominator) > 0) {
/// @audit literal: 0
474: if (prod1 == 0) {
/// @audit literal: 0
537: if (prod1 == 0) {
/// @audit literal: 0
587: if (mulmod(a, b, 2 ** 96) > 0) {
/// @audit literal: 0
614: if (prod1 == 0) {
/// @audit literal: 0
664: if (mulmod(a, b, 2 ** 128) > 0) {
/// @audit literal: 0
691: if (prod1 == 0) {
File: contracts/libraries/PanopticMath.sol
/// @audit literal: 8
207: for (uint8 i; i < 8; ++i) {
/// @audit literal: 7
211: if (rank == 7) {
/// @audit literal: 20
248: for (uint256 i = 0; i < 20; ++i) {
/// @audit literal: 19
256: for (uint256 i = 0; i < 19; ++i) {
/// @audit literal: 0
325: if (tokenId.asset(legIndex) == 0) {
/// @audit literal: 0
357: tickLower % tickSpacing != 0 ||
/// @audit literal: 0
358: tickUpper % tickSpacing != 0 ||
/// @audit literal: 0
425: if (tokenType == 0) {
/// @audit literal: 0
475: uint256 notional = asset == 0
/// @audit literal: 0
479: if (notional == 0 || notional > type(uint128).max) revert Errors.InvalidNotionalValue();
/// @audit literal: 0
532: return amount < 0 ? -absResult : absResult;
/// @audit literal: 0
537: return amount < 0 ? -absResult : absResult;
/// @audit literal: 0
555: return amount < 0 ? -absResult : absResult;
/// @audit literal: 0
564: return amount < 0 ? -absResult : absResult;
/// @audit literal: 0
584: if (tokenId.asset(legIndex) == 0) {
/// @audit literal: 0
615: bool isShort = tokenId.isLong(legIndex) == 0;
/// @audit literal: 0
618: if (tokenId.tokenType(legIndex) == 0) {
/// @audit literal: 1
785: if (tokenId.isLong(leg) == 1) {
/// @audit literal: 0
856: if (haircut0 != 0) collateral0.exercise(_liquidatee, 0, 0, 0, int128(haircut0));
/// @audit literal: 0
857: if (haircut1 != 0) collateral1.exercise(_liquidatee, 0, 0, 0, int128(haircut1));
/// @audit literal: 1
864: if (tokenId.isLong(leg) == 1) {
/// @audit literal: 0
931: if (balanceShortage > 0) {
/// @audit literal: 0
949: if (balanceShortage > 0) {
File: contracts/tokens/ERC1155Minimal.sol
/// @audit literal: 0
112: if (to.code.length != 0) {
/// @audit literal: 0
163: if (to.code.length != 0) {
/// @audit literal: 0x01ffc9a7
202: interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
/// @audit literal: 0xd9b67a26
203: interfaceId == 0xd9b67a26; // ERC165 Interface ID for ERC1155
/// @audit literal: 0
222: if (to.code.length != 0) {
File: contracts/types/TokenId.sol
/// @audit constant: 2 ** 64
376: if (optionRatios < 2 ** 64) {
/// @audit constant: 2 ** 112
378: } else if (optionRatios < 2 ** 112) {
/// @audit constant: 2 ** 160
380: } else if (optionRatios < 2 ** 160) {
/// @audit constant: 2 ** 208
382: } else if (optionRatios < 2 ** 208) {
/// @audit constant: 2 ** 64
439: if (optionRatios < 2 ** 64) {
/// @audit constant: 2 ** 112
441: } else if (optionRatios < 2 ** 112) {
/// @audit constant: 2 ** 160
443: } else if (optionRatios < 2 ** 160) {
/// @audit constant: 2 ** 208
445: } else if (optionRatios < 2 ** 208) {
/// @audit literal: 0
465: if (i == 0)
/// @audit literal: 1
471: if (i == 1)
/// @audit literal: 2
477: if (i == 2)
/// @audit literal: 3
483: if (i == 3)
/// @audit literal: 0
501: if (self.optionRatio(0) == 0) revert Errors.InvalidTokenIdParameter(1);
/// @audit literal: 4
507: for (uint256 i = 0; i < 4; ++i) {
/// @audit literal: 0
508: if (self.optionRatio(i) == 0) {
/// @audit literal: 0
512: if ((TokenId.unwrap(self) >> (64 + 48 * i)) != 0)
/// @audit literal: 0
528: if ((self.width(i) == 0)) revert Errors.InvalidTokenIdParameter(5);
/// @audit constant: Constants.MIN_V3POOL_TICK
531: (self.strike(i) == Constants.MIN_V3POOL_TICK) ||
/// @audit constant: Constants.MAX_V3POOL_TICK
532: (self.strike(i) == Constants.MAX_V3POOL_TICK)
/// @audit literal: 1
566: if (((_isLong != isLongP) || _isLong == 1) && (_tokenType != tokenTypeP))
/// @audit literal: 1
592: if (self.isLong(i) == 1) return; // validated
</details>
<a id="n-18"></a> [N-18] Contract should expose an interface
Description:
All external
/public
functions should extend an interface
. This is useful to make sure that the whole API is extracted.
Instances:
There are 71 instances of this issue.
<details><summary>View 71 Instances</summary>
File: contracts/CollateralTracker.sol
221: function startToken(
222: bool underlyingIsToken0,
223: address token0,
224: address token1,
225: uint24 fee,
226: PanopticPool panopticPool
227: ) external {
277: function getPoolData()
278: external
279: view
280: returns (uint256 poolAssets, uint256 insideAMM, int256 currentPoolUtilization)
281: {
289: function name() external view returns (string memory) {
303: function symbol() external view returns (string memory) {
310: function decimals() external view returns (uint8) {
361: function asset() external view returns (address assetTokenAddress) {
370: function totalAssets() public view returns (uint256 totalManagedAssets) {
379: function convertToShares(uint256 assets) public view returns (uint256 shares) {
386: function convertToAssets(uint256 shares) public view returns (uint256 assets) {
392: function maxDeposit(address) external pure returns (uint256 maxAssets) {
399: function previewDeposit(uint256 assets) public view returns (uint256 shares) {
417: function deposit(uint256 assets, address receiver) external returns (uint256 shares) {
444: function maxMint(address) external view returns (uint256 maxShares) {
453: function previewMint(uint256 shares) public view returns (uint256 assets) {
477: function mint(uint256 shares, address receiver) external returns (uint256 assets) {
507: function maxWithdraw(address owner) public view returns (uint256 maxAssets) {
518: function previewWithdraw(uint256 assets) public view returns (uint256 shares) {
531: function withdraw(
532: uint256 assets,
533: address receiver,
534: address owner
535: ) external returns (uint256 shares) {
572: function maxRedeem(address owner) public view returns (uint256 maxShares) {
581: function previewRedeem(uint256 shares) public view returns (uint256 assets) {
591: function redeem(
592: uint256 shares,
593: address receiver,
594: address owner
595: ) external returns (uint256 assets) {
650: function exerciseCost(
651: int24 currentTick,
652: int24 oracleTick,
653: TokenId positionId,
654: uint128 positionBalance,
655: LeftRightSigned longAmounts
656: ) external view returns (LeftRightSigned exerciseFees) {
864: function delegate(
865: address delegator,
866: address delegatee,
867: uint256 assets
868: ) external onlyPanopticPool {
894: function delegate(address delegatee, uint256 assets) external onlyPanopticPool {
903: function refund(address delegatee, uint256 assets) external onlyPanopticPool {
911: function revoke(
912: address delegator,
913: address delegatee,
914: uint256 assets
915: ) external onlyPanopticPool {
975: function refund(address refunder, address refundee, int256 assets) external onlyPanopticPool {
995: function takeCommissionAddData(
996: address optionOwner,
997: int128 longAmount,
998: int128 shortAmount,
999: int128 swappedAmount
1000: ) external onlyPanopticPool returns (int256 utilization) {
1043: function exercise(
1044: address optionOwner,
1045: int128 longAmount,
1046: int128 shortAmount,
1047: int128 swappedAmount,
1048: int128 realizedPremium
1049: ) external onlyPanopticPool returns (int128) {
1141: function getAccountMarginDetails(
1142: address user,
1143: int24 currentTick,
1144: uint256[2][] memory positionBalanceArray,
1145: int128 premiumAllPositions
1146: ) public view returns (LeftRightUnsigned tokenData) {
File: contracts/PanopticFactory.sol
147: function transferOwnership(address newOwner) external {
159: function owner() external view returns (address) {
172: function uniswapV3MintCallback(
173: uint256 amount0Owed,
174: uint256 amount1Owed,
175: bytes calldata data
176: ) external {
210: function deployNewPool(
211: address token0,
212: address token1,
213: uint24 fee,
214: bytes32 salt
215: ) external returns (PanopticPool newPoolContract) {
290: function minePoolAddress(
291: bytes32 salt,
292: uint256 loops,
293: uint256 minTargetRarity
294: ) external view returns (bytes32 bestSalt, uint256 highestRarity) {
420: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
File: contracts/PanopticPool.sol
291: function startPool(
292: IUniswapV3Pool _univ3pool,
293: address token0,
294: address token1,
295: CollateralTracker collateralTracker0,
296: CollateralTracker collateralTracker1
297: ) external {
338: function assertPriceWithinBounds(uint160 sqrtLowerBound, uint160 sqrtUpperBound) external view {
352: function optionPositionBalance(
353: address user,
354: TokenId tokenId
355: ) external view returns (uint128 balance, uint64 poolUtilization0, uint64 poolUtilization1) {
381: function calculateAccumulatedFeesBatch(
382: address user,
383: bool includePendingPremium,
384: TokenId[] calldata positionIdList
385: ) external view returns (int128 premium0, int128 premium1, uint256[2][] memory) {
410: function calculatePortfolioValue(
411: address user,
412: int24 atTick,
413: TokenId[] calldata positionIdList
414: ) external view returns (int256 value0, int256 value1) {
522: function pokeMedian() external {
547: function mintOptions(
548: TokenId[] calldata positionIdList,
549: uint128 positionSize,
550: uint64 effectiveLiquidityLimitX32,
551: int24 tickLimitLow,
552: int24 tickLimitHigh
553: ) external {
569: function burnOptions(
570: TokenId tokenId,
571: TokenId[] calldata newPositionIdList,
572: int24 tickLimitLow,
573: int24 tickLimitHigh
574: ) external {
586: function burnOptions(
587: TokenId[] calldata positionIdList,
588: TokenId[] calldata newPositionIdList,
589: int24 tickLimitLow,
590: int24 tickLimitHigh
591: ) external {
1017: function liquidate(
1018: TokenId[] calldata positionIdListLiquidator,
1019: address liquidatee,
1020: LeftRightUnsigned delegations,
1021: TokenId[] calldata positionIdList
1022: ) external {
1179: function forceExercise(
1180: address account,
1181: TokenId[] calldata touchedId,
1182: TokenId[] calldata positionIdListExercisee,
1183: TokenId[] calldata positionIdListExercisor
1184: ) external {
1425: function univ3pool() external view returns (IUniswapV3Pool) {
1431: function collateralToken0() external view returns (CollateralTracker collateralToken) {
1437: function collateralToken1() external view returns (CollateralTracker) {
1444: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) {
1587: function settleLongPremium(
1588: TokenId[] calldata positionIdList,
1589: address owner,
1590: uint256 legIndex
1591: ) external {
File: contracts/SemiFungiblePositionManager.sol
350: function initializeAMMPool(address token0, address token1, uint24 fee) external {
402: function uniswapV3MintCallback(
403: uint256 amount0Owed,
404: uint256 amount1Owed,
405: bytes calldata data
406: ) external {
435: function uniswapV3SwapCallback(
436: int256 amount0Delta,
437: int256 amount1Delta,
438: bytes calldata data
439: ) external {
471: function burnTokenizedPosition(
472: TokenId tokenId,
473: uint128 positionSize,
474: int24 slippageTickLimitLow,
475: int24 slippageTickLimitHigh
476: )
477: external
478: ReentrancyLock(tokenId.poolId())
479: returns (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped)
480: {
504: function mintTokenizedPosition(
505: TokenId tokenId,
506: uint128 positionSize,
507: int24 slippageTickLimitLow,
508: int24 slippageTickLimitHigh
509: )
510: external
511: ReentrancyLock(tokenId.poolId())
512: returns (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped)
513: {
1421: function getAccountLiquidity(
1422: address univ3pool,
1423: address owner,
1424: uint256 tokenType,
1425: int24 tickLower,
1426: int24 tickUpper
1427: ) external view returns (LeftRightUnsigned accountLiquidities) {
1449: function getAccountPremium(
1450: address univ3pool,
1451: address owner,
1452: uint256 tokenType,
1453: int24 tickLower,
1454: int24 tickUpper,
1455: int24 atTick,
1456: uint256 isLong
1457: ) external view returns (uint128, uint128) {
1535: function getAccountFeesBase(
1536: address univ3pool,
1537: address owner,
1538: uint256 tokenType,
1539: int24 tickLower,
1540: int24 tickUpper
1541: ) external view returns (int128 feesBase0, int128 feesBase1) {
1555: function getUniswapV3PoolFromId(
1556: uint64 poolId
1557: ) external view returns (IUniswapV3Pool UniswapV3Pool) {
1566: function getPoolId(address univ3pool) external view returns (uint64 poolId) {
File: contracts/multicall/Multicall.sol
12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
File: contracts/tokens/ERC1155Minimal.sol
81: function setApprovalForAll(address operator, bool approved) public {
94: function safeTransferFrom(
95: address from,
96: address to,
97: uint256 id,
98: uint256 amount,
99: bytes calldata data
100: ) public virtual {
130: function safeBatchTransferFrom(
131: address from,
132: address to,
133: uint256[] calldata ids,
134: uint256[] calldata amounts,
135: bytes calldata data
136: ) public virtual {
178: function balanceOfBatch(
179: address[] calldata owners,
180: uint256[] calldata ids
181: ) public view returns (uint256[] memory balances) {
200: function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
File: contracts/tokens/ERC20Minimal.sol
49: function approve(address spender, uint256 amount) public returns (bool) {
61: function transfer(address to, uint256 amount) public virtual returns (bool) {
81: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
</details>
<a id="n-19"></a> [N-19] Contract uses both require()
/revert()
as well as custom errors
Description:
To increase consistency and maintainability, consider using just one method in a single file.
Recommendation:
Use custom errors.
Instances:
There is 1 instance of this issue.
File: contracts/libraries/Math.sol
13: library Math {
<a id="n-20"></a> [N-20] Custom errors should be used rather than revert()
/require()
Description:
Custom errors have been available since Solidity version 0.8.4. Custom errors are more easily processed in try
-catch
blocks, and are easier to reuse and maintain.
Recommendation:
Refactor revert("description")
and require()
calls to use custom errors.
Instances:
There are 9 instances of this issue.
<details><summary>View 9 Instances</summary>
File: contracts/libraries/Math.sol
361: require(denominator > 0);
370: require(denominator > prod1);
448: require(result < type(uint256).max);
484: require(2 ** 64 > prod1);
547: require(2 ** 96 > prod1);
588: require(result < type(uint256).max);
624: require(2 ** 128 > prod1);
665: require(result < type(uint256).max);
701: require(2 ** 192 > prod1);
</details>
<a id="n-21"></a> [N-21] Duplicated revert()
checks should be refactored to a modifier
or function
Description:
By factoring out the logic to a modifier, if it needs to change it can be done in just one place. The compiler will inline the function, which will avoid JUMP
instructions usually associated with functions.
Instances:
There are 2 instances of this issue.
File: contracts/CollateralTracker.sol
/// @audit same revert also defined at contracts/CollateralTracker.sol:418
480: if (assets > type(uint104).max) revert Errors.DepositTooLarge();
File: contracts/tokens/ERC1155Minimal.sol
/// @audit same revert also defined at contracts/tokens/ERC1155Minimal.sol:101
137: if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized();
<a id="n-22"></a> [N-22] else
-block not required
Description:
To improve readability and maintainability, one level of nesting can be removed by not having an else
block when the if
block always returns.
Recommendation:
Drop the else
and pull the else
block body code up one level when there is a return
in the if
block. Example:
// Before:
if (foo) {
return bar1;
} else {
return bar2;
}
// After:
if (foo) {
return bar1;
}
return bar2;
Instances:
There are 10 instances of this issue.
<details><summary>View 10 Instances</summary>
File: contracts/SemiFungiblePositionManager.sol
1013: if (startingLiquidity < chunkLiquidity) {
1014: // the amount we want to move (liquidityChunk.legLiquidity()) out of uniswap is greater than
1015: // what the account that owns the liquidity in uniswap has (startingLiquidity)
1016: // we must ensure that an account can only move its own liquidity out of uniswap
1017: // so we revert in this case
1018: revert Errors.NotEnoughLiquidity();
1019: } else {
1020: // startingLiquidity is >= chunkLiquidity, so no possible underflow
1021: unchecked {
1022: // we want to move less than what already sits in uniswap, no problem:
1023: updatedLiquidity = startingLiquidity - chunkLiquidity;
1024: }
1025: }
File: contracts/libraries/PanopticMath.sol
325: if (tokenId.asset(legIndex) == 0) {
326: return Math.getLiquidityForAmount0(tickLower, tickUpper, amount);
327: } else {
328: return Math.getLiquidityForAmount1(tickLower, tickUpper, amount);
329: }
425: if (tokenType == 0) {
426: return (
427: tokenData0.rightSlot() + convert1to0(tokenData1.rightSlot(), sqrtPriceX96),
428: tokenData0.leftSlot() + convert1to0(tokenData1.leftSlot(), sqrtPriceX96)
429: );
430: } else {
431: return (
432: tokenData1.rightSlot() + convert0to1(tokenData0.rightSlot(), sqrtPriceX96),
433: tokenData1.leftSlot() + convert0to1(tokenData0.leftSlot(), sqrtPriceX96)
434: );
435: }
494: if (sqrtPriceX96 < type(uint128).max) {
495: return Math.mulDiv192(amount, uint256(sqrtPriceX96) ** 2);
496: } else {
497: return Math.mulDiv128(amount, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96));
498: }
511: if (sqrtPriceX96 < type(uint128).max) {
512: return Math.mulDiv(amount, 2 ** 192, uint256(sqrtPriceX96) ** 2);
513: } else {
514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96));
515: }
528: if (sqrtPriceX96 < type(uint128).max) {
529: int256 absResult = Math
530: .mulDiv192(Math.absUint(amount), uint256(sqrtPriceX96) ** 2)
531: .toInt256();
532: return amount < 0 ? -absResult : absResult;
533: } else {
534: int256 absResult = Math
535: .mulDiv128(Math.absUint(amount), Math.mulDiv64(sqrtPriceX96, sqrtPriceX96))
536: .toInt256();
537: return amount < 0 ? -absResult : absResult;
538: }
551: if (sqrtPriceX96 < type(uint128).max) {
552: int256 absResult = Math
553: .mulDiv(Math.absUint(amount), 2 ** 192, uint256(sqrtPriceX96) ** 2)
554: .toInt256();
555: return amount < 0 ? -absResult : absResult;
556: } else {
557: int256 absResult = Math
558: .mulDiv(
559: Math.absUint(amount),
560: 2 ** 128,
561: Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)
562: )
563: .toInt256();
564: return amount < 0 ? -absResult : absResult;
565: }
File: contracts/types/TokenId.sol
439: if (optionRatios < 2 ** 64) {
440: return 0;
441: } else if (optionRatios < 2 ** 112) {
442: return 1;
443: } else if (optionRatios < 2 ** 160) {
444: return 2;
445: } else if (optionRatios < 2 ** 208) {
446: return 3;
447: }
441: } else if (optionRatios < 2 ** 112) {
442: return 1;
443: } else if (optionRatios < 2 ** 160) {
444: return 2;
445: } else if (optionRatios < 2 ** 208) {
446: return 3;
447: }
443: } else if (optionRatios < 2 ** 160) {
444: return 2;
445: } else if (optionRatios < 2 ** 208) {
446: return 3;
447: }
</details>
<a id="n-23"></a> [N-23] Enable IR-based code generation
Description:
Solidity can generate EVM bytecode in two different ways: Either directly from Solidity to EVM opcodes (โold codegenโ) or through an intermediate representation (โIRโ) in Yul (โnew codegenโ or โIR-based codegenโ). The IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions.
Recommendation:
Use --via-ir
(command line) or {"viaIR": true}
(in standard-json) to take advantage of the benefits of IR-based codegen including extra gas savings. When using Foundry, set the via_ir property to true
.
Instances:
There is 1 instance of this issue.
<a id="n-24"></a> [N-24] Events are missing sender information
Description:
When an event is emitted based on a user's action, not being able to filter based on the address that triggered the event makes event processing more difficult, particularly when msg.sender
is not tx.origin
.
Recommendation:
Include msg.sender
in user-triggered event emissions.
Instances:
There are 5 instances of this issue.
File: contracts/PanopticFactory.sol
154: emit OwnershipTransferred(currentOwner, newOwner);
268: emit PoolDeployed(
269: newPoolContract,
270: v3Pool,
271: collateralTracker0,
272: collateralTracker1,
273: amount0,
274: amount1
275: );
File: contracts/PanopticPool.sol
1654: emit PremiumSettled(owner, tokenId, realizedPremia);
File: contracts/SemiFungiblePositionManager.sol
390: emit PoolInitialized(univ3pool, poolId);
File: contracts/tokens/ERC20Minimal.sol
94: emit Transfer(from, to, amount);
<a id="n-25"></a> [N-25] High cyclomatic complexity
Description:
Consider breaking down these blocks into more manageable units, by splitting things into utility functions, by reducing nesting, and by using early returns.
Instances:
There are 5 instances of this issue.
File: contracts/SemiFungiblePositionManager.sol
958: function _createLegInAMM(
959: IUniswapV3Pool univ3pool,
960: TokenId tokenId,
961: uint256 leg,
962: LiquidityChunk liquidityChunk,
963: bool isBurn
964: )
965: internal
966: returns (
967: LeftRightSigned moved,
968: LeftRightSigned itmAmounts,
969: LeftRightUnsigned collectedSingleLeg
970: )
971: {
File: contracts/libraries/Math.sol
128: function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160) {
753: function quickSort(int256[] memory arr, int256 left, int256 right) internal pure {
File: contracts/libraries/PanopticMath.sol
768: function haircutPremia(
769: address liquidatee,
770: TokenId[] memory positionIdList,
771: LeftRightSigned[4][] memory premiasByLeg,
772: LeftRightSigned collateralRemaining,
773: CollateralTracker collateral0,
774: CollateralTracker collateral1,
775: uint160 sqrtPriceX96Final,
776: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens
777: ) external returns (int256, int256) {
File: contracts/types/TokenId.sol
500: function validate(TokenId self) internal pure {
<a id="n-26"></a> [N-26] if
-statement can be converted to a ternary
Description:
The code can be made more compact while also increasing readability by converting the following if
-statements to ternaries. For example: foo = (x > y) ? a : b;
Instances:
There are 6 instances of this issue.
<details><summary>View 6 Instances</summary>
File: contracts/CollateralTracker.sol
1544: if (tokenType == 0) {
1545: spreadRequirement = movedRight < movedPartnerRight
1546: ? movedPartnerRight - movedRight
1547: : movedRight - movedPartnerRight;
1548: } else {
1549: spreadRequirement = movedLeft < movedPartnerLeft
1550: ? movedPartnerLeft - movedLeft
1551: : movedLeft - movedPartnerLeft;
1552: }
File: contracts/libraries/FeesCalc.sol
67: if (tokenId.isLong(leg) == 0) {
68: unchecked {
69: value0 += int256(amount0);
70: value1 += int256(amount1);
71: }
72: } else {
73: unchecked {
74: value0 -= int256(amount0);
75: value1 -= int256(amount1);
76: }
77: }
File: contracts/libraries/PanopticMath.sol
325: if (tokenId.asset(legIndex) == 0) {
326: return Math.getLiquidityForAmount0(tickLower, tickUpper, amount);
327: } else {
328: return Math.getLiquidityForAmount1(tickLower, tickUpper, amount);
329: }
425: if (tokenType == 0) {
426: return (
427: tokenData0.rightSlot() + convert1to0(tokenData1.rightSlot(), sqrtPriceX96),
428: tokenData0.leftSlot() + convert1to0(tokenData1.leftSlot(), sqrtPriceX96)
429: );
430: } else {
431: return (
432: tokenData1.rightSlot() + convert0to1(tokenData0.rightSlot(), sqrtPriceX96),
433: tokenData1.leftSlot() + convert0to1(tokenData0.leftSlot(), sqrtPriceX96)
434: );
435: }
494: if (sqrtPriceX96 < type(uint128).max) {
495: return Math.mulDiv192(amount, uint256(sqrtPriceX96) ** 2);
496: } else {
497: return Math.mulDiv128(amount, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96));
498: }
511: if (sqrtPriceX96 < type(uint128).max) {
512: return Math.mulDiv(amount, 2 ** 192, uint256(sqrtPriceX96) ** 2);
513: } else {
514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96));
515: }
</details>
<a id="n-27"></a> [N-27] Inconsistent spacing in comments
Description:
Single line comments should be formatted with one space after the two forward slashes, like // Comment here
. The following instances lack the space after the comment-starting slashes.
Recommendation:
Add a space after the two forward slashes for single line comments. Incorporate a tool like forge fmt
or Prettier in your toolchain to do this automatically.
Instances:
There are 5 instances of this issue.
File: contracts/CollateralTracker.sol
1117:
1118: //compute total commission amount = commission rate + spread fee
File: contracts/SemiFungiblePositionManager.sol
609:
610: //construct the positionKey for the from and to addresses
642:
643: //update+store liquidity and fee values between accounts
820:
821: //compute the swap amount, set as positive (exact input)
989: {
990: // did we have liquidity already deployed in Uniswap for this chunk range from some past mint?
<a id="n-28"></a> [N-28] Large contracts should be refactored into multiple, smaller, contracts
Description:
The larger a contract is, the more difficult it can be to comprehend, test, and maintain. Smaller contracts help reduce the cyclomatic complexity of a protocol. For splitting contracts, ask yourself:
- Which functions belong together? Each set of functions might be best in its own contract.
- Which functions do not require reading contract state or just a specific subset of the state?
- Can you split storage and functionality?
Recommendation:
Separate your contracts into multiple, smaller contracts.
Instances:
There are 6 instances of this issue.
<details><summary>View 6 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit 1,614 lines in contract
36: contract CollateralTracker is ERC20Minimal, Multicall {
File: contracts/PanopticPool.sol
/// @audit 1,954 lines in contract
27: contract PanopticPool is ERC1155Holder, Multicall {
File: contracts/SemiFungiblePositionManager.sol
/// @audit 1,497 lines in contract
72: contract SemiFungiblePositionManager is ERC1155, Multicall {
File: contracts/libraries/Math.sol
/// @audit 769 lines in contract
13: library Math {
File: contracts/libraries/PanopticMath.sol
/// @audit 949 lines in contract
18: library PanopticMath {
File: contracts/types/TokenId.sol
/// @audit 540 lines in contract
60: library TokenIdLibrary {
</details>
<a id="n-29"></a> [N-29] Local variable shadows state variable
Description:
Note that this is not the same as shadowing, which is reported with a different compiler warning.
Instances:
There are 16 instances of this issue.
<details><summary>View 16 Instances</summary>
File: contracts/CollateralTracker.sol
704: uint256 tokenType = positionId.tokenType(leg);
1319: uint256 tokenType = tokenId.tokenType(index);
1332: uint256 isLong = tokenId.isLong(index);
1352: int24 strike = tokenId.strike(index);
1449: uint256 isLong = tokenId.isLong(index);
1535: uint256 tokenType = tokenId.tokenType(index);
File: contracts/PanopticFactory.sol
391: int24 tickSpacing = v3Pool.tickSpacing();
File: contracts/PanopticPool.sol
749: uint256 isLong = tokenId.isLong(leg);
1519: uint256 isLong = tokenId.isLong(leg);
1526: uint256 tokenType = tokenId.tokenType(leg);
1604: uint256 tokenType = tokenId.tokenType(legIndex);
1627: uint256 liquidity = PanopticMath
1628: .getLiquidityChunk(tokenId, legIndex, s_positionBalance[owner][tokenId].rightSlot())
1629: .liquidity();
1810: uint256 tokenType = tokenId.tokenType(leg);
File: contracts/SemiFungiblePositionManager.sol
972: uint256 tokenType = tokenId.tokenType(leg);
987: uint256 isLong = tokenId.isLong(leg);
File: contracts/libraries/PanopticMath.sol
49: int24 tickSpacing = IUniswapV3Pool(univ3pool).tickSpacing();
</details>
<a id="n-30"></a> [N-30] Missing checks for state variable assignments
Description:
Consider whether reasonable bounds checks for variables would be useful.
Instances:
There are 18 instances of this issue.
<details><summary>View 18 Instances</summary>
File: contracts/CollateralTracker.sol
895: balanceOf[delegatee] += convertToShares(assets);
904: balanceOf[delegatee] -= convertToShares(assets);
File: contracts/PanopticPool.sol
654: s_positionBalance[msg.sender][tokenId] = LeftRightUnsigned
655: .wrap(0)
656: .toLeftSlot(poolUtilizations)
657: .toRightSlot(positionSize);
File: contracts/tokens/ERC1155Minimal.sol
103: balanceOf[from][id] -= amount;
107: balanceOf[to][id] += amount;
217: balanceOf[to][id] += amount;
237: balanceOf[from][id] -= amount;
File: contracts/tokens/ERC20Minimal.sol
50: allowance[msg.sender][spender] = amount;
62: balanceOf[msg.sender] -= amount;
67: balanceOf[to] += amount;
86: balanceOf[from] -= amount;
91: balanceOf[to] += amount;
104: balanceOf[from] -= amount;
109: balanceOf[to] += amount;
126: balanceOf[to] += amount;
128: totalSupply += amount;
137: balanceOf[from] -= amount;
142: totalSupply -= amount;
</details>
<a id="n-31"></a> [N-31] Missing empty bytes
parameter check
Description:
It is important to validate the bytes
inputs of functions to ensure they are not empty, especially when they represent crucial data such as addresses, identifiers, or raw data that the contract needs to process. Missing empty bytes checks can lead to unexpected behavior. For example, certain operations might fail, produce incorrect results, or consume unnecessary gas when performed with empty bytes. Also, missing input validation can potentially expose your contract to malicious activity, including exploitation of unhandled edge cases.
Recommendation:
Always validate that bytes
parameters are not empty when the logic of the contract requires that they are not.
Instances:
There are 4 instances of this issue.
File: contracts/PanopticFactory.sol
/// @audit not checked: salt
210: function deployNewPool(
211: address token0,
212: address token1,
213: uint24 fee,
214: bytes32 salt
215: ) external returns (PanopticPool newPoolContract) {
/// @audit not checked: salt
290: function minePoolAddress(
291: bytes32 salt,
292: uint256 loops,
293: uint256 minTargetRarity
294: ) external view returns (bytes32 bestSalt, uint256 highestRarity) {
File: contracts/SemiFungiblePositionManager.sol
/// @audit not checked: positionKey
1110: function _updateStoredPremia(
1111: bytes32 positionKey,
1112: LeftRightUnsigned currentLiquidity,
1113: LeftRightUnsigned collectedAmounts
1114: ) private {
/// @audit not checked: positionKey
1255: function _collectAndWritePositionData(
1256: LiquidityChunk liquidityChunk,
1257: IUniswapV3Pool univ3pool,
1258: LeftRightUnsigned currentLiquidity,
1259: bytes32 positionKey,
1260: LeftRightSigned movedInLeg,
1261: uint256 isLong
1262: ) internal returns (LeftRightUnsigned collectedChunk) {
<a id="n-32"></a> [N-32] Missing event emission in initializer
Description:
Consider emitting an event when the contract is initialized to make it easier for to off-chain tools to track the exact point in time when the contract was initialized.
Instances:
There is 1 instance of this issue.
File: contracts/PanopticFactory.sol
134: function initialize(address _owner) public {
<a id="n-33"></a> [N-33] Missing event for critical parameter change
Description:
Events help off-chain tools to track changes.
Recommendation:
Emit an event for critical parameter changes, including the old and new values.
Instances:
There is 1 instance of this issue.
File: contracts/PanopticPool.sol
/// @audit state variable changed: s_settledTokens
1666: function _updateSettlementPostMint(
1667: TokenId tokenId,
1668: LeftRightUnsigned[4] memory collectedByLeg,
1669: uint128 positionSize
1670: ) internal {
<a id="n-34"></a> [N-34] Multiple address
/ID mappings can be combined into a single mapping
of an address
/ID to a struct
, for readability
Description:
Well-organized data structures make code reviews easier, which may lead to fewer bugs. Consider combining related mappings into mappings to structs, so it is clear what data is related.
Instances:
There are 12 instances of this issue.
<details><summary>View 12 Instances</summary>
File: contracts/PanopticPool.sol
245: mapping(bytes32 chunkKey => LeftRightUnsigned lastGrossPremium) internal s_grossPremiumLast;
251: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) internal s_settledTokens;
272: mapping(address account => uint256 positionsHash) internal s_positionsHash;
File: contracts/SemiFungiblePositionManager.sol
145: mapping(address univ3pool => uint256 poolIdData) internal s_AddrToPoolIdData;
177: mapping(bytes32 positionKey => LeftRightUnsigned removedAndNetLiquidity)
287: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumOwed;
289: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumGross;
295: mapping(bytes32 positionKey => LeftRightSigned baseFees0And1) internal s_accountFeesBase;
File: contracts/libraries/PanopticMath.sol
776: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens
865: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens)
File: contracts/tokens/ERC20Minimal.sol
35: mapping(address account => uint256 balance) public balanceOf;
39: mapping(address owner => mapping(address spender => uint256 allowance)) public allowance;
</details>
<a id="n-35"></a> [N-35] NatSpec: Contract declarations should have @author
tags
Description:
The @author
tag provides the name of the author of the contract/interface. If there is a comment with the @author
tag, verify that the comment block starts with the NatSpec comment indicator, either ///
or /**
.
Instances:
There are 3 instances of this issue.
File: contracts/CollateralTracker.sol
36: contract CollateralTracker is ERC20Minimal, Multicall {
File: contracts/tokens/interfaces/IDonorNFT.sol
6: interface IDonorNFT {
File: contracts/types/LiquidityChunk.sol
52: library LiquidityChunkLibrary {
<a id="n-36"></a> [N-36] NatSpec: Contract declarations should have @dev
tags
Description:
@dev
tags are used to explain to a developer any extra details. If there is a comment with the @dev
tag, verify that the comment block starts with the NatSpec comment indicator, either ///
or /**
.
Instances:
There are 12 instances of this issue.
<details><summary>View 12 Instances</summary>
File: contracts/CollateralTracker.sol
36: contract CollateralTracker is ERC20Minimal, Multicall {
File: contracts/PanopticFactory.sol
26: contract PanopticFactory is Multicall {
File: contracts/libraries/CallbackLib.sol
12: library CallbackLib {
File: contracts/libraries/Constants.sol
7: library Constants {
File: contracts/libraries/Errors.sol
7: library Errors {
File: contracts/libraries/InteractionHelper.sol
17: library InteractionHelper {
File: contracts/libraries/Math.sol
13: library Math {
File: contracts/libraries/PanopticMath.sol
18: library PanopticMath {
File: contracts/tokens/interfaces/IDonorNFT.sol
6: interface IDonorNFT {
File: contracts/types/LeftRight.sol
17: library LeftRightLibrary {
File: contracts/types/LiquidityChunk.sol
52: library LiquidityChunkLibrary {
File: contracts/types/TokenId.sol
60: library TokenIdLibrary {
</details>
<a id="n-37"></a> [N-37] NatSpec: Contract declarations should have @title
tag
Description:
The @title
tag provides a title that describes the contract/interface. If there is a comment with the @title
tag, verify that the comment block starts with the NatSpec comment indicator, either ///
or /**
.
Instances:
There are 5 instances of this issue.
File: contracts/CollateralTracker.sol
36: contract CollateralTracker is ERC20Minimal, Multicall {
File: contracts/libraries/InteractionHelper.sol
17: library InteractionHelper {
File: contracts/libraries/SafeTransferLib.sol
11: library SafeTransferLib {
File: contracts/tokens/interfaces/IDonorNFT.sol
6: interface IDonorNFT {
File: contracts/types/LiquidityChunk.sol
52: library LiquidityChunkLibrary {
<a id="n-38"></a> [N-38] NatSpec: Contract declarations should have descriptions
Description:
The Solidity documentation recommends "that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI)." NatSpec documentation should be used for improved readability, a better user experience, enhanced auditability, enablement of automated testing and verification, and to promote standardization and interoperability.
Recommendation:
Add NatSpec documentation to all public contracts. It must appear above the contract definition braces in order to be identified by the compiler as NatSpec
Instances:
There is 1 instance of this issue.
File: contracts/tokens/interfaces/IDonorNFT.sol
6: interface IDonorNFT {
<a id="n-39"></a> [N-39] NatSpec: Function declarations should have @notice
tag
Description:
@notice
tags are used to explain to end users what the function does. If there is a comment with the @notice
tag, verify that the comment block starts with the NatSpec comment indicator, either ///
or /**
.
Instances:
There are 3 instances of this issue.
File: contracts/CollateralTracker.sol
178: constructor(
179: uint256 _commissionFee,
180: uint256 _sellerCollateralRatio,
181: uint256 _buyerCollateralRatio,
182: int256 _forceExerciseCost,
183: uint256 _targetPoolUtilization,
184: uint256 _saturatedPoolUtilization,
185: uint256 _ITMSpreadMultiplier
186: ) {
323: function transfer(
324: address recipient,
325: uint256 amount
326: ) public override(ERC20Minimal) returns (bool) {
341: function transferFrom(
342: address from,
343: address to,
344: uint256 amount
345: ) public override(ERC20Minimal) returns (bool) {
<a id="n-40"></a> [N-40] NatSpec: Function declarations should have descriptions
Description:
The Solidity documentation recommends "that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI)." NatSpec documentation should be used for improved readability, a better user experience, enhanced auditability, enablement of automated testing and verification, and to promote standardization and interoperability.
Recommendation:
Add NatSpec documentation to all public functions.
Instances:
There is 1 instance of this issue.
File: contracts/CollateralTracker.sol
178: constructor(
179: uint256 _commissionFee,
180: uint256 _sellerCollateralRatio,
181: uint256 _buyerCollateralRatio,
182: int256 _forceExerciseCost,
183: uint256 _targetPoolUtilization,
184: uint256 _saturatedPoolUtilization,
185: uint256 _ITMSpreadMultiplier
186: ) {
<a id="n-41"></a> [N-41] NatSpec: Invalid NatSpec comment style
Description:
NatSpec must begin with ///
, or use /** ... */
syntax.
Instances:
There are 6 instances of this issue.
<details><summary>View 6 Instances</summary>
File: contracts/SemiFungiblePositionManager.sol
358: // @dev pools can be initialized from a Panoptic pool or by calling initializeAMMPool directly, reverting
360: // @dev some pools may not be deployable if the poolId has a collision (since we take only 8 bytes)
603: // @dev see `contracts/types/LiquidityChunk.sol`
836: // @dev note this triggers our swap callback function
903: // @dev see `contracts/types/LiquidityChunk.sol`
File: contracts/libraries/PanopticMath.sol
98: // @dev 0 ^ x = x
</details>
<a id="n-42"></a> [N-42] NatSpec: State variable declarations should have descriptions
Description:
NatSpec documentation should be used for improved readability, a better user experience, enhanced auditability, enablement of automated testing and verification, and to promote standardization and interoperability.
Recommendation:
Use @notice
for public state variables, and @dev
for non-public ones.
Instances:
There are 9 instances of this issue.
<details><summary>View 9 Instances</summary>
File: contracts/PanopticPool.sol
120: bool internal constant DONOT_COMMIT_LONG_SETTLED = false;
133: bool internal constant SLOW_ORACLE_UNISWAP_MODE = false;
136: uint256 internal constant MEDIAN_PERIOD = 60;
156: int256 internal constant MAX_TWAP_DELTA_LIQUIDATION = 513;
171: uint256 internal constant BP_DECREASE_BUFFER = 13_333;
174: uint256 internal constant NO_BUFFER = 10_000;
File: contracts/SemiFungiblePositionManager.sol
126: bool internal constant BURN = true;
133: uint128 private constant VEGOID = 2;
289: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumGross;
</details>
<a id="n-43"></a> [N-43] NatSpec: Use @inheritdoc
for overridden functions
Description:
To ensure all NatSpec documentation is provided, @inheritdoc
should be used rather than non-standard annotations.
Recommendation:
When overriding a base function from an overridden contract, use the @inheritdoc
notation which will copy all missing tags from the base function.
Instances:
There are 4 instances of this issue.
File: contracts/CollateralTracker.sol
319: /// @dev See {IERC20-transfer}.
320: /// Requirements:
321: /// - the caller must have a balance of at least 'amount'.
322: /// - the msg.sender must not have any position on the panoptic pool
323: function transfer(
324: address recipient,
325: uint256 amount
326: ) public override(ERC20Minimal) returns (bool) {
336: /// @dev See {IERC20-transferFrom}.
337: /// Requirements:
338: /// - the 'from' must have a balance of at least 'amount'.
339: /// - the caller must have allowance for 'from' of at least 'amount' tokens.
340: /// - 'from' must not have any open positions on the panoptic pool.
341: function transferFrom(
342: address from,
343: address to,
344: uint256 amount
345: ) public override(ERC20Minimal) returns (bool) {
File: contracts/SemiFungiblePositionManager.sol
533: /// @notice Transfer a single token from one user to another
534: /// @dev supports token approvals
535: /// @param from the user to transfer tokens from
536: /// @param to the user to transfer tokens to
537: /// @param id the ERC1155 token id to transfer
538: /// @param amount the amount of tokens to transfer
539: /// @param data optional data to include in the receive hook
540: function safeTransferFrom(
541: address from,
542: address to,
543: uint256 id,
544: uint256 amount,
545: bytes calldata data
546: ) public override {
558: /// @notice Transfer multiple tokens from one user to another
559: /// @dev supports token approvals
560: /// @dev ids and amounts must be of equal length
561: /// @param from the user to transfer tokens from
562: /// @param to the user to transfer tokens to
563: /// @param ids the ERC1155 token ids to transfer
564: /// @param amounts the amounts of tokens to transfer
565: /// @param data optional data to include in the receive hook
566: function safeBatchTransferFrom(
567: address from,
568: address to,
569: uint256[] calldata ids,
570: uint256[] calldata amounts,
571: bytes calldata data
572: ) public override {
<a id="n-44"></a> [N-44] NatSpec: Use appropriate tags for state variable declarations
Description:
The NatSpec documentation indicates that @notice
should be used for public state variables, and @dev
should be used for non-public ones.
Recommendation:
Use @notice
for public state variables, and @dev
for non-public ones.
Instances:
There are 43 instances of this issue.
<details><summary>View 43 Instances</summary>
File: contracts/CollateralTracker.sol
70: string internal constant TICKER_PREFIX = "po";
73: string internal constant NAME_PREFIX = "POPT-V1";
96: address internal s_univ3token0;
99: address internal s_univ3token1;
102: bool internal s_underlyingIsToken0;
136: uint256 immutable COMMISSION_FEE;
141: uint256 immutable SELLER_COLLATERAL_RATIO;
145: uint256 immutable BUYER_COLLATERAL_RATIO;
149: int256 immutable FORCE_EXERCISE_COST;
154: uint256 immutable TARGET_POOL_UTIL;
158: uint256 immutable SATURATED_POOL_UTIL;
162: uint256 immutable ITM_SPREAD_MULTIPLIER;
File: contracts/PanopticFactory.sol
63: IUniswapV3Factory internal immutable UNIV3_FACTORY;
66: SemiFungiblePositionManager internal immutable SFPM;
69: IDonorNFT internal immutable DONOR_NFT;
72: address internal immutable POOL_REFERENCE;
75: address internal immutable COLLATERAL_REFERENCE;
78: address internal immutable WETH;
89: uint16 internal constant CARDINALITY_INCREASE = 100;
96: address internal s_owner;
99: bool internal s_initialized;
102: mapping(IUniswapV3Pool univ3pool => PanopticPool panopticPool) internal s_getPanopticPool;
File: contracts/PanopticPool.sol
160: int256 internal constant MAX_SLOW_FAST_DELTA = 1800;
179: SemiFungiblePositionManager internal immutable SFPM;
File: contracts/SemiFungiblePositionManager.sol
287: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumOwed;
File: contracts/libraries/Constants.sol
9: uint256 internal constant FP96 = 0x1000000000000000000000000;
12: int24 internal constant MIN_V3POOL_TICK = -887272;
15: int24 internal constant MAX_V3POOL_TICK = 887272;
18: uint160 internal constant MIN_V3POOL_SQRT_RATIO = 4295128739;
21: uint160 internal constant MAX_V3POOL_SQRT_RATIO =
22: 1461446703485210103287273052203988822378723970342;
File: contracts/libraries/Math.sol
15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
File: contracts/libraries/PanopticMath.sol
23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
26: uint64 internal constant TICKSPACING_MASK = 0xFFFF000000000000;
File: contracts/types/LeftRight.sol
22: uint256 internal constant LEFT_HALF_BIT_MASK =
23: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000;
26: int256 internal constant LEFT_HALF_BIT_MASK_INT =
27: int256(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000));
30: int256 internal constant RIGHT_HALF_BIT_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
File: contracts/types/LiquidityChunk.sol
54: uint256 internal constant CLEAR_TL_MASK =
55: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
58: uint256 internal constant CLEAR_TU_MASK =
59: 0xFFFFFF000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
File: contracts/types/TokenId.sol
62: uint256 internal constant LONG_MASK =
63: 0x100_000000000100_000000000100_000000000100_0000000000000000;
66: uint256 internal constant CLEAR_POOLID_MASK =
67: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF_0000000000000000;
70: uint256 internal constant OPTION_RATIO_MASK =
71: 0x0000000000FE_0000000000FE_0000000000FE_0000000000FE_0000000000000000;
74: uint256 internal constant CHUNK_MASK =
75: 0xFFFFFFFFF200_FFFFFFFFF200_FFFFFFFFF200_FFFFFFFFF200_0000000000000000;
78: int256 internal constant BITMASK_INT24 = 0xFFFFFF;
</details>
<a id="n-45"></a> [N-45] Non-library/interface files should use fixed compiler versions, not floating ones
Description:
Avoid floating pragma
s for non-library/interface contracts. While floating pragma
s can make sense for libraries to allow them to be included with multiple different versions of applications, it may be a security risk for application implementations. A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version. It is recommended to pin to a concrete compiler version. See Locking Pragmas for more information.
Instances:
There are 7 instances of this issue.
<details><summary>View 7 Instances</summary>
File: contracts/CollateralTracker.sol
2: pragma solidity ^0.8.18;
File: contracts/PanopticFactory.sol
2: pragma solidity ^0.8.18;
File: contracts/PanopticPool.sol
2: pragma solidity ^0.8.18;
File: contracts/SemiFungiblePositionManager.sol
2: pragma solidity ^0.8.18;
File: contracts/multicall/Multicall.sol
2: pragma solidity ^0.8.18;
File: contracts/tokens/ERC1155Minimal.sol
2: pragma solidity ^0.8.0;
File: contracts/tokens/ERC20Minimal.sol
2: pragma solidity ^0.8.0;
</details>
<a id="n-46"></a> [N-46] Not using the named return variables anywhere in the function is confusing
Description:
When specifying a named return variable, the variable should be used within the function.
Recommendation:
Since the return variable is never assigned, nor is it returned by name, consider changing the return value to be unnamed. If the optimizer is not turned on, leaving the code as it is will also waste gas for the stack variable.
Instances:
There are 15 instances of this issue.
<details><summary>View 15 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit unused return variable: assetTokenAddress
361: function asset() external view returns (address assetTokenAddress) {
/// @audit unused return variable: totalManagedAssets
370: function totalAssets() public view returns (uint256 totalManagedAssets) {
/// @audit unused return variable: shares
379: function convertToShares(uint256 assets) public view returns (uint256 shares) {
/// @audit unused return variable: assets
386: function convertToAssets(uint256 shares) public view returns (uint256 assets) {
/// @audit unused return variable: maxAssets
392: function maxDeposit(address) external pure returns (uint256 maxAssets) {
/// @audit unused return variable: maxShares
444: function maxMint(address) external view returns (uint256 maxShares) {
/// @audit unused return variable: maxAssets
507: function maxWithdraw(address owner) public view returns (uint256 maxAssets) {
/// @audit unused return variable: shares
518: function previewWithdraw(uint256 assets) public view returns (uint256 shares) {
/// @audit unused return variable: maxShares
572: function maxRedeem(address owner) public view returns (uint256 maxShares) {
/// @audit unused return variable: assets
581: function previewRedeem(uint256 shares) public view returns (uint256 assets) {
/// @audit unused return variable: poolUtilization
741: function _poolUtilization() internal view returns (int256 poolUtilization) {
/// @audit unused return variable: sellCollateralRatio
751: function _sellCollateralRatio(
752: int256 utilization
753: ) internal view returns (uint256 sellCollateralRatio) {
File: contracts/PanopticPool.sol
/// @audit unused return variables: premium0, premium1
381: function calculateAccumulatedFeesBatch(
382: address user,
383: bool includePendingPremium,
384: TokenId[] calldata positionIdList
385: ) external view returns (int128 premium0, int128 premium1, uint256[2][] memory) {
/// @audit unused return variable: collateralToken
1431: function collateralToken0() external view returns (CollateralTracker collateralToken) {
File: contracts/SemiFungiblePositionManager.sol
/// @audit unused return variable: UniswapV3Pool
1555: function getUniswapV3PoolFromId(
1556: uint64 poolId
1557: ) external view returns (IUniswapV3Pool UniswapV3Pool) {
</details>
<a id="n-47"></a> [N-47] Numeric values having to do with time should use time units for readability
Description:
There are time unit suffixes (seconds
, minutes
, hours
, days
and weeks
) that can be used after literal numbers to specify units of time where seconds are the base unit. Using these suffixes increases the readability and maintainability of the code. For example, uint256 internal constant TRADING_DELAY = 6 hours;
is more readable and easier to maintain than uint256 internal constant TRADING_DELAY = 6 * 60 * 60;
Recommendation:
Whenever specifying units of time, use the built in time suffixes.
Instances:
There are 8 instances of this issue.
<details><summary>View 8 Instances</summary>
File: contracts/PanopticPool.sol
/// @audit 60
136: uint256 internal constant MEDIAN_PERIOD = 60;
/// @audit 7
148: uint256 internal constant SLOW_ORACLE_CARDINALITY = 7;
/// @audit 24
313: (uint256(uint24(currentTick)) << 24) + // add to slot 4
File: contracts/libraries/PanopticMath.sol
/// @audit 24
178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) +
/// @audit 24
179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) /
/// @audit 7
211: if (rank == 7) {
/// @audit 24
217: entry = int24(uint24(medianData >> (rank * 24)));
/// @audit 24
229: uint256(uint192(medianData << 24)) +
</details>
<a id="n-48"></a> [N-48] Polymorphic functions make security audits more time-consuming and error-prone
Description:
In Solidity, while function overriding allows for functions with the same name to coexist, it is advisable to avoid this practice to enhance code readability and maintainability. Having multiple functions with the same name, even with different parameters or in inherited contracts, can cause confusion and increase the likelihood of errors during development, testing, and debugging.
Recommendation:
Consider using distinct and descriptive function names to clarify the purpose and behavior of each function, and to help prevent unintended function calls or incorrect overriding.
Instances:
There are 31 instances of this issue.
<details><summary>View 31 Instances</summary>
File: contracts/CollateralTracker.sol
864: function delegate(
865: address delegator,
866: address delegatee,
867: uint256 assets
868: ) external onlyPanopticPool {
894: function delegate(address delegatee, uint256 assets) external onlyPanopticPool {
903: function refund(address delegatee, uint256 assets) external onlyPanopticPool {
975: function refund(address refunder, address refundee, int256 assets) external onlyPanopticPool {
File: contracts/PanopticPool.sol
569: function burnOptions(
570: TokenId tokenId,
571: TokenId[] calldata newPositionIdList,
572: int24 tickLimitLow,
573: int24 tickLimitHigh
574: ) external {
586: function burnOptions(
587: TokenId[] calldata positionIdList,
588: TokenId[] calldata newPositionIdList,
589: int24 tickLimitLow,
590: int24 tickLimitHigh
591: ) external {
File: contracts/libraries/Math.sol
41: function min(uint256 a, uint256 b) internal pure returns (uint256) {
49: function min(int256 a, int256 b) internal pure returns (int256) {
57: function max(uint256 a, uint256 b) internal pure returns (uint256) {
65: function max(int256 a, int256 b) internal pure returns (int256) {
311: function toInt128(uint128 toCast) internal pure returns (int128 downcastedInt) {
318: function toInt128(int256 toCast) internal pure returns (int128 downcastedInt) {
File: contracts/libraries/PanopticMath.sol
419: function convertCollateralData(
420: LeftRightUnsigned tokenData0,
421: LeftRightUnsigned tokenData1,
422: uint256 tokenType,
423: uint160 sqrtPriceX96
424: ) internal pure returns (uint256, uint256) {
445: function convertCollateralData(
446: LeftRightUnsigned tokenData0,
447: LeftRightUnsigned tokenData1,
448: uint256 tokenType,
449: int24 tick
450: ) internal pure returns (uint256, uint256) {
490: function convert0to1(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) {
507: function convert1to0(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) {
524: function convert0to1(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) {
547: function convert1to0(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) {
File: contracts/types/LeftRight.sol
39: function rightSlot(LeftRightUnsigned self) internal pure returns (uint128) {
46: function rightSlot(LeftRightSigned self) internal pure returns (int128) {
59: function toRightSlot(
60: LeftRightUnsigned self,
61: uint128 right
62: ) internal pure returns (LeftRightUnsigned) {
78: function toRightSlot(
79: LeftRightSigned self,
80: int128 right
81: ) internal pure returns (LeftRightSigned) {
101: function leftSlot(LeftRightUnsigned self) internal pure returns (uint128) {
108: function leftSlot(LeftRightSigned self) internal pure returns (int128) {
121: function toLeftSlot(
122: LeftRightUnsigned self,
123: uint128 left
124: ) internal pure returns (LeftRightUnsigned) {
134: function toLeftSlot(LeftRightSigned self, int128 left) internal pure returns (LeftRightSigned) {
148: function add(
149: LeftRightUnsigned x,
150: LeftRightUnsigned y
151: ) internal pure returns (LeftRightUnsigned z) {
171: function sub(
172: LeftRightUnsigned x,
173: LeftRightUnsigned y
174: ) internal pure returns (LeftRightUnsigned z) {
194: function add(LeftRightUnsigned x, LeftRightSigned y) internal pure returns (LeftRightSigned z) {
214: function add(LeftRightSigned x, LeftRightSigned y) internal pure returns (LeftRightSigned z) {
232: function sub(LeftRightSigned x, LeftRightSigned y) internal pure returns (LeftRightSigned z) {
</details>
<a id="n-49"></a> [N-49] Setters should prevent re-setting of the same value
Description:
When setting the value of a state variable, validation should be added to prevent resetting it to its current value. This is particularly important if the setter also emits the new (same) value, which may be confusing to off-chain tools that parse events.
Instances:
There are 20 instances of this issue.
<details><summary>View 20 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit state variables: s_underlyingIsToken0, s_univ3token0, s_univ3token1, s_panopticPool
221: function startToken(
222: bool underlyingIsToken0,
223: address token0,
224: address token1,
225: uint24 fee,
226: PanopticPool panopticPool
227: ) external {
/// @audit state variable: s_poolAssets
417: function deposit(uint256 assets, address receiver) external returns (uint256 shares) {
/// @audit state variable: s_poolAssets
531: function withdraw(
532: uint256 assets,
533: address receiver,
534: address owner
535: ) external returns (uint256 shares) {
/// @audit state variable: balanceOf
894: function delegate(address delegatee, uint256 assets) external onlyPanopticPool {
/// @audit state variable: balanceOf
903: function refund(address delegatee, uint256 assets) external onlyPanopticPool {
File: contracts/PanopticFactory.sol
/// @audit state variable: s_owner
147: function transferOwnership(address newOwner) external {
File: contracts/PanopticPool.sol
/// @audit state variables: s_univ3pool, s_collateralToken0, s_collateralToken1
291: function startPool(
292: IUniswapV3Pool _univ3pool,
293: address token0,
294: address token1,
295: CollateralTracker collateralTracker0,
296: CollateralTracker collateralTracker1
297: ) external {
/// @audit state variable: s_positionBalance
614: function _mintOptions(
615: TokenId[] calldata positionIdList,
616: uint128 positionSize,
617: uint64 effectiveLiquidityLimitX32,
618: int24 tickLimitLow,
619: int24 tickLimitHigh
620: ) internal {
/// @audit state variable: s_settledTokens
1666: function _updateSettlementPostMint(
1667: TokenId tokenId,
1668: LeftRightUnsigned[4] memory collectedByLeg,
1669: uint128 positionSize
1670: ) internal {
File: contracts/SemiFungiblePositionManager.sol
/// @audit state variable: s_accountFeesBase
958: function _createLegInAMM(
959: IUniswapV3Pool univ3pool,
960: TokenId tokenId,
961: uint256 leg,
962: LiquidityChunk liquidityChunk,
963: bool isBurn
964: )
965: internal
966: returns (
967: LeftRightSigned moved,
968: LeftRightSigned itmAmounts,
969: LeftRightUnsigned collectedSingleLeg
970: )
971: {
File: contracts/tokens/ERC1155Minimal.sol
/// @audit state variable: isApprovedForAll
81: function setApprovalForAll(address operator, bool approved) public {
/// @audit state variable: balanceOf
94: function safeTransferFrom(
95: address from,
96: address to,
97: uint256 id,
98: uint256 amount,
99: bytes calldata data
100: ) public virtual {
/// @audit state variable: balanceOf
214: function _mint(address to, uint256 id, uint256 amount) internal {
/// @audit state variable: balanceOf
236: function _burn(address from, uint256 id, uint256 amount) internal {
File: contracts/tokens/ERC20Minimal.sol
/// @audit state variable: allowance
49: function approve(address spender, uint256 amount) public returns (bool) {
/// @audit state variable: balanceOf
61: function transfer(address to, uint256 amount) public virtual returns (bool) {
/// @audit state variable: balanceOf
81: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
/// @audit state variable: balanceOf
103: function _transferFrom(address from, address to, uint256 amount) internal {
/// @audit state variable: balanceOf
122: function _mint(address to, uint256 amount) internal {
/// @audit state variable: balanceOf
136: function _burn(address from, uint256 amount) internal {
</details>
<a id="n-50"></a> [N-50] String literals used more than once should be replaced with a constant
Description:
Using a constant for string literals used more than once will make the code more maintainable. If the string needs to change, it will only have to be changed in one place.
Instances:
There are 5 instances of this issue.
File: contracts/libraries/InteractionHelper.sol
/// @audit "???" also defined at contracts/libraries/InteractionHelper.sol:68
63: symbol0 = "???";
/// @audit "???" also defined at contracts/libraries/InteractionHelper.sol:63
68: symbol1 = "???";
/// @audit " " also defined at contracts/libraries/InteractionHelper.sol:80
74: " ",
/// @audit " " also defined at contracts/libraries/InteractionHelper.sol:74
80: " ",
/// @audit "???" also defined at contracts/libraries/InteractionHelper.sol:63
100: return string.concat(prefix, "???");
<a id="n-51"></a> [N-51] Style guide: Horizontal whitespace around binary operators
Description:
According to the Solidity style guide there should be one space around a binary operator. For example, use x + y
instead of x+y
.
Recommendation:
Add a space surround all binary operators. Incorporate a tool like forge fmt
or Prettier in your toolchain to do this automatically.
Instances:
There is 1 instance of this issue.
File: contracts/PanopticPool.sol
/// @audit operator: >
1415: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen();
<a id="n-52"></a> [N-52] Style guide: Modifier names should use lowerCamelCase
Description:
According to the Solidity style guide modifier names should use mixedCase (lowerCamelCase).
Instances:
There is 1 instance of this issue.
File: contracts/SemiFungiblePositionManager.sol
305: modifier ReentrancyLock(uint64 poolId) {
<a id="n-53"></a> [N-53] Style guide: State and local variables should be named using lowerCamelCase
Description:
According to the Solidity style guide local and state variable names should use mixedCase (lowerCamelCase). Note: While OpenZeppelin may not follow this advice, it still is the recommended way of naming variables.
Instances:
There are 52 instances of this issue.
<details><summary>View 52 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit s_underlyingToken
89: address internal s_underlyingToken;
/// @audit s_initialized
93: bool internal s_initialized;
/// @audit s_univ3token0
96: address internal s_univ3token0;
/// @audit s_univ3token1
99: address internal s_univ3token1;
/// @audit s_underlyingIsToken0
102: bool internal s_underlyingIsToken0;
/// @audit s_panopticPool
109: PanopticPool internal s_panopticPool;
/// @audit s_poolAssets
112: uint128 internal s_poolAssets;
/// @audit s_inAMM
115: uint128 internal s_inAMM;
/// @audit s_ITMSpreadFee
121: uint128 internal s_ITMSpreadFee;
/// @audit s_poolFee
124: uint24 internal s_poolFee;
/// @audit _ITMSpreadMultiplier
185: uint256 _ITMSpreadMultiplier
/// @audit min_sell_ratio
773: uint256 min_sell_ratio = SELLER_COLLATERAL_RATIO;
File: contracts/PanopticFactory.sol
/// @audit s_owner
96: address internal s_owner;
/// @audit s_initialized
99: bool internal s_initialized;
/// @audit s_getPanopticPool
102: mapping(IUniswapV3Pool univ3pool => PanopticPool panopticPool) internal s_getPanopticPool;
/// @audit _WETH9
116: address _WETH9,
/// @audit _SFPM
117: SemiFungiblePositionManager _SFPM,
File: contracts/PanopticPool.sol
/// @audit s_univ3pool
186: IUniswapV3Pool internal s_univ3pool;
/// @audit s_miniMedian
225: uint256 internal s_miniMedian;
/// @audit s_collateralToken0
231: CollateralTracker internal s_collateralToken0;
/// @audit s_collateralToken1
233: CollateralTracker internal s_collateralToken1;
/// @audit s_options
238: mapping(address account => mapping(TokenId tokenId => mapping(uint256 leg => LeftRightUnsigned premiaGrowth)))
239: internal s_options;
/// @audit s_grossPremiumLast
245: mapping(bytes32 chunkKey => LeftRightUnsigned lastGrossPremium) internal s_grossPremiumLast;
/// @audit s_settledTokens
251: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) internal s_settledTokens;
/// @audit s_positionBalance
258: mapping(address account => mapping(TokenId tokenId => LeftRightUnsigned balanceAndUtilizations))
259: internal s_positionBalance;
/// @audit s_positionsHash
272: mapping(address account => uint256 positionsHash) internal s_positionsHash;
/// @audit c_user
439: address c_user = user;
File: contracts/SemiFungiblePositionManager.sol
/// @audit s_AddrToPoolIdData
145: mapping(address univ3pool => uint256 poolIdData) internal s_AddrToPoolIdData;
/// @audit s_poolContext
150: mapping(uint64 poolId => PoolAddressAndLock contextData) internal s_poolContext;
/// @audit s_accountLiquidity
177: mapping(bytes32 positionKey => LeftRightUnsigned removedAndNetLiquidity)
178: internal s_accountLiquidity;
/// @audit s_accountPremiumOwed
287: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumOwed;
/// @audit s_accountPremiumGross
289: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumGross;
/// @audit s_accountFeesBase
295: mapping(bytes32 positionKey => LeftRightSigned baseFees0And1) internal s_accountFeesBase;
/// @audit positionKey_from
611: bytes32 positionKey_from = keccak256(
/// @audit positionKey_to
620: bytes32 positionKey_to = keccak256(
/// @audit premium0X64_base
1342: uint256 premium0X64_base;
/// @audit premium1X64_base
1343: uint256 premium1X64_base;
/// @audit premium0X64_owed
1363: uint128 premium0X64_owed;
/// @audit premium1X64_owed
1364: uint128 premium1X64_owed;
/// @audit premium0X64_gross
1384: uint128 premium0X64_gross;
/// @audit premium1X64_gross
1385: uint128 premium1X64_gross;
/// @audit UniswapV3Pool
1557: ) external view returns (IUniswapV3Pool UniswapV3Pool) {
File: contracts/libraries/PanopticMath.sol
/// @audit timestamp_old
186: (uint256 timestamp_old, int56 tickCumulative_old, , ) = univ3pool.observations(
/// @audit tickCumulative_old
186: (uint256 timestamp_old, int56 tickCumulative_old, , ) = univ3pool.observations(
/// @audit timestamp_last
192: (uint256 timestamp_last, int56 tickCumulative_last, , ) = univ3pool
/// @audit tickCumulative_last
192: (uint256 timestamp_last, int56 tickCumulative_last, , ) = univ3pool
File: contracts/types/LeftRight.sol
/// @audit z_xR
285: uint128 z_xR = (uint256(x.rightSlot()) + dx.rightSlot()).toUint128Capped();
/// @audit z_xL
286: uint128 z_xL = (uint256(x.leftSlot()) + dx.leftSlot()).toUint128Capped();
/// @audit z_yR
287: uint128 z_yR = (uint256(y.rightSlot()) + dy.rightSlot()).toUint128Capped();
/// @audit z_yL
288: uint128 z_yL = (uint256(y.leftSlot()) + dy.leftSlot()).toUint128Capped();
/// @audit r_Enabled
290: bool r_Enabled = !(z_xR == type(uint128).max || z_yR == type(uint128).max);
/// @audit l_Enabled
291: bool l_Enabled = !(z_xL == type(uint128).max || z_yL == type(uint128).max);
</details>
<a id="n-54"></a> [N-54] Style guide: Variable names that consist of all capital letters and underscores should be reserved for constants and immutable
variables
Description:
Per the Solidity Style Guide, constants should be named with all capital letters with underscores separating words. Local and state variables should use mixedCase. The Style Guide does not mention a naming convention for immutable
variables, but using the same convention as constants is common.
Recommendation:
Follow the Solidity Style Guide for naming conventions.
Instances:
There are 2 instances of this issue.
File: contracts/PanopticFactory.sol
116: address _WETH9,
117: SemiFungiblePositionManager _SFPM,
<a id="n-55"></a> [N-55] Typos
Description:
Typos in comments reflect poorly on the protocol, can indicate a lack of attention to detail by the developers, and can decrease confidence in the project.
Recommendation:
Correct the typos and use an IDE extension/plugin to assist with spelling.
Instances:
There are 68 instances of this issue.
<details><summary>View 68 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit safecasting
37: // Used for safecasting
/// @audit initalized
92: /// @dev As each instance is deployed via proxy clone, initial parameters must only be initalized once via startToken().
/// @audit exercisee
634: /// - The forceExercisor will have to *pay* the exercisee because their position will be closed "against their will"
/// @audit liquidatee
946: // T: transferred shares from liquidatee which are a component of N but do not contribute toward protocol loss
/// @audit refundee
971: /// @dev can handle negative refund amounts that go from refundee to refunder in the case of high exercise fees.
/// @audit refundee
974: /// @param assets The amount of assets to refund. Positive means a transfer from refunder to refundee, vice versa for negative.
/// @audit premum
1180: // if premium is positive (ie. user will receive funds due to selling options), add this premum to the user's balance
File: contracts/PanopticFactory.sol
/// @audit deployers
68: /// @notice Contract called to issue reward NFT to pool deployers
/// @audit numeraire
77: /// @notice Address of the Wrapped Ether (or other numeraire token) contract
/// @audit numeraire
109: /// @param _WETH9 Address of the Wrapped Ether (or other numeraire token) contract
/// @audit permissionless
222: // restrict pool deployment to owner if contract has not been made permissionless
File: contracts/PanopticPool.sol
/// @audit permissionless
23: /// @title The Panoptic Pool: Create permissionless options on top of a concentrated liquidity AMM like Uniswap v3.
/// @audit exercisor
49: /// @param exerciseFee LeftRight encoding for the cost paid by the exercisor to force the exercise of the token.
/// @audit caluculation
107: // Flags used as arguments to premia caluculation functions
/// @audit blocktime
142: /// Note that the *minimum* total observation time is determined by the blocktime and may need to be adjusted by chain
/// @audit subtick
335: /// @dev Can also be used for more granular subtick precision on slippage checks
/// @audit slippagelimit
545: /// @param tickLimitLow The lower tick slippagelimit.
/// @audit slippagelimit
546: /// @param tickLimitHigh The upper tick slippagelimit.
/// @audit slippagelimit
612: /// @param tickLimitLow The lower tick slippagelimit.
/// @audit slippagelimit
613: /// @param tickLimitHigh The upper tick slippagelimit.
/// @audit liquidatee
1015: /// @param delegations LeftRight amounts of token0 and token1 (token0:token1 right:left) delegated to the liquidatee by the liquidator so the option can be smoothly exercised.
/// @audit liquidatee
1081: // burn all options from the liquidatee
/// @audit liquidatee
1084: // This is to prevent any short positions the liquidatee has being settled with tokens that will later be revoked
/// @audit liquidatee
1108: // premia cannot be paid if there is protocol loss associated with the liquidatee
/// @audit liquidatee
1109: // otherwise, an economic exploit could occur if the liquidator and liquidatee collude to
/// @audit liquidatee
1112: // thus, we haircut any premium paid by the liquidatee (converting tokens as necessary) until the protocol loss is covered or the premium is exhausted
/// @audit exercisee
1173: /// @notice Force the exercise of a single position. Exercisor will have to pay a fee to the force exercisee.
/// @audit exercisor
1264: // refund the protocol any virtual shares after settling the difference with the exercisor
/// @audit overstimate
1351: // overstimate by rounding up
File: contracts/SemiFungiblePositionManager.sol
/// @audit safecasting
108: // Used for safecasting
/// @audit streamia
130: // and vegoid modifies the sensitivity of the streamia to changes in that utilization,
/// @audit feesbase, feesbase
1097: // round up the stored feesbase to minimize feesbase when we next calculate it
/// @audit postion
1106: /// @notice caches/stores the accumulated premia values for the specified postion.
/// @audit feesbase
1137: /// @dev stored fees base is rounded up and the current fees base is rounded down to minimize the amount of fees collected (feesbase) in favor of the protocol
/// @audit feesbase
1264: // round down current fees base to minimize feesbase
/// @audit seperated
1337: // premia, and is only seperated to simplify the calculation
File: contracts/libraries/Errors.sol
/// @audit exercisee
25: /// @notice PanopticPool: force exercisee is insolvent - liquidatable accounts are not permitted to open or close positions outside of a liquidation
/// @audit irst
31: /// @notice PanopticFactory: irst 20 bytes of provided salt does not match caller address
/// @audit avaiable
62: /// @notice PanopticPool: there is not enough avaiable liquidity to buy an option
File: contracts/libraries/InteractionHelper.sol
/// @audit metadada
95: // not guaranteed that token supports metadada extension
/// @audit metadada
108: // not guaranteed that token supports metadada extension
File: contracts/libraries/PanopticMath.sol
/// @audit safecasting
19: // Used for safecasting
/// @audit blocktime
116: /// @dev Each period has a minimum length of blocktime * period, but may be longer if the Uniswap pool is relatively inactive.
/// @audit stots
247: // construct the time stots
/// @audit consistentcy
663: // evaluate at TWAP price to keep consistentcy with solvency calculations
/// @audit liquidatee
691: // negative premium (owed to the liquidatee) is credited to the collateral balance
/// @audit liquidatee
705: // liquidatee cannot pay back the liquidator fully in either token, so no protocol loss can be avoided
/// @audit liquidatee
707: // liquidatee has insufficient token0 but some token1 left over, so we use what they have left to mitigate token0 losses
/// @audit liquidatee
711: // and paid0 - balance0 is the amount of token0 that the liquidatee is missing, i.e the protocol loss
/// @audit liquidatee
725: // liquidatee has insufficient token1 but some token0 left over, so we use what they have left to mitigate token1 losses
/// @audit liquidatee
729: // and paid1 - balance1 is the amount of token1 that the liquidatee is missing, i.e the protocol loss
/// @audit liquidatee
760: /// @param premiasByLeg The premium paid (or received) by the liquidatee for each leg of each position
/// @audit liquidatee
779: // get the amount of premium paid by the liquidatee
/// @audit liquidatee
790: // Ignore any surplus collateral - the liquidatee is either solvent or it converts to <1 unit of the other token
/// @audit commited
886: // The long premium is not commited to storage during the liquidation, so we add the entire adjusted amount
/// @audit exercisee
911: /// @param refunder Address of the user the refund is coming from (the force exercisee)
/// @audit refundee
926: // if the refunder lacks sufficient token0 to pay back the refundee, have them pay back the equivalent value in token1
File: contracts/multicall/Multicall.sol
/// @audit paranthetical
22: // Other solutions will do work to differentiate the revert reasons and provide paranthetical information
File: contracts/types/LeftRight.sol
/// @audit safecasting
18: // Used for safecasting
/// @audit bitmask
21: /// @notice AND bitmask to isolate the right half of a uint256
/// @audit bitmask
25: /// @notice AND bitmask to isolate the left half of an int256
/// @audit bitmask
29: /// @notice AND bitmask to isolate the left half of an int256
/// @audit explictily
153: // adding leftRight packed uint128's is same as just adding the values explictily
/// @audit occured
159: // then an overflow has occured
/// @audit explictily
176: // subtracting leftRight packed uint128's is same as just subtracting the values explictily
/// @audit occured
182: // then an underflow has occured
File: contracts/types/TokenId.sol
/// @audit stranlges
565: // unlike short stranlges, long strangles also cannot be partnered, because there is no reduction in risk (both legs can earn premia simultaneously)
/// @audit exercisability
576: /// @param self The TokenId to validate for exercisability
</details>
<a id="n-56"></a> [N-56] Unnecessary cast
Description:
The variable is being cast to its own type.
Recommendation:
Remove unnecessary casting to increase code clarity and readability.
Instances:
There are 11 instances of this issue.
<details><summary>View 11 Instances</summary>
File: contracts/PanopticPool.sol
/// @audit uint256()
309: (uint256(block.timestamp) << 216) +
File: contracts/SemiFungiblePositionManager.sol
/// @audit address()
447: ? address(decoded.poolFeatures.token0)
File: contracts/types/TokenId.sol
/// @audit uint256()
110: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48)) % 2);
/// @audit uint256()
120: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 1)) % 128);
/// @audit uint256()
130: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 8)) % 2);
/// @audit uint256()
140: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 9)) % 2);
/// @audit uint256()
150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4);
/// @audit uint256()
212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48)));
/// @audit uint256()
229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1))
/// @audit uint256()
263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9))
/// @audit uint256()
281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10))
</details>
<a id="n-57"></a> [N-57] Unsafe conversion from unsigned to signed integer
Description:
Because Solidity uses two's complement for integer representation, converting an unsigned integer to a signed integer may result in a change of the sign and/or magnitude of the value being converted. Because of this, casting an unsigned integer to a signed one may result in a change of the sign and or magnitude of the value. For example, int8(type(uint8).max)
is not equal to type(int8).max
, but is equal to -1
. type(uint8).max
in binary is 11111111
, which if cast to a signed value, means the first binary 1
indicates a negative value, and the binary 1
s, invert to all zeroes, and when one is added, it becomes one, but negative, and therefore the decimal value of binary 11111111
is -1
.
Recommendation:
Avoid conversion from unsigned integers to signed integers, or add input validation to prevent unexpected results.
Instances:
There are 79 instances of this issue.
<details><summary>View 79 Instances</summary>
File: contracts/CollateralTracker.sol
/// @audit uint256 _sellerCollateralRatio -> int256
200: int256 ratioTick = (int256(_sellerCollateralRatio) - 2000);
/// @audit uint256 -> int256
668: int256(
669: Math.unsafeDivRoundingUp(
670: uint24(positionId.width(leg) * positionId.tickSpacing()),
671: 2
672: )
673: )
/// @audit uint128 -> int128
715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0))
/// @audit uint128 -> int128
715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0))
/// @audit uint128 -> int128
718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1))
/// @audit uint128 -> int128
718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1))
/// @audit uint256 -> int256
743: return int256((s_inAMM * DECIMALS) / totalAssets());
/// @audit uint256 -> int256
959: uint256(Math.max(1, int256(totalAssets()) - int256(assets)))
/// @audit uint256 assets -> int256
959: uint256(Math.max(1, int256(totalAssets()) - int256(assets)))
/// @audit uint256 -> int256
1003: int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;
/// @audit uint256 -> int256
1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount)));
/// @audit uint256 -> int256
1052: int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;
/// @audit uint256 -> int256
1085: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount)));
/// @audit uint256 swapCommission -> int256
1115: exchangedAmount = intrinsicValue + int256(swapCommission);
/// @audit uint256 -> int256
1119: exchangedAmount += int256(
1120: Math.unsafeDivRoundingUp(
1121: uint256(uint128(shortAmount + longAmount)) * COMMISSION_FEE,
1122: DECIMALS
1123: )
1124: );
/// @audit uint64 -> int64
1329: ? int64(uint64(poolUtilization))
/// @audit uint64 -> int64
1330: : int64(uint64(poolUtilization >> 64));
/// @audit uint64 -> int64
1585: ? int64(uint64(poolUtilization))
/// @audit uint64 -> int64
1586: : int64(uint64(poolUtilization >> 64))
/// @audit uint64 -> int64
1637: uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) +
/// @audit uint64 -> int64
1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
File: contracts/PanopticPool.sol
/// @audit uint256 -> int256
477: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium)))
/// @audit uint256 -> int256
1144: uint256(int256(uint256(_delegations.rightSlot())) + liquidationBonus0)
/// @audit uint256 -> int256
1149: uint256(int256(uint256(_delegations.leftSlot())) + liquidationBonus1)
/// @audit uint256 -> int256
1549: int256(
1550: ((premiumAccumulatorsByLeg[leg][0] -
1551: premiumAccumulatorLast.rightSlot()) *
1552: (liquidityChunk.liquidity())) / 2 ** 64
1553: )
/// @audit uint256 -> int256
1558: int256(
1559: ((premiumAccumulatorsByLeg[leg][1] -
1560: premiumAccumulatorLast.leftSlot()) *
1561: (liquidityChunk.liquidity())) / 2 ** 64
1562: )
/// @audit uint256 -> int256
1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64)))
/// @audit uint256 -> int256
1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));
/// @audit uint256 -> int256
1870: .wrap(int256(LeftRightUnsigned.unwrap(settledTokens)))
/// @audit uint256 -> int256
1901: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium)))
/// @audit uint256 -> int256
1935: (int256(
1936: grossPremiumLast.rightSlot() *
1937: totalLiquidityBefore
1938: ) -
/// @audit uint256 -> int256
1939: int256(
1940: _premiumAccumulatorsByLeg[_leg][0] *
1941: positionLiquidity
1942: )) + int256(legPremia.rightSlot() * 2 ** 64),
/// @audit uint256 -> int256
1952: (int256(
1953: grossPremiumLast.leftSlot() *
1954: totalLiquidityBefore
1955: ) -
/// @audit uint256 -> int256
1956: int256(
1957: _premiumAccumulatorsByLeg[_leg][1] *
1958: positionLiquidity
1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
File: contracts/SemiFungiblePositionManager.sol
/// @audit uint256 -> int256
1169: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside0LastX128, liquidity)))
/// @audit uint256 -> int256
1172: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside1LastX128, liquidity)))
/// @audit uint256 -> int256
1176: .toRightSlot(int128(int256(Math.mulDiv128(feeGrowthInside0LastX128, liquidity))))
/// @audit uint256 -> int256
1177: .toLeftSlot(int128(int256(Math.mulDiv128(feeGrowthInside1LastX128, liquidity))));
/// @audit uint256 amount0 -> int256
1214: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(int128(int256(amount0))).toLeftSlot(
/// @audit uint256 amount1 -> int256
1215: int128(int256(amount1))
/// @audit uint256 amount0 -> int256
1241: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(-int128(int256(amount0))).toLeftSlot(
/// @audit uint256 amount1 -> int256
1242: -int128(int256(amount1))
File: contracts/libraries/FeesCalc.sol
/// @audit uint256 amount0 -> int256
69: value0 += int256(amount0);
/// @audit uint256 amount1 -> int256
70: value1 += int256(amount1);
/// @audit uint256 amount0 -> int256
74: value0 -= int256(amount0);
/// @audit uint256 amount1 -> int256
75: value1 -= int256(amount1);
/// @audit uint256 -> int256
117: .toRightSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken0X128, liquidity))))
/// @audit uint256 -> int256
118: .toLeftSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken1X128, liquidity))));
File: contracts/libraries/Math.sol
/// @audit uint128 toCast -> int128
312: if ((downcastedInt = int128(toCast)) < 0) revert Errors.CastingError();
/// @audit uint256 toCast -> int256
327: return int256(toCast);
/// @audit uint256 -> int256
778: quickSort(data, int256(0), int256(data.length - 1));
File: contracts/libraries/PanopticMath.sol
/// @audit uint256 observationIndex -> int256
140: (int256(observationIndex) - int256(i * period)) +
/// @audit uint256 -> int256
140: (int256(observationIndex) - int256(i * period)) +
/// @audit uint256 observationCardinality -> int256
141: int256(observationCardinality)
/// @audit uint256 -> int256
151: int256(timestamps[i] - timestamps[i + 1]);
/// @audit uint24 -> int24
178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) +
/// @audit uint24 -> int24
179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) /
/// @audit uint256 observationIndex -> int256
188: int256(observationIndex) - int256(1) + int256(observationCardinality)
/// @audit uint256 observationCardinality -> int256
188: int256(observationIndex) - int256(1) + int256(observationCardinality)
/// @audit uint256 -> int256
196: int256(timestamp_last - timestamp_old)
/// @audit uint24 -> int24
217: entry = int24(uint24(medianData >> (rank * 24)));
/// @audit uint56 -> int56
258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20))
/// @audit uint256 -> int256
377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2)))
/// @audit uint256 -> int256
681: bonus0 = int256(Math.mulDiv128(bonusCross, requiredRatioX128));
/// @audit uint256 -> int256
683: bonus1 = int256(
684: PanopticMath.convert0to1(
685: Math.mulDiv128(bonusCross, 2 ** 128 - requiredRatioX128),
686: sqrtPriceX96Final
687: )
688: );
/// @audit uint256 -> int256
693: int256 balance0 = int256(uint256(tokenData0.rightSlot())) -
/// @audit uint256 -> int256
695: int256 balance1 = int256(uint256(tokenData1.rightSlot())) -
/// @audit uint256 -> int256
929: int256(collateral0.convertToAssets(collateral0.balanceOf(refunder)));
/// @audit uint256 -> int256
938: int256(
939: PanopticMath.convert0to1(uint256(balanceShortage), sqrtPriceX96)
940: ) + refundValues.leftSlot()
/// @audit uint256 -> int256
947: int256(collateral1.convertToAssets(collateral1.balanceOf(refunder)));
/// @audit uint256 -> int256
956: int256(
957: PanopticMath.convert1to0(uint256(balanceShortage), sqrtPriceX96)
958: ) + refundValues.rightSlot()
File: contracts/types/LeftRight.sol
/// @audit uint256 -> int256
27: int256(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000));
/// @audit uint256 -> int256
196: int256 left = int256(uint256(x.leftSlot())) + y.leftSlot();
/// @audit uint256 -> int256
201: int256 right = int256(uint256(x.rightSlot())) + y.rightSlot();
File: contracts/types/LiquidityChunk.sol
/// @audit uint256 -> int256
173: return int24(int256(LiquidityChunk.unwrap(self) >> 232));
/// @audit uint256 -> int256
182: return int24(int256(LiquidityChunk.unwrap(self) >> 208));
File: contracts/types/TokenId.sol
/// @audit uint24 -> int24
98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16));
/// @audit uint256 -> int256
160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12)));
/// @audit uint256 -> int256
171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096));
</details>
<a id="n-58"></a> [N-58] Unused contract variables
Description:
Unused variables impair readability of the code.
Recommendation:
Remove unused variables.
Instances:
There are 3 instances of this issue.
File: contracts/libraries/Constants.sol
9: uint256 internal constant FP96 = 0x1000000000000000000000000;
File: contracts/libraries/Math.sol
15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
File: contracts/libraries/PanopticMath.sol
23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
<a id="n-59"></a> [N-59] Unused import
Description:
The identifier is imported but never used within the file.
Instances:
There is 1 instance of this issue.
File: contracts/types/LiquidityChunk.sol
/// @audit TokenId
5: import {TokenId} from "@types/TokenId.sol";
<a id="n-60"></a> [N-60] Use bit shifts rather than long bit masks of a single bit, for readability
Description:
To make the code more readable and maintainable, use bit shifts rather than long bit masks of a single bit.
Instances:
There are 8 instances of this issue.
<details><summary>View 8 Instances</summary>
File: contracts/libraries/Constants.sol
9: uint256 internal constant FP96 = 0x1000000000000000000000000;
File: contracts/libraries/Math.sol
93: if (x >= 0x100000000000000000000000000000000) {
97: if (x >= 0x10000000000000000) {
135: : 0x100000000000000000000000000000000;
494: remainder := mulmod(a, b, 0x10000000000000000)
557: remainder := mulmod(a, b, 0x1000000000000000000000000)
634: remainder := mulmod(a, b, 0x100000000000000000000000000000000)
711: remainder := mulmod(a, b, 0x1000000000000000000000000000000000000000000000000)
</details>
<a id="n-61"></a> [N-61] Use more recent OpenZeppelin version for bug fixes and performance improvements
Description:
OpenZeppelin version 5.0.0+ provides bug fixes and performance improvements. See the release notes for more info.
Instances:
There are 5 instances of this issue.
File: contracts/PanopticFactory.sol
/// @audit version in use: 4.8.3
14: import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
File: contracts/PanopticPool.sol
/// @audit version in use: 4.8.3
9: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
File: contracts/libraries/InteractionHelper.sol
/// @audit version in use: 4.8.3
6: import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
/// @audit version in use: 4.8.3
10: import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
File: contracts/tokens/ERC1155Minimal.sol
/// @audit version in use: 4.8.3
5: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
<a id="n-62"></a> [N-62] Variables need not be initialized to false
Description:
The default values of variables are the typical โzero-stateโ of whatever the type is. The default value for a bool
is false
, so initializing it to this value is unnecessary.
Instances:
There is 1 instance of this issue.
File: contracts/SemiFungiblePositionManager.sol
125: bool internal constant MINT = false;
<a id="n-63"></a> [N-63] Visibility should be set explicitly rather than defaulting to internal
Description:
The default visibility for state variables is internal
. To clarify the visibility of state variables, it is a best practice is to always explicitly specify a visibility (internal
, private
, or public
).
Recommendation:
Consider always specifying an explicit visibility for state variables.
Instances:
There are 8 instances of this issue.
<details><summary>View 8 Instances</summary>
File: contracts/CollateralTracker.sol
131: uint256 immutable TICK_DEVIATION;
136: uint256 immutable COMMISSION_FEE;
141: uint256 immutable SELLER_COLLATERAL_RATIO;
145: uint256 immutable BUYER_COLLATERAL_RATIO;
149: int256 immutable FORCE_EXERCISE_COST;
154: uint256 immutable TARGET_POOL_UTIL;
158: uint256 immutable SATURATED_POOL_UTIL;
162: uint256 immutable ITM_SPREAD_MULTIPLIER;
</details>