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: 16/55
Findings: 2
Award: $1,076.50
🌟 Selected for report: 2
🚀 Solo Findings: 0
🌟 Selected for report: DadeKuma
Also found by: Bauchibred, Dup1337, Vancelot, jesjupyter, sammy
799.7513 USDC - $799.75
When deployNewPool
is called it uses the spot price of the pool, which can be manipulated through a flashloan and thus could return a highly inaccurate result.
The price is used when deciding how much liquidity should be minted for each token, so this can result in an unbalanced pool.
In other parts of the code, this is not an issue as there are oracles that prevent price manipulations, but in case there aren't any checks to avoid so.
The spot price is used to calculate the range liquidity for each token:
@> (uint160 currentSqrtPriceX96, , , , , , ) = v3Pool.slot0(); // For full range: L = Δx * sqrt(P) = Δy / sqrt(P) // We start with fixed token amounts and apply this equation to calculate the liquidity // Note that for pools with a tickSpacing that is not a power of 2 or greater than 8 (887272 % ts != 0), // a position at the maximum and minimum allowable ticks will be wide, but not necessarily full-range. // In this case, the `fullRangeLiquidity` will always be an underestimate in respect to the token amounts required to mint. uint128 fullRangeLiquidity; unchecked { // Since we know one of the tokens is WETH, we simply add 0.1 ETH + worth in tokens if (token0 == WETH) { fullRangeLiquidity = uint128( @> Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_WETH, currentSqrtPriceX96) ); } else if (token1 == WETH) { fullRangeLiquidity = uint128( Math.mulDivRoundingUp( FULL_RANGE_LIQUIDITY_AMOUNT_WETH, Constants.FP96, @> currentSqrtPriceX96 ) ); } else { // Find the resulting liquidity for providing 1e6 of both tokens uint128 liquidity0 = uint128( @> Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN, currentSqrtPriceX96) ); uint128 liquidity1 = uint128( Math.mulDivRoundingUp( FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN, Constants.FP96, @> currentSqrtPriceX96 ) ); // Pick the greater of the liquidities - i.e the more "expensive" option // This ensures that the liquidity added is sufficiently large fullRangeLiquidity = liquidity0 > liquidity1 ? liquidity0 : liquidity1; } }
But unlike other parts of the code, the PanopticFactory
doesn't have any checks against the price (it doesn't use any oracles nor the TWAP), so each token liquidity is manipulable through flash loans.
Manual review
Consider using the TWAP price instead of the spot price.
Uniswap
#0 - c4-judge
2024-04-23T11:52:34Z
Picodes marked the issue as primary issue
#1 - dyedm1
2024-04-26T19:49:15Z
True, but I don't see negative consequences for this? This function is just a way to add some full-range liquidity to the pool so the entire range can be swapped across, and depending on the tokens we can add very small/large amounts of liquidity anyway (mentioned in the readme: Depending on the token, the amount of funds required for the initial factory deployment may be high or unrealistic)
#2 - Picodes
2024-05-01T10:48:50Z
@dyedm1 assuming the deployer has infinite approvals, can't we imagine a scenario where, by manipulating the spot pool price, it ends up depositing way too many token1 and getting sandwiched leading to a significant loss?
Said differently the risk is that currently the deployer has no control over the amount of token1 he will donate and this amount can be manipulated by an attacker.
#3 - c4-judge
2024-05-01T10:49:06Z
Picodes marked the issue as satisfactory
#4 - c4-judge
2024-05-01T10:49:15Z
Picodes changed the severity to 2 (Med Risk)
#5 - Picodes
2024-05-01T10:50:09Z
This is at most Medium to me considering pool deployers are advanced users and you need to deploy a pool where the manipulation cost is low which should remain exceptional.
#6 - c4-judge
2024-05-01T10:50:22Z
Picodes marked the issue as selected for report
#7 - dyedm1
2024-05-06T19:56:04Z
Yeah I agree this might be less than ideal if you have infinite approvals. Our UI doesn't do infinite approvals to the factory, but some wallets allow users to edit the approval amount before signing the transaction, so it might be prudent to add slippage checks here (to make the process idiot-proof).
🌟 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
276.7522 USDC - $276.75
Id | Title | Instances |
---|---|---|
[L-01] | Some parts of the codebase contain unreachable code | 1 |
[L-02] | Wrong median calculation | 1 |
[L-03] | Lack of deadline when swapping | 1 |
[L-04] | approve will always revert as the IERC20 interface mismatch | 4 |
[L-05] | Some tokens do not consider type(uint256).max as an infinite approval | 4 |
[L-06] | Contracts use infinite approvals with no means to revoke | 4 |
[L-07] | Return values of approve not checked | 4 |
[L-08] | File allows a version of solidity that is susceptible to .selector-related optimizer bug | 3 |
[L-09] | Low level calls with Solidity before 0.8.14 result in an optimiser bug | 30 |
[L-10] | Using delegatecall inside a loop may cause issues with payable functions | 1 |
[L-11] | Missing checks in constructor/initialize | 2 |
[L-12] | Missing checks for state variable assignments | 23 |
[L-13] | Vulnerability to storage write removal | 30 |
[L-14] | payable function does not transfer ETH | 1 |
[L-15] | Functions calling contracts with transfer hooks are missing reentrancy guards | 2 |
[L-16] | Large approvals may not work with some ERC20 tokens | 4 |
[L-17] | Initializers could be front-run | 2 |
[L-18] | Array lengths not checked | 4 |
[L-19] | Possible division by 0 is not prevented | 2 |
[L-20] | Missing limits when setting min/max amounts | 2 |
[L-21] | External calls in an unbounded loop can result in a DoS | 22 |
[L-22] | Solidity version 0.8.20 may not work on other chains due to PUSH0 | 20 |
[L-23] | Use of abi.encodePacked with dynamic types inside keccak256 | 9 |
[L-24] | Loss of precision on division | 35 |
[L-25] | Use increaseAllowance/decreaseAllowance instead of approve/safeApprove | 4 |
[L-26] | Using a vulnerable dependency from some libraries | 5 |
[L-27] | Missing checks for address(0) in constructor/initializers | 9 |
[L-28] | Missing checks for address(0) when updating state variables | 20 |
Total: 249 instances over 28 issues.
Id | Title | Instances |
---|---|---|
[N-01] | Custom error should be used rather than require /assert | 9 |
[N-02] | Use of transfer instead of safeTransfer is not recommended | 2 |
[N-03] | High cyclomatic complexity | 33 |
[N-04] | Missing events in sensitive functions | 8 |
[N-05] | Missing events in initializers | 1 |
[N-06] | Consider emitting an event at the end of the constructor | 4 |
[N-07] | Setters should prevent re-setting the same value | 6 |
[N-08] | Using zero as a parameter | 63 |
[N-09] | Unused named return | 20 |
[N-10] | Unused error definition | 2 |
[N-11] | Unused arguments should be removed or implemented | 8 |
[N-12] | Unused state variables | 2 |
[N-13] | OpenZeppelin libraries should be upgraded to a newer version | 5 |
[N-14] | Same constant is redefined elsewhere | 4 |
[N-15] | Enum values should be used in place of constant array indexes | 26 |
[N-16] | Variable initialization with zero value | 31 |
[N-17] | Duplicated require/if statements should be refactored | 5 |
[N-18] | Inconsistent usage of require /error | 1 |
[N-19] | Some functions contain the same exact logic | 2 |
[N-20] | Unbounded loop may run out of gas | 34 |
[N-21] | Public functions not called internally | 13 |
[N-22] | Large multiples of ten should use scientific notation | 7 |
[N-23] | Use of exponentiation instead of scientific notation | 1 |
[N-24] | Missing/malformed underscores for large numeric literals | 5 |
[N-25] | Avoid complex casting | 67 |
[N-26] | Consider using the using-for syntax | 460 |
[N-27] | Consider making contracts Upgradeable | 20 |
[N-28] | Dependence on external protocols | 19 |
[N-29] | Debug imports in production code | 1 |
[N-30] | 2**<n> - 1 should be re-written as type(uint<n>).max | 44 |
[N-31] | Use of non-named numeric constants | 285 |
[N-32] | Consider splitting complex checks into multiple steps | 23 |
[N-33] | Complex math should be split into multiple steps | 53 |
[N-34] | Time related variables should use time units instead of numbers | 3 |
[N-35] | Control structures do not comply with best practices | 120 |
[N-36] | Use a single file for system wide constants | 10 |
[N-37] | Old Solidity version | 20 |
[N-38] | Use of floating pragma | 9 |
[N-39] | No checks for empty bytes | 1 |
[N-40] | Use of abi.encodePacked instead of bytes.concat | 5 |
[N-41] | Contract functions should use an interface | 85 |
[N-42] | require /revert without any message | 9 |
[N-43] | else block is not required | 5 |
[N-44] | Multiple address /ID mappings can be combined into a single mapping of an address /ID to a struct , for readability | 4 |
[N-45] | Lack of specific import identifier | 1 |
[N-46] | Imports should be organized more systematically | 4 |
[N-47] | Long bitmasks are hard to read | 1 |
[N-48] | Use a struct to encapsulate multiple function parameters | 42 |
[N-49] | Event is missing msg.sender parameter | 9 |
[N-50] | Events should emit both new and old values | 2 |
[N-51] | Events may be emitted out of order due to reentrancy | 8 |
[N-52] | Use of polymorphism is discouraged for security audits | 15 |
[N-53] | Avoid external calls in modifiers | 1 |
[N-54] | Custom error without details | 34 |
[N-55] | Don't use uppercase for non constant /immutable variables | 2 |
[N-56] | Constants in comparisons should appear on the left side | 119 |
[N-57] | Consider using delete instead of assigning zero/false to clear values | 5 |
[N-58] | Consider disallowing transfers to address(this) | 3 |
[N-59] | Use a ternary statement instead of if /else when appropriate | 3 |
[N-60] | Consider using named returns | 103 |
[N-61] | Layout order does not comply with best practices | 6 |
[N-62] | Function visibility order does not comply with best practices | 56 |
[N-63] | Long functions should be refactored into multiple functions | 33 |
[N-64] | Consider moving duplicated strings to constants | 5 |
[N-65] | Lines are too long | 346 |
[N-66] | Some variables have a implicit default visibility | 8 |
[N-67] | Consider adding a block/deny-list | 3 |
[N-68] | Use of override is unnecessary | 4 |
[N-69] | Missing variable names | 2 |
[N-70] | Typos in comments | 86 |
[N-71] | Contracts should have full test coverage | - |
[N-72] | Large or complicated code bases should implement invariant tests | - |
[N-73] | Codebase should implement formal verification testing | - |
[N-74] | Inconsistent spacing in comments | 105 |
[N-75] | State variables should include comments | 3 |
[N-76] | Complex functions should have explicit comments | 24 |
[N-77] | Assembly code should have explicit comments | 3 |
[N-78] | Use @inheritdoc for overridden functions | 4 |
[N-79] | Modifier names don't follow the Solidity naming convention | 1 |
[N-80] | Variable names don't follow the Solidity naming convention | 52 |
[N-81] | Missing underscore prefix for non-external functions | 104 |
[N-82] | Missing underscore prefix for non-external variables | 36 |
[N-83] | Invalid NatSpec comment style | 3 |
[N-84] | Missing NatSpec from contract declarations | 1 |
[N-85] | Missing NatSpec @author from contract declaration | 1 |
[N-86] | Missing NatSpec @dev from contract declaration | 11 |
[N-87] | Missing NatSpec @notice from contract declaration | 5 |
[N-88] | Missing NatSpec @title from contract declaration | 2 |
[N-89] | Missing NatSpec @dev from error declaration | 33 |
[N-90] | Missing NatSpec @param from error declaration | 34 |
[N-91] | Missing NatSpec @dev from event declaration | 11 |
[N-92] | Missing NatSpec @dev from modifier declaration | 1 |
[N-93] | Missing NatSpec @param from modifier declaration | 1 |
[N-94] | Missing NatSpec @dev from function declaration | 142 |
[N-95] | Missing NatSpec @notice from function declaration | 3 |
[N-96] | Missing NatSpec @param from function declaration | 19 |
[N-97] | Incomplete NatSpec @return from function declaration | 8 |
Total: 3043 instances over 97 issues.
Some branches are hard-coded with constants: for example, in the following case SLOW_ORACLE_UNISWAP_MODE
is hardcoded to false, so the first part of the branch can never execute.
There is 1 instance of this issue.
File: contracts/PanopticPool.sol 914: if (SLOW_ORACLE_UNISWAP_MODE) { 915: slowOracleTick = PanopticMath.computeMedianObservedPrice( 916: _univ3pool, 917: observationIndex, 918: observationCardinality, 919: SLOW_ORACLE_CARDINALITY, 920: SLOW_ORACLE_PERIOD 921: ); 922: } else { 923: (slowOracleTick, medianData) = PanopticMath.computeInternalMedian( 924: observationIndex, 925: observationCardinality, 926: MEDIAN_PERIOD, 927: s_miniMedian, //@audit-info does not update? 928: _univ3pool 929: ); 930: }
[914-L930]
The median calculation is wrong, it always assumes an odd length, but the math is different if the length is even.
In that case, it should be:
return int24((sortedTicks[9] + sortedTicks[10]) / 2);
There is 1 instance of this issue.
File: contracts/libraries/PanopticMath.sol 266: return int24(sortedTicks[10]);
[266]
The inclusion of a transaction expiration check provides a safeguard for users against swapping tokens at a price that is lower than the current market price, but there isn't a check for a deadline, so users can be sandwiched by a MEV bot.
This can happen when the transaction remains in the mempool for a period of time because the gas cost paid by the transaction is lower than the current gas price.
There is 1 instance of this issue.
File: contracts/SemiFungiblePositionManager.sol 838: (int256 swap0, int256 swap1) = _univ3pool.swap( 839: msg.sender, 840: zeroForOne, 841: swapAmount, 842: zeroForOne 843: ? Constants.MIN_V3POOL_SQRT_RATIO + 1 844: : Constants.MAX_V3POOL_SQRT_RATIO - 1, 845: data 846: );
[838-846]
approve
will always revert as the IERC20
interface mismatchSome tokens, such as USDT, have a different implementation for the approve function: when the address is cast to a compliant IERC20
interface and the approve function is used, it will always revert due to the interface mismatch.
There are 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol 33: IERC20Partial(token0).approve(address(sfpm), type(uint256).max); 34: IERC20Partial(token1).approve(address(sfpm), type(uint256).max); 37: IERC20Partial(token0).approve(address(ct0), type(uint256).max); 38: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
type(uint256).max
as an infinite approvalSome tokens such as COMP downcast such approvals to uint96 and use that as a raw value rather than interpreting it as an infinite approval. Eventually these approvals will reach zero, at which point the calling contract will no longer function properly.
There are 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol 33: IERC20Partial(token0).approve(address(sfpm), type(uint256).max); 34: IERC20Partial(token1).approve(address(sfpm), type(uint256).max); 37: IERC20Partial(token0).approve(address(ct0), type(uint256).max); 38: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
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 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol 33: IERC20Partial(token0).approve(address(sfpm), type(uint256).max); 34: IERC20Partial(token1).approve(address(sfpm), type(uint256).max); 37: IERC20Partial(token0).approve(address(ct0), type(uint256).max); 38: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
approve
not checkedNot all IERC20
implementations (e.g. USDT, KNC) 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 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol 33: IERC20Partial(token0).approve(address(sfpm), type(uint256).max); 34: IERC20Partial(token1).approve(address(sfpm), type(uint256).max); 37: IERC20Partial(token0).approve(address(ct0), type(uint256).max); 38: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
In solidity versions prior to 0.8.21, there is a legacy code generation bug where if foo().selector
is called, foo()
doesn't actually get evaluated. It is listed as low-severity, because projects usually use the contract name rather than a function call to look up the selector.
There are 3 instances of this issue.
File: contracts/tokens/ERC1155Minimal.sol 115: ERC1155Holder.onERC1155Received.selector 166: ERC1155Holder.onERC1155BatchReceived.selector 225: ERC1155Holder.onERC1155Received.selector
0.8.14
result in an optimiser bugThe project contracts in scope are using low level calls with solidity version before 0.8.14 which can result in an optimizer bug.
This bug causes the optimizer to consider some memory operations in inline assembly as being 'dead' and remove them.
Later operations that would read the values written by these improperly removed memory operations will instead observe the old version of memory.
There are 30 instances of this issue.
<details> <summary>Expand findings</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") {
[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 25: assembly ("memory-safe") { 56: assembly ("memory-safe") {
delegatecall
inside a loop may cause issues with payable
functionsIf one of the delegatecall
consumes part of the msg.value
, other calls might fail, if they expect the full msg.value
. Consider using a different design, or fully document this decision to avoid potential issues.
There is 1 instance of this issue.
File: contracts/multicall/Multicall.sol 15: (bool success, bytes memory result) = address(this).delegatecall(data[i]);
[15]
There are some missing checks in these functions, and this could lead to unexpected scenarios. Consider always adding a sanity check for state variables.
There are 2 instances of this issue.
File: contracts/CollateralTracker.sol // @audit _commissionFee, _sellerCollateralRatio, _buyerCollateralRatio, _forceExerciseCost, _targetPoolUtilization, _saturatedPoolUtilization, _ITMSpreadMultiplier 178: constructor( 179: uint256 _commissionFee, 180: uint256 _sellerCollateralRatio, 181: uint256 _buyerCollateralRatio, 182: int256 _forceExerciseCost, 183: uint256 _targetPoolUtilization, 184: uint256 _saturatedPoolUtilization, 185: uint256 _ITMSpreadMultiplier
[178-185]
File: contracts/SemiFungiblePositionManager.sol // @audit fee 350: function initializeAMMPool(address token0, address token1, uint24 fee) external {
[350]
There are some missing checks in these functions, and this could lead to unexpected scenarios. Consider always adding a sanity check for state variables.
There are 23 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 241: s_underlyingToken = underlyingIsToken0 ? token0 : token1; 244: s_panopticPool = panopticPool; 251: s_poolFee = _poolFee; 254: s_univ3token0 = token0; 255: s_univ3token1 = token1; 258: s_underlyingIsToken0 = underlyingIsToken0; 262: s_ITMSpreadFee = uint128((ITM_SPREAD_MULTIPLIER * _poolFee) / DECIMALS); 1028: s_poolAssets = uint128(uint256(updatedAssets)); 1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount))); 1084: s_poolAssets = uint128(uint256(updatedAssets + realizedPremium)); 1085: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount)));
[241, 244, 251, 254, 255, 258, 262, 1028, 1029, 1084, 1085]
File: contracts/PanopticPool.sol 665: if (medianData != 0) s_miniMedian = medianData; 763: s_options[msg.sender][tokenId][leg] = LeftRightUnsigned 764: .wrap(0) 765: .toRightSlot(premiumAccumulator0) 766: .toLeftSlot(premiumAccumulator1); 1628: s_options[owner][tokenId][legIndex] = accumulatedPremium; //@audit-ok can't be lower than before 1656: s_settledTokens[chunkKey] = s_settledTokens[chunkKey].add( 1657: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(realizedPremia))) 1658: ); 1683: s_settledTokens[chunkKey] = s_settledTokens[chunkKey].add(collectedByLeg[leg]); 1731: s_grossPremiumLast[chunkKey] = LeftRightUnsigned 1732: .wrap(0) 1733: .toRightSlot( 1734: uint128( 1735: (grossCurrent[0] * 1736: positionLiquidity + 1737: grossPremiumLast.rightSlot() * 1738: totalLiquidityBefore) / (totalLiquidity) 1739: ) 1740: ) 1741: .toLeftSlot( 1742: uint128( 1743: (grossCurrent[1] * 1744: positionLiquidity + 1745: grossPremiumLast.leftSlot() * 1746: totalLiquidityBefore) / (totalLiquidity) 1747: ) 1748: ); 1934: s_grossPremiumLast[chunkKey] = totalLiquidity != 0 1935: ? LeftRightUnsigned 1936: .wrap(0) 1937: .toRightSlot( 1938: uint128( 1939: uint256( 1940: Math.max( 1941: (int256( 1942: grossPremiumLast.rightSlot() * 1943: totalLiquidityBefore 1944: ) - 1945: int256( 1946: _premiumAccumulatorsByLeg[_leg][0] * 1947: positionLiquidity 1948: )) + int256(legPremia.rightSlot() * 2 ** 64), 1949: 0 1950: ) 1951: ) / totalLiquidity 1952: ) 1953: ) 1954: .toLeftSlot( 1955: uint128( 1956: uint256( 1957: Math.max( 1958: (int256( 1959: grossPremiumLast.leftSlot() * 1960: totalLiquidityBefore 1961: ) - 1962: int256( 1963: _premiumAccumulatorsByLeg[_leg][1] * 1964: positionLiquidity 1965: )) + int256(legPremia.leftSlot()) * 2 ** 64, 1966: 0 1967: ) 1968: ) / totalLiquidity 1969: ) 1970: ) 1971: : LeftRightUnsigned 1972: .wrap(0) 1973: .toRightSlot(uint128(premiumAccumulatorsByLeg[_leg][0])) 1974: .toLeftSlot(uint128(premiumAccumulatorsByLeg[_leg][1])); 1980: s_settledTokens[chunkKey] = settledTokens;
[665, 763-766, 1628, 1656-1658, 1683, 1731-1748, 1934-1974, 1980]
File: contracts/SemiFungiblePositionManager.sol 1039: s_accountLiquidity[positionKey] = LeftRightUnsigned 1040: .wrap(0) 1041: .toLeftSlot(removedLiquidity) 1042: .toRightSlot(updatedLiquidity); 1099: s_accountFeesBase[positionKey] = _getFeesBase( 1100: univ3pool, 1101: updatedLiquidity, 1102: liquidityChunk, 1103: true 1104: );
</details>File: contracts/tokens/ERC20Minimal.sol 51: allowance[msg.sender][spender] = amount; 85: if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
This bug was introduced in Solidity version 0.8.13, and it was fixed in version 0.8.17. This bug is significantly easier to trigger with optimized via-IR code generation, but can theoretically also occur in optimized legacy code generation.. More info can be read in this post.
There are 30 instances of this issue.
<details> <summary>Expand findings</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") {
[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 25: assembly ("memory-safe") { 56: assembly ("memory-safe") {
payable
function does not transfer ETHThe following functions can be called by any user, who may also send some funds by mistake. In that case, those funds will be lost (this also applies to delegatecalls, in case they don't use the transferred ETH).
There is 1 instance of this issue.
File: contracts/multicall/Multicall.sol 12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
[12]
Even if the function follows the best practice of check-effects-interaction, not using a reentrancy guard when there may be transfer hooks will open the users of this protocol up to read-only reentrancies with no way to protect against it, except by block-listing the whole protocol.
There are 2 instances of this issue.
File: contracts/CollateralTracker.sol 333: return ERC20Minimal.transfer(recipient, amount); 352: return ERC20Minimal.transferFrom(from, to, amount);
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 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol 33: IERC20Partial(token0).approve(address(sfpm), type(uint256).max); 34: IERC20Partial(token1).approve(address(sfpm), type(uint256).max); 37: IERC20Partial(token0).approve(address(ct0), type(uint256).max); 38: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
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 are 2 instances of this issue.
File: contracts/PanopticFactory.sol 135: function initialize(address _owner) public {
[135]
File: contracts/SemiFungiblePositionManager.sol 350: function initializeAMMPool(address token0, address token1, uint24 fee) external {
[350]
If the length of the arrays are not required to be of the same length, user operations may not be fully executed.
There are 4 instances of this issue.
File: contracts/SemiFungiblePositionManager.sol // @audit ids, amounts 566: function safeBatchTransferFrom(
[566]
File: contracts/libraries/PanopticMath.sol // @audit positionIdList, premiasByLeg 768: function haircutPremia(
[768]
File: contracts/tokens/ERC1155Minimal.sol // @audit ids, amounts 130: function safeBatchTransferFrom( // @audit owners, ids 178: function balanceOfBatch(
These functions can be called with 0 value in the input and this value is not checked for being bigger than 0, that means in some scenarios this can potentially trigger a division by zero.
There are 2 instances of this issue.
File: contracts/libraries/PanopticMath.sol // @audit tickSpacing 346: int24 minTick = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing; // @audit tickSpacing 347: int24 maxTick = (Constants.MAX_V3POOL_TICK / tickSpacing) * tickSpacing;
There are some missing limits in these functions, and this could lead to unexpected scenarios. Consider adding a min/max limit for the following values, when appropriate.
There are 2 instances of this issue.
File: contracts/PanopticPool.sol // @audit missing both checks -> positionSize 1672: function _updateSettlementPostMint( 1673: TokenId tokenId, 1674: LeftRightUnsigned[4] memory collectedByLeg, 1675: uint128 positionSize // @audit missing both checks -> positionSize 1839: function _updateSettlementPostBurn( 1840: address owner, 1841: TokenId tokenId, 1842: LeftRightUnsigned[4] memory collectedByLeg, 1843: uint128 positionSize, 1844: bool commitLongSettled
Consider limiting the number of iterations in loops that make external calls, as just a single one of them failing will result in a revert.
There are 22 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol // @audit countLegs (662), isLong (664), unsafeDivRoundingUp (669), width (670), tickSpacing (670), max (675), abs (677), strike (677), getLiquidityChunk (687), getAmountsForLiquidity (693), getAmountsForLiquidity (698), tokenType (704), toLeftSlot (712), toRightSlot (712), wrap (712) 662: for (uint256 leg = 0; leg < positionId.countLegs(); ++leg) { // @audit wrap (1210), rightSlot (1213), wrap (1213), leftSlot (1216), wrap (1216) 1208: for (uint256 i = 0; i < totalIterations; ) { // @audit tokenType (1257) 1255: for (uint256 index = 0; index < numLegs; ++index) {
File: contracts/PanopticFactory.sol // @audit numberOfLeadingHexZeros (305), predictDeterministicAddress (306) 304: for (; uint256(salt) < maxSalt; ) {
[304]
File: contracts/PanopticPool.sol // @audit unwrap (445), unwrap (446), rightSlot (453), wrap (453), countLegs (459), isLong (461), strike (464), width (465), tokenType (466), wrap (474), unwrap (474), wrap (478), unwrap (478) 442: for (uint256 k = 0; k < pLength; ) { //@audit H - uncapped positions -> unliquidable // @audit asTicks (749), isLong (750), getAccountPremium (752), tokenType (755), toLeftSlot (763), toRightSlot (763), wrap (763), min (776) 746: for (uint256 leg = 0; leg < numLegs; ) { // @audit isLong (866), asTicks (868), wrap (871) 865: for (uint256 leg = 0; leg < numLegs; ) { // @audit updatePositionsHash (1389) 1388: for (uint256 i = 0; i < pLength; ) { // @audit isLong (1525), getLiquidityChunk (1527), tokenType (1532), getAccountPremium (1534), tickLower (1539), tickUpper (1540), toLeftSlot (1551), toRightSlot (1551), wrap (1551), rightSlot (1557), liquidity (1558), leftSlot (1566), liquidity (1567), wrap (1573) 1524: for (uint256 leg = 0; leg < numLegs; ) { // @audit strike (1680), width (1680), tokenType (1680), isLong (1685), getLiquidityChunk (1686), getAccountPremium (1713), tokenType (1716), tickLower (1717), tickUpper (1718), liquidity (1727), toLeftSlot (1731), toRightSlot (1731), wrap (1731), rightSlot (1737), leftSlot (1745) 1678: for (uint256 leg = 0; leg < numLegs; ++leg) { // @audit strike (1862), width (1862), tokenType (1862), unwrap (1868), isLong (1870), wrap (1872), unwrap (1874), wrap (1875), unwrap (1876), liquidity (1883), getLiquidityChunk (1883), wrap (1898), unwrap (1898), wrap (1907), unwrap (1907), toLeftSlot (1935), toRightSlot (1935), wrap (1935), max (1940), rightSlot (1942), rightSlot (1948), max (1957), leftSlot (1959), leftSlot (1965), toLeftSlot (1971), toRightSlot (1971), wrap (1971) 1858: for (uint256 leg = 0; leg < numLegs; ) {
[442, 746, 865, 1388, 1524, 1678, 1858]
File: contracts/SemiFungiblePositionManager.sol // @audit incrementPoolPattern (373) 372: while (address(s_poolContext[poolId].pool) != address(0)) { // @audit poolId (576), wrap (576), ReentrantCall (576), wrap (577) 575: for (uint256 i = 0; i < ids.length; ) { // @audit getLiquidityChunk (604), tokenType (615), tickLower (616), tickUpper (617), tokenType (624), tickLower (625), tickUpper (626), unwrap (632), unwrap (633), TransferFailed (634), unwrap (639), liquidity (639), TransferFailed (640), wrap (646), wrap (649) 601: for (uint256 leg = 0; leg < numLegs; ) { // @audit getLiquidityChunk (905), getAmount0ForLiquidity (923), getAmount1ForLiquidity (925) 883: for (uint256 leg = 0; leg < numLegs; ) {
File: contracts/libraries/FeesCalc.sol // @audit rightSlot (53), countLegs (54), getLiquidityChunk (56), getAmountsForLiquidity (62), isLong (67) 51: for (uint256 k = 0; k < positionIdList.length; ) {
[51]
File: contracts/libraries/PanopticMath.sol // @audit observations (138) 137: for (uint256 i = 0; i < cardinality + 1; ++i) { // @audit countLegs (783), isLong (785) 781: for (uint256 i = 0; i < positionIdList.length; ++i) { // @audit countLegs (863), isLong (864), unsafeDivRoundingUp (869), rightSlot (870), rightSlot (871), unsafeDivRoundingUp (873), leftSlot (874), leftSlot (875), strike (880), width (881), tokenType (882), max (888), rightSlot (890), max (892), leftSlot (894), toLeftSlot (898), toRightSlot (898), wrap (898) 860: for (uint256 i = 0; i < positionIdList.length; i++) {
File: contracts/multicall/Multicall.sol // @audit delegatecall (15) 14: for (uint256 i = 0; i < data.length; ) {
[14]
</details>File: contracts/types/TokenId.sol // @audit optionRatio (508), unwrap (512), InvalidTokenIdParameter (513), countLegs (519), InvalidTokenIdParameter (522), width (528), InvalidTokenIdParameter (528), strike (531), strike (532), InvalidTokenIdParameter (533), riskPartner (538), riskPartner (541), InvalidTokenIdParameter (542), asset (546), asset (546), optionRatio (547), optionRatio (547), InvalidTokenIdParameter (548), isLong (551), isLong (552), tokenType (555), tokenType (556), InvalidTokenIdParameter (561), InvalidTokenIdParameter (567) 507: for (uint256 i = 0; i < 4; ++i) { // @audit getRangesFromStrike (582), width (583), tickSpacing (584), strike (587), isLong (592) 581: for (uint256 i = 0; i < numLegs; ++i) {
0.8.20
may not work on other chains due to PUSH0
In Solidity 0.8.20
's compiler, the default target EVM version has been changed to Shanghai. This version introduces a new op code called PUSH0
.
However, not all Layer 2 solutions have implemented this op code yet, leading to deployment failures on these chains. To overcome this problem, it is recommended to utilize an earlier EVM version.
There are 20 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/PanopticFactory.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/PanopticPool.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/SemiFungiblePositionManager.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/libraries/CallbackLib.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/Constants.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/Errors.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/FeesCalc.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/InteractionHelper.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/Math.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/PanopticMath.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/SafeTransferLib.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/multicall/Multicall.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/tokens/ERC1155Minimal.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/ERC20Minimal.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/types/LeftRight.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/types/LiquidityChunk.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/types/TokenId.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/interfaces/IDonorNFT.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/interfaces/IERC20Partial.sol 2: pragma solidity ^0.8.0;
[2]
</details>abi.encodePacked
with dynamic types inside keccak256
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, to prevent any hash collisions.
There are 9 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/PanopticPool.sol 462: bytes32 chunkKey = keccak256( 463: abi.encodePacked( 464: tokenId.strike(leg), 465: tokenId.width(leg), 466: tokenId.tokenType(leg) 467: ) //@audit-info collision between two legs? (isLong or riskPartner) 468: ); 1648: bytes32 chunkKey = keccak256( 1649: abi.encodePacked( 1650: tokenId.strike(legIndex), 1651: tokenId.width(legIndex), 1652: tokenId.tokenType(legIndex) 1653: ) 1654: ); 1679: bytes32 chunkKey = keccak256( 1680: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1681: ); 1861: bytes32 chunkKey = keccak256( 1862: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1863: );
[462-468, 1648-1654, 1679-1681, 1861-1863]
File: contracts/SemiFungiblePositionManager.sol 611: bytes32 positionKey_from = keccak256( 612: abi.encodePacked( 613: address(univ3pool), 614: from, 615: id.tokenType(leg), 616: liquidityChunk.tickLower(), 617: liquidityChunk.tickUpper() 618: ) 619: ); 620: bytes32 positionKey_to = keccak256( 621: abi.encodePacked( 622: address(univ3pool), 623: to, 624: id.tokenType(leg), 625: liquidityChunk.tickLower(), 626: liquidityChunk.tickUpper() 627: ) 628: ); 975: bytes32 positionKey = keccak256( 976: abi.encodePacked( 977: address(univ3pool), 978: msg.sender, 979: tokenType, 980: liquidityChunk.tickLower(), 981: liquidityChunk.tickUpper() 982: ) 983: ); 1151: keccak256( 1152: abi.encodePacked( 1153: address(this), 1154: liquidityChunk.tickLower(), 1155: liquidityChunk.tickUpper() 1156: ) 1157: )
[611-619, 620-628, 975-983, 1151-1157]
File: contracts/libraries/PanopticMath.sol 878: bytes32 chunkKey = keccak256( 879: abi.encodePacked( 880: tokenId.strike(0), 881: tokenId.width(0), 882: tokenId.tokenType(0) 883: ) 884: );
[878-884]
</details>Solidity doesn't support fractions, so divisions by large numbers could result in the quotient being zero.
To avoid this, it's recommended to require a minimum numerator amount to ensure that it is always greater than the denominator.
There are 35 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 205: (7812 * ratioTick ** 2) / 206: 10_000 ** 2 + 207: (6510 * ratioTick ** 3) / 208: 10_000 ** 3 249: _poolFee = fee / 100; 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) 731: .toRightSlot(int128((longAmounts.rightSlot() * fee) / DECIMALS_128)) 732: .toLeftSlot(int128((longAmounts.leftSlot() * fee) / DECIMALS_128)); 743: return int256((s_inAMM * DECIMALS) / totalAssets()); 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / 798: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL); 850: (BUYER_COLLATERAL_RATIO * (SATURATED_POOL_UTIL - utilization)) / 851: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL)) / 2; // do the division by 2 at the end after all addition and multiplication; b/c y1 = buyCollateralRatio / 2
[205-206, 207-208, 249, 262, 446, 677, 731, 732, 743, 797-798, 850-851]
File: contracts/PanopticFactory.sol 393: tickLower = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing;
[393]
File: contracts/PanopticPool.sol 1493: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; 1556: ((premiumAccumulatorsByLeg[leg][0] - 1557: premiumAccumulatorLast.rightSlot()) * 1558: (liquidityChunk.liquidity())) / 2 ** 64 1565: ((premiumAccumulatorsByLeg[leg][1] - 1566: premiumAccumulatorLast.leftSlot()) * 1567: (liquidityChunk.liquidity())) / 2 ** 64 1641: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) 1642: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); 1735: (grossCurrent[0] * 1736: positionLiquidity + 1737: grossPremiumLast.rightSlot() * 1738: totalLiquidityBefore) / (totalLiquidity) 1743: (grossCurrent[1] * 1744: positionLiquidity + 1745: grossPremiumLast.leftSlot() * 1746: totalLiquidityBefore) / (totalLiquidity) 1774: uint256 accumulated0 = ((premiumAccumulators[0] - grossPremiumLast.rightSlot()) * 1775: totalLiquidity) / 2 ** 64; 1776: uint256 accumulated1 = ((premiumAccumulators[1] - grossPremiumLast.leftSlot()) * 1777: totalLiquidity) / 2 ** 64; 1785: (uint256(premiumOwed.rightSlot()) * settledTokens.rightSlot()) / 1786: (accumulated0 == 0 ? type(uint256).max : accumulated0), 1794: (uint256(premiumOwed.leftSlot()) * settledTokens.leftSlot()) / 1795: (accumulated1 == 0 ? type(uint256).max : accumulated1), 1939: uint256( 1940: Math.max( 1941: (int256( 1942: grossPremiumLast.rightSlot() * 1943: totalLiquidityBefore 1944: ) - 1945: int256( 1946: _premiumAccumulatorsByLeg[_leg][0] * 1947: positionLiquidity 1948: )) + int256(legPremia.rightSlot() * 2 ** 64), 1949: 0 1950: ) 1951: ) / totalLiquidity 1956: uint256( 1957: Math.max( 1958: (int256( 1959: grossPremiumLast.leftSlot() * 1960: totalLiquidityBefore 1961: ) - 1962: int256( 1963: _premiumAccumulatorsByLeg[_leg][1] * 1964: positionLiquidity 1965: )) + int256(legPremia.leftSlot()) * 2 ** 64, 1966: 0 1967: ) 1968: ) / totalLiquidity
[1493, 1556-1558, 1565-1567, 1641, 1642, 1735-1738, 1743-1746, 1774-1775, 1776-1777, 1785-1786, 1794-1795, 1939-1951, 1956-1968]
File: contracts/SemiFungiblePositionManager.sol 1368: uint256 numerator = netLiquidity + (removedLiquidity / 2 ** VEGOID); 1392: ((removedLiquidity ** 2) / 2 ** (VEGOID));
File: contracts/libraries/Math.sol 176: if (tick > 0) sqrtR = type(uint256).max / sqrtR; 196: mulDiv( 197: uint256(liquidityChunk.liquidity()) << 96, 198: highPriceX96 - lowPriceX96, 199: highPriceX96 200: ) / lowPriceX96;
File: contracts/libraries/PanopticMath.sol 150: (tickCumulatives[i] - tickCumulatives[i + 1]) / 151: int256(timestamps[i] - timestamps[i + 1]); 195: (tickCumulative_last - tickCumulative_old) / 196: int256(timestamp_last - timestamp_old) 258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20)) 346: int24 minTick = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing; 347: int24 maxTick = (Constants.MAX_V3POOL_TICK / tickSpacing) * tickSpacing; 669: uint256 requiredRatioX128 = (required0 << 128) / (required0 + required1);
[150-151, 195-196, 258, 346, 347, 669]
</details>increaseAllowance/decreaseAllowance
instead of 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 ths problem.
There are 4 instances of this issue.
File: contracts/libraries/InteractionHelper.sol 33: IERC20Partial(token0).approve(address(sfpm), type(uint256).max); 34: IERC20Partial(token1).approve(address(sfpm), type(uint256).max); 37: IERC20Partial(token0).approve(address(ct0), type(uint256).max); 38: IERC20Partial(token1).approve(address(ct1), type(uint256).max);
This project is using a vulnerable version of some libraries, which have the following issues:
Current @openzeppelin/contracts
version: 4.6.0
Risk | Title | Min Version | Max Version |
---|---|---|---|
LOW | Denial of Service (DoS) | >=3.2.0 | <4.8.3 |
MEDIUM | Improper Input Validation | >=3.3.0 | <4.9.2 |
MEDIUM | Improper Encoding or Escaping of Output | >=4.0.0 | <4.9.3 |
HIGH | Improper Verification of Cryptographic Signature | >=0.0.0 | <4.7.3 |
MEDIUM | Denial of Service (DoS) | >=2.3.0 | <4.7.2 |
LOW | Incorrect Resource Transfer Between Spheres | >=4.6.0 | <4.7.2 |
HIGH | Incorrect Calculation | >=4.3.0 | <4.7.2 |
HIGH | Information Exposure | >=4.1.0 | <4.7.1 |
HIGH | Information Exposure | >=4.0.0 | <4.7.1 |
LOW | Missing Authorization | >=4.3.0 | <4.9.1 |
There are 5 instances of this issue.
File: contracts/PanopticFactory.sol 14: import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
[14]
File: contracts/PanopticPool.sol 9: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
[9]
File: contracts/libraries/InteractionHelper.sol 6: import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 10: import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
File: contracts/tokens/ERC1155Minimal.sol 5: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
[5]
address(0)
in constructor/initializersCheck for zero-address to avoid the risk of setting address(0)
for state variables when deploying.
There are 9 instances of this issue.
File: contracts/PanopticFactory.sol // @audit _WETH9 124: WETH = _WETH9; // @audit _SFPM 125: SFPM = _SFPM; // @audit _univ3Factory 128: UNIV3_FACTORY = _univ3Factory; // @audit _donorNFT 126: DONOR_NFT = _donorNFT; // @audit _poolReference 129: POOL_REFERENCE = _poolReference; // @audit _collateralReference 130: COLLATERAL_REFERENCE = _collateralReference; // @audit _owner 137: s_owner = _owner;
[124, 125, 128, 126, 129, 130, 137]
File: contracts/PanopticPool.sol // @audit _sfpm 282: SFPM = _sfpm;
[282]
File: contracts/SemiFungiblePositionManager.sol // @audit _factory 342: FACTORY = _factory;
[342]
address(0)
when updating state variablesCheck for zero-address to avoid the risk of setting address(0)
for state variables after an update.
There are 20 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol // @audit token0 241: s_underlyingToken = underlyingIsToken0 ? token0 : token1; // @audit token1 241: s_underlyingToken = underlyingIsToken0 ? token0 : token1; // @audit panopticPool 244: s_panopticPool = panopticPool;
File: contracts/PanopticFactory.sol // @audit newOwner 153: s_owner = newOwner;
[153]
File: contracts/PanopticPool.sol // @audit _univ3pool 303: s_univ3pool = IUniswapV3Pool(_univ3pool); // @audit collateralTracker0 319: s_collateralToken0 = collateralTracker0; // @audit collateralTracker1 320: s_collateralToken1 = collateralTracker1; // @audit tokenId 763: s_options[msg.sender][tokenId][leg] = LeftRightUnsigned 764: .wrap(0) 765: .toRightSlot(premiumAccumulator0) 766: .toLeftSlot(premiumAccumulator1); // @audit owner 862: s_positionBalance[owner][tokenId] = LeftRightUnsigned.wrap(0); // @audit tokenId 862: s_positionBalance[owner][tokenId] = LeftRightUnsigned.wrap(0); // @audit account 1422: s_positionsHash[account] = newHash; // @audit owner 1628: s_options[owner][tokenId][legIndex] = accumulatedPremium; //@audit-ok can't be lower than before
[303, 319, 320, 763-766, 862, 862, 1422, 1628]
File: contracts/SemiFungiblePositionManager.sol // @audit from 645: s_accountLiquidity[positionKey_to] = fromLiq; // @audit to 645: s_accountLiquidity[positionKey_to] = fromLiq; // @audit id 645: s_accountLiquidity[positionKey_to] = fromLiq; // @audit univ3pool 1099: s_accountFeesBase[positionKey] = _getFeesBase( 1100: univ3pool, 1101: updatedLiquidity, 1102: liquidityChunk, 1103: true 1104: ); // @audit liquidityChunk 1099: s_accountFeesBase[positionKey] = _getFeesBase( 1100: univ3pool, 1101: updatedLiquidity, 1102: liquidityChunk, 1103: true 1104: );
[645, 645, 645, 1099-1104, 1099-1104]
File: contracts/tokens/ERC1155Minimal.sol // @audit operator 82: isApprovedForAll[msg.sender][operator] = approved;
[82]
</details>File: contracts/tokens/ERC20Minimal.sol // @audit spender 51: allowance[msg.sender][spender] = amount; // @audit from 85: if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
error
should be used rather than require
/assert
Custom errors are available from solidity version 0.8.4. Custom errors are more easily processed in try-catch blocks, and are easier to re-use and maintain.
There are 9 instances of this issue.
File: contracts/libraries/Math.sol 361: require(denominator > 0); 370: require(denominator > prod1); 448: require(result < type(uint256).max); 484: require(2 ** 64 > prod1); 547: require(2 ** 96 > prod1); 588: require(result < type(uint256).max); 624: require(2 ** 128 > prod1); 665: require(result < type(uint256).max); 701: require(2 ** 192 > prod1);
[361, 370, 448, 484, 547, 588, 624, 665, 701]
transfer
instead of safeTransfer
is not recommendedIt is good to add a require
statement that checks the return value of token transfers, or to use something like OpenZeppelin's safeTransfer
/safeTransferFrom
, even if one is sure that the given token reverts in case of a failure.
This reduces the risk to zero even if these contracts are upgreadable, and it also helps with security reviews, as the auditor will not have to check this specific edge case.
There are 2 instances of this issue.
File: contracts/CollateralTracker.sol 333: return ERC20Minimal.transfer(recipient, amount); 352: return ERC20Minimal.transferFrom(from, to, amount);
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 33 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 650: function exerciseCost( 911: function revoke( 1311: function _getRequiredCollateralSingleLegNoPartner( 1510: function _computeSpread(
File: contracts/PanopticFactory.sol 211: function deployNewPool( 336: function _mintFullRange(
File: contracts/PanopticPool.sol 430: function _calculateAccumulatedPremia( //@audit-info positive premia??? 615: function _mintOptions( 888: function _validateSolvency( 1018: function liquidate( 1180: function forceExercise( 1509: function _getPremia( 1593: function settleLongPremium( 1672: function _updateSettlementPostMint( 1839: function _updateSettlementPostBurn(
[430, 615, 888, 1018, 1180, 1509, 1593, 1672, 1839]
File: contracts/SemiFungiblePositionManager.sol 593: function registerTokenTransfer(address from, address to, TokenId id, uint256 amount) internal { 757: function swapInAMM( 864: function _createPositionInAMM( 959: function _createLegInAMM( 1256: function _collectAndWritePositionData( 1322: function _getPremiaDeltas( 1450: function getAccountPremium(
[593, 757, 864, 959, 1256, 1322, 1450]
File: contracts/libraries/FeesCalc.sol 130: function _getAMMSwapFeesPerLiquidityCollected(
[130]
File: contracts/libraries/Math.sol 128: function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160) { //@audit-ok 340: function mulDiv( 458: function mulDiv64(uint256 a, uint256 b) internal pure returns (uint256) { 521: function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256) { 598: function mulDiv128(uint256 a, uint256 b) internal pure returns (uint256) { 675: function mulDiv192(uint256 a, uint256 b) internal pure returns (uint256) {
[128, 340, 458, 521, 598, 675]
File: contracts/libraries/PanopticMath.sol 168: function computeInternalMedian( 651: function getLiquidationBonus( 768: function haircutPremia(
File: contracts/types/TokenId.sol 500: function validate(TokenId self) internal pure {
[500]
</details>Events should be emitted when sensitive changes are made to the contracts, but some functions lack them.
There are 8 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/PanopticPool.sol 860: function _updatePositionDataBurn(address owner, TokenId tokenId) internal { 861: // reset balances and delete stored option data 862: s_positionBalance[owner][tokenId] = LeftRightUnsigned.wrap(0); 863: 864: uint256 numLegs = tokenId.countLegs(); 865: for (uint256 leg = 0; leg < numLegs; ) { 866: if (tokenId.isLong(leg) == 0) { 867: // Check the liquidity spread, make sure that closing the option does not exceed the MAX_SPREAD allowed 868: (int24 tickLower, int24 tickUpper) = tokenId.asTicks(leg); 869: _checkLiquiditySpread(tokenId, leg, tickLower, tickUpper, MAX_SPREAD); 870: } 871: s_options[owner][tokenId][leg] = LeftRightUnsigned.wrap(0); 872: unchecked { 873: ++leg; 874: } 875: } 876: 877: // Update the position list hash (hash = XOR of all keccak256(tokenId)). Remove hash by XOR'ing again 878: _updatePositionsHash(owner, tokenId, !ADD); 879: } 1411: function _updatePositionsHash(address account, TokenId tokenId, bool addFlag) internal { 1412: // Get the current position hash value (fingerprint of all pre-existing positions created by '_account') 1413: // Add the current tokenId to the positionsHash as XOR'd 1414: // since 0 ^ x = x, no problem on first mint 1415: // Store values back into the user option details with the updated hash (leaves the other parameters unchanged) 1416: uint256 newHash = PanopticMath.updatePositionsHash( 1417: s_positionsHash[account], 1418: tokenId, 1419: addFlag 1420: ); 1421: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen(); 1422: s_positionsHash[account] = newHash; 1423: } 1672: function _updateSettlementPostMint( 1673: TokenId tokenId, 1674: LeftRightUnsigned[4] memory collectedByLeg, 1675: uint128 positionSize 1676: ) internal { 1677: uint256 numLegs = tokenId.countLegs(); 1678: for (uint256 leg = 0; leg < numLegs; ++leg) { 1679: bytes32 chunkKey = keccak256( 1680: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1681: ); 1682: // add any tokens collected from Uniswap in a given chunk to the settled tokens available for withdrawal by sellers 1683: s_settledTokens[chunkKey] = s_settledTokens[chunkKey].add(collectedByLeg[leg]); 1684: 1685: if (tokenId.isLong(leg) == 0) { 1686: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 1687: tokenId, 1688: leg, 1689: positionSize 1690: ); 1691: 1692: // new totalLiquidity (total sold) = removedLiquidity + netLiquidity (R + N) 1693: uint256 totalLiquidity = _getTotalLiquidity(tokenId, leg); 1694: 1695: // We need to adjust the grossPremiumLast value such that the result of 1696: // (grossPremium - adjustedGrossPremiumLast)*updatedTotalLiquidityPostMint/2**64 is equal to (grossPremium - grossPremiumLast)*totalLiquidityBeforeMint/2**64 1697: // G: total gross premium 1698: // T: totalLiquidityBeforeMint 1699: // R: positionLiquidity 1700: // C: current grossPremium value 1701: // L: current grossPremiumLast value 1702: // Ln: updated grossPremiumLast value 1703: // T * (C - L) = G 1704: // (T + R) * (C - Ln) = G 1705: // 1706: // T * (C - L) = (T + R) * (C - Ln) 1707: // (TC - TL) / (T + R) = C - Ln 1708: // Ln = C - (TC - TL)/(T + R) 1709: // Ln = (CT + CR - TC + TL)/(T+R) 1710: // Ln = (CR + TL)/(T+R) 1711: 1712: uint256[2] memory grossCurrent; 1713: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium( 1714: address(s_univ3pool), 1715: address(this), 1716: tokenId.tokenType(leg), 1717: liquidityChunk.tickLower(), 1718: liquidityChunk.tickUpper(), 1719: type(int24).max, 1720: 0 1721: ); 1722: 1723: unchecked { 1724: // L 1725: LeftRightUnsigned grossPremiumLast = s_grossPremiumLast[chunkKey]; 1726: // R 1727: uint256 positionLiquidity = liquidityChunk.liquidity(); 1728: // T (totalLiquidity is (T + R) after minting) 1729: uint256 totalLiquidityBefore = totalLiquidity - positionLiquidity; 1730: 1731: s_grossPremiumLast[chunkKey] = LeftRightUnsigned 1732: .wrap(0) 1733: .toRightSlot( 1734: uint128( 1735: (grossCurrent[0] * 1736: positionLiquidity + 1737: grossPremiumLast.rightSlot() * 1738: totalLiquidityBefore) / (totalLiquidity) 1739: ) 1740: ) 1741: .toLeftSlot( 1742: uint128( 1743: (grossCurrent[1] * 1744: positionLiquidity + 1745: grossPremiumLast.leftSlot() * 1746: totalLiquidityBefore) / (totalLiquidity) 1747: ) 1748: ); 1749: } 1750: } 1751: } 1752: } 1839: function _updateSettlementPostBurn( 1840: address owner, 1841: TokenId tokenId, 1842: LeftRightUnsigned[4] memory collectedByLeg, 1843: uint128 positionSize, 1844: bool commitLongSettled 1845: ) internal returns (LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg) { 1846: uint256 numLegs = tokenId.countLegs(); 1847: uint256[2][4] memory premiumAccumulatorsByLeg; 1848: 1849: // compute accumulated fees 1850: (premiaByLeg, premiumAccumulatorsByLeg) = _getPremia( 1851: tokenId, 1852: positionSize, 1853: owner, 1854: COMPUTE_ALL_PREMIA, 1855: type(int24).max 1856: ); 1857: 1858: for (uint256 leg = 0; leg < numLegs; ) { 1859: LeftRightSigned legPremia = premiaByLeg[leg]; 1860: 1861: bytes32 chunkKey = keccak256( 1862: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1863: ); 1864: 1865: // collected from Uniswap 1866: LeftRightUnsigned settledTokens = s_settledTokens[chunkKey].add(collectedByLeg[leg]); 1867: 1868: if (LeftRightSigned.unwrap(legPremia) != 0) { 1869: // (will be) paid by long legs 1870: if (tokenId.isLong(leg) == 1) { 1871: if (commitLongSettled) 1872: settledTokens = LeftRightUnsigned.wrap( 1873: uint256( 1874: LeftRightSigned.unwrap( 1875: LeftRightSigned 1876: .wrap(int256(LeftRightUnsigned.unwrap(settledTokens))) 1877: .sub(legPremia) 1878: ) 1879: ) 1880: ); 1881: realizedPremia = realizedPremia.add(legPremia); 1882: } else { 1883: uint256 positionLiquidity = PanopticMath 1884: .getLiquidityChunk(tokenId, leg, positionSize) 1885: .liquidity(); 1886: 1887: // new totalLiquidity (total sold) = removedLiquidity + netLiquidity (T - R) 1888: uint256 totalLiquidity = _getTotalLiquidity(tokenId, leg); 1889: // T (totalLiquidity is (T - R) after burning) 1890: uint256 totalLiquidityBefore = totalLiquidity + positionLiquidity; 1891: 1892: LeftRightUnsigned grossPremiumLast = s_grossPremiumLast[chunkKey]; 1893: 1894: LeftRightUnsigned availablePremium = _getAvailablePremium( 1895: totalLiquidity + positionLiquidity, 1896: settledTokens, 1897: grossPremiumLast, 1898: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(legPremia))), 1899: premiumAccumulatorsByLeg[leg] 1900: ); 1901: 1902: // subtract settled tokens sent to seller 1903: settledTokens = settledTokens.sub(availablePremium); 1904: 1905: // add available premium to amount that should be settled 1906: realizedPremia = realizedPremia.add( 1907: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium))) 1908: ); 1909: 1910: // We need to adjust the grossPremiumLast value such that the result of 1911: // (grossPremium - adjustedGrossPremiumLast)*updatedTotalLiquidityPostBurn/2**64 is equal to 1912: // (grossPremium - grossPremiumLast)*totalLiquidityBeforeBurn/2**64 - premiumOwedToPosition 1913: // G: total gross premium (- premiumOwedToPosition) 1914: // T: totalLiquidityBeforeMint 1915: // R: positionLiquidity 1916: // C: current grossPremium value 1917: // L: current grossPremiumLast value 1918: // Ln: updated grossPremiumLast value 1919: // T * (C - L) = G 1920: // (T - R) * (C - Ln) = G - P 1921: // 1922: // T * (C - L) = (T - R) * (C - Ln) + P 1923: // (TC - TL - P) / (T - R) = C - Ln 1924: // Ln = C - (TC - TL - P) / (T - R) 1925: // Ln = (TC - CR - TC + LT + P) / (T-R) 1926: // Ln = (LT - CR + P) / (T-R) 1927: 1928: unchecked { 1929: uint256[2][4] memory _premiumAccumulatorsByLeg = premiumAccumulatorsByLeg; 1930: uint256 _leg = leg; 1931: 1932: // if there's still liquidity, compute the new grossPremiumLast 1933: // otherwise, we just reset grossPremiumLast to the current grossPremium 1934: s_grossPremiumLast[chunkKey] = totalLiquidity != 0 1935: ? LeftRightUnsigned 1936: .wrap(0) 1937: .toRightSlot( 1938: uint128( 1939: uint256( 1940: Math.max( 1941: (int256( 1942: grossPremiumLast.rightSlot() * 1943: totalLiquidityBefore 1944: ) - 1945: int256( 1946: _premiumAccumulatorsByLeg[_leg][0] * 1947: positionLiquidity 1948: )) + int256(legPremia.rightSlot() * 2 ** 64), 1949: 0 1950: ) 1951: ) / totalLiquidity 1952: ) 1953: ) 1954: .toLeftSlot( 1955: uint128( 1956: uint256( 1957: Math.max( 1958: (int256( 1959: grossPremiumLast.leftSlot() * 1960: totalLiquidityBefore 1961: ) - 1962: int256( 1963: _premiumAccumulatorsByLeg[_leg][1] * 1964: positionLiquidity 1965: )) + int256(legPremia.leftSlot()) * 2 ** 64, 1966: 0 1967: ) 1968: ) / totalLiquidity 1969: ) 1970: ) 1971: : LeftRightUnsigned 1972: .wrap(0) 1973: .toRightSlot(uint128(premiumAccumulatorsByLeg[_leg][0])) 1974: .toLeftSlot(uint128(premiumAccumulatorsByLeg[_leg][1])); 1975: } 1976: } 1977: } 1978: 1979: // update settled tokens in storage with all local deltas 1980: s_settledTokens[chunkKey] = settledTokens; 1981: 1982: unchecked { 1983: ++leg; 1984: } 1985: } 1986: }
[860-879, 1411-1423, 1672-1752, 1839-1986]
File: contracts/SemiFungiblePositionManager.sol 1111: function _updateStoredPremia( 1112: bytes32 positionKey, 1113: LeftRightUnsigned currentLiquidity, 1114: LeftRightUnsigned collectedAmounts //@audit-ok was signed 1115: ) private { 1116: ( 1117: LeftRightUnsigned deltaPremiumOwed, 1118: LeftRightUnsigned deltaPremiumGross 1119: ) = _getPremiaDeltas(currentLiquidity, collectedAmounts); 1120: 1121: // add deltas to accumulators and freeze both accumulators (for a token) if one of them overflows 1122: // (i.e if only token0 (right slot) of the owed premium overflows, then stop accumulating both token0 owed premium and token0 gross premium for the chunk) 1123: // this prevents situations where the owed premium gets out of sync with the gross premium due to one of them overflowing 1124: (s_accountPremiumOwed[positionKey], s_accountPremiumGross[positionKey]) = LeftRightLibrary 1125: .addCapped( //@audit-ok wasn't capped, but both were 256, they are unsigned now 1126: s_accountPremiumOwed[positionKey], 1127: deltaPremiumOwed, 1128: s_accountPremiumGross[positionKey], 1129: deltaPremiumGross 1130: ); 1131: }
File: contracts/libraries/PanopticMath.sol 92: function updatePositionsHash( 93: uint256 existingHash, 94: TokenId tokenId, 95: bool addFlag 96: ) internal pure returns (uint256) { 97: // add the XOR`ed hash of the single option position `tokenId` to the `existingHash` 98: // @dev 0 ^ x = x 99: 100: unchecked { 101: // update hash by taking the XOR of the new tokenId 102: uint248 updatedHash = uint248(existingHash) ^ 103: (uint248(uint256(keccak256(abi.encode(tokenId))))); 104: // increment the top 8 bit if addflag=true, decrement otherwise 105: return 106: addFlag 107: ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248) 108: : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248); 109: } 110: }
[92-110]
</details>File: contracts/types/LiquidityChunk.sol 136: function updateTickLower( 137: LiquidityChunk self, 138: int24 _tickLower 139: ) internal pure returns (LiquidityChunk) { 140: unchecked { 141: return 142: LiquidityChunk.wrap(LiquidityChunk.unwrap(self) & CLEAR_TL_MASK).addTickLower( 143: _tickLower 144: ); 145: } 146: } 152: function updateTickUpper( 153: LiquidityChunk self, 154: int24 _tickUpper 155: ) internal pure returns (LiquidityChunk) { 156: unchecked { 157: // convert tick upper to uint24 as explicit conversion from int24 to uint256 is not allowed 158: return 159: LiquidityChunk.wrap(LiquidityChunk.unwrap(self) & CLEAR_TU_MASK).addTickUpper( 160: _tickUpper 161: ); 162: } 163: }
As a best practice, consider emitting an event when the contract is initialized. In this way, it's easy for the user to track the exact point in time when the contract was initialized, by filtering the emitted events.
There is 1 instance of this issue.
File: contracts/PanopticFactory.sol 135: function initialize(address _owner) public {
[135]
This will allow users to easily exactly pinpoint when and by whom a contract was constructed.
There are 4 instances of this issue.
File: contracts/CollateralTracker.sol 178: constructor(
[178]
File: contracts/PanopticFactory.sol 116: constructor(
[116]
File: contracts/PanopticPool.sol 281: constructor(SemiFungiblePositionManager _sfpm) {
[281]
File: contracts/SemiFungiblePositionManager.sol 341: constructor(IUniswapV3Factory _factory) {
[341]
Not only is wasteful in terms of gas, but this is especially problematic when an event is emitted and the old and new values set are the same, as listeners might not expect this kind of scenario.
There are 6 instances of this issue.
File: contracts/PanopticPool.sol // @audit s_positionBalance, s_options 860: function _updatePositionDataBurn(address owner, TokenId tokenId) internal { // @audit s_positionsHash 1411: function _updatePositionsHash(address account, TokenId tokenId, bool addFlag) internal { // @audit s_options, s_settledTokens 1593: function settleLongPremium( // @audit s_settledTokens, s_grossPremiumLast 1672: function _updateSettlementPostMint( // @audit s_grossPremiumLast, s_settledTokens 1839: function _updateSettlementPostBurn(
File: contracts/tokens/ERC1155Minimal.sol // @audit isApprovedForAll 81: function setApprovalForAll(address operator, bool approved) public {
[81]
Taking zero as a valid argument without checks can lead to severe security issues in some cases.
If using a zero argument is mandatory, consider using descriptive constants or an enum instead of passing zero directly on function calls, as that might be error-prone, to fully describe the caller's intention.
There are 63 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 712: LeftRightSigned 713: .wrap(0)
[712-713]
File: contracts/PanopticPool.sol 635: revert Errors.InvalidTokenIdParameter(0); 655: s_positionBalance[msg.sender][tokenId] = LeftRightUnsigned 656: .wrap(0) 763: s_options[msg.sender][tokenId][leg] = LeftRightUnsigned 764: .wrap(0) 862: s_positionBalance[owner][tokenId] = LeftRightUnsigned.wrap(0); 871: s_options[owner][tokenId][leg] = LeftRightUnsigned.wrap(0); 894: _validatePositionList(user, positionIdList, 0); 1024: _validatePositionList(liquidatee, positionIdList, 0); 1154: _validatePositionList(msg.sender, positionIdListLiquidator, 0); 1166: LeftRightSigned bonusAmounts = LeftRightSigned 1167: .wrap(0) 1192: _validatePositionList(msg.sender, positionIdListExercisor, 0); 1233: _burnAllOptionsFrom(account, 0, 0, COMMIT_LONG_SETTLED, touchedId); 1551: premiaByLeg[leg] = LeftRightSigned 1552: .wrap(0) 1573: premiaByLeg[leg] = LeftRightSigned.wrap(0).sub(premiaByLeg[leg]); 1598: _validatePositionList(owner, positionIdList, 0); 1620: accumulatedPremium = LeftRightUnsigned 1621: .wrap(0) 1639: LeftRightSigned realizedPremia = LeftRightSigned 1640: .wrap(0) 1645: s_collateralToken0.exercise(owner, 0, 0, 0, realizedPremia.rightSlot()); 1646: s_collateralToken1.exercise(owner, 0, 0, 0, realizedPremia.leftSlot()); 1713: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium( 1714: address(s_univ3pool), 1715: address(this), 1716: tokenId.tokenType(leg), 1717: liquidityChunk.tickLower(), 1718: liquidityChunk.tickUpper(), 1719: type(int24).max, 1720: 0 1721: ); 1731: s_grossPremiumLast[chunkKey] = LeftRightUnsigned 1732: .wrap(0) 1780: LeftRightUnsigned 1781: .wrap(0) 1935: ? LeftRightUnsigned 1936: .wrap(0) 1940: Math.max( 1941: (int256( 1942: grossPremiumLast.rightSlot() * 1943: totalLiquidityBefore 1944: ) - 1945: int256( 1946: _premiumAccumulatorsByLeg[_leg][0] * 1947: positionLiquidity 1948: )) + int256(legPremia.rightSlot() * 2 ** 64), 1949: 0 1950: ) 1957: Math.max( 1958: (int256( 1959: grossPremiumLast.leftSlot() * 1960: totalLiquidityBefore 1961: ) - 1962: int256( 1963: _premiumAccumulatorsByLeg[_leg][1] * 1964: positionLiquidity 1965: )) + int256(legPremia.leftSlot()) * 2 ** 64, 1966: 0 1967: ) 1971: : LeftRightUnsigned 1972: .wrap(0)
[635, 655-656, 763-764, 862, 871, 894, 1024, 1154, 1166-1167, 1192, 1233, 1551-1552, 1573, 1598, 1620-1621, 1639-1640, 1645, 1646, 1713-1721, 1731-1732, 1780-1781, 1935-1936, 1940-1950, 1957-1967, 1971-1972]
File: contracts/SemiFungiblePositionManager.sol 646: s_accountLiquidity[positionKey_from] = LeftRightUnsigned.wrap(0); 649: s_accountFeesBase[positionKey_from] = LeftRightSigned.wrap(0); 834: if (swapAmount == 0) return LeftRightSigned.wrap(0); 849: totalSwapped = LeftRightSigned.wrap(0).toRightSlot(swap0.toInt128()).toLeftSlot( 1039: s_accountLiquidity[positionKey] = LeftRightUnsigned 1040: .wrap(0) 1167: ? LeftRightSigned 1168: .wrap(0) 1175: : LeftRightSigned 1176: .wrap(0) 1215: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(int128(int256(amount0))).toLeftSlot( 1242: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(-int128(int256(amount0))).toLeftSlot( 1307: collectedChunk = LeftRightUnsigned.wrap(0).toRightSlot(collected0).toLeftSlot( 1377: deltaPremiumOwed = LeftRightUnsigned 1378: .wrap(0) 1401: deltaPremiumGross = LeftRightUnsigned 1402: .wrap(0)
[646, 649, 834, 849, 1039-1040, 1167-1168, 1175-1176, 1215, 1242, 1307, 1377-1378, 1401-1402]
File: contracts/libraries/FeesCalc.sol 115: LeftRightSigned 116: .wrap(0)
[115-116]
File: contracts/libraries/Math.sol 778: quickSort(data, int256(0), int256(data.length - 1));
[778]
File: contracts/libraries/PanopticMath.sol 598: return LeftRightUnsigned.wrap(0).toRightSlot(amount0).toLeftSlot(amount1); 671: (uint256 balanceCross, uint256 thresholdCross) = PanopticMath.convertCollateralData( 672: tokenData0, 673: tokenData1, 674: 0, 675: sqrtPriceX96Twap 676: ); 694: Math.max(premia.rightSlot(), 0); 696: Math.max(premia.leftSlot(), 0); 749: LeftRightSigned.wrap(0).toRightSlot(int128(balance0 - paid0)).toLeftSlot( 791: int256 collateralDelta0 = -Math.min(collateralRemaining.rightSlot(), 0); 792: int256 collateralDelta1 = -Math.min(collateralRemaining.leftSlot(), 0); 856: if (haircut0 != 0) collateral0.exercise(_liquidatee, 0, 0, 0, int128(haircut0)); 857: if (haircut1 != 0) collateral1.exercise(_liquidatee, 0, 0, 0, int128(haircut1)); 880: tokenId.strike(0), 881: tokenId.width(0), 882: tokenId.tokenType(0) 888: settled0 = Math.max( 889: 0, 890: uint128(-_premiasByLeg[i][leg].rightSlot()) - settled0 891: ); 892: settled1 = Math.max( 893: 0, 894: uint128(-_premiasByLeg[i][leg].leftSlot()) - settled1 895: ); 898: LeftRightUnsigned.wrap(0).toRightSlot(uint128(settled0)).toLeftSlot( 933: LeftRightSigned 934: .wrap(0) 951: LeftRightSigned 952: .wrap(0)
[598, 671-676, 694, 696, 749, 791, 792, 856, 857, 880, 881, 882, 888-891, 892-895, 898, 933-934, 951-952]
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);
return
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.
There are 20 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 361: function asset() external view returns (address assetTokenAddress) { 362: return s_underlyingToken; 370: function totalAssets() public view returns (uint256 totalManagedAssets) { 372: return s_poolAssets + s_inAMM; 379: function convertToShares(uint256 assets) public view returns (uint256 shares) { 380: return Math.mulDiv(assets, totalSupply, totalAssets()); 386: function convertToAssets(uint256 shares) public view returns (uint256 assets) { 387: return Math.mulDiv(shares, totalAssets(), totalSupply); 392: function maxDeposit(address) external pure returns (uint256 maxAssets) { 393: return type(uint104).max; 444: function maxMint(address) external view returns (uint256 maxShares) { 446: return (convertToShares(type(uint104).max) * DECIMALS) / (DECIMALS + COMMISSION_FEE); 507: function maxWithdraw(address owner) public view returns (uint256 maxAssets) { 512: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0; 518: function previewWithdraw(uint256 assets) public view returns (uint256 shares) { 521: return Math.mulDivRoundingUp(assets, supply, totalAssets()); 572: function maxRedeem(address owner) public view returns (uint256 maxShares) { 575: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0; 581: function previewRedeem(uint256 shares) public view returns (uint256 assets) { 582: return convertToAssets(shares); 741: function _poolUtilization() internal view returns (int256 poolUtilization) { 743: return int256((s_inAMM * DECIMALS) / totalAssets()); 753: ) internal view returns (uint256 sellCollateralRatio) { 785: return min_sell_ratio; 791: return DECIMALS; 795: return 796: min_sell_ratio + 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / 798: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL); 808: ) internal view returns (uint256 buyCollateralRatio) { 836: return BUYER_COLLATERAL_RATIO; 843: return BUYER_COLLATERAL_RATIO / 2; 848: return 849: (BUYER_COLLATERAL_RATIO + 850: (BUYER_COLLATERAL_RATIO * (SATURATED_POOL_UTIL - utilization)) / 851: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL)) / 2; // do the division by 2 at the end after all addition and multiplication; b/c y1 = buyCollateralRatio / 2 1284: ) internal view returns (uint256 required) { 1285: return 1286: tokenId.riskPartner(index) == index // does this leg have a risk partner? Affects required collateral 1287: ? _getRequiredCollateralSingleLegNoPartner( 1288: tokenId, 1289: index, 1290: positionSize, 1291: atTick, 1292: poolUtilization 1293: ) 1294: : _getRequiredCollateralSingleLegPartner( 1295: tokenId, 1296: index, 1297: positionSize, 1298: atTick, 1299: poolUtilization 1300: );
[361, 370, 379, 386, 392, 444, 507, 518, 572, 581, 741, 753, 808, 1284]
File: contracts/PanopticPool.sol 386: ) external view returns (int128 premium0, int128 premium1, uint256[2][] memory) { 400: return (premia.rightSlot(), premia.leftSlot(), balances); 1437: function collateralToken0() external view returns (CollateralTracker collateralToken) { 1438: return s_collateralToken0; 1519: LeftRightSigned[4] memory premiaByLeg,
File: contracts/SemiFungiblePositionManager.sol 872: LeftRightSigned totalMoved, 1558: ) external view returns (IUniswapV3Pool UniswapV3Pool) { 1559: return s_poolContext[poolId].pool;
File: contracts/libraries/Math.sol 738: function unsafeDivRoundingUp(uint256 a, uint256 b) internal pure returns (uint256 result) {
[738]
</details>error
definitionThe following errors are never used, consider to remove them.
There are 2 instances of this issue.
File: contracts/libraries/Errors.sol 26: error ExerciseeNotSolvent(); 48: error LeftRightInputError();
Some arguments are never used: if this is intentional, consider removing these arguments from the function. Otherwise, implement the missing logic accordingly.
There are 8 instances of this issue.
File: contracts/libraries/Math.sol // @audit a, b 340: function mulDiv( 341: uint256 a, 342: uint256 b, 343: uint256 denominator // @audit a, b 458: function mulDiv64(uint256 a, uint256 b) internal pure returns (uint256) { // @audit a, b 521: function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256) { // @audit a, b 598: function mulDiv128(uint256 a, uint256 b) internal pure returns (uint256) { // @audit a, b 675: function mulDiv192(uint256 a, uint256 b) internal pure returns (uint256) { // @audit a, b 738: function unsafeDivRoundingUp(uint256 a, uint256 b) internal pure returns (uint256 result) {
[340-343, 458, 521, 598, 675, 738]
File: contracts/libraries/SafeTransferLib.sol // @audit token, from, to, amount 22: function safeTransferFrom(address token, address from, address to, uint256 amount) internal { // @audit token, to, amount 53: function safeTransfer(address token, address to, uint256 amount) internal {
Consider removing any unusued state variable to improve the readability of the codebase.
There are 2 instances of this issue.
File: contracts/libraries/Math.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
[15]
File: contracts/libraries/PanopticMath.sol 23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
[23]
These contracts import some OpenZeppelin libraries, but they are using an old version.
There are 5 instances of this issue.
File: contracts/PanopticFactory.sol 14: import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
[14]
File: contracts/PanopticPool.sol 9: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
[9]
File: contracts/libraries/InteractionHelper.sol 6: import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 10: import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
File: contracts/tokens/ERC1155Minimal.sol 5: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
[5]
constant
is redefined elsewhereKeeping the same constants in different files may cause some problems, as the values could become out of sync when only one location is updated; reading constants from a single file is preferable. This should also be preferred for gas optimizations.
There are 4 instances of this issue.
File: contracts/PanopticFactory.sol // @audit seen in contracts/PanopticPool.sol 67: SemiFungiblePositionManager internal immutable SFPM;
[67]
File: contracts/PanopticPool.sol // @audit seen in contracts/PanopticFactory.sol 180: SemiFungiblePositionManager internal immutable SFPM;
[180]
File: contracts/libraries/Math.sol // @audit seen in contracts/libraries/PanopticMath.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
[15]
File: contracts/libraries/PanopticMath.sol // @audit seen in contracts/libraries/Math.sol 23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
[23]
Consider using an enum instead of hardcoding an index access to make the code easier to understand.
There are 26 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 1210: TokenId tokenId = TokenId.wrap(positionBalanceArray[i][0]); 1213: uint128 positionSize = LeftRightUnsigned.wrap(positionBalanceArray[i][1]).rightSlot(); 1216: uint128 poolUtilization = LeftRightUnsigned.wrap(positionBalanceArray[i][1]).leftSlot();
File: contracts/PanopticPool.sol 445: balances[k][0] = TokenId.unwrap(tokenId); 446: balances[k][1] = LeftRightUnsigned.unwrap(s_positionBalance[c_user][tokenId]); 453: LeftRightUnsigned.wrap(balances[k][1]).rightSlot(), 1194: uint128 positionBalance = s_positionBalance[account][touchedId[0]].rightSlot(); 1199: .computeExercisedAmounts(touchedId[0], positionBalance); 1225: touchedId[0].validateIsExercisable(twapTick); 1240: touchedId[0], 1283: emit ForcedExercised(msg.sender, account, touchedId[0], exerciseFees); 1534: (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM 1534: (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM 1556: ((premiumAccumulatorsByLeg[leg][0] - 1565: ((premiumAccumulatorsByLeg[leg][1] - 1713: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium( 1713: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium( 1735: (grossCurrent[0] * 1743: (grossCurrent[1] * 1774: uint256 accumulated0 = ((premiumAccumulators[0] - grossPremiumLast.rightSlot()) * 1776: uint256 accumulated1 = ((premiumAccumulators[1] - grossPremiumLast.leftSlot()) * 1946: _premiumAccumulatorsByLeg[_leg][0] * 1963: _premiumAccumulatorsByLeg[_leg][1] * 1973: .toRightSlot(uint128(premiumAccumulatorsByLeg[_leg][0])) 1974: .toLeftSlot(uint128(premiumAccumulatorsByLeg[_leg][1]));
[445, 446, 453, 1194, 1199, 1225, 1240, 1283, 1534, 1534, 1556, 1565, 1713, 1713, 1735, 1743, 1774, 1776, 1946, 1963, 1973, 1974]
File: contracts/libraries/PanopticMath.sol 266: return int24(sortedTicks[10]); //@audit-issue L - median should be (sortedTicks[9] + sortedTicks[10]) / 2
[266]
</details>It's not necessary to initialize a variable with a zero value, as it's the default behaviour, and it's actually worse in gas terms as it adds an overhead.
There are 31 instances of this issue.
<details> <summary>Expand findings</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 442: for (uint256 k = 0; k < pLength; ) { //@audit H - uncapped positions -> unliquidable 460: for (uint256 leg = 0; leg < numLegs; ) { 746: for (uint256 leg = 0; leg < numLegs; ) { 803: for (uint256 i = 0; i < positionIdList.length; ) { 865: for (uint256 leg = 0; leg < numLegs; ) { 1388: for (uint256 i = 0; i < pLength; ) { 1524: for (uint256 leg = 0; leg < numLegs; ) { 1678: for (uint256 leg = 0; leg < numLegs; ++leg) { 1858: for (uint256 leg = 0; leg < numLegs; ) {
[442, 460, 746, 803, 865, 1388, 1524, 1678, 1858]
File: contracts/SemiFungiblePositionManager.sol 575: for (uint256 i = 0; i < ids.length; ) { 601: for (uint256 leg = 0; leg < numLegs; ) { 883: 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) {
[137, 148, 248, 256, 395, 781, 784, 860, 863]
File: contracts/multicall/Multicall.sol 14: for (uint256 i = 0; i < data.length; ) {
[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) {
require/if
statements should be refactoredThese statements should be refactored to a separate function, as there are multiple parts of the codebase that use the same logic, to improve the code readability and reduce code duplication.
There are 5 instances of this issue.
File: contracts/CollateralTracker.sol // @audit this if condition is duplicated on line 480 418: if (assets > type(uint104).max) revert Errors.DepositTooLarge();
[418]
File: contracts/libraries/Math.sol // @audit this require is duplicated on line 588, 665 448: require(result < type(uint256).max);
[448]
File: contracts/libraries/SafeTransferLib.sol // @audit this if condition is duplicated on line 76 46: if (!success) revert Errors.TransferFailed();
[46]
File: contracts/tokens/ERC1155Minimal.sol // @audit this if condition is duplicated on line 137 101: if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized();
[101]
File: contracts/types/LeftRight.sol // @audit this if condition is duplicated on line 240, 262 222: if (left128 != left256 || right128 != right256) revert Errors.UnderOverFlow();
[222]
require
/error
Some parts of the codebase use require
statements, while others use custom error
s. Consider refactoring the code to use the same approach: the following findings represent the minority of require
vs error
, and they show the first occurance in each file, for brevity.
There is 1 instance of this issue.
File: contracts/libraries/Math.sol 361: require(denominator > 0);
[361]
These functions might be a problem if the logic changes before the contract is deployed, as the developer must remember to syncronize the logic between all the function instances.
Consider using a single function instead of duplicating the code, for example by using a library
, or through inheritance.
There are 2 instances of this issue.
File: contracts/libraries/Math.sol // @audit duplicated logic in contracts/libraries/Math.sol -> min24, contracts/libraries/Math.sol -> min, contracts/libraries/Math.sol -> min 25: function min24(int24 a, int24 b) internal pure returns (int24) { // @audit duplicated logic in contracts/libraries/Math.sol -> max24, contracts/libraries/Math.sol -> max, contracts/libraries/Math.sol -> max 33: function max24(int24 a, int24 b) internal pure returns (int24) {
Consider limiting the number of iterations in loops with an explicit revert reason to avoid iterating an array that is too large.
The function would eventually revert if out of gas anyway, but by limiting it the user avoids wasting too much gas, as the loop doesn't execute if an excessive value is provided.
There are 34 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 662: for (uint256 leg = 0; leg < positionId.countLegs(); ++leg) { 663: // short legs are not counted - exercise is intended to be based on long legs 664: if (positionId.isLong(leg) == 0) continue; 665: 666: { 667: int24 range = int24( 668: int256( 669: Math.unsafeDivRoundingUp( 670: uint24(positionId.width(leg) * positionId.tickSpacing()), 671: 2 672: ) 673: ) 674: ); 675: maxNumRangesFromStrike = Math.max( 676: maxNumRangesFromStrike, 677: uint256(Math.abs(currentTick - positionId.strike(leg)) / range) 678: ); 679: } 680: 681: uint256 currentValue0; 682: uint256 currentValue1; 683: uint256 oracleValue0; 684: uint256 oracleValue1; 685: 686: { 687: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 688: positionId, 689: leg, 690: positionBalance 691: ); 692: 693: (currentValue0, currentValue1) = Math.getAmountsForLiquidity( 694: currentTick, 695: liquidityChunk 696: ); 697: 698: (oracleValue0, oracleValue1) = Math.getAmountsForLiquidity( 699: oracleTick, 700: liquidityChunk 701: ); 702: } 703: 704: uint256 tokenType = positionId.tokenType(leg); 705: // compensate user for loss in value if chunk has lost money between current and median tick 706: // note: the delta for one token will be positive and the other will be negative. This cancels out any moves in their positions 707: if ( 708: (tokenType == 0 && currentValue1 < oracleValue1) || 709: (tokenType == 1 && currentValue0 < oracleValue0) 710: ) 711: exerciseFees = exerciseFees.sub( 712: LeftRightSigned 713: .wrap(0) 714: .toRightSlot( 715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0)) 716: ) 717: .toLeftSlot( 718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1)) 719: ) 720: ); 721: } 1208: for (uint256 i = 0; i < totalIterations; ) { 1209: // read the ith tokenId from the account 1210: TokenId tokenId = TokenId.wrap(positionBalanceArray[i][0]); 1211: 1212: // read the position size and the pool utilization at mint 1213: uint128 positionSize = LeftRightUnsigned.wrap(positionBalanceArray[i][1]).rightSlot(); 1214: 1215: // read the pool utilization at mint 1216: uint128 poolUtilization = LeftRightUnsigned.wrap(positionBalanceArray[i][1]).leftSlot(); 1217: 1218: // Get tokens required for the current tokenId (a single active position) 1219: uint256 _tokenRequired = _getRequiredCollateralAtTickSinglePosition( 1220: tokenId, 1221: positionSize, 1222: atTick, 1223: poolUtilization 1224: ); 1225: 1226: // add to the tokenRequired accumulator 1227: unchecked { 1228: tokenRequired += _tokenRequired; 1229: } 1230: unchecked { 1231: ++i; 1232: } 1233: } 1255: for (uint256 index = 0; index < numLegs; ++index) { 1256: // revert if the tokenType does not match the current collateral token 1257: if (tokenId.tokenType(index) != (underlyingIsToken0 ? 0 : 1)) continue; 1258: // Increment the tokenRequired accumulator 1259: tokenRequired += _getRequiredCollateralSingleLeg( 1260: tokenId, 1261: index, 1262: positionSize, 1263: atTick, 1264: poolUtilization 1265: ); 1266: }
[662-721, 1208-1233, 1255-1266]
File: contracts/PanopticPool.sol 442: for (uint256 k = 0; k < pLength; ) { //@audit H - uncapped positions -> unliquidable 443: TokenId tokenId = positionIdList[k]; 444: 445: balances[k][0] = TokenId.unwrap(tokenId); 446: balances[k][1] = LeftRightUnsigned.unwrap(s_positionBalance[c_user][tokenId]); 447: 448: ( 449: LeftRightSigned[4] memory premiaByLeg, //@audit-info positive premia 450: uint256[2][4] memory premiumAccumulatorsByLeg 451: ) = _getPremia( 452: tokenId, 453: LeftRightUnsigned.wrap(balances[k][1]).rightSlot(), 454: c_user, 455: computeAllPremia, 456: atTick 457: ); 458: 459: uint256 numLegs = tokenId.countLegs(); 460: for (uint256 leg = 0; leg < numLegs; ) { 461: if (tokenId.isLong(leg) == 0 && !includePendingPremium) { 462: bytes32 chunkKey = keccak256( 463: abi.encodePacked( 464: tokenId.strike(leg), 465: tokenId.width(leg), 466: tokenId.tokenType(leg) 467: ) //@audit-info collision between two legs? (isLong or riskPartner) 468: ); 469: 470: LeftRightUnsigned availablePremium = _getAvailablePremium( 471: _getTotalLiquidity(tokenId, leg), 472: s_settledTokens[chunkKey], 473: s_grossPremiumLast[chunkKey], 474: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))), 475: premiumAccumulatorsByLeg[leg] 476: ); 477: portfolioPremium = portfolioPremium.add( 478: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium))) 479: ); 480: } else { 481: portfolioPremium = portfolioPremium.add(premiaByLeg[leg]); //@audit-info adds it if is long or includePendingPremium = true 482: } 483: unchecked { 484: ++leg; 485: } 486: } 487: 488: unchecked { 489: ++k; 490: } 491: } 460: for (uint256 leg = 0; leg < numLegs; ) { 461: if (tokenId.isLong(leg) == 0 && !includePendingPremium) { 462: bytes32 chunkKey = keccak256( 463: abi.encodePacked( 464: tokenId.strike(leg), 465: tokenId.width(leg), 466: tokenId.tokenType(leg) 467: ) //@audit-info collision between two legs? (isLong or riskPartner) 468: ); 469: 470: LeftRightUnsigned availablePremium = _getAvailablePremium( 471: _getTotalLiquidity(tokenId, leg), 472: s_settledTokens[chunkKey], 473: s_grossPremiumLast[chunkKey], 474: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))), 475: premiumAccumulatorsByLeg[leg] 476: ); 477: portfolioPremium = portfolioPremium.add( 478: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium))) 479: ); 480: } else { 481: portfolioPremium = portfolioPremium.add(premiaByLeg[leg]); //@audit-info adds it if is long or includePendingPremium = true 482: } 483: unchecked { 484: ++leg; 485: } 486: } 746: for (uint256 leg = 0; leg < numLegs; ) { 747: // Extract base fee (AMM swap/trading fees) for the position and add it to s_options 748: // (ie. the (feeGrowth * liquidity) / 2**128 for each token) 749: (int24 tickLower, int24 tickUpper) = tokenId.asTicks(leg); 750: uint256 isLong = tokenId.isLong(leg); 751: { 752: (uint128 premiumAccumulator0, uint128 premiumAccumulator1) = SFPM.getAccountPremium( 753: address(s_univ3pool), 754: address(this), 755: tokenId.tokenType(leg), 756: tickLower, 757: tickUpper, 758: type(int24).max, 759: isLong 760: ); 761: 762: // update the premium accumulators 763: s_options[msg.sender][tokenId][leg] = LeftRightUnsigned 764: .wrap(0) 765: .toRightSlot(premiumAccumulator0) 766: .toLeftSlot(premiumAccumulator1); 767: } 768: // verify base Liquidity limit only if new position is long 769: if (isLong == 1) { 770: // Move this into a new function 771: _checkLiquiditySpread( 772: tokenId, 773: leg, 774: tickLower, 775: tickUpper, 776: uint64(Math.min(effectiveLiquidityLimitX32, MAX_SPREAD)) 777: ); 778: } 779: unchecked { 780: ++leg; 781: } 782: } 803: for (uint256 i = 0; i < positionIdList.length; ) { 804: LeftRightSigned paidAmounts; 805: (paidAmounts, premiasByLeg[i]) = _burnOptions( 806: commitLongSettled, 807: positionIdList[i], 808: owner, 809: tickLimitLow, 810: tickLimitHigh 811: ); 812: netPaid = netPaid.add(paidAmounts); 813: unchecked { 814: ++i; 815: } 816: } 865: for (uint256 leg = 0; leg < numLegs; ) { 866: if (tokenId.isLong(leg) == 0) { 867: // Check the liquidity spread, make sure that closing the option does not exceed the MAX_SPREAD allowed 868: (int24 tickLower, int24 tickUpper) = tokenId.asTicks(leg); 869: _checkLiquiditySpread(tokenId, leg, tickLower, tickUpper, MAX_SPREAD); 870: } 871: s_options[owner][tokenId][leg] = LeftRightUnsigned.wrap(0); 872: unchecked { 873: ++leg; 874: } 875: } 1388: for (uint256 i = 0; i < pLength; ) { 1389: fingerprintIncomingList = PanopticMath.updatePositionsHash( 1390: fingerprintIncomingList, 1391: positionIdList[i], 1392: ADD 1393: ); 1394: unchecked { 1395: ++i; 1396: } 1397: } 1524: for (uint256 leg = 0; leg < numLegs; ) { 1525: uint256 isLong = tokenId.isLong(leg); 1526: if ((isLong == 1) || computeAllPremia) { 1527: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 1528: tokenId, 1529: leg, 1530: positionSize 1531: ); 1532: uint256 tokenType = tokenId.tokenType(leg); 1533: 1534: (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM 1535: .getAccountPremium( 1536: address(s_univ3pool), 1537: address(this), 1538: tokenType, 1539: liquidityChunk.tickLower(), 1540: liquidityChunk.tickUpper(), 1541: atTick, 1542: isLong 1543: ); 1544: 1545: unchecked { 1546: LeftRightUnsigned premiumAccumulatorLast = s_options[owner][tokenId][leg]; 1547: 1548: // if the premium accumulatorLast is higher than current, it means the premium accumulator has overflowed and rolled over at least once 1549: // we can account for one rollover by doing (acc_cur + (acc_max - acc_last)) 1550: // if there are multiple rollovers or the rollover goes past the last accumulator, rolled over fees will just remain unclaimed 1551: premiaByLeg[leg] = LeftRightSigned 1552: .wrap(0) 1553: .toRightSlot( 1554: int128( 1555: int256( 1556: ((premiumAccumulatorsByLeg[leg][0] - 1557: premiumAccumulatorLast.rightSlot()) * 1558: (liquidityChunk.liquidity())) / 2 ** 64 1559: ) 1560: ) 1561: ) 1562: .toLeftSlot( 1563: int128( 1564: int256( 1565: ((premiumAccumulatorsByLeg[leg][1] - 1566: premiumAccumulatorLast.leftSlot()) * 1567: (liquidityChunk.liquidity())) / 2 ** 64 1568: ) 1569: ) 1570: ); 1571: 1572: if (isLong == 1) { 1573: premiaByLeg[leg] = LeftRightSigned.wrap(0).sub(premiaByLeg[leg]); 1574: } 1575: } 1576: } 1577: unchecked { 1578: ++leg; 1579: } 1580: } 1678: for (uint256 leg = 0; leg < numLegs; ++leg) { 1679: bytes32 chunkKey = keccak256( 1680: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1681: ); 1682: // add any tokens collected from Uniswap in a given chunk to the settled tokens available for withdrawal by sellers 1683: s_settledTokens[chunkKey] = s_settledTokens[chunkKey].add(collectedByLeg[leg]); 1684: 1685: if (tokenId.isLong(leg) == 0) { 1686: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 1687: tokenId, 1688: leg, 1689: positionSize 1690: ); 1691: 1692: // new totalLiquidity (total sold) = removedLiquidity + netLiquidity (R + N) 1693: uint256 totalLiquidity = _getTotalLiquidity(tokenId, leg); 1694: 1695: // We need to adjust the grossPremiumLast value such that the result of 1696: // (grossPremium - adjustedGrossPremiumLast)*updatedTotalLiquidityPostMint/2**64 is equal to (grossPremium - grossPremiumLast)*totalLiquidityBeforeMint/2**64 1697: // G: total gross premium 1698: // T: totalLiquidityBeforeMint 1699: // R: positionLiquidity 1700: // C: current grossPremium value 1701: // L: current grossPremiumLast value 1702: // Ln: updated grossPremiumLast value 1703: // T * (C - L) = G 1704: // (T + R) * (C - Ln) = G 1705: // 1706: // T * (C - L) = (T + R) * (C - Ln) 1707: // (TC - TL) / (T + R) = C - Ln 1708: // Ln = C - (TC - TL)/(T + R) 1709: // Ln = (CT + CR - TC + TL)/(T+R) 1710: // Ln = (CR + TL)/(T+R) 1711: 1712: uint256[2] memory grossCurrent; 1713: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium( 1714: address(s_univ3pool), 1715: address(this), 1716: tokenId.tokenType(leg), 1717: liquidityChunk.tickLower(), 1718: liquidityChunk.tickUpper(), 1719: type(int24).max, 1720: 0 1721: ); 1722: 1723: unchecked { 1724: // L 1725: LeftRightUnsigned grossPremiumLast = s_grossPremiumLast[chunkKey]; 1726: // R 1727: uint256 positionLiquidity = liquidityChunk.liquidity(); 1728: // T (totalLiquidity is (T + R) after minting) 1729: uint256 totalLiquidityBefore = totalLiquidity - positionLiquidity; 1730: 1731: s_grossPremiumLast[chunkKey] = LeftRightUnsigned 1732: .wrap(0) 1733: .toRightSlot( 1734: uint128( 1735: (grossCurrent[0] * 1736: positionLiquidity + 1737: grossPremiumLast.rightSlot() * 1738: totalLiquidityBefore) / (totalLiquidity) 1739: ) 1740: ) 1741: .toLeftSlot( 1742: uint128( 1743: (grossCurrent[1] * 1744: positionLiquidity + 1745: grossPremiumLast.leftSlot() * 1746: totalLiquidityBefore) / (totalLiquidity) 1747: ) 1748: ); 1749: } 1750: } 1751: } 1858: for (uint256 leg = 0; leg < numLegs; ) { 1859: LeftRightSigned legPremia = premiaByLeg[leg]; 1860: 1861: bytes32 chunkKey = keccak256( 1862: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1863: ); 1864: 1865: // collected from Uniswap 1866: LeftRightUnsigned settledTokens = s_settledTokens[chunkKey].add(collectedByLeg[leg]); 1867: 1868: if (LeftRightSigned.unwrap(legPremia) != 0) { 1869: // (will be) paid by long legs 1870: if (tokenId.isLong(leg) == 1) { 1871: if (commitLongSettled) 1872: settledTokens = LeftRightUnsigned.wrap( 1873: uint256( 1874: LeftRightSigned.unwrap( 1875: LeftRightSigned 1876: .wrap(int256(LeftRightUnsigned.unwrap(settledTokens))) 1877: .sub(legPremia) 1878: ) 1879: ) 1880: ); 1881: realizedPremia = realizedPremia.add(legPremia); 1882: } else { 1883: uint256 positionLiquidity = PanopticMath 1884: .getLiquidityChunk(tokenId, leg, positionSize) 1885: .liquidity(); 1886: 1887: // new totalLiquidity (total sold) = removedLiquidity + netLiquidity (T - R) 1888: uint256 totalLiquidity = _getTotalLiquidity(tokenId, leg); 1889: // T (totalLiquidity is (T - R) after burning) 1890: uint256 totalLiquidityBefore = totalLiquidity + positionLiquidity; 1891: 1892: LeftRightUnsigned grossPremiumLast = s_grossPremiumLast[chunkKey]; 1893: 1894: LeftRightUnsigned availablePremium = _getAvailablePremium( 1895: totalLiquidity + positionLiquidity, 1896: settledTokens, 1897: grossPremiumLast, 1898: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(legPremia))), 1899: premiumAccumulatorsByLeg[leg] 1900: ); 1901: 1902: // subtract settled tokens sent to seller 1903: settledTokens = settledTokens.sub(availablePremium); 1904: 1905: // add available premium to amount that should be settled 1906: realizedPremia = realizedPremia.add( 1907: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium))) 1908: ); 1909: 1910: // We need to adjust the grossPremiumLast value such that the result of 1911: // (grossPremium - adjustedGrossPremiumLast)*updatedTotalLiquidityPostBurn/2**64 is equal to 1912: // (grossPremium - grossPremiumLast)*totalLiquidityBeforeBurn/2**64 - premiumOwedToPosition 1913: // G: total gross premium (- premiumOwedToPosition) 1914: // T: totalLiquidityBeforeMint 1915: // R: positionLiquidity 1916: // C: current grossPremium value 1917: // L: current grossPremiumLast value 1918: // Ln: updated grossPremiumLast value 1919: // T * (C - L) = G 1920: // (T - R) * (C - Ln) = G - P 1921: // 1922: // T * (C - L) = (T - R) * (C - Ln) + P 1923: // (TC - TL - P) / (T - R) = C - Ln 1924: // Ln = C - (TC - TL - P) / (T - R) 1925: // Ln = (TC - CR - TC + LT + P) / (T-R) 1926: // Ln = (LT - CR + P) / (T-R) 1927: 1928: unchecked { 1929: uint256[2][4] memory _premiumAccumulatorsByLeg = premiumAccumulatorsByLeg; 1930: uint256 _leg = leg; 1931: 1932: // if there's still liquidity, compute the new grossPremiumLast 1933: // otherwise, we just reset grossPremiumLast to the current grossPremium 1934: s_grossPremiumLast[chunkKey] = totalLiquidity != 0 1935: ? LeftRightUnsigned 1936: .wrap(0) 1937: .toRightSlot( 1938: uint128( 1939: uint256( 1940: Math.max( 1941: (int256( 1942: grossPremiumLast.rightSlot() * 1943: totalLiquidityBefore 1944: ) - 1945: int256( 1946: _premiumAccumulatorsByLeg[_leg][0] * 1947: positionLiquidity 1948: )) + int256(legPremia.rightSlot() * 2 ** 64), 1949: 0 1950: ) 1951: ) / totalLiquidity 1952: ) 1953: ) 1954: .toLeftSlot( 1955: uint128( 1956: uint256( 1957: Math.max( 1958: (int256( 1959: grossPremiumLast.leftSlot() * 1960: totalLiquidityBefore 1961: ) - 1962: int256( 1963: _premiumAccumulatorsByLeg[_leg][1] * 1964: positionLiquidity 1965: )) + int256(legPremia.leftSlot()) * 2 ** 64, 1966: 0 1967: ) 1968: ) / totalLiquidity 1969: ) 1970: ) 1971: : LeftRightUnsigned 1972: .wrap(0) 1973: .toRightSlot(uint128(premiumAccumulatorsByLeg[_leg][0])) 1974: .toLeftSlot(uint128(premiumAccumulatorsByLeg[_leg][1])); 1975: } 1976: } 1977: } 1978: 1979: // update settled tokens in storage with all local deltas 1980: s_settledTokens[chunkKey] = settledTokens; 1981: 1982: unchecked { 1983: ++leg; 1984: } 1985: }
[442-491, 460-486, 746-782, 803-816, 865-875, 1388-1397, 1524-1580, 1678-1751, 1858-1985]
File: contracts/SemiFungiblePositionManager.sol 575: for (uint256 i = 0; i < ids.length; ) { 576: if (s_poolContext[TokenId.wrap(ids[i]).poolId()].locked) revert Errors.ReentrantCall(); 577: registerTokenTransfer(from, to, TokenId.wrap(ids[i]), amounts[i]); 578: unchecked { 579: ++i; 580: } 581: } 601: for (uint256 leg = 0; leg < numLegs; ) { 602: // for this leg index: extract the liquidity chunk: a 256bit word containing the liquidity amount and upper/lower tick 603: // @dev see `contracts/types/LiquidityChunk.sol` 604: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 605: id, 606: leg, 607: uint128(amount) 608: ); 609: 610: //construct the positionKey for the from and to addresses 611: bytes32 positionKey_from = keccak256( 612: abi.encodePacked( 613: address(univ3pool), 614: from, 615: id.tokenType(leg), 616: liquidityChunk.tickLower(), 617: liquidityChunk.tickUpper() 618: ) 619: ); 620: bytes32 positionKey_to = keccak256( 621: abi.encodePacked( 622: address(univ3pool), 623: to, 624: id.tokenType(leg), 625: liquidityChunk.tickLower(), 626: liquidityChunk.tickUpper() 627: ) 628: ); 629: 630: // Revert if recipient already has that position 631: if ( 632: (LeftRightUnsigned.unwrap(s_accountLiquidity[positionKey_to]) != 0) || 633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0) 634: ) revert Errors.TransferFailed(); 635: 636: // Revert if sender has long positions in that chunk or the entire liquidity is not being transferred 637: LeftRightUnsigned fromLiq = s_accountLiquidity[positionKey_from]; 638: //@audit-ok before: if (fromLiq.rightSlot() != liquidityChunk.liquidity()) revert Errors.TransferFailed(); 639: if (LeftRightUnsigned.unwrap(fromLiq) != liquidityChunk.liquidity()) //@audit-ok before it checked only the rightSlot 640: revert Errors.TransferFailed(); 641: 642: LeftRightSigned fromBase = s_accountFeesBase[positionKey_from]; 643: 644: //update+store liquidity and fee values between accounts 645: s_accountLiquidity[positionKey_to] = fromLiq; 646: s_accountLiquidity[positionKey_from] = LeftRightUnsigned.wrap(0); 647: 648: s_accountFeesBase[positionKey_to] = fromBase; 649: s_accountFeesBase[positionKey_from] = LeftRightSigned.wrap(0); 650: unchecked { 651: ++leg; 652: } 653: } 883: for (uint256 leg = 0; leg < numLegs; ) { 884: LeftRightSigned _moved; 885: LeftRightSigned _itmAmounts; 886: LeftRightUnsigned _collectedSingleLeg; 887: 888: { 889: // cache the univ3pool, tokenId, isBurn, and _positionSize variables to get rid of stack too deep error 890: IUniswapV3Pool _univ3pool = univ3pool; 891: TokenId _tokenId = tokenId; 892: bool _isBurn = isBurn; 893: uint128 _positionSize = positionSize; 894: uint256 _leg; 895: 896: unchecked { 897: // Reverse the order of the legs if this call is burning a position (LIFO) 898: // We loop in reverse order if burning a position so that any dependent long liquidity is returned to the pool first, 899: // allowing the corresponding short liquidity to be removed 900: _leg = _isBurn ? numLegs - leg - 1 : leg; 901: } 902: 903: // for this _leg index: extract the liquidity chunk: a 256bit word containing the liquidity amount and upper/lower tick 904: // @dev see `contracts/types/LiquidityChunk.sol` 905: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 906: _tokenId, 907: _leg, 908: _positionSize 909: ); 910: 911: (_moved, _itmAmounts, _collectedSingleLeg) = _createLegInAMM( 912: _univ3pool, 913: _tokenId, 914: _leg, 915: liquidityChunk, 916: _isBurn 917: ); 918: 919: collectedByLeg[_leg] = _collectedSingleLeg; 920: 921: unchecked { 922: // increment accumulators of the upper bound on tokens contained across all legs of the position at any given tick 923: amount0 += Math.getAmount0ForLiquidity(liquidityChunk); 924: 925: amount1 += Math.getAmount1ForLiquidity(liquidityChunk); 926: } 927: } 928: 929: totalMoved = totalMoved.add(_moved); 930: itmAmounts = itmAmounts.add(_itmAmounts); 931: 932: unchecked { 933: ++leg; 934: } 935: } 372: while (address(s_poolContext[poolId].pool) != address(0)) { 373: poolId = PanopticMath.incrementPoolPattern(poolId); 374: }
[575-581, 601-653, 883-935, 372-374]
File: contracts/libraries/FeesCalc.sol 51: for (uint256 k = 0; k < positionIdList.length; ) { 52: TokenId tokenId = positionIdList[k]; 53: uint128 positionSize = userBalance[tokenId].rightSlot(); 54: uint256 numLegs = tokenId.countLegs(); 55: for (uint256 leg = 0; leg < numLegs; ) { 56: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 57: tokenId, 58: leg, 59: positionSize 60: ); 61: 62: (uint256 amount0, uint256 amount1) = Math.getAmountsForLiquidity( 63: atTick, 64: liquidityChunk 65: ); 66: 67: if (tokenId.isLong(leg) == 0) { 68: unchecked { 69: value0 += int256(amount0); 70: value1 += int256(amount1); 71: } 72: } else { 73: unchecked { 74: value0 -= int256(amount0); 75: value1 -= int256(amount1); 76: } 77: } 78: 79: unchecked { 80: ++leg; 81: } 82: } 83: unchecked { 84: ++k; 85: } 86: } 55: for (uint256 leg = 0; leg < numLegs; ) { 56: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 57: tokenId, 58: leg, 59: positionSize 60: ); 61: 62: (uint256 amount0, uint256 amount1) = Math.getAmountsForLiquidity( 63: atTick, 64: liquidityChunk 65: ); 66: 67: if (tokenId.isLong(leg) == 0) { 68: unchecked { 69: value0 += int256(amount0); 70: value1 += int256(amount1); 71: } 72: } else { 73: unchecked { 74: value0 -= int256(amount0); 75: value1 -= int256(amount1); 76: } 77: } 78: 79: unchecked { 80: ++leg; 81: } 82: }
File: contracts/libraries/Math.sol 759: while (i < j) { 760: while (arr[uint256(i)] < pivot) i++; 761: while (pivot < arr[uint256(j)]) j--; 762: if (i <= j) { 763: (arr[uint256(i)], arr[uint256(j)]) = (arr[uint256(j)], arr[uint256(i)]); 764: i++; 765: j--; 766: } 767: } 760: while (arr[uint256(i)] < pivot) i++; 761: while (pivot < arr[uint256(j)]) j--;
File: contracts/libraries/PanopticMath.sol 137: for (uint256 i = 0; i < cardinality + 1; ++i) { 138: (timestamps[i], tickCumulatives[i], , ) = univ3pool.observations( 139: uint256( 140: (int256(observationIndex) - int256(i * period)) + 141: int256(observationCardinality) 142: ) % observationCardinality 143: ); 144: } 148: for (uint256 i = 0; i < cardinality; ++i) { 149: ticks[i] = 150: (tickCumulatives[i] - tickCumulatives[i + 1]) / 151: int256(timestamps[i] - timestamps[i + 1]); 152: } 207: for (uint8 i; i < 8; ++i) { 208: // read the rank from the existing ordering 209: rank = (orderMap >> (3 * i)) % 8; 210: 211: if (rank == 7) { 212: shift -= 1; 213: continue; 214: } 215: 216: // read the corresponding entry 217: entry = int24(uint24(medianData >> (rank * 24))); 218: if ((below) && (lastObservedTick > entry)) { 219: shift += 1; 220: below = false; 221: } 222: 223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1))); 224: } 248: for (uint256 i = 0; i < 20; ++i) { 249: secondsAgos[i] = uint32(((i + 1) * twapWindow) / 20); 250: } 256: for (uint256 i = 0; i < 19; ++i) { 257: twapMeasurement[i] = int24( 258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20)) 259: ); 260: } 395: for (uint256 leg = 0; leg < numLegs; ) { 396: // Compute the amount of funds that have been removed from the Panoptic Pool 397: (LeftRightSigned longs, LeftRightSigned shorts) = _calculateIOAmounts( 398: tokenId, 399: positionSize, 400: leg 401: ); 402: 403: longAmounts = longAmounts.add(longs); 404: shortAmounts = shortAmounts.add(shorts); 405: 406: unchecked { 407: ++leg; 408: } 409: } 781: for (uint256 i = 0; i < positionIdList.length; ++i) { 782: TokenId tokenId = positionIdList[i]; 783: uint256 numLegs = tokenId.countLegs(); 784: for (uint256 leg = 0; leg < numLegs; ++leg) { 785: if (tokenId.isLong(leg) == 1) { 786: longPremium = longPremium.sub(premiasByLeg[i][leg]); 787: } 788: } 789: } 784: for (uint256 leg = 0; leg < numLegs; ++leg) { 785: if (tokenId.isLong(leg) == 1) { 786: longPremium = longPremium.sub(premiasByLeg[i][leg]); 787: } 788: } 860: for (uint256 i = 0; i < positionIdList.length; i++) { 861: TokenId tokenId = positionIdList[i]; 862: LeftRightSigned[4][] memory _premiasByLeg = premiasByLeg; 863: for (uint256 leg = 0; leg < tokenId.countLegs(); ++leg) { 864: if (tokenId.isLong(leg) == 1) { 865: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) 866: storage _settledTokens = settledTokens; 867: 868: // calculate amounts to revoke from settled and subtract from haircut req 869: uint256 settled0 = Math.unsafeDivRoundingUp( 870: uint128(-_premiasByLeg[i][leg].rightSlot()) * uint256(haircut0), 871: uint128(longPremium.rightSlot()) 872: ); 873: uint256 settled1 = Math.unsafeDivRoundingUp( 874: uint128(-_premiasByLeg[i][leg].leftSlot()) * uint256(haircut1), 875: uint128(longPremium.leftSlot()) 876: ); 877: 878: bytes32 chunkKey = keccak256( 879: abi.encodePacked( 880: tokenId.strike(0), 881: tokenId.width(0), 882: tokenId.tokenType(0) 883: ) 884: ); 885: 886: // The long premium is not commited to storage during the liquidation, so we add the entire adjusted amount 887: // for the haircut directly to the accumulator 888: settled0 = Math.max( 889: 0, 890: uint128(-_premiasByLeg[i][leg].rightSlot()) - settled0 891: ); 892: settled1 = Math.max( 893: 0, 894: uint128(-_premiasByLeg[i][leg].leftSlot()) - settled1 895: ); 896: 897: _settledTokens[chunkKey] = _settledTokens[chunkKey].add( 898: LeftRightUnsigned.wrap(0).toRightSlot(uint128(settled0)).toLeftSlot( 899: uint128(settled1) 900: ) 901: ); 902: } 903: } 904: } 863: for (uint256 leg = 0; leg < tokenId.countLegs(); ++leg) { 864: if (tokenId.isLong(leg) == 1) { 865: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) 866: storage _settledTokens = settledTokens; 867: 868: // calculate amounts to revoke from settled and subtract from haircut req 869: uint256 settled0 = Math.unsafeDivRoundingUp( 870: uint128(-_premiasByLeg[i][leg].rightSlot()) * uint256(haircut0), 871: uint128(longPremium.rightSlot()) 872: ); 873: uint256 settled1 = Math.unsafeDivRoundingUp( 874: uint128(-_premiasByLeg[i][leg].leftSlot()) * uint256(haircut1), 875: uint128(longPremium.leftSlot()) 876: ); 877: 878: bytes32 chunkKey = keccak256( 879: abi.encodePacked( 880: tokenId.strike(0), 881: tokenId.width(0), 882: tokenId.tokenType(0) 883: ) 884: ); 885: 886: // The long premium is not commited to storage during the liquidation, so we add the entire adjusted amount 887: // for the haircut directly to the accumulator 888: settled0 = Math.max( 889: 0, 890: uint128(-_premiasByLeg[i][leg].rightSlot()) - settled0 891: ); 892: settled1 = Math.max( 893: 0, 894: uint128(-_premiasByLeg[i][leg].leftSlot()) - settled1 895: ); 896: 897: _settledTokens[chunkKey] = _settledTokens[chunkKey].add( 898: LeftRightUnsigned.wrap(0).toRightSlot(uint128(settled0)).toLeftSlot( 899: uint128(settled1) 900: ) 901: ); 902: } 903: }
[137-144, 148-152, 207-224, 248-250, 256-260, 395-409, 781-789, 784-788, 860-904, 863-903]
File: contracts/multicall/Multicall.sol 14: for (uint256 i = 0; i < data.length; ) { 15: (bool success, bytes memory result) = address(this).delegatecall(data[i]); 16: 17: if (!success) { 18: // Bubble up the revert reason 19: // The bytes type is ABI encoded as a length-prefixed byte array 20: // So we simply need to add 32 to the pointer to get the start of the data 21: // And then revert with the size loaded from the first 32 bytes 22: // Other solutions will do work to differentiate the revert reasons and provide paranthetical information 23: // However, we have chosen to simply replicate the the normal behavior of the call 24: // NOTE: memory-safe because it reads from memory already allocated by solidity (the bytes memory result) 25: assembly ("memory-safe") { 26: revert(add(result, 32), mload(result)) 27: } 28: } 29: 30: results[i] = result; 31: 32: unchecked { 33: ++i; 34: } 35: }
[14-35]
</details>File: contracts/tokens/ERC1155Minimal.sol 143: for (uint256 i = 0; i < ids.length; ) { 144: id = ids[i]; 145: amount = amounts[i]; 146: 147: balanceOf[from][id] -= amount; 148: 149: // balance will never overflow 150: unchecked { 151: balanceOf[to][id] += amount; 152: } 153: 154: // An array can't have a total length 155: // larger than the max uint256 value. 156: unchecked { 157: ++i; 158: } 159: } 187: for (uint256 i = 0; i < owners.length; ++i) { 188: balances[i] = balanceOf[owners[i]][ids[i]]; 189: }
Those functions should be declared as external
instead of public
, as they are never called internally by the contract.
Contracts are allowed to override their parents' functions and change the visibility from external to public.
There are 13 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 1141: function getAccountMarginDetails(
[1141]
File: contracts/PanopticFactory.sol 135: function initialize(address _owner) public {
[135]
File: contracts/PanopticPool.sol 1450: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) {
[1450]
File: contracts/libraries/FeesCalc.sol 97: function calculateAMMSwapFees(
[97]
File: contracts/multicall/Multicall.sol 12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
[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) {
</details>File: contracts/tokens/ERC20Minimal.sol 50: function approve(address spender, uint256 amount) public returns (bool) { 62: function transfer(address to, uint256 amount) public virtual returns (bool) { 82: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
Use a scientific notation rather than decimal literals (e.g. 1e6
instead of 1000000
), for better code readability.
There are 7 instances of this issue.
File: contracts/CollateralTracker.sol // @audit 10_000 -> 1e4 77: uint256 internal constant DECIMALS = 10_000; // @audit 10_000 -> 1e4 81: int128 internal constant DECIMALS_128 = 10_000; // @audit 10_000 -> 1e4 204: 10_000 + // @audit 10_000 -> 1e4 206: 10_000 ** 2 + // @audit 10_000 -> 1e4 208: 10_000 ** 3
File: contracts/PanopticPool.sol // @audit 10_000 -> 1e4 175: uint256 internal constant NO_BUFFER = 10_000; // @audit 10_000 -> 1e4 1335: return balanceCross >= Math.unsafeDivRoundingUp(thresholdCross * buffer, 10_000);
Use a scientific notation rather than exponentiation (e.g. 1e18
instead of 10**18
): although the compiler is capable of optimizing it, it is considered good coding practice to utilize idioms that don't rely on compiler optimization, whenever possible.
There is 1 instance of this issue.
File: contracts/CollateralTracker.sol 234: totalSupply = 10 ** 6;
[234]
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.
There are 5 instances of this issue.
File: contracts/CollateralTracker.sol 203: (12500 * ratioTick) /
[203]
File: contracts/libraries/Constants.sol 13: int24 internal constant MIN_V3POOL_TICK = -887272; 16: int24 internal constant MAX_V3POOL_TICK = 887272; 19: uint160 internal constant MIN_V3POOL_SQRT_RATIO = 4295128739; 23: 1461446703485210103287273052203988822378723970342;
Consider refactoring the following code, as double casting is confusing, and, in some scenarios, it may introduce unexpected truncations or rounding issues.
Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging.
There are 67 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol // @audit int24(int256) 667: int24 range = int24( 668: int256( 669: Math.unsafeDivRoundingUp( 670: uint24(positionId.width(leg) * positionId.tickSpacing()), 671: 2 672: ) 673: ) 674: ); // @audit int128(uint128) 715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0)) // @audit int128(uint128) 715: int128(uint128(oracleValue0)) - int128(uint128(currentValue0)) // @audit int128(uint128) 718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1)) // @audit int128(uint128) 718: int128(uint128(oracleValue1)) - int128(uint128(currentValue1)) // @audit int256(uint256) 1003: int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount; // @audit uint128(uint256) 1028: s_poolAssets = uint128(uint256(updatedAssets)); // @audit uint128(uint256) 1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount))); // @audit int256(uint256) 1029: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) + (shortAmount - longAmount))); // @audit int256(uint256) 1052: int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount; // @audit uint128(uint256) 1084: s_poolAssets = uint128(uint256(updatedAssets + realizedPremium)); // @audit uint128(uint256) 1085: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount))); // @audit int256(uint256) 1085: s_inAMM = uint128(uint256(int256(uint256(s_inAMM)) - (shortAmount - longAmount))); // @audit uint256(uint128) 1121: uint256(uint128(shortAmount + longAmount)) * COMMISSION_FEE, // @audit uint256(uint128) 1184: netBalance += uint256(uint128(premiumAllPositions)); // @audit int64(uint64) 1329: ? int64(uint64(poolUtilization)) // @audit int64(uint64) 1330: : int64(uint64(poolUtilization >> 64)); // @audit int64(uint64) 1585: ? int64(uint64(poolUtilization)) // @audit int64(uint64) 1586: : int64(uint64(poolUtilization >> 64)) // @audit uint128(uint64) 1637: uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) + // @audit uint128(uint64) 1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
[667-674, 715, 715, 718, 718, 1003, 1028, 1029, 1029, 1052, 1084, 1085, 1085, 1121, 1184, 1329, 1330, 1585, 1586, 1637, 1638]
File: contracts/PanopticPool.sol // @audit uint256(uint24) 314: (uint256(uint24(currentTick)) << 24) + // add to slot 4 // @audit uint256(uint24) 315: (uint256(uint24(currentTick))); // add to slot 3 // @audit int256(uint256) 1145: uint256(int256(uint256(_delegations.rightSlot())) + liquidationBonus0) // @audit int256(uint256) 1150: uint256(int256(uint256(_delegations.leftSlot())) + liquidationBonus1) // @audit int128(int256) 1554: int128( 1555: int256( 1556: ((premiumAccumulatorsByLeg[leg][0] - 1557: premiumAccumulatorLast.rightSlot()) * 1558: (liquidityChunk.liquidity())) / 2 ** 64 1559: ) 1560: ) // @audit int128(int256) 1563: int128( 1564: int256( 1565: ((premiumAccumulatorsByLeg[leg][1] - 1566: premiumAccumulatorLast.leftSlot()) * 1567: (liquidityChunk.liquidity())) / 2 ** 64 1568: ) 1569: ) // @audit int128(int256) 1641: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) // @audit int128(int256) 1642: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64)));
[314, 315, 1145, 1150, 1554-1560, 1563-1569, 1641, 1642]
File: contracts/SemiFungiblePositionManager.sol // @audit int128(int256) 1170: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside0LastX128, liquidity))) // @audit int128(int256) 1173: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside1LastX128, liquidity))) // @audit int128(int256) 1177: .toRightSlot(int128(int256(Math.mulDiv128(feeGrowthInside0LastX128, liquidity)))) // @audit int128(int256) 1178: .toLeftSlot(int128(int256(Math.mulDiv128(feeGrowthInside1LastX128, liquidity)))); // @audit int128(int256) 1215: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(int128(int256(amount0))).toLeftSlot( // @audit int128(int256) 1216: int128(int256(amount1)) // @audit int128(int256) 1242: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(-int128(int256(amount0))).toLeftSlot( // @audit int128(int256) 1243: -int128(int256(amount1))
[1170, 1173, 1177, 1178, 1215, 1216, 1242, 1243]
File: contracts/libraries/FeesCalc.sol // @audit int128(int256) 117: .toRightSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken0X128, liquidity)))) // @audit int128(int256) 118: .toLeftSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken1X128, liquidity))));
File: contracts/libraries/Math.sol // @audit uint256(int256) 130: uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); // @audit uint256(int256) 131: if (absTick > uint256(int256(Constants.MAX_V3POOL_TICK))) revert Errors.InvalidTick();
File: contracts/libraries/PanopticMath.sol // @audit uint64(uint24) 51: poolId += uint64(uint24(tickSpacing)) << 48; // @audit uint248(uint256) 103: (uint248(uint256(keccak256(abi.encode(tokenId))))); // @audit int24(uint24) 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + // @audit int24(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 int24(uint24) 217: entry = int24(uint24(medianData >> (rank * 24))); // @audit uint256(uint192) 229: uint256(uint192(medianData << 24)) + // @audit uint256(uint24) 230: uint256(uint24(lastObservedTick)); // @audit int56(uint56) 258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20)) // @audit int24(int256) 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) // @audit int256(uint256) 693: int256 balance0 = int256(uint256(tokenData0.rightSlot())) - // @audit int256(uint256) 695: int256 balance1 = int256(uint256(tokenData1.rightSlot())) -
[51, 103, 178, 179, 183, 217, 229, 230, 258, 377, 693, 695]
File: contracts/types/LeftRight.sol // @audit int256(uint256) 27: int256(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000)); // @audit int256(uint256) 27: int256(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000)); // @audit int256(uint256) 196: int256 left = int256(uint256(x.leftSlot())) + y.leftSlot(); // @audit int256(uint256) 201: int256 right = int256(uint256(x.rightSlot())) + y.rightSlot();
File: contracts/types/LiquidityChunk.sol // @audit uint256(uint24) 79: (uint256(uint24(_tickLower)) << 232) + // @audit uint256(uint24) 80: (uint256(uint24(_tickUpper)) << 208) + // @audit uint256(uint24) 110: LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232) // @audit uint256(uint24) 127: LiquidityChunk.unwrap(self) + ((uint256(uint24(_tickUpper))) << 208) // @audit int24(int256) 174: return int24(int256(LiquidityChunk.unwrap(self) >> 232)); // @audit int24(int256) 183: return int24(int256(LiquidityChunk.unwrap(self) >> 208));
</details>File: contracts/types/TokenId.sol // @audit int24(uint24) 98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16)); // @audit int24(int256) 160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12))); // @audit int24(int256) 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); // @audit uint256(uint24) 195: return TokenId.wrap(TokenId.unwrap(self) + (uint256(uint24(_tickSpacing)) << 48));
using-for
syntaxThe using-for
syntax is the more common way of calling library functions.
There are 460 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 170: if (msg.sender != address(s_panopticPool)) revert Errors.NotPanopticPool(); 229: if (s_initialized) revert Errors.CollateralTokenAlreadyInitialized(); 292: InteractionHelper.computeName( 305: return InteractionHelper.computeSymbol(s_underlyingToken, TICKER_PREFIX); 312: return InteractionHelper.computeDecimals(s_underlyingToken); 331: if (s_panopticPool.numberOfPositions(msg.sender) != 0) revert Errors.PositionCountNotZero(); 333: return ERC20Minimal.transfer(recipient, amount); 350: if (s_panopticPool.numberOfPositions(from) != 0) revert Errors.PositionCountNotZero(); 352: return ERC20Minimal.transferFrom(from, to, amount); 380: return Math.mulDiv(assets, totalSupply, totalAssets()); 387: return Math.mulDiv(shares, totalAssets(), totalSupply); 402: shares = Math.mulDiv( 418: if (assets > type(uint104).max) revert Errors.DepositTooLarge(); 424: SafeTransferLib.safeTransferFrom( 462: assets = Math.mulDivRoundingUp( 480: if (assets > type(uint104).max) revert Errors.DepositTooLarge(); 484: SafeTransferLib.safeTransferFrom( 512: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0; 521: return Math.mulDivRoundingUp(assets, supply, totalAssets()); 536: if (assets > maxWithdraw(owner)) revert Errors.ExceedsMaximumRedemption(); 556: SafeTransferLib.safeTransferFrom( 575: return s_panopticPool.numberOfPositions(owner) == 0 ? Math.min(available, balance) : 0; 596: if (shares > maxRedeem(owner)) revert Errors.ExceedsMaximumRedemption(); 616: SafeTransferLib.safeTransferFrom( 669: Math.unsafeDivRoundingUp( 675: maxNumRangesFromStrike = Math.max( 677: uint256(Math.abs(currentTick - positionId.strike(leg)) / range) 687: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 693: (currentValue0, currentValue1) = Math.getAmountsForLiquidity( 698: (oracleValue0, oracleValue1) = Math.getAmountsForLiquidity( 712: LeftRightSigned 956: Math.mulDiv( 959: uint256(Math.max(1, int256(totalAssets()) - int256(assets))) 1012: uint256 sharesToBurn = Math.mulDivRoundingUp( 1069: uint256 sharesToBurn = Math.mulDivRoundingUp( 1109: uint256 swapCommission = Math.unsafeDivRoundingUp( 1110: s_ITMSpreadFee * uint256(Math.abs(intrinsicValue)), 1120: Math.unsafeDivRoundingUp( 1210: TokenId tokenId = TokenId.wrap(positionBalanceArray[i][0]); 1213: uint128 positionSize = LeftRightUnsigned.wrap(positionBalanceArray[i][1]).rightSlot(); 1216: uint128 poolUtilization = LeftRightUnsigned.wrap(positionBalanceArray[i][1]).leftSlot(); 1322: LeftRightUnsigned amountsMoved = PanopticMath.getAmountsMoved(tokenId, positionSize, index); 1363: ? Math.getSqrtRatioAtTick( 1364: Math.max24(2 * (atTick - strike), Constants.MIN_V3POOL_TICK) 1364: Math.max24(2 * (atTick - strike), Constants.MIN_V3POOL_TICK) 1366: : Math.getSqrtRatioAtTick( 1367: Math.max24(2 * (strike - atTick), Constants.MIN_V3POOL_TICK) 1367: Math.max24(2 * (strike - atTick), Constants.MIN_V3POOL_TICK) 1397: uint256 c2 = Constants.FP96 - ratio; 1401: required += Math.mulDiv96RoundingUp(amountMoved, c2); 1408: uint160 scaleFactor = Math.getSqrtRatioAtTick( 1411: uint256 c3 = Math.mulDivRoundingUp( 1414: scaleFactor + Constants.FP96 1486: required = Math.unsafeDivRoundingUp(amount * sellCollateral, DECIMALS); 1496: required = Math.unsafeDivRoundingUp(amount * buyCollateral, DECIMALS); 1518: LeftRightUnsigned amountsMoved = PanopticMath.getAmountsMoved(tokenId, positionSize, index); 1521: LeftRightUnsigned amountsMovedPartner = PanopticMath.getAmountsMoved( 1571: ? Math.unsafeDivRoundingUp((notionalP - notional) * contracts, notional) 1572: : Math.unsafeDivRoundingUp((notional - notionalP) * contracts, notionalP); 1579: spreadRequirement = Math.max(
[170, 229, 292, 305, 312, 331, 333, 350, 352, 380, 387, 402, 418, 424, 462, 480, 484, 512, 521, 536, 556, 575, 596, 616, 669, 675, 677, 687, 693, 698, 712, 956, 959, 1012, 1069, 1109, 1110, 1120, 1210, 1213, 1216, 1322, 1363, 1364, 1364, 1366, 1367, 1367, 1397, 1401, 1408, 1411, 1414, 1486, 1496, 1518, 1521, 1571, 1572, 1579]
File: contracts/PanopticFactory.sol 151: if (msg.sender != currentOwner) revert Errors.NotOwner(); 178: CallbackLib.CallbackData memory decoded = abi.decode(data, (CallbackLib.CallbackData)); 180: CallbackLib.validateCallback(msg.sender, UNIV3_FACTORY, decoded.poolFeatures); 183: SafeTransferLib.safeTransferFrom( 190: SafeTransferLib.safeTransferFrom( 221: if (address(bytes20(salt)) != msg.sender) revert Errors.InvalidSalt(); 225: if (_owner != address(0) && _owner != msg.sender) revert Errors.NotOwner(); 227: IUniswapV3Pool v3Pool = IUniswapV3Pool(UNIV3_FACTORY.getPool(token0, token1, fee)); 228: if (address(v3Pool) == address(0)) revert Errors.UniswapPoolNotInitialized(); 231: revert Errors.PoolAlreadyInitialized(); 234: SFPM.initializeAMMPool(token0, token1, fee); 238: newPoolContract = PanopticPool(POOL_REFERENCE.cloneDeterministic(salt)); 242: Clones.clone(COLLATERAL_REFERENCE) 245: Clones.clone(COLLATERAL_REFERENCE) 267: DONOR_NFT.issueNFT(msg.sender, newPoolContract, token0, token1, fee); 305: uint256 rarity = PanopticMath.numberOfLeadingHexZeros( 306: POOL_REFERENCE.predictDeterministicAddress(salt) 354: Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_WETH, currentSqrtPriceX96) 358: Math.mulDivRoundingUp( 360: Constants.FP96, 367: Math.mulDiv96RoundingUp(FULL_RANGE_LIQUIDITY_AMOUNT_TOKEN, currentSqrtPriceX96) 370: Math.mulDivRoundingUp( 372: Constants.FP96, 393: tickLower = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing; 398: CallbackLib.CallbackData({ 399: poolFeatures: CallbackLib.PoolFeatures({token0: token0, token1: token1, fee: fee}),
[151, 178, 180, 183, 190, 221, 225, 227, 228, 231, 234, 238, 242, 245, 267, 305, 306, 354, 358, 360, 367, 370, 372, 393, 398, 399]
File: contracts/PanopticPool.sol 104: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1; 104: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1; 106: int24 internal constant MAX_SWAP_TICK = Constants.MAX_V3POOL_TICK - 1; 106: int24 internal constant MAX_SWAP_TICK = Constants.MAX_V3POOL_TICK - 1; 300: if (address(s_univ3pool) != address(0)) revert Errors.PoolAlreadyInitialized(); 327: InteractionHelper.doApprovals(SFPM, collateralTracker0, collateralTracker1, token0, token1); 343: revert Errors.PriceBoundFail(); 416: (value0, value1) = FeesCalc.getPortfolioValue( 445: balances[k][0] = TokenId.unwrap(tokenId); 446: balances[k][1] = LeftRightUnsigned.unwrap(s_positionBalance[c_user][tokenId]); 453: LeftRightUnsigned.wrap(balances[k][1]).rightSlot(), 474: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))), 474: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(premiaByLeg[leg]))), 478: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium))) 478: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium))) 526: (, uint256 medianData) = PanopticMath.computeInternalMedian( 634: if (tokenId.poolId() != SFPM.getPoolId(address(s_univ3pool))) 635: revert Errors.InvalidTokenIdParameter(0); 639: if (LeftRightUnsigned.unwrap(s_positionBalance[msg.sender][tokenId]) != 0) 640: revert Errors.PositionAlreadyMinted(); 655: s_positionBalance[msg.sender][tokenId] = LeftRightUnsigned 686: (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) = SFPM 713: (LeftRightSigned longAmounts, LeftRightSigned shortAmounts) = PanopticMath 752: (uint128 premiumAccumulator0, uint128 premiumAccumulator1) = SFPM.getAccountPremium( 763: s_options[msg.sender][tokenId][leg] = LeftRightUnsigned 776: uint64(Math.min(effectiveLiquidityLimitX32, MAX_SPREAD)) 862: s_positionBalance[owner][tokenId] = LeftRightUnsigned.wrap(0); 871: s_options[owner][tokenId][leg] = LeftRightUnsigned.wrap(0); 906: int24 fastOracleTick = PanopticMath.computeMedianObservedPrice( 916: slowOracleTick = PanopticMath.computeMedianObservedPrice( 924: (slowOracleTick, medianData) = PanopticMath.computeInternalMedian( 941: if (!solventAtFast) revert Errors.NotEnoughCollateral(); 944: if (Math.abs(int256(fastOracleTick) - slowOracleTick) > MAX_SLOW_FAST_DELTA) 946: revert Errors.NotEnoughCollateral(); 971: (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) = SFPM 982: (LeftRightSigned longAmounts, LeftRightSigned shortAmounts) = PanopticMath 1036: if (Math.abs(currentTick - twapTick) > MAX_TWAP_DELTA_LIQUIDATION) 1037: revert Errors.StaleTWAP(); 1064: Math.getSqrtRatioAtTick(twapTick) 1067: if (balanceCross >= thresholdCross) revert Errors.NotMarginCalled(); 1089: Constants.MIN_V3POOL_TICK, 1090: Constants.MAX_V3POOL_TICK, 1099: (liquidationBonus0, liquidationBonus1, collateralRemaining) = PanopticMath 1103: Math.getSqrtRatioAtTick(twapTick), 1104: Math.getSqrtRatioAtTick(finalTick), 1123: (deltaBonus0, deltaBonus1) = PanopticMath.haircutPremia( 1130: Math.getSqrtRatioAtTick(_finalTick), 1164: ) revert Errors.NotEnoughCollateral(); 1166: LeftRightSigned bonusAmounts = LeftRightSigned 1189: if (touchedId.length != 1) revert Errors.InputListFail(); 1198: (LeftRightSigned longAmounts, LeftRightSigned delegatedAmounts) = PanopticMath 1248: refundAmounts = PanopticMath.getRefundAmounts( 1330: Math.getSqrtRatioAtTick(atTick) 1335: return balanceCross >= Math.unsafeDivRoundingUp(thresholdCross * buffer, 10_000); 1354: Math.mulDiv(uint256(tokenData1.rightSlot()), 2 ** 96, sqrtPriceX96) + 1355: Math.mulDiv96(tokenData0.rightSlot(), sqrtPriceX96); 1359: Math.mulDivRoundingUp(uint256(tokenData1.leftSlot()), 2 ** 96, sqrtPriceX96) + 1360: Math.mulDiv96RoundingUp(tokenData0.leftSlot(), sqrtPriceX96); 1389: fingerprintIncomingList = PanopticMath.updatePositionsHash( 1400: if (fingerprintIncomingList != currentHash) revert Errors.InputListFail(); 1416: uint256 newHash = PanopticMath.updatePositionsHash( 1421: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen(); 1457: twapTick = PanopticMath.twapFilter(s_univ3pool, TWAP_WINDOW); 1478: LeftRightUnsigned accountLiquidities = SFPM.getAccountLiquidity( 1499: revert Errors.EffectiveLiquidityAboveThreshold(); 1527: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 1534: (premiumAccumulatorsByLeg[leg][0], premiumAccumulatorsByLeg[leg][1]) = SFPM 1551: premiaByLeg[leg] = LeftRightSigned 1573: premiaByLeg[leg] = LeftRightSigned.wrap(0).sub(premiaByLeg[leg]); 1602: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg(); 1611: (uint128 premiumAccumulator0, uint128 premiumAccumulator1) = SFPM.getAccountPremium( 1620: accumulatedPremium = LeftRightUnsigned 1633: uint256 liquidity = PanopticMath 1639: LeftRightSigned realizedPremia = LeftRightSigned 1657: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(realizedPremia))) 1657: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(realizedPremia))) 1686: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 1713: (grossCurrent[0], grossCurrent[1]) = SFPM.getAccountPremium( 1731: s_grossPremiumLast[chunkKey] = LeftRightUnsigned 1780: LeftRightUnsigned 1784: Math.min( 1793: Math.min( 1817: LeftRightUnsigned accountLiquidities = SFPM.getAccountLiquidity( 1868: if (LeftRightSigned.unwrap(legPremia) != 0) { 1872: settledTokens = LeftRightUnsigned.wrap( 1874: LeftRightSigned.unwrap( 1875: LeftRightSigned 1876: .wrap(int256(LeftRightUnsigned.unwrap(settledTokens))) 1883: uint256 positionLiquidity = PanopticMath 1898: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(legPremia))), 1898: LeftRightUnsigned.wrap(uint256(LeftRightSigned.unwrap(legPremia))), 1907: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium))) 1907: LeftRightSigned.wrap(int256(LeftRightUnsigned.unwrap(availablePremium))) 1935: ? LeftRightUnsigned 1940: Math.max( 1957: Math.max( 1971: : LeftRightUnsigned
[104, 104, 106, 106, 300, 327, 343, 416, 445, 446, 453, 474, 474, 478, 478, 526, 634, 635, 639, 640, 655, 686, 713, 752, 763, 776, 862, 871, 906, 916, 924, 941, 944, 946, 971, 982, 1036, 1037, 1064, 1067, 1089, 1090, 1099, 1103, 1104, 1123, 1130, 1164, 1166, 1189, 1198, 1248, 1330, 1335, 1354, 1355, 1359, 1360, 1389, 1400, 1416, 1421, 1457, 1478, 1499, 1527, 1534, 1551, 1573, 1602, 1611, 1620, 1633, 1639, 1657, 1657, 1686, 1713, 1731, 1780, 1784, 1793, 1817, 1868, 1872, 1874, 1875, 1876, 1883, 1898, 1898, 1907, 1907, 1935, 1940, 1957, 1971]
File: contracts/SemiFungiblePositionManager.sol 322: if (s_poolContext[poolId].locked) revert Errors.ReentrantCall(); 352: address univ3pool = FACTORY.getPool(token0, token1, fee); 355: if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized(); 367: uint64 poolId = PanopticMath.getPoolId(univ3pool); 373: poolId = PanopticMath.incrementPoolPattern(poolId); 408: CallbackLib.CallbackData memory decoded = abi.decode(data, (CallbackLib.CallbackData)); 410: CallbackLib.validateCallback(msg.sender, FACTORY, decoded.poolFeatures); 413: SafeTransferLib.safeTransferFrom( 420: SafeTransferLib.safeTransferFrom( 441: CallbackLib.CallbackData memory decoded = abi.decode(data, (CallbackLib.CallbackData)); 443: CallbackLib.validateCallback(msg.sender, FACTORY, decoded.poolFeatures); 456: SafeTransferLib.safeTransferFrom(token, decoded.payer, msg.sender, amountToPay); 482: _burn(msg.sender, TokenId.unwrap(tokenId), positionSize); 515: _mint(msg.sender, TokenId.unwrap(tokenId), positionSize); 549: if (s_poolContext[TokenId.wrap(id).poolId()].locked) revert Errors.ReentrantCall(); 549: if (s_poolContext[TokenId.wrap(id).poolId()].locked) revert Errors.ReentrantCall(); 552: registerTokenTransfer(from, to, TokenId.wrap(id), amount); 576: if (s_poolContext[TokenId.wrap(ids[i]).poolId()].locked) revert Errors.ReentrantCall(); 576: if (s_poolContext[TokenId.wrap(ids[i]).poolId()].locked) revert Errors.ReentrantCall(); 577: registerTokenTransfer(from, to, TokenId.wrap(ids[i]), amounts[i]); 604: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 632: (LeftRightUnsigned.unwrap(s_accountLiquidity[positionKey_to]) != 0) || 633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0) 634: ) revert Errors.TransferFailed(); 639: if (LeftRightUnsigned.unwrap(fromLiq) != liquidityChunk.liquidity()) //@audit-ok before it checked only the rightSlot 640: revert Errors.TransferFailed(); 646: s_accountLiquidity[positionKey_from] = LeftRightUnsigned.wrap(0); 649: s_accountFeesBase[positionKey_from] = LeftRightSigned.wrap(0); 689: if (positionSize == 0) revert Errors.OptionsBalanceZero(); 703: if (univ3pool == IUniswapV3Pool(address(0))) revert Errors.UniswapPoolNotInitialized(); 718: if ((LeftRightSigned.unwrap(itmAmounts) != 0)) { 729: revert Errors.PriceBoundFail(); 775: CallbackLib.CallbackData({ 776: poolFeatures: CallbackLib.PoolFeatures({ 818: int256 net0 = itm0 - PanopticMath.convert1to0(itm1, sqrtPriceX96); 834: if (swapAmount == 0) return LeftRightSigned.wrap(0); 843: ? Constants.MIN_V3POOL_SQRT_RATIO + 1 844: : Constants.MAX_V3POOL_SQRT_RATIO - 1, 849: totalSwapped = LeftRightSigned.wrap(0).toRightSlot(swap0.toInt128()).toLeftSlot( 905: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 923: amount0 += Math.getAmount0ForLiquidity(liquidityChunk); 925: amount1 += Math.getAmount1ForLiquidity(liquidityChunk); 941: revert Errors.PositionTooLarge(); 1019: revert Errors.NotEnoughLiquidity(); 1039: s_accountLiquidity[positionKey] = LeftRightUnsigned 1124: (s_accountPremiumOwed[positionKey], s_accountPremiumGross[positionKey]) = LeftRightLibrary 1167: ? LeftRightSigned 1170: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside0LastX128, liquidity))) 1173: int128(int256(Math.mulDiv128RoundingUp(feeGrowthInside1LastX128, liquidity))) 1175: : LeftRightSigned 1177: .toRightSlot(int128(int256(Math.mulDiv128(feeGrowthInside0LastX128, liquidity)))) 1178: .toLeftSlot(int128(int256(Math.mulDiv128(feeGrowthInside1LastX128, liquidity)))); 1192: CallbackLib.CallbackData({ // compute by reading values from univ3pool every time 1193: poolFeatures: CallbackLib.PoolFeatures({ 1215: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(int128(int256(amount0))).toLeftSlot( 1242: movedAmounts = LeftRightSigned.wrap(0).toRightSlot(-int128(int256(amount0))).toLeftSlot( 1280: if (LeftRightSigned.unwrap(amountToCollect) != 0) { 1307: collectedChunk = LeftRightUnsigned.wrap(0).toRightSlot(collected0).toLeftSlot( 1351: premium0X64_base = Math.mulDiv( 1356: premium1X64_base = Math.mulDiv( 1370: premium0X64_owed = Math 1373: premium1X64_owed = Math 1377: deltaPremiumOwed = LeftRightUnsigned 1394: premium0X64_gross = Math 1397: premium1X64_gross = Math 1401: deltaPremiumGross = LeftRightUnsigned 1481: LeftRightSigned feesBase = FeesCalc.calculateAMMSwapFees( 1493: amountToCollect = LeftRightUnsigned.wrap( 1495: LeftRightSigned.unwrap(feesBase.subRect(s_accountFeesBase[positionKey])) 1508: (premiumOwed, premiumGross) = LeftRightLibrary.addCapped(
[322, 352, 355, 367, 373, 408, 410, 413, 420, 441, 443, 456, 482, 515, 549, 549, 552, 576, 576, 577, 604, 632, 633, 634, 639, 640, 646, 649, 689, 703, 718, 729, 775, 776, 818, 834, 843, 844, 849, 905, 923, 925, 941, 1019, 1039, 1124, 1167, 1170, 1173, 1175, 1177, 1178, 1192, 1193, 1215, 1242, 1280, 1307, 1351, 1356, 1370, 1373, 1377, 1394, 1397, 1401, 1481, 1493, 1495, 1508]
File: contracts/libraries/CallbackLib.sol 38: revert Errors.InvalidUniswapCallback();
[38]
File: contracts/libraries/FeesCalc.sol 56: LiquidityChunk liquidityChunk = PanopticMath.getLiquidityChunk( 62: (uint256 amount0, uint256 amount1) = Math.getAmountsForLiquidity( 115: LeftRightSigned 117: .toRightSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken0X128, liquidity)))) 118: .toLeftSlot(int128(int256(Math.mulDiv128(ammFeesPerLiqToken1X128, liquidity))));
File: contracts/libraries/InteractionHelper.sol 82: Strings.toString(fee),
[82]
File: contracts/libraries/Math.sol 131: if (absTick > uint256(int256(Constants.MAX_V3POOL_TICK))) revert Errors.InvalidTick(); 131: if (absTick > uint256(int256(Constants.MAX_V3POOL_TICK))) revert Errors.InvalidTick(); 251: LiquidityChunkLibrary.createChunk( 281: LiquidityChunkLibrary.createChunk( 284: toUint128(mulDiv(amount1, Constants.FP96, highPriceX96 - lowPriceX96)) 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();
[131, 131, 251, 281, 284, 297, 312, 319, 326]
File: contracts/libraries/PanopticMath.sol 77: return addr == address(0) ? 40 : 39 - Math.mostSignificantNibble(uint160(addr)); 155: return int24(Math.sort(ticks)[cardinality / 2]); 263: int256[] memory sortedTicks = Math.sort(twapMeasurement); 326: return Math.getLiquidityForAmount0(tickLower, tickUpper, amount); 328: return Math.getLiquidityForAmount1(tickLower, tickUpper, amount); 346: int24 minTick = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing; 347: int24 maxTick = (Constants.MAX_V3POOL_TICK / tickSpacing) * tickSpacing; 349: (int24 rangeDown, int24 rangeUp) = PanopticMath.getRangesFromStrike(width, tickSpacing); 361: ) revert Errors.TicksNotInitializable(); 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) 452: convertCollateralData(tokenData0, tokenData1, tokenType, Math.getSqrtRatioAtTick(tick)); 476: ? convert0to1(contractSize, Math.getSqrtRatioAtTick((tickUpper + tickLower) / 2)) 477: : convert1to0(contractSize, Math.getSqrtRatioAtTick((tickUpper + tickLower) / 2)); 479: if (notional == 0 || notional > type(uint128).max) revert Errors.InvalidNotionalValue(); 495: return Math.mulDiv192(amount, uint256(sqrtPriceX96) ** 2); 497: return Math.mulDiv128(amount, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 497: return Math.mulDiv128(amount, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 512: return Math.mulDiv(amount, 2 ** 192, uint256(sqrtPriceX96) ** 2); 514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 529: int256 absResult = Math 530: .mulDiv192(Math.absUint(amount), uint256(sqrtPriceX96) ** 2) 534: int256 absResult = Math 535: .mulDiv128(Math.absUint(amount), Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)) 535: .mulDiv128(Math.absUint(amount), Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)) 552: int256 absResult = Math 553: .mulDiv(Math.absUint(amount), 2 ** 192, uint256(sqrtPriceX96) ** 2) 557: int256 absResult = Math 559: Math.absUint(amount), 561: Math.mulDiv64(sqrtPriceX96, sqrtPriceX96) 587: amount1 = Math 588: .getAmount1ForLiquidity(Math.getLiquidityForAmount0(tickLower, tickUpper, amount0)) 593: amount0 = Math 594: .getAmount0ForLiquidity(Math.getLiquidityForAmount1(tickLower, tickUpper, amount1)) 598: return LeftRightUnsigned.wrap(0).toRightSlot(amount0).toLeftSlot(amount1); 621: shorts = shorts.toRightSlot(Math.toInt128(amountsMoved.rightSlot())); 624: longs = longs.toRightSlot(Math.toInt128(amountsMoved.rightSlot())); 629: shorts = shorts.toLeftSlot(Math.toInt128(amountsMoved.leftSlot())); 632: longs = longs.toLeftSlot(Math.toInt128(amountsMoved.leftSlot())); 664: uint256 required0 = PanopticMath.convert0to1( 671: (uint256 balanceCross, uint256 thresholdCross) = PanopticMath.convertCollateralData( 678: uint256 bonusCross = Math.min(balanceCross / 2, thresholdCross - balanceCross); 681: bonus0 = int256(Math.mulDiv128(bonusCross, requiredRatioX128)); 684: PanopticMath.convert0to1( 685: Math.mulDiv128(bonusCross, 2 ** 128 - requiredRatioX128), 694: Math.max(premia.rightSlot(), 0); 696: Math.max(premia.leftSlot(), 0); 715: bonus1 += Math.min( 717: PanopticMath.convert0to1(paid0 - balance0, sqrtPriceX96Final) 719: bonus0 -= Math.min( 720: PanopticMath.convert1to0(balance1 - paid1, sqrtPriceX96Final), 733: bonus0 += Math.min( 735: PanopticMath.convert1to0(paid1 - balance1, sqrtPriceX96Final) 737: bonus1 -= Math.min( 738: PanopticMath.convert0to1(balance0 - paid0, sqrtPriceX96Final), 749: LeftRightSigned.wrap(0).toRightSlot(int128(balance0 - paid0)).toLeftSlot( 791: int256 collateralDelta0 = -Math.min(collateralRemaining.rightSlot(), 0); 792: int256 collateralDelta1 = -Math.min(collateralRemaining.leftSlot(), 0); 803: -Math.min( 805: PanopticMath.convert1to0( 810: Math.min( 812: PanopticMath.convert0to1( 827: Math.min( 829: PanopticMath.convert1to0( 834: -Math.min( 836: PanopticMath.convert0to1( 847: haircut0 = Math.min(collateralDelta0, longPremium.rightSlot()); 848: haircut1 = Math.min(collateralDelta1, longPremium.leftSlot()); 869: uint256 settled0 = Math.unsafeDivRoundingUp( 873: uint256 settled1 = Math.unsafeDivRoundingUp( 888: settled0 = Math.max( 892: settled1 = Math.max( 898: LeftRightUnsigned.wrap(0).toRightSlot(uint128(settled0)).toLeftSlot( 924: uint160 sqrtPriceX96 = Math.getSqrtRatioAtTick(atTick); 933: LeftRightSigned 939: PanopticMath.convert0to1(uint256(balanceShortage), sqrtPriceX96) 951: LeftRightSigned 957: PanopticMath.convert1to0(uint256(balanceShortage), sqrtPriceX96)
[77, 155, 263, 326, 328, 346, 347, 349, 361, 377, 452, 476, 477, 479, 495, 497, 497, 512, 514, 514, 529, 530, 534, 535, 535, 552, 553, 557, 559, 561, 587, 588, 593, 594, 598, 621, 624, 629, 632, 664, 671, 678, 681, 684, 685, 694, 696, 715, 717, 719, 720, 733, 735, 737, 738, 749, 791, 792, 803, 805, 810, 812, 827, 829, 834, 836, 847, 848, 869, 873, 888, 892, 898, 924, 933, 939, 951, 957]
File: contracts/libraries/SafeTransferLib.sol 46: if (!success) revert Errors.TransferFailed(); 76: if (!success) revert Errors.TransferFailed();
File: contracts/tokens/ERC1155Minimal.sol 115: ERC1155Holder.onERC1155Received.selector 166: ERC1155Holder.onERC1155BatchReceived.selector 225: ERC1155Holder.onERC1155Received.selector
File: contracts/types/LeftRight.sol 40: return uint128(LeftRightUnsigned.unwrap(self)); 47: return int128(LeftRightSigned.unwrap(self)); 67: LeftRightUnsigned.wrap( 68: (LeftRightUnsigned.unwrap(self) & LEFT_HALF_BIT_MASK) + 69: uint256(uint128(LeftRightUnsigned.unwrap(self)) + right) 87: LeftRightSigned.wrap( 88: (LeftRightSigned.unwrap(self) & LEFT_HALF_BIT_MASK_INT) + 89: (int256(int128(LeftRightSigned.unwrap(self)) + right) & RIGHT_HALF_BIT_MASK) 102: return uint128(LeftRightUnsigned.unwrap(self) >> 128); 109: return int128(LeftRightSigned.unwrap(self) >> 128); 126: return LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(self) + (uint256(left) << 128)); 126: return LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(self) + (uint256(left) << 128)); 136: return LeftRightSigned.wrap(LeftRightSigned.unwrap(self) + (int256(left) << 128)); 136: return LeftRightSigned.wrap(LeftRightSigned.unwrap(self) + (int256(left) << 128)); 155: z = LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(x) + LeftRightUnsigned.unwrap(y)); 155: z = LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(x) + LeftRightUnsigned.unwrap(y)); 155: z = LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(x) + LeftRightUnsigned.unwrap(y)); 161: LeftRightUnsigned.unwrap(z) < LeftRightUnsigned.unwrap(x) || 161: LeftRightUnsigned.unwrap(z) < LeftRightUnsigned.unwrap(x) || 162: (uint128(LeftRightUnsigned.unwrap(z)) < uint128(LeftRightUnsigned.unwrap(x))) 162: (uint128(LeftRightUnsigned.unwrap(z)) < uint128(LeftRightUnsigned.unwrap(x))) 163: ) revert Errors.UnderOverFlow(); 178: z = LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(x) - LeftRightUnsigned.unwrap(y)); 178: z = LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(x) - LeftRightUnsigned.unwrap(y)); 178: z = LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(x) - LeftRightUnsigned.unwrap(y)); 184: LeftRightUnsigned.unwrap(z) > LeftRightUnsigned.unwrap(x) || 184: LeftRightUnsigned.unwrap(z) > LeftRightUnsigned.unwrap(x) || 185: (uint128(LeftRightUnsigned.unwrap(z)) > uint128(LeftRightUnsigned.unwrap(x))) 185: (uint128(LeftRightUnsigned.unwrap(z)) > uint128(LeftRightUnsigned.unwrap(x))) 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(); 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(
[40, 47, 67, 68, 69, 87, 88, 89, 102, 109, 126, 126, 136, 136, 155, 155, 155, 161, 161, 162, 162, 163, 178, 178, 178, 184, 184, 185, 185, 186, 199, 204, 222, 240, 262, 265, 266, 294, 297]
File: contracts/types/LiquidityChunk.sol 78: LiquidityChunk.wrap( 95: return LiquidityChunk.wrap(LiquidityChunk.unwrap(self) + amount); 95: return LiquidityChunk.wrap(LiquidityChunk.unwrap(self) + amount); 109: LiquidityChunk.wrap( 110: LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232) 126: LiquidityChunk.wrap( 127: LiquidityChunk.unwrap(self) + ((uint256(uint24(_tickUpper))) << 208) 142: LiquidityChunk.wrap(LiquidityChunk.unwrap(self) & CLEAR_TL_MASK).addTickLower( 142: LiquidityChunk.wrap(LiquidityChunk.unwrap(self) & CLEAR_TL_MASK).addTickLower( 159: LiquidityChunk.wrap(LiquidityChunk.unwrap(self) & CLEAR_TU_MASK).addTickUpper( 159: LiquidityChunk.wrap(LiquidityChunk.unwrap(self) & CLEAR_TU_MASK).addTickUpper( 174: return int24(int256(LiquidityChunk.unwrap(self) >> 232)); 183: return int24(int256(LiquidityChunk.unwrap(self) >> 208)); 192: return uint128(LiquidityChunk.unwrap(self));
[78, 95, 95, 109, 110, 126, 127, 142, 142, 159, 159, 174, 183, 192]
File: contracts/types/TokenId.sol 89: return uint64(TokenId.unwrap(self)); 98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16)); 110: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48)) % 2); 120: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 1)) % 128); 130: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 8)) % 2); 140: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 9)) % 2); 150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4); 160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12))); 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); 185: return TokenId.wrap(TokenId.unwrap(self) + _poolId); 185: return TokenId.wrap(TokenId.unwrap(self) + _poolId); 195: return TokenId.wrap(TokenId.unwrap(self) + (uint256(uint24(_tickSpacing)) << 48)); 195: return TokenId.wrap(TokenId.unwrap(self) + (uint256(uint24(_tickSpacing)) << 48)); 212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48))); 212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48))); 228: TokenId.wrap( 229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1)) 246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8))); 246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8))); 262: TokenId.wrap( 263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9)) 280: TokenId.wrap( 281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10)) 298: TokenId.wrap( 299: TokenId.unwrap(self) + 318: TokenId.wrap( 319: TokenId.unwrap(self) + 371: uint256 optionRatios = TokenId.unwrap(self) & OPTION_RATIO_MASK; 393: TokenId.wrap( 394: TokenId.unwrap(self) ^ 420: (legLowerTick, legUpperTick) = PanopticMath.getTicks( 434: uint256 optionRatios = TokenId.unwrap(self) & OPTION_RATIO_MASK; 467: TokenId.wrap( 468: TokenId.unwrap(self) & 473: TokenId.wrap( 474: TokenId.unwrap(self) & 479: TokenId.wrap( 480: TokenId.unwrap(self) & 485: TokenId.wrap( 486: TokenId.unwrap(self) & 501: if (self.optionRatio(0) == 0) revert Errors.InvalidTokenIdParameter(1); 506: uint256 chunkData = (TokenId.unwrap(self) & CHUNK_MASK) >> 64; 512: if ((TokenId.unwrap(self) >> (64 + 48 * i)) != 0) 513: revert Errors.InvalidTokenIdParameter(1); 522: revert Errors.InvalidTokenIdParameter(6); 528: if ((self.width(i) == 0)) revert Errors.InvalidTokenIdParameter(5); 531: (self.strike(i) == Constants.MIN_V3POOL_TICK) || 532: (self.strike(i) == Constants.MAX_V3POOL_TICK) 533: ) revert Errors.InvalidTokenIdParameter(4); //@audit-info what if is > or < tick? 542: revert Errors.InvalidTokenIdParameter(3); 548: ) revert Errors.InvalidTokenIdParameter(3); 561: revert Errors.InvalidTokenIdParameter(4); 567: revert Errors.InvalidTokenIdParameter(5); 582: (int24 rangeDown, int24 rangeUp) = PanopticMath.getRangesFromStrike( 598: revert Errors.NoLegsExercisable();
[89, 98, 110, 120, 130, 140, 150, 160, 171, 185, 185, 195, 195, 212, 212, 228, 229, 246, 246, 262, 263, 280, 281, 298, 299, 318, 319, 371, 393, 394, 420, 434, 467, 468, 473, 474, 479, 480, 485, 486, 501, 506, 512, 513, 522, 528, 531, 532, 533, 542, 548, 561, 567, 582, 598]
</details>Upgradeable
This allows for bugs to be fixed in production, at the expense of significantly increasing centralization.
There are 20 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
[36]
File: contracts/PanopticFactory.sol 27: contract PanopticFactory is Multicall {
[27]
File: contracts/PanopticPool.sol 28: contract PanopticPool is ERC1155Holder, Multicall {
[28]
File: contracts/SemiFungiblePositionManager.sol 72: contract SemiFungiblePositionManager is ERC1155, Multicall {
[72]
File: contracts/libraries/CallbackLib.sol 13: library CallbackLib {
[13]
File: contracts/libraries/Constants.sol 8: library Constants {
[8]
File: contracts/libraries/Errors.sol 7: library Errors {
[7]
File: contracts/libraries/FeesCalc.sol 39: library FeesCalc {
[39]
File: contracts/libraries/InteractionHelper.sol 18: library InteractionHelper {
[18]
File: contracts/libraries/Math.sol 13: library Math {
[13]
File: contracts/libraries/PanopticMath.sol 18: library PanopticMath {
[18]
File: contracts/libraries/SafeTransferLib.sol 12: library SafeTransferLib {
[12]
File: contracts/multicall/Multicall.sol 8: abstract contract Multicall {
[8]
File: contracts/tokens/ERC1155Minimal.sol 11: abstract contract ERC1155 {
[11]
File: contracts/tokens/ERC20Minimal.sol 10: abstract contract ERC20Minimal {
[10]
File: contracts/types/LeftRight.sol 17: library LeftRightLibrary {
[17]
File: contracts/types/LiquidityChunk.sol 53: library LiquidityChunkLibrary {
[53]
File: contracts/types/TokenId.sol 60: library TokenIdLibrary {
[60]
File: contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
[6]
File: contracts/tokens/interfaces/IERC20Partial.sol 11: interface IERC20Partial {
[11]
</details>External protocols should be monitored as such dependencies may introduce vulnerabilities if a vulnerability is found in the external protocol.
There are 19 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/PanopticFactory.sol 9: import {IUniswapV3Factory} from "univ3-core/interfaces/IUniswapV3Factory.sol"; 10: import {IUniswapV3Pool} from "univ3-core/interfaces/IUniswapV3Pool.sol"; 14: import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
File: contracts/PanopticPool.sol 7: import {IUniswapV3Pool} from "univ3-core/interfaces/IUniswapV3Pool.sol"; 9: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
File: contracts/SemiFungiblePositionManager.sol 5: import {IUniswapV3Factory} from "univ3-core/interfaces/IUniswapV3Factory.sol"; 6: import {IUniswapV3Pool} from "univ3-core/interfaces/IUniswapV3Pool.sol";
File: contracts/libraries/CallbackLib.sol 5: import {IUniswapV3Factory} from "univ3-core/interfaces/IUniswapV3Factory.sol"; 33: IUniswapV3Factory factory, 38: revert Errors.InvalidUniswapCallback();
File: contracts/libraries/Errors.sol 45: error InvalidUniswapCallback(); 111: error UniswapPoolNotInitialized();
File: contracts/libraries/FeesCalc.sol 5: import {IUniswapV3Pool} from "univ3-core/interfaces/IUniswapV3Pool.sol"; 98: IUniswapV3Pool univ3pool, 131: IUniswapV3Pool univ3pool,
File: contracts/libraries/InteractionHelper.sol 6: import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 10: import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
File: contracts/libraries/PanopticMath.sol 6: import {IUniswapV3Pool} from "univ3-core/interfaces/IUniswapV3Pool.sol";
[6]
File: contracts/tokens/ERC1155Minimal.sol 5: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
[5]
</details>Remove debug imports, as they increase the contract size, and they are useful only for local tests.
There is 1 instance of this issue.
File: contracts/PanopticPool.sol 22: import "forge-std/console.sol";
[22]
2**<n> - 1
should be re-written as type(uint<n>).max
Earlier versions of solidity can use uint<n>(-1)
instead. Expressions not including the - 1
can often be re-written to accomodate the change (e.g. by using a >
rather than a >=
, which will also save some gas).
There are 44 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/PanopticPool.sol 166: uint64 internal constant MAX_SPREAD = 9 * (2 ** 32); 166: uint64 internal constant MAX_SPREAD = 9 * (2 ** 32); 1354: Math.mulDiv(uint256(tokenData1.rightSlot()), 2 ** 96, sqrtPriceX96) + 1359: Math.mulDivRoundingUp(uint256(tokenData1.leftSlot()), 2 ** 96, sqrtPriceX96) + 1493: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; 1558: (liquidityChunk.liquidity())) / 2 ** 64 1567: (liquidityChunk.liquidity())) / 2 ** 64 1641: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) 1642: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); 1775: totalLiquidity) / 2 ** 64; 1777: totalLiquidity) / 2 ** 64; 1948: )) + int256(legPremia.rightSlot() * 2 ** 64), 1965: )) + int256(legPremia.leftSlot()) * 2 ** 64,
[166, 166, 1354, 1359, 1493, 1558, 1567, 1641, 1642, 1775, 1777, 1948, 1965]
File: contracts/SemiFungiblePositionManager.sol 388: s_AddrToPoolIdData[univ3pool] = uint256(poolId) + 2 ** 255; 1353: totalLiquidity * 2 ** 64, 1358: totalLiquidity * 2 ** 64,
File: contracts/libraries/Math.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1; 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1; 484: require(2 ** 64 > prod1); 511: prod0 |= prod1 * 2 ** 192; 547: require(2 ** 96 > prod1); 574: prod0 |= prod1 * 2 ** 160; 587: if (mulmod(a, b, 2 ** 96) > 0) { 624: require(2 ** 128 > prod1); 651: prod0 |= prod1 * 2 ** 128; 664: if (mulmod(a, b, 2 ** 128) > 0) { 701: require(2 ** 192 > prod1); 728: prod0 |= prod1 * 2 ** 64;
[15, 15, 484, 511, 547, 574, 587, 624, 651, 664, 701, 728]
File: contracts/libraries/PanopticMath.sol 23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1; 23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1; 512: return Math.mulDiv(amount, 2 ** 192, uint256(sqrtPriceX96) ** 2); 514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 553: .mulDiv(Math.absUint(amount), 2 ** 192, uint256(sqrtPriceX96) ** 2) 560: 2 ** 128, 685: Math.mulDiv128(bonusCross, 2 ** 128 - requiredRatioX128),
[23, 23, 512, 514, 553, 560, 685]
File: contracts/types/TokenId.sol 98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16)); 376: if (optionRatios < 2 ** 64) { 378: } else if (optionRatios < 2 ** 112) { 380: } else if (optionRatios < 2 ** 160) { 382: } else if (optionRatios < 2 ** 208) { 439: if (optionRatios < 2 ** 64) { 441: } else if (optionRatios < 2 ** 112) { 443: } else if (optionRatios < 2 ** 160) { 445: } else if (optionRatios < 2 ** 208) {
[98, 376, 378, 380, 382, 439, 441, 443, 445]
</details>Constants should be defined instead of using magic numbers.
There are 285 instances of this issue.
<details> <summary>Expand findings</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 10_000 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 208: 10_000 ** 3 // @audit 10 234: totalSupply = 10 ** 6; // @audit 6 234: totalSupply = 10 ** 6; // @audit 100 249: _poolFee = fee / 100; // @audit 64 1330: : int64(uint64(poolUtilization >> 64)); // @audit 64 1586: : int64(uint64(poolUtilization >> 64)) // @audit 64 1632: uint64 poolUtilization1 = uint64(poolUtilization >> 64); // @audit 64 1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
[200, 202, 203, 204, 205, 206, 207, 207, 208, 208, 234, 234, 249, 1330, 1586, 1632, 1638]
File: contracts/PanopticPool.sol // @audit 216 310: (uint256(block.timestamp) << 216) + // @audit 0xF590A6F276170D89E9F276170D89E9F276170D89E9000000000000 313: (uint256(0xF590A6F276170D89E9F276170D89E9F276170D89E9000000000000)) + // @audit 24 314: (uint256(uint24(currentTick)) << 24) + // add to slot 4 // @audit 64 371: poolUtilization1 = uint64(balanceData.leftSlot() >> 64); // @audit 4 449: LeftRightSigned[4] memory premiaByLeg, //@audit-info positive premia // @audit 4 450: uint256[2][4] memory premiumAccumulatorsByLeg // @audit 4 686: (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) = SFPM // @audit 64 731: return uint128(uint256(utilization0) + uint128(uint256(utilization1) << 64)); // @audit 4 801: ) internal returns (LeftRightSigned netPaid, LeftRightSigned[4][] memory premiasByLeg) { // @audit 4 802: premiasByLeg = new LeftRightSigned[4][](positionIdList.length); // @audit 4 833: ) internal returns (LeftRightSigned paidAmounts, LeftRightSigned[4] memory premiaByLeg) { // @audit 4 967: LeftRightSigned[4] memory premiaByLeg, // @audit 4 971: (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) = SFPM // @audit 4 1081: LeftRightSigned[4][] memory premiasByLeg; // @audit 10_000 1335: return balanceCross >= Math.unsafeDivRoundingUp(thresholdCross * buffer, 10_000); // @audit 96 1354: Math.mulDiv(uint256(tokenData1.rightSlot()), 2 ** 96, sqrtPriceX96) + // @audit 96 1359: Math.mulDivRoundingUp(uint256(tokenData1.leftSlot()), 2 ** 96, sqrtPriceX96) + // @audit 248 1421: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen(); // @audit 248 1451: _numberOfPositions = (s_positionsHash[user] >> 248); // @audit 32 1493: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; // @audit 4 1519: LeftRightSigned[4] memory premiaByLeg, // @audit 4 1520: uint256[2][4] memory premiumAccumulatorsByLeg // @audit 64 1558: (liquidityChunk.liquidity())) / 2 ** 64 // @audit 64 1567: (liquidityChunk.liquidity())) / 2 ** 64 // @audit 3 1602: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg(); // @audit 64 1641: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) // @audit 64 1642: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); // @audit 4 1674: LeftRightUnsigned[4] memory collectedByLeg, // @audit 64 1775: totalLiquidity) / 2 ** 64; // @audit 64 1777: totalLiquidity) / 2 ** 64; // @audit 4 1842: LeftRightUnsigned[4] memory collectedByLeg, // @audit 4 1845: ) internal returns (LeftRightSigned realizedPremia, LeftRightSigned[4] memory premiaByLeg) { // @audit 4 1847: uint256[2][4] memory premiumAccumulatorsByLeg; // @audit 4 1929: uint256[2][4] memory _premiumAccumulatorsByLeg = premiumAccumulatorsByLeg; // @audit 64 1948: )) + int256(legPremia.rightSlot() * 2 ** 64), // @audit 64 1965: )) + int256(legPremia.leftSlot()) * 2 ** 64,
[310, 313, 314, 371, 449, 450, 686, 731, 801, 802, 833, 967, 971, 1081, 1335, 1354, 1359, 1421, 1451, 1493, 1519, 1520, 1558, 1567, 1602, 1641, 1642, 1674, 1775, 1777, 1842, 1845, 1847, 1929, 1948, 1965]
File: contracts/SemiFungiblePositionManager.sol // @audit 255 388: s_AddrToPoolIdData[univ3pool] = uint256(poolId) + 2 ** 255; // @audit 4 479: returns (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) // @audit 4 512: returns (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalSwapped) // @audit 4 687: ) internal returns (LeftRightUnsigned[4] memory collectedByLeg, LeftRightSigned totalMoved) { // @audit 4 873: LeftRightUnsigned[4] memory collectedByLeg, // @audit 64 1353: totalLiquidity * 2 ** 64, // @audit 64 1358: totalLiquidity * 2 ** 64,
[388, 479, 512, 687, 873, 1353, 1358]
File: contracts/libraries/Math.sol // @audit 0x100000000000000000000000000000000 93: if (x >= 0x100000000000000000000000000000000) { // @audit 128 94: x >>= 128; // @audit 32 95: r += 32; // @audit 0x10000000000000000 97: if (x >= 0x10000000000000000) { // @audit 64 98: x >>= 64; // @audit 16 99: r += 16; // @audit 0x100000000 101: if (x >= 0x100000000) { // @audit 32 102: x >>= 32; // @audit 8 103: r += 8; // @audit 0x10000 105: if (x >= 0x10000) { // @audit 16 106: x >>= 16; // @audit 4 107: r += 4; // @audit 0x100 109: if (x >= 0x100) { // @audit 8 110: x >>= 8; // @audit 0x10 113: if (x >= 0x10) { // @audit 0x1 133: uint256 sqrtR = absTick & 0x1 != 0 // @audit 0xfffcb933bd6fad37aa2d162d1a594001 134: ? 0xfffcb933bd6fad37aa2d162d1a594001 // @audit 0x100000000000000000000000000000000 135: : 0x100000000000000000000000000000000; // @audit 0x2 137: if (absTick & 0x2 != 0) sqrtR = (sqrtR * 0xfff97272373d413259a46990580e213a) >> 128; // @audit 0xfff97272373d413259a46990580e213a 137: if (absTick & 0x2 != 0) sqrtR = (sqrtR * 0xfff97272373d413259a46990580e213a) >> 128; // @audit 128 137: if (absTick & 0x2 != 0) sqrtR = (sqrtR * 0xfff97272373d413259a46990580e213a) >> 128; // @audit 0x4 139: if (absTick & 0x4 != 0) sqrtR = (sqrtR * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; // @audit 0xfff2e50f5f656932ef12357cf3c7fdcc 139: if (absTick & 0x4 != 0) sqrtR = (sqrtR * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; // @audit 128 139: if (absTick & 0x4 != 0) sqrtR = (sqrtR * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; // @audit 0x8 141: if (absTick & 0x8 != 0) sqrtR = (sqrtR * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; // @audit 0xffe5caca7e10e4e61c3624eaa0941cd0 141: if (absTick & 0x8 != 0) sqrtR = (sqrtR * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; // @audit 128 141: if (absTick & 0x8 != 0) sqrtR = (sqrtR * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; // @audit 0x10 143: if (absTick & 0x10 != 0) sqrtR = (sqrtR * 0xffcb9843d60f6159c9db58835c926644) >> 128; // @audit 0xffcb9843d60f6159c9db58835c926644 143: if (absTick & 0x10 != 0) sqrtR = (sqrtR * 0xffcb9843d60f6159c9db58835c926644) >> 128; // @audit 128 143: if (absTick & 0x10 != 0) sqrtR = (sqrtR * 0xffcb9843d60f6159c9db58835c926644) >> 128; // @audit 0x20 145: if (absTick & 0x20 != 0) sqrtR = (sqrtR * 0xff973b41fa98c081472e6896dfb254c0) >> 128; // @audit 0xff973b41fa98c081472e6896dfb254c0 145: if (absTick & 0x20 != 0) sqrtR = (sqrtR * 0xff973b41fa98c081472e6896dfb254c0) >> 128; // @audit 128 145: if (absTick & 0x20 != 0) sqrtR = (sqrtR * 0xff973b41fa98c081472e6896dfb254c0) >> 128; // @audit 0x40 147: if (absTick & 0x40 != 0) sqrtR = (sqrtR * 0xff2ea16466c96a3843ec78b326b52861) >> 128; // @audit 0xff2ea16466c96a3843ec78b326b52861 147: if (absTick & 0x40 != 0) sqrtR = (sqrtR * 0xff2ea16466c96a3843ec78b326b52861) >> 128; // @audit 128 147: if (absTick & 0x40 != 0) sqrtR = (sqrtR * 0xff2ea16466c96a3843ec78b326b52861) >> 128; // @audit 0x80 149: if (absTick & 0x80 != 0) sqrtR = (sqrtR * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; // @audit 0xfe5dee046a99a2a811c461f1969c3053 149: if (absTick & 0x80 != 0) sqrtR = (sqrtR * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; // @audit 128 149: if (absTick & 0x80 != 0) sqrtR = (sqrtR * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; // @audit 0x100 151: if (absTick & 0x100 != 0) sqrtR = (sqrtR * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; // @audit 0xfcbe86c7900a88aedcffc83b479aa3a4 151: if (absTick & 0x100 != 0) sqrtR = (sqrtR * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; // @audit 128 151: if (absTick & 0x100 != 0) sqrtR = (sqrtR * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; // @audit 0x200 153: if (absTick & 0x200 != 0) sqrtR = (sqrtR * 0xf987a7253ac413176f2b074cf7815e54) >> 128; // @audit 0xf987a7253ac413176f2b074cf7815e54 153: if (absTick & 0x200 != 0) sqrtR = (sqrtR * 0xf987a7253ac413176f2b074cf7815e54) >> 128; // @audit 128 153: if (absTick & 0x200 != 0) sqrtR = (sqrtR * 0xf987a7253ac413176f2b074cf7815e54) >> 128; // @audit 0x400 155: if (absTick & 0x400 != 0) sqrtR = (sqrtR * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; // @audit 0xf3392b0822b70005940c7a398e4b70f3 155: if (absTick & 0x400 != 0) sqrtR = (sqrtR * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; // @audit 128 155: if (absTick & 0x400 != 0) sqrtR = (sqrtR * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; // @audit 0x800 157: if (absTick & 0x800 != 0) sqrtR = (sqrtR * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; // @audit 0xe7159475a2c29b7443b29c7fa6e889d9 157: if (absTick & 0x800 != 0) sqrtR = (sqrtR * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; // @audit 128 157: if (absTick & 0x800 != 0) sqrtR = (sqrtR * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; // @audit 0x1000 159: if (absTick & 0x1000 != 0) sqrtR = (sqrtR * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; // @audit 0xd097f3bdfd2022b8845ad8f792aa5825 159: if (absTick & 0x1000 != 0) sqrtR = (sqrtR * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; // @audit 128 159: if (absTick & 0x1000 != 0) sqrtR = (sqrtR * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; // @audit 0x2000 161: if (absTick & 0x2000 != 0) sqrtR = (sqrtR * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; // @audit 0xa9f746462d870fdf8a65dc1f90e061e5 161: if (absTick & 0x2000 != 0) sqrtR = (sqrtR * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; // @audit 128 161: if (absTick & 0x2000 != 0) sqrtR = (sqrtR * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; // @audit 0x4000 163: if (absTick & 0x4000 != 0) sqrtR = (sqrtR * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; // @audit 0x70d869a156d2a1b890bb3df62baf32f7 163: if (absTick & 0x4000 != 0) sqrtR = (sqrtR * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; // @audit 128 163: if (absTick & 0x4000 != 0) sqrtR = (sqrtR * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; // @audit 0x8000 165: if (absTick & 0x8000 != 0) sqrtR = (sqrtR * 0x31be135f97d08fd981231505542fcfa6) >> 128; // @audit 0x31be135f97d08fd981231505542fcfa6 165: if (absTick & 0x8000 != 0) sqrtR = (sqrtR * 0x31be135f97d08fd981231505542fcfa6) >> 128; // @audit 128 165: if (absTick & 0x8000 != 0) sqrtR = (sqrtR * 0x31be135f97d08fd981231505542fcfa6) >> 128; // @audit 0x10000 167: if (absTick & 0x10000 != 0) sqrtR = (sqrtR * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; // @audit 0x9aa508b5b7a84e1c677de54f3e99bc9 167: if (absTick & 0x10000 != 0) sqrtR = (sqrtR * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; // @audit 128 167: if (absTick & 0x10000 != 0) sqrtR = (sqrtR * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; // @audit 0x20000 169: if (absTick & 0x20000 != 0) sqrtR = (sqrtR * 0x5d6af8dedb81196699c329225ee604) >> 128; // @audit 0x5d6af8dedb81196699c329225ee604 169: if (absTick & 0x20000 != 0) sqrtR = (sqrtR * 0x5d6af8dedb81196699c329225ee604) >> 128; // @audit 128 169: if (absTick & 0x20000 != 0) sqrtR = (sqrtR * 0x5d6af8dedb81196699c329225ee604) >> 128; // @audit 0x40000 171: if (absTick & 0x40000 != 0) sqrtR = (sqrtR * 0x2216e584f5fa1ea926041bedfe98) >> 128; // @audit 0x2216e584f5fa1ea926041bedfe98 171: if (absTick & 0x40000 != 0) sqrtR = (sqrtR * 0x2216e584f5fa1ea926041bedfe98) >> 128; // @audit 128 171: if (absTick & 0x40000 != 0) sqrtR = (sqrtR * 0x2216e584f5fa1ea926041bedfe98) >> 128; // @audit 0x80000 173: if (absTick & 0x80000 != 0) sqrtR = (sqrtR * 0x48a170391f7dc42444e8fa2) >> 128; // @audit 0x48a170391f7dc42444e8fa2 173: if (absTick & 0x80000 != 0) sqrtR = (sqrtR * 0x48a170391f7dc42444e8fa2) >> 128; // @audit 128 173: if (absTick & 0x80000 != 0) sqrtR = (sqrtR * 0x48a170391f7dc42444e8fa2) >> 128; // @audit 32 179: return uint160((sqrtR >> 32) + (sqrtR % (1 << 32) == 0 ? 0 : 1)); // @audit 32 179: return uint160((sqrtR >> 32) + (sqrtR % (1 << 32) == 0 ? 0 : 1)); // @audit 96 197: uint256(liquidityChunk.liquidity()) << 96, // @audit 3 414: uint256 inv = (3 * denominator) ^ 2; // @audit 64 484: require(2 ** 64 > prod1); // @audit 192 511: prod0 |= prod1 * 2 ** 192; // @audit 96 547: require(2 ** 96 > prod1); // @audit 160 574: prod0 |= prod1 * 2 ** 160; // @audit 96 587: if (mulmod(a, b, 2 ** 96) > 0) { // @audit 128 624: require(2 ** 128 > prod1); // @audit 128 651: prod0 |= prod1 * 2 ** 128; // @audit 128 664: if (mulmod(a, b, 2 ** 128) > 0) { // @audit 192 701: require(2 ** 192 > prod1); // @audit 64 728: prod0 |= prod1 * 2 ** 64;
[93, 94, 95, 97, 98, 99, 101, 102, 103, 105, 106, 107, 109, 110, 113, 133, 134, 135, 137, 137, 137, 139, 139, 139, 141, 141, 141, 143, 143, 143, 145, 145, 145, 147, 147, 147, 149, 149, 149, 151, 151, 151, 153, 153, 153, 155, 155, 155, 157, 157, 157, 159, 159, 159, 161, 161, 161, 163, 163, 163, 165, 165, 165, 167, 167, 167, 169, 169, 169, 171, 171, 171, 173, 173, 173, 179, 179, 197, 414, 484, 511, 547, 574, 587, 624, 651, 664, 701, 728]
File: contracts/libraries/PanopticMath.sol // @audit 112 50: uint64 poolId = uint64(uint160(univ3pool) >> 112); // @audit 48 51: poolId += uint64(uint24(tickSpacing)) << 48; // @audit 40 77: return addr == address(0) ? 40 : 39 - Math.mostSignificantNibble(uint160(addr)); // @audit 39 77: return addr == address(0) ? 40 : 39 - Math.mostSignificantNibble(uint160(addr)); // @audit 248 107: ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248) // @audit 248 107: ? uint256(updatedHash) + (((existingHash >> 248) + 1) << 248) // @audit 248 108: : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248); // @audit 248 108: : uint256(updatedHash) + (((existingHash >> 248) - 1) << 248); // @audit 192 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + // @audit 3 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + // @audit 3 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + // @audit 8 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + // @audit 24 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + // @audit 192 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / // @audit 3 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / // @audit 4 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / // @audit 8 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / // @audit 24 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / // @audit 216 183: if (block.timestamp >= uint256(uint40(medianData >> 216)) + period) { // @audit 192 200: uint24 orderMap = uint24(medianData >> 192); // @audit 8 207: for (uint8 i; i < 8; ++i) { // @audit 3 209: rank = (orderMap >> (3 * i)) % 8; // @audit 8 209: rank = (orderMap >> (3 * i)) % 8; // @audit 7 211: if (rank == 7) { // @audit 24 217: entry = int24(uint24(medianData >> (rank * 24))); // @audit 3 223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1))); // @audit 216 227: (block.timestamp << 216) + // @audit 192 228: (uint256(newOrderMap) << 192) + // @audit 24 229: uint256(uint192(medianData << 24)) + // @audit 20 242: uint32[] memory secondsAgos = new uint32[](20); // @audit 19 244: int256[] memory twapMeasurement = new int256[](19); // @audit 20 248: for (uint256 i = 0; i < 20; ++i) { // @audit 20 249: secondsAgos[i] = uint32(((i + 1) * twapWindow) / 20); // @audit 19 256: for (uint256 i = 0; i < 19; ++i) { // @audit 20 258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20)) // @audit 10 266: return int24(sortedTicks[10]); //@audit-issue L - median should be (sortedTicks[9] + sortedTicks[10]) / 2 // @audit 192 512: return Math.mulDiv(amount, 2 ** 192, uint256(sqrtPriceX96) ** 2); // @audit 128 514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); // @audit 192 553: .mulDiv(Math.absUint(amount), 2 ** 192, uint256(sqrtPriceX96) ** 2) // @audit 128 560: 2 ** 128, // @audit 128 669: uint256 requiredRatioX128 = (required0 << 128) / (required0 + required1); // @audit 128 685: Math.mulDiv128(bonusCross, 2 ** 128 - requiredRatioX128), // @audit 4 771: LeftRightSigned[4][] memory premiasByLeg, //@audit pos or neg? // @audit 4 862: LeftRightSigned[4][] memory _premiasByLeg = premiasByLeg;
[50, 51, 77, 77, 107, 107, 108, 108, 178, 178, 178, 178, 178, 179, 179, 179, 179, 179, 183, 200, 207, 209, 209, 211, 217, 223, 227, 228, 229, 242, 244, 248, 249, 256, 258, 266, 512, 514, 553, 560, 669, 685, 771, 862]
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
File: contracts/types/LeftRight.sol // @audit 128 102: return uint128(LeftRightUnsigned.unwrap(self) >> 128); // @audit 128 109: return int128(LeftRightSigned.unwrap(self) >> 128); // @audit 128 126: return LeftRightUnsigned.wrap(LeftRightUnsigned.unwrap(self) + (uint256(left) << 128)); // @audit 128 136: return LeftRightSigned.wrap(LeftRightSigned.unwrap(self) + (int256(left) << 128));
File: contracts/types/LiquidityChunk.sol // @audit 232 79: (uint256(uint24(_tickLower)) << 232) + // @audit 208 80: (uint256(uint24(_tickUpper)) << 208) + // @audit 232 110: LiquidityChunk.unwrap(self) + (uint256(uint24(_tickLower)) << 232) // @audit 208 127: LiquidityChunk.unwrap(self) + ((uint256(uint24(_tickUpper))) << 208) // @audit 232 174: return int24(int256(LiquidityChunk.unwrap(self) >> 232)); // @audit 208 183: return int24(int256(LiquidityChunk.unwrap(self) >> 208));
File: contracts/types/TokenId.sol // @audit 48 98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16)); // @audit 16 98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16)); // @audit 64 110: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48)) % 2); // @audit 48 110: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48)) % 2); // @audit 64 120: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 1)) % 128); // @audit 48 120: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 1)) % 128); // @audit 128 120: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 1)) % 128); // @audit 64 130: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 8)) % 2); // @audit 48 130: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 8)) % 2); // @audit 8 130: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 8)) % 2); // @audit 64 140: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 9)) % 2); // @audit 48 140: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 9)) % 2); // @audit 9 140: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 9)) % 2); // @audit 64 150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4); // @audit 48 150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4); // @audit 10 150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4); // @audit 4 150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4); // @audit 64 160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12))); // @audit 48 160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12))); // @audit 12 160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12))); // @audit 64 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); // @audit 48 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); // @audit 36 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); // @audit 4096 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); // @audit 48 195: return TokenId.wrap(TokenId.unwrap(self) + (uint256(uint24(_tickSpacing)) << 48)); // @audit 64 212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48))); // @audit 48 212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48))); // @audit 128 229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1)) // @audit 64 229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1)) // @audit 48 229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1)) // @audit 64 246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8))); // @audit 48 246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8))); // @audit 8 246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8))); // @audit 64 263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9)) // @audit 48 263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9)) // @audit 9 263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9)) // @audit 4 281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10)) // @audit 64 281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10)) // @audit 48 281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10)) // @audit 10 281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10)) // @audit 64 300: uint256((int256(_strike) & BITMASK_INT24) << (64 + legIndex * 48 + 12)) // @audit 48 300: uint256((int256(_strike) & BITMASK_INT24) << (64 + legIndex * 48 + 12)) // @audit 12 300: uint256((int256(_strike) & BITMASK_INT24) << (64 + legIndex * 48 + 12)) // @audit 4096 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36)) // @audit 64 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36)) // @audit 48 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36)) // @audit 36 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36)) // @audit 64 376: if (optionRatios < 2 ** 64) { // @audit 112 378: } else if (optionRatios < 2 ** 112) { // @audit 160 380: } else if (optionRatios < 2 ** 160) { // @audit 208 382: } else if (optionRatios < 2 ** 208) { // @audit 3 383: optionRatios = 3; // @audit 4 385: optionRatios = 4; // @audit 48 395: ((LONG_MASK >> (48 * (4 - optionRatios))) & CLEAR_POOLID_MASK) // @audit 4 395: ((LONG_MASK >> (48 * (4 - optionRatios))) & CLEAR_POOLID_MASK) // @audit 3 406: return self.isLong(0) + self.isLong(1) + self.isLong(2) + self.isLong(3); // @audit 64 439: if (optionRatios < 2 ** 64) { // @audit 112 441: } else if (optionRatios < 2 ** 112) { // @audit 160 443: } else if (optionRatios < 2 ** 160) { // @audit 208 445: } else if (optionRatios < 2 ** 208) { // @audit 3 446: return 3; // @audit 4 448: return 4; // @audit 0xFFFFFFFFFFFF_FFFFFFFFFFFF_FFFFFFFFFFFF_000000000000_FFFFFFFFFFFFFFFF 469: 0xFFFFFFFFFFFF_FFFFFFFFFFFF_FFFFFFFFFFFF_000000000000_FFFFFFFFFFFFFFFF // @audit 0xFFFFFFFFFFFF_FFFFFFFFFFFF_000000000000_FFFFFFFFFFFF_FFFFFFFFFFFFFFFF 475: 0xFFFFFFFFFFFF_FFFFFFFFFFFF_000000000000_FFFFFFFFFFFF_FFFFFFFFFFFFFFFF // @audit 0xFFFFFFFFFFFF_000000000000_FFFFFFFFFFFF_FFFFFFFFFFFF_FFFFFFFFFFFFFFFF 481: 0xFFFFFFFFFFFF_000000000000_FFFFFFFFFFFF_FFFFFFFFFFFF_FFFFFFFFFFFFFFFF // @audit 3 483: if (i == 3) // @audit 0x000000000000_FFFFFFFFFFFF_FFFFFFFFFFFF_FFFFFFFFFFFF_FFFFFFFFFFFFFFFF 487: 0x000000000000_FFFFFFFFFFFF_FFFFFFFFFFFF_FFFFFFFFFFFF_FFFFFFFFFFFFFFFF // @audit 64 506: uint256 chunkData = (TokenId.unwrap(self) & CHUNK_MASK) >> 64; // @audit 4 507: for (uint256 i = 0; i < 4; ++i) { // @audit 64 512: if ((TokenId.unwrap(self) >> (64 + 48 * i)) != 0) // @audit 48 512: if ((TokenId.unwrap(self) >> (64 + 48 * i)) != 0) // @audit 48 521: if (uint48(chunkData >> (48 * i)) == uint48(chunkData >> (48 * j))) { // @audit 48 521: if (uint48(chunkData >> (48 * i)) == uint48(chunkData >> (48 * j))) { // @audit 6 522: revert Errors.InvalidTokenIdParameter(6); // @audit 5 528: if ((self.width(i) == 0)) revert Errors.InvalidTokenIdParameter(5); // @audit 4 533: ) revert Errors.InvalidTokenIdParameter(4); //@audit-info what if is > or < tick? // @audit 3 542: revert Errors.InvalidTokenIdParameter(3); // @audit 3 548: ) revert Errors.InvalidTokenIdParameter(3); // @audit 4 561: revert Errors.InvalidTokenIdParameter(4); // @audit 5 567: revert Errors.InvalidTokenIdParameter(5);
[98, 98, 110, 110, 120, 120, 120, 130, 130, 130, 140, 140, 140, 150, 150, 150, 150, 160, 160, 160, 171, 171, 171, 171, 195, 212, 212, 229, 229, 229, 246, 246, 246, 263, 263, 263, 281, 281, 281, 281, 300, 300, 300, 320, 320, 320, 320, 376, 378, 380, 382, 383, 385, 395, 395, 406, 439, 441, 443, 445, 446, 448, 469, 475, 481, 483, 487, 506, 507, 512, 512, 521, 521, 522, 528, 533, 542, 548, 561, 567]
</details>Assign the expression's parts to intermediate local variables, and check against those instead.
There are 23 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 708: (tokenType == 0 && currentValue1 < oracleValue1) || 709: (tokenType == 1 && currentValue0 < oracleValue0) 1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) { 1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) { 1346: ((atTick >= tickUpper) && (tokenType == 1)) || // strike OTM when price >= upperTick for tokenType=1 1347: ((atTick < tickLower) && (tokenType == 0)) // strike OTM when price < lowerTick for tokenType=0 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
[708-709, 1060, 1060, 1346-1347, 1374-1375]
File: contracts/PanopticFactory.sol 225: if (_owner != address(0) && _owner != msg.sender) revert Errors.NotOwner();
[225]
File: contracts/SemiFungiblePositionManager.sol 632: (LeftRightUnsigned.unwrap(s_accountLiquidity[positionKey_to]) != 0) || 633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0) 788: if ((itm0 != 0) && (itm1 != 0)) {
File: contracts/libraries/PanopticMath.sol 357: tickLower % tickSpacing != 0 || 358: tickUpper % tickSpacing != 0 || 359: tickLower < minTick || 360: tickUpper > maxTick 357: tickLower % tickSpacing != 0 || 358: tickUpper % tickSpacing != 0 || 359: tickLower < minTick || 357: tickLower % tickSpacing != 0 || 358: tickUpper % tickSpacing != 0 ||
File: contracts/tokens/ERC1155Minimal.sol 202: interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 203: interfaceId == 0xd9b67a26; // ERC165 Interface ID for ERC1155 201: return 202: interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 203: interfaceId == 0xd9b67a26; // ERC165 Interface ID for ERC1155
File: contracts/types/LeftRight.sol 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(); 290: bool r_Enabled = !(z_xR == type(uint128).max || z_yR == type(uint128).max); 291: bool l_Enabled = !(z_xL == type(uint128).max || z_yL == type(uint128).max);
File: contracts/types/TokenId.sol 531: (self.strike(i) == Constants.MIN_V3POOL_TICK) || 532: (self.strike(i) == Constants.MAX_V3POOL_TICK) 546: (self.asset(riskPartnerIndex) != self.asset(i)) || 547: (self.optionRatio(riskPartnerIndex) != self.optionRatio(i)) 560: if ((_isLong == isLongP) && (_tokenType == tokenTypeP)) 566: if (((_isLong != isLongP) || _isLong == 1) && (_tokenType != tokenTypeP)) 566: if (((_isLong != isLongP) || _isLong == 1) && (_tokenType != tokenTypeP))
[531-532, 546-547, 560, 566, 566]
</details>Consider splitting long arithmetic calculations into multiple steps to improve the code readability.
There are 53 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 201: TICK_DEVIATION = uint256( 202: 2230 + 203: (12500 * ratioTick) / 204: 10_000 + 205: (7812 * ratioTick ** 2) / 206: 10_000 ** 2 + 207: (6510 * ratioTick ** 3) / 208: 10_000 ** 3 209: ); 402: shares = Math.mulDiv( 403: assets * (DECIMALS - COMMISSION_FEE), 404: totalSupply, 405: totalAssets() * DECIMALS 406: ); 446: return (convertToShares(type(uint104).max) * DECIMALS) / (DECIMALS + COMMISSION_FEE); 795: return 796: min_sell_ratio + 797: ((DECIMALS - min_sell_ratio) * (uint256(utilization) - TARGET_POOL_UTIL)) / 798: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL); 848: return 849: (BUYER_COLLATERAL_RATIO + 850: (BUYER_COLLATERAL_RATIO * (SATURATED_POOL_UTIL - utilization)) / 851: (SATURATED_POOL_UTIL - TARGET_POOL_UTIL)) / 2; // do the division by 2 at the end after all addition and multiplication; b/c y1 = buyCollateralRatio / 2 1119: exchangedAmount += int256( 1120: Math.unsafeDivRoundingUp( 1121: uint256(uint128(shortAmount + longAmount)) * COMMISSION_FEE, 1122: DECIMALS 1123: ) 1124: ); 1408: uint160 scaleFactor = Math.getSqrtRatioAtTick( 1409: (tickUpper - strike) + (strike - tickLower) 1410: );
[201-209, 402-406, 446, 795-798, 848-851, 1119-1124, 1408-1410]
File: contracts/PanopticPool.sol 309: s_miniMedian = 310: (uint256(block.timestamp) << 216) + 311: // magic number which adds (7,5,3,1,0,2,4,6) order and minTick in positions 7, 5, 3 and maxTick in 6, 4, 2 312: // see comment on s_miniMedian initialization for format of this magic number 313: (uint256(0xF590A6F276170D89E9F276170D89E9F276170D89E9000000000000)) + 314: (uint256(uint24(currentTick)) << 24) + // add to slot 4 315: (uint256(uint24(currentTick))); // add to slot 3 1493: effectiveLiquidityFactorX32 = (uint256(totalLiquidity) * 2 ** 32) / netLiquidity; 1551: premiaByLeg[leg] = LeftRightSigned 1552: .wrap(0) 1553: .toRightSlot( 1554: int128( 1555: int256( 1556: ((premiumAccumulatorsByLeg[leg][0] - 1557: premiumAccumulatorLast.rightSlot()) * 1558: (liquidityChunk.liquidity())) / 2 ** 64 1559: ) 1560: ) 1561: ) 1562: .toLeftSlot( 1563: int128( 1564: int256( 1565: ((premiumAccumulatorsByLeg[leg][1] - 1566: premiumAccumulatorLast.leftSlot()) * 1567: (liquidityChunk.liquidity())) / 2 ** 64 1568: ) 1569: ) 1570: ); 1639: LeftRightSigned realizedPremia = LeftRightSigned 1640: .wrap(0) 1641: .toRightSlot(int128(int256((accumulatedPremium.rightSlot() * liquidity) / 2 ** 64))) 1642: .toLeftSlot(int128(int256((accumulatedPremium.leftSlot() * liquidity) / 2 ** 64))); 1731: s_grossPremiumLast[chunkKey] = LeftRightUnsigned 1732: .wrap(0) 1733: .toRightSlot( 1734: uint128( 1735: (grossCurrent[0] * 1736: positionLiquidity + 1737: grossPremiumLast.rightSlot() * 1738: totalLiquidityBefore) / (totalLiquidity) 1739: ) 1740: ) 1741: .toLeftSlot( 1742: uint128( 1743: (grossCurrent[1] * 1744: positionLiquidity + 1745: grossPremiumLast.leftSlot() * 1746: totalLiquidityBefore) / (totalLiquidity) 1747: ) 1748: ); 1774: uint256 accumulated0 = ((premiumAccumulators[0] - grossPremiumLast.rightSlot()) * 1775: totalLiquidity) / 2 ** 64; 1776: uint256 accumulated1 = ((premiumAccumulators[1] - grossPremiumLast.leftSlot()) * 1777: totalLiquidity) / 2 ** 64;
[309-315, 1493, 1551-1570, 1639-1642, 1731-1748, 1774-1775, 1776-1777]
File: contracts/SemiFungiblePositionManager.sol 1368: uint256 numerator = netLiquidity + (removedLiquidity / 2 ** VEGOID); 1389: uint256 numerator = totalLiquidity ** 2 - 1390: totalLiquidity * 1391: removedLiquidity + 1392: ((removedLiquidity ** 2) / 2 ** (VEGOID));
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 758: int256 pivot = arr[uint256(left + (right - left) / 2)];
[418, 419, 420, 421, 422, 423, 758]
File: contracts/libraries/PanopticMath.sol 138: (timestamps[i], tickCumulatives[i], , ) = univ3pool.observations( 139: uint256( 140: (int256(observationIndex) - int256(i * period)) + 141: int256(observationCardinality) 142: ) % observationCardinality 143: ); 149: ticks[i] = 150: (tickCumulatives[i] - tickCumulatives[i + 1]) / 151: int256(timestamps[i] - timestamps[i + 1]); 186: (uint256 timestamp_old, int56 tickCumulative_old, , ) = univ3pool.observations( 187: uint256( 188: int256(observationIndex) - int256(1) + int256(observationCardinality) 189: ) % observationCardinality 190: ); 177: medianTick = 178: (int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 3)) % 8) * 24))) + 179: int24(uint24(medianData >> ((uint24(medianData >> (192 + 3 * 4)) % 8) * 24)))) / 180: 2; 194: lastObservedTick = int24( 195: (tickCumulative_last - tickCumulative_old) / 196: int256(timestamp_last - timestamp_old) 197: ); 209: rank = (orderMap >> (3 * i)) % 8; 223: newOrderMap = newOrderMap + ((rank + 1) << (3 * (i + shift - 1))); 226: updatedMedianData = 227: (block.timestamp << 216) + 228: (uint256(newOrderMap) << 192) + 229: uint256(uint192(medianData << 24)) + 230: uint256(uint24(lastObservedTick)); 249: secondsAgos[i] = uint32(((i + 1) * twapWindow) / 20); 257: twapMeasurement[i] = int24( 258: (tickCumulatives[i] - tickCumulatives[i + 1]) / int56(uint56(twapWindow / 20)) 259: ); 375: return ( 376: (width * tickSpacing) / 2, 377: int24(int256(Math.unsafeDivRoundingUp(uint24(width) * uint24(tickSpacing), 2))) 378: ); 669: uint256 requiredRatioX128 = (required0 << 128) / (required0 + required1);
[138-143, 149-151, 186-190, 177-180, 194-197, 209, 223, 226-230, 249, 257-259, 375-378, 669]
File: contracts/types/LiquidityChunk.sol 77: return 78: LiquidityChunk.wrap( 79: (uint256(uint24(_tickLower)) << 232) + 80: (uint256(uint24(_tickUpper)) << 208) + 81: uint256(amount) 82: );
[77-82]
File: contracts/types/TokenId.sol 98: return int24(uint24((TokenId.unwrap(self) >> 48) % 2 ** 16)); 110: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48)) % 2); 120: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 1)) % 128); 130: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 8)) % 2); 140: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 9)) % 2); 150: return uint256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 10)) % 4); 160: return int24(int256(TokenId.unwrap(self) >> (64 + legIndex * 48 + 12))); 171: return int24(int256((TokenId.unwrap(self) >> (64 + legIndex * 48 + 36)) % 4096)); 211: return 212: TokenId.wrap(TokenId.unwrap(self) + (uint256(_asset % 2) << (64 + legIndex * 48))); 227: return 228: TokenId.wrap( 229: TokenId.unwrap(self) + (uint256(_optionRatio % 128) << (64 + legIndex * 48 + 1)) 230: ); 246: return TokenId.wrap(TokenId.unwrap(self) + ((_isLong % 2) << (64 + legIndex * 48 + 8))); 261: return 262: TokenId.wrap( 263: TokenId.unwrap(self) + (uint256(_tokenType % 2) << (64 + legIndex * 48 + 9)) 264: ); 279: return 280: TokenId.wrap( 281: TokenId.unwrap(self) + (uint256(_riskPartner % 4) << (64 + legIndex * 48 + 10)) 282: ); 297: return 298: TokenId.wrap( 299: TokenId.unwrap(self) + 300: uint256((int256(_strike) & BITMASK_INT24) << (64 + legIndex * 48 + 12)) 301: ); 317: return 318: TokenId.wrap( 319: TokenId.unwrap(self) + 320: (uint256(uint24(_width) % 4096) << (64 + legIndex * 48 + 36)) 321: ); 392: return 393: TokenId.wrap( 394: TokenId.unwrap(self) ^ 395: ((LONG_MASK >> (48 * (4 - optionRatios))) & CLEAR_POOLID_MASK) 396: ); 406: return self.isLong(0) + self.isLong(1) + self.isLong(2) + self.isLong(3);
[98, 110, 120, 130, 140, 150, 160, 171, 211-212, 227-230, 246, 261-264, 279-282, 297-301, 317-321, 392-396, 406]
</details>There are units for seconds, minutes, hours, days, and weeks, and since they're defined, they should be used
There are 3 instances of this issue.
File: contracts/PanopticPool.sol 137: uint256 internal constant MEDIAN_PERIOD = 60; 146: uint256 internal constant FAST_ORACLE_PERIOD = 1; 153: uint256 internal constant SLOW_ORACLE_PERIOD = 5;
This is a best practice that should be followed.
There are 120 instances of this issue.
<details> <summary>Expand findings</summary>File: 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(); 544: if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; 596: if (shares > maxRedeem(owner)) revert Errors.ExceedsMaximumRedemption(); 602: if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; 664: if (positionId.isLong(leg) == 0) continue; 707: if ( 1257: if (tokenId.tokenType(index) != (underlyingIsToken0 ? 0 : 1)) continue; 1345: if ( 1373: if (
[170, 229, 331, 350, 418, 480, 536, 544, 596, 602, 664, 707, 1257, 1345, 1373]
File: contracts/PanopticFactory.sol 151: if (msg.sender != currentOwner) revert Errors.NotOwner(); 182: if (amount0Owed > 0) 189: if (amount1Owed > 0) 221: if (address(bytes20(salt)) != msg.sender) revert Errors.InvalidSalt(); 225: if (_owner != address(0) && _owner != msg.sender) revert Errors.NotOwner(); 228: if (address(v3Pool) == address(0)) revert Errors.UniswapPoolNotInitialized(); 230: if (address(s_getPanopticPool[v3Pool]) != address(0))
[151, 182, 189, 221, 225, 228, 230]
File: contracts/PanopticPool.sol 300: if (address(s_univ3pool) != address(0)) revert Errors.PoolAlreadyInitialized(); 534: if (medianData != 0) s_miniMedian = medianData; 634: if (tokenId.poolId() != SFPM.getPoolId(address(s_univ3pool))) 639: if (LeftRightUnsigned.unwrap(s_positionBalance[msg.sender][tokenId]) != 0) 665: if (medianData != 0) s_miniMedian = medianData; 941: if (!solventAtFast) revert Errors.NotEnoughCollateral(); 944: if (Math.abs(int256(fastOracleTick) - slowOracleTick) > MAX_SLOW_FAST_DELTA) 945: if (!_checkSolvencyAtTick(user, positionIdList, currentTick, slowOracleTick, buffer)) 1036: if (Math.abs(currentTick - twapTick) > MAX_TWAP_DELTA_LIQUIDATION) 1067: if (balanceCross >= thresholdCross) revert Errors.NotMarginCalled(); 1156: if ( 1189: if (touchedId.length != 1) revert Errors.InputListFail(); 1280: if (positionIdListExercisor.length > 0) 1400: if (fingerprintIncomingList != currentHash) revert Errors.InputListFail(); 1421: if ((newHash >> 248) > MAX_POSITIONS) revert Errors.TooManyPositionsOpen(); 1489: if (netLiquidity == 0) return; 1498: if (effectiveLiquidityFactorX32 > uint256(effectiveLiquidityLimitX32)) 1602: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg(); 1871: if (commitLongSettled)
[300, 534, 634, 639, 665, 941, 944, 945, 1036, 1067, 1156, 1189, 1280, 1400, 1421, 1489, 1498, 1602, 1871]
File: contracts/SemiFungiblePositionManager.sol 322: if (s_poolContext[poolId].locked) revert Errors.ReentrantCall(); 355: if (univ3pool == address(0)) revert Errors.UniswapPoolNotInitialized(); 362: if (s_AddrToPoolIdData[univ3pool] != 0) return; 412: if (amount0Owed > 0) 419: if (amount1Owed > 0) 549: if (s_poolContext[TokenId.wrap(id).poolId()].locked) revert Errors.ReentrantCall(); 576: if (s_poolContext[TokenId.wrap(ids[i]).poolId()].locked) revert Errors.ReentrantCall(); 631: if ( 639: if (LeftRightUnsigned.unwrap(fromLiq) != liquidityChunk.liquidity()) //@audit-ok before it checked only the rightSlot 689: if (positionSize == 0) revert Errors.OptionsBalanceZero(); 703: if (univ3pool == IUniswapV3Pool(address(0))) revert Errors.UniswapPoolNotInitialized(); 728: if ((currentTick >= tickLimitHigh) || (currentTick <= tickLimitLow)) //@audit-issue L off by one 834: if (swapAmount == 0) return LeftRightSigned.wrap(0); 940: if (amount0 > uint128(type(int128).max) || amount1 > uint128(type(int128).max))
[322, 355, 362, 412, 419, 549, 576, 631, 639, 689, 703, 728, 834, 940]
File: contracts/libraries/CallbackLib.sol 37: if (factory.getPool(features.token0, features.token1, features.fee) != sender)
[37]
File: contracts/libraries/Math.sol 131: if (absTick > uint256(int256(Constants.MAX_V3POOL_TICK))) revert Errors.InvalidTick(); 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; 176: if (tick > 0) sqrtR = type(uint256).max / sqrtR; 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(); 757: if (i == j) return; 768: if (left < j) quickSort(arr, left, j); 769: if (i < right) quickSort(arr, i, right); 760: while (arr[uint256(i)] < pivot) i++; 761: while (pivot < arr[uint256(j)]) j--;
[131, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 176, 297, 312, 319, 326, 757, 768, 769, 760, 761]
File: contracts/libraries/PanopticMath.sol 356: if ( 479: if (notional == 0 || notional > type(uint128).max) revert Errors.InvalidNotionalValue(); 797: if ( 821: } else if ( 856: if (haircut0 != 0) collateral0.exercise(_liquidatee, 0, 0, 0, int128(haircut0)); 857: if (haircut1 != 0) collateral1.exercise(_liquidatee, 0, 0, 0, int128(haircut1));
[356, 479, 797, 821, 856, 857]
File: contracts/libraries/SafeTransferLib.sol 46: if (!success) revert Errors.TransferFailed(); 76: if (!success) revert Errors.TransferFailed();
File: contracts/tokens/ERC1155Minimal.sol 101: if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized(); 113: if ( 137: if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized(); 164: if ( 223: if (
File: contracts/tokens/ERC20Minimal.sol 85: if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
[85]
File: contracts/types/LeftRight.sol 160: if ( 183: if ( 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();
[160, 183, 199, 204, 222, 240, 262]
File: contracts/types/TokenId.sol 465: if (i == 0) 471: if (i == 1) 477: if (i == 2) 483: if (i == 3) 501: if (self.optionRatio(0) == 0) revert Errors.InvalidTokenIdParameter(1); 512: if ((TokenId.unwrap(self) >> (64 + 48 * i)) != 0) 528: if ((self.width(i) == 0)) revert Errors.InvalidTokenIdParameter(5); 530: if ( 541: if (self.riskPartner(riskPartnerIndex) != i) 545: if ( 560: if ((_isLong == isLongP) && (_tokenType == tokenTypeP)) 566: if (((_isLong != isLongP) || _isLong == 1) && (_tokenType != tokenTypeP)) 592: if (self.isLong(i) == 1) return; // validated
[465, 471, 477, 483, 501, 512, 528, 530, 541, 545, 560, 566, 592]
</details>Consider grouping all the system constants under a single file. This finding shows only the first constant for each file, for brevity.
There are 10 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 70: string internal constant TICKER_PREFIX = "po";
[70]
File: contracts/PanopticFactory.sol 83: uint256 internal constant FULL_RANGE_LIQUIDITY_AMOUNT_WETH = 0.1 ether;
[83]
File: contracts/PanopticPool.sol 104: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1;
[104]
File: contracts/SemiFungiblePositionManager.sol 125: bool internal constant MINT = false;
[125]
File: contracts/libraries/Constants.sol 10: uint256 internal constant FP96 = 0x1000000000000000000000000;
[10]
File: contracts/libraries/Math.sol 15: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
[15]
File: contracts/libraries/PanopticMath.sol 23: uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
[23]
File: contracts/types/LeftRight.sol 22: uint256 internal constant LEFT_HALF_BIT_MASK = 23: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000;
[22-23]
File: contracts/types/LiquidityChunk.sol 55: uint256 internal constant CLEAR_TL_MASK = 56: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;
[55-56]
File: contracts/types/TokenId.sol 62: uint256 internal constant LONG_MASK = 63: 0x100_000000000100_000000000100_000000000100_0000000000000000;
[62-63]
</details>Use a more recent version of Solidity: it's safer to use at least the version 0.8.19 to get the latest bugfixes and features when deploying on L2.
There are 20 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/PanopticFactory.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/PanopticPool.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/SemiFungiblePositionManager.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/libraries/CallbackLib.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/Constants.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/Errors.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/FeesCalc.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/InteractionHelper.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/Math.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/PanopticMath.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/libraries/SafeTransferLib.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/multicall/Multicall.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/tokens/ERC1155Minimal.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/ERC20Minimal.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/types/LeftRight.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/types/LiquidityChunk.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/types/TokenId.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/interfaces/IDonorNFT.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/interfaces/IERC20Partial.sol 2: pragma solidity ^0.8.0;
[2]
</details>Locking the pragma helps avoid accidental deploys with an outdated compiler version that may introduce bugs and unexpected vulnerabilities.
Floating pragma is meant to be used for libraries and contracts that have external users and need backward compatibility.
There are 9 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/PanopticFactory.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/PanopticPool.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/SemiFungiblePositionManager.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/multicall/Multicall.sol 2: pragma solidity ^0.8.18;
[2]
File: contracts/tokens/ERC1155Minimal.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/ERC20Minimal.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/interfaces/IDonorNFT.sol 2: pragma solidity ^0.8.0;
[2]
File: contracts/tokens/interfaces/IERC20Partial.sol 2: pragma solidity ^0.8.0;
[2]
</details>Like the zero-address check, an empty bytes assignment might be undesiderable, but the following functions don't have it.
There is 1 instance of this issue.
File: contracts/SemiFungiblePositionManager.sol // @audit positionKey 1111: function _updateStoredPremia( 1112: bytes32 positionKey, 1113: LeftRightUnsigned currentLiquidity, 1114: LeftRightUnsigned collectedAmounts //@audit-ok was signed
abi.encodePacked
instead of bytes.concat
Starting from version 0.8.4
, the recommended approach for appending bytes is to use bytes.concat
instead of abi.encodePacked
.
There are 5 instances of this issue.
File: contracts/PanopticPool.sol 463: abi.encodePacked( 464: tokenId.strike(leg), 465: tokenId.width(leg), 466: tokenId.tokenType(leg) 467: ) //@audit-info collision between two legs? (isLong or riskPartner) 1649: abi.encodePacked( 1650: tokenId.strike(legIndex), 1651: tokenId.width(legIndex), 1652: tokenId.tokenType(legIndex) 1653: ) 1680: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg)) 1862: abi.encodePacked(tokenId.strike(leg), tokenId.width(leg), tokenId.tokenType(leg))
[463-467, 1649-1653, 1680, 1862]
File: contracts/libraries/PanopticMath.sol 879: abi.encodePacked( 880: tokenId.strike(0), 881: tokenId.width(0), 882: tokenId.tokenType(0) 883: )
[879-883]
interface
All external
/public
functions should override an interface
. This is useful to make sure that the whole API is extracted.
There are 85 instances of this issue.
<details> <summary>Expand findings</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) { 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( 894: function delegate(address delegatee, uint256 assets) external onlyPanopticPool { 903: function refund(address delegatee, uint256 assets) external onlyPanopticPool { 911: function revoke( 975: function refund(address refunder, address refundee, int256 assets) external onlyPanopticPool { 995: function takeCommissionAddData( 1043: function exercise( 1141: function getAccountMarginDetails(
[221, 277, 289, 303, 310, 361, 370, 379, 386, 392, 399, 417, 444, 453, 477, 507, 518, 531, 572, 581, 591, 650, 864, 894, 903, 911, 975, 995, 1043, 1141]
File: contracts/PanopticFactory.sol 135: function initialize(address _owner) public { 148: function transferOwnership(address newOwner) external { 160: function owner() external view returns (address) { 173: function uniswapV3MintCallback( 211: function deployNewPool( 291: function minePoolAddress( 421: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
[135, 148, 160, 173, 211, 291, 421]
File: contracts/PanopticPool.sol 292: function startPool( 339: function assertPriceWithinBounds(uint160 sqrtLowerBound, uint160 sqrtUpperBound) external view { 353: function optionPositionBalance( 382: function calculateAccumulatedFeesBatch( 411: function calculatePortfolioValue( 523: function pokeMedian() external { 548: function mintOptions( 570: function burnOptions( 587: function burnOptions( 1018: function liquidate( 1180: function forceExercise( 1431: function univ3pool() external view returns (IUniswapV3Pool) { 1437: function collateralToken0() external view returns (CollateralTracker collateralToken) { 1443: function collateralToken1() external view returns (CollateralTracker) { 1450: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) { 1593: function settleLongPremium(
[292, 339, 353, 382, 411, 523, 548, 570, 587, 1018, 1180, 1431, 1437, 1443, 1450, 1593]
File: contracts/SemiFungiblePositionManager.sol 350: function initializeAMMPool(address token0, address token1, uint24 fee) external { 402: function uniswapV3MintCallback( 435: function uniswapV3SwapCallback( 471: function burnTokenizedPosition( 504: function mintTokenizedPosition( 1422: function getAccountLiquidity( 1450: function getAccountPremium( 1536: function getAccountFeesBase( 1556: function getUniswapV3PoolFromId( 1567: function getPoolId(address univ3pool) external view returns (uint64 poolId) {
[350, 402, 435, 471, 504, 1422, 1450, 1536, 1556, 1567]
File: contracts/libraries/FeesCalc.sol 46: function getPortfolioValue( 97: function calculateAMMSwapFees(
File: contracts/libraries/InteractionHelper.sol 25: function doApprovals( 49: function computeName( 92: function computeSymbol( 108: function computeDecimals(address token) external view returns (uint8) {
File: contracts/libraries/PanopticMath.sol 75: function numberOfLeadingHexZeros(address addr) external pure returns (uint256) { 125: function computeMedianObservedPrice( 168: function computeInternalMedian( 241: function twapFilter(IUniswapV3Pool univ3pool, uint32 twapWindow) external view returns (int24) { 651: function getLiquidationBonus( 768: function haircutPremia( 917: function getRefundAmounts(
[75, 125, 168, 241, 651, 768, 917]
File: contracts/multicall/Multicall.sol 12: function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
[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) {
</details>File: contracts/tokens/ERC20Minimal.sol 50: function approve(address spender, uint256 amount) public returns (bool) { 62: function transfer(address to, uint256 amount) public virtual returns (bool) { 82: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
require
/revert
without any messageIf a transaction reverts, it can be confusing to debug if there aren't any messages. Add a descriptive message to all require
/revert
statements.
There are 9 instances of this issue.
File: contracts/libraries/Math.sol 361: require(denominator > 0); 370: require(denominator > prod1); 448: require(result < type(uint256).max); 484: require(2 ** 64 > prod1); 547: require(2 ** 96 > prod1); 588: require(result < type(uint256).max); 624: require(2 ** 128 > prod1); 665: require(result < type(uint256).max); 701: require(2 ** 192 > prod1);
[361, 370, 448, 484, 547, 588, 624, 665, 701]
else
block is not requiredConsider moving the logic outside the else
block, and then removing it (it's not required, as the function is returning a value). There will be one less level of nesting, which will improve the quality of the codebase.
There are 5 instances of this issue.
File: contracts/SemiFungiblePositionManager.sol 1517: } else { 1518: // Extract the account liquidity for a given uniswap pool, owner, token type, and ticks 1519: acctPremia = isLong == 1 1520: ? s_accountPremiumOwed[positionKey] 1521: : s_accountPremiumGross[positionKey]; 1522: }
File: contracts/libraries/PanopticMath.sol 327: } else { 328: return Math.getLiquidityForAmount1(tickLower, tickUpper, amount); 329: } 430: } else { 431: return ( 432: tokenData1.rightSlot() + convert0to1(tokenData0.rightSlot(), sqrtPriceX96), 433: tokenData1.leftSlot() + convert0to1(tokenData0.leftSlot(), sqrtPriceX96) 434: ); 435: } 496: } else { 497: return Math.mulDiv128(amount, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 498: } 513: } else { 514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 515: }
[327-329, 430-435, 496-498, 513-515]
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 4 instances of this issue.
File: contracts/PanopticPool.sol // @audit consider merging s_positionBalance, s_positionsHash 259: mapping(address account => mapping(TokenId tokenId => LeftRightUnsigned balanceAndUtilizations)) 260: internal s_positionBalance; 273: mapping(address account => uint256 positionsHash) internal s_positionsHash;
[259-260]
File: contracts/SemiFungiblePositionManager.sol // @audit consider merging s_accountLiquidity, s_accountFeesBase 177: mapping(bytes32 positionKey => LeftRightUnsigned removedAndNetLiquidity) 178: internal s_accountLiquidity; 295: mapping(bytes32 positionKey => LeftRightSigned baseFees0And1) internal s_accountFeesBase; // @audit consider merging s_accountPremiumOwed, s_accountPremiumGross 287: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumOwed; 289: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumGross;
File: contracts/tokens/ERC20Minimal.sol // @audit consider merging balanceOf, allowance 36: mapping(address account => uint256 balance) public balanceOf; 40: mapping(address owner => mapping(address spender => uint256 allowance)) public allowance;
[36]
import
identifierIt is better to use import {<identifier>} from "<file.sol>"
instead of import "<file.sol>"
to improve readability and speed up the compilation time.
There is 1 instance of this issue.
File: contracts/PanopticPool.sol 22: import "forge-std/console.sol";
[22]
The contract's interface should be imported first, followed by each of the interfaces it uses, followed by all other files. The examples below do not follow this layout.
There are 4 instances of this issue.
File: contracts/CollateralTracker.sol 7: import {ERC20Minimal} from "@tokens/ERC20Minimal.sol";
[7]
File: contracts/PanopticFactory.sol 12: import {Multicall} from "@multicall/Multicall.sol";
[12]
File: contracts/PanopticPool.sol 9: import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
[9]
File: contracts/SemiFungiblePositionManager.sol 9: import {Multicall} from "@multicall/Multicall.sol";
[9]
Consider using bitshifts instead of a bitmap if the value is a power of 2 and the variable is constant, to improve the code readability.
There is 1 instance of this issue.
File: contracts/libraries/Constants.sol 10: uint256 internal constant FP96 = 0x1000000000000000000000000;
[10]
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 42 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 178: constructor( 179: uint256 _commissionFee, 180: uint256 _sellerCollateralRatio, 181: uint256 _buyerCollateralRatio, 182: int256 _forceExerciseCost, 183: uint256 _targetPoolUtilization, 184: uint256 _saturatedPoolUtilization, 185: uint256 _ITMSpreadMultiplier 221: function startToken( 222: bool underlyingIsToken0, 223: address token0, 224: address token1, 225: uint24 fee, 226: PanopticPool panopticPool 650: function exerciseCost( 651: int24 currentTick, 652: int24 oracleTick, 653: TokenId positionId, 654: uint128 positionBalance, 655: LeftRightSigned longAmounts 1043: function exercise( 1044: address optionOwner, 1045: int128 longAmount, 1046: int128 shortAmount, 1047: int128 swappedAmount, 1048: int128 realizedPremium 1278: function _getRequiredCollateralSingleLeg( 1279: TokenId tokenId, 1280: uint256 index, 1281: uint128 positionSize, 1282: int24 atTick, 1283: uint128 poolUtilization 1311: function _getRequiredCollateralSingleLegNoPartner( 1312: TokenId tokenId, 1313: uint256 index, 1314: uint128 positionSize, 1315: int24 atTick, 1316: uint128 poolUtilization 1439: function _getRequiredCollateralSingleLegPartner( 1440: TokenId tokenId, 1441: uint256 index, 1442: uint128 positionSize, 1443: int24 atTick, 1444: uint128 poolUtilization 1510: function _computeSpread( 1511: TokenId tokenId, 1512: uint128 positionSize, 1513: uint256 index, 1514: uint256 partnerIndex, 1515: uint128 poolUtilization 1600: function _computeStrangle( 1601: TokenId tokenId, 1602: uint256 index, 1603: uint128 positionSize, 1604: int24 atTick, 1605: uint128 poolUtilization
[178-185, 221-226, 650-655, 1043-1048, 1278-1283, 1311-1316, 1439-1444, 1510-1515, 1600-1605]
File: contracts/PanopticFactory.sol 116: constructor( 117: address _WETH9, 118: SemiFungiblePositionManager _SFPM, 119: IUniswapV3Factory _univ3Factory, 120: IDonorNFT _donorNFT, 121: address _poolReference, 122: address _collateralReference
[116-122]
File: contracts/PanopticPool.sol 292: function startPool( 293: IUniswapV3Pool _univ3pool, 294: address token0, 295: address token1, 296: CollateralTracker collateralTracker0, 297: CollateralTracker collateralTracker1 430: function _calculateAccumulatedPremia( //@audit-info positive premia??? 431: address user, 432: TokenId[] calldata positionIdList, 433: bool computeAllPremia, 434: bool includePendingPremium, 435: int24 atTick 548: function mintOptions( 549: TokenId[] calldata positionIdList, 550: uint128 positionSize, 551: uint64 effectiveLiquidityLimitX32, 552: int24 tickLimitLow, 553: int24 tickLimitHigh 615: function _mintOptions( 616: TokenId[] calldata positionIdList, 617: uint128 positionSize, 618: uint64 effectiveLiquidityLimitX32, 619: int24 tickLimitLow, 620: int24 tickLimitHigh 795: function _burnAllOptionsFrom( 796: address owner, 797: int24 tickLimitLow, 798: int24 tickLimitHigh, 799: bool commitLongSettled, 800: TokenId[] calldata positionIdList 827: function _burnOptions( 828: bool commitLongSettled, 829: TokenId tokenId, 830: address owner, 831: int24 tickLimitLow, 832: int24 tickLimitHigh 956: function _burnAndHandleExercise( 957: bool commitLongSettled, 958: int24 tickLimitLow, 959: int24 tickLimitHigh, 960: TokenId tokenId, 961: uint128 positionSize, 962: address owner 1296: function _checkSolvencyAtTick( 1297: address account, 1298: TokenId[] calldata positionIdList, 1299: int24 currentTick, 1300: int24 atTick, 1301: uint256 buffer 1471: function _checkLiquiditySpread( 1472: TokenId tokenId, 1473: uint256 leg, 1474: int24 tickLower, 1475: int24 tickUpper, 1476: uint64 effectiveLiquidityLimitX32 1509: function _getPremia( 1510: TokenId tokenId, 1511: uint128 positionSize, 1512: address owner, 1513: bool computeAllPremia, 1514: int24 atTick 1763: function _getAvailablePremium( 1764: uint256 totalLiquidity, 1765: LeftRightUnsigned settledTokens, 1766: LeftRightUnsigned grossPremiumLast, 1767: LeftRightUnsigned premiumOwed, 1768: uint256[2] memory premiumAccumulators 1839: function _updateSettlementPostBurn( 1840: address owner, 1841: TokenId tokenId, 1842: LeftRightUnsigned[4] memory collectedByLeg, 1843: uint128 positionSize, 1844: bool commitLongSettled
[292-297, 430-435, 548-553, 615-620, 795-800, 827-832, 956-962, 1296-1301, 1471-1476, 1509-1514, 1763-1768, 1839-1844]
File: contracts/SemiFungiblePositionManager.sol 540: function safeTransferFrom( //@audit-info new function 541: address from, 542: address to, 543: uint256 id, 544: uint256 amount, 545: bytes calldata data //@audit-issue L - allows zero address transfer? 566: function safeBatchTransferFrom( 567: address from, 568: address to, 569: uint256[] calldata ids, 570: uint256[] calldata amounts, 571: bytes calldata data 681: function _validateAndForwardToAMM( 682: TokenId tokenId, 683: uint128 positionSize, 684: int24 tickLimitLow, 685: int24 tickLimitHigh, 686: bool isBurn 959: function _createLegInAMM( 960: IUniswapV3Pool univ3pool, 961: TokenId tokenId, 962: uint256 leg, 963: LiquidityChunk liquidityChunk, 964: bool isBurn 1256: function _collectAndWritePositionData( 1257: LiquidityChunk liquidityChunk, 1258: IUniswapV3Pool univ3pool, 1259: LeftRightUnsigned currentLiquidity, 1260: bytes32 positionKey, 1261: LeftRightSigned movedInLeg, 1262: uint256 isLong 1422: function getAccountLiquidity( 1423: address univ3pool, 1424: address owner, 1425: uint256 tokenType, 1426: int24 tickLower, 1427: int24 tickUpper 1450: function getAccountPremium( 1451: address univ3pool, 1452: address owner, 1453: uint256 tokenType, 1454: int24 tickLower, 1455: int24 tickUpper, 1456: int24 atTick, 1457: uint256 isLong 1536: function getAccountFeesBase( 1537: address univ3pool, 1538: address owner, 1539: uint256 tokenType, 1540: int24 tickLower, 1541: int24 tickUpper
[540-545, 566-571, 681-686, 959-964, 1256-1262, 1422-1427, 1450-1457, 1536-1541]
File: contracts/libraries/FeesCalc.sol 97: function calculateAMMSwapFees( 98: IUniswapV3Pool univ3pool, 99: int24 currentTick, 100: int24 tickLower, 101: int24 tickUpper, 102: uint128 liquidity
[97-102]
File: contracts/libraries/InteractionHelper.sol 25: function doApprovals( 26: SemiFungiblePositionManager sfpm, 27: CollateralTracker ct0, 28: CollateralTracker ct1, 29: address token0, 30: address token1 49: function computeName( 50: address token0, 51: address token1, 52: bool isToken0, 53: uint24 fee, 54: string memory prefix
File: contracts/libraries/PanopticMath.sol 125: function computeMedianObservedPrice( 126: IUniswapV3Pool univ3pool, 127: uint256 observationIndex, 128: uint256 observationCardinality, 129: uint256 cardinality, 130: uint256 period 168: function computeInternalMedian( 169: uint256 observationIndex, 170: uint256 observationCardinality, 171: uint256 period, 172: uint256 medianData, 173: IUniswapV3Pool univ3pool 651: function getLiquidationBonus( 652: LeftRightUnsigned tokenData0, 653: LeftRightUnsigned tokenData1, 654: uint160 sqrtPriceX96Twap, 655: uint160 sqrtPriceX96Final, 656: LeftRightSigned netExchanged, 657: LeftRightSigned premia 768: function haircutPremia( 769: address liquidatee, 770: TokenId[] memory positionIdList, 771: LeftRightSigned[4][] memory premiasByLeg, //@audit pos or neg? 772: LeftRightSigned collateralRemaining, 773: CollateralTracker collateral0, 774: CollateralTracker collateral1, 775: uint160 sqrtPriceX96Final, 776: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens 917: function getRefundAmounts( 918: address refunder, 919: LeftRightSigned refundValues, 920: int24 atTick, 921: CollateralTracker collateral0, 922: CollateralTracker collateral1
[125-130, 168-173, 651-657, 768-776, 917-922]
File: contracts/tokens/ERC1155Minimal.sol 94: function safeTransferFrom( 95: address from, 96: address to, 97: uint256 id, 98: uint256 amount, 99: bytes calldata data 130: function safeBatchTransferFrom( 131: address from, 132: address to, 133: uint256[] calldata ids, 134: uint256[] calldata amounts, 135: bytes calldata data
File: contracts/types/TokenId.sol 336: function addLeg( 337: TokenId self, 338: uint256 legIndex, 339: uint256 _optionRatio, 340: uint256 _asset, 341: uint256 _isLong, 342: uint256 _tokenType, 343: uint256 _riskPartner, 344: int24 _strike, 345: int24 _width
[336-345]
File: contracts/tokens/interfaces/IDonorNFT.sol 13: function issueNFT( 14: address deployer, 15: PanopticPool newPoolContract, 16: address token0, 17: address token1, 18: uint24 fee
[13-18]
</details>msg.sender
parameterThe following functions are missing some parameters when emitting an event: when dealing with a source address which uses the value of msg.sender
, the msg.sender
value should be specified in every event.
Otherwise, a contract or web page listening to events cannot react to connected users: basically, those events cannot be used properly.
There are 9 instances of this issue.
File: contracts/PanopticFactory.sol 155: emit OwnershipTransferred(currentOwner, newOwner); 269: emit PoolDeployed( 270: newPoolContract, 271: v3Pool, 272: collateralTracker0, 273: collateralTracker1, 274: amount0, 275: amount1 276: );
File: contracts/PanopticPool.sol 854: emit OptionBurnt(owner, positionSize, tokenId, premiaOwed); 1660: emit PremiumSettled(owner, tokenId, realizedPremia);
File: contracts/SemiFungiblePositionManager.sol 390: emit PoolInitialized(univ3pool, poolId);
[390]
File: contracts/tokens/ERC20Minimal.sol 95: emit Transfer(from, to, amount); 113: emit Transfer(from, to, amount); 131: emit Transfer(address(0), to, amount); 146: emit Transfer(from, address(0), amount);
Events are generally emitted when sensitive changes are made to the contracts.
However, some are missing important parameters, as they should include both the new value and old value where possible.
There are 2 instances of this issue.
File: contracts/PanopticPool.sol 1660: emit PremiumSettled(owner, tokenId, realizedPremia);
[1660]
File: contracts/tokens/ERC1155Minimal.sol 84: emit ApprovalForAll(msg.sender, operator, approved);
[84]
If a reentrancy occurs, some events may be emitted in an unexpected order, and this may be a problem if a third party expects a specific order for these events. Ensure that events follow the best practice of CEI.
There are 8 instances of this issue.
File: contracts/PanopticFactory.sol // @audit collateralTracker0.startToken(true, token0, token1, fee, newPoolContract) 269: emit PoolDeployed( 270: newPoolContract, 271: v3Pool, 272: collateralTracker0, 273: collateralTracker1, 274: amount0, 275: amount1 276: );
[269-276]
File: contracts/PanopticPool.sol // @audit tokenId.poolId() 667: emit OptionMinted(msg.sender, positionSize, tokenId, poolUtilizations); // @audit s_positionBalance[owner][tokenId].rightSlot() 854: emit OptionBurnt(owner, positionSize, tokenId, premiaOwed); // @audit s_univ3pool.slot0() 1171: emit AccountLiquidated(msg.sender, liquidatee, bonusAmounts); // @audit s_positionBalance[account][touchedId[0]].rightSlot() 1283: emit ForcedExercised(msg.sender, account, touchedId[0], exerciseFees); // @audit tokenId.isLong(legIndex) 1660: emit PremiumSettled(owner, tokenId, realizedPremia);
File: contracts/SemiFungiblePositionManager.sol // @audit tokenId.poolId() 485: emit TokenizedPositionBurnt(msg.sender, tokenId, positionSize); // @audit tokenId.poolId() 517: emit TokenizedPositionMinted(msg.sender, tokenId, positionSize);
A duplicated function name in the same contract might have problems with automated auditing tools, so it should be avoided. Consider always using a different name for functions to improve the readability of the code.
There are 15 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol // @audit found also on line 864 894: function delegate(address delegatee, uint256 assets) external onlyPanopticPool { // @audit found also on line 903 975: function refund(address refunder, address refundee, int256 assets) external onlyPanopticPool {
File: contracts/PanopticPool.sol // @audit found also on line 570 587: function burnOptions(
[587]
File: contracts/libraries/Math.sol // @audit found also on line 41 49: function min(int256 a, int256 b) internal pure returns (int256) { // @audit found also on line 57 65: function max(int256 a, int256 b) internal pure returns (int256) { // @audit found also on line 311 318: function toInt128(int256 toCast) internal pure returns (int128 downcastedInt) {
File: contracts/libraries/PanopticMath.sol // @audit found also on line 419 445: function convertCollateralData( // @audit found also on line 490 524: function convert0to1(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) { // @audit found also on line 507 547: function convert1to0(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) {
</details>File: contracts/types/LeftRight.sol // @audit found also on line 39 46: function rightSlot(LeftRightSigned self) internal pure returns (int128) { // @audit found also on line 59 78: function toRightSlot( // @audit found also on line 101 108: function leftSlot(LeftRightSigned self) internal pure returns (int128) { // @audit found also on line 121 134: function toLeftSlot(LeftRightSigned self, int128 left) internal pure returns (LeftRightSigned) { // @audit found also on lines 148, 194 214: function add(LeftRightSigned x, LeftRightSigned y) internal pure returns (LeftRightSigned z) { // @audit found also on line 171 232: function sub(LeftRightSigned x, LeftRightSigned y) internal pure returns (LeftRightSigned z) {
It is unusual to have external calls in modifiers, and doing so will make reviewers more likely to miss important external interactions. Consider moving the external call to an internal function, and calling that function from the modifier.
There is 1 instance of this issue.
File: contracts/CollateralTracker.sol 170: if (msg.sender != address(s_panopticPool)) revert Errors.NotPanopticPool();
[170]
error
without detailsConsider adding some parameters to the error to indicate which user or values caused the failure.
There are 34 instances of this issue.
<details> <summary>Expand findings</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();
[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();
constant
/immutable
variablesVariables which are not constants or immutable should not be declared in all uppercase.
There are 2 instances of this issue.
File: contracts/PanopticFactory.sol 117: address _WETH9, 118: SemiFungiblePositionManager _SFPM,
This is useful to avoid doing some typo bugs.
There are 119 instances of this issue.
<details> <summary>Expand findings</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) || 709: (tokenType == 1 && currentValue0 < oracleValue0) 1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) { 1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) { 1060: if ((intrinsicValue != 0) && ((shortAmount != 0) || (longAmount != 0))) { 1107: if (intrinsicValue != 0) { 1325: uint128 amountMoved = tokenType == 0 ? amountsMoved.rightSlot() : amountsMoved.leftSlot(); 1328: int64 utilization = tokenType == 0 1339: if (isLong == 0) { 1346: ((atTick >= tickUpper) && (tokenType == 1)) || // strike OTM when price >= upperTick for tokenType=1 1347: ((atTick < tickLower) && (tokenType == 0)) // strike OTM when price < lowerTick for tokenType=0 1362: uint160 ratio = tokenType == 1 // tokenType 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 1451: if (isLong == 1) { 1479: if (isLong == 0) { 1488: } else if (isLong == 1) { 1544: if (tokenType == 0) { 1559: if (tokenType == 1) { 1582: tokenType == 0 ? movedRight : movedLeft, 1584: tokenType == 0 1637: uint128(uint64(-int64(poolUtilization0 == 0 ? 1 : poolUtilization0))) + 1638: (uint128(uint64(-int64(poolUtilization1 == 0 ? 1 : poolUtilization1))) << 64);
[331, 350, 512, 575, 664, 708, 709, 1060, 1060, 1060, 1107, 1325, 1328, 1339, 1346, 1347, 1362, 1374, 1375, 1451, 1479, 1488, 1544, 1559, 1582, 1584, 1637, 1638]
File: contracts/PanopticPool.sol 461: if (tokenId.isLong(leg) == 0 && !includePendingPremium) { 534: if (medianData != 0) s_miniMedian = medianData; 639: if (LeftRightUnsigned.unwrap(s_positionBalance[msg.sender][tokenId]) != 0) 665: if (medianData != 0) s_miniMedian = medianData; 769: if (isLong == 1) { 866: if (tokenId.isLong(leg) == 0) { 1189: if (touchedId.length != 1) revert Errors.InputListFail(); 1489: if (netLiquidity == 0) return; 1526: if ((isLong == 1) || computeAllPremia) { 1572: if (isLong == 1) { 1602: if (tokenId.isLong(legIndex) == 0 || legIndex > 3) revert Errors.NotALongLeg(); 1685: if (tokenId.isLong(leg) == 0) { 1786: (accumulated0 == 0 ? type(uint256).max : accumulated0), 1795: (accumulated1 == 0 ? type(uint256).max : accumulated1), 1868: if (LeftRightSigned.unwrap(legPremia) != 0) { 1870: if (tokenId.isLong(leg) == 1) { 1934: s_grossPremiumLast[chunkKey] = totalLiquidity != 0
[461, 534, 639, 665, 769, 866, 1189, 1489, 1526, 1572, 1602, 1685, 1786, 1795, 1868, 1870, 1934]
File: contracts/SemiFungiblePositionManager.sol 362: if (s_AddrToPoolIdData[univ3pool] != 0) return; 632: (LeftRightUnsigned.unwrap(s_accountLiquidity[positionKey_to]) != 0) || 633: (LeftRightSigned.unwrap(s_accountFeesBase[positionKey_to]) != 0) 689: if (positionSize == 0) revert Errors.OptionsBalanceZero(); 718: if ((LeftRightSigned.unwrap(itmAmounts) != 0)) { 788: if ((itm0 != 0) && (itm1 != 0)) { 788: if ((itm0 != 0) && (itm1 != 0)) { 824: } else if (itm0 != 0) { 834: if (swapAmount == 0) return LeftRightSigned.wrap(0); 1000: if (isLong == 0) { 1067: moved = isLong == 0 1074: if (tokenType == 1) { 1079: if (tokenType == 0) { 1276: if (isLong == 1) { 1280: if (LeftRightSigned.unwrap(amountToCollect) != 0) { 1470: if (netLiquidity != 0) { 1515: acctPremia = isLong == 1 ? premiumOwed : premiumGross; 1519: acctPremia = isLong == 1
[362, 632, 633, 689, 718, 788, 788, 824, 834, 1000, 1067, 1074, 1079, 1276, 1280, 1470, 1515, 1519]
File: contracts/libraries/FeesCalc.sol 67: if (tokenId.isLong(leg) == 0) {
[67]
File: contracts/libraries/Math.sol 133: uint256 sqrtR = absTick & 0x1 != 0 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) {
[133, 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 211: if (rank == 7) { 325: if (tokenId.asset(legIndex) == 0) { 357: tickLower % tickSpacing != 0 || 358: tickUpper % tickSpacing != 0 || 425: if (tokenType == 0) { 475: uint256 notional = asset == 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) { 785: if (tokenId.isLong(leg) == 1) { 856: if (haircut0 != 0) collateral0.exercise(_liquidatee, 0, 0, 0, int128(haircut0)); 857: if (haircut1 != 0) collateral1.exercise(_liquidatee, 0, 0, 0, int128(haircut1)); 864: if (tokenId.isLong(leg) == 1) {
[211, 325, 357, 358, 425, 475, 479, 584, 615, 618, 785, 856, 857, 864]
File: contracts/tokens/ERC1155Minimal.sol 112: if (to.code.length != 0) { 163: if (to.code.length != 0) { 202: interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 203: interfaceId == 0xd9b67a26; // ERC165 Interface ID for ERC1155 222: if (to.code.length != 0) {
File: contracts/types/TokenId.sol 465: if (i == 0) 471: if (i == 1) 477: if (i == 2) 483: if (i == 3) 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); 566: if (((_isLong != isLongP) || _isLong == 1) && (_tokenType != tokenTypeP)) 592: if (self.isLong(i) == 1) return; // validated
[465, 471, 477, 483, 501, 508, 512, 528, 566, 592]
</details>delete
instead of 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 are 5 instances of this issue.
File: contracts/SemiFungiblePositionManager.sol 332: s_poolContext[poolId].locked = false;
[332]
File: contracts/libraries/PanopticMath.sol 220: below = false; 850: collateralDelta0 = 0; 851: collateralDelta1 = 0;
File: contracts/types/TokenId.sol 377: optionRatios = 0;
[377]
address(this)
Consider preventing a contract's tokens from being transferred to the contract itself.
There are 3 instances of this issue.
File: contracts/CollateralTracker.sol 323: function transfer( 324: address recipient, 325: uint256 amount 326: ) public override(ERC20Minimal) returns (bool) { 327: // make sure the caller does not have any open option positions 328: // if they do: we don't want them sending panoptic pool shares to others 329: // since that's like reducing collateral 330: 331: if (s_panopticPool.numberOfPositions(msg.sender) != 0) revert Errors.PositionCountNotZero(); 332: 333: return ERC20Minimal.transfer(recipient, amount); 334: } 341: function transferFrom( 342: address from, 343: address to, 344: uint256 amount 345: ) public override(ERC20Minimal) returns (bool) { 346: // make sure the caller does not have any open option positions 347: // if they do: we don't want them sending panoptic pool shares to others 348: // as this would reduce their amount of collateral against the opened positions 349: 350: if (s_panopticPool.numberOfPositions(from) != 0) revert Errors.PositionCountNotZero(); 351: 352: return ERC20Minimal.transferFrom(from, to, amount); 353: }
File: contracts/SemiFungiblePositionManager.sol 540: function safeTransferFrom( //@audit-info new function 541: address from, 542: address to, 543: uint256 id, 544: uint256 amount, 545: bytes calldata data //@audit-issue L - allows zero address transfer? 546: ) public override { //@audit-ok has callback on transfer 547: // we don't need to reentrancy lock on transfers, but we can't allow transfers for a pool during mint/burn with a reentrant call 548: // so just check if there is an active reentrancy lock for the relevant pool on the token we're transferring 549: if (s_poolContext[TokenId.wrap(id).poolId()].locked) revert Errors.ReentrantCall(); 550: 551: // update the position data 552: registerTokenTransfer(from, to, TokenId.wrap(id), amount); 553: 554: // transfer the token (note that all state updates are completed before reentrancy is possible through onReceived callbacks) 555: super.safeTransferFrom(from, to, id, amount, data); 556: }
[540-556]
if
/else
when appropriateThe if
/else
statement can be written in a shorthand way using the ternary operator, as it increases readability and reduces the number of lines of code.
There are 3 instances of this issue.
File: contracts/libraries/PanopticMath.sol 325: if (tokenId.asset(legIndex) == 0) { 326: return Math.getLiquidityForAmount0(tickLower, tickUpper, amount); 327: } else { 328: return Math.getLiquidityForAmount1(tickLower, tickUpper, amount); 329: } 494: if (sqrtPriceX96 < type(uint128).max) { 495: return Math.mulDiv192(amount, uint256(sqrtPriceX96) ** 2); 496: } else { 497: return Math.mulDiv128(amount, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 498: } 511: if (sqrtPriceX96 < type(uint128).max) { 512: return Math.mulDiv(amount, 2 ** 192, uint256(sqrtPriceX96) ** 2); 513: } else { 514: return Math.mulDiv(amount, 2 ** 128, Math.mulDiv64(sqrtPriceX96, sqrtPriceX96)); 515: }
Using named returns makes the code more self-documenting, makes it easier to fill out NatSpec, and in some cases can save gas. The cases below are where there currently is at most one return statement, which is ideal for named returns.
There are 103 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 289: function name() external view returns (string memory) { 303: function symbol() external view returns (string memory) { 310: function decimals() external view returns (uint8) { 323: function transfer( 324: address recipient, 325: uint256 amount 326: ) public override(ERC20Minimal) returns (bool) { 341: function transferFrom( 342: address from, 343: address to, 344: uint256 amount 345: ) public override(ERC20Minimal) returns (bool) { 1043: function exercise( 1044: address optionOwner, 1045: int128 longAmount, 1046: int128 shortAmount, 1047: int128 swappedAmount, 1048: int128 realizedPremium 1049: ) external onlyPanopticPool returns (int128) {
[289, 303, 310, 323-326, 341-345, 1043-1049]
File: contracts/PanopticFactory.sol 160: function owner() external view returns (address) { 336: function _mintFullRange( 337: IUniswapV3Pool v3Pool, 338: address token0, 339: address token1, 340: uint24 fee 341: ) internal returns (uint256, uint256) { 421: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
File: contracts/PanopticPool.sol 500: function _getSlippageLimits( 501: int24 tickLimitLow, 502: int24 tickLimitHigh 503: ) internal pure returns (int24, int24) { 678: function _mintInSFPMAndUpdateCollateral( 679: TokenId tokenId, 680: uint128 positionSize, 681: int24 tickLimitLow, 682: int24 tickLimitHigh 683: ) internal returns (uint128) { 707: function _payCommissionAndWriteData( 708: TokenId tokenId, 709: uint128 positionSize, 710: LeftRightSigned totalSwapped 711: ) internal returns (uint128) { 1296: function _checkSolvencyAtTick( 1297: address account, 1298: TokenId[] calldata positionIdList, 1299: int24 currentTick, 1300: int24 atTick, 1301: uint256 buffer 1302: ) internal view returns (bool) { 1431: function univ3pool() external view returns (IUniswapV3Pool) { 1443: function collateralToken1() external view returns (CollateralTracker) { 1763: function _getAvailablePremium( 1764: uint256 totalLiquidity, 1765: LeftRightUnsigned settledTokens, 1766: LeftRightUnsigned grossPremiumLast, 1767: LeftRightUnsigned premiumOwed, 1768: uint256[2] memory premiumAccumulators 1769: ) internal pure returns (LeftRightUnsigned) {
[500-503, 678-683, 707-711, 1296-1302, 1431, 1443, 1763-1769]
File: contracts/SemiFungiblePositionManager.sol 1450: function getAccountPremium( 1451: address univ3pool, 1452: address owner, 1453: uint256 tokenType, 1454: int24 tickLower, 1455: int24 tickUpper, 1456: int24 atTick, 1457: uint256 isLong 1458: ) external view returns (uint128, uint128) {
File: contracts/libraries/FeesCalc.sol 97: function calculateAMMSwapFees( 98: IUniswapV3Pool univ3pool, 99: int24 currentTick, 100: int24 tickLower, 101: int24 tickUpper, 102: uint128 liquidity 103: ) public view returns (LeftRightSigned) {
[97-103]
File: contracts/libraries/InteractionHelper.sol 49: function computeName( 50: address token0, 51: address token1, 52: bool isToken0, 53: uint24 fee, 54: string memory prefix 55: ) external view returns (string memory) { 92: function computeSymbol( 93: address token, 94: string memory prefix 95: ) external view returns (string memory) { 108: function computeDecimals(address token) external view returns (uint8) {
File: contracts/libraries/Math.sol 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) { 73: function abs(int256 x) internal pure returns (int256) { 81: function absUint(int256 x) internal pure returns (uint256) { 128: function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160) { //@audit-ok 191: function getAmount0ForLiquidity(LiquidityChunk liquidityChunk) internal pure returns (uint256) { 207: function getAmount1ForLiquidity(LiquidityChunk liquidityChunk) internal pure returns (uint256) { 241: function getLiquidityForAmount0( //@audit-info price is new: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/LiquidityAmounts.sol#L23 242: int24 tickLower, 243: int24 tickUpper, 244: uint256 amount0 245: ) internal pure returns (LiquidityChunk) { 271: function getLiquidityForAmount1( 272: int24 tickLower, 273: int24 tickUpper, 274: uint256 amount1 275: ) internal pure returns (LiquidityChunk) { 325: function toInt256(uint256 toCast) internal pure returns (int256) { 458: function mulDiv64(uint256 a, uint256 b) internal pure returns (uint256) { 521: function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256) { 598: function mulDiv128(uint256 a, uint256 b) internal pure returns (uint256) { 675: function mulDiv192(uint256 a, uint256 b) internal pure returns (uint256) { 776: function sort(int256[] memory data) internal pure returns (int256[] memory) {
[25, 33, 41, 49, 57, 65, 73, 81, 128, 191, 207, 241-245, 271-275, 325, 458, 521, 598, 675, 776]
File: contracts/libraries/PanopticMath.sol 47: function getPoolId(address univ3pool) internal view returns (uint64) { 59: function incrementPoolPattern(uint64 poolId) internal pure returns (uint64) { 75: function numberOfLeadingHexZeros(address addr) external pure returns (uint256) { 92: function updatePositionsHash( 93: uint256 existingHash, 94: TokenId tokenId, 95: bool addFlag 96: ) internal pure returns (uint256) { 125: function computeMedianObservedPrice( 126: IUniswapV3Pool univ3pool, 127: uint256 observationIndex, 128: uint256 observationCardinality, 129: uint256 cardinality, 130: uint256 period 131: ) external view returns (int24) { 241: function twapFilter(IUniswapV3Pool univ3pool, uint32 twapWindow) external view returns (int24) { 289: function getLiquidityChunk( 290: TokenId tokenId, 291: uint256 legIndex, 292: uint128 positionSize 293: ) internal pure returns (LiquidityChunk) { 371: function getRangesFromStrike( 372: int24 width, 373: int24 tickSpacing 374: ) internal pure returns (int24, int24) { 419: function convertCollateralData( 420: LeftRightUnsigned tokenData0, 421: LeftRightUnsigned tokenData1, 422: uint256 tokenType, 423: uint160 sqrtPriceX96 424: ) internal pure returns (uint256, uint256) { 445: function convertCollateralData( 446: LeftRightUnsigned tokenData0, 447: LeftRightUnsigned tokenData1, 448: uint256 tokenType, 449: int24 tick 450: ) internal pure returns (uint256, uint256) { 468: function convertNotional( 469: uint128 contractSize, 470: int24 tickLower, 471: int24 tickUpper, 472: uint256 asset 473: ) internal pure returns (uint128) { 490: function convert0to1(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) { 507: function convert1to0(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) { 524: function convert0to1(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) { 547: function convert1to0(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) { 574: function getAmountsMoved( 575: TokenId tokenId, 576: uint128 positionSize, 577: uint256 legIndex 578: ) internal pure returns (LeftRightUnsigned) { 768: function haircutPremia( 769: address liquidatee, 770: TokenId[] memory positionIdList, 771: LeftRightSigned[4][] memory premiasByLeg, //@audit pos or neg? 772: LeftRightSigned collateralRemaining, 773: CollateralTracker collateral0, 774: CollateralTracker collateral1, 775: uint160 sqrtPriceX96Final, 776: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) storage settledTokens 777: ) external returns (int256, int256) { 917: function getRefundAmounts( 918: address refunder, 919: LeftRightSigned refundValues, 920: int24 atTick, 921: CollateralTracker collateral0, 922: CollateralTracker collateral1 923: ) external view returns (LeftRightSigned) {
[47, 59, 75, 92-96, 125-131, 241, 289-293, 371-374, 419-424, 445-450, 468-473, 490, 507, 524, 547, 574-578, 768-777, 917-923]
File: contracts/tokens/ERC1155Minimal.sol 200: function supportsInterface(bytes4 interfaceId) public pure returns (bool) {
[200]
File: contracts/tokens/ERC20Minimal.sol 50: function approve(address spender, uint256 amount) public returns (bool) { 62: function transfer(address to, uint256 amount) public virtual returns (bool) { 82: function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
File: contracts/types/LeftRight.sol 39: function rightSlot(LeftRightUnsigned self) internal pure returns (uint128) { 46: function rightSlot(LeftRightSigned self) internal pure returns (int128) { 59: function toRightSlot( 60: LeftRightUnsigned self, 61: uint128 right 62: ) internal pure returns (LeftRightUnsigned) { 78: function toRightSlot( 79: LeftRightSigned self, 80: int128 right 81: ) internal pure returns (LeftRightSigned) { 101: function leftSlot(LeftRightUnsigned self) internal pure returns (uint128) { 108: function leftSlot(LeftRightSigned self) internal pure returns (int128) { 121: function toLeftSlot( 122: LeftRightUnsigned self, 123: uint128 left 124: ) internal pure returns (LeftRightUnsigned) { 134: function toLeftSlot(LeftRightSigned self, int128 left) internal pure returns (LeftRightSigned) { 279: function addCapped( 280: LeftRightUnsigned x, 281: LeftRightUnsigned dx, 282: LeftRightUnsigned y, 283: LeftRightUnsigned dy 284: ) internal pure returns (LeftRightUnsigned, LeftRightUnsigned) {
[39, 46, 59-62, 78-81, 101, 108, 121-124, 134, 279-284]
File: contracts/types/LiquidityChunk.sol 71: function createChunk( 72: int24 _tickLower, 73: int24 _tickUpper, 74: uint128 amount 75: ) internal pure returns (LiquidityChunk) { 90: function addLiquidity( 91: LiquidityChunk self, 92: uint128 amount 93: ) internal pure returns (LiquidityChunk) { 103: function addTickLower( 104: LiquidityChunk self, 105: int24 _tickLower 106: ) internal pure returns (LiquidityChunk) { 119: function addTickUpper( 120: LiquidityChunk self, 121: int24 _tickUpper 122: ) internal pure returns (LiquidityChunk) { 136: function updateTickLower( 137: LiquidityChunk self, 138: int24 _tickLower 139: ) internal pure returns (LiquidityChunk) { 152: function updateTickUpper( 153: LiquidityChunk self, 154: int24 _tickUpper 155: ) internal pure returns (LiquidityChunk) { 172: function tickLower(LiquidityChunk self) internal pure returns (int24) { 181: function tickUpper(LiquidityChunk self) internal pure returns (int24) { 190: function liquidity(LiquidityChunk self) internal pure returns (uint128) {
[71-75, 90-93, 103-106, 119-122, 136-139, 152-155, 172, 181, 190]
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( 206: TokenId self, 207: uint256 _asset, 208: uint256 legIndex 209: ) internal pure returns (TokenId) { 221: function addOptionRatio( 222: TokenId self, 223: uint256 _optionRatio, 224: uint256 legIndex 225: ) internal pure returns (TokenId) { 240: function addIsLong( 241: TokenId self, 242: uint256 _isLong, 243: uint256 legIndex 244: ) internal pure returns (TokenId) { 255: function addTokenType( 256: TokenId self, 257: uint256 _tokenType, 258: uint256 legIndex 259: ) internal pure returns (TokenId) { 273: function addRiskPartner( 274: TokenId self, 275: uint256 _riskPartner, 276: uint256 legIndex 277: ) internal pure returns (TokenId) { 291: function addStrike( 292: TokenId self, 293: int24 _strike, 294: uint256 legIndex 295: ) internal pure returns (TokenId) { 310: function addWidth( 311: TokenId self, 312: int24 _width, 313: uint256 legIndex 314: ) internal pure returns (TokenId) { 366: function flipToBurnToken(TokenId self) internal pure returns (TokenId) { 404: function countLongs(TokenId self) internal pure returns (uint256) { 432: function countLegs(TokenId self) internal pure returns (uint256) { 464: function clearLeg(TokenId self, uint256 i) internal pure returns (TokenId) {
[87, 96, 108, 118, 128, 138, 148, 158, 169, 183, 193, 205-209, 221-225, 240-244, 255-259, 273-277, 291-295, 310-314, 366, 404, 432, 464]
File: contracts/tokens/interfaces/IERC20Partial.sol 16: function balanceOf(address account) external view returns (uint256);
[16]
</details>This is a best practice that should be followed.
Inside each contract, library or interface, use the following order:
There are 6 instances of this issue.
File: contracts/CollateralTracker.sol // @audit event Withdraw found on line 57 70: string internal constant TICKER_PREFIX = "po";
[70]
File: contracts/PanopticFactory.sol // @audit event PoolDeployed found on line 44 64: IUniswapV3Factory internal immutable UNIV3_FACTORY;
[64]
File: contracts/PanopticPool.sol // @audit event OptionMinted found on line 91 104: int24 internal constant MIN_SWAP_TICK = Constants.MIN_V3POOL_TICK + 1;
[104]
File: contracts/SemiFungiblePositionManager.sol // @audit event TokenizedPositionMinted found on line 98 125: bool internal constant MINT = false;
[125]
File: contracts/tokens/ERC1155Minimal.sol // @audit error UnsafeRecipient found on line 58 66: mapping(address account => mapping(uint256 tokenId => uint256 balance)) public balanceOf;
[66]
File: contracts/tokens/ERC20Minimal.sol // @audit event Approval found on line 25 33: uint256 public totalSupply;
[33]
This is a best practice that should be followed.
Functions should be grouped according to their visibility and ordered:
Within a grouping, place the view and pure functions last.
There are 56 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol // @audit transferFrom on line 341, which is public 361: function asset() external view returns (address assetTokenAddress) { // @audit convertToAssets on line 386, which is public 392: function maxDeposit(address) external pure returns (uint256 maxAssets) { // @audit previewDeposit on line 399, which is public 417: function deposit(uint256 assets, address receiver) external returns (uint256 shares) { // @audit previewDeposit on line 399, which is public 444: function maxMint(address) external view returns (uint256 maxShares) { // @audit previewMint on line 453, which is public 477: function mint(uint256 shares, address receiver) external returns (uint256 assets) { // @audit previewWithdraw on line 518, which is public 531: function withdraw( // @audit previewRedeem on line 581, which is public 591: function redeem( // @audit previewRedeem on line 581, which is public 650: function exerciseCost( // @audit _buyCollateralRatio on line 806, which is internal 864: function delegate( // @audit _buyCollateralRatio on line 806, which is internal 894: function delegate(address delegatee, uint256 assets) external onlyPanopticPool { // @audit _buyCollateralRatio on line 806, which is internal 903: function refund(address delegatee, uint256 assets) external onlyPanopticPool { // @audit _buyCollateralRatio on line 806, which is internal 911: function revoke( // @audit _buyCollateralRatio on line 806, which is internal 975: function refund(address refunder, address refundee, int256 assets) external onlyPanopticPool { // @audit _buyCollateralRatio on line 806, which is internal 995: function takeCommissionAddData( // @audit _buyCollateralRatio on line 806, which is internal 1043: function exercise( // @audit _getExchangedAmount on line 1096, which is internal 1141: function getAccountMarginDetails(
[361, 392, 417, 444, 477, 531, 591, 650, 864, 894, 903, 911, 975, 995, 1043, 1141]
File: contracts/PanopticFactory.sol // @audit initialize on line 135, which is public 148: function transferOwnership(address newOwner) external { // @audit initialize on line 135, which is public 160: function owner() external view returns (address) { // @audit initialize on line 135, which is public 173: function uniswapV3MintCallback( // @audit initialize on line 135, which is public 211: function deployNewPool( // @audit initialize on line 135, which is public 291: function minePoolAddress( // @audit _mintFullRange on line 336, which is internal 421: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
[148, 160, 173, 211, 291, 421]
File: contracts/PanopticPool.sol // @audit _getSlippageLimits on line 500, which is internal 523: function pokeMedian() external { // @audit _getSlippageLimits on line 500, which is internal 548: function mintOptions( // @audit _getSlippageLimits on line 500, which is internal 570: function burnOptions( // @audit _getSlippageLimits on line 500, which is internal 587: function burnOptions( // @audit _burnAndHandleExercise on line 956, which is internal 1018: function liquidate( // @audit _burnAndHandleExercise on line 956, which is internal 1180: function forceExercise( // @audit _updatePositionsHash on line 1411, which is internal 1431: function univ3pool() external view returns (IUniswapV3Pool) { // @audit _updatePositionsHash on line 1411, which is internal 1437: function collateralToken0() external view returns (CollateralTracker collateralToken) { // @audit _updatePositionsHash on line 1411, which is internal 1443: function collateralToken1() external view returns (CollateralTracker) { // @audit _updatePositionsHash on line 1411, which is internal 1450: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) { // @audit _getPremia on line 1509, which is internal 1593: function settleLongPremium(
[523, 548, 570, 587, 1018, 1180, 1431, 1437, 1443, 1450, 1593]
File: contracts/SemiFungiblePositionManager.sol // @audit endReentrancyLock on line 330, which is internal 341: constructor(IUniswapV3Factory _factory) { // @audit endReentrancyLock on line 330, which is internal 350: function initializeAMMPool(address token0, address token1, uint24 fee) external { // @audit endReentrancyLock on line 330, which is internal 402: function uniswapV3MintCallback( // @audit endReentrancyLock on line 330, which is internal 435: function uniswapV3SwapCallback( // @audit endReentrancyLock on line 330, which is internal 471: function burnTokenizedPosition( // @audit endReentrancyLock on line 330, which is internal 504: function mintTokenizedPosition( // @audit endReentrancyLock on line 330, which is internal 540: function safeTransferFrom( //@audit-info new function // @audit endReentrancyLock on line 330, which is internal 566: function safeBatchTransferFrom( // @audit _getFeesBase on line 1139, which is private 1186: function _mintLiquidity( // @audit _getFeesBase on line 1139, which is private 1225: function _burnLiquidity( // @audit _getFeesBase on line 1139, which is private 1256: function _collectAndWritePositionData( // @audit _getPremiaDeltas on line 1322, which is private 1422: function getAccountLiquidity( // @audit _getPremiaDeltas on line 1322, which is private 1450: function getAccountPremium( // @audit _getPremiaDeltas on line 1322, which is private 1536: function getAccountFeesBase( // @audit _getPremiaDeltas on line 1322, which is private 1556: function getUniswapV3PoolFromId( // @audit _getPremiaDeltas on line 1322, which is private 1567: function getPoolId(address univ3pool) external view returns (uint64 poolId) {
[341, 350, 402, 435, 471, 504, 540, 566, 1186, 1225, 1256, 1422, 1450, 1536, 1556, 1567]
File: contracts/libraries/PanopticMath.sol // @audit incrementPoolPattern on line 59, which is internal 75: function numberOfLeadingHexZeros(address addr) external pure returns (uint256) { // @audit updatePositionsHash on line 92, which is internal 125: function computeMedianObservedPrice( // @audit updatePositionsHash on line 92, which is internal 168: function computeInternalMedian( // @audit updatePositionsHash on line 92, which is internal 241: function twapFilter(IUniswapV3Pool univ3pool, uint32 twapWindow) external view returns (int24) { // @audit _calculateIOAmounts on line 607, which is internal 651: function getLiquidationBonus( // @audit _calculateIOAmounts on line 607, which is internal 768: function haircutPremia( // @audit _calculateIOAmounts on line 607, which is internal 917: function getRefundAmounts(
[75, 125, 168, 241, 651, 768, 917]
</details>Consider splitting long functions into multiple, smaller functions to improve the code readability.
There are 33 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 650: function exerciseCost( 911: function revoke( 1311: function _getRequiredCollateralSingleLegNoPartner( 1510: function _computeSpread(
File: contracts/PanopticFactory.sol 211: function deployNewPool( 336: function _mintFullRange(
File: contracts/PanopticPool.sol 430: function _calculateAccumulatedPremia( //@audit-info positive premia??? 615: function _mintOptions( 888: function _validateSolvency( 1018: function liquidate( 1180: function forceExercise( 1509: function _getPremia( 1593: function settleLongPremium( 1672: function _updateSettlementPostMint( 1839: function _updateSettlementPostBurn(
[430, 615, 888, 1018, 1180, 1509, 1593, 1672, 1839]
File: contracts/SemiFungiblePositionManager.sol 593: function registerTokenTransfer(address from, address to, TokenId id, uint256 amount) internal { 757: function swapInAMM( 864: function _createPositionInAMM( 959: function _createLegInAMM( 1256: function _collectAndWritePositionData( 1322: function _getPremiaDeltas( 1450: function getAccountPremium(
[593, 757, 864, 959, 1256, 1322, 1450]
File: contracts/libraries/FeesCalc.sol 130: function _getAMMSwapFeesPerLiquidityCollected(
[130]
File: contracts/libraries/Math.sol 128: function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160) { //@audit-ok 340: function mulDiv( 458: function mulDiv64(uint256 a, uint256 b) internal pure returns (uint256) { 521: function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256) { 598: function mulDiv128(uint256 a, uint256 b) internal pure returns (uint256) { 675: function mulDiv192(uint256 a, uint256 b) internal pure returns (uint256) {
[128, 340, 458, 521, 598, 675]
File: contracts/libraries/PanopticMath.sol 168: function computeInternalMedian( 651: function getLiquidationBonus( 768: function haircutPremia(
File: contracts/types/TokenId.sol 500: function validate(TokenId self) internal pure {
[500]
</details>This will decrease the probability of making typos, and the code will be more readable
There are 5 instances of this issue.
File: contracts/libraries/InteractionHelper.sol 64: symbol0 = "???"; 69: symbol1 = "???"; 75: " ", 81: " ", 101: return string.concat(prefix, "???");
Maximum suggested line length is 120 characters according to the documentation.
There are 346 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 25: /// This is represented as an ERC20 share token. A Panoptic pool has 2 tokens, each issued by its own instance of a CollateralTracker. 28: /// @notice This contract uses the ERC4626 standard allowing the minting and burning of "shares" (represented using ERC20 inheritance) in exchange for underlying "assets". 88: /// @dev Whether this is token0 or token1 depends on which collateral token is being tracked in this CollateralTracker instance. 92: /// @dev As each instance is deployed via proxy clone, initial parameters must only be initalized once via startToken(). 111: /// @dev Cached amount of assets accounted to be held by the Panoptic Pool — ignores donations, pending fee payouts, and other untracked balance changes. 135: /// We believe that this will eliminate the impact of the commission fee on the user's decision-making process when closing a position. 199: /// since we're dropping the higher order terms, which are all negative, this will underestimate the number of ticks for a 20% move 213: /// @notice Initialize a new collateral tracker for a specific token corresponding to the Panoptic Pool being created by the factory that called it. 214: /// @dev The factory calls this function to start a new collateral tracking system for the incoming token at 'underlyingToken'. 215: /// The factory will do this for each of the two tokens being tracked. Thus, the collateral tracking system does not track *both* tokens at once. 257: // store whether the current collateral token is token0 (true) or token1 (false; since there's always exactly two tokens it could be) 272: /// @return poolAssets cached amount of assets accounted to be held by the Panoptic Pool - ignores donations, pending fee payouts, and other untracked balance changes. 274: /// @return currentPoolUtilization Packing of the pool utilization (how much funds are in the Panoptic pool versus the AMM pool at the time of minting), 276: /// Where totalAssets is the total tracked assets in the AMM and PanopticPool minus fees and donations to the Panoptic pool. 290: // this logic requires multiple external calls and error handling, so we do it in a delegatecall to a library to save bytecode size 304: // this logic requires multiple external calls and error handling, so we do it in a delegatecall to a library to save bytecode size 311: // this logic requires multiple external calls and error handling, so we do it in a delegatecall to a library to save bytecode size 400: // compute the MEV tax, which is equal to a single payment of the commissionRate on the FINAL (post mev-tax) assets paid 410: /// @notice Deposit underlying tokens (assets) to the Panoptic pool from the LP and mint corresponding amount of shares. 456: // compute the MEV tax, which is equal to a single payment of the commissionRate on the FINAL (post mev-tax) assets paid 635: /// - The cost must be larger when the position is close to being in-range, and should be minimal when it is far from being in range. eg. Exercising a (1000, 1050) 637: /// - The cost is an exponentially decaying function of the distance between the position's strike and the current price 706: // note: the delta for one token will be positive and the other will be negative. This cancels out any moves in their positions 723: // note: we HAVE to start with a negative number as the base exercise cost because when shifting a negative number right by n bits, 725: // this divergence is observed when n (the number of half ranges) is > 10 (ensuring the floor is not zero, but -1 = 1bps at that point) 727: int256 fee = (FORCE_EXERCISE_COST >> (maxNumRangesFromStrike - 1)); // exponential decay of fee based on number of half ranges away from the price 736: /// @notice Get the pool utilization; it is a measure of the ratio of assets in the AMM vs the total assets managed by the pool. 774: /// if utilization is less than zero, this is the calculation for a strangle, which gets 2x the capital efficiency at low pool utilization 814: // aka the buy ratio starts high and drops to a lower value with increased utilization; the sell ratio does the opposite (slope is positive) 820: // this denotes a situation where the median is too far away from the current price, so we need to require fully collateralized positions for safety 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 974: /// @param assets The amount of assets to refund. Positive means a transfer from refunder to refundee, vice versa for negative. 993: /// @param swappedAmount The amount of tokens swapped during creation of the option position (non-zero for options minted ITM). 1005: // constrict premium to only assets not belonging to PLPs (i.e premium paid by sellers or collected from the pool earlier) 1025: // the inflow or outflow of pool assets is defined by the swappedAmount: it includes both the ITM swap amounts and the short/long amounts used to create the position 1026: // however, any intrinsic value is paid for by the users, so we only add the portion that comes from PLPs: the short/long amounts 1095: /// @return exchangedAmount The amount of funds to be exchanged for minting an option (includes commission, swapFee, and intrinsic value). 1137: /// @param positionBalanceArray The list of all historical positions held by the 'optionOwner', stored as [[tokenId, balance/poolUtilizationAtMint], ...]. 1155: /// @param positionBalanceArray the list of all historical positions held by the 'optionOwner', stored as [[tokenId, balance/poolUtilizationAtMint], ...]. 1172: // If premium is negative (ie. user has to pay for their purchased options), add this long premium to the token requirement 1195: /// @notice Get the total required amount of collateral tokens of a user/account across all active positions to stay above the margin requirement. 1196: /// @dev Returns the token amounts required for the entire account with active positions in 'positionIdList' (list of tokenIds). 1198: /// @param positionBalanceArray The list of all historical positions held by the 'optionOwner', stored as [[tokenId, balance/poolUtilizationAtMint], ...]. 1199: /// @return tokenRequired The amount of tokens required to stay above the margin threshold for all active positions of user. 1238: /// @notice Get the required amount of collateral tokens corresponding to a specific single position 'tokenId' at a price 'tick'. 1239: /// The required collateral of an account depends on the price ('tick') in the AMM pool: if in the position's favor less collateral needed, etc. 1270: /// @notice Calculate the required amount of collateral for a single leg 'index' of position 'tokenId' when the leg does not have a risk partner. 1303: /// @notice Calculate the required amount of collateral for leg 'index' of position 'tokenId' when the leg does not have a risk partner. 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. 1425: /// @notice If the two token long-types are different (one is a long, the other a short, e.g.) but the tokenTypes are the same, this is a spread 1427: /// @notice If the two token long-types are the same but the tokenTypes are different (one is a call, the other a put, e.g.), this is a strangle - 1430: /// 1) The difference in notional value at both strikes: abs(strikeLong - strikeShort) or abs(strikeShort - strikeLong) 1432: /// @dev If a position is a strangle, only one leg can be tested at a time which allows us to increase the capital efficiency. 1438: /// @return required the required amount collateral needed for this leg 'index', accounting for what the leg's risk partner is. 1467: /// @notice For a given incoming 'amount' - which is the size of a user position (e.g. opening a position), what is the corresponding required collateral to have. 1468: /// @dev NOTE this does not depend on the price of the AMM pool. This only computes what is needed in response to the current utilization. 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.
[25, 28, 88, 92, 111, 135, 199, 213, 214, 215, 257, 272, 274, 276, 290, 304, 311, 400, 410, 456, 635, 637, 706, 723, 725, 727, 736, 774, 814, 820, 851, 974, 993, 1005, 1025, 1026, 1095, 1137, 1155, 1172, 1195, 1196, 1198, 1199, 1238, 1239, 1270, 1303, 1416, 1424, 1425, 1427, 1430, 1432, 1438, 1467, 1468, 1593]
File: contracts/PanopticFactory.sol 102: /// @notice Mapping from address(UniswapV3Pool) to address(PanopticPool) that stores the address of all deployed Panoptic Pools 257: // If this is not the case, we increase the next cardinality during deployment so the cardinality can catch up over time 286: /// @param salt Salt value ([160-bit deployer address][96-bit nonce]) to start from, useful as a checkpoint across multiple calls 288: /// @param minTargetRarity The minimum target rarity to mine for. The internal loop stops when this is reached *or* when no more iterations 348: // In this case, the `fullRangeLiquidity` will always be an underestimate in respect to the token amounts required to mint.
File: contracts/PanopticPool.sol 37: /// @param bonusAmounts LeftRight encoding for the the bonus paid for token 0 (right slot) and 1 (left slot) from the Panoptic Pool to the liquidator. 62: /// @param settledAmounts LeftRight encoding for the amount of premium settled for token0 (right slot) and token1 (left slot). 88: /// @param poolUtilizations Packing of the pool utilization (how much funds are in the Panoptic pool versus the AMM pool at the time of minting), 90: /// Where totalAssets is the total tracked assets in the AMM and PanopticPool minus fees and donations to the Panoptic pool. 114: /// @dev Only include the share of (settled) premium that is available to collect when calling `_calculateAccumulatedPremia` 132: // This oracle is updated with the last Uniswap observation during `mintOptions` if MEDIAN_PERIOD has elapsed past the last observation 133: // If true, the "slow" oracle price is instead computed on-the-fly from 7 Uniswap observations (spaced 5 observations apart) irrespective of the frequency of `mintOptions` calls 145: /// In this case, if there is an interaction every block, the "fast" oracle can consider 3 consecutive block end prices (min=36 seconds on Ethereum) 152: /// @dev Structured such that the minimum total observation time is 7 minutes on Ethereum (similar to internal median mode) 155: // The maximum allowed delta between the currentTick and the Uniswap TWAP tick during a liquidation (~5% down, ~5.26% up) 160: /// Falls back on the more conservative (less solvent) tick during times of extreme volatility (to ensure the account is always solvent) 171: // multiplier (x10k) for the collateral requirement in the event of a buying power decrease, such as minting or force exercising 238: // collected for every chunk per unit of liquidity (net or short, depending on the isLong value of the specific leg index) 242: /// @dev Per-chunk `last` value that gives the aggregate amount of premium owed to all sellers when multiplied by the total amount of liquidity `totalLiquidity` 254: /// @dev Tracks the amount of liquidity for a user+tokenId (right slot) and the initial pool utilizations when that position was minted (left slot) 268: /// The accumulator also tracks the total number of positions (ie. makes sure the length of the provided positionIdList matches); 271: /// this hash can be cheaply verified on every operation with a user provided positionIdList - and we can use that for operations 279: /// @notice During construction: sets the address of the panoptic factory smart contract and the SemiFungiblePositionMananger (SFPM). 369: // the 64 most significant bits are the utilization of token1, so we can shift the number to the right by 64 to extract it 378: /// @param includePendingPremium true = include premium that is owed to the user but has not yet settled, false = only include premium that is available to collect. 381: /// @return balances A list of balances and pool utilization for each position, of the form [[tokenId0, balances0], [tokenId1, balances1], ...]. 426: /// @param computeAllPremia Whether to compute accumulated premia for all legs held by the user (true), or just owed premia for long legs (false). 427: /// @param includePendingPremium true = include premium that is owed to the user but has not yet settled, false = only include premium that is available to collect. 428: /// @return portfolioPremium The computed premia of the user's positions, where premia contains the accumulated premia for token0 in the right slot and for token1 in the left slot. 429: /// @return balances A list of balances and pool utilization for each position, of the form [[tokenId0, balances0], [tokenId1, balances1], ...]. 481: portfolioPremium = portfolioPremium.add(premiaByLeg[leg]); //@audit-info adds it if is long or includePendingPremium = true 495: /// @notice Disable slippage checks if tickLimitLow == tickLimitHigh and reverses ticks if given in correct order to enable ITM swaps 542: /// @param positionIdList the list of currently held positions by the user, where the newly minted position(token) will be the last element in 'positionIdList'. 544: /// @param effectiveLiquidityLimitX32 Maximum amount of "spread" defined as totalLiquidity/netLiquidity for a new position. 576: _burnOptions(COMMIT_LONG_SETTLED, tokenId, msg.sender, tickLimitLow, tickLimitHigh); //@audit-issue M - why no update median? 609: /// @param positionIdList the list of currently held positions by the user, where the newly minted position(token) will be the last element in 'positionIdList'. 611: /// @param effectiveLiquidityLimitX32 Maximum amount of "spread" defined as totalLiquidity/netLiquidity for a new position. 676: /// @return poolUtilizations Packing of the pool utilization (how much funds are in the Panoptic pool versus the AMM pool) at the time of minting, 704: /// @return poolUtilizations Packing of the pool utilization (how much funds are in the Panoptic pool versus the AMM pool at the time of minting), 706: /// Where totalAssets is the total tracked assets in the AMM and PanopticPool minus fees and donations to the Panoptic pool. 738: /// @param effectiveLiquidityLimitX32 Maximum amount of "spread" defined as totalLiquidity/netLiquidity for a new position 882: /// @notice Falls back to the more conservative tick if the delta between the fast and slow oracle exceeds `MAX_SLOW_FAST_DELTA`. 883: /// @dev Effectively, this means that the users must be solvent at both the fast and slow oracle ticks if one of them is stale to mint or burn options. 887: /// @return medianData If nonzero (enough time has passed since last observation), the updated value for `s_miniMedian` with a new observation 943: // If one of the ticks is too stale, we fall back to the more conservative tick, i.e, the user must be solvent at both the fast and slow oracle ticks. 1013: /// @dev Will revert if liquidated account is solvent at the TWAP tick or if TWAP tick is too far away from the current tick. 1016: /// @param delegations LeftRight amounts of token0 and token1 (token0:token1 right:left) delegated to the liquidatee by the liquidator so the option can be smoothly exercised. 1071: // Works like a transfer, so the liquidator must possess all the tokens they are delegating, resulting in no net supply change 1084: // Do not commit any settled long premium to storage - we will do this after we determine if any long premium must be revoked 1113: // thus, we haircut any premium paid by the liquidatee (converting tokens as necessary) until the protocol loss is covered or the premium is exhausted 1114: // note that the haircutPremia function also commits the settled amounts (adjusted for the haircut) to storage, so it will be called even if there is no haircut 1116: // if premium is haircut from a token that is not in protocol loss, some of the liquidation bonus will be converted into that token 1188: // '_calculateAccumulatedPremia' expects a list of positions to be touched, and this is the only way to pass a single position 1191: // validate the exercisor's position list (the exercisee's list will be evaluated after their position is force exercised) 1290: /// @notice check whether an account is solvent at a given `atTick` with a collateral requirement of `buffer`/10_000 multiplied by the requirement of `positionIdList`. 1340: /// @param tokenData0 Leftright encoded word with balance of token0 in the right slot, and required balance in left slot. 1341: /// @param tokenData1 Leftright encoded word with balance of token1 in the right slot, and required balance in left slot. 1384: // note that if pLength == 0 even if a user has existing position(s) the below will fail b/c the fingerprints will mismatch 1403: /// @notice Updates the hash for all positions owned by an account. This fingerprints the list of all incoming options with a single hash. 1406: /// @dev The positions hash is stored as the XOR of the keccak256 of each tokenId. Updating will XOR the existing hash with the new tokenId. 1407: /// The same update can either add a new tokenId (when minting an option), or remove an existing one (when burning it) - this happens through the XOR. 1410: /// @param addFlag Pass addFlag=true when this is adding a position, needed to ensure the number of positions increases or decreases. 1469: /// @param effectiveLiquidityLimitX32 Maximum amount of "spread" defined as totalLiquidity/netLiquidity for a new position 1497: // the effective liquidity measures how many times more the newly added liquidity is compared to the existing/base liquidity 1506: /// @param computeAllPremia Whether to compute accumulated premia for all legs held by the user (true), or just owed premia for long legs (false). 1548: // if the premium accumulatorLast is higher than current, it means the premium accumulator has overflowed and rolled over at least once 1550: // if there are multiple rollovers or the rollover goes past the last accumulator, rolled over fees will just remain unclaimed 1588: /// @dev Called by sellers on buyers of their chunk to increase the available premium for withdrawal (before closing their position). 1590: /// @param positionIdList Exhaustive list of open positions for the `owners` used for solvency checks where the tokenId to be settled is the last element. 1696: // (grossPremium - adjustedGrossPremiumLast)*updatedTotalLiquidityPostMint/2**64 is equal to (grossPremium - grossPremiumLast)*totalLiquidityBeforeMint/2**64 1759: /// @param grossPremiumLast The `last` values used with `premiumAccumulators` to compute the total premium owed to sellers
[37, 62, 88, 90, 114, 132, 133, 145, 152, 155, 160, 171, 238, 242, 254, 268, 271, 279, 369, 378, 381, 426, 427, 428, 429, 481, 495, 542, 544, 576, 609, 611, 676, 704, 706, 738, 882, 883, 887, 943, 1013, 1016, 1071, 1084, 1113, 1114, 1116, 1188, 1191, 1290, 1340, 1341, 1384, 1403, 1406, 1407, 1410, 1469, 1497, 1506, 1548, 1550, 1588, 1590, 1696, 1759]
File: contracts/SemiFungiblePositionManager.sol 24: // ,. .,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,. ,, 25: // ,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,, 26: // .,,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,, 27: // .,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,, 28: // ,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,, 29: // ,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,, 30: // ,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,,, .,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,, 31: // ,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,, .,,,,,,,,,,,,, 32: // ,,,,,,,,,,,,,. .,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,. 33: // ,,,,,,,,,,,,, ,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,, ,,,,,,,,,,,,, 34: // ,,,,,,,,,,,,, ,,,,,,,,,,,,,,. ,,,,,,,,,,,,,, ,,,,,,,,,,,,, 35: // ,,,,,,,,,,,,, ,,,,,,,,,,,,,, ,,,,,,,,,,,,,, ,,,,,,,,,,,,, 36: // ,,,,,,,,,,,,, ,,,,,,,,,,,,, ,,,,,,,,,,,,,, ,,,,,,,,,,,,. 37: // .,,,,,,,,,,,, .,,,,,,,,,,,,, ,,,,,,,,,,,,, ,,,,,,,,,,,, 38: // ,,,,,,,,,,,, ,,,,,,,,,,,, ,,,,,,,,,,,,, .,,,,,,,,,,,, 39: // ,,,,,,,,,,,, ,,,,,,,,,,,, ,,,,,,,,,,,,. ,,,,,,,,,,,, 40: // ,,,,,,,,,,,, ,,,,,,,,,,,,. █████████ ███████████ ███████████ ██████ ██████ ,,,,,,,,,,,, ,,,,,,,,,,,, 41: // .,,,,,,,,,,,, ,,,,,,,,,,,, ███░░░░░███░░███░░░░░░█░░███░░░░░███░░██████ ██████ .,,,,,,,,,,,, ,,,,,,,,,,,, 42: // ,,,,,,,,,,,, ,,,,,,,,,,,, ░███ ░░░ ░███ █ ░ ░███ ░███ ░███░█████░███ ,,,,,,,,,,,, ,,,,,,,,,,,,. 43: // ,,,,,,,,,,,, ,,,,,,,,,,,, ░░█████████ ░███████ ░██████████ ░███░░███ ░███ .,,,,,,,,,,, ,,,,,,,,,,,. 44: // ,,,,,,,,,,,, ,,,,,,,,,,,, ░░░░░░░░███ ░███░░░█ ░███░░░░░░ ░███ ░░░ ░███ ,,,,,,,,,,,. ,,,,,,,,,,,, 45: // ,,,,,,,,,,,, ,,,,,,,,,,,, ███ ░███ ░███ ░ ░███ ░███ ░███ ,,,,,,,,,,,, ,,,,,,,,,,,, 46: // ,,,,,,,,,,,, ,,,,,,,,,,,, ░░█████████ █████ █████ █████ █████ ,,,,,,,,,,, ,,,,,,,,,,,, 47: // ,,,,,,,,,,,, ,,,,,,,,,,,, ░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ,,,,,,,,,,,, ,,,,,,,,,,,. 48: // ,,,,,,,,,,,, .,,,,,,,,,,,. ,,,,,,,,,,,, ,,,,,,,,,,,, 49: // .,,,,,,,,,,,, ,,,,,,,,,,,, .,,,,,,,,,,,, ,,,,,,,,,,,, 50: // ,,,,,,,,,,,, ,,,,,,,,,,,,, ,,,,,,,,,,,, ,,,,,,,,,,,, 51: // ,,,,,,,,,,,,. ,,,,,,,,,,,,. ,,,,,,,,,,,,. ,,,,,,,,,,,, 52: // ,,,,,,,,,,,, ,,,,,,,,,,,,, ,,,,,,,,,,,,, .,,,,,,,,,,,, 53: // ,,,,,,,,,,,, ,,,,,,,,,,,,, ,,,,,,,,,,,,, .,,,,,,,,,,,, 54: // .,,,,,,,,,,,, ,,,,,,,,,,,,, ,,,,,,,,,,,,,. ,,,,,,,,,,,, 55: // ,,,,,,,,,,,,, ,,,,,,,,,,,,,, .,,,,,,,,,,,,,. ,,,,,,,,,,,, 56: // ,,,,,,,,,,,,, .,,,,,,,,,,,,,, .,,,,,,,,,,,,,, .,,,,,,,,,,,, 57: // ,,,,,,,,,,,,, ,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,. 58: // ,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,, .,,,,,,,,,,,,,,,, ,,,,,,,,,,,,, 59: // .,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,, .,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,, 60: // ,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,, 61: // ,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,, .,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,, 62: // ,,,,,,,,,,,,,,, .,,,,,,,,,,,,,,,,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, .,,,,,,,,,,,,,,. 63: // ,,,,,,,,,,,,,,. ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,,,,,,,,,,, 64: // ,,,,,,,,,, ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, .,,,,,,,,,, 65: // ,,,,,. ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ,,,,,, 132: // The effect of vegoid on the long premium multiplier can be explored here: https://www.desmos.com/calculator/mdeqob2m04 173: /// @dev mapping that stores the liquidity data of keccak256(abi.encodePacked(address poolAddress, address owner, int24 tickLower, int24 tickUpper)) 174: // liquidityData is a LeftRight. The right slot represents the liquidity currently sold (added) in the AMM owned by the user 175: // 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 176: // the reason why it is called "removedLiquidity" is because long options are created by removed liquidity -ie. short selling LP positions 291: /// @dev mapping that stores a LeftRight packing of feesBase of keccak256(abi.encodePacked(address poolAddress, address owner, int24 tickLower, int24 tickUpper)) 292: /// @dev Base fees is stored as int128((feeGrowthInside0LastX128 * liquidity) / 2**128), which allows us to store the accumulated fees as int128 instead of uint256 294: /// feesBase represents the baseline fees collected by the position last time it was updated - this is recalculated every time the position is collected from with the new value 467: /// @param slippageTickLimitLow The lower price slippage limit when minting an ITM position (set to larger than slippageTickLimitHigh for swapping when minting) 468: /// @param slippageTickLimitHigh The higher slippage limit when minting an ITM position (set to lower than slippageTickLimitLow for swapping when minting) 469: /// @return collectedByLeg An array of LeftRight encoded words containing the amount of token0 and token1 collected as fees for each leg 470: /// @return totalSwapped A LeftRight encoded word containing the total amount of token0 and token1 swapped if minting ITM 500: /// @param slippageTickLimitLow The lower price slippage limit when minting an ITM position (set to larger than slippageTickLimitHigh for swapping when minting) 501: /// @param slippageTickLimitHigh The higher slippage limit when minting an ITM position (set to lower than slippageTickLimitLow for swapping when minting) 502: /// @return collectedByLeg An array of LeftRight encoded words containing the amount of token0 and token1 collected as fees for each leg 503: /// @return totalSwapped A LeftRight encoded word containing the total amount of token0 and token1 swapped if minting ITM 547: // we don't need to reentrancy lock on transfers, but we can't allow transfers for a pool during mint/burn with a reentrant call 554: // transfer the token (note that all state updates are completed before reentrancy is possible through onReceived callbacks) 573: // we don't need to reentrancy lock on transfers, but we can't allow transfers for a pool during mint/burn with a reentrant call 588: /// @dev token transfers are only allowed if you transfer your entire liquidity of a given chunk and the recipient has none 660: /// @notice Helper that checks the proposed option position and size and forwards the minting and potential swapping tasks. 665: /// @notice and then forwards the minting/burning/swapping to another internal helper functions which perform the AMM pool actions. 679: /// @return collectedByLeg An array of LeftRight encoded words containing the amount of token0 and token1 collected as fees for each leg 717: // 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 732: /// @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. 733: /// @notice The swapping for ITM options is needed because only one of the tokens are "borrowed" by a user to create the position. 751: /// If we take token0 as an example, we deploy it to the AMM pool and *then* swap to get the right mix of token0 and token1 753: /// 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. 785: // note: upstream users of this function such as the Panoptic Pool should ensure users always compensate for the ITM amount delta 786: // the netting swap is not perfectly accurate, and it is possible for swaps to run out of liquidity, so we do not want to rely on it 792: // note: negative ITM amounts denote a surplus of tokens (burning liquidity), while positive amounts denote a shortage of tokens (minting liquidity) 862: /// @return collectedByLeg An array of LeftRight encoded words containing the amount of token0 and token1 collected as fees for each leg 937: // Ensure upper bound on amount of tokens contained across all legs of the position on any given tick does not exceed a maximum of (2**127-1). 938: // This is the maximum value of the `int128` type we frequently use to hold token amounts, so a given position's size should be guaranteed to 993: // s_accountLiquidity is a LeftRight. The right slot represents the liquidity currently sold (added) in the AMM owned by the user 994: // 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 995: // the reason why it is called "removedLiquidity" is because long options are created by removing -ie.short selling LP positions 1012: // so we seek to move the incoming liquidity chunk *out* of uniswap - but was there sufficient liquidity sitting in uniswap 1122: // (i.e if only token0 (right slot) of the owed premium overflows, then stop accumulating both token0 owed premium and token0 gross premium for the chunk) 1123: // this prevents situations where the owed premium gets out of sync with the gross premium due to one of them overflowing 1133: /// @notice Compute the feesGrowth * liquidity / 2**128 by reading feeGrowthInside0LastX128 and feeGrowthInside1LastX128 from univ3pool.positions. 1138: /// @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 1161: /// @dev here we're converting the value to an int128 even though all values (feeGrowth, liquidity, Q128) are strictly positive. 1162: /// That's because of the way feeGrowthInside works in Uniswap v3, where it can underflow when stored for the first time. 1163: /// This is not a problem in Uniswap v3 because the fees are always calculated by taking the difference of the feeGrowths, 1165: /// So by using int128 instead of uint128, we remove the need to handle extremely large underflows and simply allow it to be negative 1220: /// @notice Burn a chunk of liquidity (`liquidityChunk`) in the Uniswap v3 pool and send to msg.sender; return the amount moved. 1248: /// @notice Helper to collect amounts between msg.sender and Uniswap and also to update the Uniswap fees collected to date from the AMM. 1255: /// @return collectedChunk the incoming amount collected with potentially whatever is collected in this function added to it 1320: /// @return deltaPremiumOwed The extra premium (per liquidity X64) to be added to the owed accumulator for token0 (right) and token1 (left) 1321: /// @return deltaPremiumGross The extra premium (per liquidity X64) to be added to the gross accumulator for token0 (right) and token1 (left) 1335: // explains how we get from the premium per liquidity (calculated here) to the total premia collected and the multiplier 1421: /// @return accountLiquidities The amount of liquidity that has been shorted/added to the Uniswap contract (netLiquidity:removedLiquidity -> rightSlot:leftSlot) 1430: /// @dev tokenType input here is the asset of the positions minted, this avoids put liquidity to be used for call, and vice-versa 1436: /// @notice Return the premium associated with a given position, where Premium is an accumulator of feeGrowth for the touched position. 1437: /// @dev Computes s_accountPremium{isLong ? Owed : Gross}[keccak256(abi.encodePacked(univ3pool, owner, tokenType, tickLower, tickUpper))] 1438: /// @dev if an atTick parameter is provided that is different from type(int24).max, then it will update the premium up to the current 1439: /// @dev block at the provided atTick value. We do this because this may be called immediately after the Uni v3 pool has been touched 1446: /// @param atTick The current tick. Set atTick < type(int24).max = 8388608 to get latest premium up to the current block 1448: /// @return premiumToken0 The amount of premium (per liquidity X64) for token0 = sum (feeGrowthLast0X128) over every block where the position has been touched 1449: /// @return premiumToken1 The amount of premium (per liquidity X64) for token1 = sum (feeGrowthLast0X128) over every block where the position has been touched 1465: // Compute the premium up to the current block (ie. after last touch until now). Do not proceed if atTick == type(int24).max = 8388608 1479: // currentFeesGrowth (calculated from FeesCalc.calculateAMMSwapFeesLiquidityChunk) is (ammFeesCollectedPerLiquidity * liquidityChunk.liquidity()) 1480: // oldFeesGrowth is the last stored update of fee growth within the position range in the past (feeGrowthRange*liquidityChunk.liquidity()) (s_accountFeesBase[positionKey]) 1506: // (i.e if only token0 (right slot) of the owed premium overflows, then stop accumulating both token0 owed premium and token0 gross premium for the chunk) 1507: // this prevents situations where the owed premium gets out of sync with the gross premium due to one of them overflowing
[24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 132, 173, 174, 175, 176, 291, 292, 294, 467, 468, 469, 470, 500, 501, 502, 503, 547, 554, 573, 588, 660, 665, 679, 717, 732, 733, 751, 753, 785, 786, 792, 862, 937, 938, 993, 994, 995, 1012, 1122, 1123, 1133, 1138, 1161, 1162, 1163, 1165, 1220, 1248, 1255, 1320, 1321, 1335, 1421, 1430, 1436, 1437, 1438, 1439, 1446, 1448, 1449, 1465, 1479, 1480, 1506, 1507]
File: contracts/libraries/CallbackLib.sol 11: /// @notice This library provides functions to verify that a callback came from a canonical Uniswap V3 pool with a claimed set of features.
[11]
File: contracts/libraries/Errors.sol 18: /// @notice PanopticPool: the effective liquidity (X32) is greater than min(`MAX_SPREAD`, `USER_PROVIDED_THRESHOLD`) during a long mint or short burn 22: /// @notice CollateralTracker: attempted to withdraw/redeem more than available liquidity, owned shares, or open positions would allow for 25: /// @notice PanopticPool: force exercisee is insolvent - liquidatable accounts are not permitted to open or close positions outside of a liquidation 41: /// @param parameterType poolId=0, ratio=1, tokenType=2, risk_partner=3 , strike=4, width=5, two identical strike/width/tokenType chunks=6 44: /// @notice A mint or swap callback was attempted from an address that did not match the canonical Uniswap V3 pool with the claimed features
File: contracts/libraries/FeesCalc.sol 17: /// @dev Some options positions involve moving liquidity chunks to the AMM/Uniswap. Those chunks can then earn AMM swap fees. 96: /// @return The fees collected from the AMM for each token (LeftRight-packed) with token0 in the right slot and token1 in the left slot
File: contracts/libraries/InteractionHelper.sol 19: /// @notice Function that performs approvals on behalf of the PanopticPool for CollateralTracker and SemiFungiblePositionManager. 41: /// @notice Computes the name of a CollateralTracker based on the token composition and fee of the underlying Uniswap Pool. 42: /// @dev Some tokens do not have proper symbols so error handling is required - this logic takes up significant bytecode size, which is why it is in a library.
File: contracts/libraries/Math.sol 124: /// @dev Implemented using Uniswap's "incorrect" constants. Supplying commented-out real values for an accurate calculation. 241: function getLiquidityForAmount0( //@audit-info price is new: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/LiquidityAmounts.sol#L23 334: /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0. 435: /// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0. 502: // Divide [prod1 prod0] by the factors of two (note that this is just 2**96 since the denominator is a power of 2 itself) 565: // Divide [prod1 prod0] by the factors of two (note that this is just 2**96 since the denominator is a power of 2 itself) 642: // Divide [prod1 prod0] by the factors of two (note that this is just 2**128 since the denominator is a power of 2 itself) 719: // Divide [prod1 prod0] by the factors of two (note that this is just 2**96 since the denominator is a power of 2 itself) 748: /// @notice QuickSort is a sorting algorithm that employs the Divide and Conquer strategy. It selects a pivot element and arranges the given array around
[124, 241, 334, 435, 502, 565, 642, 719, 748]
File: contracts/libraries/PanopticMath.sol 34: /// the 64 bits are the 48 *last* (most significant) bits - and thus corresponds to the *first* 12 hex characters (reading left to right) 35: /// of the Uniswap v3 pool address, with the tickSpacing written in the highest 16 bits (i.e, max tickSpacing is 32768) 86: /// @notice The positions hash contains a single fingerprint of all positions created by an account/user as well as a tally of the positions. 88: /// @param existingHash The existing position hash containing all historical N positions created and the count of the positions 90: /// @param addFlag Whether to mint (add) the tokenId to the count of positions or burn (subtract) it from the count (existingHash >> 248) +/- 1 114: /// @dev Uniswap observations snapshot the closing price of the last block before the first interaction of a given block. 115: /// @dev The maximum frequency of observations is 1 per block, but there is no guarantee that the pool will be observed at every block. 116: /// @dev Each period has a minimum length of blocktime * period, but may be longer if the Uniswap pool is relatively inactive. 117: /// @dev The final price used in the array (of length `cardinality`) is the average of all observations comprising `period` (which is itself a number of observations). 136: // get the last 4 timestamps/tickCumulatives (if observationIndex < cardinality, the index will wrap back from observationCardinality) 147: // use cardinality periods given by cardinality + 1 accumulator observations to compute the last cardinality observed ticks spaced by period 159: /// @notice Takes a packed structure representing a sorted 7-slot ring buffer of ticks and returns the median of those values. 160: /// @dev Also inserts the latest Uniswap observation into the buffer, resorts, and returns if the last entry is at least `period` seconds old. 163: /// @param period The minimum time in seconds that must have passed since the last observation was inserted into the buffer 167: /// @return updatedMedianData The updated 7-slot ring buffer of ticks with the latest observation inserted if the last entry is at least `period` seconds old (returns 0 otherwise) 237: /// @dev We instead observe the average price over a series of time intervals, and define the TWAP as the median of those averages. 274: /// @notice For a given option position (`tokenId`), leg index within that position (`legIndex`), and `positionSize` get the tick range spanned and its 310: // Because Uni v3 chooses token0 and token1 from the alphanumeric order, there is no consistency as to whether token0 is 311: // stablecoin, ETH, or an ERC20. Some pools may want ETH to be the asset (e.g. ETH-DAI) and some may wish the stablecoin to 344: // The max/min ticks that can be initialized are the closest multiple of tickSpacing to the actual max/min tick abs()=887272 365: /// @notice Returns the distances of the upper and lower ticks from the strike for a position with the given width and tickSpacing. 385: /// @notice Compute the amount of funds that are underlying this option position. This is useful when exercising a position. 388: /// @return longAmounts Left-right packed word where the right contains the total contract size and the left total notional 389: /// @return shortAmounts Left-right packed word where the right contains the total contract size and the left total notional 412: /// @notice Adds required collateral and collateral balance from collateralTracker0 and collateralTracker1 and converts to single values in terms of `tokenType`. 413: /// @param tokenData0 LeftRight type container holding the collateralBalance (right slot) and requiredCollateral (left slot) for a user in CollateralTracker0 (expressed in terms of token0) 414: /// @param tokenData1 LeftRight type container holding the collateralBalance (right slot) and requiredCollateral (left slot) for a user in CollateralTracker0 (expressed in terms of token1) 438: /// @notice Adds required collateral and collateral balance from collateralTracker0 and collateralTracker1 and converts to single values in terms of `tokenType`. 439: /// @param tokenData0 LeftRight type container holding the collateralBalance (right slot) and requiredCollateral (left slot) for a user in CollateralTracker0 (expressed in terms of token0) 440: /// @param tokenData1 LeftRight type container holding the collateralBalance (right slot) and requiredCollateral (left slot) for a user in CollateralTracker0 (expressed in terms of token1) 455: /// @notice Compute the notional amount given an incoming total number of `contracts` deployed between `tickLower` and `tickUpper`. 456: /// @dev The notional value of an option is the value of the crypto assets that are controlled (rather than the cost of the transaction). 461: /// @dev Thus, `contracts` refer to "100" in this example. The $20 is the strike price. We get the strike price from `tickLower` and `tickUpper`. 462: /// @dev From TradFi: [https://www.investopedia.com/terms/n/notionalvalue.asp](https://www.investopedia.com/terms/n/notionalvalue.asp). 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. 502: /// @notice Convert an amount of token1 into an amount of token0 given the sqrtPriceX96 in a Uniswap pool defined as sqrt(1/0)*2^96. 519: /// @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. 542: /// @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. 569: /// @notice Compute the amount of token0 and token1 moved. Given an option position `tokenId`, leg index `legIndex`, and how many contracts are in the leg `positionSize`. 571: /// @param positionSize The number of option contracts held in this position (each contract can control multiple tokens) 573: /// @return A LeftRight encoded variable containing the amount0 and the amount1 value controlled by this option position's leg 642: /// @param tokenData0 Leftright encoded word with balance of token0 in the right slot, and required balance in left slot 643: /// @param tokenData1 Leftright encoded word with balance of token1 in the right slot, and required balance in left slot 650: /// @return The LeftRight-packed protocol loss for both tokens, i.e., the delta between the user's balance and expended tokens 702: // their actual balances at the time of computation may be higher, but these are a buffer representing the amount of tokens we 707: // liquidatee has insufficient token0 but some token1 left over, so we use what they have left to mitigate token0 losses 708: // we do this by substituting an equivalent value of token1 in our refund to the liquidator, plus a bonus, for the token0 we convert 709: // we want to convert the minimum amount of tokens required to achieve the lowest possible protocol loss (to avoid overpaying on the conversion bonus) 710: // the maximum level of protocol loss mitigation that can be achieved is the liquidatee's excess token1 balance: balance1 - paid1 712: // if the protocol loss is lower than the excess token1 balance, then we can fully mitigate the loss and we should only convert the loss amount 713: // if the protocol loss is higher than the excess token1 balance, we can only mitigate part of the loss, so we should convert only the excess token1 balance 725: // liquidatee has insufficient token1 but some token0 left over, so we use what they have left to mitigate token1 losses 726: // we do this by substituting an equivalent value of token0 in our refund to the liquidator, plus a bonus, for the token1 we convert 727: // we want to convert the minimum amount of tokens required to achieve the lowest possible protocol loss (to avoid overpaying on the conversion bonus) 728: // the maximum level of protocol loss mitigation that can be achieved is the liquidatee's excess token0 balance: balance0 - paid0 730: // if the protocol loss is lower than the excess token0 balance, then we can fully mitigate the loss and we should only convert the loss amount 731: // if the protocol loss is higher than the excess token0 balance, we can only mitigate part of the loss, so we should convert only the excess token0 balance 756: /// @notice Haircut/clawback any premium paid by `liquidatee` on `positionIdList` over the protocol loss threshold during a liquidation. 757: /// @dev Note that the storage mapping provided as the `settledTokens` parameter WILL be modified on the caller by this function. 765: /// @param settledTokens The per-chunk accumulator of settled tokens in storage from which to subtract the haircut premium 910: /// @notice Returns the original delegated value to a user at a certain tick based on the available collateral from the exercised user. 927: // note: it is possible for refunds to be negative when the exercise fee is higher than the delegated amounts. This is expected behavior
[34, 35, 86, 88, 90, 114, 115, 116, 117, 136, 147, 159, 160, 163, 167, 237, 274, 310, 311, 344, 365, 385, 388, 389, 412, 413, 414, 438, 439, 440, 455, 456, 461, 462, 485, 502, 519, 542, 569, 571, 573, 642, 643, 650, 702, 707, 708, 709, 710, 712, 713, 725, 726, 727, 728, 730, 731, 756, 757, 765, 910, 927]
File: contracts/tokens/ERC1155Minimal.sol 57: /// @notice Emitted when an attempt is made to initiate a transfer to a contract recipient that fails to signal support for ERC1155.
[57]
File: contracts/types/LeftRight.sol 51: // Typically, the slot is already clear when writing to it, but if it is not, the bits will be added to the existing bits 53: // Note that the values *within* the slots are allowed to overflow, but overflows are contained and will not leak into the other slot 113: // Typically, the slot is already clear when writing to it, but if it is not, the bits will be added to the existing bits 115: // Note that the values *within* the slots are allowed to overflow, but overflows are contained and will not leak into the other slot 272: /// @dev Used for linked accumulators, so if the accumulator for one side overflows for a token, both cease to accumulate.
File: contracts/types/LiquidityChunk.sol 10: /// @title A Panoptic Liquidity Chunk. Tracks Tick Range and Liquidity Information for a "chunk." Used to track movement of chunks. 13: /// @notice A liquidity chunk is an amount of `liquidity` (an amount of WETH, e.g.) deployed between two ticks: `tickLower` and `tickUpper`
File: contracts/types/TokenId.sol 24: // (0) univ3pool 48bits 0bits : first 6 bytes of the Uniswap v3 pool address (first 48 bits; little-endian), plus a pseudorandom number in the event of a collision 43: // <---- 48 bits ----> <---- 48 bits ----> <---- 48 bits ----> <---- 48 bits ----> <- 16 bits -> <- 48 bits -> 44: // Leg 4 Leg 3 Leg 2 Leg 1 tickSpacing Univ3 Pool Address 46: // <--- most significant bit least significant bit ---> 51: // - if a leg is active (e.g. leg 1) there can be no gaps in other legs meaning: if leg 1 is active then leg 3 cannot be active if leg 2 is inactive. 55: // We also refer to the legs via their index, so leg number 2 has leg index 1 (legIndex) (counting from zero), and in general leg number N has leg index N-1. 56: // - the underlying strike price of the 2nd leg (leg index = 1) in this option position starts at bit index (64 + 12 + 48 * (leg index=1))=123 73: /// @notice AND mask to clear all bits except for the components of the chunk key (strike, width, tokenType) for each leg 86: /// @return The `poolId` (Panoptic's pool fingerprint, contains the whole 64 bit sequence with the tickSpacing) of the Uniswap V3 pool 144: /// @notice Get the associated risk partner of the leg index (generally another leg index in the position if enabled or the same leg index if no partner). 164: /// @notice Get the width of the nth leg (index `legIndex`). This is half the tick-range covered by the leg (tickUpper - tickLower)/2. 390: // i.e the whole mask is used to flip all legs with 4 legs, but only the first leg is flipped with 1 leg so we shift by 3 legs 391: // We also clear the poolId area of the mask to ensure the bits that are shifted right into the area don't flip and cause issues 430: /// @dev ASSUMPTION: For any leg, the option ratio is always > 0 (the leg always has a number of contracts associated with it). 563: // if the two token long-types and the tokenTypes are both different (one is a short call, the other a long put, e.g.), this is a synthetic position 564: // A synthetic long or short is more capital efficient than each leg separated because the long+short premia accumulate proportionally 565: // unlike short stranlges, long strangles also cannot be partnered, because there is no reduction in risk (both legs can earn premia simultaneously)
[24, 43, 44, 46, 51, 55, 56, 73, 86, 144, 164, 390, 391, 430, 563, 564, 565]
</details>File: contracts/tokens/interfaces/IERC20Partial.sol 8: /// @dev However, we cannot productively handle a failed approval and such a situation would surely cause a revert later in execution. 9: /// @dev In addition, no notable instances exist of tokens that both i) contain a failure case for `approve` and ii) return `false` instead of reverting.
Consider always adding an explicit visibility modifier for variables, as the default is internal
.
There are 8 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;
[131, 136, 141, 145, 149, 154, 158, 162]
This can help to prevent hackers from using stolen tokens, but as a side effect it will increase the project centralization.
There are 3 instances of this issue.
File: contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
[36]
File: contracts/PanopticPool.sol 28: contract PanopticPool is ERC1155Holder, Multicall {
[28]
File: contracts/SemiFungiblePositionManager.sol 72: contract SemiFungiblePositionManager is ERC1155, Multicall {
[72]
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 4 instances of this issue.
File: contracts/CollateralTracker.sol 323: function transfer( 341: function transferFrom(
File: contracts/SemiFungiblePositionManager.sol 540: function safeTransferFrom( //@audit-info new function 566: function safeBatchTransferFrom(
Consider adding a comment with the variable name even if it's not used, to improve the code readability.
There are 2 instances of this issue.
File: contracts/CollateralTracker.sol 392: function maxDeposit(address) external pure returns (uint256 maxAssets) { 444: function maxMint(address) external view returns (uint256 maxShares) {
Avoid typos, and proper nouns should be capitalized.
There are 86 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol // @audit safecasting 37: // Used for safecasting // @audit initalized 92: /// @dev As each instance is deployed via proxy clone, initial parameters must only be initalized once via startToken(). // @audit taylor 198: // taylor expand log(1-sellCollateralRatio)/log(1.0001) around sellCollateralRatio=2000 up to 3rd order // @audit exercisee 634: /// - The forceExercisor will have to *pay* the exercisee because their position will be closed "against their will" // @audit liquidatee 946: // T: transferred shares from liquidatee which are a component of N but do not contribute toward protocol loss // @audit refundee 969: /// @notice Refunds delegated tokens to 'refunder' from 'refundee', similar to 'revoke' // @audit refundee 971: /// @dev can handle negative refund amounts that go from refundee to refunder in the case of high exercise fees. // @audit refundee 972: /// @param refunder The account refunding tokens to 'refundee'. // @audit refundee 974: /// @param assets The amount of assets to refund. Positive means a transfer from refunder to refundee, vice versa for negative. // @audit premum 1180: // if premium is positive (ie. user will receive funds due to selling options), add this premum to the user's balance // @audit ith 1209: // read the ith tokenId from the account
[37, 92, 198, 634, 946, 969, 971, 972, 974, 1180, 1209]
File: contracts/PanopticFactory.sol // @audit numeraire 78: /// @notice Address of the Wrapped Ether (or other numeraire token) contract // @audit numeraire 110: /// @param _WETH9 Address of the Wrapped Ether (or other numeraire token) contract // @audit incentivize 237: // Users can specify a salt, the aim is to incentivize the mining of addresses with leading zeros
File: contracts/PanopticPool.sol // @audit exercisor 50: /// @param exerciseFee LeftRight encoding for the cost paid by the exercisor to force the exercise of the token. // @audit caluculation 108: // Flags used as arguments to premia caluculation functions // @audit blocktime 143: /// Note that the *minimum* total observation time is determined by the blocktime and may need to be adjusted by chain // @audit subtick 336: /// @dev Can also be used for more granular subtick precision on slippage checks // @audit unliquidable 442: for (uint256 k = 0; k < pLength; ) { //@audit H - uncapped positions -> unliquidable // @audit slippagelimit 546: /// @param tickLimitLow The lower tick slippagelimit. // @audit slippagelimit 547: /// @param tickLimitHigh The upper tick slippagelimit. // @audit slippagelimit 613: /// @param tickLimitLow The lower tick slippagelimit. // @audit slippagelimit 614: /// @param tickLimitHigh The upper tick slippagelimit. // @audit liquidatee 1016: /// @param delegations LeftRight amounts of token0 and token1 (token0:token1 right:left) delegated to the liquidatee by the liquidator so the option can be smoothly exercised. // @audit liquidatee 1070: // Perform the specified delegation from `msg.sender` to `liquidatee` // @audit liquidatee 1072: // If not enough tokens are delegated for the positions of `liquidatee` to be closed, the liquidation will fail // @audit liquidatee 1082: // burn all options from the liquidatee // @audit liquidatee 1085: // This is to prevent any short positions the liquidatee has being settled with tokens that will later be revoked // @audit liquidatee 1109: // premia cannot be paid if there is protocol loss associated with the liquidatee // @audit liquidatee 1110: // otherwise, an economic exploit could occur if the liquidator and liquidatee collude to // @audit liquidatee 1113: // thus, we haircut any premium paid by the liquidatee (converting tokens as necessary) until the protocol loss is covered or the premium is exhausted // @audit exercisee 1174: /// @notice Force the exercise of a single position. Exercisor will have to pay a fee to the force exercisee. // @audit exercisee 1178: /// @param positionIdListExercisee Post-burn list of open positions in the exercisee's (account) account // @audit exercisor 1179: /// @param positionIdListExercisor List of open positions in the exercisor's (msg.sender) account // @audit exercisor, exercisee 1191: // validate the exercisor's position list (the exercisee's list will be evaluated after their position is force exercised) // @audit twap 1222: //@audit doesnt check twap? // @audit exercisor 1232: // Note: tick limits are not applied here since it is not the exercisor's position being closed // @audit exercisor 1270: // refund the protocol any virtual shares after settling the difference with the exercisor // @audit exercisor 1276: // the exercisor's position list is validated above // @audit overstimate 1357: // overstimate by rounding up
[50, 108, 143, 336, 442, 546, 547, 613, 614, 1016, 1070, 1072, 1082, 1085, 1109, 1110, 1113, 1174, 1178, 1179, 1191, 1222, 1232, 1270, 1276, 1357]
File: contracts/SemiFungiblePositionManager.sol // @audit safecasting 108: // Used for safecasting // @audit ν 128: // ν = 1/2**VEGOID = multiplicative factor for long premium (Eqns 1-5) // @audit vegoid, streamia 130: // and vegoid modifies the sensitivity of the streamia to changes in that utilization, // @audit ν 216: spread = ν*(liquidity removed from that strike)/(netLiquidity remaining at that strike) 217: = ν*R/N 218: 219: For an arbitrary parameter 0 <= ν <= 1 (ν = 1/2^VEGOID). This way, the gross_feesCollectedX128 will be given by: 220: 221: gross_feesCollectedX128 = feeGrowthX128 * N + feeGrowthX128*R*(1 + ν*R/N) 222: = feeGrowthX128 * T + feesGrowthX128*ν*R^2/N 223: = feeGrowthX128 * T * (1 + ν*R^2/(N*T)) (Eqn 2) 224: 225: The s_accountPremiumOwed accumulator tracks the feeGrowthX128 * R * (1 + spread) term 226: per unit of removed liquidity R every time the position touched: 227: 228: s_accountPremiumOwed += feeGrowthX128 * R * (1 + ν*R/N) / R 229: += feeGrowthX128 * (T - R + ν*R)/N 230: += feeGrowthX128 * T/N * (1 - R/T + ν*R/T) 231: 232: Note that the value of feeGrowthX128 can be extracted from the amount of fees collected by 233: the smart contract since the amount of feesCollected is related to feeGrowthX128 according 234: to: 235: 236: feesCollected = feesGrowthX128 * (T-R) 237: 238: So that we get: 239: 240: feesGrowthX128 = feesCollected/N 241: 242: And the accumulator is computed from the amount of collected fees according to: 243: 244: s_accountPremiumOwed += feesCollected * T/N^2 * (1 - R/T + ν*R/T) (Eqn 3) 245: 246: So, the amount of owed premia for a position of size r minted at time t1 and burnt at 247: time t2 is: 248: 249: owedPremia(t1, t2) = (s_accountPremiumOwed_t2-s_accountPremiumOwed_t1) * r 250: = ∆feesGrowthX128 * r * T/N * (1 - R/T + ν*R/T) 251: = ∆feesGrowthX128 * r * (T - R + ν*R)/N 252: = ∆feesGrowthX128 * r * (N + ν*R)/N 253: = ∆feesGrowthX128 * r * (1 + ν*R/N) (same as Eqn 1) 254: 255: This way, the amount of premia owed for a position will match Eqn 1 exactly. 256: 257: Similarly, the amount of gross fees for the total liquidity is tracked in a similar manner 258: by the s_accountPremiumGross accumulator. 259: 260: However, since we require that Eqn 2 holds up-- ie. the gross fees collected should be equal 261: to the net fees collected plus the ower fees plus the small spread, the expression for the 262: s_accountPremiumGross accumulator has to be given by (you`ll see why in a minute): 263: 264: s_accountPremiumGross += feesCollected * T/N^2 * (1 - R/T + ν*R^2/T^2) (Eqn 4) 265: 266: This expression can be used to calculate the fees collected by a position of size t between times 267: t1 and t2 according to: 268: 269: grossPremia(t1, t2) = ∆(s_accountPremiumGross) * t 270: = ∆feeGrowthX128 * t * T/N * (1 - R/T + ν*R^2/T^2) 271: = ∆feeGrowthX128 * t * (T - R + ν*R^2/T) / N 272: = ∆feeGrowthX128 * t * (N + ν*R^2/T) / N 273: = ∆feeGrowthX128 * t * (1 + ν*R^2/(N*T)) (same as Eqn 2) 274: 275: where the last expression matches Eqn 2 exactly. 276: 277: In summary, the s_accountPremium accumulators allow smart contracts that need to handle 278: long+short liquidity to guarantee that liquidity deposited always receives the correct 279: premia, whether that liquidity has been removed from the AMM or not. 280: 281: Note that the expression for the spread is extremely opinionated, and may not fit the 282: specific risk management profile of every smart contract. And simply setting the ν parameter // @audit feesbase 1098: // round up the stored feesbase to minimize Δfeesbase when we next calculate it // @audit postion 1107: /// @notice caches/stores the accumulated premia values for the specified postion. // @audit feegrowth 1160: // (feegrowth * liquidity) / 2 ** 128 // @audit seperated 1338: // premia, and is only seperated to simplify the calculation
[108, 128, 130, 216-282, 1098, 1107, 1160, 1338]
File: contracts/libraries/Errors.sol // @audit exercisee 25: /// @notice PanopticPool: force exercisee is insolvent - liquidatable accounts are not permitted to open or close positions outside of a liquidation // @audit irst 31: /// @notice PanopticFactory: irst 20 bytes of provided salt does not match caller address // @audit avaiable 62: /// @notice PanopticPool: there is not enough avaiable liquidity to buy an option
File: contracts/libraries/InteractionHelper.sol // @audit metadada 96: // not guaranteed that token supports metadada extension // @audit metadada 109: // not guaranteed that token supports metadada extension
File: contracts/libraries/PanopticMath.sol // @audit safecasting 19: // Used for safecasting // @audit tickspacing 25: /// @notice masks 16-bit tickSpacing out of 64-bit [16-bit tickspacing][48-bit poolPattern] format poolId // @audit addflag 104: // increment the top 8 bit if addflag=true, decrement otherwise // @audit blocktime 116: /// @dev Each period has a minimum length of blocktime * period, but may be longer if the Uniswap pool is relatively inactive. // @audit blocktime 118: /// @dev Thus, the minimum total time window is `cardinality` * `period` * `blocktime`. // @audit twap 235: /// @notice Computes the twap of a Uniswap V3 pool using data from its oracle. // @audit stots 247: // construct the time stots // @audit consistentcy 663: // evaluate at TWAP price to keep consistentcy with solvency calculations // @audit liquidatee 691: // negative premium (owed to the liquidatee) is credited to the collateral balance // @audit liquidatee 701: // note that "balance0" and "balance1" are the liquidatee's original balances before token delegation by a liquidator // @audit liquidatee 705: // liquidatee cannot pay back the liquidator fully in either token, so no protocol loss can be avoided // @audit liquidatee 707: // liquidatee has insufficient token0 but some token1 left over, so we use what they have left to mitigate token0 losses // @audit liquidatee 710: // the maximum level of protocol loss mitigation that can be achieved is the liquidatee's excess token1 balance: balance1 - paid1 // @audit liquidatee 711: // and paid0 - balance0 is the amount of token0 that the liquidatee is missing, i.e the protocol loss // @audit liquidatee 725: // liquidatee has insufficient token1 but some token0 left over, so we use what they have left to mitigate token1 losses // @audit liquidatee 728: // the maximum level of protocol loss mitigation that can be achieved is the liquidatee's excess token0 balance: balance0 - paid0 // @audit liquidatee 729: // and paid1 - balance1 is the amount of token1 that the liquidatee is missing, i.e the protocol loss // @audit liquidatee 756: /// @notice Haircut/clawback any premium paid by `liquidatee` on `positionIdList` over the protocol loss threshold during a liquidation. // @audit liquidatee 760: /// @param premiasByLeg The premium paid (or received) by the liquidatee for each leg of each position // @audit liquidatee 779: // get the amount of premium paid by the liquidatee // @audit liquidatee 790: // Ignore any surplus collateral - the liquidatee is either solvent or it converts to <1 unit of the other token // @audit commited 886: // The long premium is not commited to storage during the liquidation, so we add the entire adjusted amount // @audit exercisee 911: /// @param refunder Address of the user the refund is coming from (the force exercisee) // @audit refundee 926: // if the refunder lacks sufficient token0 to pay back the refundee, have them pay back the equivalent value in token1
[19, 25, 104, 116, 118, 235, 247, 663, 691, 701, 705, 707, 710, 711, 725, 728, 729, 756, 760, 779, 790, 886, 911, 926]
File: contracts/multicall/Multicall.sol // @audit paranthetical 22: // Other solutions will do work to differentiate the revert reasons and provide paranthetical information
[22]
File: contracts/types/LeftRight.sol // @audit safecasting 18: // Used for safecasting // @audit explictily 153: // adding leftRight packed uint128's is same as just adding the values explictily // @audit occured 159: // then an overflow has occured // @audit explictily 176: // subtracting leftRight packed uint128's is same as just subtracting the values explictily // @audit occured 182: // then an underflow has occured
</details>File: contracts/types/TokenId.sol // @audit ith 525: // now validate this ith leg in the position: // @audit stranlges 565: // unlike short stranlges, long strangles also cannot be partnered, because there is no reduction in risk (both legs can earn premia simultaneously) // @audit exercisability 576: /// @param self The TokenId to validate for exercisability
A 100% test coverage is not foolproof, but it helps immensely in reducing the amount of bugs that may occur.
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.
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.
Some lines use // x
and some use //x
. The instances below point out the usages that don't follow the majority, within each file.
There are 105 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 23: // 27: // 32: // 34: // 420: shares = previewDeposit(assets); //@audit-issue L zero shares 1021: _mint(optionOwner, sharesToMint); //@audit-issue L - can mint zero 1078: _mint(optionOwner, sharesToMint); //@audit-issue L - can mint zero 1118: //compute total commission amount = commission rate + spread fee
[23, 27, 32, 34, 420, 1021, 1078, 1118]
File: contracts/PanopticFactory.sol 26: //@audit-ok
[26]
File: contracts/PanopticPool.sol 194: // 199: // 202: // 205: // 208: // 211: // 214: // 217: // 220: // 223: // 430: function _calculateAccumulatedPremia( //@audit-info positive premia??? 442: for (uint256 k = 0; k < pLength; ) { //@audit H - uncapped positions -> unliquidable 449: LeftRightSigned[4] memory premiaByLeg, //@audit-info positive premia 467: ) //@audit-info collision between two legs? (isLong or riskPartner) 481: portfolioPremium = portfolioPremium.add(premiaByLeg[leg]); //@audit-info adds it if is long or includePendingPremium = true 576: _burnOptions(COMMIT_LONG_SETTLED, tokenId, msg.sender, tickLimitLow, tickLimitHigh); //@audit-issue M - why no update median? 915: if (SLOW_ORACLE_UNISWAP_MODE) { //@audit-issue L dead code? 928: s_miniMedian, //@audit-info does not update? 1040: (premia, positionBalanceArray) = _calculateAccumulatedPremia( //@audit-info owed premia (positive??) 1220: delegatedAmounts = delegatedAmounts.sub(positionPremia); //@audit seems false? 1222: //@audit doesnt check twap? 1274: _validateSolvency(account, positionIdListExercisee, NO_BUFFER); //@audit-ok checks also validity 1597: ) external { //@audit-ok 1606: LeftRightUnsigned accumulatedPremium; //@audit-info delta premium 1628: s_options[owner][tokenId][legIndex] = accumulatedPremium; //@audit-ok can't be lower than before 1705: // 1921: //
[194, 199, 202, 205, 208, 211, 214, 217, 220, 223, 430, 442, 449, 467, 481, 576, 915, 928, 1040, 1220, 1222, 1274, 1597, 1606, 1628, 1705, 1921]
File: contracts/SemiFungiblePositionManager.sol 172: /// 540: function safeTransferFrom( //@audit-info new function 545: bytes calldata data //@audit-issue L - allows zero address transfer? 546: ) public override { //@audit-ok has callback on transfer 610: //construct the positionKey for the from and to addresses 638: //@audit-ok before: if (fromLiq.rightSlot() != liquidityChunk.liquidity()) revert Errors.TransferFailed(); 639: if (LeftRightUnsigned.unwrap(fromLiq) != liquidityChunk.liquidity()) //@audit-ok before it checked only the rightSlot 644: //update+store liquidity and fee values between accounts 728: if ((currentTick >= tickLimitHigh) || (currentTick <= tickLimitLow)) //@audit-issue L off by one 735: /// 750: /// 822: //compute the swap amount, set as positive (exact input) 989: LeftRightUnsigned currentLiquidity = s_accountLiquidity[positionKey]; //cache 1022: unchecked { //@audit recheck this as it's new 1032: unchecked { //@audit recheck this as it's new 1114: LeftRightUnsigned collectedAmounts //@audit-ok was signed 1125: .addCapped( //@audit-ok wasn't capped, but both were 256, they are unsigned now 1274: ).subRect(s_accountFeesBase[positionKey]); //@audit-info used sub before 1308: collected1 //@audit was signed but now is unsigned, bug? 1396: .toUint128Capped(); //@audit-info was uncapped before 1471: LeftRightUnsigned amountToCollect; //@audit was signed 1484: _tickLower, //@audit-info was calculateAMMSwapFeesLiquidityChunk with unsigned chunk made with signed values
[172, 540, 545, 546, 610, 638, 639, 644, 728, 735, 750, 822, 989, 1022, 1032, 1114, 1125, 1274, 1308, 1396, 1471, 1484]
File: contracts/libraries/CallbackLib.sol 12: //@audit-ok
[12]
File: contracts/libraries/Constants.sol 7: //@audit-ok
[7]
File: contracts/libraries/FeesCalc.sol 18: // 38: //
File: contracts/libraries/InteractionHelper.sol 17: //@audit-ok
[17]
File: contracts/libraries/Math.sol 128: function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160) { //@audit-ok 241: function getLiquidityForAmount0( //@audit-info price is new: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/LiquidityAmounts.sol#L23 372: /////////////////////////////////////////////// 374: /////////////////////////////////////////////// 486: /////////////////////////////////////////////// 488: /////////////////////////////////////////////// 549: /////////////////////////////////////////////// 551: /////////////////////////////////////////////// 626: /////////////////////////////////////////////// 628: /////////////////////////////////////////////// 703: /////////////////////////////////////////////// 705: ///////////////////////////////////////////////
[128, 241, 372, 374, 486, 488, 549, 551, 626, 628, 703, 705]
File: contracts/libraries/PanopticMath.sol 44: /// 72: /// 266: return int24(sortedTicks[10]); //@audit-issue L - median should be (sortedTicks[9] + sortedTicks[10]) / 2 299: // 305: // 309: // 314: // 319: // 322: // 323: // 771: LeftRightSigned[4][] memory premiasByLeg, //@audit pos or neg?
[44, 72, 266, 299, 305, 309, 314, 319, 322, 323, 771]
File: contracts/libraries/SafeTransferLib.sol 11: //@audit-ok
[11]
File: contracts/tokens/ERC1155Minimal.sol 100: ) public virtual { //@audit-issue L - can burn with transfer to 0 address 204: } //@audit-issue L - no metadata uri
File: contracts/tokens/ERC20Minimal.sol 9: //@audit-ok but no permit / EIP-2612
[9]
File: contracts/types/LiquidityChunk.sol 12: /// 15: // 29: // 33: // 43: // 45: // 49: // 51: // 52: //@audit-ok
[12, 15, 29, 33, 43, 45, 49, 51, 52]
</details>File: contracts/types/TokenId.sol 36: // 38: // 45: // 47: // 52: // 533: ) revert Errors.InvalidTokenIdParameter(4); //@audit-info what if is > or < tick?
Consider adding some comments on critical state variables to explain what they are supposed to do: this will help for future code reviews.
There are 3 instances of this issue.
File: contracts/PanopticPool.sol 121: bool internal constant DONOT_COMMIT_LONG_SETTLED = false;
[121]
File: contracts/SemiFungiblePositionManager.sol 126: bool internal constant BURN = true; 289: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumGross;
Large and/or complex functions should have more comments to better explain the purpose of each logic step.
There are 24 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 650: function exerciseCost( 1311: function _getRequiredCollateralSingleLegNoPartner( 1510: function _computeSpread(
File: contracts/PanopticFactory.sol 336: function _mintFullRange(
[336]
File: contracts/PanopticPool.sol 1018: function liquidate( 1180: function forceExercise( 1509: function _getPremia( 1593: function settleLongPremium( 1672: function _updateSettlementPostMint( 1839: function _updateSettlementPostBurn(
[1018, 1180, 1509, 1593, 1672, 1839]
File: contracts/SemiFungiblePositionManager.sol 757: function swapInAMM( 864: function _createPositionInAMM( 959: function _createLegInAMM( 1322: function _getPremiaDeltas( 1450: function getAccountPremium(
File: contracts/libraries/FeesCalc.sol 130: function _getAMMSwapFeesPerLiquidityCollected(
[130]
File: contracts/libraries/Math.sol 340: function mulDiv( 458: function mulDiv64(uint256 a, uint256 b) internal pure returns (uint256) { 521: function mulDiv96(uint256 a, uint256 b) internal pure returns (uint256) { 598: function mulDiv128(uint256 a, uint256 b) internal pure returns (uint256) { 675: function mulDiv192(uint256 a, uint256 b) internal pure returns (uint256) {
File: contracts/libraries/PanopticMath.sol 651: function getLiquidationBonus( 768: function haircutPremia(
File: contracts/types/TokenId.sol 500: function validate(TokenId self) internal pure {
[500]
</details>Low-level languages like assembly should require extensive documentation and comments to clarify the purpose of each instruction.
There are 3 instances of this issue.
File: contracts/libraries/Math.sol 739: assembly ("memory-safe") {
[739]
File: contracts/libraries/SafeTransferLib.sol 25: assembly ("memory-safe") { 56: assembly ("memory-safe") {
@inheritdoc
for overridden functions@inheritdoc
Copies all missing tags from the base function. Documentation
There are 4 instances of this issue.
File: contracts/CollateralTracker.sol 323: function transfer( 341: function transferFrom(
File: contracts/SemiFungiblePositionManager.sol 540: function safeTransferFrom( //@audit-info new function 566: function safeBatchTransferFrom(
Use mixedCase. Examples: onlyBy, onlyAfter, onlyDuringThePreSale
. Documentation
There is 1 instance of this issue.
File: contracts/SemiFungiblePositionManager.sol 305: modifier ReentrancyLock(uint64 poolId) {
[305]
Use mixedCase
for local and state variables that are not constants, and add a trailing underscore for non-external variables. Documentation
There are 52 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol // @audit s_underlyingToken 89: address internal s_underlyingToken; // @audit s_initialized 93: bool internal s_initialized; // @audit s_univ3token0 96: address internal s_univ3token0; // @audit s_univ3token1 99: address internal s_univ3token1; // @audit s_underlyingIsToken0 102: bool internal s_underlyingIsToken0; // @audit s_panopticPool 109: PanopticPool internal s_panopticPool; // @audit s_poolAssets 112: uint128 internal s_poolAssets; // @audit s_inAMM 115: uint128 internal s_inAMM; // @audit s_ITMSpreadFee 121: uint128 internal s_ITMSpreadFee; // @audit s_poolFee 124: uint24 internal s_poolFee; // @audit _ITMSpreadMultiplier 185: uint256 _ITMSpreadMultiplier // @audit min_sell_ratio 773: uint256 min_sell_ratio = SELLER_COLLATERAL_RATIO;
[89, 93, 96, 99, 102, 109, 112, 115, 121, 124, 185, 773]
File: contracts/PanopticFactory.sol // @audit s_owner 97: address internal s_owner; // @audit s_initialized 100: bool internal s_initialized; // @audit s_getPanopticPool 103: mapping(IUniswapV3Pool univ3pool => PanopticPool panopticPool) internal s_getPanopticPool; // @audit _WETH9 117: address _WETH9, // @audit _SFPM 118: SemiFungiblePositionManager _SFPM,
File: contracts/PanopticPool.sol // @audit s_univ3pool 187: IUniswapV3Pool internal s_univ3pool; // @audit s_miniMedian 226: uint256 internal s_miniMedian; // @audit s_collateralToken0 232: CollateralTracker internal s_collateralToken0; // @audit s_collateralToken1 234: CollateralTracker internal s_collateralToken1; // @audit s_options 239: mapping(address account => mapping(TokenId tokenId => mapping(uint256 leg => LeftRightUnsigned premiaGrowth))) 240: internal s_options; // @audit s_grossPremiumLast 246: mapping(bytes32 chunkKey => LeftRightUnsigned lastGrossPremium) internal s_grossPremiumLast; // @audit s_settledTokens 252: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) internal s_settledTokens; // @audit s_positionBalance 259: mapping(address account => mapping(TokenId tokenId => LeftRightUnsigned balanceAndUtilizations)) 260: internal s_positionBalance; // @audit s_positionsHash 273: mapping(address account => uint256 positionsHash) internal s_positionsHash; // @audit c_user 440: address c_user = user;
[187, 226, 232, 234, 239-240, 246, 252, 259-260, 273, 440]
File: contracts/SemiFungiblePositionManager.sol // @audit s_AddrToPoolIdData 145: mapping(address univ3pool => uint256 poolIdData) internal s_AddrToPoolIdData; // @audit s_poolContext 150: mapping(uint64 poolId => PoolAddressAndLock contextData) internal s_poolContext; // @audit s_accountLiquidity 177: mapping(bytes32 positionKey => LeftRightUnsigned removedAndNetLiquidity) 178: internal s_accountLiquidity; // @audit s_accountPremiumOwed 287: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumOwed; // @audit s_accountPremiumGross 289: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumGross; // @audit s_accountFeesBase 295: mapping(bytes32 positionKey => LeftRightSigned baseFees0And1) internal s_accountFeesBase; // @audit positionKey_from 611: bytes32 positionKey_from = keccak256( // @audit positionKey_to 620: bytes32 positionKey_to = keccak256( // @audit premium0X64_base 1343: uint256 premium0X64_base; // @audit premium1X64_base 1344: uint256 premium1X64_base; // @audit premium0X64_owed 1364: uint128 premium0X64_owed; // @audit premium1X64_owed 1365: uint128 premium1X64_owed; // @audit premium0X64_gross 1385: uint128 premium0X64_gross; // @audit premium1X64_gross 1386: uint128 premium1X64_gross; // @audit UniswapV3Pool 1558: ) external view returns (IUniswapV3Pool UniswapV3Pool) {
[145, 150, 177-178, 287, 289, 295, 611, 620, 1343, 1344, 1364, 1365, 1385, 1386, 1558]
File: contracts/libraries/PanopticMath.sol // @audit timestamp_old 186: (uint256 timestamp_old, int56 tickCumulative_old, , ) = univ3pool.observations( // @audit tickCumulative_old 186: (uint256 timestamp_old, int56 tickCumulative_old, , ) = univ3pool.observations( // @audit timestamp_last 192: (uint256 timestamp_last, int56 tickCumulative_last, , ) = univ3pool // @audit tickCumulative_last 192: (uint256 timestamp_last, int56 tickCumulative_last, , ) = univ3pool
File: contracts/types/LeftRight.sol // @audit z_xR 285: uint128 z_xR = (uint256(x.rightSlot()) + dx.rightSlot()).toUint128Capped(); // @audit z_xL 286: uint128 z_xL = (uint256(x.leftSlot()) + dx.leftSlot()).toUint128Capped(); // @audit z_yR 287: uint128 z_yR = (uint256(y.rightSlot()) + dy.rightSlot()).toUint128Capped(); // @audit z_yL 288: uint128 z_yL = (uint256(y.leftSlot()) + dy.leftSlot()).toUint128Capped(); // @audit r_Enabled 290: bool r_Enabled = !(z_xR == type(uint128).max || z_yR == type(uint128).max); // @audit l_Enabled 291: bool l_Enabled = !(z_xL == type(uint128).max || z_yL == type(uint128).max);
[285, 286, 287, 288, 290, 291]
</details>This convention is suggested for non-external functions (private or internal). Documentation
There are 104 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/PanopticPool.sol 1456: function getUniV3TWAP() internal view returns (int24 twapTick) {
[1456]
File: contracts/SemiFungiblePositionManager.sol 320: function beginReentrancyLock(uint64 poolId) internal { 330: function endReentrancyLock(uint64 poolId) internal { 593: function registerTokenTransfer(address from, address to, TokenId id, uint256 amount) internal { 757: function swapInAMM(
File: contracts/libraries/CallbackLib.sol 31: function validateCallback(
[31]
File: contracts/libraries/Math.sol 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) { 73: function abs(int256 x) internal pure returns (int256) { 81: function absUint(int256 x) internal pure returns (uint256) { 91: function mostSignificantNibble(uint160 x) internal pure returns (uint256 r) { 128: function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160) { //@audit-ok 191: function getAmount0ForLiquidity(LiquidityChunk liquidityChunk) internal pure returns (uint256) { 207: function getAmount1ForLiquidity(LiquidityChunk liquidityChunk) internal pure returns (uint256) { 221: function getAmountsForLiquidity( 241: function getLiquidityForAmount0( //@audit-info price is new: https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/LiquidityAmounts.sol#L23 271: function getLiquidityForAmount1( 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) { 340: function mulDiv( 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) {
[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) { 59: function incrementPoolPattern(uint64 poolId) internal pure returns (uint64) { 92: function updatePositionsHash( 289: function getLiquidityChunk( 338: function getTicks( 371: function getRangesFromStrike( 390: function computeExercisedAmounts( 419: function convertCollateralData( 445: function convertCollateralData( 468: function convertNotional( 490: function convert0to1(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) { 507: function convert1to0(uint256 amount, uint160 sqrtPriceX96) internal pure returns (uint256) { 524: function convert0to1(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) { 547: function convert1to0(int256 amount, uint160 sqrtPriceX96) internal pure returns (int256) { 574: function getAmountsMoved(
[47, 59, 92, 289, 338, 371, 390, 419, 445, 468, 490, 507, 524, 547, 574]
File: contracts/libraries/SafeTransferLib.sol 22: function safeTransferFrom(address token, address from, address to, uint256 amount) internal { 53: 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( 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( 279: function addCapped(
[39, 46, 59, 78, 101, 108, 121, 134, 148, 171, 194, 214, 232, 251, 279]
File: contracts/types/LiquidityChunk.sol 71: function createChunk( 90: function addLiquidity( 103: function addTickLower( 119: function addTickUpper( 136: function updateTickLower( 152: function updateTickUpper( 172: function tickLower(LiquidityChunk self) internal pure returns (int24) { 181: function tickUpper(LiquidityChunk self) internal pure returns (int24) { 190: function liquidity(LiquidityChunk self) internal pure returns (uint128) {
[71, 90, 103, 119, 136, 152, 172, 181, 190]
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( 221: function addOptionRatio( 240: function addIsLong( 255: function addTokenType( 273: function addRiskPartner( 291: function addStrike( 310: function addWidth( 336: function addLeg( 366: function flipToBurnToken(TokenId self) internal pure returns (TokenId) { 404: function countLongs(TokenId self) internal pure returns (uint256) { 416: function asTicks( 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 {
[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>This convention is suggested for non-external state variables (private or internal). Documentation
There are 36 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 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;
[89, 93, 96, 99, 102, 109, 112, 115, 121, 124]
File: contracts/PanopticFactory.sol 64: IUniswapV3Factory internal immutable UNIV3_FACTORY; 67: SemiFungiblePositionManager internal immutable SFPM; 70: IDonorNFT internal immutable DONOR_NFT; 73: address internal immutable POOL_REFERENCE; 76: address internal immutable COLLATERAL_REFERENCE; 79: address internal immutable WETH; 97: address internal s_owner; 100: bool internal s_initialized; 103: mapping(IUniswapV3Pool univ3pool => PanopticPool panopticPool) internal s_getPanopticPool;
[64, 67, 70, 73, 76, 79, 97, 100, 103]
File: contracts/PanopticPool.sol 180: SemiFungiblePositionManager internal immutable SFPM; 187: IUniswapV3Pool internal s_univ3pool; 226: uint256 internal s_miniMedian; 232: CollateralTracker internal s_collateralToken0; 234: CollateralTracker internal s_collateralToken1; 239: mapping(address account => mapping(TokenId tokenId => mapping(uint256 leg => LeftRightUnsigned premiaGrowth))) 240: internal s_options; 246: mapping(bytes32 chunkKey => LeftRightUnsigned lastGrossPremium) internal s_grossPremiumLast; 252: mapping(bytes32 chunkKey => LeftRightUnsigned settledTokens) internal s_settledTokens; 259: mapping(address account => mapping(TokenId tokenId => LeftRightUnsigned balanceAndUtilizations)) 260: internal s_positionBalance; 273: mapping(address account => uint256 positionsHash) internal s_positionsHash;
[180, 187, 226, 232, 234, 239-240, 246, 252, 259-260, 273]
File: contracts/SemiFungiblePositionManager.sol 137: IUniswapV3Factory internal immutable FACTORY; 145: mapping(address univ3pool => uint256 poolIdData) internal s_AddrToPoolIdData; 150: mapping(uint64 poolId => PoolAddressAndLock contextData) internal s_poolContext; 177: mapping(bytes32 positionKey => LeftRightUnsigned removedAndNetLiquidity) 178: internal s_accountLiquidity; 287: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumOwed; 289: mapping(bytes32 positionKey => LeftRightUnsigned accountPremium) private s_accountPremiumGross; 295: mapping(bytes32 positionKey => LeftRightSigned baseFees0And1) internal s_accountFeesBase;
[137, 145, 150, 177-178, 287, 289, 295]
</details>NatSpec must begin with ///
, or use the /* ... */
syntax.
There are 3 instances of this issue.
File: contracts/SemiFungiblePositionManager.sol 358: // @dev pools can be initialized from a Panoptic pool or by calling initializeAMMPool directly, reverting 603: // @dev see `contracts/types/LiquidityChunk.sol` 904: // @dev see `contracts/types/LiquidityChunk.sol`
Some contracts miss a @dev/@notice
NatSpec, which should be a best practice to add as a documentation.
There is 1 instance of this issue.
File: contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
[6]
@author
from contract declarationSome contract definitions have an incomplete NatSpec: add a @author
notation to improve the code documentation.
There is 1 instance of this issue.
File: contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
[6]
@dev
from contract declarationSome contract definitions have an incomplete NatSpec: add a @dev
notation to describe the contract to improve the code documentation.
There are 11 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 36: contract CollateralTracker is ERC20Minimal, Multicall {
[36]
File: contracts/PanopticFactory.sol 27: contract PanopticFactory is Multicall {
[27]
File: contracts/libraries/CallbackLib.sol 13: library CallbackLib {
[13]
File: contracts/libraries/Constants.sol 8: library Constants {
[8]
File: contracts/libraries/Errors.sol 7: library Errors {
[7]
File: contracts/libraries/Math.sol 13: library Math {
[13]
File: contracts/libraries/PanopticMath.sol 18: library PanopticMath {
[18]
File: contracts/types/LeftRight.sol 17: library LeftRightLibrary {
[17]
File: contracts/types/LiquidityChunk.sol 53: library LiquidityChunkLibrary {
[53]
File: contracts/types/TokenId.sol 60: library TokenIdLibrary {
[60]
File: contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
[6]
</details>@notice
from contract declarationSome contract definitions have an incomplete NatSpec: add a @notice
notation to describe the contract to improve the code documentation.
There are 5 instances of this issue.
File: contracts/libraries/PanopticMath.sol 18: library PanopticMath {
[18]
File: contracts/tokens/ERC1155Minimal.sol 11: abstract contract ERC1155 {
[11]
File: contracts/tokens/ERC20Minimal.sol 10: abstract contract ERC20Minimal {
[10]
File: contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
[6]
File: contracts/tokens/interfaces/IERC20Partial.sol 11: interface IERC20Partial {
[11]
@title
from contract declarationSome contract definitions have an incomplete NatSpec: add a @title
notation to describe the contract to improve the code documentation.
There are 2 instances of this issue.
File: contracts/libraries/SafeTransferLib.sol 12: library SafeTransferLib {
[12]
File: contracts/tokens/interfaces/IDonorNFT.sol 6: interface IDonorNFT {
[6]
@dev
from error declarationSome errors have an incomplete NatSpec: add a @dev
notation to describe the error to improve the code documentation.
There are 33 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/libraries/Errors.sol 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(); 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();
[13, 16, 20, 23, 26, 29, 32, 35, 38, 42, 45, 48, 51, 54, 57, 60, 63, 66, 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();
@param
from error declarationSome errors have an incomplete NatSpec: add a @param
notation to describe the error parameters to improve the code documentation.
There are 34 instances of this issue.
<details> <summary>Expand findings</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();
[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();
@dev
from event declarationSome events have an incomplete NatSpec: add a @dev
notation to describe the event to improve the code documentation.
There are 11 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 49: event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); 57: event Withdraw(
File: contracts/PanopticFactory.sol 35: event OwnershipTransferred(address indexed oldOwner, address indexed newOwner); 44: event PoolDeployed(
File: contracts/PanopticPool.sol 63: event PremiumSettled(
[63]
File: contracts/SemiFungiblePositionManager.sol 80: event PoolInitialized(address indexed uniswapPool, uint64 poolId);
[80]
File: contracts/tokens/ERC1155Minimal.sol 22: event TransferSingle( 36: event TransferBatch( 48: event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
</details>File: contracts/tokens/ERC20Minimal.sol 19: event Transfer(address indexed from, address indexed to, uint256 amount); 25: event Approval(address indexed owner, address indexed spender, uint256 amount);
@dev
from modifier declarationSome modifiers have an incomplete NatSpec: add a @dev
notation to describe the modifier to improve the code documentation.
There is 1 instance of this issue.
File: contracts/CollateralTracker.sol 169: modifier onlyPanopticPool() {
[169]
@param
from modifier declarationSome modifiers have an incomplete NatSpec: add a @param
notation to describe the modifier parameters to improve the code documentation.
There is 1 instance of this issue.
File: contracts/CollateralTracker.sol 169: modifier onlyPanopticPool() {
[169]
@dev
from function declarationSome functions have an incomplete NatSpec: add a @dev
notation to describe the function to improve the code documentation.
There are 142 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 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( 911: function revoke( 995: function takeCommissionAddData( 1096: function _getExchangedAmount( 1245: function _getRequiredCollateralAtTickSinglePosition(
[289, 303, 310, 361, 379, 386, 392, 399, 444, 453, 518, 572, 581, 591, 911, 995, 1096, 1245]
File: contracts/PanopticFactory.sol 135: function initialize(address _owner) public { 148: function transferOwnership(address newOwner) external { 160: function owner() external view returns (address) { 336: function _mintFullRange( 421: function getPanopticPool(IUniswapV3Pool univ3pool) external view returns (PanopticPool) {
File: contracts/PanopticPool.sol 353: function optionPositionBalance( 430: function _calculateAccumulatedPremia( //@audit-info positive premia??? 500: function _getSlippageLimits( 523: function pokeMedian() external { 548: function mintOptions( 615: function _mintOptions( 795: function _burnAllOptionsFrom( 827: function _burnOptions( 860: function _updatePositionDataBurn(address owner, TokenId tokenId) internal { 956: function _burnAndHandleExercise( 1296: function _checkSolvencyAtTick( 1345: function _getSolvencyBalances( 1431: function univ3pool() external view returns (IUniswapV3Pool) { 1437: function collateralToken0() external view returns (CollateralTracker collateralToken) { 1443: function collateralToken1() external view returns (CollateralTracker) { 1450: function numberOfPositions(address user) public view returns (uint256 _numberOfPositions) { 1456: function getUniV3TWAP() internal view returns (int24 twapTick) { 1471: function _checkLiquiditySpread( 1509: function _getPremia(
[353, 430, 500, 523, 548, 615, 795, 827, 860, 956, 1296, 1345, 1431, 1437, 1443, 1450, 1456, 1471, 1509]
File: contracts/SemiFungiblePositionManager.sol 330: function endReentrancyLock(uint64 poolId) internal { 504: function mintTokenizedPosition( 681: function _validateAndForwardToAMM( 757: function swapInAMM( 1111: function _updateStoredPremia( 1256: function _collectAndWritePositionData(
[330, 504, 681, 757, 1111, 1256]
File: contracts/libraries/CallbackLib.sol 31: function validateCallback(
[31]
File: contracts/libraries/FeesCalc.sol 46: function getPortfolioValue(
[46]
File: contracts/libraries/InteractionHelper.sol 25: function doApprovals( 92: function computeSymbol( 108: function computeDecimals(address token) external view returns (uint8) {
File: contracts/libraries/Math.sol 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) {
[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 59: function incrementPoolPattern(uint64 poolId) internal pure returns (uint64) { 75: function numberOfLeadingHexZeros(address addr) external pure returns (uint256) { 289: function getLiquidityChunk( 338: function getTicks( 390: function computeExercisedAmounts( 419: function convertCollateralData( 445: function convertCollateralData( 574: function getAmountsMoved( 607: function _calculateIOAmounts( 651: function getLiquidationBonus( 917: function getRefundAmounts(
[59, 75, 289, 338, 390, 419, 445, 574, 607, 651, 917]
File: contracts/libraries/SafeTransferLib.sol 22: function safeTransferFrom(address token, address from, address to, uint256 amount) internal { 53: 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) {
[12]
File: contracts/tokens/ERC1155Minimal.sol 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 {
File: contracts/tokens/ERC20Minimal.sol 50: function approve(address spender, uint256 amount) public returns (bool) { 62: function transfer(address to, uint256 amount) public virtual returns (bool) { 104: function _transferFrom(address from, address to, uint256 amount) internal { 123: function _mint(address to, uint256 amount) internal { 137: function _burn(address from, 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( 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(
[39, 46, 59, 78, 101, 108, 121, 134, 148, 171, 194, 214, 232, 251]
File: contracts/types/LiquidityChunk.sol 71: function createChunk( 90: function addLiquidity( 103: function addTickLower( 119: function addTickUpper( 136: function updateTickLower( 152: function updateTickUpper( 172: function tickLower(LiquidityChunk self) internal pure returns (int24) { 181: function tickUpper(LiquidityChunk self) internal pure returns (int24) { 190: function liquidity(LiquidityChunk self) internal pure returns (uint128) {
[71, 90, 103, 119, 136, 152, 172, 181, 190]
File: contracts/types/TokenId.sol 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) {
[87, 96, 118, 128, 138, 148, 158, 183, 193, 221, 240, 255, 273, 291, 310, 336, 404]
File: contracts/tokens/interfaces/IDonorNFT.sol 13: function issueNFT(
[13]
File: contracts/tokens/interfaces/IERC20Partial.sol 27: function transfer(address to, uint256 amount) external;
[27]
</details>@notice
from function declarationSome functions have an incomplete NatSpec: add a @notice
notation to describe the function to improve the code documentation.
There are 3 instances of this issue.
File: contracts/CollateralTracker.sol 323: function transfer( 341: function transferFrom(
File: contracts/SemiFungiblePositionManager.sol 681: function _validateAndForwardToAMM(
[681]
@param
from function declarationSome functions have an incomplete NatSpec: add a @param
notation to describe the function parameters to improve the code documentation.
There are 19 instances of this issue.
<details> <summary>Expand findings</summary>File: contracts/CollateralTracker.sol 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) { // @audit missing recipient, amount 323: function transfer( // @audit missing from, to, amount 341: function transferFrom( 361: function asset() external view returns (address assetTokenAddress) { 370: function totalAssets() public view returns (uint256 totalManagedAssets) { 392: function maxDeposit(address) external pure returns (uint256 maxAssets) { 444: function maxMint(address) external view returns (uint256 maxShares) { 741: function _poolUtilization() internal view returns (int256 poolUtilization) {
[277, 289, 303, 310, 323, 341, 361, 370, 392, 444, 741]
File: contracts/PanopticFactory.sol 160: function owner() external view returns (address) {
[160]
File: contracts/PanopticPool.sol // @audit missing atTick 430: function _calculateAccumulatedPremia( //@audit-info positive premia??? 523: function pokeMedian() external { 1431: function univ3pool() external view returns (IUniswapV3Pool) { 1437: function collateralToken0() external view returns (CollateralTracker collateralToken) { 1443: function collateralToken1() external view returns (CollateralTracker) { 1456: function getUniV3TWAP() internal view returns (int24 twapTick) {
[430, 523, 1431, 1437, 1443, 1456]
File: contracts/libraries/Math.sol // @audit missing toDowncast 302: function toUint128Capped(uint256 toDowncast) internal pure returns (uint128 downcastedInt) {
[302]
</details>@return
from function declarationSome functions have an incomplete NatSpec: add a @return
notation to describe the function return value to improve the code documentation.
There are 8 instances of this issue.
File: contracts/CollateralTracker.sol 323: function transfer( 341: function transferFrom(
File: contracts/PanopticPool.sol 795: function _burnAllOptionsFrom( 956: function _burnAndHandleExercise( 1296: function _checkSolvencyAtTick( 1509: function _getPremia( 1808: function _getTotalLiquidity(
File: contracts/SemiFungiblePositionManager.sol 1139: function _getFeesBase(
[1139]
#0 - c4-judge
2024-04-26T17:07:55Z
Picodes marked the issue as grade-a
#1 - c4-judge
2024-05-06T16:22:04Z
Picodes marked the issue as selected for report
#2 - Picodes
2024-05-06T16:22:47Z
Flagging as best as this is the best mix of custom issues (#523, the bug on the median) and automated findings I've seen so to me it brings the most value.
#3 - Picodes
2024-05-13T14:28:00Z
For reporting purposes: L01, L03, L04, L07, L10, L11, L12, L14, L20, L24, L26, L27, L28 can be downgraded to NC
Please include #540 and #523 in the final report as Low