Platform: Code4rena
Start Date: 01/04/2024
Pot Size: $120,000 USDC
Total HM: 11
Participants: 55
Period: 21 days
Judge: Picodes
Total Solo HM: 6
Id: 354
League: ETH
Rank: 50/55
Findings: 1
Award: $32.96
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: DadeKuma
Also found by: 0xStalin, 0xhacksmithh, 99Crits, Aymen0909, Bauchibred, CodeWasp, Dup1337, IllIllI, John_Femi, K42, KupiaSec, Naresh, Rhaydden, Rolezn, Sathish9098, Topmark, ZanyBonzy, albahaca, bareli, blockchainbuttonmasher, cheatc0d3, codeslide, crc32, d3e4, favelanky, grearlake, hihen, jasonxiale, jesjupyter, lanrebayode77, lirezArAzAvi, lsaudit, mining_mario, oualidpro, pfapostol, radin100, rbserver, sammy, satoshispeedrunner, slvDev, twcctop, zabihullahazadzoi
32.9585 USDC - $32.96
Total <b>19</b> instances over <b>4</b> issues:
ID | Issue | Instances |
---|---|---|
M-2 | Return values of approve() not checked | 4 |
M-3 | Return values of transfer() /transferFrom() not checked | 2 |
M-4 | Usage of slot0 is extremely easy to manipulate | 12 |
Total <b>569</b> instances over <b>36</b> issues:
ID | Issue | Instances |
---|---|---|
L-1 | Missing zero address check in functions with address parameters | 80 |
L-2 | Use increase/decrease allowance instead of approve() /safeApprove() | 4 |
L-3 | Consider implementing two-step procedure for updating protocol addresses | 4 |
L-4 | Constructor / initialization function lacks parameter validation | 5 |
L-5 | Contracts use infinite approvals | 4 |
L-6 | Code does not follow the best practice of check-effects-interaction | 9 |
L-7 | External function calls within loops | 4 |
L-8 | Functions calling contracts/addresses with transfer hooks are missing reentrancy guards | 7 |
L-9 | Large approvals may not work with some ERC20 tokens | 4 |
L-10 | Loss of precision in divisions | 31 |
L-11 | Missing checks for state variable assignments | 23 |
L-12 | Missing zero address check in constructor | 3 |
L-13 | Consider some checks for address(0) when setting address state variables | 15 |
L-14 | Multiplication on the result of a division | 4 |
L-15 | Solidity version 0.8.20 or above may not work on other chains due to PUSH0 | 20 |
L-16 | Some tokens may revert when large transfers are made | 2 |
L-17 | Some tokens may revert when zero value transfers are made | 2 |
L-18 | Subtraction may underflow if result of multiplication is too large | 36 |
L-19 | Sending tokens in a for loop | 1 |
L-20 | Tokens may be minted to address(0) | 3 |
L-21 | Unsafe downcast | 122 |
L-22 | Unsafe addition/multiplication in unchecked block | 72 |
L-23 | Using zero as a parameter | 11 |
L-24 | Vulnerable version of OZ contarcts is used (4.8.3 ) | 1 |
L-25 | Missing zero address check in initializer | 2 |
L-26 | abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256() | 2 |
L-27 | Centralization Risk for trusted owners | 7 |
L-28 | decimals() is not a part of the ERC-20 standard | 1 |
L-29 | Initializers could be front-run | 1 |
L-30 | symbol() is not a part of the ERC-20 standard | 3 |
L-31 | Low level calls in solidity versions preceding 0.8.14 can result in an optimiser bug | 30 |
L-32 | For loops in public or external functions should be avoided due to high gas costs and possible DOS | 3 |
L-33 | Function with two (or more) array parameters missing a length check | 4 |
L-34 | Library has public/external functions | 13 |
L-35 | _safeMint() should be used rather than _mint() wherever possible | 1 |
L-36 | int/uint passed into abi.encodePacked without casting to a string | 35 |
Total <b>2220</b> instances over <b>90</b> issues:
ID | Issue | Instances |
---|---|---|
NC-1 | Assembly blocks should have extensive comments | 31 |
NC-2 | abi.encodePacked() should be replaced with bytes.concat() | 12 |
NC-3 | Add inline comments for unnamed variables | 2 |
NC-4 | Avoid mutating function parameters | 4 |
NC-5 | Multiple address /ID mappings can be combined into a single mapping of an address /ID to a struct , for readability | 4 |
NC-6 | Complex casting | 70 |
NC-7 | Consider adding a block/deny-list | 7 |
NC-8 | Consider adding emergency-stop functionality | 4 |
NC-9 | Consider adding formal verification proofs | 1 |
NC-10 | Consider bounding input array length | 7 |
NC-11 | Consider using descriptive constant s when passing zero as a function argument | 62 |
NC-12 | Constant decimal values | 1 |
NC-13 | Constant redefined elsewhere | 1 |
NC-14 | Constants should be put on the left side of comparisons | 92 |
NC-15 | Constructor should emit an event | 4 |
NC-16 | Contract does not follow the Solidity Style Guide's suggested layout ordering | 81 |
NC-17 | Contract should expose an interface | 73 |
NC-18 | Contracts should have full test coverage | 1 |
NC-19 | Convert simple if statements to ternary expressions | 2 |
NC-20 | Events that mark critical parameter changes should contain both the old and the new value | 24 |
NC-21 | Custom errors has no error details | 34 |
NC-22 | Solidity version is different in some files | 2 |
NC-23 | Polymorphic functions make security audits more time-consuming and error-prone | 15 |
NC-24 | Duplicated require() /revert() checks should be refactored | 2 |
NC-25 | else block not required | 6 |
NC-26 | Empty bytes check is missing | 7 |
NC-27 | Enable IR-based code generation | 1 |
NC-28 | Enum values should be used instead of constant array indexes | 26 |
NC-29 | Events are emitted without the sender information | 8 |
NC-30 | Events may be emitted out of order due to reentrancy | 5 |
NC-31 | Function ordering does not follow the Solidity Style Guide | 56 |
NC-32 | Functions and modifiers should be named in mixedCase style | 1 |
NC-33 | Functions with array parameters should have length checks in place | 19 |
NC-34 | High cyclomatic complexity | 3 |
NC-35 | Inconsistent spacing in comments | 95 |
NC-36 | Invalid NatSpec comment style | 7 |
NC-37 | It is best practice to use linear inheritance | 3 |
NC-38 | Lack of brace spacing | 1 |
NC-39 | Large or complicated code bases should implement invariant tests | 1 |
NC-40 | Large multiples of ten should use scientific notation | 12 |
NC-41 | Large numeric literals should use underscores for readability | 12 |
NC-42 | Long functions should be refactored into multiple, smaller, functions | 33 |
NC-43 | Magic numbers should be replaced with constants | 108 |
NC-44 | Missing NatSpec from contract/error/event/function/modifier declarations | 2 |
NC-45 | Missing NatSpec @author from contract declaration | 3 |
NC-46 | Missing NatSpec @dev from contract/event/function/modifier declarations | 171 |
NC-47 | Missing NatSpec @notice from contract/event/function/modifier declarations | 12 |
NC-48 | Missing NatSpec @param from event/function/modifier declarations | 5 |
NC-49 | Missing NatSpec @return from function declaration | 8 |
NC-50 | Missing NatSpec @title from contract declaration | 5 |
NC-51 | There is no need to initialize variables with 0 | 31 |
NC-52 | Non-assembly method available | 10 |
NC-53 | Put all system-wide constants in one file | 49 |
NC-54 | Redundant return statement in a function with named return variables | 21 |
NC-55 | Lines are too long | 5 |
NC-56 | Typos | 11 |
NC-57 | Unnecessary cast | 120 |
NC-58 | Unsafe conversion from unsigned to signed values | 79 |
NC-59 | Unsafe solidity low-level call can cause gas grief attack | 1 |
NC-60 | Unused error definition | 33 |
NC-61 | Unused arguments should be removed or implemented | 4 |
NC-62 | Unused import | 1 |
NC-63 | Unused named return | 18 |
NC-64 | Unused contract variables | 10 |
NC-65 | Consider using delete rather than assigning zero/false to clear values | 1 |
NC-66 | Solidity compiler version is not fixed | 20 |
NC-67 | Expressions for constant values should use immutable rather than constant | 49 |
NC-68 | Upgrade openzeppelin to the latest version (5.0.2 ) | 1 |
NC-69 | Use the latest solidity version for deployment (0.8.25 ) | 20 |
NC-70 | Use the Modern Upgradeable Contract Paradigm | 7 |
NC-71 | Consider using modifiers for address control | 8 |
NC-72 | Use of override is unnecessary | 2 |
NC-73 | Use scientific notation (e.g. 1e18 ) rather than exponentiation (e.g. 10**18 ) | 1 |
NC-74 | Use a struct to encapsulate multiple function parameters | 3 |
NC-75 | Use type(X).max instead of constant formulas like 2**n | 34 |
NC-76 | Visibility of state variables is not explicitly defined | 8 |
NC-77 | Whitespace in Expressions | 19 |
NC-78 | Common functions should be refactored to a common base contract | 4 |
NC-79 | Names of private /internal functions should be prefixed with an underscore | 104 |
NC-80 | Names of private /internal state variables should be prefixed with an underscore | 93 |
NC-81 | Internal function calls within for loops | 3 |
NC-82 | require() / revert() statements should have descriptive reason strings | 74 |
NC-83 | Variables should be named in mixedCase style | 2 |
NC-84 | Event is missing indexed fields | 12 |
NC-85 | Functions not used internally could be marked external | 13 |
NC-86 | Default address(0) can be returned | 2 |
NC-87 | Consider moving duplicated strings to constants | 5 |
NC-88 | Don't only depend on Solidity's arithmetic ordering | 24 |
NC-89 | Payable functions which do not interact with the native token should not be payable | 1 |
NC-90 | Use using keyword when using specific imports rather than calling the specific import directly | 171 |
<a name="M-2"></a>
approve()
not checkedNot all IERC20 implementations revert()
when there's a failure in approve()
. The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything
There are <b>4</b> 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 name="M-3"></a>
transfer()
/transferFrom()
not checkedNot all ERC20 implementations revert()
when there's a failure in transfer()
or transferFrom()
. The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually transfer anything.
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 333: return ERC20Minimal.transfer(recipient, amount); 352: return ERC20Minimal.transferFrom(from, to, amount);
<a name="M-4"></a>
slot0
is extremely easy to manipulateFile: ./contracts/PanopticFactory.sol 341: (uint160 currentSqrtPriceX96, , , , , , ) = v3Pool.slot0();
Github: [341]
File: ./contracts/PanopticPool.sol 304: (, int24 currentTick, , , , , ) = IUniswapV3Pool(_univ3pool).slot0(); 339: (uint160 currentSqrtPriceX96, , , , , , ) = s_univ3pool.slot0(); 387: (, int24 currentTick, , , , , ) = s_univ3pool.slot0(); 523: (, , uint16 observationIndex, uint16 observationCardinality, , , ) = s_univ3pool.slot0(); 904: ) = _univ3pool.slot0(); 1032: (, int24 currentTick, , , , , ) = s_univ3pool.slot0(); 1094: (, finalTick, , , , , ) = s_univ3pool.slot0(); 1202: (, int24 currentTick, , , , , ) = s_univ3pool.slot0(); 1598: (, int24 currentTick, , , , , ) = s_univ3pool.slot0();
Github: [304, 339, 387, 523, 904, 1032, 1094, 1202, 1598]
</details>File: ./contracts/SemiFungiblePositionManager.sol 725: (, int24 currentTick, , , , , ) = univ3pool.slot0(); 788: (uint160 sqrtPriceX96, , , , , , ) = _univ3pool.slot0();
<a name="L-1"></a>
Adding a zero address check for each address type parameter can prevent errors.
<details> <summary> There are <b>80</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol /// @audit missing zero check for `token0` /// @audit missing zero check for `token1` /// @audit missing zero check for `panopticPool` 221: function startToken( bool underlyingIsToken0, address token0, address token1, uint24 fee, PanopticPool panopticPool ) external { // fails if already initialized /// @audit missing zero check for `recipient` 323: function transfer( address recipient, uint256 amount ) public override(ERC20Minimal) returns (bool) { // make sure the caller does not have any open option positions /// @audit missing zero check for `to` 341: function transferFrom( address from, address to, uint256 amount ) public override(ERC20Minimal) returns (bool) { // make sure the caller does not have any open option positions /// @audit missing zero check for `` 392: function maxDeposit(address) external pure returns (uint256 maxAssets) { return type(uint104).max; /// @audit missing zero check for `receiver` 417: function deposit(uint256 assets, address receiver) external returns (uint256 shares) { if (assets > type(uint104).max) revert Errors.DepositTooLarge(); /// @audit missing zero check for `` 444: function maxMint(address) external view returns (uint256 maxShares) { unchecked { /// @audit missing zero check for `receiver` 477: function mint(uint256 shares, address receiver) external returns (uint256 assets) { assets = previewMint(shares); /// @audit missing zero check for `receiver` 531: function withdraw( uint256 assets, address receiver, address owner ) external returns (uint256 shares) { if (assets > maxWithdraw(owner)) revert Errors.ExceedsMaximumRedemption(); /// @audit missing zero check for `receiver` 591: function redeem( uint256 shares, address receiver, address owner ) external returns (uint256 assets) { if (shares > maxRedeem(owner)) revert Errors.ExceedsMaximumRedemption(); /// @audit missing zero check for `delegator` /// @audit missing zero check for `delegatee` 864: function delegate( address delegator, address delegatee, uint256 assets ) external onlyPanopticPool { /* /// @audit missing zero check for `delegatee` 898: /// @notice Refunds delegated tokens back to the protocol /// @dev Assumes that `delegatee` has enough money to pay for the refund /// @audit missing zero check for `delegatee` 907: /// @notice Revoke previously delegated shares. The opposite of 'delegate'. /// @param delegator The delegator to send shares *to* (because we are revoking - opposite when we delegate). /// @audit missing zero check for `delegator` /// @audit missing zero check for `delegatee` 917: ┌────────┐ │Panoptic│ │Pool │ └────┬───┘ │(1) convert 'assets' to shares (this ERC20 contract) /// @audit missing zero check for `refunder` /// @audit missing zero check for `refundee` 980: _transferFrom(refundee, refunder, convertToShares(uint256(-assets))); } } } /*////////////////////////////////////////////////////////////// /// @audit missing zero check for `optionOwner` 1002: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount; // constrict premium to only assets not belonging to PLPs (i.e premium paid by sellers or collected from the pool earlier) /// @audit missing zero check for `optionOwner` 1051: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount; // add premium to be paid/collected on position close int256 tokenToPay = -realizedPremium; /// @audit missing zero check for `user` 1147: tokenData = _getAccountMargin(user, currentTick, positionBalanceArray, premiumAllPositions); } /// @notice Get the collateral status/margin details of an account/user. /// NOTE: It's up to the caller to confirm from the returned result that the account has enough collateral. /// @dev This can be used to check the health: how many tokens a user has compared to the margin threshold. /// @audit missing zero check for `user` 1167: // if the account has active options, compute the required collateral to keep account in good health if (positionBalanceArray.length > 0) { // get all collateral required for the incoming list of positions tokenRequired = _getTotalRequiredCollateral(atTick, positionBalanceArray);
Github: [221, 323, 341, 392, 417, 444, 477, 531, 591, 864, 898, 907, 917, 980, 1002, 1051, 1147, 1167]
File: ./contracts/PanopticFactory.sol /// @audit missing zero check for `_owner` 134: function initialize(address _owner) public { /// @audit missing zero check for `newOwner` 147: function transferOwnership(address newOwner) external { /// @audit missing zero check for `token0` /// @audit missing zero check for `token1` 210: function deployNewPool( address token0, address token1, uint24 fee, bytes32 salt ) external returns (PanopticPool newPoolContract) { /// @audit missing zero check for `token0` /// @audit missing zero check for `token1` 335: function _mintFullRange( IUniswapV3Pool v3Pool, address token0, address token1, uint24 fee ) internal returns (uint256, uint256) { /// @audit missing zero check for `univ3pool` 420: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) { return s_getPanopticPool[univ3pool];
Github: [134, 147, 210, 335, 420]
File: ./contracts/PanopticPool.sol /// @audit missing zero check for `token0` /// @audit missing zero check for `token1` /// @audit missing zero check for `collateralTracker0` /// @audit missing zero check for `collateralTracker1` 291: function startPool( IUniswapV3Pool _univ3pool, address token0, address token1, CollateralTracker collateralTracker0, CollateralTracker collateralTracker1 ) external { /// @audit missing zero check for `user` 352: function optionPositionBalance( address user, TokenId tokenId ) external view returns (uint128 balance, uint64 poolUtilization0, uint64 poolUtilization1) { /// @audit missing zero check for `user` 381: function calculateAccumulatedFeesBatch( address user, bool includePendingPremium, TokenId[] calldata positionIdList ) external view returns (int128 premium0, int128 premium1, uint256[2][] memory) { /// @audit missing zero check for `user` 410: function calculatePortfolioValue( address user, int24 atTick, TokenId[] calldata positionIdList ) external view returns (int256 value0, int256 value1) { /// @audit missing zero check for `user` 429: function _calculateAccumulatedPremia( address user, TokenId[] calldata positionIdList, bool computeAllPremia, bool includePendingPremium, int24 atTick ) internal view returns (LeftRightSigned portfolioPremium, uint256[2][] memory balances) { /// @audit missing zero check for `owner` 794: function _burnAllOptionsFrom( address owner, int24 tickLimitLow, int24 tickLimitHigh, bool commitLongSettled, TokenId[] calldata positionIdList ) internal returns (LeftRightSigned netPaid, LeftRightSigned[4][] memory premiasByLeg) { /// @audit missing zero check for `owner` 826: function _burnOptions( bool commitLongSettled, TokenId tokenId, address owner, int24 tickLimitLow, int24 tickLimitHigh ) internal returns (LeftRightSigned paidAmounts, LeftRightSigned[4] memory premiaByLeg) { /// @audit missing zero check for `owner` 859: function _updatePositionDataBurn(address owner, TokenId tokenId) internal { /// @audit missing zero check for `user` 887: function _validateSolvency( address user, TokenId[] calldata positionIdList, uint256 buffer ) internal view returns (uint256 medianData) { /// @audit missing zero check for `owner` 955: function _burnAndHandleExercise( bool commitLongSettled, int24 tickLimitLow, int24 tickLimitHigh, TokenId tokenId, uint128 positionSize, address owner ) internal returns ( LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg, LeftRightSigned paidAmounts ) { /// @audit missing zero check for `liquidatee` 1017: function liquidate( TokenId[] calldata positionIdListLiquidator, address liquidatee, LeftRightUnsigned delegations, TokenId[] calldata positionIdList ) external { /// @audit missing zero check for `account` 1179: function forceExercise( address account, TokenId[] calldata touchedId, TokenId[] calldata positionIdListExercisee, TokenId[] calldata positionIdListExercisor ) external { /// @audit missing zero check for `account` 1290: function _checkSolvencyAtTick( address account, TokenId[] calldata positionIdList, int24 currentTick, int24 atTick, uint256 buffer ) internal view returns (bool) { /// @audit missing zero check for `account` 1367: function _validatePositionList( address account, TokenId[] calldata positionIdList, uint256 offset ) internal view { uint256 pLength; /// @audit missing zero check for `account` 1405: function _updatePositionsHash(address account, TokenId tokenId, bool addFlag) internal { // Get the current position hash value (fingerprint of all pre-existing positions created by '_account') /// @audit missing zero check for `user` 1444: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) { _numberOfPositions = (s_positionsHash[user] >> 248); /// @audit missing zero check for `owner` 1503: function _getPremia( TokenId tokenId, uint128 positionSize, address owner, bool computeAllPremia, int24 atTick ) internal view returns ( LeftRightSigned[4] memory premiaByLeg, uint256[2][4] memory premiumAccumulatorsByLeg ) { uint256 numLegs = tokenId.countLegs(); /// @audit missing zero check for `owner` 1587: function settleLongPremium( TokenId[] calldata positionIdList, address owner, uint256 legIndex ) external { _validatePositionList(owner, positionIdList, 0); /// @audit missing zero check for `owner` 1833: function _updateSettlementPostBurn( address owner, TokenId tokenId, LeftRightUnsigned[4] memory collectedByLeg, uint128 positionSize, bool commitLongSettled ) internal returns (LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg) { uint256 numLegs = tokenId.countLegs();
Github: [291, 352, 381, 410, 429, 794, 826, 859, 887, 955, 1017, 1179, 1290, 1367, 1405, 1444, 1503, 1587, 1833]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit missing zero check for `token0` /// @audit missing zero check for `token1` 361: // if poolId == 0, we have a bit on the left set if it was initialized, so this will still return properly if (s_AddrToPoolIdData[univ3pool] != 0) return; /// @audit missing zero check for `from` /// @audit missing zero check for `to` 555: super.safeTransferFrom(from, to, id, amount, data); } /// @notice Transfer multiple tokens from one user to another /// @dev supports token approvals /// @dev ids and amounts must be of equal length /// @audit missing zero check for `from` /// @audit missing zero check for `to` 583: // transfer the token (note that all state updates are completed before reentrancy is possible) super.safeBatchTransferFrom(from, to, ids, amounts, data); } /// @notice update user position data following a token transfer /// @dev token transfers are only allowed if you transfer your entire liquidity of a given chunk and the recipient has none /// @audit missing zero check for `from` /// @audit missing zero check for `to` 611: bytes32 positionKey_from = keccak256( abi.encodePacked( address(univ3pool), /// @audit missing zero check for `univ3pool` 786: // this is simply a convenience feature, and should be treated as such if ((itm0 != 0) && (itm1 != 0)) { (uint160 sqrtPriceX96, , , , , , ) = _univ3pool.slot0(); /// @audit missing zero check for `univ3pool` 897: // We loop in reverse order if burning a position so that any dependent long liquidity is returned to the pool first, // allowing the corresponding short liquidity to be removed _leg = _isBurn ? numLegs - leg - 1 : leg; } // for this _leg index: extract the liquidity chunk: a 256bit word containing the liquidity amount and upper/lower tick /// @audit missing zero check for `univ3pool` 1169: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside0LastX128, liquidity))) ) .toLeftSlot( int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside1LastX128, liquidity))) ) : LeftRightSigned /// @audit missing zero check for `univ3pool` 1221: /// @param liquidityChunk the chunk of liquidity to burn given by tick upper, tick lower, and its size /// @param univ3pool the Uniswap v3 pool to burn liquidity in/from /// @audit missing zero check for `univ3pool` 1252: /// @param movedInLeg how much liquidity has been moved between msg.sender and Uniswap before this function call /// @param isLong whether the leg in question is long (=1) or short (=0) /// @return collectedChunk the incoming amount collected with potentially whatever is collected in this function added to it /// @audit missing zero check for `univ3pool` 1288: uint128(amountToCollect.rightSlot()), uint128(amountToCollect.leftSlot()) ); // moved will be negative if the leg was long (funds left the caller, don't count it in collected fees) uint128 collected0; uint128 collected1; unchecked { collected0 = movedInLeg.rightSlot() < 0 /// @audit missing zero check for `univ3pool` /// @audit missing zero check for `owner` 1445: /// @param atTick The current tick. Set atTick < type(int24).max = 8388608 to get latest premium up to the current block /// @param isLong whether the position is long (=1) or short (=0) /// @return premiumToken0 The amount of premium (per liquidity X64) for token0 = sum (feeGrowthLast0X128) over every block where the position has been touched /// @audit missing zero check for `owner` 1479: // oldFeesGrowth is the last stored update of fee growth within the position range in the past (feeGrowthRange*liquidityChunk.liquidity()) (s_accountFeesBase[positionKey]) LeftRightSigned feesBase = FeesCalc.calculateAMMSwapFees( _univ3pool, atTick, _tickLower, _tickUpper, /// @audit missing zero check for `univ3pool` /// @audit missing zero check for `owner` 1570: /// @audit missing zero check for `univ3pool` 1570:
Github: [361, 555, 583, 611, 786, 897, 1169, 1221, 1252, 1288, 1445, 1479, 1570, 1570]
File: ./contracts/libraries/CallbackLib.sol /// @audit missing zero check for `sender` /// @audit missing zero check for `factory` 30: function validateCallback( address sender, IUniswapV3Factory factory, PoolFeatures memory features ) internal view {
Github: [30]
File: ./contracts/libraries/FeesCalc.sol /// @audit missing zero check for `univ3pool` 104: // extract the amount of AMM fees collected within the liquidity chunk` // note: the fee variables are *per unit of liquidity*; so more "rate" variables ( uint256 ammFeesPerLiqToken0X128, /// @audit missing zero check for `univ3pool` 135: ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { // Get feesGrowths from the option position's lower+upper ticks // lowerOut0: For token0: fee growth per unit of liquidity on the _other_ side of tickLower (relative to currentTick) // only has relative meaning, not absolute — the value depends on when the tick is initialized
File: ./contracts/libraries/PanopticMath.sol /// @audit missing zero check for `univ3pool` 125: function computeMedianObservedPrice( IUniswapV3Pool univ3pool, uint256 observationIndex, uint256 observationCardinality, uint256 cardinality, uint256 period ) external view returns (int24) { unchecked { /// @audit missing zero check for `univ3pool` 168: function computeInternalMedian( uint256 observationIndex, uint256 observationCardinality, uint256 period, uint256 medianData, IUniswapV3Pool univ3pool ) external view returns (int24 medianTick, uint256 updatedMedianData) { unchecked { /// @audit missing zero check for `univ3pool` 241: function twapFilter(IUniswapV3Pool univ3pool, uint32 twapWindow) external view returns (int24) { uint32[] memory secondsAgos = new uint32[](20); /// @audit missing zero check for `liquidatee` /// @audit missing zero check for `collateral0` /// @audit missing zero check for `collateral1` 770: TokenId[] memory positionIdList, LeftRightSigned[4][] memory premiasByLeg, LeftRightSigned collateralRemaining, CollateralTracker collateral0, CollateralTracker collateral1, uint160 sqrtPriceX96Final, mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens ) external returns (int256, int256) { unchecked { // get the amount of premium paid by the liquidatee LeftRightSigned longPremium; /// @audit missing zero check for `collateral0` /// @audit missing zero check for `collateral1` 919: LeftRightSigned refundValues, int24 atTick, CollateralTracker collateral0, CollateralTracker collateral1 ) external view returns (LeftRightSigned) { uint160 sqrtPriceX96 = Math.getSqrtRatioAtTick(atTick); unchecked { // if the refunder lacks sufficient token0 to pay back the refundee, have them pay back the equivalent value in token1
Github: [125, 168, 241, 770, 919]
File: ./contracts/libraries/SafeTransferLib.sol /// @audit missing zero check for `token` /// @audit missing zero check for `from` /// @audit missing zero check for `to` 21: function safeTransferFrom(address token, address from, address to, uint256 amount) internal { /// @audit missing zero check for `token` /// @audit missing zero check for `to` 52: function safeTransfer(address token, address to, uint256 amount) internal {
File: ./contracts/tokens/ERC1155Minimal.sol /// @audit missing zero check for `operator` 81: function setApprovalForAll(address operator, bool approved) public { /// @audit missing zero check for `from` 94: function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes calldata data ) public virtual { /// @audit missing zero check for `from` 130: function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) public virtual { /// @audit missing zero check for `from` 236: function _burn(address from, uint256 id, uint256 amount) internal {
File: ./contracts/tokens/ERC20Minimal.sol /// @audit missing zero check for `spender` 49: function approve(address spender, uint256 amount) public returns (bool) { /// @audit missing zero check for `to` 61: function transfer(address to, uint256 amount) public virtual returns (bool) { /// @audit missing zero check for `from` /// @audit missing zero check for `to` 81: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { /// @audit missing zero check for `from` /// @audit missing zero check for `to` 103: function _transferFrom(address from, address to, uint256 amount) internal { /// @audit missing zero check for `to` 122: function _mint(address to, uint256 amount) internal { /// @audit missing zero check for `from` 136: function _burn(address from, uint256 amount) internal {
Github: [49, 61, 81, 103, 122, 136]
File: ./contracts/tokens/interfaces/IDonorNFT.sol /// @audit missing zero check for `deployer` /// @audit missing zero check for `newPoolContract` /// @audit missing zero check for `token0` /// @audit missing zero check for `token1` 13: function issueNFT(
Github: [13]
</details>File: ./contracts/tokens/interfaces/IERC20Partial.sol /// @audit missing zero check for `account` 16: function balanceOf(address account) external view returns (uint256); /// @audit missing zero check for `spender` 22: function approve(address spender, uint256 amount) external; /// @audit missing zero check for `to` 27: function transfer(address to, uint256 amount) external;
<a name="L-2"></a>
approve()
/safeApprove()
Changing an allowance with approve() brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. Refer to ERC20 API: An Attack Vector on the Approve/TransferFrom Methods. It is recommended to use the increaseAllowance()
/decreaseAllowance()
to avoid this problem.
There are <b>4</b> 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 name="L-3"></a>
A copy-paste error or a typo may end up bricking protocol functionality, or sending tokens to an address with no known private key. Consider implementing a two-step procedure for updating protocol addresses, where the recipient is set as pending, and must "accept" the assignment by making an affirmative call. A straight forward way of doing this would be to have the target contracts implement EIP-165, and to have the "set" functions ensure that the recipient is of the right interface type.
There are <b>4</b> instances of this issue:
File: ./contracts/CollateralTracker.sol /// @audit `s_underlyingToken` /// @audit `s_panopticPool` /// @audit `s_univ3token0` /// @audit `s_univ3token1` 221: function startToken( bool underlyingIsToken0, address token0, address token1, uint24 fee, PanopticPool panopticPool ) external { // fails if already initialized
Github: [221]
File: ./contracts/PanopticFactory.sol /// @audit `s_owner` 147: function transferOwnership(address newOwner) external { /// @audit `s_getPanopticPool` 210: function deployNewPool( address token0, address token1, uint24 fee, bytes32 salt ) external returns (PanopticPool newPoolContract) {
File: ./contracts/PanopticPool.sol /// @audit `s_univ3pool` /// @audit `s_collateralToken0` /// @audit `s_collateralToken1` 291: function startPool( IUniswapV3Pool _univ3pool, address token0, address token1, CollateralTracker collateralTracker0, CollateralTracker collateralTracker1 ) external {
Github: [291]
<a name="L-4"></a>
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's crucial to carefully check each parameter in the constructor and initialization functions. If an exception is found, the transaction should be rolled back.
There are <b>5</b> instances of this issue:
File: ./contracts/CollateralTracker.sol /// @audit `_commissionFee` not validated /// @audit `_buyerCollateralRatio` not validated /// @audit `_forceExerciseCost` not validated /// @audit `_targetPoolUtilization` not validated /// @audit `_saturatedPoolUtilization` not validated /// @audit `_ITMSpreadMultiplier` not validated 178: constructor(
Github: [178]
File: ./contracts/PanopticFactory.sol /// @audit `_WETH9` not validated /// @audit `_SFPM` not validated /// @audit `_univ3Factory` not validated /// @audit `_donorNFT` not validated /// @audit `_poolReference` not validated /// @audit `_collateralReference` not validated 115: constructor( /// @audit `_owner` not validated 134: function initialize(address _owner) public {
File: ./contracts/PanopticPool.sol /// @audit `_sfpm` not validated 280: constructor(SemiFungiblePositionManager _sfpm) {
Github: [280]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `_factory` not validated 355: if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized();
Github: [355]
<a name="L-5"></a>
Infinite approvals on external contracts can be dangerous if the target becomes compromised. See here for a list of approval exploits. The following contracts are vulnerable to such attacks since they have no functionality to revoke the approval (call approve with amount 0). Consider enabling the contract to revoke approval in emergency situations.
There are <b>4</b> 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 name="L-6"></a>
Code should follow the best-practice of check-effects-interaction, where state variables are updated before any external calls are made. Doing so prevents a large class of reentrancy bugs.
There are <b>9</b> instances of this issue:
File: ./contracts/PanopticFactory.sol /// @audit `.getPool(...)` is called on line 226 /// @audit `.initializeAMMPool(...)` is called on line 233 /// @audit `.cloneDeterministic(...)` is called on line 237 /// @audit `.startToken(...)` is called on line 248 /// @audit `.startToken(...)` is called on line 249 /// @audit `.startPool(...)` is called on line 251 253: s_getPanopticPool[v3Pool] = newPoolContract;
Github: [253]
File: ./contracts/PanopticPool.sol /// @audit `.slot0(...)` is called on line 304 308: s_miniMedian = /// @audit `.slot0(...)` is called on line 304 318: s_collateralToken0 = collateralTracker0; /// @audit `.slot0(...)` is called on line 304 319: s_collateralToken1 = collateralTracker1; /// @audit `.slot0(...)` is called on line 523 533: if (medianData != 0) s_miniMedian = medianData; /// @audit `.getPoolId(...)` is called on line 633 654: s_positionBalance[msg.sender][tokenId] = LeftRightUnsigned /// @audit `.getPoolId(...)` is called on line 633 664: if (medianData != 0) s_miniMedian = medianData; /// @audit `.slot0(...)` is called on line 1598 /// @audit `.getAccountPremium(...)` is called on line 1605 /// @audit `.exercise(...)` is called on line 1639 /// @audit `.exercise(...)` is called on line 1640 1650: s_settledTokens[chunkKey] = s_settledTokens[chunkKey].add( /// @audit `.getAccountPremium(...)` is called on line 1707 1725: s_grossPremiumLast[chunkKey] = LeftRightUnsigned
Github: [308, 318, 319, 533, 654, 664, 1650, 1725]
<a name="L-7"></a>
Calling external functions within loops can easily result in insufficient gas. This greatly increases the likelihood of transaction failures, DOS attacks, and other unexpected actions. It is recommended to limit the number of loops within loops that call external functions, and to limit the gas line for each external call.
There are <b>4</b> instances of this issue:
File: ./contracts/PanopticFactory.sol /// @audit `.predictDeterministicAddress(...)` 305: POOL_REFERENCE.predictDeterministicAddress(salt)
Github: [305]
File: ./contracts/PanopticPool.sol /// @audit `.getAccountPremium(...)` 751: (uint128 premiumAccumulator0, uint128 premiumAccumulator1) = SFPM.getAccountPremium( /// @audit `.getAccountPremium(...)` 1707: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium(
File: ./contracts/libraries/PanopticMath.sol /// @audit `.observations(...)` 138: (timestamps[i], tickCumulatives[i], , ) = univ3pool.observations(
Github: [138]
<a name="L-8"></a>
While adherence to the check-effects-interaction pattern is commendable, the absence of a reentrancy guard in functions, especially where transfer hooks might be present, can expose the protocol users to risks of read-only reentrancies. Such reentrancy vulnerabilities can be exploited to execute malicious actions even without altering the contract state. Without a reentrancy guard, the only potential mitigation would be to blocklist the entire protocol - an extreme and disruptive measure. Therefore, incorporating a reentrancy guard into these functions is vital to bolster security, as it helps protect against both traditional reentrancy attacks and read-only reentrancies, ensuring robust and safe protocol operations.
There are <b>7</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 341: function transferFrom( address from, address to, uint256 amount ) public override(ERC20Minimal) returns (bool) { // make sure the caller does not have any open option positions // if they do: we don't want them sending panoptic pool shares to others // as this would reduce their amount of collateral against the opened positions if (s_panopticPool.numberOfPositions(from) != 0) revert Errors.PositionCountNotZero(); return ERC20Minimal.transferFrom(from, to, amount);
Github: [341]
File: ./contracts/SemiFungiblePositionManager.sol 450: // Transform the amount to pay to uint256 (take positive one from amount0 and amount1) // the pool will always pass one delta with a positive sign and one with a negative sign or zero, // so this logic always picks the correct delta to pay uint256 amountToPay = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); // Pay the required token from the payer to the caller of this contract SafeTransferLib.safeTransferFrom(token, decoded.payer, msg.sender, amountToPay); 583: // transfer the token (note that all state updates are completed before reentrancy is possible) super.safeBatchTransferFrom(from, to, ids, amounts, data); } /// @notice update user position data following a token transfer /// @dev token transfers are only allowed if you transfer your entire liquidity of a given chunk and the recipient has none /// @param from The address of the sender /// @param to The address of the recipient /// @param id The tokenId being transferred' /// @param amount The amount of the token being transferred function registerTokenTransfer(address from, address to, TokenId id, uint256 amount) internal {
File: ./contracts/tokens/ERC1155Minimal.sol 94: function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes calldata data ) public virtual { if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized(); balanceOf[from][id] -= amount; // balance will never overflow unchecked { balanceOf[to][id] += amount; } emit TransferSingle(msg.sender, from, to, id, amount); 130: function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) public virtual { if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized(); // Storing these outside the loop saves ~15 gas per iteration. uint256 id; uint256 amount; for (uint256 i = 0; i < ids.length; ) { id = ids[i]; amount = amounts[i]; balanceOf[from][id] -= amount; // balance will never overflow unchecked { balanceOf[to][id] += amount; } // An array can't have a total length // larger than the max uint256 value. unchecked { ++i; } } emit TransferBatch(msg.sender, from, to, ids, amounts);
File: ./contracts/tokens/ERC20Minimal.sol 61: function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); 81: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount);
<a name="L-9"></a>
ERC20
tokensNot 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
, which may cause issues with systems that expect the value passed to approve to be reflected in the allowances mapping.
There are <b>4</b> 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 name="L-10"></a>
Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.
<details> <summary> There are <b>31</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 262: s_ITMSpreadFee = uint128((ITM_SPREAD_MULTIPLIER * _poolFee) / DECIMALS); 446: return (convertToShares(type(uint104).max) * DECIMALS) / (DECIMALS + COMMISSION_FEE); 677: uint256(Math.abs(currentTick - positionId.strike(leg)) / range) 743: return int256((s_inAMM * DECIMALS) / totalAssets()); 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / 1121: uint256(uint128(shortAmount + longAmount)) * COMMISSION_FEE, 1409: (tickUpper - strike) + (strike - tickLower) 1571: ? Math.unsafeDivRoundingUp((notionalP - notional) * contracts, notional) 1572: : Math.unsafeDivRoundingUp((notional - notionalP) * contracts, notionalP);
Github: [262, 446, 677, 743, 797, 1121, 1409, 1571, 1572]
File: ./contracts/PanopticFactory.sol 392: tickLower = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing;
Github: [392]
File: ./contracts/PanopticPool.sol 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity;
Github: [1487]
File: ./contracts/SemiFungiblePositionManager.sol 228: s_accountPremiumOwed += feeGrowthX128 * R * (1 + ν*R/N) / R 271: = ∆feeGrowthX128 * t * (T - R + ν*R^2/T) / N 272: = ∆feeGrowthX128 * t * (N + ν*R^2/T) / N
File: ./contracts/libraries/PanopticMath.sol 62: return (poolId & TICKSPACING_MASK) + (uint48(poolId) + 1); 107: ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248) 108: : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248); 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / 188: int256(observationIndex) - int256(1) + int256(observationCardinality) 223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1))); 346: int24 minTick = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing; 347: int24 maxTick = (Constants.MAX_V3POOL_TICK / tickSpacing) * tickSpacing;
Github: [62, 107, 108, 178, 179, 188, 223, 346, 347]
File: ./contracts/types/TokenId.sol 212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48))); 229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1)) 246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8))); 263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9)) 281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10)) 406: return self.isLong(0) + self.isLong(1) + self.isLong(2) + self.isLong(3); 521: if (uint48(chunkData >> (48 * i)) == uint48(chunkData >> (48 * j))) { 589: if ((currentTick >= _strike + rangeUp) || (currentTick < _strike - rangeDown)) {
Github: [212, 229, 246, 263, 281, 406, 521, 589]
</details><a name="L-11"></a>
There are some missing checks in these functions, and this could lead to unexpected scenarios. Consider always adding a sanity check for state variables.
<details> <summary> There are <b>23</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol /// @audit `_commissionFee` 187: COMMISSION_FEE = _commissionFee; /// @audit `_sellerCollateralRatio` 188: SELLER_COLLATERAL_RATIO = _sellerCollateralRatio; /// @audit `_buyerCollateralRatio` 189: BUYER_COLLATERAL_RATIO = _buyerCollateralRatio; /// @audit `_forceExerciseCost` 190: FORCE_EXERCISE_COST = _forceExerciseCost; /// @audit `_targetPoolUtilization` 191: TARGET_POOL_UTIL = _targetPoolUtilization; /// @audit `_saturatedPoolUtilization` 192: SATURATED_POOL_UTIL = _saturatedPoolUtilization; /// @audit `_ITMSpreadMultiplier` 193: ITM_SPREAD_MULTIPLIER = _ITMSpreadMultiplier; /// @audit `panopticPool` 244: s_panopticPool = panopticPool; /// @audit `token0` 254: s_univ3token0 = token0; /// @audit `token1` 255: s_univ3token1 = token1; /// @audit `underlyingIsToken0` 258: s_underlyingIsToken0 = underlyingIsToken0;
Github: [187, 188, 189, 190, 191, 192, 193, 244, 254, 255, 258]
File: ./contracts/PanopticFactory.sol /// @audit `_WETH9` 123: WETH = _WETH9; /// @audit `_SFPM` 124: SFPM = _SFPM; /// @audit `_donorNFT` 125: DONOR_NFT = _donorNFT; /// @audit `_univ3Factory` 127: UNIV3_FACTORY = _univ3Factory; /// @audit `_poolReference` 128: POOL_REFERENCE = _poolReference; /// @audit `_collateralReference` 129: COLLATERAL_REFERENCE = _collateralReference; /// @audit `_owner` 136: s_owner = _owner; /// @audit `newOwner` 152: s_owner = newOwner;
Github: [123, 124, 125, 127, 128, 129, 136, 152]
File: ./contracts/PanopticPool.sol /// @audit `_sfpm` 281: SFPM = _sfpm; /// @audit `collateralTracker0` 318: s_collateralToken0 = collateralTracker0; /// @audit `collateralTracker1` 319: s_collateralToken1 = collateralTracker1;
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `_factory` 357: // return if the pool has already been initialized in SFPM
Github: [357]
</details><a name="L-12"></a>
Constructors often take address parameters to initialize important components of a contract, such as owner or linked contracts. However, without a checking, there's a risk that an address parameter could be mistakenly set to the zero address (0x0). This could be due to an error or oversight during contract deployment. A zero address in a crucial role can cause serious issues, as it cannot perform actions like a normal address, and any funds sent to it will be irretrievable. It's therefore crucial to include a zero address check in constructors to prevent such potential problems. If a zero address is detected, the constructor should revert the transaction.
There are <b>3</b> instances of this issue:
File: ./contracts/PanopticFactory.sol /// @audit missing zero check for `_WETH9` /// @audit missing zero check for `_SFPM` /// @audit missing zero check for `_univ3Factory` /// @audit missing zero check for `_donorNFT` /// @audit missing zero check for `_poolReference` /// @audit missing zero check for `_collateralReference` 115: constructor( address _WETH9, SemiFungiblePositionManager _SFPM, IUniswapV3Factory _univ3Factory, IDonorNFT _donorNFT, address _poolReference, address _collateralReference ) {
Github: [115]
File: ./contracts/PanopticPool.sol /// @audit missing zero check for `_sfpm` 280: constructor(SemiFungiblePositionManager _sfpm) {
Github: [280]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit missing zero check for `_factory` 355: if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized();
Github: [355]
<a name="L-13"></a>
address(0)
when setting address state variablesCheck for zero-address to avoid the risk of setting address(0)
for state variables after an update.
File: ./contracts/CollateralTracker.sol /// @audit `panopticPool` 244: s_panopticPool = panopticPool; /// @audit `token0` 254: s_univ3token0 = token0; /// @audit `token1` 255: s_univ3token1 = token1;
File: ./contracts/PanopticFactory.sol /// @audit `_WETH9` 123: WETH = _WETH9; /// @audit `_SFPM` 124: SFPM = _SFPM; /// @audit `_donorNFT` 125: DONOR_NFT = _donorNFT; /// @audit `_univ3Factory` 127: UNIV3_FACTORY = _univ3Factory; /// @audit `_poolReference` 128: POOL_REFERENCE = _poolReference; /// @audit `_collateralReference` 129: COLLATERAL_REFERENCE = _collateralReference; /// @audit `_owner` 136: s_owner = _owner; /// @audit `newOwner` 152: s_owner = newOwner;
Github: [123, 124, 125, 127, 128, 129, 136, 152]
File: ./contracts/PanopticPool.sol /// @audit `_sfpm` 281: SFPM = _sfpm; /// @audit `collateralTracker0` 318: s_collateralToken0 = collateralTracker0; /// @audit `collateralTracker1` 319: s_collateralToken1 = collateralTracker1;
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `_factory` 357: // return if the pool has already been initialized in SFPM
Github: [357]
</details><a name="L-14"></a>
Dividing an integer by another integer will often result in loss of precision. When the result is multiplied by another number, the loss of precision is magnified, often to material magnitudes. (X / Z) * Y
should be re-written as (X * Y) / Z
There are <b>4</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 1614: Put side of a short strangle, BPR = 100% - (100% - SCR/2)*(price/strike)
Github: [1614]
File: ./contracts/PanopticFactory.sol 392: tickLower = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing;
Github: [392]
File: ./contracts/libraries/PanopticMath.sol 346: int24 minTick = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing; 347: int24 maxTick = (Constants.MAX_V3POOL_TICK / tickSpacing) * tickSpacing;
<a name="L-15"></a>
Solidity version 0.8.20 or above uses the new Shanghai EVM which introduces the PUSH0 opcode. This op code may not yet be implemented on all evm-chains or Layer2s, so deployment on these chains will fail. Consider using an earlier solidity version.
<details> <summary> There are <b>20</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/PanopticFactory.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/PanopticPool.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/SemiFungiblePositionManager.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/libraries/CallbackLib.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Constants.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Errors.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/FeesCalc.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/InteractionHelper.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Math.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/PanopticMath.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/SafeTransferLib.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/multicall/Multicall.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/tokens/ERC1155Minimal.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/ERC20Minimal.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/interfaces/IDonorNFT.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/interfaces/IERC20Partial.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/LeftRight.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/LiquidityChunk.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/TokenId.sol 2: pragma solidity ^0.8.0;
Github: [2]
</details><a name="L-16"></a>
Tokens such as COMP or UNI will revert when an address' balance reaches type(uint96).max
. Ensure that the calls below can be broken up into smaller batches if necessary.
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 333: return ERC20Minimal.transfer(recipient, amount); 352: return ERC20Minimal.transferFrom(from, to, amount);
<a name="L-17"></a>
Despite the fact that EIP-20 states that zero-value transfers must be accepted, some tokens, such as LEND, will revert if this is attempted, which may cause transactions that involve other tokens (such as batch operations) to fully revert. Consider skipping the transfer if the amount is zero, which will also save gas.
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 333: return ERC20Minimal.transfer(recipient, amount); 352: return ERC20Minimal.transferFrom(from, to, amount);
<a name="L-18"></a>
The following expressions may underflow, as the subtrahend could be greater than the minuend in case the former is multiplied by a large number.
<details> <summary> There are <b>36</b> instances (click to show): </summary>File: ./contracts/libraries/Math.sol /// @audit `denominator * inv` 418: inv *= 2 - denominator * inv; // inverse mod 2**8 /// @audit `denominator * inv` 418: inv *= 2 - denominator * inv; // inverse mod 2**8 /// @audit `denominator * inv` 418: inv *= 2 - denominator * inv; // inverse mod 2**8 /// @audit `denominator * inv` 418: inv *= 2 - denominator * inv; // inverse mod 2**8 /// @audit `denominator * inv` 418: inv *= 2 - denominator * inv; // inverse mod 2**8 /// @audit `denominator * inv` 418: inv *= 2 - denominator * inv; // inverse mod 2**8 /// @audit `denominator * inv` 419: inv *= 2 - denominator * inv; // inverse mod 2**16 /// @audit `denominator * inv` 419: inv *= 2 - denominator * inv; // inverse mod 2**16 /// @audit `denominator * inv` 419: inv *= 2 - denominator * inv; // inverse mod 2**16 /// @audit `denominator * inv` 419: inv *= 2 - denominator * inv; // inverse mod 2**16 /// @audit `denominator * inv` 419: inv *= 2 - denominator * inv; // inverse mod 2**16 /// @audit `denominator * inv` 419: inv *= 2 - denominator * inv; // inverse mod 2**16 /// @audit `denominator * inv` 420: inv *= 2 - denominator * inv; // inverse mod 2**32 /// @audit `denominator * inv` 420: inv *= 2 - denominator * inv; // inverse mod 2**32 /// @audit `denominator * inv` 420: inv *= 2 - denominator * inv; // inverse mod 2**32 /// @audit `denominator * inv` 420: inv *= 2 - denominator * inv; // inverse mod 2**32 /// @audit `denominator * inv` 420: inv *= 2 - denominator * inv; // inverse mod 2**32 /// @audit `denominator * inv` 420: inv *= 2 - denominator * inv; // inverse mod 2**32 /// @audit `denominator * inv` 421: inv *= 2 - denominator * inv; // inverse mod 2**64 /// @audit `denominator * inv` 421: inv *= 2 - denominator * inv; // inverse mod 2**64 /// @audit `denominator * inv` 421: inv *= 2 - denominator * inv; // inverse mod 2**64 /// @audit `denominator * inv` 421: inv *= 2 - denominator * inv; // inverse mod 2**64 /// @audit `denominator * inv` 421: inv *= 2 - denominator * inv; // inverse mod 2**64 /// @audit `denominator * inv` 421: inv *= 2 - denominator * inv; // inverse mod 2**64 /// @audit `denominator * inv` 422: inv *= 2 - denominator * inv; // inverse mod 2**128 /// @audit `denominator * inv` 422: inv *= 2 - denominator * inv; // inverse mod 2**128 /// @audit `denominator * inv` 422: inv *= 2 - denominator * inv; // inverse mod 2**128 /// @audit `denominator * inv` 422: inv *= 2 - denominator * inv; // inverse mod 2**128 /// @audit `denominator * inv` 422: inv *= 2 - denominator * inv; // inverse mod 2**128 /// @audit `denominator * inv` 422: inv *= 2 - denominator * inv; // inverse mod 2**128 /// @audit `denominator * inv` 423: inv *= 2 - denominator * inv; // inverse mod 2**256 /// @audit `denominator * inv` 423: inv *= 2 - denominator * inv; // inverse mod 2**256 /// @audit `denominator * inv` 423: inv *= 2 - denominator * inv; // inverse mod 2**256 /// @audit `denominator * inv` 423: inv *= 2 - denominator * inv; // inverse mod 2**256 /// @audit `denominator * inv` 423: inv *= 2 - denominator * inv; // inverse mod 2**256 /// @audit `denominator * inv` 423: inv *= 2 - denominator * inv; // inverse mod 2**256
Github: [418, 418, 418, 418, 418, 418, 419, 419, 419, 419, 419, 419, 420, 420, 420, 420, 420, 420, 421, 421, 421, 421, 421, 421, 422, 422, 422, 422, 422, 422, 423, 423, 423, 423, 423, 423]
</details><a name="L-19"></a>
Performing token transfers in a loop in a Solidity contract is generally not recommended due to various reasons. One of these reasons is the "Fail-Silently" issue.
In a Solidity loop, if one transfer operation fails, it causes the entire transaction to fail. This issue can be particularly troublesome when you're dealing with multiple transfers in one transaction. For instance, if you're looping through an array of recipients to distribute dividends or rewards, a single failed transfer will prevent all the subsequent recipients from receiving their transfers. This could be due to the recipient contract throwing an exception or due to other issues like a gas limit being exceeded.
Moreover, such a design could also inadvertently lead to a situation where a malicious contract intentionally causes a failure when receiving Ether to prevent other participants from getting their rightful transfers. This could open up avenues for griefing attacks in the system.
Resolution: To mitigate this problem, it's typically recommended to follow the "withdraw pattern" in your contracts instead of pushing payments. In this model, each recipient would be responsible for triggering their own payment. This separates each transfer operation, so a failure in one doesn't impact the others. Additionally, it greatly reduces the chance of malicious interference as the control over fund withdrawal lies with the intended recipient and not with an external loop operation.
There is one instance of this issue:
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `poolId` 591: /// @param id The tokenId being transferred' /// @param amount The amount of the token being transferred function registerTokenTransfer(address from, address to, TokenId id, uint256 amount) internal {
Github: [591]
<a name="L-20"></a>
address(0)
There are <b>3</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 477: function mint(uint256 shares, address receiver) external returns (uint256 assets) { assets = previewMint(shares); if (assets > type(uint104).max) revert Errors.DepositTooLarge(); // transfer assets (underlying token funds) from the user/the LP to the PanopticPool // in return for the shares to be minted SafeTransferLib.safeTransferFrom( s_underlyingToken, msg.sender, address(s_panopticPool), assets ); // mint collateral shares of the Panoptic Pool funds (this ERC20 token) _mint(receiver, shares); // update tracked asset balance unchecked { s_poolAssets += uint128(assets); } emit Deposit(msg.sender, receiver, assets, shares); }
Github: [477]
File: ./contracts/tokens/ERC1155Minimal.sol 214: function _mint(address to, uint256 id, uint256 amount) internal { // balance will never overflow unchecked { balanceOf[to][id] += amount; } emit TransferSingle(msg.sender, address(0), to, id, amount); if (to.code.length != 0) { if ( ERC1155Holder(to).onERC1155Received(msg.sender, address(0), id, amount, "") != ERC1155Holder.onERC1155Received.selector ) { revert UnsafeRecipient(); } } }
Github: [214]
File: ./contracts/tokens/ERC20Minimal.sol 122: function _mint(address to, uint256 amount) internal { // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } totalSupply += amount; emit Transfer(address(0), to, amount); }
Github: [122]
<a name="L-21"></a>
When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. Without any other checks, this wrapping will lead to unexpected behavior and bugs.
<details> <summary> There are <b>122</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol /// @audit uint256 -> uint128 262: s_ITMSpreadFee = uint128((ITM_SPREAD_MULTIPLIER * _poolFee) / DECIMALS); /// @audit uint256 -> uint128 436: s_poolAssets += uint128(assets); /// @audit uint256 -> uint128 496: s_poolAssets += uint128(assets); /// @audit uint256 -> uint128 552: s_poolAssets -= uint128(assets); /// @audit uint256 -> uint128 612: s_poolAssets -= uint128(assets); /// @audit int256 -> int24 667: int24 range = int24( /// @audit uint256 -> uint128 715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0)) /// @audit uint256 -> uint128 715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0)) /// @audit uint256 -> uint128 718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1)) /// @audit uint256 -> uint128 718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1)) /// @audit int256 -> int128 731: .toRightSlot(int128((longAmounts.rightSlot() * fee) / DECIMALS_128)) /// @audit int256 -> int128 732: .toLeftSlot(int128((longAmounts.leftSlot() * fee) / DECIMALS_128)); /// @audit uint256 -> uint128 1036: /// @dev Called when a position is burnt because it may need to be exercised. /// @audit uint256 -> uint128 1037: /// @param optionOwner The owner of the option being burned and potentially exercised. /// @audit uint256 -> uint128 1092: /// @param longAmount The amount of long options held. /// @audit uint256 -> uint128 1094: /// @param swappedAmount The (potential) amount swapped during any ITM option creations. /// @audit int256 -> int128 1095: /// @return exchangedAmount The amount of funds to be exchanged for minting an option (includes commission, swapFee, and intrinsic value). /// @audit uint128 -> uint64 1337: // if the position is long, required tokens does not depend on price /// @audit uint128 -> uint64 1338: unchecked { /// @audit uint128 -> uint64 1593: /// @dev A strangle can only have only one of its leg tested at the same time, so this reduces the total risk and collateral requirement. /// @audit uint128 -> uint64 1593: /// @dev A strangle can only have only one of its leg tested at the same time, so this reduces the total risk and collateral requirement. /// @audit uint128 -> uint64 1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64); /// @audit uint128 -> uint64 1641: strangleRequired = _getRequiredCollateralSingleLegNoPartner(
Github: [262, 436, 496, 552, 612, 667, 715, 715, 718, 718, 731, 732, 1036, 1037, 1092, 1094, 1095, 1337, 1338, 1593, 1593, 1638, 1641]
File: ./contracts/PanopticFactory.sol /// @audit uint256 -> uint128 352: fullRangeLiquidity = uint128( /// @audit uint256 -> uint128 356: fullRangeLiquidity = uint128( /// @audit uint256 -> uint128 365: uint128 liquidity0 = uint128( /// @audit uint256 -> uint128 368: uint128 liquidity1 = uint128(
File: ./contracts/PanopticPool.sol /// @audit uint128 -> uint64 366: poolUtilization0 = uint64(balanceData.leftSlot()); /// @audit uint128 -> uint64 370: poolUtilization1 = uint64(balanceData.leftSlot() >> 64); /// @audit uint256 -> uint128 730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64)); /// @audit uint256 -> uint128 730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64)); /// @audit uint256 -> uint64 775: uint64(Math.min(effectiveLiquidityLimitX32, MAX_SPREAD)) /// @audit int256 -> int128 1167: .toRightSlot(int128(liquidationBonus0)) /// @audit int256 -> int128 1168: .toLeftSlot(int128(liquidationBonus1)); /// @audit int256 -> int128 1549: int256( /// @audit int256 -> int128 1558: int256( /// @audit int256 -> int128 1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) /// @audit int256 -> int128 1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); /// @audit uint256 -> uint128 1728: uint128( /// @audit uint256 -> uint128 1736: uint128( /// @audit uint256 -> uint128 1777: uint128( /// @audit uint256 -> uint128 1786: uint128( /// @audit uint256 -> uint128 1932: uint128( /// @audit uint256 -> uint128 1949: uint128( /// @audit uint256 -> uint128 1967: .toRightSlot(uint128(premiumAccumulatorsByLeg[_leg][0])) /// @audit uint256 -> uint128 1968: .toLeftSlot(uint128(premiumAccumulatorsByLeg[_leg][1]));
Github: [366, 370, 730, 730, 775, 1167, 1168, 1549, 1558, 1635, 1636, 1728, 1736, 1777, 1786, 1932, 1949, 1967, 1968]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit uint256 -> uint128 631: if ( /// @audit int256 -> int128 1201: /// mint the required amount in the Uniswap pool /// @audit int256 -> int128 1204: address(this), /// @audit int256 -> int128 1211: // amount0 The amount of token0 that was paid to mint the given amount of liquidity /// @audit int256 -> int128 1212: // amount1 The amount of token1 that was paid to mint the given amount of liquidity /// @audit int256 -> int128 1247: /// @notice Helper to collect amounts between msg.sender and Uniswap and also to update the Uniswap fees collected to date from the AMM. /// @audit int256 -> int128 1247: /// @notice Helper to collect amounts between msg.sender and Uniswap and also to update the Uniswap fees collected to date from the AMM. /// @audit int256 -> int128 1267: // When this is the case, we want to behave as if there are 0 fees, so we just rectify the values. /// @audit int256 -> int128 1267: // When this is the case, we want to behave as if there are 0 fees, so we just rectify the values. /// @audit uint256 -> uint64 1570:
Github: [631, 1201, 1204, 1211, 1212, 1247, 1247, 1267, 1267, 1570]
File: ./contracts/libraries/FeesCalc.sol /// @audit int256 -> int128 121: /// @notice Calculates the fee growth that has occurred (per unit of liquidity) in the AMM/Uniswap for an /// @audit int256 -> int128 122: /// option position's tick range.
File: ./contracts/libraries/Math.sol /// @audit uint256 -> uint160 179: return uint160((sqrtR >> 32) + (sqrtR % (1 << 32) == 0 ? 0 : 1)); /// @audit uint256 -> uint128 297: if ((downcastedInt = uint128(toDowncast)) != toDowncast) revert Errors.CastingError(); /// @audit uint256 -> uint128 303: if ((downcastedInt = uint128(toDowncast)) != toDowncast) { /// @audit int256 -> int128 319: if (!((downcastedInt = int128(toCast)) == toCast)) revert Errors.CastingError();
File: ./contracts/libraries/PanopticMath.sol /// @audit uint160 -> uint64 50: uint64 poolId = uint64(uint160(univ3pool) >> 112); /// @audit uint64 -> uint48 62: return (poolId & TICKSPACING_MASK) + (uint48(poolId) + 1); /// @audit uint256 -> uint248 102: uint248 updatedHash = uint248(existingHash) ^ /// @audit uint256 -> uint248 103: (uint248(uint256(keccak256(abi.encode(tokenId))))); /// @audit int256 -> int24 155: return int24(Math.sort(ticks)[cardinality / 2]); /// @audit uint256 -> uint24 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + /// @audit uint256 -> uint24 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + /// @audit uint256 -> uint24 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / /// @audit uint256 -> uint24 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / /// @audit uint256 -> uint40 183: if (block.timestamp >= uint256(uint40(medianData >> 216)) + period) { /// @audit int256 -> int24 195: (tickCumulative_last - tickCumulative_old) / /// @audit uint256 -> uint24 200: uint24 orderMap = uint24(medianData >> 192); /// @audit uint256 -> uint24 217: entry = int24(uint24(medianData >> (rank * 24))); /// @audit uint256 -> uint192 229: uint256(uint192(medianData << 24)) + /// @audit uint256 -> uint32 249: secondsAgos[i] = uint32(((i + 1) * twapWindow) / 20); /// @audit int56 -> int24 258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20)) /// @audit int256 -> int24 266: return int24(sortedTicks[10]); /// @audit int256 -> int24 378: ); /// @audit uint256 -> uint128 485: /// @notice Convert an amount of token0 into an amount of token1 given the sqrtPriceX96 in a Uniswap pool defined as sqrt(1/0)*2^96. /// @audit uint256 -> uint128 588: .getAmount1ForLiquidity(Math.getLiquidityForAmount0(tickLower, tickUpper, amount0)) /// @audit uint256 -> uint128 594: .getAmount0ForLiquidity(Math.getLiquidityForAmount1(tickLower, tickUpper, amount1)) /// @audit int256 -> int128 751: ) /// @audit int256 -> int128 756: /// @notice Haircut/clawback any premium paid by `liquidatee` on `positionIdList` over the protocol loss threshold during a liquidation. /// @audit int256 -> int128 857: if (haircut1 != 0) collateral1.exercise(_liquidatee, 0, 0, 0, int128(haircut1)); /// @audit int256 -> int128 860: for (uint256 i = 0; i < positionIdList.length; i++) { /// @audit uint256 -> uint128 900: ) /// @audit uint256 -> uint128 902: } /// @audit int256 -> int128 937: int128( /// @audit int256 -> int128 939: PanopticMath.convert0to1(uint256(balanceShortage), sqrtPriceX96) /// @audit int256 -> int128 955: int128( /// @audit int256 -> int128 957: PanopticMath.convert1to0(uint256(balanceShortage), sqrtPriceX96)
Github: [50, 62, 102, 103, 155, 178, 178, 179, 179, 183, 195, 200, 217, 229, 249, 258, 266, 378, 485, 588, 594, 751, 756, 857, 860, 900, 902, 937, 939, 955, 957]
File: ./contracts/types/LeftRight.sol /// @audit uint256 -> uint128 40: return uint128(LeftRightUnsigned.unwrap(self)); /// @audit int256 -> int128 47: return int128(LeftRightSigned.unwrap(self)); /// @audit uint256 -> uint128 69: uint256(uint128(LeftRightUnsigned.unwrap(self)) + right) /// @audit int256 -> int128 89: (int256(int128(LeftRightSigned.unwrap(self)) + right) & RIGHT_HALF_BIT_MASK) /// @audit uint256 -> uint128 102: return uint128(LeftRightUnsigned.unwrap(self) >> 128); /// @audit int256 -> int128 109: return int128(LeftRightSigned.unwrap(self) >> 128); /// @audit uint256 -> uint128 162: (uint128(LeftRightUnsigned.unwrap(z)) < uint128(LeftRightUnsigned.unwrap(x))) /// @audit uint256 -> uint128 162: (uint128(LeftRightUnsigned.unwrap(z)) < uint128(LeftRightUnsigned.unwrap(x))) /// @audit uint256 -> uint128 185: (uint128(LeftRightUnsigned.unwrap(z)) > uint128(LeftRightUnsigned.unwrap(x))) /// @audit uint256 -> uint128 185: (uint128(LeftRightUnsigned.unwrap(z)) > uint128(LeftRightUnsigned.unwrap(x))) /// @audit int256 -> int128 197: int128 left128 = int128(left); /// @audit int256 -> int128 202: int128 right128 = int128(right); /// @audit int256 -> int128 217: int128 left128 = int128(left256); /// @audit int256 -> int128 220: int128 right128 = int128(right256); /// @audit int256 -> int128 235: int128 left128 = int128(left256); /// @audit int256 -> int128 238: int128 right128 = int128(right256); /// @audit int256 -> int128 257: int128 left128 = int128(left256); /// @audit int256 -> int128 260: int128 right128 = int128(right256); /// @audit int256 -> int128 265: z.toRightSlot(int128(Math.max(right128, 0))).toLeftSlot( /// @audit int256 -> int128 266: int128(Math.max(left128, 0))
Github: [40, 47, 69, 89, 102, 109, 162, 162, 185, 185, 197, 202, 217, 220, 235, 238, 257, 260, 265, 266]
File: ./contracts/types/LiquidityChunk.sol /// @audit int256 -> int24 178: /// @param self The LiquidityChunk to get the upper tick from /// @audit int256 -> int24 187: /// @param self The LiquidityChunk to get the liquidity from /// @audit uint256 -> uint128 195:
File: ./contracts/types/TokenId.sol /// @audit uint256 -> uint64 89: return uint64(TokenId.unwrap(self)); /// @audit uint256 -> uint24 98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16)); /// @audit int256 -> int24 160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12))); /// @audit int256 -> int24 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); /// @audit uint256 -> uint48 521: if (uint48(chunkData >> (48 * i)) == uint48(chunkData >> (48 * j))) { /// @audit uint256 -> uint48 521: if (uint48(chunkData >> (48 * i)) == uint48(chunkData >> (48 * j))) {
Github: [89, 98, 160, 171, 521, 521]
</details><a name="L-22"></a>
unchecked
blockThese additions/multiplications may silently overflow because they're in unchecked blocks with no preceding value checks, which may lead to unexpected results.
<details> <summary> There are <b>72</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 202: 2230 + 203: (12500 * ratioTick) / 205: (7812 * ratioTick ** 2) / 207: (6510 * ratioTick ** 3) / 262: s_ITMSpreadFee = uint128((ITM_SPREAD_MULTIPLIER * _poolFee) / DECIMALS); 372: return s_poolAssets + s_inAMM; 403: assets * (DECIMALS - COMMISSION_FEE), 405: totalAssets() * DECIMALS 446: return (convertToShares(type(uint104).max) * DECIMALS) / (DECIMALS + COMMISSION_FEE); 463: shares * DECIMALS, 465: totalSupply * (DECIMALS - COMMISSION_FEE) 670: uint24(positionId.width(leg) * positionId.tickSpacing()), 731: .toRightSlot(int128((longAmounts.rightSlot() * fee) / DECIMALS_128)) 732: .toLeftSlot(int128((longAmounts.leftSlot() * fee) / DECIMALS_128)); 743: return int256((s_inAMM * DECIMALS) / totalAssets()); 796: min_sell_ratio + 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / 849: (BUYER_COLLATERAL_RATIO + 850: (BUYER_COLLATERAL_RATIO * (SATURATED_POOL_UTIL - utilization)) / 1037: /// @param optionOwner The owner of the option being burned and potentially exercised. 1093: /// @param shortAmount The amount of short options held. 1118: //compute total commission amount = commission rate + spread fee 1122: DECIMALS 1130: //////////////////////////////////////////////////////////////*/ 1132: /// @notice Get the collateral status/margin details of an account/user. 1370: // compute the collateral requirement depending on whether the position is ITM & out-of-range or ITM and in-range: 1374: ((atTick < tickLower) && (tokenType == 1)) || // strike ITM but out of range price < lowerTick for tokenType=1 1416: // position is in-the-money, collateral requirement = amountMoved*(1-SRC)*(scaleFactor-ratio)/(scaleFactor+1) + SCR*amountMoved 1424: /// @notice Calculate the required amount of collateral for leg 'index' for position 'tokenId' accounting for its partner leg. 1493: // compute required as amount*collateralRatio 1503: /// @dev may be higher than the requirement of non risk-partnered legs if the spread is very wide (risky) 1577: // narrower spreads will be very capital efficient (1/3 of non-partnered CR!), but 1578: // wider spreads (an uncommon position w/ high max loss) may not benefit from risk partnering 1644: positionSize,
Github: [202, 203, 205, 207, 262, 372, 403, 405, 446, 463, 465, 670, 731, 732, 743, 796, 797, 849, 850, 1037, 1093, 1118, 1122, 1130, 1132, 1370, 1374, 1416, 1424, 1493, 1503, 1577, 1578, 1644]
File: ./contracts/PanopticFactory.sol 300: maxSalt = uint256(salt) + loops; 323: salt = bytes32(uint256(salt) + 1); 392: tickLower = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing;
File: ./contracts/PanopticPool.sol 309: (uint256(block.timestamp) << 216) + 730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64)); 1329: return balanceCross >= Math.unsafeDivRoundingUp(thresholdCross * buffer, 10_000); 1348: Math.mulDiv(uint256(tokenData1.rightSlot()), 2 ** 96, sqrtPriceX96) + 1353: Math.mulDivRoundingUp(uint256(tokenData1.leftSlot()), 2 ** 96, sqrtPriceX96) + 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; 1550: ((premiumAccumulatorsByLeg[leg][0] - 1559: ((premiumAccumulatorsByLeg[leg][1] - 1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) 1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); 1729: (grossCurrent[0] * 1731: grossPremiumLast.rightSlot() * 1737: (grossCurrent[1] * 1739: grossPremiumLast.leftSlot() * 1768: uint256 accumulated0 = ((premiumAccumulators[0] - grossPremiumLast.rightSlot()) * 1770: uint256 accumulated1 = ((premiumAccumulators[1] - grossPremiumLast.leftSlot()) * 1779: (uint256(premiumOwed.rightSlot()) * settledTokens.rightSlot()) / 1788: (uint256(premiumOwed.leftSlot()) * settledTokens.leftSlot()) / 1820: totalLiquidity = accountLiquidities.rightSlot() + accountLiquidities.leftSlot(); 1935: (int256( 1936: grossPremiumLast.rightSlot() * 1940: _premiumAccumulatorsByLeg[_leg][0] * 1942: )) + int256(legPremia.rightSlot() * 2 ** 64), 1952: (int256( 1953: grossPremiumLast.leftSlot() * 1957: _premiumAccumulatorsByLeg[_leg][1] * 1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
Github: [309, 730, 1329, 1348, 1353, 1487, 1550, 1559, 1635, 1636, 1729, 1731, 1737, 1739, 1768, 1770, 1779, 1788, 1820, 1935, 1936, 1940, 1942, 1952, 1953, 1957, 1959]
File: ./contracts/SemiFungiblePositionManager.sol 404: uint256 amount1Owed, 872: LeftRightUnsigned[4] memory collectedByLeg, 1385: uint128 premium1X64_gross; 1395: .toUint128Capped(); 1398: .toUint128Capped(); 1409: /*////////////////////////////////////////////////////////////// 1420: /// @return accountLiquidities The amount of liquidity that has been shorted/added to the Uniswap contract (netLiquidity:removedLiquidity -> rightSlot:leftSlot) 1421: function getAccountLiquidity(
Github: [404, 872, 1385, 1395, 1398, 1409, 1420, 1421]
</details><a name="L-23"></a>
Taking 0
as a valid argument in Solidity without checks can lead to severe security issues. A historical example is the infamous 0x0
address bug where numerous tokens were lost. This happens because 0 can be interpreted as an uninitialized address
, leading to transfers to the 0x0 address, effectively burning tokens. Moreover, 0
as a denominator in division operations would cause a runtime exception. It's also often indicative of a logical error in the caller's code. It's important to always validate input and handle edge cases like 0
appropriately. Use require()
statements to enforce conditions and provide clear error messages to facilitate debugging and safer code.
File: ./contracts/PanopticPool.sol 893: _validatePositionList(user, positionIdList, 0); 1023: _validatePositionList(liquidatee, positionIdList, 0); 1153: _validatePositionList(msg.sender, positionIdListLiquidator, 0); 1191: _validatePositionList(msg.sender, positionIdListExercisor, 0); 1227: _burnAllOptionsFrom(account, 0, 0, COMMIT_LONG_SETTLED, touchedId); 1592: _validatePositionList(owner, positionIdList, 0);
Github: [893, 1023, 1153, 1191, 1227, 1592]
File: ./contracts/SemiFungiblePositionManager.sol 729: } /// @notice When a position is minted or burnt in-the-money (ITM) we are *not* 100% token0 or 100% token1: we have a mix of both tokens.
Github: [729]
File: ./contracts/tokens/ERC1155Minimal.sol 220: emit TransferSingle(msg.sender, address(0), to, id, amount); 239: emit TransferSingle(msg.sender, from, address(0), id, amount);
</details>File: ./contracts/tokens/ERC20Minimal.sol 130: emit Transfer(address(0), to, amount); 145: emit Transfer(from, address(0), amount);
<a name="L-24"></a>
4.8.3
)The project is using a vulnerable version. See the list of OZ vulnerabilities here.
There is one instance of this issue:
Global finding
Github: [Global]
<a name="L-25"></a>
There are <b>2</b> instances of this issue:
File: ./contracts/PanopticFactory.sol /// @audit missing zero check for `_owner` 134: function initialize(address _owner) public {
Github: [134]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit missing zero check for `token0` /// @audit missing zero check for `token1` 361: // if poolId == 0, we have a bit on the left set if it was initialized, so this will still return properly if (s_AddrToPoolIdData[univ3pool] != 0) return;
Github: [361]
<a name="L-26"></a>
abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such as keccak256()
Use abi.encode()
instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456)
=> 0x123456
=> abi.encodePacked(0x1,0x23456)
, but abi.encode(0x123,0x456)
=> 0x0...1230...456
). "Unless there is a compelling reason, abi.encode
should be preferred". If there is only one argument to abi.encodePacked()
it can often be cast to bytes()
or bytes32()
instead.
If all arguments are strings and or bytes, bytes.concat()
should be used instead
There are <b>2</b> instances of this issue:
File: ./contracts/SemiFungiblePositionManager.sol 1431: keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper)) 1544: keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper))
<a name="L-27"></a>
Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds.
There are <b>7</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 864: function delegate( address delegator, address delegatee, uint256 assets ) external onlyPanopticPool { 898: /// @notice Refunds delegated tokens back to the protocol /// @dev Assumes that `delegatee` has enough money to pay for the refund 907: /// @notice Revoke previously delegated shares. The opposite of 'delegate'. /// @param delegator The delegator to send shares *to* (because we are revoking - opposite when we delegate). 917: ┌────────┐ │Panoptic│ │Pool │ └────┬───┘ │(1) convert 'assets' to shares (this ERC20 contract) 980: _transferFrom(refundee, refunder, convertToShares(uint256(-assets))); } } } /*////////////////////////////////////////////////////////////// 1002: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount; // constrict premium to only assets not belonging to PLPs (i.e premium paid by sellers or collected from the pool earlier) 1051: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount; // add premium to be paid/collected on position close int256 tokenToPay = -realizedPremium;
Github: [864, 898, 907, 917, 980, 1002, 1051]
<a name="L-28"></a>
decimals()
is not a part of the ERC-20 standardThe decimals()
function is not a part of the ERC-20 standard, and was added later as an optional extension. As such, some valid ERC20 tokens do not support this interface, so it is unsafe to blindly cast all tokens to this interface, and then call this function.
There is one instance of this issue:
File: ./contracts/libraries/InteractionHelper.sol 110: try IERC20Metadata(token).decimals() returns (uint8 _decimals) {
Github: [110]
<a name="L-29"></a>
Initializers could be front-run, allowing an attacker to either set their own values, take ownership of the contract, and in the best case forcing a re-deployment
There is one instance of this issue:
File: ./contracts/PanopticFactory.sol 134: function initialize(address _owner) public {
Github: [134]
<a name="L-30"></a>
symbol()
is not a part of the ERC-20 standardThe symbol()
function is not a part of the ERC-20 standard, and was added later as an optional extension. As such, some valid ERC20 tokens do not support this interface, so it is unsafe to blindly cast all tokens to this interface, and then call this function.
There are <b>3</b> instances of this issue:
File: ./contracts/libraries/InteractionHelper.sol 60: try IERC20Metadata(token0).symbol() returns (string memory _symbol) { 65: try IERC20Metadata(token1).symbol() returns (string memory _symbol) { 97: try IERC20Metadata(token).symbol() returns (string memory tokenSymbol) {
<a name="L-31"></a>
In Solidity versions 0.8.13 and 0.8.14, a known optimizer bug presents potential risks when a variable is used in a separate assembly block from the one in which it was stored. Specifically, the 'mstore' operation could be optimized out due to this bug, leading to the use of uninitialized memory. Although the current code does not exhibit this risky pattern of execution, it does utilize 'mstore' within assembly blocks, which introduces a vulnerability risk for future code modifications. As a preventative measure, it is advisable to avoid the usage of the afflicted Solidity versions, 0.8.13 and 0.8.14. Instead, consider utilizing a version that is not impacted by this optimizer bug to prevent potential memory initialization issues in your smart contract.
<details> <summary> There are <b>30</b> instances (click to show): </summary>File: ./contracts/libraries/Math.sol 353: assembly ("memory-safe") { 362: assembly ("memory-safe") { 379: assembly ("memory-safe") { 383: assembly ("memory-safe") { 393: assembly ("memory-safe") { 398: assembly ("memory-safe") { 404: assembly ("memory-safe") { 467: assembly ("memory-safe") { 476: assembly ("memory-safe") { 493: assembly ("memory-safe") { 497: assembly ("memory-safe") { 503: assembly ("memory-safe") { 530: assembly ("memory-safe") { 539: assembly ("memory-safe") { 556: assembly ("memory-safe") { 560: assembly ("memory-safe") { 566: assembly ("memory-safe") { 607: assembly ("memory-safe") { 616: assembly ("memory-safe") { 633: assembly ("memory-safe") { 637: assembly ("memory-safe") { 643: assembly ("memory-safe") { 684: assembly ("memory-safe") { 693: assembly ("memory-safe") { 710: assembly ("memory-safe") { 714: assembly ("memory-safe") { 720: assembly ("memory-safe") { 739: assembly ("memory-safe") {
Github: [353, 362, 379, 383, 393, 398, 404, 467, 476, 493, 497, 503, 530, 539, 556, 560, 566, 607, 616, 633, 637, 643, 684, 693, 710, 714, 720, 739]
</details>File: ./contracts/libraries/SafeTransferLib.sol 24: assembly ("memory-safe") { 55: assembly ("memory-safe") {
<a name="L-32"></a>
In Solidity, for 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 loops can lead to DoS attacks: Nested for loops can become exceptionally gas expensive and should be used sparingly.
There are <b>3</b> instances of this issue:
File: ./contracts/multicall/Multicall.sol 12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i = 0; i < data.length; ) {
Github: [12]
File: ./contracts/tokens/ERC1155Minimal.sol 130: function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) public virtual { if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized(); // Storing these outside the loop saves ~15 gas per iteration. uint256 id; uint256 amount; for (uint256 i = 0; i < ids.length; ) { 178: function balanceOfBatch( address[] calldata owners, uint256[] calldata ids ) public view returns (uint256[] memory balances) { balances = new uint256[](owners.length); // Unchecked because the only math done is incrementing // the array index counter which cannot possibly overflow. unchecked { for (uint256 i = 0; i < owners.length; ++i) {
<a name="L-33"></a>
In Solidity, if two array parameters are used within a function and one of their lengths is used as the for-loop range, it's essential to have a length check. If the arrays are not the same length, you could experience out-of-bounds errors or unintended behavior. This could happen if the function tries to access an index that doesn't exist in the shorter array.
Resolution: Always validate that the lengths of both arrays are the same before entering the loop. Add a require statement at the start of the function to check that both arrays are of equal length. This helps maintain the integrity of the function and prevents potential errors due to differing array lengths. This requirement ensures the function fails early if the arrays don't match, rather than failing unpredictably or silently during execution.
There are <b>4</b> instances of this issue:
File: ./contracts/SemiFungiblePositionManager.sol 583: // transfer the token (note that all state updates are completed before reentrancy is possible)
Github: [583]
File: ./contracts/libraries/PanopticMath.sol 770: TokenId[] memory positionIdList, LeftRightSigned[4][] memory premiasByLeg, LeftRightSigned collateralRemaining, CollateralTracker collateral0, CollateralTracker collateral1, uint160 sqrtPriceX96Final, mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens ) external returns (int256, int256) { unchecked { // get the amount of premium paid by the liquidatee LeftRightSigned longPremium; for (uint256 i = 0; i < positionIdList.length; ++i) {
Github: [770]
File: ./contracts/tokens/ERC1155Minimal.sol 130: function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) public virtual { if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized(); // Storing these outside the loop saves ~15 gas per iteration. uint256 id; uint256 amount; for (uint256 i = 0; i < ids.length; ) { 178: function balanceOfBatch( address[] calldata owners, uint256[] calldata ids ) public view returns (uint256[] memory balances) { balances = new uint256[](owners.length); // Unchecked because the only math done is incrementing // the array index counter which cannot possibly overflow. unchecked { for (uint256 i = 0; i < owners.length; ++i) {
<a name="L-34"></a>
Libraries in Solidity are meant to be static collections of functions. They are deployed once and their code is reused by contracts through DELEGATECALL, which executes the library's code in the context of the calling contract. Public or external functions in libraries can be misleading because libraries are not supposed to maintain state or have external interactions in the way contracts do. Designing libraries with these kinds of functions goes against their intended purpose and can lead to confusion or misuse. For state management or external interactions, using contracts instead of libraries is more appropriate. Libraries should focus on utility functions that can operate on the data passed to them without side effects, enhancing code reuse and gas efficiency.
<details> <summary> There are <b>13</b> instances (click to show): </summary>File: ./contracts/libraries/FeesCalc.sol 50: ) external view returns (int256 value0, int256 value1) { for (uint256 k = 0; k < positionIdList.length; ) { TokenId tokenId = positionIdList[k]; uint128 positionSize = userBalance[tokenId].rightSlot(); uint256 numLegs = tokenId.countLegs(); 104: // extract the amount of AMM fees collected within the liquidity chunk` // note: the fee variables are *per unit of liquidity*; so more "rate" variables ( uint256 ammFeesPerLiqToken0X128,
File: ./contracts/libraries/InteractionHelper.sol 24: function doApprovals( SemiFungiblePositionManager sfpm, CollateralTracker ct0, CollateralTracker ct1, address token0, address token1 ) external { 48: function computeName( address token0, address token1, bool isToken0, uint24 fee, string memory prefix ) external view returns (string memory) { 91: function computeSymbol( address token, string memory prefix ) external view returns (string memory) { 107: function computeDecimals(address token) external view returns (uint8) {
File: ./contracts/libraries/PanopticMath.sol 75: function numberOfLeadingHexZeros(address addr) external pure returns (uint256) { unchecked { 125: function computeMedianObservedPrice( IUniswapV3Pool univ3pool, uint256 observationIndex, uint256 observationCardinality, uint256 cardinality, uint256 period ) external view returns (int24) { unchecked { 168: function computeInternalMedian( uint256 observationIndex, uint256 observationCardinality, uint256 period, uint256 medianData, IUniswapV3Pool univ3pool ) external view returns (int24 medianTick, uint256 updatedMedianData) { unchecked { 241: function twapFilter(IUniswapV3Pool univ3pool, uint32 twapWindow) external view returns (int24) { uint32[] memory secondsAgos = new uint32[](20); 653: LeftRightUnsigned tokenData1, uint160 sqrtPriceX96Twap, uint160 sqrtPriceX96Final, LeftRightSigned netExchanged, LeftRightSigned premia ) external pure returns (int256 bonus0, int256 bonus1, LeftRightSigned) { unchecked { // compute bonus as min(collateralBalance/2, required-collateralBalance) 770: TokenId[] memory positionIdList, LeftRightSigned[4][] memory premiasByLeg, LeftRightSigned collateralRemaining, CollateralTracker collateral0, CollateralTracker collateral1, uint160 sqrtPriceX96Final, mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens ) external returns (int256, int256) { unchecked { // get the amount of premium paid by the liquidatee LeftRightSigned longPremium; 919: LeftRightSigned refundValues, int24 atTick, CollateralTracker collateral0, CollateralTracker collateral1 ) external view returns (LeftRightSigned) { uint160 sqrtPriceX96 = Math.getSqrtRatioAtTick(atTick); unchecked { // if the refunder lacks sufficient token0 to pay back the refundee, have them pay back the equivalent value in token1
Github: [75, 125, 168, 241, 653, 770, 919]
</details><a name="L-35"></a>
_safeMint()
should be used rather than _mint()
wherever possible_mint()
is discouraged in favor of _safeMint()
which ensures that the recipient is either an EOA or implements IERC721Receiver
.
There is one instance of this issue:
File: ./contracts/SemiFungiblePositionManager.sol 515: _mint(msg.sender, TokenId.unwrap(tokenId), positionSize);
Github: [515]
<a name="L-36"></a>
Not casting an integer as a string before passing it into abi.encode can result in unintended behaviour. lets say '1' being encoded as '1' it will be encoded as char(1) which is the 'start of heading' control character or id of 60 would be encoded as '<'. This is may not be intended. To rectify this, simply cast the Id as a string with string(id) or ideally use solmate's libString library (toString)
<details> <summary> There are <b>35</b> instances (click to show): </summary>File: ./contracts/PanopticPool.sol 462: abi.encodePacked( 462: abi.encodePacked( 462: abi.encodePacked( 1643: abi.encodePacked( 1643: abi.encodePacked( 1643: abi.encodePacked( 1674: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1674: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1674: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1856: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1856: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1856: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg))
Github: [462, 462, 462, 1643, 1643, 1643, 1674, 1674, 1674, 1856, 1856, 1856]
File: ./contracts/SemiFungiblePositionManager.sol 633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0) 633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0) 633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0) 638: if (LeftRightUnsigned.unwrap(fromLiq) != liquidityChunk.liquidity()) 638: if (LeftRightUnsigned.unwrap(fromLiq) != liquidityChunk.liquidity()) 638: if (LeftRightUnsigned.unwrap(fromLiq) != liquidityChunk.liquidity()) 997: uint128 chunkLiquidity = liquidityChunk.liquidity(); 997: uint128 chunkLiquidity = liquidityChunk.liquidity(); 997: uint128 chunkLiquidity = liquidityChunk.liquidity(); 1181: /// @dev note that "moved" means: mint in Uniswap and move tokens from msg.sender. 1181: /// @dev note that "moved" means: mint in Uniswap and move tokens from msg.sender. 1450: address univ3pool, 1450: address univ3pool, 1450: address univ3pool, 1485: netLiquidity 1485: netLiquidity 1485: netLiquidity 1570: 1570: 1570:
Github: [633, 633, 633, 638, 638, 638, 997, 997, 997, 1181, 1181, 1450, 1450, 1450, 1485, 1485, 1485, 1570, 1570, 1570]
</details>File: ./contracts/libraries/PanopticMath.sol 881: tokenId.width(0), 881: tokenId.width(0), 881: tokenId.width(0),
<a name="NC-1"></a>
Assembly blocks take a lot more time to audit than normal Solidity code, and often have gotchas and side-effects that the Solidity versions of the same code do not. Consider adding more comments explaining what is being done in every step of the assembly code, and describe why assembly is being used instead of Solidity.
<details> <summary> There are <b>31</b> instances (click to show): </summary>File: ./contracts/libraries/Math.sol 353: assembly ("memory-safe") { 362: assembly ("memory-safe") { 379: assembly ("memory-safe") { 383: assembly ("memory-safe") { 393: assembly ("memory-safe") { 398: assembly ("memory-safe") { 404: assembly ("memory-safe") { 467: assembly ("memory-safe") { 476: assembly ("memory-safe") { 493: assembly ("memory-safe") { 497: assembly ("memory-safe") { 503: assembly ("memory-safe") { 530: assembly ("memory-safe") { 539: assembly ("memory-safe") { 556: assembly ("memory-safe") { 560: assembly ("memory-safe") { 566: assembly ("memory-safe") { 607: assembly ("memory-safe") { 616: assembly ("memory-safe") { 633: assembly ("memory-safe") { 637: assembly ("memory-safe") { 643: assembly ("memory-safe") { 684: assembly ("memory-safe") { 693: assembly ("memory-safe") { 710: assembly ("memory-safe") { 714: assembly ("memory-safe") { 720: assembly ("memory-safe") { 739: assembly ("memory-safe") {
Github: [353, 362, 379, 383, 393, 398, 404, 467, 476, 493, 497, 503, 530, 539, 556, 560, 566, 607, 616, 633, 637, 643, 684, 693, 710, 714, 720, 739]
File: ./contracts/libraries/SafeTransferLib.sol 24: assembly ("memory-safe") { 55: assembly ("memory-safe") {
File: ./contracts/multicall/Multicall.sol 25: assembly ("memory-safe") {
Github: [25]
</details><a name="NC-2"></a>
abi.encodePacked()
should be replaced with bytes.concat()
Solidity version 0.8.4 introduces bytes.concat()
, which can be used to replace abi.encodePacked()
on bytes/strings. It can make the intended operation clearer, leading to less reviewer confusion.
File: ./contracts/PanopticPool.sol 462: abi.encodePacked( 1643: abi.encodePacked( 1674: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1856: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg))
Github: [462, 1643, 1674, 1856]
File: ./contracts/SemiFungiblePositionManager.sol 612: abi.encodePacked( 621: abi.encodePacked( 975: abi.encodePacked( 1151: abi.encodePacked( 1431: keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper)) 1459: abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper) 1544: keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper))
Github: [612, 621, 975, 1151, 1431, 1459, 1544]
File: ./contracts/libraries/PanopticMath.sol 879: abi.encodePacked(
Github: [879]
</details><a name="NC-3"></a>
function foo(address x, address)
-> function foo(address x, address /* y */)
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 392: function maxDeposit(address) external pure returns (uint256 maxAssets) { return type(uint104).max; 444: function maxMint(address) external view returns (uint256 maxShares) { unchecked {
<a name="NC-4"></a>
Function parameters in Solidity are passed by value, meaning they are essentially local copies. Mutating them can lead to confusion and errors because the changes don't persist outside the function. By keeping function parameters immutable, you ensure clarity in code behavior, preventing unintended side-effects. If you need to modify a value based on a parameter, use a local variable inside the function, leaving the original parameter unaltered. By adhering to this practice, you maintain a clear distinction between input data and the internal processing logic, improving code readability and reducing the potential for bugs.
There are <b>4</b> instances of this issue:
File: ./contracts/CollateralTracker.sol /// @audit `utilization` 751: function _sellCollateralRatio( int256 utilization ) internal view returns (uint256 sellCollateralRatio) { // the sell ratio is on a straight line defined between two points (x0,y0) and (x1,y1): // (x0,y0) = (targetPoolUtilization,min_sell_ratio) and // (x1,y1) = (saturatedPoolUtilization,max_sell_ratio) // the line's formula: y = a * (x - x0) + y0, where a = (y1 - y0) / (x1 - x0) /** SELL COLLATERAL RATIO ^ | max ratio = 100% 100% - | _------ | _-¯ | _-¯ 20% - |---------¯ | . . . +---------+-------+-+---> POOL_ 50% 90% 100% UTILIZATION */ uint256 min_sell_ratio = SELLER_COLLATERAL_RATIO; /// if utilization is less than zero, this is the calculation for a strangle, which gets 2x the capital efficiency at low pool utilization /// at 0% utilization, strangle legs do not compound efficiency if (utilization < 0) { unchecked { min_sell_ratio /= 2; utilization = -utilization; /// @audit `poolUtilization` 1608: // A strangle is an options strategy in which the investor holds a position // in both a call and a put option with different strike prices, // but with the same expiration date and underlying asset. /// collateral requirement is for short strangles depicted: /** Put side of a short strangle, BPR = 100% - (100% - SCR/2)*(price/strike) BUYING POWER REQUIREMENT ^ . | <- ITM . OTM -> 100% - |--__ . | ¯¯--__ . | ¯¯--__ . SCR/2 - | ¯¯--______ <------ base collateral is half that of a single-leg +--------------------+---> current 0 strike price */ unchecked { // A negative pool utilization is used to denote a position which is a strangle // at low pool utilization's strangle legs are evaluated at 2x capital efficiency uint64 poolUtilization0 = uint64(poolUtilization); uint64 poolUtilization1 = uint64(poolUtilization >> 64); // add 1 to handle poolUtilization = 0 poolUtilization = uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) + (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64); return strangleRequired = _getRequiredCollateralSingleLegNoPartner( tokenId, index,
File: ./contracts/PanopticFactory.sol /// @audit `salt` 290: function minePoolAddress( bytes32 salt, uint256 loops, uint256 minTargetRarity ) external view returns (bytes32 bestSalt, uint256 highestRarity) { // Start at the given 'salt' value (a checkpoint used to continue mining across multiple calls) // Runs until 'bestSalt' reaches 'minTargetRarity' or for 'loops', whichever comes first uint256 maxSalt; unchecked { maxSalt = uint256(salt) + loops; } for (; uint256(salt) < maxSalt; ) { uint256 rarity = PanopticMath.numberOfLeadingHexZeros( POOL_REFERENCE.predictDeterministicAddress(salt) ); if (rarity > highestRarity) { // found a more rare address at this nonce highestRarity = rarity; bestSalt = salt; } if (rarity >= minTargetRarity) { // desired target met highestRarity = rarity; bestSalt = salt; break; } unchecked { // increment the nonce of `currentSalt` (lower 96 bits) salt = bytes32(uint256(salt) + 1);
Github: [290]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `tokenId` 708: (totalMoved, collectedByLeg, itmAmounts) = _createPositionInAMM( univ3pool, tokenId, positionSize, isBurn ); if (tickLimitLow > tickLimitHigh) { // if the in-the-money amount is not zero (i.e. positions were minted ITM) and the user did provide tick limits LOW > HIGH, then swap necessary amounts if ((LeftRightSigned.unwrap(itmAmounts) != 0)) { totalMoved = swapInAMM(univ3pool, itmAmounts).add(totalMoved); } (tickLimitLow, tickLimitHigh) = (tickLimitHigh, tickLimitLow);
Github: [708]
<a name="NC-5"></a>
address
/ID mappings can be combined into a single mapping
of an address
/ID to a struct
, for readabilityWell-organized data structures make code reviews easier, which may lead to fewer bugs. Consider combining related mappings into mappings to structs, so it's clear what data is related
There are <b>4</b> instances of this issue:
File: ./contracts/PanopticPool.sol 238: mapping(address account => mapping(TokenId tokenId => mapping(uint256 leg => LeftRightUnsigned premiaGrowth)))
Github: [238]
File: ./contracts/tokens/ERC1155Minimal.sol 66: mapping(address account => mapping(uint256 tokenId => uint256 balance)) public balanceOf; 71: mapping(address owner => mapping(address operator => bool approvedForAll))
File: ./contracts/tokens/ERC20Minimal.sol 39: mapping(address owner => mapping(address spender => uint256 allowance)) public allowance;
Github: [39]
<a name="NC-6"></a>
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.
<details> <summary> There are <b>70</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 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)) 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))); 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))); 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))) + 1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
Github: [715, 715, 718, 718, 1003, 1028, 1029, 1029, 1052, 1084, 1085, 1085, 1121, 1184, 1329, 1330, 1585, 1586, 1637, 1638]
File: ./contracts/PanopticFactory.sol 323: salt = bytes32(uint256(salt) + 1);
Github: [323]
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) 1149: uint256(int256(uint256(_delegations.leftSlot())) + liquidationBonus1) 1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) 1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));
Github: [313, 314, 730, 730, 1144, 1149, 1635, 1636]
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))
Github: [1169, 1172, 1176, 1177, 1214, 1215, 1241, 1242]
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)); 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))))); 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / 183: if (block.timestamp >= uint256(uint40(medianData >> 216)) + period) { 217: entry = int24(uint24(medianData >> (rank * 24))); 229: uint256(uint192(medianData << 24)) + 230: uint256(uint24(lastObservedTick)); 258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20)) 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) 693: int256 balance0 = int256(uint256(tokenData0.rightSlot())) - 695: int256 balance1 = int256(uint256(tokenData1.rightSlot())) -
Github: [50, 51, 103, 178, 179, 183, 217, 229, 230, 258, 377, 693, 695]
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();
Github: [27, 69, 89, 196, 201]
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));
Github: [78, 79, 109, 126, 173, 182]
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)); 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36))
Github: [98, 160, 171, 195, 320]
</details><a name="NC-7"></a>
Doing so will significantly increase centralization, but will help to prevent hackers from using stolen tokens.
There are <b>7</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
Github: [36]
File: ./contracts/PanopticFactory.sol 26: contract PanopticFactory is Multicall {
Github: [26]
File: ./contracts/PanopticPool.sol 27: contract PanopticPool is ERC1155Holder, Multicall {
Github: [27]
File: ./contracts/SemiFungiblePositionManager.sol 82: /// @notice Emitted when a position is destroyed/burned /// @dev Recipient is used to track whether it was burned directly by the user or through an option contract /// @param recipient The address of the user who burned the position /// @param tokenId The tokenId of the burned position /// @param positionSize The number of contracts burnt, expressed in terms of the asset event TokenizedPositionBurnt( address indexed recipient, TokenId indexed tokenId, uint128 positionSize ); /// @notice Emitted when a position is created/minted /// @dev Recipient is used to track whether it was minted directly by the user or through an option contract /// @param caller the caller who created the position. In 99% of cases `caller` == `recipient`. /// @param tokenId The tokenId of the minted position /// @param positionSize The number of contracts minted, expressed in terms of the asset event TokenizedPositionMinted( address indexed caller, TokenId indexed tokenId, uint128 positionSize ); /*////////////////////////////////////////////////////////////// TYPES //////////////////////////////////////////////////////////////*/ // Used for safecasting using Math for uint256; using Math for int256; // Packs the pool address and reentrancy lock into a single slot // `locked` can be initialized to false because the pool address makes the slot nonzero // false = unlocked, true = locked struct PoolAddressAndLock {
Github: [82]
File: ./contracts/multicall/Multicall.sol 8: abstract contract Multicall {
Github: [8]
File: ./contracts/tokens/ERC1155Minimal.sol 11: abstract contract ERC1155 {
Github: [11]
File: ./contracts/tokens/ERC20Minimal.sol 9: abstract contract ERC20Minimal {
Github: [9]
<a name="NC-8"></a>
Consider adding Pausable
to the following contracts so it's possible to stop them in case of an emergency.
There are <b>4</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
Github: [36]
File: ./contracts/PanopticFactory.sol 26: contract PanopticFactory is Multicall {
Github: [26]
File: ./contracts/PanopticPool.sol 27: contract PanopticPool is ERC1155Holder, Multicall {
Github: [27]
File: ./contracts/SemiFungiblePositionManager.sol 82: /// @notice Emitted when a position is destroyed/burned
Github: [82]
<a name="NC-9"></a>
Formal verification is the act of proving or disproving the correctness of intended algorithms underlying a system with respect to a certain formal specification/property/invariant, using formal methods of mathematics.
Some tools that are currently available to perform these tests on smart contracts are SMTChecker and Certora Prover.
There is one instance of this issue:
Global finding
Github: [Global]
<a name="NC-10"></a>
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 nicer user experience to require() that the length of the array is below some reasonable maximum, so that the user doesn't have to use up a full transaction's gas only to see that the transaction reverts.
There are <b>7</b> instances of this issue:
File: ./contracts/PanopticPool.sol /// @audit consider length check for `positionIdList` 802: for (uint256 i = 0; i < positionIdList.length; ) {
Github: [802]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit consider length check for `ids` 575: for (uint256 i = 0; i < ids.length; ) {
Github: [575]
File: ./contracts/libraries/FeesCalc.sol /// @audit consider length check for `positionIdList` 51: for (uint256 k = 0; k < positionIdList.length; ) {
Github: [51]
File: ./contracts/libraries/PanopticMath.sol /// @audit consider length check for `positionIdList` 781: for (uint256 i = 0; i < positionIdList.length; ++i) {
Github: [781]
File: ./contracts/multicall/Multicall.sol /// @audit consider length check for `data` 14: for (uint256 i = 0; i < data.length; ) {
Github: [14]
File: ./contracts/tokens/ERC1155Minimal.sol /// @audit consider length check for `ids` 143: for (uint256 i = 0; i < ids.length; ) { /// @audit consider length check for `owners` 187: for (uint256 i = 0; i < owners.length; ++i) {
<a name="NC-11"></a>
constant
s when passing zero as a function argumentPassing zero as a function argument can sometimes result in a security issue (e.g. passing zero as the slippage parameter). Consider using a constant
variable with a descriptive name, so it's clear that the argument is intentionally being used, and for the right reasons.
File: ./contracts/CollateralTracker.sol 712: LeftRightSigned .wrap(0)
Github: [712]
File: ./contracts/PanopticPool.sol 634: revert Errors.InvalidTokenIdParameter(0); 654: s_positionBalance[msg.sender][tokenId] = LeftRightUnsigned .wrap(0) 762: s_options[msg.sender][tokenId][leg] = LeftRightUnsigned .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 .wrap(0) 1191: _validatePositionList(msg.sender, positionIdListExercisor, 0); 1227: _burnAllOptionsFrom(account, 0, 0, COMMIT_LONG_SETTLED, touchedId); 1545: premiaByLeg[leg] = LeftRightSigned .wrap(0) .toRightSlot( 1567: premiaByLeg[leg] = LeftRightSigned.wrap(0).sub(premiaByLeg[leg]); 1592: _validatePositionList(owner, positionIdList, 0); TokenId tokenId = positionIdList[positionIdList.length - 1]; 1614: accumulatedPremium = LeftRightUnsigned .wrap(0) .toRightSlot(premiumAccumulator0) 1633: LeftRightSigned realizedPremia = LeftRightSigned .wrap(0) .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) 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( address(s_univ3pool), address(this), tokenId.tokenType(leg), liquidityChunk.tickLower(), liquidityChunk.tickUpper(), type(int24).max, 0 ); 1725: s_grossPremiumLast[chunkKey] = LeftRightUnsigned .wrap(0) .toRightSlot( 1774: LeftRightUnsigned .wrap(0) .toRightSlot( 1929: ? LeftRightUnsigned .wrap(0) .toRightSlot( 1934: Math.max( (int256( grossPremiumLast.rightSlot() * totalLiquidityBefore ) - int256( _premiumAccumulatorsByLeg[_leg][0] * positionLiquidity )) + int256(legPremia.rightSlot() * 2 ** 64), 0 ) 1951: Math.max( (int256( grossPremiumLast.leftSlot() * totalLiquidityBefore ) - int256( _premiumAccumulatorsByLeg[_leg][1] * positionLiquidity )) + int256(legPremia.leftSlot()) * 2 ** 64, 0 ) 1965: : LeftRightUnsigned .wrap(0) .toRightSlot(uint128(premiumAccumulatorsByLeg[_leg][0]))
Github: [634, 654, 762, 861, 870, 893, 1023, 1153, 1165, 1191, 1227, 1545, 1567, 1592, 1614, 1633, 1639, 1640, 1707, 1725, 1774, 1929, 1934, 1951, 1965]
File: ./contracts/SemiFungiblePositionManager.sol 664: /// @notice and then forwards the minting/burning/swapping to another internal helper functions which perform the AMM pool actions. 666: /// │mintTokenizedPosition()├───┐ 862: /// @return itmAmounts the amount of tokens swapped due to legs being in-the-money 878: uint256 amount1; uint256 numLegs = tokenId.countLegs(); 1063: └──┴───────┴──► └──────┘ Uniswap v3 msg.sender 1197: payer: msg.sender }) ); /// mint the required amount in the Uniswap pool 1207: liquidityChunk.liquidity(), mintdata 1244: } } /// @notice Helper to collect amounts between msg.sender and Uniswap and also to update the Uniswap fees collected to date from the AMM. 1266: // This is because the stored feesBase is rounded up, and the current feesBase is rounded down. // When this is the case, we want to behave as if there are 0 fees, so we just rectify the values. 1334: // explains how we get from the premium per liquidity (calculated here) to the total premia collected and the multiplier // as well as how the value of VEGOID affects the premia 1416: /// @param owner The address of the account that is queried /// @param tokenType The tokenType of the position (the token it started as) 1431: keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper))
Github: [664, 666, 862, 878, 1063, 1197, 1207, 1244, 1266, 1334, 1416, 1431]
File: ./contracts/libraries/FeesCalc.sol 118: .toLeftSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken1X128, liquidity)))); } /// @notice Calculates the fee growth that has occurred (per unit of liquidity) in the AMM/Uniswap for an
Github: [118]
File: ./contracts/libraries/PanopticMath.sol 601: /// @notice Compute the amount of funds that are moved to and removed from the Panoptic Pool. 673: tokenData1, 0, sqrtPriceX96Twap ); uint256 bonusCross = Math.min(balanceCross / 2, thresholdCross - balanceCross); 695: int256 balance1 = int256(uint256(tokenData1.rightSlot())) - Math.max(premia.leftSlot(), 0); 698: int256 paid0 = bonus0 + int256(netExchanged.rightSlot()); int256 paid1 = bonus1 + int256(netExchanged.leftSlot()); 750: int128(balance1 - paid1) 792: int256 collateralDelta1 = -Math.min(collateralRemaining.leftSlot(), 0); int256 haircut0; 794: int256 haircut1; // if the premium in the same token is not enough to cover the loss and there is a surplus of the other token, 857: if (haircut1 != 0) collateral1.exercise(_liquidatee, 0, 0, 0, int128(haircut1)); 860: for (uint256 i = 0; i < positionIdList.length; i++) { 882: tokenId.tokenType(0) 883: ) ); 886: // The long premium is not commited to storage during the liquidation, so we add the entire adjusted amount 890: uint128(-_premiasByLeg[i][leg].rightSlot()) - settled0 ); 894: uint128(-_premiasByLeg[i][leg].leftSlot()) - settled1 ); 899: uint128(settled1) 935: .toRightSlot(int128(refundValues.rightSlot() - balanceShortage)) 953: .toLeftSlot(int128(refundValues.leftSlot() - balanceShortage)) .toRightSlot(
Github: [601, 673, 695, 698, 750, 792, 794, 857, 860, 882, 883, 886, 890, 894, 899, 935, 953]
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(
</details>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);
<a name="NC-12"></a>
The use of fixed decimal values such as 1e18 or 1e8 in Solidity contracts can lead to inaccuracies, bugs, and vulnerabilities, particularly when interacting with tokens having different decimal configurations. Not all ERC20 tokens follow the standard 18 decimal places, and assumptions about decimal places can lead to miscalculations.
Resolution: Always retrieve and use the decimals() function from the token contract itself when performing calculations involving token amounts. This ensures that your contract correctly handles tokens with any number of decimal places, mitigating the risk of numerical errors or under/overflows that could jeopardize contract integrity and user funds.
There is one instance of this issue:
File: ./contracts/PanopticFactory.sol 86: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN = 1e6;
Github: [86]
<a name="NC-13"></a>
Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant
in a library
. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.
There is one instance of this issue:
File: ./contracts/libraries/Math.sol /// @audit also seen in ./contracts/libraries/PanopticMath.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
Github: [15]
<a name="NC-14"></a>
Putting constants on the left side of comparison statements is a best practice known as Yoda conditions. Although solidity's static typing system prevents accidental assignments within conditionals, adopting this practice can improve code readability and consistency, especially when working across multiple languages.
<details> <summary> There are <b>92</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 331: if (s_panopticPool.numberOfPositions(msg.sender) != 0) revert Errors.PositionCountNotZero(); 350: if (s_panopticPool.numberOfPositions(from) != 0) revert Errors.PositionCountNotZero(); 512: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0; 575: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0; 664: if (positionId.isLong(leg) == 0) continue; 708: (tokenType == 0 && currentValue1 < oracleValue1) || 1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) { 1107: if (intrinsicValue != 0) { 1339: if (isLong == 0) { 1347: ((atTick < tickLower) && (tokenType == 0)) // strike OTM when price < lowerTick for tokenType=0 1375: ((atTick >= tickUpper) && (tokenType == 0)) // strike ITM but out of range when price >= upperTick for tokenType=0 1479: if (isLong == 0) { 1544: if (tokenType == 0) { 1637: uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) + 1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
Github: [331, 350, 512, 575, 664, 708, 1060, 1107, 1339, 1347, 1375, 1479, 1544, 1637, 1638]
File: ./contracts/PanopticFactory.sol 224: if (_owner != address(0) && _owner != msg.sender) revert Errors.NotOwner(); 227: if (address(v3Pool) == address(0)) revert Errors.UniswapPoolNotInitialized(); 229: if (address(s_getPanopticPool[v3Pool]) != address(0))
File: ./contracts/PanopticPool.sol 299: if (address(s_univ3pool) != address(0)) revert Errors.PoolAlreadyInitialized(); 460: if (tokenId.isLong(leg) == 0 && !includePendingPremium) { 533: if (medianData != 0) s_miniMedian = medianData; 638: if (LeftRightUnsigned.unwrap(s_positionBalance[msg.sender][tokenId]) != 0) 664: if (medianData != 0) s_miniMedian = medianData; 865: if (tokenId.isLong(leg) == 0) { 1483: if (netLiquidity == 0) return; 1596: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg(); 1679: if (tokenId.isLong(leg) == 0) { 1780: (accumulated0 == 0 ? type(uint256).max : accumulated0), 1789: (accumulated1 == 0 ? type(uint256).max : accumulated1), 1862: if (LeftRightSigned.unwrap(legPremia) != 0) {
Github: [299, 460, 533, 638, 664, 865, 1483, 1596, 1679, 1780, 1789, 1862]
File: ./contracts/SemiFungiblePositionManager.sol 355: if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized(); 362: if (s_AddrToPoolIdData[univ3pool] != 0) return; 372: while (address(s_poolContext[poolId].pool) != address(0)) { 632: (LeftRightUnsigned.unwrap(s_accountLiquidity[positionKey_to]) != 0) || 633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0) 688: if (positionSize == 0) revert Errors.OptionsBalanceZero(); 717: if ((LeftRightSigned.unwrap(itmAmounts) != 0)) { 787: if ((itm0 != 0) && (itm1 != 0)) { 823: } else if (itm0 != 0) { 833: if (swapAmount == 0) return LeftRightSigned.wrap(0); 999: if (isLong == 0) { 1078: if (tokenType == 0) { 1279: if (LeftRightSigned.unwrap(amountToCollect) != 0) { 1469: if (netLiquidity != 0) {
Github: [355, 362, 372, 632, 633, 688, 717, 787, 823, 833, 999, 1078, 1279, 1469]
File: ./contracts/libraries/FeesCalc.sol 67: if (tokenId.isLong(leg) == 0) {
Github: [67]
File: ./contracts/libraries/Math.sol 93: if (x >= 0x100000000000000000000000000000000) { 97: if (x >= 0x10000000000000000) { 101: if (x >= 0x100000000) { 105: if (x >= 0x10000) { 109: if (x >= 0x100) { 113: if (x >= 0x10) { 137: if (absTick & 0x2 != 0) sqrtR = (sqrtR * 0xfff97272373d413259a46990580e213a) >> 128; 139: if (absTick & 0x4 != 0) sqrtR = (sqrtR * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; 141: if (absTick & 0x8 != 0) sqrtR = (sqrtR * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; 143: if (absTick & 0x10 != 0) sqrtR = (sqrtR * 0xffcb9843d60f6159c9db58835c926644) >> 128; 145: if (absTick & 0x20 != 0) sqrtR = (sqrtR * 0xff973b41fa98c081472e6896dfb254c0) >> 128; 147: if (absTick & 0x40 != 0) sqrtR = (sqrtR * 0xff2ea16466c96a3843ec78b326b52861) >> 128; 149: if (absTick & 0x80 != 0) sqrtR = (sqrtR * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; 151: if (absTick & 0x100 != 0) sqrtR = (sqrtR * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; 153: if (absTick & 0x200 != 0) sqrtR = (sqrtR * 0xf987a7253ac413176f2b074cf7815e54) >> 128; 155: if (absTick & 0x400 != 0) sqrtR = (sqrtR * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; 157: if (absTick & 0x800 != 0) sqrtR = (sqrtR * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; 159: if (absTick & 0x1000 != 0) sqrtR = (sqrtR * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; 161: if (absTick & 0x2000 != 0) sqrtR = (sqrtR * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; 163: if (absTick & 0x4000 != 0) sqrtR = (sqrtR * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; 165: if (absTick & 0x8000 != 0) sqrtR = (sqrtR * 0x31be135f97d08fd981231505542fcfa6) >> 128; 167: if (absTick & 0x10000 != 0) sqrtR = (sqrtR * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; 169: if (absTick & 0x20000 != 0) sqrtR = (sqrtR * 0x5d6af8dedb81196699c329225ee604) >> 128; 171: if (absTick & 0x40000 != 0) sqrtR = (sqrtR * 0x2216e584f5fa1ea926041bedfe98) >> 128; 173: if (absTick & 0x80000 != 0) sqrtR = (sqrtR * 0x48a170391f7dc42444e8fa2) >> 128; 179: return uint160((sqrtR >> 32) + (sqrtR % (1 << 32) == 0 ? 0 : 1)); 360: if (prod1 == 0) { 474: if (prod1 == 0) { 537: if (prod1 == 0) { 614: if (prod1 == 0) { 691: if (prod1 == 0) {
Github: [93, 97, 101, 105, 109, 113, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 179, 360, 474, 537, 614, 691]
File: ./contracts/libraries/PanopticMath.sol 325: if (tokenId.asset(legIndex) == 0) { 425: if (tokenType == 0) { 479: if (notional == 0 || notional > type(uint128).max) revert Errors.InvalidNotionalValue(); 584: if (tokenId.asset(legIndex) == 0) { 615: bool isShort = tokenId.isLong(legIndex) == 0; 618: if (tokenId.tokenType(legIndex) == 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));
Github: [325, 425, 479, 584, 615, 618, 856, 857]
File: ./contracts/tokens/ERC1155Minimal.sol 112: if (to.code.length != 0) { 163: if (to.code.length != 0) { 222: if (to.code.length != 0) {
File: ./contracts/types/TokenId.sol 465: if (i == 0) 501: if (self.optionRatio(0) == 0) revert Errors.InvalidTokenIdParameter(1); 508: if (self.optionRatio(i) == 0) { 512: if ((TokenId.unwrap(self) >> (64 + 48 * i)) != 0) 528: if ((self.width(i) == 0)) revert Errors.InvalidTokenIdParameter(5);
Github: [465, 501, 508, 512, 528]
</details><a name="NC-15"></a>
Constructor
should emit an eventUse events to signal significant changes to off-chain monitoring tools.
There are <b>4</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 178: constructor(
Github: [178]
File: ./contracts/PanopticFactory.sol 115: constructor(
Github: [115]
File: ./contracts/PanopticPool.sol 280: constructor(SemiFungiblePositionManager _sfpm) {
Github: [280]
File: ./contracts/SemiFungiblePositionManager.sol 355: if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized();
Github: [355]
<a name="NC-16"></a>
The style guide says that, within a contract, the ordering should be 1) Type declarations
, 2) State variables
, 3) Events
, 4) Modifiers
, and 5) Functions
, but the contract(s) below do not follow this ordering
File: ./contracts/CollateralTracker.sol /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 70: string internal constant TICKER_PREFIX = "po"; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 73: string internal constant NAME_PREFIX = "POPT-V1"; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 77: uint256 internal constant DECIMALS = 10_000; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 81: int128 internal constant DECIMALS_128 = 10_000; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 89: address internal s_underlyingToken; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 93: bool internal s_initialized; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 96: address internal s_univ3token0; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 99: address internal s_univ3token1; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 102: bool internal s_underlyingIsToken0; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 109: PanopticPool internal s_panopticPool; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 112: uint128 internal s_poolAssets; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 115: uint128 internal s_inAMM; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 121: uint128 internal s_ITMSpreadFee; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 124: uint24 internal s_poolFee; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 131: uint256 immutable TICK_DEVIATION; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 136: uint256 immutable COMMISSION_FEE; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 141: uint256 immutable SELLER_COLLATERAL_RATIO; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 145: uint256 immutable BUYER_COLLATERAL_RATIO; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 149: int256 immutable FORCE_EXERCISE_COST; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 154: uint256 immutable TARGET_POOL_UTIL; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 158: uint256 immutable SATURATED_POOL_UTIL; /// @audit event/error `Deposit` came earlier /// @audit event/error `Withdraw` came earlier 162: uint256 immutable ITM_SPREAD_MULTIPLIER;
Github: [70, 73, 77, 81, 89, 93, 96, 99, 102, 109, 112, 115, 121, 124, 131, 136, 141, 145, 149, 154, 158, 162]
File: ./contracts/PanopticFactory.sol /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 63: IUniswapV3Factory internal immutable UNIV3_FACTORY; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 66: SemiFungiblePositionManager internal immutable SFPM; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 69: IDonorNFT internal immutable DONOR_NFT; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 72: address internal immutable POOL_REFERENCE; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 75: address internal immutable COLLATERAL_REFERENCE; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 78: address internal immutable WETH; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 82: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_WETH = 0.1 ether; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 86: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN = 1e6; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 89: uint16 internal constant CARDINALITY_INCREASE = 100; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 96: address internal s_owner; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 99: bool internal s_initialized; /// @audit event/error `OwnershipTransferred` came earlier /// @audit event/error `PoolDeployed` came earlier 102: mapping(IUniswapV3Pool univ3pool => PanopticPool panopticPool) internal s_getPanopticPool;
Github: [63, 66, 69, 72, 75, 78, 82, 86, 89, 96, 99, 102]
File: ./contracts/PanopticPool.sol /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 103: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 105: int24 internal constant MAX_SWAP_TICK = Constants.MAX_V3POOL_TICK - 1; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 109: bool internal constant COMPUTE_ALL_PREMIA = true; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 111: bool internal constant COMPUTE_LONG_PREMIA = false; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 114: bool internal constant ONLY_AVAILABLE_PREMIUM = false; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 119: bool internal constant COMMIT_LONG_SETTLED = true; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 120: bool internal constant DONOT_COMMIT_LONG_SETTLED = false; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 123: bool internal constant ADD = true; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 128: uint32 internal constant TWAP_WINDOW = 600; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 133: bool internal constant SLOW_ORACLE_UNISWAP_MODE = false; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 136: uint256 internal constant MEDIAN_PERIOD = 60; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 139: uint256 internal constant FAST_ORACLE_CARDINALITY = 3; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 145: uint256 internal constant FAST_ORACLE_PERIOD = 1; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 148: uint256 internal constant SLOW_ORACLE_CARDINALITY = 7; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 152: uint256 internal constant SLOW_ORACLE_PERIOD = 5; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 156: int256 internal constant MAX_TWAP_DELTA_LIQUIDATION = 513; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 160: int256 internal constant MAX_SLOW_FAST_DELTA = 1800; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 165: uint64 internal constant MAX_SPREAD = 9 * (2 ** 32); /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 168: uint64 internal constant MAX_POSITIONS = 32; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 171: uint256 internal constant BP_DECREASE_BUFFER = 13_333; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 174: uint256 internal constant NO_BUFFER = 10_000; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 179: SemiFungiblePositionManager internal immutable SFPM; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 186: IUniswapV3Pool internal s_univ3pool; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 225: uint256 internal s_miniMedian; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 231: CollateralTracker internal s_collateralToken0; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 233: CollateralTracker internal s_collateralToken1; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 238: mapping(address account => mapping(TokenId tokenId => mapping(uint256 leg => LeftRightUnsigned premiaGrowth))) /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 245: mapping(bytes32 chunkKey => LeftRightUnsigned lastGrossPremium) internal s_grossPremiumLast; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 251: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) internal s_settledTokens; /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 258: mapping(address account => mapping(TokenId tokenId => LeftRightUnsigned balanceAndUtilizations)) /// @audit event/error `AccountLiquidated` came earlier /// @audit event/error `ForcedExercised` came earlier /// @audit event/error `PremiumSettled` came earlier /// @audit event/error `OptionBurnt` came earlier /// @audit event/error `OptionMinted` came earlier 272: mapping(address account => uint256 positionsHash) internal s_positionsHash;
Github: [103, 105, 109, 111, 114, 119, 120, 123, 128, 133, 136, 139, 145, 148, 152, 156, 160, 165, 168, 171, 174, 179, 186, 225, 231, 233, 238, 245, 251, 258, 272]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 130: // and vegoid modifies the sensitivity of the streamia to changes in that utilization, /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 133: uint128 private constant VEGOID = 2; /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 135: /// @dev Uniswap V3 Factory address. Initialized in the constructor. /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 144: /// @dev pool address => pool id + 2 ** 255 (initialization bit for `poolId == 0`, set if the pool exists) /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 147: /// @dev also contains a boolean which is used for the reentrancy lock - saves gas since this slot is often warmed /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 155: net amount /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 163: │ │ │ │ │ │ │ /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 190: Let`s say Charlie the smart contract deposited T into the AMM and later removed R from that /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 295: mapping(bytes32 positionKey => LeftRightSigned baseFees0And1) internal s_accountFeesBase; /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 297: /*////////////////////////////////////////////////////////////// /// @audit event/error `PoolInitialized` came earlier /// @audit event/error `TokenizedPositionBurnt` came earlier /// @audit event/error `TokenizedPositionMinted` came earlier 310: // execute function
Github: [130, 133, 135, 144, 147, 155, 163, 190, 295, 297, 310]
File: ./contracts/tokens/ERC1155Minimal.sol /// @audit event/error `TransferSingle` came earlier /// @audit event/error `TransferBatch` came earlier /// @audit event/error `ApprovalForAll` came earlier /// @audit event/error `NotAuthorized` came earlier /// @audit event/error `UnsafeRecipient` came earlier 66: mapping(address account => mapping(uint256 tokenId => uint256 balance)) public balanceOf; /// @audit event/error `TransferSingle` came earlier /// @audit event/error `TransferBatch` came earlier /// @audit event/error `ApprovalForAll` came earlier /// @audit event/error `NotAuthorized` came earlier /// @audit event/error `UnsafeRecipient` came earlier 71: mapping(address owner => mapping(address operator => bool approvedForAll))
</details>File: ./contracts/tokens/ERC20Minimal.sol /// @audit event/error `Transfer` came earlier /// @audit event/error `Approval` came earlier 32: uint256 public totalSupply; /// @audit event/error `Transfer` came earlier /// @audit event/error `Approval` came earlier 35: mapping(address account => uint256 balance) public balanceOf; /// @audit event/error `Transfer` came earlier /// @audit event/error `Approval` came earlier 39: mapping(address owner => mapping(address spender => uint256 allowance)) public allowance;
<a name="NC-17"></a>
interface
All external
/public
functions should extend an interface
. This is useful to make sure that the whole API is extracted.
Note: It is possible that the interface is out-of-scope and has not been seen by the bot.
<details> <summary> There are <b>73</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 221: function startToken( 277: function getPoolData() 289: function name() external view returns (string memory) { 303: function symbol() external view returns (string memory) { 310: function decimals() external view returns (uint8) { 341: function transferFrom( 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( 572: function maxRedeem(address owner) public view returns (uint256 maxShares) { 581: function previewRedeem(uint256 shares) public view returns (uint256 assets) { 591: function redeem( 650: function exerciseCost( 864: function delegate( 898: /// @notice Refunds delegated tokens back to the protocol 907: /// @notice Revoke previously delegated shares. The opposite of 'delegate'. 917: ┌────────┐ 980: _transferFrom(refundee, refunder, convertToShares(uint256(-assets))); 1002: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid 1051: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid 1147: tokenData = _getAccountMargin(user, currentTick, positionBalanceArray, premiumAllPositions);
Github: [221, 277, 289, 303, 310, 341, 361, 370, 379, 386, 392, 399, 417, 444, 453, 477, 507, 518, 531, 572, 581, 591, 650, 864, 898, 907, 917, 980, 1002, 1051, 1147]
File: ./contracts/PanopticFactory.sol 134: function initialize(address _owner) public { 147: function transferOwnership(address newOwner) external { 159: function owner() external view returns (address) { 172: function uniswapV3MintCallback( 210: function deployNewPool( 290: function minePoolAddress( 420: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
Github: [134, 147, 159, 172, 210, 290, 420]
File: ./contracts/PanopticPool.sol 291: function startPool( 338: function assertPriceWithinBounds(uint160 sqrtLowerBound, uint160 sqrtUpperBound) external view { 352: function optionPositionBalance( 381: function calculateAccumulatedFeesBatch( 410: function calculatePortfolioValue( 522: function pokeMedian() external { 547: function mintOptions( 569: function burnOptions( 586: function burnOptions( 1017: function liquidate( 1179: function forceExercise( 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(
Github: [291, 338, 352, 381, 410, 522, 547, 569, 586, 1017, 1179, 1425, 1431, 1437, 1444, 1587]
File: ./contracts/SemiFungiblePositionManager.sol 361: // if poolId == 0, we have a bit on the left set if it was initialized, so this will still return properly 421: decoded.poolFeatures.token1, 450: // Transform the amount to pay to uint256 (take positive one from amount0 and amount1) 492: slippageTickLimitHigh, 524: slippageTickLimitHigh, 555: super.safeTransferFrom(from, to, id, amount, data); 583: // transfer the token (note that all state updates are completed before reentrancy is possible) 1445: /// @param atTick The current tick. Set atTick < type(int24).max = 8388608 to get latest premium up to the current block 1479: // oldFeesGrowth is the last stored update of fee growth within the position range in the past (feeGrowthRange*liquidityChunk.liquidity()) (s_accountFeesBase[positionKey]) 1570: 1570: 1570:
Github: [361, 421, 450, 492, 524, 555, 583, 1445, 1479, 1570, 1570, 1570]
File: ./contracts/multicall/Multicall.sol 12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
Github: [12]
File: ./contracts/tokens/ERC1155Minimal.sol 81: function setApprovalForAll(address operator, bool approved) public { 94: function safeTransferFrom( 130: function safeBatchTransferFrom( 178: function balanceOfBatch( 200: function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
Github: [81, 94, 130, 178, 200]
File: ./contracts/tokens/ERC20Minimal.sol 81: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
Github: [81]
</details><a name="NC-18"></a>
While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit.
There is one instance of this issue:
Global finding
Github: [Global]
<a name="NC-19"></a>
if
statements to ternary expressionsConverting some if statements to ternaries (such as z = (a < b) ? x : y
) can make the code more concise and easier to read.
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 1550: ? movedPartnerLeft - movedLeft : movedLeft - movedPartnerLeft; } } } else { unchecked { uint256 notional; uint256 notionalP; uint128 contracts;
Github: [1550]
File: ./contracts/types/TokenId.sol 382: } else if (optionRatios < 2 ** 208) { optionRatios = 3; } else { optionRatios = 4;
Github: [382]
<a name="NC-20"></a>
This should especially be done if the new value is not required to be different from the old value.
<details> <summary> There are <b>24</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 439: emit Deposit(msg.sender, receiver, assets, shares); 499: emit Deposit(msg.sender, receiver, assets, shares); 563: emit Withdraw(msg.sender, receiver, owner, assets, shares); 623: emit Withdraw(msg.sender, receiver, owner, assets, shares);
File: ./contracts/PanopticFactory.sol 154: emit OwnershipTransferred(currentOwner, newOwner);
Github: [154]
File: ./contracts/PanopticPool.sol 666: emit OptionMinted(msg.sender, positionSize, tokenId, poolUtilizations); 853: emit OptionBurnt(owner, positionSize, tokenId, premiaOwed); 1170: emit AccountLiquidated(msg.sender, liquidatee, bonusAmounts); 1277: emit ForcedExercised(msg.sender, account, touchedId[0], exerciseFees); 1654: emit PremiumSettled(owner, tokenId, realizedPremia);
Github: [666, 853, 1170, 1277, 1654]
File: ./contracts/SemiFungiblePositionManager.sol 390: emit PoolInitialized(univ3pool, poolId); 485: emit TokenizedPositionBurnt(msg.sender, tokenId, positionSize); 517: emit TokenizedPositionMinted(msg.sender, tokenId, positionSize);
File: ./contracts/tokens/ERC1155Minimal.sol 84: emit ApprovalForAll(msg.sender, operator, approved); 110: emit TransferSingle(msg.sender, from, to, id, amount); 161: emit TransferBatch(msg.sender, from, to, ids, amounts); 220: emit TransferSingle(msg.sender, address(0), to, id, amount); 239: emit TransferSingle(msg.sender, from, address(0), id, amount);
Github: [84, 110, 161, 220, 239]
File: ./contracts/tokens/ERC20Minimal.sol 52: emit Approval(msg.sender, spender, amount); 70: emit Transfer(msg.sender, to, amount); 94: emit Transfer(from, to, amount); 112: emit Transfer(from, to, amount); 130: emit Transfer(address(0), to, amount); 145: emit Transfer(from, address(0), amount);
Github: [52, 70, 94, 112, 130, 145]
</details><a name="NC-21"></a>
Consider adding parameters to the error to indicate which user or values caused the failure.
<details> <summary> There are <b>34</b> instances (click to show): </summary>File: ./contracts/libraries/Errors.sol 10: error CastingError(); 13: error CollateralTokenAlreadyInitialized(); 16: error DepositTooLarge(); 20: error EffectiveLiquidityAboveThreshold(); 23: error ExceedsMaximumRedemption(); 26: error ExerciseeNotSolvent(); 29: error InputListFail(); 32: error InvalidSalt(); 35: error InvalidTick(); 38: error InvalidNotionalValue(); 45: error InvalidUniswapCallback(); 48: error LeftRightInputError(); 51: error NoLegsExercisable(); 54: error NotEnoughCollateral(); 57: error PositionTooLarge(); 60: error NotALongLeg(); 63: error NotEnoughLiquidity(); 66: error NotMarginCalled(); 70: error NotOwner(); 73: error NotPanopticPool(); 76: error OptionsBalanceZero(); 79: error PoolAlreadyInitialized(); 82: error PositionAlreadyMinted(); 85: error PositionCountNotZero(); 88: error PriceBoundFail(); 91: error ReentrantCall(); 95: error StaleTWAP(); 98: error TooManyPositionsOpen(); 101: error TransferFailed(); 105: error TicksNotInitializable(); 108: error UnderOverFlow(); 111: error UniswapPoolNotInitialized();
Github: [10, 13, 16, 20, 23, 26, 29, 32, 35, 38, 45, 48, 51, 54, 57, 60, 63, 66, 70, 73, 76, 79, 82, 85, 88, 91, 95, 98, 101, 105, 108, 111]
</details>File: ./contracts/tokens/ERC1155Minimal.sol 55: error NotAuthorized(); 58: error UnsafeRecipient();
<a name="NC-22"></a>
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/libraries/CallbackLib.sol 2: pragma solidity ^0.8.0;
Github: [2]
<a name="NC-23"></a>
The instances below point to one of two functions with the same name. Consider naming each function differently, in order to make code navigation and analysis easier.
<details> <summary> There are <b>15</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol /// @audit also found on line 898 864: function delegate( /// @audit also found on line 980 907: /// @notice Revoke previously delegated shares. The opposite of 'delegate'.
File: ./contracts/PanopticPool.sol /// @audit also found on line 586 569: function burnOptions(
Github: [569]
File: ./contracts/libraries/Math.sol /// @audit also found on line 49 41: function min(uint256 a, uint256 b) internal pure returns (uint256) { /// @audit also found on line 65 57: function max(uint256 a, uint256 b) internal pure returns (uint256) { /// @audit also found on line 318 311: function toInt128(uint128 toCast) internal pure returns (int128 downcastedInt) {
File: ./contracts/libraries/PanopticMath.sol /// @audit also found on line 447 421: LeftRightUnsigned tokenData1, /// @audit also found on line 524 490: function convert0to1(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) { /// @audit also found on line 547 507: function convert1to0(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) {
File: ./contracts/types/LeftRight.sol /// @audit also found on line 46 39: function rightSlot(LeftRightUnsigned self) internal pure returns (uint128) { /// @audit also found on line 78 59: function toRightSlot( /// @audit also found on line 108 101: function leftSlot(LeftRightUnsigned self) internal pure returns (uint128) { /// @audit also found on line 134 121: function toLeftSlot( /// @audit also found on line 194 148: function add( /// @audit also found on line 232 171: function sub(
Github: [39, 59, 101, 121, 148, 171]
</details><a name="NC-24"></a>
require()
/revert()
checks should be refactoredRefactoring duplicate require()
/revert()
checks into a modifier or function can make the code more concise, readable and maintainable, and less likely to make errors or omissions when modifying the require()
or revert()
.
There are <b>2</b> instances of this issue:
File: ./contracts/libraries/Math.sol /// @audit duplicated on line 588 448: require(result < type(uint256).max);
Github: [448]
File: ./contracts/tokens/ERC1155Minimal.sol /// @audit duplicated on line 168 117: revert UnsafeRecipient();
Github: [117]
<a name="NC-25"></a>
else
block not requiredOne level of nesting can be removed by not having an else
block when the if
-block always jumps at the end. For example:
if (condition) { body1... return x; } else { body2... }
can be changed to:
if (condition) { body1... return x; } body2...
There are <b>6</b> instances of this issue:
File: ./contracts/libraries/PanopticMath.sol 326: return Math.getLiquidityForAmount0(tickLower, tickUpper, amount); } else { return Math.getLiquidityForAmount1(tickLower, tickUpper, amount); } } 427: tokenData0.rightSlot() + convert1to0(tokenData1.rightSlot(), sqrtPriceX96), tokenData0.leftSlot() + convert1to0(tokenData1.leftSlot(), sqrtPriceX96) ); } else { return ( tokenData1.rightSlot() + convert0to1(tokenData0.rightSlot(), sqrtPriceX96), 495: return Math.mulDiv192(amount, uint256(sqrtPriceX96) ** 2); } else { return Math.mulDiv128(amount, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 512: return Math.mulDiv(amount, 2 ** 192, uint256(sqrtPriceX96) ** 2); } else { return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 530: .mulDiv192(Math.absUint(amount), uint256(sqrtPriceX96) ** 2) .toInt256(); return amount < 0 ? -absResult : absResult; } else { int256 absResult = Math .mulDiv128(Math.absUint(amount), Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)) 553: .mulDiv(Math.absUint(amount), 2 ** 192, uint256(sqrtPriceX96) ** 2) .toInt256(); return amount < 0 ? -absResult : absResult; } else { int256 absResult = Math .mulDiv( Math.absUint(amount),
Github: [326, 427, 495, 512, 530, 553]
<a name="NC-26"></a>
Passing empty bytes to a function can cause unexpected behavior, such as certain operations failing, producing incorrect results, or wasting gas. It is recommended to check that all byte parameters are not empty.
There are <b>7</b> instances of this issue:
File: ./contracts/PanopticFactory.sol /// @audit missing empty check for `data` 172: function uniswapV3MintCallback( uint256 amount0Owed, uint256 amount1Owed, bytes calldata data ) external {
Github: [172]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit missing empty check for `data` 421: decoded.poolFeatures.token1, decoded.payer, msg.sender, amount1Owed ); /// @audit missing empty check for `data` 450: // Transform the amount to pay to uint256 (take positive one from amount0 and amount1) // the pool will always pass one delta with a positive sign and one with a negative sign or zero, // so this logic always picks the correct delta to pay /// @audit missing empty check for `data` 555: super.safeTransferFrom(from, to, id, amount, data); } /// @notice Transfer multiple tokens from one user to another /// @dev supports token approvals /// @dev ids and amounts must be of equal length /// @audit missing empty check for `data` 583: // transfer the token (note that all state updates are completed before reentrancy is possible) super.safeBatchTransferFrom(from, to, ids, amounts, data); } /// @notice update user position data following a token transfer /// @dev token transfers are only allowed if you transfer your entire liquidity of a given chunk and the recipient has none
File: ./contracts/tokens/ERC1155Minimal.sol /// @audit missing empty check for `data` 94: function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes calldata data ) public virtual { /// @audit missing empty check for `data` 130: function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) public virtual {
<a name="NC-27"></a>
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. You can enable it on the command line using --via-ir
or with the option {"viaIR": true}
. This will take longer to compile, but you can just simple test it before deploying and if you got a better benchmark then you can add --via-ir to your deploy command More on: https://docs.soliditylang.org/en/v0.8.17/ir-breaking-changes.html
There is one instance of this issue:
Global finding
Github: [Global]
<a name="NC-28"></a>
Create a commented enum value to use instead of constant array indexes, this makes the code far easier to understand.
<details> <summary> There are <b>26</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 1216: uint128 poolUtilization = LeftRightUnsigned.wrap(positionBalanceArray[i][1]).leftSlot(); 1219: uint256 _tokenRequired = _getRequiredCollateralAtTickSinglePosition( 1224: );
File: ./contracts/PanopticPool.sol 444: balances[k][0] = TokenId.unwrap(tokenId); 445: balances[k][1] = LeftRightUnsigned.unwrap(s_positionBalance[c_user][tokenId]); 452: LeftRightUnsigned.wrap(balances[k][1]).rightSlot(), 1193: uint128 positionBalance = s_positionBalance[account][touchedId[0]].rightSlot(); 1198: .computeExercisedAmounts(touchedId[0], positionBalance); 1219: touchedId[0].validateIsExercisable(twapTick); 1234: touchedId[0], 1277: emit ForcedExercised(msg.sender, account, touchedId[0], exerciseFees); 1528: (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM 1528: (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM 1550: ((premiumAccumulatorsByLeg[leg][0] - 1559: ((premiumAccumulatorsByLeg[leg][1] - 1707: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium( 1707: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium( 1729: (grossCurrent[0] * 1737: (grossCurrent[1] * 1768: uint256 accumulated0 = ((premiumAccumulators[0] - grossPremiumLast.rightSlot()) * 1770: uint256 accumulated1 = ((premiumAccumulators[1] - grossPremiumLast.leftSlot()) * 1940: _premiumAccumulatorsByLeg[_leg][0] * 1957: _premiumAccumulatorsByLeg[_leg][1] * 1967: .toRightSlot(uint128(premiumAccumulatorsByLeg[_leg][0])) 1968: .toLeftSlot(uint128(premiumAccumulatorsByLeg[_leg][1]));
Github: [444, 445, 452, 1193, 1198, 1219, 1234, 1277, 1528, 1528, 1550, 1559, 1707, 1707, 1729, 1737, 1768, 1770, 1940, 1957, 1967, 1968]
File: ./contracts/libraries/PanopticMath.sol 266: return int24(sortedTicks[10]);
Github: [266]
</details><a name="NC-29"></a>
When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the msg.sender
the events of these types of action will make events much more useful to end users, especially when msg.sender
is not tx.origin
.
There are <b>8</b> instances of this issue:
File: ./contracts/PanopticFactory.sol 154: emit OwnershipTransferred(currentOwner, newOwner); 268: emit PoolDeployed(
File: ./contracts/PanopticPool.sol 853: emit OptionBurnt(owner, positionSize, tokenId, premiaOwed);
Github: [853]
File: ./contracts/SemiFungiblePositionManager.sol 406: ) external {
Github: [406]
File: ./contracts/tokens/ERC20Minimal.sol 94: emit Transfer(from, to, amount); 112: emit Transfer(from, to, amount); 130: emit Transfer(address(0), to, amount); 145: emit Transfer(from, address(0), amount);
<a name="NC-30"></a>
Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls.
There are <b>5</b> instances of this issue:
File: ./contracts/PanopticFactory.sol /// @audit `.getPool(...)` is called on line 226 /// @audit `.initializeAMMPool(...)` is called on line 233 /// @audit `.cloneDeterministic(...)` is called on line 237 /// @audit `.startToken(...)` is called on line 248 /// @audit `.startToken(...)` is called on line 249 /// @audit `.startPool(...)` is called on line 251 /// @audit `.increaseObservationCardinalityNext(...)` is called on line 258 /// @audit `.issueNFT(...)` is called on line 266 268: emit PoolDeployed(
Github: [268]
File: ./contracts/PanopticPool.sol /// @audit `.getPoolId(...)` is called on line 633 666: emit OptionMinted(msg.sender, positionSize, tokenId, poolUtilizations); /// @audit `.slot0(...)` is called on line 1032 /// @audit `.getAccountMarginDetails(...)` is called on line 1046 /// @audit `.getAccountMarginDetails(...)` is called on line 1053 /// @audit `.delegate(...)` is called on line 1072 /// @audit `.delegate(...)` is called on line 1073 /// @audit `.slot0(...)` is called on line 1094 /// @audit `.revoke(...)` is called on line 1141 /// @audit `.revoke(...)` is called on line 1146 1170: emit AccountLiquidated(msg.sender, liquidatee, bonusAmounts); /// @audit `.slot0(...)` is called on line 1202 /// @audit `.delegate(...)` is called on line 1222 /// @audit `.delegate(...)` is called on line 1223 /// @audit `.exerciseCost(...)` is called on line 1231 /// @audit `.refund(...)` is called on line 1252 /// @audit `.refund(...)` is called on line 1257 /// @audit `.refund(...)` is called on line 1265 /// @audit `.refund(...)` is called on line 1266 1277: emit ForcedExercised(msg.sender, account, touchedId[0], exerciseFees); /// @audit `.slot0(...)` is called on line 1598 /// @audit `.getAccountPremium(...)` is called on line 1605 /// @audit `.exercise(...)` is called on line 1639 /// @audit `.exercise(...)` is called on line 1640 1654: emit PremiumSettled(owner, tokenId, realizedPremia);
Github: [666, 1170, 1277, 1654]
<a name="NC-31"></a>
According to the solidity style guide, functions should be laid out in the following order: constructor()
, receive()
, fallback()
, external
, public
, internal
, private
, but the cases below do not follow this pattern
File: ./contracts/CollateralTracker.sol /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function 361: function asset() external view returns (address assetTokenAddress) { /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function 392: function maxDeposit(address) external pure returns (uint256 maxAssets) { /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function 417: function deposit(uint256 assets, address receiver) external returns (uint256 shares) { /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function 444: function maxMint(address) external view returns (uint256 maxShares) { /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function 477: function mint(uint256 shares, address receiver) external returns (uint256 assets) { /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function 531: function withdraw( /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function 591: function redeem( /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function 650: function exerciseCost( /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function /// @audit internal function `_poolUtilization` came earlier from this external function /// @audit internal function `_sellCollateralRatio` came earlier from this external function /// @audit internal function `_buyCollateralRatio` came earlier from this external function 864: function delegate( /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function /// @audit internal function `_poolUtilization` came earlier from this external function /// @audit internal function `_sellCollateralRatio` came earlier from this external function /// @audit internal function `_buyCollateralRatio` came earlier from this external function 898: /// @notice Refunds delegated tokens back to the protocol /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function /// @audit internal function `_poolUtilization` came earlier from this external function /// @audit internal function `_sellCollateralRatio` came earlier from this external function /// @audit internal function `_buyCollateralRatio` came earlier from this external function 907: /// @notice Revoke previously delegated shares. The opposite of 'delegate'. /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function /// @audit internal function `_poolUtilization` came earlier from this external function /// @audit internal function `_sellCollateralRatio` came earlier from this external function /// @audit internal function `_buyCollateralRatio` came earlier from this external function 917: ┌────────┐ /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function /// @audit internal function `_poolUtilization` came earlier from this external function /// @audit internal function `_sellCollateralRatio` came earlier from this external function /// @audit internal function `_buyCollateralRatio` came earlier from this external function 980: _transferFrom(refundee, refunder, convertToShares(uint256(-assets))); /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function /// @audit internal function `_poolUtilization` came earlier from this external function /// @audit internal function `_sellCollateralRatio` came earlier from this external function /// @audit internal function `_buyCollateralRatio` came earlier from this external function 1002: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid /// @audit public function `transfer` came earlier from this external function /// @audit public function `transferFrom` came earlier from this external function /// @audit public function `totalAssets` came earlier from this external function /// @audit public function `convertToShares` came earlier from this external function /// @audit public function `convertToAssets` came earlier from this external function /// @audit public function `previewDeposit` came earlier from this external function /// @audit public function `previewMint` came earlier from this external function /// @audit public function `maxWithdraw` came earlier from this external function /// @audit public function `previewWithdraw` came earlier from this external function /// @audit public function `maxRedeem` came earlier from this external function /// @audit public function `previewRedeem` came earlier from this external function /// @audit internal function `_poolUtilization` came earlier from this external function /// @audit internal function `_sellCollateralRatio` came earlier from this external function /// @audit internal function `_buyCollateralRatio` came earlier from this external function 1051: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid /// @audit internal function `_poolUtilization` came earlier from this public function /// @audit internal function `_sellCollateralRatio` came earlier from this public function /// @audit internal function `_buyCollateralRatio` came earlier from this public function /// @audit internal function `_getExchangedAmount` came earlier from this public function 1147: tokenData = _getAccountMargin(user, currentTick, positionBalanceArray, premiumAllPositions);
Github: [361, 392, 417, 444, 477, 531, 591, 650, 864, 898, 907, 917, 980, 1002, 1051, 1147]
File: ./contracts/PanopticFactory.sol /// @audit public function `initialize` came earlier from this external function 147: function transferOwnership(address newOwner) external { /// @audit public function `initialize` came earlier from this external function 159: function owner() external view returns (address) { /// @audit public function `initialize` came earlier from this external function 172: function uniswapV3MintCallback( /// @audit public function `initialize` came earlier from this external function 210: function deployNewPool( /// @audit public function `initialize` came earlier from this external function 290: function minePoolAddress( /// @audit public function `initialize` came earlier from this external function /// @audit internal function `_mintFullRange` came earlier from this external function 420: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
Github: [147, 159, 172, 210, 290, 420]
File: ./contracts/PanopticPool.sol /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function 522: function pokeMedian() external { /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function 547: function mintOptions( /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function 569: function burnOptions( /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function 586: function burnOptions( /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function /// @audit internal function `_mintOptions` came earlier from this external function /// @audit internal function `_mintInSFPMAndUpdateCollateral` came earlier from this external function /// @audit internal function `_payCommissionAndWriteData` came earlier from this external function /// @audit internal function `_addUserOption` came earlier from this external function /// @audit internal function `_burnAllOptionsFrom` came earlier from this external function /// @audit internal function `_burnOptions` came earlier from this external function /// @audit internal function `_updatePositionDataBurn` came earlier from this external function /// @audit internal function `_validateSolvency` came earlier from this external function /// @audit internal function `_burnAndHandleExercise` came earlier from this external function 1017: function liquidate( /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function /// @audit internal function `_mintOptions` came earlier from this external function /// @audit internal function `_mintInSFPMAndUpdateCollateral` came earlier from this external function /// @audit internal function `_payCommissionAndWriteData` came earlier from this external function /// @audit internal function `_addUserOption` came earlier from this external function /// @audit internal function `_burnAllOptionsFrom` came earlier from this external function /// @audit internal function `_burnOptions` came earlier from this external function /// @audit internal function `_updatePositionDataBurn` came earlier from this external function /// @audit internal function `_validateSolvency` came earlier from this external function /// @audit internal function `_burnAndHandleExercise` came earlier from this external function 1179: function forceExercise( /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function /// @audit internal function `_mintOptions` came earlier from this external function /// @audit internal function `_mintInSFPMAndUpdateCollateral` came earlier from this external function /// @audit internal function `_payCommissionAndWriteData` came earlier from this external function /// @audit internal function `_addUserOption` came earlier from this external function /// @audit internal function `_burnAllOptionsFrom` came earlier from this external function /// @audit internal function `_burnOptions` came earlier from this external function /// @audit internal function `_updatePositionDataBurn` came earlier from this external function /// @audit internal function `_validateSolvency` came earlier from this external function /// @audit internal function `_burnAndHandleExercise` came earlier from this external function /// @audit internal function `_checkSolvencyAtTick` came earlier from this external function /// @audit internal function `_getSolvencyBalances` came earlier from this external function /// @audit internal function `_validatePositionList` came earlier from this external function /// @audit internal function `_updatePositionsHash` came earlier from this external function 1425: function univ3pool() external view returns (IUniswapV3Pool) { /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function /// @audit internal function `_mintOptions` came earlier from this external function /// @audit internal function `_mintInSFPMAndUpdateCollateral` came earlier from this external function /// @audit internal function `_payCommissionAndWriteData` came earlier from this external function /// @audit internal function `_addUserOption` came earlier from this external function /// @audit internal function `_burnAllOptionsFrom` came earlier from this external function /// @audit internal function `_burnOptions` came earlier from this external function /// @audit internal function `_updatePositionDataBurn` came earlier from this external function /// @audit internal function `_validateSolvency` came earlier from this external function /// @audit internal function `_burnAndHandleExercise` came earlier from this external function /// @audit internal function `_checkSolvencyAtTick` came earlier from this external function /// @audit internal function `_getSolvencyBalances` came earlier from this external function /// @audit internal function `_validatePositionList` came earlier from this external function /// @audit internal function `_updatePositionsHash` came earlier from this external function 1431: function collateralToken0() external view returns (CollateralTracker collateralToken) { /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function /// @audit internal function `_mintOptions` came earlier from this external function /// @audit internal function `_mintInSFPMAndUpdateCollateral` came earlier from this external function /// @audit internal function `_payCommissionAndWriteData` came earlier from this external function /// @audit internal function `_addUserOption` came earlier from this external function /// @audit internal function `_burnAllOptionsFrom` came earlier from this external function /// @audit internal function `_burnOptions` came earlier from this external function /// @audit internal function `_updatePositionDataBurn` came earlier from this external function /// @audit internal function `_validateSolvency` came earlier from this external function /// @audit internal function `_burnAndHandleExercise` came earlier from this external function /// @audit internal function `_checkSolvencyAtTick` came earlier from this external function /// @audit internal function `_getSolvencyBalances` came earlier from this external function /// @audit internal function `_validatePositionList` came earlier from this external function /// @audit internal function `_updatePositionsHash` came earlier from this external function 1437: function collateralToken1() external view returns (CollateralTracker) { /// @audit internal function `_calculateAccumulatedPremia` came earlier from this public function /// @audit internal function `_getSlippageLimits` came earlier from this public function /// @audit internal function `_mintOptions` came earlier from this public function /// @audit internal function `_mintInSFPMAndUpdateCollateral` came earlier from this public function /// @audit internal function `_payCommissionAndWriteData` came earlier from this public function /// @audit internal function `_addUserOption` came earlier from this public function /// @audit internal function `_burnAllOptionsFrom` came earlier from this public function /// @audit internal function `_burnOptions` came earlier from this public function /// @audit internal function `_updatePositionDataBurn` came earlier from this public function /// @audit internal function `_validateSolvency` came earlier from this public function /// @audit internal function `_burnAndHandleExercise` came earlier from this public function /// @audit internal function `_checkSolvencyAtTick` came earlier from this public function /// @audit internal function `_getSolvencyBalances` came earlier from this public function /// @audit internal function `_validatePositionList` came earlier from this public function /// @audit internal function `_updatePositionsHash` came earlier from this public function 1444: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) { /// @audit internal function `_calculateAccumulatedPremia` came earlier from this external function /// @audit internal function `_getSlippageLimits` came earlier from this external function /// @audit internal function `_mintOptions` came earlier from this external function /// @audit internal function `_mintInSFPMAndUpdateCollateral` came earlier from this external function /// @audit internal function `_payCommissionAndWriteData` came earlier from this external function /// @audit internal function `_addUserOption` came earlier from this external function /// @audit internal function `_burnAllOptionsFrom` came earlier from this external function /// @audit internal function `_burnOptions` came earlier from this external function /// @audit internal function `_updatePositionDataBurn` came earlier from this external function /// @audit internal function `_validateSolvency` came earlier from this external function /// @audit internal function `_burnAndHandleExercise` came earlier from this external function /// @audit internal function `_checkSolvencyAtTick` came earlier from this external function /// @audit internal function `_getSolvencyBalances` came earlier from this external function /// @audit internal function `_validatePositionList` came earlier from this external function /// @audit internal function `_updatePositionsHash` came earlier from this external function /// @audit public function `numberOfPositions` came earlier from this external function /// @audit internal function `getUniV3TWAP` came earlier from this external function /// @audit internal function `_checkLiquiditySpread` came earlier from this external function /// @audit internal function `_getPremia` came earlier from this external function 1587: function settleLongPremium(
Github: [522, 547, 569, 586, 1017, 1179, 1425, 1431, 1437, 1444, 1587]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit a function was seen before constructor /// @audit a function was seen before constructor 355: if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized(); /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function 361: // if poolId == 0, we have a bit on the left set if it was initialized, so this will still return properly /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function 421: decoded.poolFeatures.token1, /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function 450: // Transform the amount to pay to uint256 (take positive one from amount0 and amount1) /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function 492: slippageTickLimitHigh, /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function 524: slippageTickLimitHigh, /// @audit internal function `beginReentrancyLock` came earlier from this public function /// @audit internal function `endReentrancyLock` came earlier from this public function 555: super.safeTransferFrom(from, to, id, amount, data); /// @audit internal function `beginReentrancyLock` came earlier from this public function /// @audit internal function `endReentrancyLock` came earlier from this public function 583: // transfer the token (note that all state updates are completed before reentrancy is possible) /// @audit private function `_updateStoredPremia` came earlier from this internal function /// @audit private function `_getFeesBase` came earlier from this internal function 1221: /// @param liquidityChunk the chunk of liquidity to burn given by tick upper, tick lower, and its size /// @audit private function `_updateStoredPremia` came earlier from this internal function /// @audit private function `_getFeesBase` came earlier from this internal function 1252: /// @param movedInLeg how much liquidity has been moved between msg.sender and Uniswap before this function call /// @audit private function `_updateStoredPremia` came earlier from this internal function /// @audit private function `_getFeesBase` came earlier from this internal function 1288: uint128(amountToCollect.rightSlot()), /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function /// @audit public function `safeTransferFrom` came earlier from this external function /// @audit public function `safeBatchTransferFrom` came earlier from this external function /// @audit internal function `registerTokenTransfer` came earlier from this external function /// @audit internal function `_validateAndForwardToAMM` came earlier from this external function /// @audit internal function `swapInAMM` came earlier from this external function /// @audit internal function `_createPositionInAMM` came earlier from this external function /// @audit internal function `_createLegInAMM` came earlier from this external function /// @audit private function `_updateStoredPremia` came earlier from this external function /// @audit private function `_getFeesBase` came earlier from this external function /// @audit internal function `_mintLiquidity` came earlier from this external function /// @audit internal function `_burnLiquidity` came earlier from this external function /// @audit internal function `_collectAndWritePositionData` came earlier from this external function /// @audit private function `_getPremiaDeltas` came earlier from this external function 1445: /// @param atTick The current tick. Set atTick < type(int24).max = 8388608 to get latest premium up to the current block /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function /// @audit public function `safeTransferFrom` came earlier from this external function /// @audit public function `safeBatchTransferFrom` came earlier from this external function /// @audit internal function `registerTokenTransfer` came earlier from this external function /// @audit internal function `_validateAndForwardToAMM` came earlier from this external function /// @audit internal function `swapInAMM` came earlier from this external function /// @audit internal function `_createPositionInAMM` came earlier from this external function /// @audit internal function `_createLegInAMM` came earlier from this external function /// @audit private function `_updateStoredPremia` came earlier from this external function /// @audit private function `_getFeesBase` came earlier from this external function /// @audit internal function `_mintLiquidity` came earlier from this external function /// @audit internal function `_burnLiquidity` came earlier from this external function /// @audit internal function `_collectAndWritePositionData` came earlier from this external function /// @audit private function `_getPremiaDeltas` came earlier from this external function 1479: // oldFeesGrowth is the last stored update of fee growth within the position range in the past (feeGrowthRange*liquidityChunk.liquidity()) (s_accountFeesBase[positionKey]) /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function /// @audit public function `safeTransferFrom` came earlier from this external function /// @audit public function `safeBatchTransferFrom` came earlier from this external function /// @audit internal function `registerTokenTransfer` came earlier from this external function /// @audit internal function `_validateAndForwardToAMM` came earlier from this external function /// @audit internal function `swapInAMM` came earlier from this external function /// @audit internal function `_createPositionInAMM` came earlier from this external function /// @audit internal function `_createLegInAMM` came earlier from this external function /// @audit private function `_updateStoredPremia` came earlier from this external function /// @audit private function `_getFeesBase` came earlier from this external function /// @audit internal function `_mintLiquidity` came earlier from this external function /// @audit internal function `_burnLiquidity` came earlier from this external function /// @audit internal function `_collectAndWritePositionData` came earlier from this external function /// @audit private function `_getPremiaDeltas` came earlier from this external function 1570: /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function /// @audit public function `safeTransferFrom` came earlier from this external function /// @audit public function `safeBatchTransferFrom` came earlier from this external function /// @audit internal function `registerTokenTransfer` came earlier from this external function /// @audit internal function `_validateAndForwardToAMM` came earlier from this external function /// @audit internal function `swapInAMM` came earlier from this external function /// @audit internal function `_createPositionInAMM` came earlier from this external function /// @audit internal function `_createLegInAMM` came earlier from this external function /// @audit private function `_updateStoredPremia` came earlier from this external function /// @audit private function `_getFeesBase` came earlier from this external function /// @audit internal function `_mintLiquidity` came earlier from this external function /// @audit internal function `_burnLiquidity` came earlier from this external function /// @audit internal function `_collectAndWritePositionData` came earlier from this external function /// @audit private function `_getPremiaDeltas` came earlier from this external function 1570: /// @audit internal function `beginReentrancyLock` came earlier from this external function /// @audit internal function `endReentrancyLock` came earlier from this external function /// @audit public function `safeTransferFrom` came earlier from this external function /// @audit public function `safeBatchTransferFrom` came earlier from this external function /// @audit internal function `registerTokenTransfer` came earlier from this external function /// @audit internal function `_validateAndForwardToAMM` came earlier from this external function /// @audit internal function `swapInAMM` came earlier from this external function /// @audit internal function `_createPositionInAMM` came earlier from this external function /// @audit internal function `_createLegInAMM` came earlier from this external function /// @audit private function `_updateStoredPremia` came earlier from this external function /// @audit private function `_getFeesBase` came earlier from this external function /// @audit internal function `_mintLiquidity` came earlier from this external function /// @audit internal function `_burnLiquidity` came earlier from this external function /// @audit internal function `_collectAndWritePositionData` came earlier from this external function /// @audit private function `_getPremiaDeltas` came earlier from this external function 1570:
Github: [355, 361, 421, 450, 492, 524, 555, 583, 1221, 1252, 1288, 1445, 1479, 1570, 1570, 1570]
File: ./contracts/libraries/PanopticMath.sol /// @audit internal function `getPoolId` came earlier from this external function /// @audit internal function `incrementPoolPattern` came earlier from this external function 75: function numberOfLeadingHexZeros(address addr) external pure returns (uint256) { /// @audit internal function `getPoolId` came earlier from this external function /// @audit internal function `incrementPoolPattern` came earlier from this external function /// @audit internal function `updatePositionsHash` came earlier from this external function 125: function computeMedianObservedPrice( /// @audit internal function `getPoolId` came earlier from this external function /// @audit internal function `incrementPoolPattern` came earlier from this external function /// @audit internal function `updatePositionsHash` came earlier from this external function 168: function computeInternalMedian( /// @audit internal function `getPoolId` came earlier from this external function /// @audit internal function `incrementPoolPattern` came earlier from this external function /// @audit internal function `updatePositionsHash` came earlier from this external function 241: function twapFilter(IUniswapV3Pool univ3pool, uint32 twapWindow) external view returns (int24) { /// @audit internal function `getPoolId` came earlier from this external function /// @audit internal function `incrementPoolPattern` came earlier from this external function /// @audit internal function `updatePositionsHash` came earlier from this external function /// @audit internal function `getLiquidityChunk` came earlier from this external function /// @audit internal function `getTicks` came earlier from this external function /// @audit internal function `getRangesFromStrike` came earlier from this external function /// @audit internal function `computeExercisedAmounts` came earlier from this external function /// @audit internal function `convertCollateralData` came earlier from this external function /// @audit internal function `convertCollateralData` came earlier from this external function /// @audit internal function `convertNotional` came earlier from this external function /// @audit internal function `convert0to1` came earlier from this external function /// @audit internal function `convert1to0` came earlier from this external function /// @audit internal function `convert0to1` came earlier from this external function /// @audit internal function `convert1to0` came earlier from this external function /// @audit internal function `getAmountsMoved` came earlier from this external function /// @audit internal function `_calculateIOAmounts` came earlier from this external function 653: LeftRightUnsigned tokenData1, /// @audit internal function `getPoolId` came earlier from this external function /// @audit internal function `incrementPoolPattern` came earlier from this external function /// @audit internal function `updatePositionsHash` came earlier from this external function /// @audit internal function `getLiquidityChunk` came earlier from this external function /// @audit internal function `getTicks` came earlier from this external function /// @audit internal function `getRangesFromStrike` came earlier from this external function /// @audit internal function `computeExercisedAmounts` came earlier from this external function /// @audit internal function `convertCollateralData` came earlier from this external function /// @audit internal function `convertCollateralData` came earlier from this external function /// @audit internal function `convertNotional` came earlier from this external function /// @audit internal function `convert0to1` came earlier from this external function /// @audit internal function `convert1to0` came earlier from this external function /// @audit internal function `convert0to1` came earlier from this external function /// @audit internal function `convert1to0` came earlier from this external function /// @audit internal function `getAmountsMoved` came earlier from this external function /// @audit internal function `_calculateIOAmounts` came earlier from this external function 770: TokenId[] memory positionIdList, /// @audit internal function `getPoolId` came earlier from this external function /// @audit internal function `incrementPoolPattern` came earlier from this external function /// @audit internal function `updatePositionsHash` came earlier from this external function /// @audit internal function `getLiquidityChunk` came earlier from this external function /// @audit internal function `getTicks` came earlier from this external function /// @audit internal function `getRangesFromStrike` came earlier from this external function /// @audit internal function `computeExercisedAmounts` came earlier from this external function /// @audit internal function `convertCollateralData` came earlier from this external function /// @audit internal function `convertCollateralData` came earlier from this external function /// @audit internal function `convertNotional` came earlier from this external function /// @audit internal function `convert0to1` came earlier from this external function /// @audit internal function `convert1to0` came earlier from this external function /// @audit internal function `convert0to1` came earlier from this external function /// @audit internal function `convert1to0` came earlier from this external function /// @audit internal function `getAmountsMoved` came earlier from this external function /// @audit internal function `_calculateIOAmounts` came earlier from this external function 919: LeftRightSigned refundValues,
Github: [75, 125, 168, 241, 653, 770, 919]
</details><a name="NC-32"></a>
As the Solidity Style Guide suggests: functions and modifiers should be named in mixedCase style.
There is one instance of this issue:
File: ./contracts/SemiFungiblePositionManager.sol 329: /// @param poolId The poolId of the pool to remove the reentrancy lock from
Github: [329]
<a name="NC-33"></a>
File: ./contracts/CollateralTracker.sol /// @audit `positionBalanceArray` 1147: tokenData = _getAccountMargin(user, currentTick, positionBalanceArray, premiumAllPositions);
Github: [1147]
File: ./contracts/PanopticPool.sol /// @audit `positionIdList` 381: function calculateAccumulatedFeesBatch( /// @audit `positionIdList` 410: function calculatePortfolioValue( /// @audit `positionIdList` 547: function mintOptions( /// @audit `newPositionIdList` 569: function burnOptions( /// @audit `positionIdList` /// @audit `newPositionIdList` 586: function burnOptions( /// @audit `positionIdList` 887: function _validateSolvency( /// @audit `positionIdListLiquidator` 1017: function liquidate( /// @audit `positionIdListExercisee` 1179: function forceExercise( /// @audit `positionIdList` 1290: function _checkSolvencyAtTick( /// @audit `collectedByLeg` 1666: function _updateSettlementPostMint( /// @audit `premiumAccumulators` 1757: function _getAvailablePremium( /// @audit `collectedByLeg` 1833: function _updateSettlementPostBurn(
Github: [381, 410, 547, 569, 586, 887, 1017, 1179, 1290, 1666, 1757, 1833]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `ids` /// @audit `amounts` 583: // transfer the token (note that all state updates are completed before reentrancy is possible)
Github: [583]
File: ./contracts/libraries/FeesCalc.sol /// @audit `positionIdList` 50: ) external view returns (int256 value0, int256 value1) {
Github: [50]
File: ./contracts/libraries/Math.sol /// @audit `arr` 753: function quickSort(int256[] memory arr, int256 left, int256 right) internal pure {
Github: [753]
File: ./contracts/libraries/PanopticMath.sol /// @audit `positionIdList` /// @audit `premiasByLeg` 770: TokenId[] memory positionIdList,
Github: [770]
</details>File: ./contracts/tokens/ERC1155Minimal.sol /// @audit `ids` /// @audit `amounts` 130: function safeBatchTransferFrom( /// @audit `ids` 178: function balanceOfBatch(
<a name="NC-34"></a>
Consider breaking down these blocks into more manageable units, by splitting things into utility functions, by reducing nesting, and by using early returns
There are <b>3</b> instances of this issue:
File: ./contracts/SemiFungiblePositionManager.sol /// @audit Number of blocks: 14 992: // s_accountLiquidity is a LeftRight. The right slot represents the liquidity currently sold (added) in the AMM owned by the user
Github: [992]
File: ./contracts/libraries/Math.sol /// @audit Number of blocks: 10 340: function mulDiv(
Github: [340]
File: ./contracts/libraries/PanopticMath.sol /// @audit Number of blocks: 12 770: TokenId[] memory positionIdList,
Github: [770]
<a name="NC-35"></a>
Some lines use // x
and some use //x
. The instances below point out the usages that don't follow the majority, within each file
File: ./contracts/CollateralTracker.sol 42: //////////////////////////////////////////////////////////////*/ 67: //////////////////////////////////////////////////////////////*/ 85: //////////////////////////////////////////////////////////////*/ 106: //////////////////////////////////////////////////////////////*/ 128: //////////////////////////////////////////////////////////////*/ 166: //////////////////////////////////////////////////////////////*/ 176: //////////////////////////////////////////////////////////////*/ 268: //////////////////////////////////////////////////////////////*/ 317: //////////////////////////////////////////////////////////////*/ 357: //////////////////////////////////////////////////////////////*/ 630: //////////////////////////////////////////////////////////////*/ 857: ////////////////////////////////////////////////////////////////////*/ 987: //////////////////////////////////////////////////////////////*/ 1118: //compute total commission amount = commission rate + spread fee 1130: //////////////////////////////////////////////////////////////*/
Github: [42, 67, 85, 106, 128, 166, 176, 268, 317, 357, 630, 857, 987, 1118, 1130]
File: ./contracts/PanopticFactory.sol 29: //////////////////////////////////////////////////////////////*/ 54: //////////////////////////////////////////////////////////////*/ 60: //////////////////////////////////////////////////////////////*/ 93: //////////////////////////////////////////////////////////////*/ 106: //////////////////////////////////////////////////////////////*/ 143: //////////////////////////////////////////////////////////////*/ 165: //////////////////////////////////////////////////////////////*/ 199: //////////////////////////////////////////////////////////////*/ 280: //////////////////////////////////////////////////////////////*/ 415: //////////////////////////////////////////////////////////////*/
Github: [29, 54, 60, 93, 106, 143, 165, 199, 280, 415]
File: ./contracts/PanopticPool.sol 30: //////////////////////////////////////////////////////////////*/ 99: //////////////////////////////////////////////////////////////*/ 183: //////////////////////////////////////////////////////////////*/ 276: //////////////////////////////////////////////////////////////*/ 331: //////////////////////////////////////////////////////////////*/ 519: //////////////////////////////////////////////////////////////*/ 538: //////////////////////////////////////////////////////////////*/ 605: //////////////////////////////////////////////////////////////*/ 786: //////////////////////////////////////////////////////////////*/ 1009: //////////////////////////////////////////////////////////////*/ 1282: //////////////////////////////////////////////////////////////*/ 1360: //////////////////////////////////////////////////////////////*/ 1421: //////////////////////////////////////////////////////////////*/ 1456: //////////////////////////////////////////////////////////////*/ 1579: //////////////////////////////////////////////////////////////*/
Github: [30, 99, 183, 276, 331, 519, 538, 605, 786, 1009, 1282, 1360, 1421, 1456, 1579]
File: ./contracts/SemiFungiblePositionManager.sol 75: //////////////////////////////////////////////////////////////*/ 106: //////////////////////////////////////////////////////////////*/ 122: //////////////////////////////////////////////////////////////*/ 141: //////////////////////////////////////////////////////////////*/ 299: //////////////////////////////////////////////////////////////*/ 337: //////////////////////////////////////////////////////////////*/ 395: //////////////////////////////////////////////////////////////*/ 461: //////////////////////////////////////////////////////////////*/ 531: //////////////////////////////////////////////////////////////*/ 610: //construct the positionKey for the from and to addresses 643: //update+store liquidity and fee values between accounts 657: //////////////////////////////////////////////////////////////*/ 821: //compute the swap amount, set as positive (exact input) 1411: //////////////////////////////////////////////////////////////*/
Github: [75, 106, 122, 141, 299, 337, 395, 461, 531, 610, 643, 657, 821, 1411]
File: ./contracts/libraries/Math.sol 19: //////////////////////////////////////////////////////////////*/ 121: //////////////////////////////////////////////////////////////*/ 185: //////////////////////////////////////////////////////////////*/ 291: //////////////////////////////////////////////////////////////*/ 332: //////////////////////////////////////////////////////////////*/ 372: /////////////////////////////////////////////// 374: /////////////////////////////////////////////// 486: /////////////////////////////////////////////// 488: /////////////////////////////////////////////// 549: /////////////////////////////////////////////// 551: /////////////////////////////////////////////// 626: /////////////////////////////////////////////// 628: /////////////////////////////////////////////// 703: /////////////////////////////////////////////// 705: /////////////////////////////////////////////// 746: //////////////////////////////////////////////////////////////*/
Github: [19, 121, 185, 291, 332, 372, 374, 486, 488, 549, 551, 626, 628, 703, 705, 746]
File: ./contracts/libraries/PanopticMath.sol 30: //////////////////////////////////////////////////////////////*/ 83: //////////////////////////////////////////////////////////////*/ 272: //////////////////////////////////////////////////////////////*/ 383: //////////////////////////////////////////////////////////////*/ 639: //////////////////////////////////////////////////////////////*/
Github: [30, 83, 272, 383, 639]
File: ./contracts/libraries/SafeTransferLib.sol 14: //////////////////////////////////////////////////////////////*/
Github: [14]
File: ./contracts/tokens/ERC1155Minimal.sol 14: //////////////////////////////////////////////////////////////*/ 52: //////////////////////////////////////////////////////////////*/ 62: //////////////////////////////////////////////////////////////*/ 76: //////////////////////////////////////////////////////////////*/ 195: //////////////////////////////////////////////////////////////*/ 208: //////////////////////////////////////////////////////////////*/
Github: [14, 52, 62, 76, 195, 208]
File: ./contracts/tokens/ERC20Minimal.sol 12: //////////////////////////////////////////////////////////////*/ 28: //////////////////////////////////////////////////////////////*/ 43: //////////////////////////////////////////////////////////////*/ 117: //////////////////////////////////////////////////////////////*/
File: ./contracts/types/LeftRight.sol 34: //////////////////////////////////////////////////////////////*/ 96: //////////////////////////////////////////////////////////////*/ 142: //////////////////////////////////////////////////////////////*/
File: ./contracts/types/LiquidityChunk.sol 63: //////////////////////////////////////////////////////////////*/ 166: //////////////////////////////////////////////////////////////*/
</details>File: ./contracts/types/TokenId.sol 82: //////////////////////////////////////////////////////////////*/ 177: //////////////////////////////////////////////////////////////*/ 358: //////////////////////////////////////////////////////////////*/ 495: //////////////////////////////////////////////////////////////*/
<a name="NC-36"></a>
NatSpec must begin with ///
or use /* ... */
syntax
There are <b>7</b> instances of this issue:
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` 790: // implement a single "netting" swap. Thank you @danrobinson for this puzzle/idea 836: // @dev note this triggers our swap callback function 903: // @dev see `contracts/types/LiquidityChunk.sol`
Github: [358, 360, 603, 790, 836, 903]
File: ./contracts/libraries/PanopticMath.sol 98: // @dev 0 ^ x = x
Github: [98]
<a name="NC-37"></a>
In Solidity, complex inheritance structures can obfuscate code understanding, introducing potential security risks. Multiple inheritance, especially with overlapping function names or state variables, can cause unintentional overrides or ambiguous behavior.
Resolution: Strive for linear and simple inheritance chains. Avoid diamond or circular inheritance patterns. Clearly document the purpose and relationships of base contracts, ensuring that overrides are intentional. Tools like Remix or Hardhat can visualize inheritance chains, assisting in verification. Keeping inheritance streamlined aids in better code readability, reduces potential errors, and ensures smoother audits and upgrades.
There are <b>3</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
Github: [36]
File: ./contracts/PanopticPool.sol 27: contract PanopticPool is ERC1155Holder, Multicall {
Github: [27]
File: ./contracts/SemiFungiblePositionManager.sol 82: /// @notice Emitted when a position is destroyed/burned /// @dev Recipient is used to track whether it was burned directly by the user or through an option contract
Github: [82]
<a name="NC-38"></a>
Lack of brace spacing in coding refers to the absence of spaces around braces, which can hinder code readability. In Solidity, as in many programming languages, spacing can enhance the visual distinction between different parts of the code, making it easier to follow. A lack of spacing can lead to a dense, confusing appearance. The resolution to this issue is to follow a consistent style guide that defines rules for brace spacing. By including spaces around braces, such as { statement }
instead of {statement}
, developers can ensure that the code is more legible and maintainable, especially in larger codebases.
There is one instance of this issue:
File: ./contracts/PanopticFactory.sol 398: poolFeatures: CallbackLib.PoolFeatures({token0: token0, token1: token1, fee: fee}),
Github: [398]
<a name="NC-39"></a>
This includes: large code bases, or code with lots of inline-assembly, complicated math, or complicated interactions between multiple contracts. Invariant fuzzers such as Echidna require the test writer to come up with invariants which should not be violated under any circumstances, and the fuzzer tests various inputs and function calls to ensure that the invariants always hold. Even code with 100% code coverage can still have bugs due to the order of the operations a user performs, and invariant fuzzers may help significantly.
There is one instance of this issue:
Global finding
Github: [Global]
<a name="NC-40"></a>
Use a scientific notation rather than decimal literals (e.g. 1e6
instead of 1000000
), for better code readability.
File: ./contracts/CollateralTracker.sol 77: uint256 internal constant DECIMALS = 10_000; 81: int128 internal constant DECIMALS_128 = 10_000; 204: 10_000 + 206: 10_000 ** 2 + 208: 10_000 ** 3
Github: [77, 81, 204, 206, 208]
File: ./contracts/PanopticPool.sol 174: uint256 internal constant NO_BUFFER = 10_000; 1329: return balanceCross >= Math.unsafeDivRoundingUp(thresholdCross * buffer, 10_000);
</details>File: ./contracts/types/TokenId.sol 63: 0x100_000000000100_000000000100_000000000100_0000000000000000; 63: 0x100_000000000100_000000000100_000000000100_0000000000000000; 63: 0x100_000000000100_000000000100_000000000100_0000000000000000; 63: 0x100_000000000100_000000000100_000000000100_0000000000000000; 75: 0xFFFFFFFFF200_FFFFFFFFF200_FFFFFFFFF200_FFFFFFFFF200_0000000000000000;
<a name="NC-41"></a>
Large hardcoded numbers in code can be difficult to read. Consider using underscores for number literals to improve readability (e.g 1234567
-> 1_234_567
). Consider using an underscore for every third digit from the right.
File: ./contracts/CollateralTracker.sol 200: int256 ratioTick = (int256(_sellerCollateralRatio) - 2000); 202: 2230 + 203: (12500 * ratioTick) / 205: (7812 * ratioTick ** 2) / 207: (6510 * ratioTick ** 3) /
Github: [200, 202, 203, 205, 207]
File: ./contracts/PanopticPool.sol 160: int256 internal constant MAX_SLOW_FAST_DELTA = 1800;
Github: [160]
File: ./contracts/libraries/Constants.sol 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; 22: 1461446703485210103287273052203988822378723970342;
</details>File: ./contracts/types/TokenId.sol 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36))
<a name="NC-42"></a>
File: ./contracts/CollateralTracker.sol /// @audit 84 lines 650: function exerciseCost( int24 currentTick, int24 oracleTick, TokenId positionId, uint128 positionBalance, LeftRightSigned longAmounts ) external view returns (LeftRightSigned exerciseFees) { // find the leg furthest to the strike price 'currentTick'; this will have the lowest exercise cost /// @audit 56 lines 917: ┌────────┐ │Panoptic│ │Pool │ └────┬───┘ │(1) convert 'assets' to shares (this ERC20 contract) /// @audit 119 lines 1319: uint256 tokenType = tokenId.tokenType(index); // compute the total amount of funds moved for that position LeftRightUnsigned amountsMoved = PanopticMath.getAmountsMoved(tokenId, positionSize, index); // amount moved is right slot if tokenType=0, left slot otherwise /// @audit 79 lines 1518: LeftRightUnsigned amountsMoved = PanopticMath.getAmountsMoved(tokenId, positionSize, index); // compute the total amount of funds moved for the position's partner leg LeftRightUnsigned amountsMovedPartner = PanopticMath.getAmountsMoved(
Github: [650, 917, 1319, 1518]
File: ./contracts/PanopticFactory.sol /// @audit 66 lines 210: function deployNewPool( address token0, address token1, uint24 fee, bytes32 salt ) external returns (PanopticPool newPoolContract) { /// @audit 76 lines 335: function _mintFullRange( IUniswapV3Pool v3Pool, address token0, address token1, uint24 fee ) internal returns (uint256, uint256) {
File: ./contracts/PanopticPool.sol /// @audit 166 lines 280: constructor(SemiFungiblePositionManager _sfpm) { /// @audit 63 lines 429: function _calculateAccumulatedPremia( address user, TokenId[] calldata positionIdList, bool computeAllPremia, bool includePendingPremium, int24 atTick ) internal view returns (LeftRightSigned portfolioPremium, uint256[2][] memory balances) { /// @audit 53 lines 614: function _mintOptions( TokenId[] calldata positionIdList, uint128 positionSize, uint64 effectiveLiquidityLimitX32, int24 tickLimitLow, int24 tickLimitHigh ) internal { /// @audit 59 lines 887: function _validateSolvency( address user, TokenId[] calldata positionIdList, uint256 buffer ) internal view returns (uint256 medianData) { /// @audit 154 lines 1017: function liquidate( TokenId[] calldata positionIdListLiquidator, address liquidatee, LeftRightUnsigned delegations, TokenId[] calldata positionIdList ) external { /// @audit 99 lines 1179: function forceExercise( address account, TokenId[] calldata touchedId, TokenId[] calldata positionIdListExercisee, TokenId[] calldata positionIdListExercisor ) external { /// @audit 72 lines 1503: function _getPremia( TokenId tokenId, uint128 positionSize, address owner, bool computeAllPremia, int24 atTick ) internal view returns ( LeftRightSigned[4] memory premiaByLeg, uint256[2][4] memory premiumAccumulatorsByLeg ) { uint256 numLegs = tokenId.countLegs(); /// @audit 72 lines 1587: function settleLongPremium( TokenId[] calldata positionIdList, address owner, uint256 legIndex ) external { _validatePositionList(owner, positionIdList, 0); /// @audit 80 lines 1666: function _updateSettlementPostMint( TokenId tokenId, LeftRightUnsigned[4] memory collectedByLeg, uint128 positionSize ) internal { uint256 numLegs = tokenId.countLegs(); /// @audit 147 lines 1833: function _updateSettlementPostBurn( address owner, TokenId tokenId, LeftRightUnsigned[4] memory collectedByLeg, uint128 positionSize, bool commitLongSettled ) internal returns (LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg) { uint256 numLegs = tokenId.countLegs();
Github: [280, 429, 614, 887, 1017, 1179, 1503, 1587, 1666, 1833]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit 60 lines 611: bytes32 positionKey_from = keccak256( abi.encodePacked( address(univ3pool), /// @audit 96 lines 786: // this is simply a convenience feature, and should be treated as such if ((itm0 != 0) && (itm1 != 0)) { (uint160 sqrtPriceX96, , , , , , ) = _univ3pool.slot0(); /// @audit 78 lines 897: // We loop in reverse order if burning a position so that any dependent long liquidity is returned to the pool first, // allowing the corresponding short liquidity to be removed _leg = _isBurn ? numLegs - leg - 1 : leg; } // for this _leg index: extract the liquidity chunk: a 256bit word containing the liquidity amount and upper/lower tick /// @audit 146 lines 992: // s_accountLiquidity is a LeftRight. The right slot represents the liquidity currently sold (added) in the AMM owned by the user // the left slot represents the amount of liquidity currently bought (removed) that has been removed from the AMM - the user owes it to a seller // the reason why it is called "removedLiquidity" is because long options are created by removing -ie.short selling LP positions /// @audit 58 lines 1288: uint128(amountToCollect.rightSlot()), uint128(amountToCollect.leftSlot()) ); // moved will be negative if the leg was long (funds left the caller, don't count it in collected fees) uint128 collected0; uint128 collected1; unchecked { collected0 = movedInLeg.rightSlot() < 0 /// @audit 86 lines 1357: totalLiquidity * 2 ** 64, netLiquidity ** 2 ); } { uint128 premium0X64_owed; uint128 premium1X64_owed; { // compute the owed premium (from Eqn 3) /// @audit 74 lines 1479: // oldFeesGrowth is the last stored update of fee growth within the position range in the past (feeGrowthRange*liquidityChunk.liquidity()) (s_accountFeesBase[positionKey]) LeftRightSigned feesBase = FeesCalc.calculateAMMSwapFees( _univ3pool, atTick, _tickLower, _tickUpper,
Github: [611, 786, 897, 992, 1288, 1357, 1479]
File: ./contracts/libraries/FeesCalc.sol /// @audit 78 lines 135: ) internal view returns (uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) { // Get feesGrowths from the option position's lower+upper ticks // lowerOut0: For token0: fee growth per unit of liquidity on the _other_ side of tickLower (relative to currentTick) // only has relative meaning, not absolute — the value depends on when the tick is initialized
Github: [135]
File: ./contracts/libraries/Math.sol /// @audit 53 lines 128: function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160) { unchecked { /// @audit 93 lines 340: function mulDiv( uint256 a, uint256 b, uint256 denominator ) internal pure returns (uint256 result) { unchecked { /// @audit 57 lines 458: function mulDiv64(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { /// @audit 57 lines 521: function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { /// @audit 57 lines 598: function mulDiv128(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { /// @audit 57 lines 675: function mulDiv192(uint256 a, uint256 b) internal pure returns (uint256) { unchecked {
Github: [128, 340, 458, 521, 598, 675]
</details>File: ./contracts/libraries/PanopticMath.sol /// @audit 65 lines 168: function computeInternalMedian( uint256 observationIndex, uint256 observationCardinality, uint256 period, uint256 medianData, IUniswapV3Pool univ3pool ) external view returns (int24 medianTick, uint256 updatedMedianData) { unchecked { /// @audit 103 lines 653: LeftRightUnsigned tokenData1, uint160 sqrtPriceX96Twap, uint160 sqrtPriceX96Final, LeftRightSigned netExchanged, LeftRightSigned premia ) external pure returns (int256 bonus0, int256 bonus1, LeftRightSigned) { unchecked { // compute bonus as min(collateralBalance/2, required-collateralBalance) /// @audit 140 lines 770: TokenId[] memory positionIdList, LeftRightSigned[4][] memory premiasByLeg, LeftRightSigned collateralRemaining, CollateralTracker collateral0, CollateralTracker collateral1, uint160 sqrtPriceX96Final, mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens ) external returns (int256, int256) { unchecked { // get the amount of premium paid by the liquidatee LeftRightSigned longPremium;
<a name="NC-43"></a>
Magic numbers are hard-coded values in code that can make it difficult for developers and maintainers to understand the code, and can also cause confusion or errors. To improve the readability and maintainability of code, it is recommended to replace magic numbers with constants that have good readability.
<details> <summary> There are <b>108</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol // @audit `2000` 200: int256 ratioTick = (int256(_sellerCollateralRatio) - 2000); // @audit `2230` 202: 2230 + // @audit `12500` 203: (12500 * ratioTick) / // @audit `10_000` 204: 10_000 + // @audit `7812` 205: (7812 * ratioTick ** 2) / // @audit `2` 205: (7812 * ratioTick ** 2) / // @audit `10_000` 206: 10_000 ** 2 + // @audit `2` 206: 10_000 ** 2 + // @audit `6510` 207: (6510 * ratioTick ** 3) / // @audit `3` 207: (6510 * ratioTick ** 3) / // @audit `10_000` 208: 10_000 ** 3 // @audit `3` 209: ); // @audit `10` 234: totalSupply = 10 ** 6; // @audit `6` 234: totalSupply = 10 ** 6; // @audit `100` 249: _poolFee = fee / 100; // @audit `2` 672: ) // @audit `2` 779: utilization = -utilization; // @audit `2` 844: } // @audit `2` 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 `2` 1151: /// NOTE: It's up to the caller to confirm from the returned result that the account has enough collateral. // @audit `2` 1169: // get all collateral required for the incoming list of positions // @audit `2` 1207: uint256 totalIterations = positionBalanceArray.length; // @audit `64` 1339: if (isLong == 0) { // @audit `2` 1370: // compute the collateral requirement depending on whether the position is ITM & out-of-range or ITM and in-range: // @audit `2` 1374: ((atTick < tickLower) && (tokenType == 1)) || // strike ITM but out of range price < lowerTick for tokenType=1 // @audit `64` 1593: /// @dev A strangle can only have only one of its leg tested at the same time, so this reduces the total risk and collateral requirement. // @audit `64` 1641: strangleRequired = _getRequiredCollateralSingleLegNoPartner( // @audit `64` 1651:
Github: [200, 202, 203, 204, 205, 205, 206, 206, 207, 207, 208, 209, 234, 234, 249, 672, 779, 844, 851, 1151, 1169, 1207, 1339, 1370, 1374, 1593, 1641, 1651]
File: ./contracts/PanopticPool.sol // @audit `216` 309: (uint256(block.timestamp) << 216) + // @audit `0xF590A6F276170D89E9F276170D89E9F276170D89E9000000000000` 312: (uint256(0xF590A6F276170D89E9F276170D89E9F276170D89E9000000000000)) + // @audit `24` 313: (uint256(uint24(currentTick)) << 24) + // add to slot 4 // @audit `64` 370: poolUtilization1 = uint64(balanceData.leftSlot() >> 64); // @audit `2` 385: ) external view returns (int128 premium0, int128 premium1, uint256[2][] memory) { // @audit `2` 390: (LeftRightSigned premia, uint256[2][] memory balances) = _calculateAccumulatedPremia( // @audit `2` 435: ) internal view returns (LeftRightSigned portfolioPremium, uint256[2][] memory balances) { // @audit `2` 437: balances = new uint256[2][](pLength); // @audit `4` 448: LeftRightSigned[4] memory premiaByLeg, // @audit `2` 449: uint256[2][4] memory premiumAccumulatorsByLeg // @audit `4` 449: uint256[2][4] memory premiumAccumulatorsByLeg // @audit `4` 685: (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) = SFPM // @audit `64` 730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64)); // @audit `4` 800: ) internal returns (LeftRightSigned netPaid, LeftRightSigned[4][] memory premiasByLeg) { // @audit `4` 801: premiasByLeg = new LeftRightSigned[4][](positionIdList.length); // @audit `4` 832: ) internal returns (LeftRightSigned paidAmounts, LeftRightSigned[4] memory premiaByLeg) { // @audit `4` 966: LeftRightSigned[4] memory premiaByLeg, // @audit `4` 970: (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) = SFPM // @audit `2` 1038: uint256[2][] memory positionBalanceArray = new uint256[2][](positionIdList.length); // @audit `2` 1038: uint256[2][] memory positionBalanceArray = new uint256[2][](positionIdList.length); // @audit `4` 1080: LeftRightSigned[4][] memory premiasByLeg; // @audit `2` 1299: uint256[2][] memory positionBalanceArray // @audit `10_000` 1329: return balanceCross >= Math.unsafeDivRoundingUp(thresholdCross * buffer, 10_000); // @audit `2` 1348: Math.mulDiv(uint256(tokenData1.rightSlot()), 2 ** 96, sqrtPriceX96) + // @audit `96` 1348: Math.mulDiv(uint256(tokenData1.rightSlot()), 2 ** 96, sqrtPriceX96) + // @audit `2` 1353: Math.mulDivRoundingUp(uint256(tokenData1.leftSlot()), 2 ** 96, sqrtPriceX96) + // @audit `96` 1353: Math.mulDivRoundingUp(uint256(tokenData1.leftSlot()), 2 ** 96, sqrtPriceX96) + // @audit `248` 1415: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen(); // @audit `248` 1446: } // @audit `2` 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; // @audit `32` 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; // @audit `4` 1513: LeftRightSigned[4] memory premiaByLeg, // @audit `2` 1514: uint256[2][4] memory premiumAccumulatorsByLeg // @audit `4` 1514: uint256[2][4] memory premiumAccumulatorsByLeg // @audit `2` 1553: ) // @audit `64` 1553: ) // @audit `2` 1562: ) // @audit `64` 1562: ) // @audit `3` 1596: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg(); // @audit `2` 1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) // @audit `2` 1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); // @audit `64` 1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); // @audit `64` 1638: // deduct the paid premium tokens from the owner's balance and add them to the cumulative settled token delta // @audit `4` 1668: LeftRightUnsigned[4] memory collectedByLeg, // @audit `2` 1706: uint256[2] memory grossCurrent; // @audit `2` 1762: uint256[2] memory premiumAccumulators // @audit `2` 1769: totalLiquidity) / 2 ** 64; // @audit `64` 1770: uint256 accumulated1 = ((premiumAccumulators[1] - grossPremiumLast.leftSlot()) * // @audit `2` 1771: totalLiquidity) / 2 ** 64; // @audit `64` 1773: return ( // @audit `4` 1836: LeftRightUnsigned[4] memory collectedByLeg, // @audit `4` 1839: ) internal returns (LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg) { // @audit `2` 1841: uint256[2][4] memory premiumAccumulatorsByLeg; // @audit `4` 1841: uint256[2][4] memory premiumAccumulatorsByLeg; // @audit `2` 1923: uint256[2][4] memory _premiumAccumulatorsByLeg = premiumAccumulatorsByLeg; // @audit `4` 1923: uint256[2][4] memory _premiumAccumulatorsByLeg = premiumAccumulatorsByLeg; // @audit `2` 1942: )) + int256(legPremia.rightSlot() * 2 ** 64), // @audit `64` 1943: 0 // @audit `2` 1959: )) + int256(legPremia.leftSlot()) * 2 ** 64, // @audit `64` 1960: 0
Github: [309, 312, 313, 370, 385, 390, 435, 437, 448, 449, 449, 685, 730, 800, 801, 832, 966, 970, 1038, 1038, 1080, 1299, 1329, 1348, 1348, 1353, 1353, 1415, 1446, 1487, 1487, 1513, 1514, 1514, 1553, 1553, 1562, 1562, 1596, 1635, 1636, 1636, 1638, 1668, 1706, 1762, 1769, 1770, 1771, 1773, 1836, 1839, 1841, 1841, 1923, 1923, 1942, 1943, 1959, 1960]
File: ./contracts/SemiFungiblePositionManager.sol // @audit `2` 405: bytes calldata data // @audit `255` 405: bytes calldata data // @audit `4` 499: /// @param positionSize The number of contracts minted, expressed in terms of the asset // @audit `4` 533: /// @notice Transfer a single token from one user to another // @audit `4` 716: // if the in-the-money amount is not zero (i.e. positions were minted ITM) and the user did provide tick limits LOW > HIGH, then swap necessary amounts // @audit `4` 902: // for this _leg index: extract the liquidity chunk: a 256bit word containing the liquidity amount and upper/lower tick // @audit `2` 1395: .toUint128Capped(); // @audit `64` 1395: .toUint128Capped(); // @audit `2` 1396: premium1X64_gross = Math // @audit `2` 1398: .toUint128Capped(); // @audit `64` 1398: .toUint128Capped(); // @audit `2` 1400: deltaPremiumGross = LeftRightUnsigned // @audit `2` 1410: SFPM PROPERTIES // @audit `2` 1420: /// @return accountLiquidities The amount of liquidity that has been shorted/added to the Uniswap contract (netLiquidity:removedLiquidity -> rightSlot:leftSlot) // @audit `2` 1425: int24 tickLower, // @audit `2` 1425: int24 tickLower, // @audit `2` 1428: /// Extract the account liquidity for a given uniswap pool, owner, token type, and ticks // @audit `2` 1429: /// @dev tokenType input here is the asset of the positions minted, this avoids put liquidity to be used for call, and vice-versa
Github: [405, 405, 499, 533, 716, 902, 1395, 1395, 1396, 1398, 1398, 1400, 1410, 1420, 1425, 1425, 1428, 1429]
</details>File: ./contracts/tokens/ERC1155Minimal.sol // @audit `0x01ffc9a7` 202: interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 // @audit `0xd9b67a26` 203: interfaceId == 0xd9b67a26; // ERC165 Interface ID for ERC1155
<a name="NC-44"></a>
e.g. @dev
or @notice
, and it must appear above the contract definition braces in order to be identified by the compiler as NatSpec.
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 178: constructor( uint256 _commissionFee, uint256 _sellerCollateralRatio, uint256 _buyerCollateralRatio, int256 _forceExerciseCost, uint256 _targetPoolUtilization, uint256 _saturatedPoolUtilization, uint256 _ITMSpreadMultiplier ) { COMMISSION_FEE = _commissionFee;
Github: [178]
File: ./contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
Github: [6]
<a name="NC-45"></a>
@author
from contract declarationSome contract definitions have an incomplete NatSpec: add a @author
notation to improve the code documentation.
There are <b>3</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
Github: [36]
File: ./contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
Github: [6]
File: ./contracts/types/LiquidityChunk.sol 54: uint256 internal constant CLEAR_TL_MASK =
Github: [54]
<a name="NC-46"></a>
@dev
from contract/event/function/modifier declarationsSome contract definitions have an incomplete NatSpec: add a @dev
notation to improve the code documentation.
File: ./contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall { 49: event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); 57: event Withdraw( 169: modifier onlyPanopticPool() { 178: constructor( 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) { 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) { 444: function maxMint(address) external view returns (uint256 maxShares) { 453: function previewMint(uint256 shares) public view returns (uint256 assets) { 518: function previewWithdraw(uint256 assets) public view 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( 917: ┌────────┐ 1002: // current available assets belonging to PLPs (updated after settlement) excluding any premium paid 1104: // intrinsic value is the amount that need to be exchanged due to minting in-the-money 1254: unchecked {
Github: [36, 49, 57, 169, 178, 289, 303, 310, 361, 379, 386, 392, 399, 444, 453, 518, 572, 581, 591, 917, 1002, 1104, 1254]
File: ./contracts/PanopticFactory.sol 26: contract PanopticFactory is Multicall { 34: event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); 43: event PoolDeployed( 115: constructor( 134: function initialize(address _owner) public { 147: function transferOwnership(address newOwner) external { 159: function owner() external view returns (address) { 335: function _mintFullRange( 420: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
Github: [26, 34, 43, 115, 134, 147, 159, 335, 420]
File: ./contracts/PanopticPool.sol 62: event PremiumSettled( 280: constructor(SemiFungiblePositionManager _sfpm) { 352: function optionPositionBalance( 429: function _calculateAccumulatedPremia( 499: function _getSlippageLimits( 522: function pokeMedian() external { 547: function mintOptions( 614: function _mintOptions( 794: function _burnAllOptionsFrom( 826: function _burnOptions( 859: function _updatePositionDataBurn(address owner, TokenId tokenId) internal { 955: function _burnAndHandleExercise( 1290: function _checkSolvencyAtTick( 1339: function _getSolvencyBalances( 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) { 1450: function getUniV3TWAP() internal view returns (int24 twapTick) { 1465: function _checkLiquiditySpread( 1503: function _getPremia(
Github: [62, 280, 352, 429, 499, 522, 547, 614, 794, 826, 859, 955, 1290, 1339, 1425, 1431, 1437, 1444, 1450, 1465, 1503]
File: ./contracts/SemiFungiblePositionManager.sol 90: uint128 positionSize 347: /// @param token0 The contract address of token0 of the pool 355: if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized(); 524: slippageTickLimitHigh, 708: (totalMoved, collectedByLeg, itmAmounts) = _createPositionInAMM( 786: // this is simply a convenience feature, and should be treated as such 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 1288: uint128(amountToCollect.rightSlot()),
Github: [90, 347, 355, 524, 708, 786, 1137, 1288]
File: ./contracts/libraries/CallbackLib.sol 12: library CallbackLib { 30: function validateCallback(
File: ./contracts/libraries/Constants.sol 7: library Constants {
Github: [7]
File: ./contracts/libraries/Errors.sol 7: library Errors {
Github: [7]
File: ./contracts/libraries/FeesCalc.sol 50: ) external view returns (int256 value0, int256 value1) {
Github: [50]
File: ./contracts/libraries/InteractionHelper.sol 17: library InteractionHelper { 24: function doApprovals( 91: function computeSymbol( 107: function computeDecimals(address token) external view returns (uint8) {
File: ./contracts/libraries/Math.sol 13: library Math { 25: function min24(int24 a, int24 b) internal pure returns (int24) { 33: function max24(int24 a, int24 b) internal pure returns (int24) { 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) { 91: function mostSignificantNibble(uint160 x) internal pure returns (uint256 r) { 207: function getAmount1ForLiquidity(LiquidityChunk liquidityChunk) internal pure returns (uint256) { 221: function getAmountsForLiquidity( 296: function toUint128(uint256 toDowncast) internal pure returns (uint128 downcastedInt) { 302: function toUint128Capped(uint256 toDowncast) internal pure returns (uint128 downcastedInt) { 311: function toInt128(uint128 toCast) internal pure returns (int128 downcastedInt) { 318: function toInt128(int256 toCast) internal pure returns (int128 downcastedInt) { 325: function toInt256(uint256 toCast) internal pure returns (int256) { 440: function mulDivRoundingUp( 458: function mulDiv64(uint256 a, uint256 b) internal pure returns (uint256) { 521: function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256) { 584: function mulDiv96RoundingUp(uint256 a, uint256 b) internal pure returns (uint256 result) { 598: function mulDiv128(uint256 a, uint256 b) internal pure returns (uint256) { 661: function mulDiv128RoundingUp(uint256 a, uint256 b) internal pure returns (uint256 result) { 675: function mulDiv192(uint256 a, uint256 b) internal pure returns (uint256) { 738: function unsafeDivRoundingUp(uint256 a, uint256 b) internal pure returns (uint256 result) { 753: function quickSort(int256[] memory arr, int256 left, int256 right) internal pure { 776: function sort(int256[] memory data) internal pure returns (int256[] memory) {
Github: [13, 25, 33, 41, 49, 57, 65, 91, 207, 221, 296, 302, 311, 318, 325, 440, 458, 521, 584, 598, 661, 675, 738, 753, 776]
File: ./contracts/libraries/PanopticMath.sol 18: library PanopticMath { 59: function incrementPoolPattern(uint64 poolId) internal pure returns (uint64) { 75: function numberOfLeadingHexZeros(address addr) external pure returns (uint256) { 292: uint128 positionSize 342: ) internal pure returns (int24 tickLower, int24 tickUpper) { 393: ) internal pure returns (LeftRightSigned longAmounts, LeftRightSigned shortAmounts) { 421: LeftRightUnsigned tokenData1, 447: LeftRightUnsigned tokenData1, 577: uint256 legIndex 610: uint256 legIndex 653: LeftRightUnsigned tokenData1, 919: LeftRightSigned refundValues,
Github: [18, 59, 75, 292, 342, 393, 421, 447, 577, 610, 653, 919]
File: ./contracts/libraries/SafeTransferLib.sol 21: function safeTransferFrom(address token, address from, address to, uint256 amount) internal { 52: function safeTransfer(address token, address to, uint256 amount) internal {
File: ./contracts/multicall/Multicall.sol 12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
Github: [12]
File: ./contracts/tokens/ERC1155Minimal.sol 22: event TransferSingle( 36: event TransferBatch( 48: event ApprovalForAll(address indexed owner, address indexed operator, bool approved); 81: function setApprovalForAll(address operator, bool approved) public { 200: function supportsInterface(bytes4 interfaceId) public pure returns (bool) { 214: function _mint(address to, uint256 id, uint256 amount) internal { 236: function _burn(address from, uint256 id, uint256 amount) internal {
Github: [22, 36, 48, 81, 200, 214, 236]
File: ./contracts/tokens/ERC20Minimal.sol 18: event Transfer(address indexed from, address indexed to, uint256 amount); 24: event Approval(address indexed owner, address indexed spender, uint256 amount); 49: function approve(address spender, uint256 amount) public returns (bool) { 61: function transfer(address to, uint256 amount) public virtual returns (bool) { 103: function _transferFrom(address from, address to, uint256 amount) internal { 122: function _mint(address to, uint256 amount) internal { 136: function _burn(address from, uint256 amount) internal {
Github: [18, 24, 49, 61, 103, 122, 136]
File: ./contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT { 13: function issueNFT(
File: ./contracts/tokens/interfaces/IERC20Partial.sol 27: function transfer(address to, uint256 amount) external;
Github: [27]
File: ./contracts/types/LeftRight.sol 17: library LeftRightLibrary { 39: function rightSlot(LeftRightUnsigned self) internal pure returns (uint128) { 46: function rightSlot(LeftRightSigned self) internal pure returns (int128) { 59: function toRightSlot( 78: function toRightSlot( 101: function leftSlot(LeftRightUnsigned self) internal pure returns (uint128) { 108: function leftSlot(LeftRightSigned self) internal pure returns (int128) { 121: function toLeftSlot( 134: function toLeftSlot(LeftRightSigned self, int128 left) internal pure returns (LeftRightSigned) { 148: function add( 171: function sub( 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) { 251: function subRect(
Github: [17, 39, 46, 59, 78, 101, 108, 121, 134, 148, 171, 194, 214, 232, 251]
File: ./contracts/types/LiquidityChunk.sol 54: uint256 internal constant CLEAR_TL_MASK = 75: unchecked { 94: return LiquidityChunk.wrap(LiquidityChunk.unwrap(self) + amount); 107: return 123: // convert tick upper to uint24 as explicit conversion from int24 to uint256 is not allowed 139: unchecked { 155: unchecked { 173: return int24(int256(LiquidityChunk.unwrap(self) >> 232)); 182: return int24(int256(LiquidityChunk.unwrap(self) >> 208)); 191: return uint128(LiquidityChunk.unwrap(self));
Github: [54, 75, 94, 107, 123, 139, 155, 173, 182, 191]
File: ./contracts/types/TokenId.sol 60: library TokenIdLibrary { 87: function poolId(TokenId self) internal pure returns (uint64) { 96: function tickSpacing(TokenId self) internal pure returns (int24) { 118: function optionRatio(TokenId self, uint256 legIndex) internal pure returns (uint256) { 128: function isLong(TokenId self, uint256 legIndex) internal pure returns (uint256) { 138: function tokenType(TokenId self, uint256 legIndex) internal pure returns (uint256) { 148: function riskPartner(TokenId self, uint256 legIndex) internal pure returns (uint256) { 158: function strike(TokenId self, uint256 legIndex) internal pure returns (int24) { 183: function addPoolId(TokenId self, uint64 _poolId) internal pure returns (TokenId) { 193: function addTickSpacing(TokenId self, int24 _tickSpacing) internal pure returns (TokenId) { 221: function addOptionRatio( 240: function addIsLong( 255: function addTokenType( 273: function addRiskPartner( 291: function addStrike( 310: function addWidth( 336: function addLeg( 404: function countLongs(TokenId self) internal pure returns (uint256) { 464: function clearLeg(TokenId self, uint256 i) internal pure returns (TokenId) {
Github: [60, 87, 96, 118, 128, 138, 148, 158, 183, 193, 221, 240, 255, 273, 291, 310, 336, 404, 464]
</details><a name="NC-47"></a>
@notice
from contract/event/function/modifier declarationsSome contract definitions have an incomplete NatSpec: add a @notice
notation to improve the code documentation.
File: ./contracts/CollateralTracker.sol 178: constructor( 323: function transfer( 341: function transferFrom( 741: function _poolUtilization() internal view returns (int256 poolUtilization) {
File: ./contracts/SemiFungiblePositionManager.sol 708: (totalMoved, collectedByLeg, itmAmounts) = _createPositionInAMM(
Github: [708]
File: ./contracts/libraries/InteractionHelper.sol 17: library InteractionHelper {
Github: [17]
File: ./contracts/libraries/PanopticMath.sol 18: library PanopticMath {
Github: [18]
File: ./contracts/tokens/ERC1155Minimal.sol 11: abstract contract ERC1155 {
Github: [11]
File: ./contracts/tokens/ERC20Minimal.sol 9: abstract contract ERC20Minimal {
Github: [9]
File: ./contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
Github: [6]
File: ./contracts/tokens/interfaces/IERC20Partial.sol 11: interface IERC20Partial {
Github: [11]
File: ./contracts/types/TokenId.sol 464: function clearLeg(TokenId self, uint256 i) internal pure returns (TokenId) {
Github: [464]
</details><a name="NC-48"></a>
@param
from event/function/modifier declarationsSome event definitions have an incomplete NatSpec: add a @param
notation to improve the code documentation.
There are <b>5</b> instances of this issue:
File: ./contracts/CollateralTracker.sol /// @audit missing @param for `_commissionFee` /// @audit missing @param for `_sellerCollateralRatio` /// @audit missing @param for `_buyerCollateralRatio` /// @audit missing @param for `_forceExerciseCost` /// @audit missing @param for `_targetPoolUtilization` /// @audit missing @param for `_saturatedPoolUtilization` /// @audit missing @param for `_ITMSpreadMultiplier` 178: constructor( uint256 _commissionFee, uint256 _sellerCollateralRatio, uint256 _buyerCollateralRatio, int256 _forceExerciseCost, uint256 _targetPoolUtilization, uint256 _saturatedPoolUtilization, uint256 _ITMSpreadMultiplier ) { COMMISSION_FEE = _commissionFee; /// @audit missing @param for `recipient` /// @audit missing @param for `amount` 323: function transfer( address recipient, uint256 amount ) public override(ERC20Minimal) returns (bool) { // make sure the caller does not have any open option positions /// @audit missing @param for `from` /// @audit missing @param for `to` /// @audit missing @param for `amount` 341: function transferFrom( address from, address to, uint256 amount ) public override(ERC20Minimal) returns (bool) { // make sure the caller does not have any open option positions
File: ./contracts/PanopticPool.sol /// @audit missing @param for `atTick` 429: function _calculateAccumulatedPremia( address user, TokenId[] calldata positionIdList, bool computeAllPremia, bool includePendingPremium, int24 atTick ) internal view returns (LeftRightSigned portfolioPremium, uint256[2][] memory balances) {
Github: [429]
File: ./contracts/libraries/Math.sol /// @audit missing @param for `toDowncast` 302: function toUint128Capped(uint256 toDowncast) internal pure returns (uint128 downcastedInt) { if ((downcastedInt = uint128(toDowncast)) != toDowncast) {
Github: [302]
<a name="NC-49"></a>
@return
from function declarationSome function definitions have an incomplete NatSpec: add a @return
notation to improve the code documentation.
There are <b>8</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 323: function transfer( address recipient, uint256 amount ) public override(ERC20Minimal) returns (bool) { // make sure the caller does not have any open option positions 341: function transferFrom( address from, address to, uint256 amount ) public override(ERC20Minimal) returns (bool) { // make sure the caller does not have any open option positions
File: ./contracts/PanopticPool.sol 794: function _burnAllOptionsFrom( address owner, int24 tickLimitLow, int24 tickLimitHigh, bool commitLongSettled, TokenId[] calldata positionIdList ) internal returns (LeftRightSigned netPaid, LeftRightSigned[4][] memory premiasByLeg) { 955: function _burnAndHandleExercise( bool commitLongSettled, int24 tickLimitLow, int24 tickLimitHigh, TokenId tokenId, uint128 positionSize, address owner ) internal returns ( LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg, LeftRightSigned paidAmounts ) { 1290: function _checkSolvencyAtTick( address account, TokenId[] calldata positionIdList, int24 currentTick, int24 atTick, uint256 buffer ) internal view returns (bool) { 1503: function _getPremia( TokenId tokenId, uint128 positionSize, address owner, bool computeAllPremia, int24 atTick ) internal view returns ( LeftRightSigned[4] memory premiaByLeg, uint256[2][4] memory premiumAccumulatorsByLeg ) { uint256 numLegs = tokenId.countLegs(); 1802: function _getTotalLiquidity( TokenId tokenId, uint256 leg ) internal view returns (uint256 totalLiquidity) { unchecked {
Github: [794, 955, 1290, 1503, 1802]
File: ./contracts/SemiFungiblePositionManager.sol 1169: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside0LastX128, liquidity))) ) .toLeftSlot( int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside1LastX128, liquidity))) ) : LeftRightSigned
Github: [1169]
<a name="NC-50"></a>
@title
from contract declarationSome contract definitions have an incomplete NatSpec: add a @title
notation to improve the code documentation.
There are <b>5</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
Github: [36]
File: ./contracts/libraries/InteractionHelper.sol 17: library InteractionHelper {
Github: [17]
File: ./contracts/libraries/SafeTransferLib.sol 11: library SafeTransferLib {
Github: [11]
File: ./contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
Github: [6]
File: ./contracts/types/LiquidityChunk.sol 54: uint256 internal constant CLEAR_TL_MASK =
Github: [54]
<a name="NC-51"></a>
Since the variables are automatically set to 0 when created, it is redundant to initialize it with 0 again.
<details> <summary> There are <b>31</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 662: for (uint256 leg = 0; leg < positionId.countLegs(); ++leg) { 1208: for (uint256 i = 0; i < totalIterations; ) { 1255: for (uint256 index = 0; index < numLegs; ++index) {
File: ./contracts/PanopticPool.sol 441: for (uint256 k = 0; k < pLength; ) { 459: for (uint256 leg = 0; leg < numLegs; ) { 745: for (uint256 leg = 0; leg < numLegs; ) { 802: for (uint256 i = 0; i < positionIdList.length; ) { 864: for (uint256 leg = 0; leg < numLegs; ) { 1382: for (uint256 i = 0; i < pLength; ) { 1518: for (uint256 leg = 0; leg < numLegs; ) { 1672: for (uint256 leg = 0; leg < numLegs; ++leg) { 1852: for (uint256 leg = 0; leg < numLegs; ) {
Github: [441, 459, 745, 802, 864, 1382, 1518, 1672, 1852]
File: ./contracts/SemiFungiblePositionManager.sol 575: for (uint256 i = 0; i < ids.length; ) { 601: for (uint256 leg = 0; leg < numLegs; ) { 882: for (uint256 leg = 0; leg < numLegs; ) {
File: ./contracts/libraries/FeesCalc.sol 51: for (uint256 k = 0; k < positionIdList.length; ) { 55: for (uint256 leg = 0; leg < numLegs; ) {
File: ./contracts/libraries/PanopticMath.sol 137: for (uint256 i = 0; i < cardinality + 1; ++i) { 148: for (uint256 i = 0; i < cardinality; ++i) { 248: for (uint256 i = 0; i < 20; ++i) { 256: for (uint256 i = 0; i < 19; ++i) { 395: for (uint256 leg = 0; leg < numLegs; ) { 781: for (uint256 i = 0; i < positionIdList.length; ++i) { 784: for (uint256 leg = 0; leg < numLegs; ++leg) { 860: for (uint256 i = 0; i < positionIdList.length; i++) { 863: for (uint256 leg = 0; leg < tokenId.countLegs(); ++leg) {
Github: [137, 148, 248, 256, 395, 781, 784, 860, 863]
File: ./contracts/multicall/Multicall.sol 14: for (uint256 i = 0; i < data.length; ) {
Github: [14]
File: ./contracts/tokens/ERC1155Minimal.sol 143: for (uint256 i = 0; i < ids.length; ) { 187: for (uint256 i = 0; i < owners.length; ++i) {
</details>File: ./contracts/types/TokenId.sol 507: for (uint256 i = 0; i < 4; ++i) { 581: for (uint256 i = 0; i < numLegs; ++i) {
<a name="NC-52"></a>
There are some automated tools that will flag a project as having higher complexity if there is inline-assembly, so it's best to avoid using it where it's not necessary. In addition, most assembly methods can be replaced by non-assembly methods, for example:
assembly{ g := gas() }
=> uint256 g = gasleft()
assembly{ id := chainid() }
=> uint256 id = block.chainid
assembly { r := mulmod(a, b, d) }
=> uint256 m = mulmod(x, y, k)
assembly { size := extcodesize() }
=> uint256 size = address(a).code.length
There are <b>10</b> instances of this issue:
File: ./contracts/libraries/Math.sol 354: let mm := mulmod(a, b, not(0)) 380: remainder := mulmod(a, b, denominator) 468: let mm := mulmod(a, b, not(0)) 494: remainder := mulmod(a, b, 0x10000000000000000) 531: let mm := mulmod(a, b, not(0)) 557: remainder := mulmod(a, b, 0x1000000000000000000000000) 608: let mm := mulmod(a, b, not(0)) 634: remainder := mulmod(a, b, 0x100000000000000000000000000000000) 685: let mm := mulmod(a, b, not(0)) 711: remainder := mulmod(a, b, 0x1000000000000000000000000000000000000000000000000)
Github: [354, 380, 468, 494, 531, 557, 608, 634, 685, 711]
<a name="NC-53"></a>
Putting all the system-wide constants in a single file improves code readability, makes it easier to understand the basic configuration and limitations of the system, and makes maintenance easier.
<details> <summary> There are <b>49</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 70: string internal constant TICKER_PREFIX = "po"; 73: string internal constant NAME_PREFIX = "POPT-V1"; 77: uint256 internal constant DECIMALS = 10_000; 81: int128 internal constant DECIMALS_128 = 10_000;
File: ./contracts/PanopticFactory.sol 82: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_WETH = 0.1 ether; 86: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN = 1e6; 89: uint16 internal constant CARDINALITY_INCREASE = 100;
File: ./contracts/PanopticPool.sol 103: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1; 105: int24 internal constant MAX_SWAP_TICK = Constants.MAX_V3POOL_TICK - 1; 109: bool internal constant COMPUTE_ALL_PREMIA = true; 111: bool internal constant COMPUTE_LONG_PREMIA = false; 114: bool internal constant ONLY_AVAILABLE_PREMIUM = false; 119: bool internal constant COMMIT_LONG_SETTLED = true; 120: bool internal constant DONOT_COMMIT_LONG_SETTLED = false; 123: bool internal constant ADD = true; 128: uint32 internal constant TWAP_WINDOW = 600; 133: bool internal constant SLOW_ORACLE_UNISWAP_MODE = false; 136: uint256 internal constant MEDIAN_PERIOD = 60; 139: uint256 internal constant FAST_ORACLE_CARDINALITY = 3; 145: uint256 internal constant FAST_ORACLE_PERIOD = 1; 148: uint256 internal constant SLOW_ORACLE_CARDINALITY = 7; 152: uint256 internal constant SLOW_ORACLE_PERIOD = 5; 156: int256 internal constant MAX_TWAP_DELTA_LIQUIDATION = 513; 160: int256 internal constant MAX_SLOW_FAST_DELTA = 1800; 165: uint64 internal constant MAX_SPREAD = 9 * (2 ** 32); 168: uint64 internal constant MAX_POSITIONS = 32; 171: uint256 internal constant BP_DECREASE_BUFFER = 13_333; 174: uint256 internal constant NO_BUFFER = 10_000;
Github: [103, 105, 109, 111, 114, 119, 120, 123, 128, 133, 136, 139, 145, 148, 152, 156, 160, 165, 168, 171, 174]
File: ./contracts/SemiFungiblePositionManager.sol 133: uint128 private constant VEGOID = 2; 135: /// @dev Uniswap V3 Factory address. Initialized in the constructor. 144: /// @dev pool address => pool id + 2 ** 255 (initialization bit for `poolId == 0`, set if the pool exists)
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 =
File: ./contracts/libraries/Math.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
Github: [15]
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 = 26: int256 internal constant LEFT_HALF_BIT_MASK_INT = 30: int256 internal constant RIGHT_HALF_BIT_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
File: ./contracts/types/LiquidityChunk.sol 57: /// @notice AND mask to strip the `tickUpper` value from a packed LiquidityChunk 61: /*//////////////////////////////////////////////////////////////
</details>File: ./contracts/types/TokenId.sol 62: uint256 internal constant LONG_MASK = 66: uint256 internal constant CLEAR_POOLID_MASK = 70: uint256 internal constant OPTION_RATIO_MASK = 74: uint256 internal constant CHUNK_MASK = 78: int256 internal constant BITMASK_INT24 = 0xFFFFFF;
<a name="NC-54"></a>
return
statement in a function with named return variablesBecause the return variable (or its default value) has been assigned, explicit return at the end of the function is unnecessary, as it is returned automatically.
<details> <summary> There are <b>21</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 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) { 444: function maxMint(address) external view returns (uint256 maxShares) { 507: function maxWithdraw(address owner) public view returns (uint256 maxAssets) { 518: function previewWithdraw(uint256 assets) public view returns (uint256 shares) { 531: function withdraw( 572: function maxRedeem(address owner) public view returns (uint256 maxShares) { 581: function previewRedeem(uint256 shares) public view returns (uint256 assets) { 591: function redeem( 741: function _poolUtilization() internal view returns (int256 poolUtilization) { 751: function _sellCollateralRatio( 806: function _buyCollateralRatio( 1167: // if the account has active options, compute the required collateral to keep account in good health 1206: // (a potentially newly minted position)
Github: [361, 370, 379, 386, 392, 444, 507, 518, 531, 572, 581, 591, 741, 751, 806, 1167, 1206]
File: ./contracts/PanopticPool.sol 1431: function collateralToken0() external view returns (CollateralTracker collateralToken) {
Github: [1431]
File: ./contracts/SemiFungiblePositionManager.sol 786: // this is simply a convenience feature, and should be treated as such 1570:
File: ./contracts/libraries/Math.sol 340: function mulDiv(
Github: [340]
</details><a name="NC-55"></a>
The solidity style guide recommends a maximum line length of 120 characters. Lines of code that are longer than 120 should be wrapped.
There are <b>5</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 727: int256 fee = (FORCE_EXERCISE_COST >> (maxNumRangesFromStrike - 1)); // exponential decay of fee based on number of half ranges away from the price 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 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
Github: [727, 851, 1374, 1375]
File: ./contracts/SemiFungiblePositionManager.sol 219: For an arbitrary parameter 0 <= ν <= 1 (ν = 1/2^VEGOID). This way, the gross_feesCollectedX128 will be given by:
Github: [219]
<a name="NC-56"></a>
File: ./contracts/CollateralTracker.sol /// @audit `initalized` 92: /// @dev As each instance is deployed via proxy clone, initial parameters must only be initalized once via startToken(). /// @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/PanopticPool.sol /// @audit `caluculation` 107: // Flags used as arguments to premia caluculation functions
Github: [107]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `seperated` 1337: // premia, and is only seperated to simplify the calculation
Github: [1337]
File: ./contracts/libraries/Errors.sol /// @audit `avaiable` 62: /// @notice PanopticPool: there is not enough avaiable liquidity to buy an option
Github: [62]
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 `stots` 247: // construct the time stots /// @audit `consistentcy` 663: // evaluate at TWAP price to keep consistentcy with solvency calculations
</details>File: ./contracts/types/LeftRight.sol /// @audit `explictily` 153: // adding leftRight packed uint128's is same as just adding the values explictily /// @audit `explictily` 176: // subtracting leftRight packed uint128's is same as just subtracting the values explictily
<a name="NC-57"></a>
The variable is being cast to its own type.
<details> <summary> There are <b>120</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol /// @audit no need to cast `utilization` into int256 784: if (uint256(utilization) < TARGET_POOL_UTIL) { /// @audit no need to cast `utilization` into uint256 784: if (uint256(utilization) < TARGET_POOL_UTIL) { /// @audit no need to cast `utilization` into int256 784: if (uint256(utilization) < TARGET_POOL_UTIL) { /// @audit no need to cast `utilization` into int256 784: if (uint256(utilization) < TARGET_POOL_UTIL) { /// @audit no need to cast `utilization` into int256 790: if (uint256(utilization) > SATURATED_POOL_UTIL) { /// @audit no need to cast `utilization` into uint256 790: if (uint256(utilization) > SATURATED_POOL_UTIL) { /// @audit no need to cast `utilization` into int256 790: if (uint256(utilization) > SATURATED_POOL_UTIL) { /// @audit no need to cast `utilization` into int256 790: if (uint256(utilization) > SATURATED_POOL_UTIL) { /// @audit no need to cast `utilization` into int256 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / /// @audit no need to cast `utilization` into uint256 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / /// @audit no need to cast `utilization` into int256 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / /// @audit no need to cast `utilization` into int256 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / /// @audit no need to cast `assets` into int256 959: uint256(Math.max(1, int256(totalAssets()) - int256(assets))) /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into uint256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `assets` into int256 977: _transferFrom(refunder, refundee, convertToShares(uint256(assets))); /// @audit no need to cast `tokenToPay` into int256 1013: uint256(tokenToPay), /// @audit no need to cast `tokenToPay` into int256 1013: uint256(tokenToPay), /// @audit no need to cast `updatedAssets` into int256 1028: s_poolAssets = uint128(uint256(updatedAssets)); /// @audit no need to cast `updatedAssets` into int256 1028: s_poolAssets = uint128(uint256(updatedAssets)); /// @audit no need to cast `tokenToPay` into int256 1070: uint256(tokenToPay), /// @audit no need to cast `tokenToPay` into int256 1070: uint256(tokenToPay), /// @audit no need to cast `premiumAllPositions` into int128 1184: netBalance += uint256(uint128(premiumAllPositions)); /// @audit no need to cast `premiumAllPositions` into int128 1184: netBalance += uint256(uint128(premiumAllPositions)); /// @audit no need to cast `` into int128 1189: tokenData = tokenData.toRightSlot(netBalance.toUint128()).toLeftSlot( /// @audit no need to cast `` into int128 1190: tokenRequired.toUint128() /// @audit no need to cast `utilization` into int256 1491: uint256 buyCollateral = _buyCollateralRatio(uint256(utilization)); /// @audit no need to cast `utilization` into uint256 1491: uint256 buyCollateral = _buyCollateralRatio(uint256(utilization)); /// @audit no need to cast `utilization` into int256 1491: uint256 buyCollateral = _buyCollateralRatio(uint256(utilization)); /// @audit no need to cast `utilization` into int256 1491: uint256 buyCollateral = _buyCollateralRatio(uint256(utilization));
Github: [784, 784, 784, 784, 790, 790, 790, 790, 797, 797, 797, 797, 959, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 977, 1013, 1013, 1028, 1028, 1070, 1070, 1184, 1184, 1189, 1190, 1491, 1491, 1491, 1491]
File: ./contracts/PanopticPool.sol /// @audit no need to cast `currentTick` into int24 313: (uint256(uint24(currentTick)) << 24) + // add to slot 4 /// @audit no need to cast `currentTick` into int24 313: (uint256(uint24(currentTick)) << 24) + // add to slot 4 /// @audit no need to cast `currentTick` into int24 313: (uint256(uint24(currentTick)) << 24) + // add to slot 4 /// @audit no need to cast `currentTick` into int24 313: (uint256(uint24(currentTick)) << 24) + // add to slot 4 /// @audit no need to cast `currentTick` into int24 313: (uint256(uint24(currentTick)) << 24) + // add to slot 4 /// @audit no need to cast `currentTick` into int24 313: (uint256(uint24(currentTick)) << 24) + // add to slot 4 /// @audit no need to cast `currentTick` into int24 313: (uint256(uint24(currentTick)) << 24) + // add to slot 4 /// @audit no need to cast `currentTick` into int24 314: (uint256(uint24(currentTick))); // add to slot 3 /// @audit no need to cast `currentTick` into int24 314: (uint256(uint24(currentTick))); // add to slot 3 /// @audit no need to cast `currentTick` into int24 314: (uint256(uint24(currentTick))); // add to slot 3 /// @audit no need to cast `currentTick` into int24 314: (uint256(uint24(currentTick))); // add to slot 3 /// @audit no need to cast `currentTick` into int24 314: (uint256(uint24(currentTick))); // add to slot 3 /// @audit no need to cast `currentTick` into int24 314: (uint256(uint24(currentTick))); // add to slot 3 /// @audit no need to cast `currentTick` into int24 314: (uint256(uint24(currentTick))); // add to slot 3 /// @audit no need to cast `utilization0` into int256 730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64)); /// @audit no need to cast `utilization1` into int256 730: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64)); /// @audit no need to cast `totalLiquidity` into uint256 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; /// @audit no need to cast `totalLiquidity` into uint256 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; /// @audit no need to cast `totalLiquidity` into uint256 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; /// @audit no need to cast `totalLiquidity` into uint256 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity;
Github: [313, 313, 313, 313, 313, 313, 313, 314, 314, 314, 314, 314, 314, 314, 730, 730, 1487, 1487, 1487, 1487]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit no need to cast `amount0Delta` into int256 453: uint256 amountToPay = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); /// @audit no need to cast `amount1Delta` into int256 453: uint256 amountToPay = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); /// @audit no need to cast `univ3pool` into address 613: address(univ3pool), /// @audit no need to cast `univ3pool` into address 613: address(univ3pool), /// @audit no need to cast `univ3pool` into address 613: address(univ3pool), /// @audit no need to cast `univ3pool` into address 613: address(univ3pool), /// @audit no need to cast `univ3pool` into address 613: address(univ3pool), /// @audit no need to cast `univ3pool` into address 622: address(univ3pool), /// @audit no need to cast `univ3pool` into address 622: address(univ3pool), /// @audit no need to cast `univ3pool` into address 622: address(univ3pool), /// @audit no need to cast `univ3pool` into address 622: address(univ3pool), /// @audit no need to cast `univ3pool` into address 622: address(univ3pool), /// @audit no need to cast `univ3pool` into address 976: address(univ3pool), /// @audit no need to cast `univ3pool` into address 976: address(univ3pool), /// @audit no need to cast `univ3pool` into address 976: address(univ3pool), /// @audit no need to cast `univ3pool` into address 976: address(univ3pool), /// @audit no need to cast `univ3pool` into address 976: address(univ3pool),
Github: [453, 453, 613, 613, 613, 613, 613, 622, 622, 622, 622, 622, 976, 976, 976, 976, 976]
File: ./contracts/libraries/Math.sol /// @audit no need to cast `x` into int256 83: return x > 0 ? uint256(x) : uint256(-x); /// @audit no need to cast `x` into int256 83: return x > 0 ? uint256(x) : uint256(-x); /// @audit no need to cast `toCast` into int256 327: return int256(toCast); /// @audit no need to cast `i` into int256 760: while (arr[uint256(i)] < pivot) i++; /// @audit no need to cast `j` into int256 761: while (pivot < arr[uint256(j)]) j--; /// @audit no need to cast `i` into int256 763: (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); /// @audit no need to cast `j` into int256 763: (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]);
Github: [83, 83, 327, 760, 761, 763, 763]
File: ./contracts/libraries/PanopticMath.sol /// @audit no need to cast `tickSpacing` into int24 51: poolId += uint64(uint24(tickSpacing)) << 48; /// @audit no need to cast `tickSpacing` into int24 51: poolId += uint64(uint24(tickSpacing)) << 48; /// @audit no need to cast `tickSpacing` into int24 51: poolId += uint64(uint24(tickSpacing)) << 48; /// @audit no need to cast `lastObservedTick` into int24 230: uint256(uint24(lastObservedTick)); /// @audit no need to cast `tickSpacing` into int24 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) /// @audit no need to cast `width` into int24 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) /// @audit no need to cast `tickSpacing` into int24 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) /// @audit no need to cast `width` into int24 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) /// @audit no need to cast `tickSpacing` into int24 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) /// @audit no need to cast `haircut0` into int256 870: uint128(-_premiasByLeg[i][leg].rightSlot()) * uint256(haircut0), /// @audit no need to cast `haircut1` into int256 874: uint128(-_premiasByLeg[i][leg].leftSlot()) * uint256(haircut1), /// @audit no need to cast `balanceShortage` into int256 939: PanopticMath.convert0to1(uint256(balanceShortage), sqrtPriceX96) /// @audit no need to cast `balanceShortage` into int256 957: PanopticMath.convert1to0(uint256(balanceShortage), sqrtPriceX96)
Github: [51, 51, 51, 230, 377, 377, 377, 377, 377, 870, 874, 939, 957]
File: ./contracts/types/LeftRight.sol /// @audit no need to cast `left` into int256 126: return LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(self) + (uint256(left) << 128)); /// @audit no need to cast `left` into int256 136: return LeftRightSigned.wrap(LeftRightSigned.unwrap(self) + (int256(left) << 128)); /// @audit no need to cast `left` into int128 197: int128 left128 = int128(left); /// @audit no need to cast `right` into int128 202: int128 right128 = int128(right);
File: ./contracts/types/LiquidityChunk.sol /// @audit no need to cast `_tickLower` into int24 78: (uint256(uint24(_tickLower)) << 232) + /// @audit no need to cast `_tickLower` into int24 78: (uint256(uint24(_tickLower)) << 232) + /// @audit no need to cast `_tickLower` into int24 78: (uint256(uint24(_tickLower)) << 232) + /// @audit no need to cast `_tickUpper` into int24 79: (uint256(uint24(_tickUpper)) << 208) + /// @audit no need to cast `_tickUpper` into int24 79: (uint256(uint24(_tickUpper)) << 208) + /// @audit no need to cast `_tickUpper` into int24 79: (uint256(uint24(_tickUpper)) << 208) + /// @audit no need to cast `_tickLower` into int24 109: LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232) /// @audit no need to cast `_tickLower` into int24 109: LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232) /// @audit no need to cast `_tickLower` into int24 109: LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232) /// @audit no need to cast `_tickUpper` into int24 126: LiquidityChunk.unwrap(self) + ((uint256(uint24(_tickUpper))) << 208) /// @audit no need to cast `_tickUpper` into int24 126: LiquidityChunk.unwrap(self) + ((uint256(uint24(_tickUpper))) << 208) /// @audit no need to cast `_tickUpper` into int24 126: LiquidityChunk.unwrap(self) + ((uint256(uint24(_tickUpper))) << 208)
Github: [78, 78, 78, 79, 79, 79, 109, 109, 109, 126, 126, 126]
</details>File: ./contracts/types/TokenId.sol /// @audit no need to cast `_tickSpacing` into int24 195: return TokenId.wrap(TokenId.unwrap(self) + (uint256(uint24(_tickSpacing)) << 48)); /// @audit no need to cast `_width` into int24 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36)) /// @audit no need to cast `_width` into int24 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36))
<a name="NC-58"></a>
Solidity follows two's complement rules for its integers, meaning that the most significant bit for signed integers is used to denote the sign, and converting between the two requires inverting all of the bits and adding one. 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
.
File: ./contracts/CollateralTracker.sol /// @audit uint256 -> int256 200: int256 ratioTick = (int256(_sellerCollateralRatio) - 2000); /// @audit uint256 -> int256 668: int256( /// @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 969: /// @notice Refunds delegated tokens to 'refunder' from 'refundee', similar to 'revoke' /// @audit uint256 -> int256 969: /// @notice Refunds delegated tokens to 'refunder' from 'refundee', similar to 'revoke' /// @audit uint256 -> int256 1008: // compute tokens to be paid due to swap /// @audit uint256 -> int256 1037: /// @param optionOwner The owner of the option being burned and potentially exercised. /// @audit uint256 -> int256 1058: int256 intrinsicValue = swappedAmount - (longAmount - shortAmount); /// @audit uint256 -> int256 1094: /// @param swappedAmount The (potential) amount swapped during any ITM option creations. /// @audit uint256 -> int256 1123: ) /// @audit uint256 -> int256 1129: HEALTH AND COLLATERAL TRACKING /// @audit uint64 -> int64 1337: // if the position is long, required tokens does not depend on price /// @audit uint64 -> int64 1337: // if the position is long, required tokens does not depend on price /// @audit uint64 -> int64 1593: /// @dev A strangle can only have only one of its leg tested at the same time, so this reduces the total risk and collateral requirement. /// @audit uint64 -> int64 1593: /// @dev A strangle can only have only one of its leg tested at the same time, so this reduces the total risk and collateral requirement. /// @audit uint64 -> int64 1644: positionSize, /// @audit uint64 -> int64 1648: }
Github: [200, 668, 715, 715, 718, 718, 743, 969, 969, 1008, 1037, 1058, 1094, 1123, 1129, 1337, 1337, 1593, 1593, 1644, 1648]
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 1550: ((premiumAccumulatorsByLeg[leg][0] - /// @audit uint256 -> int256 1559: ((premiumAccumulatorsByLeg[leg][1] - /// @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 1936: grossPremiumLast.rightSlot() * /// @audit uint256 -> int256 1940: _premiumAccumulatorsByLeg[_leg][0] * /// @audit uint256 -> int256 1953: grossPremiumLast.leftSlot() * /// @audit uint256 -> int256 1957: _premiumAccumulatorsByLeg[_leg][1] *
Github: [477, 1144, 1149, 1550, 1559, 1635, 1636, 1870, 1901, 1936, 1940, 1953, 1957]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit uint256 -> int256 1202: /// @dev this triggers the uniswap mint callback function /// @audit uint256 -> int256 1204: address(this), /// @audit uint256 -> int256 1211: // amount0 The amount of token0 that was paid to mint the given amount of liquidity /// @audit uint256 -> int256 1212: // amount1 The amount of token1 that was paid to mint the given amount of liquidity /// @audit uint256 -> int256 1247: /// @notice Helper to collect amounts between msg.sender and Uniswap and also to update the Uniswap fees collected to date from the AMM. /// @audit uint256 -> int256 1247: /// @notice Helper to collect amounts between msg.sender and Uniswap and also to update the Uniswap fees collected to date from the AMM. /// @audit uint256 -> int256 1267: // When this is the case, we want to behave as if there are 0 fees, so we just rectify the values. /// @audit uint256 -> int256 1267: // When this is the case, we want to behave as if there are 0 fees, so we just rectify the values.
Github: [1202, 1204, 1211, 1212, 1247, 1247, 1267, 1267]
File: ./contracts/libraries/FeesCalc.sol /// @audit uint256 -> int256 75: value1 -= int256(amount1); /// @audit uint256 -> int256 77: } /// @audit uint256 -> int256 84: ++k; /// @audit uint256 -> int256 89: /// @notice Calculate the AMM Swap/trading fees for a `liquidityChunk` of each token. /// @audit uint256 -> int256 121: /// @notice Calculates the fee growth that has occurred (per unit of liquidity) in the AMM/Uniswap for an /// @audit uint256 -> int256 123: /// @dev Extracts the feeGrowth from the uniswap v3 pool.
Github: [75, 77, 84, 89, 121, 123]
File: ./contracts/libraries/Math.sol /// @audit uint128 -> int128 312: if ((downcastedInt = int128(toCast)) < 0) revert Errors.CastingError(); /// @audit uint256 -> int256 327: return int256(toCast); /// @audit uint256 -> int256 778: quickSort(data, int256(0), int256(data.length - 1));
File: ./contracts/libraries/PanopticMath.sol /// @audit uint256 -> int256 140: (int256(observationIndex) - int256(i * period)) + /// @audit uint256 -> int256 140: (int256(observationIndex) - int256(i * period)) + /// @audit uint256 -> 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 -> int256 188: int256(observationIndex) - int256(1) + int256(observationCardinality) /// @audit uint256 -> 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 379: } /// @audit uint256 -> int256 684: PanopticMath.convert0to1( /// @audit uint256 -> int256 685: Math.mulDiv128(bonusCross, 2 ** 128 - requiredRatioX128), /// @audit uint256 -> int256 694: Math.max(premia.rightSlot(), 0); /// @audit uint256 -> int256 697: /// @audit uint256 -> int256 931: if (balanceShortage > 0) { /// @audit uint256 -> int256 939: PanopticMath.convert0to1(uint256(balanceShortage), sqrtPriceX96) /// @audit uint256 -> int256 949: if (balanceShortage > 0) { /// @audit uint256 -> int256 957: PanopticMath.convert1to0(uint256(balanceShortage), sqrtPriceX96)
Github: [140, 140, 141, 151, 178, 179, 188, 188, 196, 217, 258, 379, 684, 685, 694, 697, 931, 939, 949, 957]
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 178: /// @param self The LiquidityChunk to get the upper tick from /// @audit uint256 -> int256 187: /// @param self The LiquidityChunk to get the liquidity from
</details>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));
<a name="NC-59"></a>
Using the low-level calls of a solidity address can leave the contract open to gas grief attacks. These attacks occur when the called contract returns a large amount of data. So when calling an external contract, it is necessary to check the length of the return data before reading/copying it (using returndatasize()
).
There is one instance of this issue:
File: ./contracts/multicall/Multicall.sol 15: (bool success, bytes memory result) = address(this).delegatecall(data[i]);
Github: [15]
<a name="NC-60"></a>
error
definitionThe following error
s are defined but not used. It is recommended to check the code for logical omissions that cause them not to be used. If it's determined that they are not needed anywhere, it's best to remove them from the codebase to improve code clarity and minimize confusion. Note that there may be cases where an error appears to be used because it has multiple definitions in different files. In such cases, the definitions should be moved to a separate file.
File: ./contracts/libraries/Errors.sol 10: error CastingError(); 13: error CollateralTokenAlreadyInitialized(); 16: error DepositTooLarge(); 20: error EffectiveLiquidityAboveThreshold(); 23: error ExceedsMaximumRedemption(); 26: error ExerciseeNotSolvent(); 29: error InputListFail(); 32: error InvalidSalt(); 35: error InvalidTick(); 38: error InvalidNotionalValue(); 42: error InvalidTokenIdParameter(uint256 parameterType); 45: error InvalidUniswapCallback(); 48: error LeftRightInputError(); 51: error NoLegsExercisable(); 54: error NotEnoughCollateral(); 57: error PositionTooLarge(); 60: error NotALongLeg(); 63: error NotEnoughLiquidity(); 66: error NotMarginCalled(); 70: error NotOwner(); 73: error NotPanopticPool(); 76: error OptionsBalanceZero(); 79: error PoolAlreadyInitialized(); 82: error PositionAlreadyMinted(); 85: error PositionCountNotZero(); 88: error PriceBoundFail(); 91: error ReentrantCall(); 95: error StaleTWAP(); 98: error TooManyPositionsOpen(); 101: error TransferFailed(); 105: error TicksNotInitializable(); 108: error UnderOverFlow(); 111: error UniswapPoolNotInitialized();
Github: [10, 13, 16, 20, 23, 26, 29, 32, 35, 38, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 73, 76, 79, 82, 85, 88, 91, 95, 98, 101, 105, 108, 111]
</details><a name="NC-61"></a>
Some arguments are never used: if this is intentional, consider removing these arguments from the function. Otherwise, implement the missing logic accordingly.
There are <b>4</b> instances of this issue:
File: ./contracts/tokens/interfaces/IDonorNFT.sol /// @audit `deployer` /// @audit `newPoolContract` /// @audit `token0` /// @audit `token1` /// @audit `fee` 13: function issueNFT(
Github: [13]
File: ./contracts/tokens/interfaces/IERC20Partial.sol /// @audit `account` 16: function balanceOf(address account) external view returns (uint256); /// @audit `spender` /// @audit `amount` 22: function approve(address spender, uint256 amount) external; /// @audit `to` /// @audit `amount` 27: function transfer(address to, uint256 amount) external;
<a name="NC-62"></a>
The identifier is imported but never used within the file.
There is one instance of this issue:
File: ./contracts/types/LiquidityChunk.sol /// @audit `TokenId` 5: import {TokenId} from "@types/TokenId.sol";
Github: [5]
<a name="NC-63"></a>
Declaring named returns, but not using them, is confusing to the reader. Consider either completely removing them (by declaring just the type without a name), or remove the return statement and do a variable assignment. This would improve the readability of the code, and it may also help reduce regressions during future code refactors.
<details> <summary> There are <b>18</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol /// @audit `assetTokenAddress` not used 361: function asset() external view returns (address assetTokenAddress) { return s_underlyingToken; /// @audit `totalManagedAssets` not used 370: function totalAssets() public view returns (uint256 totalManagedAssets) { unchecked { /// @audit `shares` not used 379: function convertToShares(uint256 assets) public view returns (uint256 shares) { return Math.mulDiv(assets, totalSupply, totalAssets()); /// @audit `assets` not used 386: function convertToAssets(uint256 shares) public view returns (uint256 assets) { return Math.mulDiv(shares, totalAssets(), totalSupply); /// @audit `maxAssets` not used 392: function maxDeposit(address) external pure returns (uint256 maxAssets) { return type(uint104).max; /// @audit `maxShares` not used 444: function maxMint(address) external view returns (uint256 maxShares) { unchecked { /// @audit `maxAssets` not used 507: function maxWithdraw(address owner) public view returns (uint256 maxAssets) { // We can only use the standard 4626 withdraw function if the user has no open positions /// @audit `shares` not used 518: function previewWithdraw(uint256 assets) public view returns (uint256 shares) { uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero. /// @audit `maxShares` not used 572: function maxRedeem(address owner) public view returns (uint256 maxShares) { uint256 available = convertToShares(s_poolAssets); /// @audit `assets` not used 581: function previewRedeem(uint256 shares) public view returns (uint256 assets) { return convertToAssets(shares); /// @audit `poolUtilization` not used 741: function _poolUtilization() internal view returns (int256 poolUtilization) { unchecked { /// @audit `sellCollateralRatio` not used 751: function _sellCollateralRatio( int256 utilization ) internal view returns (uint256 sellCollateralRatio) { // the sell ratio is on a straight line defined between two points (x0,y0) and (x1,y1): /// @audit `buyCollateralRatio` not used 806: function _buyCollateralRatio( uint256 utilization ) internal view returns (uint256 buyCollateralRatio) { // linear from BUY to BUY/2 between 50% and 90% /// @audit `required` not used 1286: tokenId.riskPartner(index) == index // does this leg have a risk partner? Affects required collateral ? _getRequiredCollateralSingleLegNoPartner( tokenId, index, positionSize, atTick, poolUtilization
Github: [361, 370, 379, 386, 392, 444, 507, 518, 572, 581, 741, 751, 806, 1286]
File: ./contracts/PanopticPool.sol /// @audit `premium0` not used /// @audit `premium1` not used /// @audit `` not used 381: function calculateAccumulatedFeesBatch( address user, bool includePendingPremium, TokenId[] calldata positionIdList ) external view returns (int128 premium0, int128 premium1, uint256[2][] memory) { /// @audit `collateralToken` not used 1431: function collateralToken0() external view returns (CollateralTracker collateralToken) { return s_collateralToken0;
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `UniswapV3Pool` not used 1570:
Github: [1570]
File: ./contracts/libraries/PanopticMath.sol /// @audit `` not used 653: LeftRightUnsigned tokenData1, uint160 sqrtPriceX96Twap, uint160 sqrtPriceX96Final, LeftRightSigned netExchanged, LeftRightSigned premia ) external pure returns (int256 bonus0, int256 bonus1, LeftRightSigned) { unchecked { // compute bonus as min(collateralBalance/2, required-collateralBalance)
Github: [653]
</details><a name="NC-64"></a>
The following state variables are defined but not used. It is recommended to check the code for logical omissions that cause them not to be used. If it's determined that they are not needed anywhere, it's best to remove them from the codebase to improve code clarity and minimize confusion.
There are <b>10</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 81: int128 internal constant DECIMALS_128 = 10_000; 131: uint256 immutable TICK_DEVIATION;
File: ./contracts/PanopticPool.sol 105: int24 internal constant MAX_SWAP_TICK = Constants.MAX_V3POOL_TICK - 1;
Github: [105]
File: ./contracts/libraries/Math.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
Github: [15]
File: ./contracts/libraries/PanopticMath.sol 23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
Github: [23]
File: ./contracts/types/LeftRight.sol 30: int256 internal constant RIGHT_HALF_BIT_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
Github: [30]
File: ./contracts/types/TokenId.sol 66: uint256 internal constant CLEAR_POOLID_MASK = 70: uint256 internal constant OPTION_RATIO_MASK = 74: uint256 internal constant CHUNK_MASK = 78: int256 internal constant BITMASK_INT24 = 0xFFFFFF;
<a name="NC-65"></a>
delete
rather than assigning zero/false to clear valuesThe delete
keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic.
There is one instance of this issue:
File: ./contracts/SemiFungiblePositionManager.sol 332: s_poolContext[poolId].locked = false;
Github: [332]
<a name="NC-66"></a>
To prevent the actual contracts deployed from behaving differently depending on the compiler version, it is recommended to use a fixed solidity version.
<details> <summary> There are <b>20</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/PanopticFactory.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/PanopticPool.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/SemiFungiblePositionManager.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/libraries/CallbackLib.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Constants.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Errors.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/FeesCalc.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/InteractionHelper.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Math.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/PanopticMath.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/SafeTransferLib.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/multicall/Multicall.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/tokens/ERC1155Minimal.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/ERC20Minimal.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/interfaces/IDonorNFT.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/interfaces/IERC20Partial.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/LeftRight.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/LiquidityChunk.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/TokenId.sol 2: pragma solidity ^0.8.0;
Github: [2]
</details><a name="NC-67"></a>
immutable
rather than constant
While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand. There is a difference between constant
variables and immutable
variables, and they should each be used in their appropriate contexts. constants
should be used for literal values written into the code, and immutable
variables should be used for expressions, or values calculated in, or passed into the constructor.
File: ./contracts/CollateralTracker.sol 70: string internal constant TICKER_PREFIX = "po"; 73: string internal constant NAME_PREFIX = "POPT-V1"; 77: uint256 internal constant DECIMALS = 10_000; 81: int128 internal constant DECIMALS_128 = 10_000;
File: ./contracts/PanopticFactory.sol 82: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_WETH = 0.1 ether; 86: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN = 1e6; 89: uint16 internal constant CARDINALITY_INCREASE = 100;
File: ./contracts/PanopticPool.sol 103: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1; 105: int24 internal constant MAX_SWAP_TICK = Constants.MAX_V3POOL_TICK - 1; 109: bool internal constant COMPUTE_ALL_PREMIA = true; 111: bool internal constant COMPUTE_LONG_PREMIA = false; 114: bool internal constant ONLY_AVAILABLE_PREMIUM = false; 119: bool internal constant COMMIT_LONG_SETTLED = true; 120: bool internal constant DONOT_COMMIT_LONG_SETTLED = false; 123: bool internal constant ADD = true; 128: uint32 internal constant TWAP_WINDOW = 600; 133: bool internal constant SLOW_ORACLE_UNISWAP_MODE = false; 136: uint256 internal constant MEDIAN_PERIOD = 60; 139: uint256 internal constant FAST_ORACLE_CARDINALITY = 3; 145: uint256 internal constant FAST_ORACLE_PERIOD = 1; 148: uint256 internal constant SLOW_ORACLE_CARDINALITY = 7; 152: uint256 internal constant SLOW_ORACLE_PERIOD = 5; 156: int256 internal constant MAX_TWAP_DELTA_LIQUIDATION = 513; 160: int256 internal constant MAX_SLOW_FAST_DELTA = 1800; 165: uint64 internal constant MAX_SPREAD = 9 * (2 ** 32); 168: uint64 internal constant MAX_POSITIONS = 32; 171: uint256 internal constant BP_DECREASE_BUFFER = 13_333; 174: uint256 internal constant NO_BUFFER = 10_000;
Github: [103, 105, 109, 111, 114, 119, 120, 123, 128, 133, 136, 139, 145, 148, 152, 156, 160, 165, 168, 171, 174]
File: ./contracts/SemiFungiblePositionManager.sol 125: bool internal constant MINT = false; 126: bool internal constant BURN = true; 133: uint128 private constant VEGOID = 2;
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 =
File: ./contracts/libraries/Math.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
Github: [15]
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 = 26: int256 internal constant LEFT_HALF_BIT_MASK_INT = 30: int256 internal constant RIGHT_HALF_BIT_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
File: ./contracts/types/LiquidityChunk.sol 54: uint256 internal constant CLEAR_TL_MASK = 58: uint256 internal constant CLEAR_TU_MASK =
</details>File: ./contracts/types/TokenId.sol 62: uint256 internal constant LONG_MASK = 66: uint256 internal constant CLEAR_POOLID_MASK = 70: uint256 internal constant OPTION_RATIO_MASK = 74: uint256 internal constant CHUNK_MASK = 78: int256 internal constant BITMASK_INT24 = 0xFFFFFF;
<a name="NC-68"></a>
openzeppelin
to the latest version (5.0.2
)These contracts import contracts from openzeppelin contracts but they are not using the latest version. For more information, please visit: OpenZeppelin GitHub Releases It is recommended to always use the latest version to take advantage of updates and security fixes.
There is one instance of this issue:
Global finding
Github: [Global]
<a name="NC-69"></a>
0.8.25
)Upgrading to a newer Solidity release can optimize gas usage, take advantage of new features and improve overall contract efficiency. Where possible, based on compatibility requirements, it is recommended to use newer/latest solidity version to take advantage of the latest optimizations and features.
<details> <summary> There are <b>20</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/PanopticFactory.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/PanopticPool.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/SemiFungiblePositionManager.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/libraries/CallbackLib.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Constants.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Errors.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/FeesCalc.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/InteractionHelper.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/Math.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/PanopticMath.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/libraries/SafeTransferLib.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/multicall/Multicall.sol 2: pragma solidity ^0.8.18;
Github: [2]
File: ./contracts/tokens/ERC1155Minimal.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/ERC20Minimal.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/interfaces/IDonorNFT.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/tokens/interfaces/IERC20Partial.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/LeftRight.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/LiquidityChunk.sol 2: pragma solidity ^0.8.0;
Github: [2]
File: ./contracts/types/TokenId.sol 2: pragma solidity ^0.8.0;
Github: [2]
</details><a name="NC-70"></a>
Modern smart contract development often employs upgradeable contract structures, utilizing proxy patterns like OpenZeppelin’s Upgradeable Contracts. This paradigm separates logic and state, allowing developers to amend and enhance the contract's functionality without altering its state or the deployed contract address. Transitioning to this approach enhances long-term maintainability.
Resolution: Adopt a well-established proxy pattern for upgradeability, ensuring proper initialization and employing transparent proxies to mitigate potential risks. Embrace comprehensive testing and audit practices, particularly when updating contract logic, to ensure state consistency and security are preserved across upgrades. This ensures your contract remains robust and adaptable to future requirements.
There are <b>7</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
Github: [36]
File: ./contracts/PanopticFactory.sol 26: contract PanopticFactory is Multicall {
Github: [26]
File: ./contracts/PanopticPool.sol 27: contract PanopticPool is ERC1155Holder, Multicall {
Github: [27]
File: ./contracts/SemiFungiblePositionManager.sol 82: /// @notice Emitted when a position is destroyed/burned
Github: [82]
File: ./contracts/multicall/Multicall.sol 8: abstract contract Multicall {
Github: [8]
File: ./contracts/tokens/ERC1155Minimal.sol 11: abstract contract ERC1155 {
Github: [11]
File: ./contracts/tokens/ERC20Minimal.sol 9: abstract contract ERC20Minimal {
Github: [9]
<a name="NC-71"></a>
Modifiers in Solidity can improve code readability and modularity by encapsulating repetitive checks, such as address validity checks, into a reusable construct. For example, an onlyOwner
modifier can be used to replace repetitive require(msg.sender == owner)
checks across several functions, reducing code redundancy and enhancing maintainability. To implement, define a modifier with the check, then apply the modifier to relevant functions.
There are <b>8</b> instances of this issue:
File: ./contracts/CollateralTracker.sol 170: if (msg.sender != address(s_panopticPool)) revert Errors.NotPanopticPool(); 541: if (msg.sender != owner) { 599: if (msg.sender != owner) {
File: ./contracts/PanopticFactory.sol 150: if (msg.sender != currentOwner) revert Errors.NotOwner(); 220: if (address(bytes20(salt)) != msg.sender) revert Errors.InvalidSalt(); 224: if (_owner != address(0) && _owner != msg.sender) revert Errors.NotOwner();
File: ./contracts/tokens/ERC1155Minimal.sol 101: if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized(); 137: if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized();
<a name="NC-72"></a>
override
is unnecessaryStarting with Solidity version 0.8.8, using the override
keyword when the function solely overrides an interface function, and the function doesn't exist in multiple base contracts, is unnecessary.
There are <b>2</b> instances of this issue:
File: ./contracts/SemiFungiblePositionManager.sol 546: ) public override { 572: ) public override {
<a name="NC-73"></a>
1e18
) rather than exponentiation (e.g. 10**18
)While the compiler knows to optimize away the exponentiation, it's still better coding practice to use idioms that do not require compiler optimization, if they exist.
There is one instance of this issue:
File: ./contracts/CollateralTracker.sol 234: totalSupply = 10 ** 6;
Github: [234]
<a name="NC-74"></a>
If a function has too many parameters, replacing them with a struct can improve code readability and maintainability, increase reusability, and reduce the likelihood of errors when passing the parameters.
There are <b>3</b> instances of this issue:
File: ./contracts/SemiFungiblePositionManager.sol /// @audit 7 parameters 1479: // oldFeesGrowth is the last stored update of fee growth within the position range in the past (feeGrowthRange*liquidityChunk.liquidity()) (s_accountFeesBase[positionKey]) LeftRightSigned feesBase = FeesCalc.calculateAMMSwapFees( _univ3pool, atTick, _tickLower, _tickUpper,
Github: [1479]
File: ./contracts/libraries/PanopticMath.sol /// @audit 8 parameters 770: TokenId[] memory positionIdList, LeftRightSigned[4][] memory premiasByLeg, LeftRightSigned collateralRemaining, CollateralTracker collateral0, CollateralTracker collateral1, uint160 sqrtPriceX96Final, mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens ) external returns (int256, int256) { unchecked { // get the amount of premium paid by the liquidatee LeftRightSigned longPremium;
Github: [770]
File: ./contracts/types/TokenId.sol /// @audit 9 parameters 336: function addLeg( TokenId self, uint256 legIndex, uint256 _optionRatio, uint256 _asset, uint256 _isLong, uint256 _tokenType, uint256 _riskPartner, int24 _strike, int24 _width ) internal pure returns (TokenId tokenId) {
Github: [336]
<a name="NC-75"></a>
type(X).max
instead of constant formulas like 2**n
Earlier versions of solidity can use uint<n>(-1)
instead. Expressions 2**n -1
can often be rewritten to accommodate the change (e.g. by using a >
instead of a >=
, which will also saves gas).
File: ./contracts/PanopticPool.sol 165: uint64 internal constant MAX_SPREAD = 9 * (2 ** 32); 1487: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; 1552: (liquidityChunk.liquidity())) / 2 ** 64 1561: (liquidityChunk.liquidity())) / 2 ** 64 1635: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) 1636: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); 1769: totalLiquidity) / 2 ** 64; 1771: totalLiquidity) / 2 ** 64; 1942: )) + int256(legPremia.rightSlot() * 2 ** 64), 1959: )) + int256(legPremia.leftSlot()) * 2 ** 64,
Github: [165, 1487, 1552, 1561, 1635, 1636, 1769, 1771, 1942, 1959]
File: ./contracts/SemiFungiblePositionManager.sol 1352: totalLiquidity * 2 ** 64, 1357: totalLiquidity * 2 ** 64,
File: ./contracts/libraries/Math.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1; 418: inv *= 2 - denominator * inv; // inverse mod 2**8 419: inv *= 2 - denominator * inv; // inverse mod 2**16 420: inv *= 2 - denominator * inv; // inverse mod 2**32 421: inv *= 2 - denominator * inv; // inverse mod 2**64 422: inv *= 2 - denominator * inv; // inverse mod 2**128 423: inv *= 2 - denominator * inv; // inverse mod 2**256 484: require(2 ** 64 > prod1); 574: prod0 |= prod1 * 2 ** 160; 624: require(2 ** 128 > prod1); 651: prod0 |= prod1 * 2 ** 128; 664: if (mulmod(a, b, 2 ** 128) > 0) { 728: prod0 |= prod1 * 2 ** 64;
Github: [15, 418, 419, 420, 421, 422, 423, 484, 574, 624, 651, 664, 728]
File: ./contracts/libraries/PanopticMath.sol 23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1; 514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 560: 2 ** 128, 685: Math.mulDiv128(bonusCross, 2 ** 128 - requiredRatioX128),
File: ./contracts/types/TokenId.sol 98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16)); 376: if (optionRatios < 2 ** 64) { 380: } else if (optionRatios < 2 ** 160) { 439: if (optionRatios < 2 ** 64) { 443: } else if (optionRatios < 2 ** 160) {
Github: [98, 376, 380, 439, 443]
</details><a name="NC-76"></a>
To avoid misunderstandings and unexpected state accesses, it is recommended to explicitly define the visibility of each state variable.
There are <b>8</b> instances of this issue:
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;
Github: [131, 136, 141, 145, 149, 154, 158, 162]
<a name="NC-77"></a>
See the Whitespace in Expressions section of the Solidity Style Guide.
<details> <summary> There are <b>19</b> instances (click to show): </summary>File: ./contracts/PanopticFactory.sol 341: (uint160 currentSqrtPriceX96, , , , , , ) = v3Pool.slot0();
Github: [341]
File: ./contracts/PanopticPool.sol 304: (, int24 currentTick, , , , , ) = IUniswapV3Pool(_univ3pool).slot0(); 339: (uint160 currentSqrtPriceX96, , , , , , ) = s_univ3pool.slot0(); 387: (, int24 currentTick, , , , , ) = s_univ3pool.slot0(); 523: (, , uint16 observationIndex, uint16 observationCardinality, , , ) = s_univ3pool.slot0(); 1032: (, int24 currentTick, , , , , ) = s_univ3pool.slot0(); 1094: (, finalTick, , , , , ) = s_univ3pool.slot0(); 1202: (, int24 currentTick, , , , , ) = s_univ3pool.slot0(); 1206: (LeftRightSigned positionPremia, ) = _calculateAccumulatedPremia( 1598: (, int24 currentTick, , , , , ) = s_univ3pool.slot0();
Github: [304, 339, 387, 523, 1032, 1094, 1202, 1206, 1598]
File: ./contracts/SemiFungiblePositionManager.sol 725: (, int24 currentTick, , , , , ) = univ3pool.slot0(); 788: (uint160 sqrtPriceX96, , , , , , ) = _univ3pool.slot0(); 1148: (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = univ3pool
File: ./contracts/libraries/FeesCalc.sol 142: (, , uint256 lowerOut0, uint256 lowerOut1, , , , ) = univ3pool.ticks(tickLower); 143: (, , uint256 upperOut0, uint256 upperOut1, , , , ) = univ3pool.ticks(tickUpper);
</details>File: ./contracts/libraries/PanopticMath.sol 138: (timestamps[i], tickCumulatives[i], , ) = univ3pool.observations( 186: (uint256 timestamp_old, int56 tickCumulative_old, , ) = univ3pool.observations( 192: (uint256 timestamp_last, int56 tickCumulative_last, , ) = univ3pool 253: (int56[] memory tickCumulatives, ) = univ3pool.observe(secondsAgos);
<a name="NC-78"></a>
The functions below have the same implementation as is seen in other files. The functions should be refactored into functions of a common base contract.
There are <b>4</b> instances of this issue:
File: ./contracts/tokens/interfaces/IDonorNFT.sol /// @audit seen in ./contracts/tokens/interfaces/IERC20Partial.sol 13: function issueNFT(
Github: [13]
File: ./contracts/tokens/interfaces/IERC20Partial.sol /// @audit seen in ./contracts/tokens/interfaces/IDonorNFT.sol 16: function balanceOf(address account) external view returns (uint256); /// @audit seen in ./contracts/tokens/interfaces/IDonorNFT.sol 22: function approve(address spender, uint256 amount) external; /// @audit seen in ./contracts/tokens/interfaces/IDonorNFT.sol 27: function transfer(address to, uint256 amount) external;
<a name="NC-79"></a>
private
/internal
functions should be prefixed with an underscoreIt is recommended by the Solidity Style Guide
<details> <summary> There are <b>104</b> instances (click to show): </summary>File: ./contracts/PanopticPool.sol 1450: function getUniV3TWAP() internal view returns (int24 twapTick) { twapTick = PanopticMath.twapFilter(s_univ3pool, TWAP_WINDOW);
Github: [1450]
File: ./contracts/SemiFungiblePositionManager.sol 339: /// @notice Construct the Semi-Fungible Position Manager (SFPM) /// @param _factory the Uniswap v3 Factory used to retrieve registered Uniswap pools 347: /// @param token0 The contract address of token0 of the pool /// @param token1 The contract address of token1 of the pool 611: bytes32 positionKey_from = keccak256( abi.encodePacked( address(univ3pool), 786: // this is simply a convenience feature, and should be treated as such if ((itm0 != 0) && (itm1 != 0)) { (uint160 sqrtPriceX96, , , , , , ) = _univ3pool.slot0();
File: ./contracts/libraries/CallbackLib.sol 30: function validateCallback( address sender, IUniswapV3Factory factory, PoolFeatures memory features ) internal view {
Github: [30]
File: ./contracts/libraries/Math.sol 25: function min24(int24 a, int24 b) internal pure returns (int24) { return a < b ? a : b; 33: function max24(int24 a, int24 b) internal pure returns (int24) { return a > b ? a : b; 41: function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; 49: function min(int256 a, int256 b) internal pure returns (int256) { return a < b ? a : b; 57: function max(uint256 a, uint256 b) internal pure returns (uint256) { return a > b ? a : b; 65: function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; 73: function abs(int256 x) internal pure returns (int256) { return x > 0 ? x : -x; 81: function absUint(int256 x) internal pure returns (uint256) { unchecked { 91: function mostSignificantNibble(uint160 x) internal pure returns (uint256 r) { unchecked { 128: function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160) { unchecked { 191: function getAmount0ForLiquidity(LiquidityChunk liquidityChunk) internal pure returns (uint256) { uint160 lowPriceX96 = getSqrtRatioAtTick(liquidityChunk.tickLower()); 207: function getAmount1ForLiquidity(LiquidityChunk liquidityChunk) internal pure returns (uint256) { uint160 lowPriceX96 = getSqrtRatioAtTick(liquidityChunk.tickLower()); 221: function getAmountsForLiquidity( int24 currentTick, LiquidityChunk liquidityChunk ) internal pure returns (uint256 amount0, uint256 amount1) { if (currentTick <= liquidityChunk.tickLower()) { 241: function getLiquidityForAmount0( int24 tickLower, int24 tickUpper, uint256 amount0 ) internal pure returns (LiquidityChunk) { uint160 lowPriceX96 = getSqrtRatioAtTick(tickLower); 271: function getLiquidityForAmount1( int24 tickLower, int24 tickUpper, uint256 amount1 ) internal pure returns (LiquidityChunk) { uint160 lowPriceX96 = getSqrtRatioAtTick(tickLower); 296: function toUint128(uint256 toDowncast) internal pure returns (uint128 downcastedInt) { if ((downcastedInt = uint128(toDowncast)) != toDowncast) revert Errors.CastingError(); 302: function toUint128Capped(uint256 toDowncast) internal pure returns (uint128 downcastedInt) { if ((downcastedInt = uint128(toDowncast)) != toDowncast) { 311: function toInt128(uint128 toCast) internal pure returns (int128 downcastedInt) { if ((downcastedInt = int128(toCast)) < 0) revert Errors.CastingError(); 318: function toInt128(int256 toCast) internal pure returns (int128 downcastedInt) { if (!((downcastedInt = int128(toCast)) == toCast)) revert Errors.CastingError(); 325: function toInt256(uint256 toCast) internal pure returns (int256) { if (toCast > uint256(type(int256).max)) revert Errors.CastingError(); 340: function mulDiv( uint256 a, uint256 b, uint256 denominator ) internal pure returns (uint256 result) { unchecked { 440: function mulDivRoundingUp( uint256 a, uint256 b, uint256 denominator ) internal pure returns (uint256 result) { unchecked { 458: function mulDiv64(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { 521: function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { 584: function mulDiv96RoundingUp(uint256 a, uint256 b) internal pure returns (uint256 result) { unchecked { 598: function mulDiv128(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { 661: function mulDiv128RoundingUp(uint256 a, uint256 b) internal pure returns (uint256 result) { unchecked { 675: function mulDiv192(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { 738: function unsafeDivRoundingUp(uint256 a, uint256 b) internal pure returns (uint256 result) { assembly ("memory-safe") { 753: function quickSort(int256[] memory arr, int256 left, int256 right) internal pure { unchecked { 776: function sort(int256[] memory data) internal pure returns (int256[] memory) { unchecked {
Github: [25, 33, 41, 49, 57, 65, 73, 81, 91, 128, 191, 207, 221, 241, 271, 296, 302, 311, 318, 325, 340, 440, 458, 521, 584, 598, 661, 675, 738, 753, 776]
File: ./contracts/libraries/PanopticMath.sol 47: function getPoolId(address univ3pool) internal view returns (uint64) { unchecked { 59: function incrementPoolPattern(uint64 poolId) internal pure returns (uint64) { unchecked { 92: function updatePositionsHash( uint256 existingHash, TokenId tokenId, bool addFlag ) internal pure returns (uint256) { // add the XOR`ed hash of the single option position `tokenId` to the `existingHash` 292: uint128 positionSize ) internal pure returns (LiquidityChunk) { // get the tick range for this leg (int24 tickLower, int24 tickUpper) = tokenId.asTicks(legIndex); 342: ) internal pure returns (int24 tickLower, int24 tickUpper) { unchecked { // The max/min ticks that can be initialized are the closest multiple of tickSpacing to the actual max/min tick abs()=887272 374: ) internal pure returns (int24, int24) { return ( (width * tickSpacing) / 2, int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) 393: ) internal pure returns (LeftRightSigned longAmounts, LeftRightSigned shortAmounts) { uint256 numLegs = tokenId.countLegs(); for (uint256 leg = 0; leg < numLegs; ) { 421: LeftRightUnsigned tokenData1, uint256 tokenType, uint160 sqrtPriceX96 ) internal pure returns (uint256, uint256) { if (tokenType == 0) { return ( tokenData0.rightSlot() + convert1to0(tokenData1.rightSlot(), sqrtPriceX96), 447: LeftRightUnsigned tokenData1, uint256 tokenType, int24 tick ) internal pure returns (uint256, uint256) { return convertCollateralData(tokenData0, tokenData1, tokenType, Math.getSqrtRatioAtTick(tick)); 471: int24 tickUpper, uint256 asset ) internal pure returns (uint128) { unchecked { uint256 notional = asset == 0 ? convert0to1(contractSize, Math.getSqrtRatioAtTick((tickUpper + tickLower) / 2)) 490: function convert0to1(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) { unchecked { // the tick 443636 is the maximum price where (price) * 2**192 fits into a uint256 (< 2**256-1) 507: function convert1to0(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) { unchecked { // the tick 443636 is the maximum price where (price) * 2**192 fits into a uint256 (< 2**256-1) 524: function convert0to1(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) { unchecked { // the tick 443636 is the maximum price where (price) * 2**192 fits into a uint256 (< 2**256-1) 547: function convert1to0(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) { unchecked { // the tick 443636 is the maximum price where (price) * 2**192 fits into a uint256 (< 2**256-1) 577: uint256 legIndex ) internal pure returns (LeftRightUnsigned) { // get the tick range for this leg in order to get the strike price (the underlying price)
Github: [47, 59, 92, 292, 342, 374, 393, 421, 447, 471, 490, 507, 524, 547, 577]
File: ./contracts/libraries/SafeTransferLib.sol 21: function safeTransferFrom(address token, address from, address to, uint256 amount) internal { 52: function safeTransfer(address token, address to, uint256 amount) internal {
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( LeftRightUnsigned self, uint128 right ) internal pure returns (LeftRightUnsigned) { 78: function toRightSlot( LeftRightSigned self, int128 right ) 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( LeftRightUnsigned self, uint128 left ) internal pure returns (LeftRightUnsigned) { 134: function toLeftSlot(LeftRightSigned self, int128 left) internal pure returns (LeftRightSigned) { 148: function add( LeftRightUnsigned x, LeftRightUnsigned y ) internal pure returns (LeftRightUnsigned z) { 171: function sub( LeftRightUnsigned x, LeftRightUnsigned y ) 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) { 251: function subRect( LeftRightSigned x, LeftRightSigned y ) internal pure returns (LeftRightSigned z) { 279: function addCapped( LeftRightUnsigned x, LeftRightUnsigned dx, LeftRightUnsigned y, LeftRightUnsigned dy ) internal pure returns (LeftRightUnsigned, LeftRightUnsigned) {
Github: [39, 46, 59, 78, 101, 108, 121, 134, 148, 171, 194, 214, 232, 251, 279]
File: ./contracts/types/LiquidityChunk.sol 75: unchecked { return LiquidityChunk.wrap( (uint256(uint24(_tickLower)) << 232) + (uint256(uint24(_tickUpper)) << 208) + 94: return LiquidityChunk.wrap(LiquidityChunk.unwrap(self) + amount); } } /// @notice Add the lower tick to `self`. 107: return LiquidityChunk.wrap( LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232) 123: // convert tick upper to uint24 as explicit conversion from int24 to uint256 is not allowed return LiquidityChunk.wrap( 139: unchecked { return LiquidityChunk.wrap(LiquidityChunk.unwrap(self) & CLEAR_TL_MASK).addTickLower( _tickLower 155: unchecked { // convert tick upper to uint24 as explicit conversion from int24 to uint256 is not allowed return LiquidityChunk.wrap(LiquidityChunk.unwrap(self) & CLEAR_TU_MASK).addTickUpper( 173: return int24(int256(LiquidityChunk.unwrap(self) >> 232)); } } /// @notice Get the upper tick of `self`. 182: return int24(int256(LiquidityChunk.unwrap(self) >> 208)); } } /// @notice Get the amount of liquidity/size of `self`. 191: return uint128(LiquidityChunk.unwrap(self)); } } }
Github: [75, 94, 107, 123, 139, 155, 173, 182, 191]
File: ./contracts/types/TokenId.sol 87: function poolId(TokenId self) internal pure returns (uint64) { 96: function tickSpacing(TokenId self) internal pure returns (int24) { 108: function asset(TokenId self, uint256 legIndex) internal pure returns (uint256) { 118: function optionRatio(TokenId self, uint256 legIndex) internal pure returns (uint256) { 128: function isLong(TokenId self, uint256 legIndex) internal pure returns (uint256) { 138: function tokenType(TokenId self, uint256 legIndex) internal pure returns (uint256) { 148: function riskPartner(TokenId self, uint256 legIndex) internal pure returns (uint256) { 158: function strike(TokenId self, uint256 legIndex) internal pure returns (int24) { 169: function width(TokenId self, uint256 legIndex) internal pure returns (int24) { 183: function addPoolId(TokenId self, uint64 _poolId) internal pure returns (TokenId) { 193: function addTickSpacing(TokenId self, int24 _tickSpacing) internal pure returns (TokenId) { 205: function addAsset( TokenId self, uint256 _asset, uint256 legIndex ) internal pure returns (TokenId) { 221: function addOptionRatio( TokenId self, uint256 _optionRatio, uint256 legIndex ) internal pure returns (TokenId) { 240: function addIsLong( TokenId self, uint256 _isLong, uint256 legIndex ) internal pure returns (TokenId) { 255: function addTokenType( TokenId self, uint256 _tokenType, uint256 legIndex ) internal pure returns (TokenId) { 273: function addRiskPartner( TokenId self, uint256 _riskPartner, uint256 legIndex ) internal pure returns (TokenId) { 291: function addStrike( TokenId self, int24 _strike, uint256 legIndex ) internal pure returns (TokenId) { 310: function addWidth( TokenId self, int24 _width, uint256 legIndex ) internal pure returns (TokenId) { 336: function addLeg( TokenId self, uint256 legIndex, uint256 _optionRatio, uint256 _asset, uint256 _isLong, uint256 _tokenType, uint256 _riskPartner, int24 _strike, int24 _width ) internal pure returns (TokenId tokenId) { 366: function flipToBurnToken(TokenId self) internal pure returns (TokenId) { 404: function countLongs(TokenId self) internal pure returns (uint256) { 416: function asTicks( TokenId self, uint256 legIndex ) internal pure returns (int24 legLowerTick, int24 legUpperTick) { 432: function countLegs(TokenId self) internal pure returns (uint256) { 464: function clearLeg(TokenId self, uint256 i) internal pure returns (TokenId) { 500: function validate(TokenId self) internal pure { 578: function validateIsExercisable(TokenId self, int24 currentTick) internal pure {
Github: [87, 96, 108, 118, 128, 138, 148, 158, 169, 183, 193, 205, 221, 240, 255, 273, 291, 310, 336, 366, 404, 416, 432, 464, 500, 578]
</details><a name="NC-80"></a>
private
/internal
state variables should be prefixed with an underscoreIt is recommended by the Solidity Style Guide
<details> <summary> There are <b>93</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 70: string internal constant TICKER_PREFIX = "po"; 73: string internal constant NAME_PREFIX = "POPT-V1"; 77: uint256 internal constant DECIMALS = 10_000; 81: int128 internal constant DECIMALS_128 = 10_000; 89: address internal s_underlyingToken; 93: bool internal s_initialized; 96: address internal s_univ3token0; 99: address internal s_univ3token1; 102: bool internal s_underlyingIsToken0; 109: PanopticPool internal s_panopticPool; 112: uint128 internal s_poolAssets; 115: uint128 internal s_inAMM; 121: uint128 internal s_ITMSpreadFee; 124: uint24 internal s_poolFee; 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;
Github: [70, 73, 77, 81, 89, 93, 96, 99, 102, 109, 112, 115, 121, 124, 131, 136, 141, 145, 149, 154, 158, 162]
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; 82: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_WETH = 0.1 ether; 86: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN = 1e6; 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;
Github: [63, 66, 69, 72, 75, 78, 82, 86, 89, 96, 99, 102]
File: ./contracts/PanopticPool.sol 103: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1; 105: int24 internal constant MAX_SWAP_TICK = Constants.MAX_V3POOL_TICK - 1; 109: bool internal constant COMPUTE_ALL_PREMIA = true; 111: bool internal constant COMPUTE_LONG_PREMIA = false; 114: bool internal constant ONLY_AVAILABLE_PREMIUM = false; 119: bool internal constant COMMIT_LONG_SETTLED = true; 120: bool internal constant DONOT_COMMIT_LONG_SETTLED = false; 123: bool internal constant ADD = true; 128: uint32 internal constant TWAP_WINDOW = 600; 133: bool internal constant SLOW_ORACLE_UNISWAP_MODE = false; 136: uint256 internal constant MEDIAN_PERIOD = 60; 139: uint256 internal constant FAST_ORACLE_CARDINALITY = 3; 145: uint256 internal constant FAST_ORACLE_PERIOD = 1; 148: uint256 internal constant SLOW_ORACLE_CARDINALITY = 7; 152: uint256 internal constant SLOW_ORACLE_PERIOD = 5; 156: int256 internal constant MAX_TWAP_DELTA_LIQUIDATION = 513; 160: int256 internal constant MAX_SLOW_FAST_DELTA = 1800; 165: uint64 internal constant MAX_SPREAD = 9 * (2 ** 32); 168: uint64 internal constant MAX_POSITIONS = 32; 171: uint256 internal constant BP_DECREASE_BUFFER = 13_333; 174: uint256 internal constant NO_BUFFER = 10_000; 179: SemiFungiblePositionManager internal immutable SFPM; 186: IUniswapV3Pool internal s_univ3pool; 225: uint256 internal s_miniMedian; 231: CollateralTracker internal s_collateralToken0; 233: CollateralTracker internal s_collateralToken1; 238: mapping(address account => mapping(TokenId tokenId => mapping(uint256 leg => LeftRightUnsigned premiaGrowth))) 245: mapping(bytes32 chunkKey => LeftRightUnsigned lastGrossPremium) internal s_grossPremiumLast; 251: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) internal s_settledTokens; 258: mapping(address account => mapping(TokenId tokenId => LeftRightUnsigned balanceAndUtilizations)) 272: mapping(address account => uint256 positionsHash) internal s_positionsHash;
Github: [103, 105, 109, 111, 114, 119, 120, 123, 128, 133, 136, 139, 145, 148, 152, 156, 160, 165, 168, 171, 174, 179, 186, 225, 231, 233, 238, 245, 251, 258, 272]
File: ./contracts/SemiFungiblePositionManager.sol 133: uint128 private constant VEGOID = 2; 135: /// @dev Uniswap V3 Factory address. Initialized in the constructor. 144: /// @dev pool address => pool id + 2 ** 255 (initialization bit for `poolId == 0`, set if the pool exists) 147: /// @dev also contains a boolean which is used for the reentrancy lock - saves gas since this slot is often warmed 155: net amount 163: │ │ │ │ │ │ │ 190: Let`s say Charlie the smart contract deposited T into the AMM and later removed R from that 295: mapping(bytes32 positionKey => LeftRightSigned baseFees0And1) internal s_accountFeesBase; 297: /*////////////////////////////////////////////////////////////// 310: // execute function
Github: [133, 135, 144, 147, 155, 163, 190, 295, 297, 310]
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 =
File: ./contracts/libraries/Math.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
Github: [15]
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 = 26: int256 internal constant LEFT_HALF_BIT_MASK_INT = 30: int256 internal constant RIGHT_HALF_BIT_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
File: ./contracts/types/LiquidityChunk.sol 57: /// @notice AND mask to strip the `tickUpper` value from a packed LiquidityChunk 61: /*//////////////////////////////////////////////////////////////
</details>File: ./contracts/types/TokenId.sol 62: uint256 internal constant LONG_MASK = 66: uint256 internal constant CLEAR_POOLID_MASK = 70: uint256 internal constant OPTION_RATIO_MASK = 74: uint256 internal constant CHUNK_MASK = 78: int256 internal constant BITMASK_INT24 = 0xFFFFFF;
<a name="NC-81"></a>
Making function calls or external calls within loops in Solidity can lead to inefficient gas usage, potential bottlenecks, and increased vulnerability to attacks. Each function call or external 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.
There are <b>3</b> instances of this issue:
File: ./contracts/CollateralTracker.sol /// @audit `_getRequiredCollateralAtTickSinglePosition` /// @audit `_getRequiredCollateralSingleLeg` 1259: tokenRequired += _getRequiredCollateralSingleLeg(
Github: [1259]
File: ./contracts/PanopticPool.sol /// @audit `_getPremia` /// @audit `_getAvailablePremium` /// @audit `_getTotalLiquidity` /// @audit `_getAvailablePremium` /// @audit `_getTotalLiquidity` /// @audit `_checkLiquiditySpread` /// @audit `_burnOptions` /// @audit `_checkLiquiditySpread` /// @audit `_getTotalLiquidity` /// @audit `_getTotalLiquidity` /// @audit `_getAvailablePremium` 1852: for (uint256 leg = 0; leg < numLegs; ) {
Github: [1852]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `registerTokenTransfer` /// @audit `_createLegInAMM` 908: );
Github: [908]
<a name="NC-82"></a>
require()
/ revert()
statements should have descriptive reason stringsFile: ./contracts/CollateralTracker.sol 170: if (msg.sender != address(s_panopticPool)) revert Errors.NotPanopticPool(); 229: if (s_initialized) revert Errors.CollateralTokenAlreadyInitialized(); 331: if (s_panopticPool.numberOfPositions(msg.sender) != 0) revert Errors.PositionCountNotZero(); 350: if (s_panopticPool.numberOfPositions(from) != 0) revert Errors.PositionCountNotZero(); 418: if (assets > type(uint104).max) revert Errors.DepositTooLarge(); 480: if (assets > type(uint104).max) revert Errors.DepositTooLarge(); 536: if (assets > maxWithdraw(owner)) revert Errors.ExceedsMaximumRedemption(); 596: if (shares > maxRedeem(owner)) revert Errors.ExceedsMaximumRedemption();
Github: [170, 229, 331, 350, 418, 480, 536, 596]
File: ./contracts/PanopticFactory.sol 150: if (msg.sender != currentOwner) revert Errors.NotOwner(); 220: if (address(bytes20(salt)) != msg.sender) revert Errors.InvalidSalt(); 224: if (_owner != address(0) && _owner != msg.sender) revert Errors.NotOwner(); 227: if (address(v3Pool) == address(0)) revert Errors.UniswapPoolNotInitialized(); 230: revert Errors.PoolAlreadyInitialized();
Github: [150, 220, 224, 227, 230]
File: ./contracts/PanopticPool.sol 299: if (address(s_univ3pool) != address(0)) revert Errors.PoolAlreadyInitialized(); 342: revert Errors.PriceBoundFail(); 634: revert Errors.InvalidTokenIdParameter(0); 639: revert Errors.PositionAlreadyMinted(); 940: if (!solventAtFast) revert Errors.NotEnoughCollateral(); 945: revert Errors.NotEnoughCollateral(); 1036: revert Errors.StaleTWAP(); 1066: if (balanceCross >= thresholdCross) revert Errors.NotMarginCalled(); 1163: ) revert Errors.NotEnoughCollateral(); 1188: if (touchedId.length != 1) revert Errors.InputListFail(); 1394: if (fingerprintIncomingList != currentHash) revert Errors.InputListFail(); 1415: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen(); 1493: revert Errors.EffectiveLiquidityAboveThreshold(); 1596: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg();
Github: [299, 342, 634, 639, 940, 945, 1036, 1066, 1163, 1188, 1394, 1415, 1493, 1596]
File: ./contracts/SemiFungiblePositionManager.sol 341: constructor(IUniswapV3Factory _factory) { 369: // There are 281,474,976,710,655 possible pool patterns. 566: function safeBatchTransferFrom( 593: function registerTokenTransfer(address from, address to, TokenId id, uint256 amount) internal { 650: ++leg; 659: /// @notice Helper that checks the proposed option position and size and forwards the minting and potential swapping tasks. 717: if ((LeftRightSigned.unwrap(itmAmounts) != 0)) { 731: /// @notice When a position is minted or burnt in-the-money (ITM) we are *not* 100% token0 or 100% token1: we have a mix of both tokens. 752: /// It that position is burnt, then we remove a mix of the two tokens and swap one of them so that the user receives only one. 964: ) 1047: /** if the position is NOT long (selling a put or a call), then _mintLiquidity to move liquidity
Github: [341, 369, 566, 593, 650, 659, 717, 731, 752, 964, 1047]
File: ./contracts/libraries/CallbackLib.sol 37: revert Errors.InvalidUniswapCallback();
Github: [37]
File: ./contracts/libraries/Math.sol 131: if (absTick > uint256(int256(Constants.MAX_V3POOL_TICK))) revert Errors.InvalidTick(); 297: if ((downcastedInt = uint128(toDowncast)) != toDowncast) revert Errors.CastingError(); 312: if ((downcastedInt = int128(toCast)) < 0) revert Errors.CastingError(); 319: if (!((downcastedInt = int128(toCast)) == toCast)) revert Errors.CastingError(); 326: if (toCast > uint256(type(int256).max)) revert Errors.CastingError(); 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);
Github: [131, 297, 312, 319, 326, 361, 370, 448, 484, 547, 588, 624, 665, 701]
File: ./contracts/libraries/PanopticMath.sol 365: /// @notice Returns the distances of the upper and lower ticks from the strike for a position with the given width and tickSpacing. 483: }
File: ./contracts/libraries/SafeTransferLib.sol 45: if (!success) revert Errors.TransferFailed(); 75: if (!success) revert Errors.TransferFailed();
File: ./contracts/types/LeftRight.sol 163: ) revert Errors.UnderOverFlow(); 186: ) revert Errors.UnderOverFlow(); 199: if (left128 != left) revert Errors.UnderOverFlow(); 204: if (right128 != right) revert Errors.UnderOverFlow(); 222: if (left128 != left256 || right128 != right256) revert Errors.UnderOverFlow(); 240: if (left128 != left256 || right128 != right256) revert Errors.UnderOverFlow(); 262: if (left128 != left256 || right128 != right256) revert Errors.UnderOverFlow();
Github: [163, 186, 199, 204, 222, 240, 262]
File: ./contracts/types/TokenId.sol 501: if (self.optionRatio(0) == 0) revert Errors.InvalidTokenIdParameter(1); 513: revert Errors.InvalidTokenIdParameter(1); 522: revert Errors.InvalidTokenIdParameter(6); 528: if ((self.width(i) == 0)) revert Errors.InvalidTokenIdParameter(5); 533: ) revert Errors.InvalidTokenIdParameter(4); 542: revert Errors.InvalidTokenIdParameter(3); 548: ) revert Errors.InvalidTokenIdParameter(3); 561: revert Errors.InvalidTokenIdParameter(4); 567: revert Errors.InvalidTokenIdParameter(5); 598: revert Errors.NoLegsExercisable();
Github: [501, 513, 522, 528, 533, 542, 548, 561, 567, 598]
</details><a name="NC-83"></a>
As the Solidity Style Guide suggests: arguments
, local variables
and mutable state variables
should be named in mixedCase style.
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol /// @audit `min_sell_ratio` 773: uint256 min_sell_ratio = SELLER_COLLATERAL_RATIO;
Github: [773]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `UniswapV3Pool` 1570:
Github: [1570]
<a name="NC-84"></a>
indexed
fieldsIndex event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.
<details> <summary> There are <b>12</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 49: event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
Github: [49]
File: ./contracts/PanopticFactory.sol 43: event PoolDeployed(
Github: [43]
File: ./contracts/PanopticPool.sol 38: event AccountLiquidated( 62: event PremiumSettled( 75: event OptionBurnt( 90: event OptionMinted(
File: ./contracts/SemiFungiblePositionManager.sol 90: uint128 positionSize 98: event TokenizedPositionMinted( 114: // false = unlocked, true = locked
File: ./contracts/tokens/ERC1155Minimal.sol 48: event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
Github: [48]
</details>File: ./contracts/tokens/ERC20Minimal.sol 18: event Transfer(address indexed from, address indexed to, uint256 amount); 24: event Approval(address indexed owner, address indexed spender, uint256 amount);
<a name="NC-85"></a>
external
File: ./contracts/CollateralTracker.sol 323: function transfer( 341: function transferFrom( 1147: tokenData = _getAccountMargin(user, currentTick, positionBalanceArray, premiumAllPositions);
File: ./contracts/PanopticFactory.sol 134: function initialize(address _owner) public {
Github: [134]
File: ./contracts/PanopticPool.sol 1444: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) {
Github: [1444]
File: ./contracts/SemiFungiblePositionManager.sol 555: super.safeTransferFrom(from, to, id, amount, data); 583: // transfer the token (note that all state updates are completed before reentrancy is possible)
File: ./contracts/libraries/FeesCalc.sol 104: // extract the amount of AMM fees collected within the liquidity chunk`
Github: [104]
File: ./contracts/multicall/Multicall.sol 12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
Github: [12]
File: ./contracts/tokens/ERC1155Minimal.sol 81: function setApprovalForAll(address operator, bool approved) public { 178: function balanceOfBatch( 200: function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
File: ./contracts/tokens/ERC20Minimal.sol 49: function approve(address spender, uint256 amount) public returns (bool) {
Github: [49]
</details><a name="NC-86"></a>
Allowing a function in Solidity to return the default address (address(0)) can be problematic as it can represent uninitialized or invalid addresses. If such an address is utilized in transfer operations or other sensitive actions, it could lead to loss of funds or unpredicted behavior. It's prudent to include checks in your functions to prevent the return of the zero address, enhancing contract security.
There are <b>2</b> instances of this issue:
File: ./contracts/CollateralTracker.sol /// @audit `address assetTokenAddress` 361: function asset() external view returns (address assetTokenAddress) { return s_underlyingToken;
Github: [361]
File: ./contracts/PanopticFactory.sol /// @audit `address ` 159: function owner() external view returns (address) {
Github: [159]
<a name="NC-87"></a>
There are <b>5</b> instances of this issue:
File: ./contracts/libraries/InteractionHelper.sol 63: symbol0 = "???"; 68: symbol1 = "???"; 74: " ", 80: " ", 100: return string.concat(prefix, "???");
<a name="NC-88"></a>
Relying on Solidity's default arithmetic operator precedence can lead to misunderstood or overlooked nuances in code execution. Misinterpretation risks can be significant, especially when different developers or auditors review the code. To ensure clarity and minimize potential errors, it's recommended to use parentheses. These not only override the default precedence when needed but also emphasize the intended order of operations. By deliberately showing the intended calculation sequence using parentheses, developers make the code's logic explicit, reducing the risk of unintended behaviors and making the codebase more maintainable and understandable for all stakeholders.
<details> <summary> There are <b>24</b> instances (click to show): </summary>File: ./contracts/CollateralTracker.sol 764: 100% - | _------ 767: 20% - |---------¯ 827: 10% - |----------__ min_ratio = 5% 828: 5% - | . . . . . ¯¯¯--______ 1386: 100% + SCR% - |--__ . . . 1387: 100% - | . .¯¯--__ . . . 1389: SCR - | . .¯¯--__________ 1620: 100% - |--__ . 1623: SCR/2 - | ¯¯--______ <------ base collateral is half that of a single-leg
Github: [764, 767, 827, 828, 1386, 1387, 1389, 1620, 1623]
File: ./contracts/SemiFungiblePositionManager.sol 222: = feeGrowthX128 * T + feesGrowthX128*ν*R^2/N 899: _leg = _isBurn ? numLegs - leg - 1 : leg; 1352: totalLiquidity * 2 ** 64, 1357: totalLiquidity * 2 ** 64,
Github: [222, 899, 1352, 1357]
File: ./contracts/libraries/FeesCalc.sol 166: feeGrowthInside0X128 = lowerOut0 - upperOut0; // fee growth inside the chunk
Github: [166]
File: ./contracts/libraries/Math.sol 418: inv *= 2 - denominator * inv; // inverse mod 2**8 419: inv *= 2 - denominator * inv; // inverse mod 2**16 420: inv *= 2 - denominator * inv; // inverse mod 2**32 421: inv *= 2 - denominator * inv; // inverse mod 2**64 422: inv *= 2 - denominator * inv; // inverse mod 2**128 423: inv *= 2 - denominator * inv; // inverse mod 2**256 511: prod0 |= prod1 * 2 ** 192; 574: prod0 |= prod1 * 2 ** 160; 651: prod0 |= prod1 * 2 ** 128; 728: prod0 |= prod1 * 2 ** 64;
Github: [418, 419, 420, 421, 422, 423, 511, 574, 651, 728]
</details><a name="NC-89"></a>
In Ethereum, a function marked payable
can accept Ether. If a function is intended to work with Ether but isn't marked payable, it will reject any Ether sent to it, leading to failed transactions. Conversely, if a function doesn't involve Ether transactions but is mistakenly marked payable, it can inadvertently accept Ether, potentially locking it in the contract. Thus, functions that should handle Ether transfers must be correctly marked payable, and those that shouldn't must be non-payable. Proper function designation ensures the intended flow of Ether and guards against unintended Ether reception or loss.
There is one instance of this issue:
File: ./contracts/multicall/Multicall.sol 12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
Github: [12]
<a name="NC-90"></a>
using
keyword when using specific imports rather than calling the specific import directlyIn Solidity, the using
keyword can streamline the use of library functions for specific types. Instead of calling library functions directly with their full import paths, you can declare a library once with using
for a specific type. This approach makes your code more readable and concise. For example, instead of LibraryName.functionName(variable)
, you would first declare using LibraryName for TypeName;
at the contract level. After this, you can call library functions directly on variables of TypeName
like variable.functionName()
. This method not only enhances code clarity but also promotes cleaner and more organized code, especially when multiple functions from the same library are used frequently.
File: ./contracts/CollateralTracker.sol /// @audit `Errors` 170: if (msg.sender != address(s_panopticPool)) revert Errors.NotPanopticPool(); /// @audit `Errors` 229: if (s_initialized) revert Errors.CollateralTokenAlreadyInitialized(); /// @audit `InteractionHelper` 292: InteractionHelper.computeName( /// @audit `InteractionHelper` 305: return InteractionHelper.computeSymbol(s_underlyingToken, TICKER_PREFIX); /// @audit `InteractionHelper` 312: return InteractionHelper.computeDecimals(s_underlyingToken); /// @audit `Errors` 331: if (s_panopticPool.numberOfPositions(msg.sender) != 0) revert Errors.PositionCountNotZero(); /// @audit `Errors` 350: if (s_panopticPool.numberOfPositions(from) != 0) revert Errors.PositionCountNotZero(); /// @audit `Math` 380: return Math.mulDiv(assets, totalSupply, totalAssets()); /// @audit `Math` 387: return Math.mulDiv(shares, totalAssets(), totalSupply); /// @audit `Math` 402: shares = Math.mulDiv( /// @audit `Errors` 418: if (assets > type(uint104).max) revert Errors.DepositTooLarge(); /// @audit `SafeTransferLib` 424: SafeTransferLib.safeTransferFrom( /// @audit `Math` 462: assets = Math.mulDivRoundingUp( /// @audit `Errors` 480: if (assets > type(uint104).max) revert Errors.DepositTooLarge(); /// @audit `SafeTransferLib` 484: SafeTransferLib.safeTransferFrom( /// @audit `Math` 512: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0; /// @audit `Math` 521: return Math.mulDivRoundingUp(assets, supply, totalAssets()); /// @audit `Errors` 536: if (assets > maxWithdraw(owner)) revert Errors.ExceedsMaximumRedemption(); /// @audit `SafeTransferLib` 556: SafeTransferLib.safeTransferFrom( /// @audit `Math` 575: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0; /// @audit `Errors` 596: if (shares > maxRedeem(owner)) revert Errors.ExceedsMaximumRedemption(); /// @audit `SafeTransferLib` 616: SafeTransferLib.safeTransferFrom( /// @audit `Math` 669: Math.unsafeDivRoundingUp( /// @audit `Math` 675: maxNumRangesFromStrike = Math.max( /// @audit `Math` 677: uint256(Math.abs(currentTick - positionId.strike(leg)) / range) /// @audit `PanopticMath` 687: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( /// @audit `Math` 693: (currentValue0, currentValue1) = Math.getAmountsForLiquidity( /// @audit `Math` 698: (oracleValue0, oracleValue1) = Math.getAmountsForLiquidity( /// @audit `Math` 964: else { /// @audit `Math` 969: /// @notice Refunds delegated tokens to 'refunder' from 'refundee', similar to 'revoke' /// @audit `Math` 1020: uint256 sharesToMint = convertToShares(uint256(-tokenToPay)); /// @audit `Math` 1077: uint256 sharesToMint = convertToShares(uint256(-tokenToPay)); /// @audit `Math` 1116: } /// @audit `Math` 1118: //compute total commission amount = commission rate + spread fee /// @audit `Math` 1130: //////////////////////////////////////////////////////////////*/ /// @audit `PanopticMath` 1328: int64 utilization = tokenType == 0 /// @audit `Math` 1368: ); // calls -> strike/price /// @audit `Constants` 1370: // compute the collateral requirement depending on whether the position is ITM & out-of-range or ITM and in-range: /// @audit `Math` 1370: // compute the collateral requirement depending on whether the position is ITM & out-of-range or ITM and in-range: /// @audit `Math` 1372: /// ITM and out-of-range /// @audit `Constants` 1374: ((atTick < tickLower) && (tokenType == 1)) || // strike ITM but out of range price < lowerTick for tokenType=1 /// @audit `Math` 1374: ((atTick < tickLower) && (tokenType == 1)) || // strike ITM but out of range price < lowerTick for tokenType=1 /// @audit `Constants` 1403: // position is in-range (ie. current tick is between upper+lower tick): we draw a line between the /// @audit `Math` 1405: // the collateral requirement when in-range, which always over-estimates the amount of token required /// @audit `Math` 1414: scaleFactor + Constants.FP96 /// @audit `Math` 1416: // position is in-the-money, collateral requirement = amountMoved*(1-SRC)*(scaleFactor-ratio)/(scaleFactor+1) + SCR*amountMoved /// @audit `Constants` 1424: /// @notice Calculate the required amount of collateral for leg 'index' for position 'tokenId' accounting for its partner leg. /// @audit `Math` 1491: uint256 buyCollateral = _buyCollateralRatio(uint256(utilization)); /// @audit `Math` 1503: /// @dev may be higher than the requirement of non risk-partnered legs if the spread is very wide (risky) /// @audit `PanopticMath` 1527: // amount moved is right slot if tokenType=0, left slot otherwise /// @audit `PanopticMath` 1531: // amounts moved for partner /// @audit `Math` 1577: // narrower spreads will be very capital efficient (1/3 of non-partnered CR!), but /// @audit `Math` 1578: // wider spreads (an uncommon position w/ high max loss) may not benefit from risk partnering /// @audit `Math` 1588: );
Github: [170, 229, 292, 305, 312, 331, 350, 380, 387, 402, 418, 424, 462, 480, 484, 512, 521, 536, 556, 575, 596, 616, 669, 675, 677, 687, 693, 698, 964, 969, 1020, 1077, 1116, 1118, 1130, 1328, 1368, 1370, 1370, 1372, 1374, 1374, 1403, 1405, 1414, 1416, 1424, 1491, 1503, 1527, 1531, 1577, 1578, 1588]
File: ./contracts/PanopticFactory.sol /// @audit `Errors` 150: if (msg.sender != currentOwner) revert Errors.NotOwner(); /// @audit `CallbackLib` 177: CallbackLib.CallbackData memory decoded = abi.decode(data, (CallbackLib.CallbackData)); /// @audit `CallbackLib` 179: CallbackLib.validateCallback(msg.sender, UNIV3_FACTORY, decoded.poolFeatures); /// @audit `SafeTransferLib` 182: SafeTransferLib.safeTransferFrom( /// @audit `SafeTransferLib` 189: SafeTransferLib.safeTransferFrom( /// @audit `Errors` 220: if (address(bytes20(salt)) != msg.sender) revert Errors.InvalidSalt(); /// @audit `Errors` 224: if (_owner != address(0) && _owner != msg.sender) revert Errors.NotOwner(); /// @audit `Errors` 227: if (address(v3Pool) == address(0)) revert Errors.UniswapPoolNotInitialized(); /// @audit `Errors` 230: revert Errors.PoolAlreadyInitialized(); /// @audit `PanopticMath` 304: uint256 rarity = PanopticMath.numberOfLeadingHexZeros( /// @audit `Math` 353: Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_WETH, currentSqrtPriceX96) /// @audit `Math` 357: Math.mulDivRoundingUp( /// @audit `Constants` 359: Constants.FP96, /// @audit `Math` 366: Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN, currentSqrtPriceX96) /// @audit `Math` 369: Math.mulDivRoundingUp( /// @audit `Constants` 371: Constants.FP96, /// @audit `Constants` 392: tickLower = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing; /// @audit `CallbackLib` 397: CallbackLib.CallbackData({ /// @audit `CallbackLib` 398: poolFeatures: CallbackLib.PoolFeatures({token0: token0, token1: token1, fee: fee}),
Github: [150, 177, 179, 182, 189, 220, 224, 227, 230, 304, 353, 357, 359, 366, 369, 371, 392, 397, 398]
File: ./contracts/PanopticPool.sol /// @audit `Constants` 103: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1; /// @audit `Constants` 105: int24 internal constant MAX_SWAP_TICK = Constants.MAX_V3POOL_TICK - 1; /// @audit `Errors` 299: if (address(s_univ3pool) != address(0)) revert Errors.PoolAlreadyInitialized(); /// @audit `InteractionHelper` 326: InteractionHelper.doApprovals(SFPM, collateralTracker0, collateralTracker1, token0, token1); /// @audit `Errors` 342: revert Errors.PriceBoundFail(); /// @audit `FeesCalc` 415: (value0, value1) = FeesCalc.getPortfolioValue( /// @audit `PanopticMath` 525: (, uint256 medianData) = PanopticMath.computeInternalMedian( /// @audit `Errors` 634: revert Errors.InvalidTokenIdParameter(0); /// @audit `Errors` 639: revert Errors.PositionAlreadyMinted(); /// @audit `PanopticMath` 712: (LeftRightSigned longAmounts, LeftRightSigned shortAmounts) = PanopticMath /// @audit `Math` 775: uint64(Math.min(effectiveLiquidityLimitX32, MAX_SPREAD)) /// @audit `PanopticMath` 905: int24 fastOracleTick = PanopticMath.computeMedianObservedPrice( /// @audit `PanopticMath` 915: slowOracleTick = PanopticMath.computeMedianObservedPrice( /// @audit `PanopticMath` 923: (slowOracleTick, medianData) = PanopticMath.computeInternalMedian( /// @audit `Errors` 940: if (!solventAtFast) revert Errors.NotEnoughCollateral(); /// @audit `Math` 943: if (Math.abs(int256(fastOracleTick) - slowOracleTick) > MAX_SLOW_FAST_DELTA) /// @audit `Errors` 945: revert Errors.NotEnoughCollateral(); /// @audit `PanopticMath` 981: (LeftRightSigned longAmounts, LeftRightSigned shortAmounts) = PanopticMath /// @audit `Math` 1035: if (Math.abs(currentTick - twapTick) > MAX_TWAP_DELTA_LIQUIDATION) /// @audit `Errors` 1036: revert Errors.StaleTWAP(); /// @audit `Math` 1063: Math.getSqrtRatioAtTick(twapTick) /// @audit `Errors` 1066: if (balanceCross >= thresholdCross) revert Errors.NotMarginCalled(); /// @audit `Constants` 1088: Constants.MIN_V3POOL_TICK, /// @audit `Constants` 1089: Constants.MAX_V3POOL_TICK, /// @audit `PanopticMath` 1098: (liquidationBonus0, liquidationBonus1, collateralRemaining) = PanopticMath /// @audit `Math` 1102: Math.getSqrtRatioAtTick(twapTick), /// @audit `Math` 1103: Math.getSqrtRatioAtTick(finalTick), /// @audit `PanopticMath` 1122: (deltaBonus0, deltaBonus1) = PanopticMath.haircutPremia( /// @audit `Math` 1129: Math.getSqrtRatioAtTick(_finalTick), /// @audit `Errors` 1163: ) revert Errors.NotEnoughCollateral(); /// @audit `Errors` 1188: if (touchedId.length != 1) revert Errors.InputListFail(); /// @audit `PanopticMath` 1197: (LeftRightSigned longAmounts, LeftRightSigned delegatedAmounts) = PanopticMath /// @audit `PanopticMath` 1242: refundAmounts = PanopticMath.getRefundAmounts( /// @audit `Math` 1324: Math.getSqrtRatioAtTick(atTick) /// @audit `Math` 1329: return balanceCross >= Math.unsafeDivRoundingUp(thresholdCross * buffer, 10_000); /// @audit `Math` 1348: Math.mulDiv(uint256(tokenData1.rightSlot()), 2 ** 96, sqrtPriceX96) + /// @audit `Math` 1349: Math.mulDiv96(tokenData0.rightSlot(), sqrtPriceX96); /// @audit `Math` 1353: Math.mulDivRoundingUp(uint256(tokenData1.leftSlot()), 2 ** 96, sqrtPriceX96) + /// @audit `Math` 1354: Math.mulDiv96RoundingUp(tokenData0.leftSlot(), sqrtPriceX96); /// @audit `PanopticMath` 1383: fingerprintIncomingList = PanopticMath.updatePositionsHash( /// @audit `Errors` 1394: if (fingerprintIncomingList != currentHash) revert Errors.InputListFail(); /// @audit `PanopticMath` 1410: uint256 newHash = PanopticMath.updatePositionsHash( /// @audit `Errors` 1415: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen(); /// @audit `PanopticMath` 1451: twapTick = PanopticMath.twapFilter(s_univ3pool, TWAP_WINDOW); /// @audit `Errors` 1493: revert Errors.EffectiveLiquidityAboveThreshold(); /// @audit `PanopticMath` 1521: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( /// @audit `Errors` 1596: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg(); /// @audit `PanopticMath` 1627: uint256 liquidity = PanopticMath /// @audit `PanopticMath` 1680: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( /// @audit `Math` 1778: Math.min( /// @audit `Math` 1787: Math.min( /// @audit `PanopticMath` 1877: uint256 positionLiquidity = PanopticMath /// @audit `Math` 1934: Math.max( /// @audit `Math` 1951: Math.max(
Github: [103, 105, 299, 326, 342, 415, 525, 634, 639, 712, 775, 905, 915, 923, 940, 943, 945, 981, 1035, 1036, 1063, 1066, 1088, 1089, 1098, 1102, 1103, 1122, 1129, 1163, 1188, 1197, 1242, 1324, 1329, 1348, 1349, 1353, 1354, 1383, 1394, 1410, 1415, 1451, 1493, 1521, 1596, 1627, 1680, 1778, 1787, 1877, 1934, 1951]
File: ./contracts/SemiFungiblePositionManager.sol /// @audit `Errors` 341: constructor(IUniswapV3Factory _factory) { /// @audit `Errors` 369: // There are 281,474,976,710,655 possible pool patterns. /// @audit `PanopticMath` 380: pool: IUniswapV3Pool(univ3pool), /// @audit `PanopticMath` 388: s_AddrToPoolIdData[univ3pool] = uint256(poolId) + 2 ** 255; /// @audit `CallbackLib` 429: /// @dev Pays the pool tokens owed for the swap from the payer (always the caller) /// @audit `CallbackLib` 430: /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by /// @audit `SafeTransferLib` 431: /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. /// @audit `SafeTransferLib` 433: /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. /// @audit `CallbackLib` 455: // Pay the required token from the payer to the caller of this contract /// @audit `CallbackLib` 456: SafeTransferLib.safeTransferFrom(token, decoded.payer, msg.sender, amountToPay); /// @audit `SafeTransferLib` 468: /// @param slippageTickLimitHigh The higher slippage limit when minting an ITM position (set to lower than slippageTickLimitLow for swapping when minting) /// @audit `Errors` 567: address from, /// @audit `Errors` 593: function registerTokenTransfer(address from, address to, TokenId id, uint256 amount) internal { /// @audit `PanopticMath` 627: ) /// @audit `Errors` 650: ++leg; /// @audit `Errors` 659: /// @notice Helper that checks the proposed option position and size and forwards the minting and potential swapping tasks. /// @audit `Errors` 717: if ((LeftRightSigned.unwrap(itmAmounts) != 0)) { /// @audit `Errors` 731: /// @notice When a position is minted or burnt in-the-money (ITM) we are *not* 100% token0 or 100% token1: we have a mix of both tokens. /// @audit `Errors` 752: /// It that position is burnt, then we remove a mix of the two tokens and swap one of them so that the user receives only one. /// @audit `CallbackLib` 793: // we do this by flipping the signs on the token1 ITM amount converting+deducting it against the token0 ITM amount /// @audit `CallbackLib` 794: // couple examples (price = 2 1/0): /// @audit `PanopticMath` 854: /// @notice Create the position in the AMM given in the tokenId. /// @audit `Constants` 872: LeftRightUnsigned[4] memory collectedByLeg, /// @audit `Constants` 874: ) /// @audit `PanopticMath` 939: if (amount0 > uint128(type(int128).max) || amount1 > uint128(type(int128).max)) /// @audit `Math` 949: /// but we need to pass in a flag to indicate that so the removedLiquidity is updated. /// @audit `Math` 951: /// @param tokenId the option position /// @audit `Errors` 965: internal /// @audit `Errors` 1047: /** if the position is NOT long (selling a put or a call), then _mintLiquidity to move liquidity /// @audit `LeftRightLibrary` 1156: ) /// @audit `Math` 1202: /// @dev this triggers the uniswap mint callback function /// @audit `Math` 1205: liquidityChunk.tickLower(), /// @audit `Math` 1211: // amount0 The amount of token0 that was paid to mint the given amount of liquidity /// @audit `Math` 1212: // amount1 The amount of token1 that was paid to mint the given amount of liquidity /// @audit `CallbackLib` 1223: /// @return movedAmounts how many tokens were moved from Uniswap to msg.sender /// @audit `CallbackLib` 1226: IUniswapV3Pool univ3pool /// @audit `Math` 1394: .mulDiv(premium0X64_base, numerator, totalLiquidity ** 2) /// @audit `Math` 1397: .mulDiv(premium1X64_base, numerator, totalLiquidity ** 2) /// @audit `Math` 1411: //////////////////////////////////////////////////////////////*/ /// @audit `Math` 1414: /// @dev Computes accountLiquidity[keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper))] /// @audit `Math` 1427: ) external view returns (LeftRightUnsigned accountLiquidities) { /// @audit `Math` 1429: /// @dev tokenType input here is the asset of the positions minted, this avoids put liquidity to be used for call, and vice-versa /// @audit `FeesCalc` 1508: s_accountPremiumOwed[positionKey], /// @audit `LeftRightLibrary` 1541: ) external view returns (int128 feesBase0, int128 feesBase1) {
Github: [341, 369, 380, 388, 429, 430, 431, 433, 455, 456, 468, 567, 593, 627, 650, 659, 717, 731, 752, 793, 794, 854, 872, 874, 939, 949, 951, 965, 1047, 1156, 1202, 1205, 1211, 1212, 1223, 1226, 1394, 1397, 1411, 1414, 1427, 1429, 1508, 1541]
</details>#0 - c4-judge
2024-04-26T10:27:12Z
Picodes marked the issue as grade-a
#1 - c4-judge
2024-05-06T16:14:33Z
Picodes marked the issue as grade-c
#2 - c4-judge
2024-05-06T16:14:46Z
Picodes marked the issue as grade-b
#3 - Picodes
2024-05-06T16:15:00Z
This report is close from being 100% identical than https://github.com/code-423n4/2024-04-panoptic-findings/issues/162