Platform: Code4rena
Start Date: 27/11/2023
Pot Size: $60,500 USDC
Total HM: 7
Participants: 72
Period: 7 days
Judge: Picodes
Total Solo HM: 2
Id: 309
League: ETH
Rank: 34/72
Findings: 2
Award: $66.20
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: osmanozdemir1
Also found by: 0xCiphky, Audinarey, Banditx0x, CRYP70, Cryptor, D1r3Wolf, KupiaSec, LokiThe5th, Sathish9098, Skylice, ThenPuli, Topmark, Udsen, ZanyBonzy, baice, ether_sky, fatherOfBlocks, foxb868, grearlake, hihen, hubble, hunter_w3b, lanrebayode77, leegh, lsaudit, minhtrng, nocoder, onchain-guardians, ptsanev, ro1sharkm, seaton0x1, sivanesh_808, t4sk, tapir, tpiliposian, ustas
11.3163 USDC - $11.32
Using delegatecall
inside a loop may cause issues with payable functions
for (uint256 i = 0; i < data.length; ) { (bool success, bytes memory result) = address(this).delegatecall(data[i]); //@note
If one of the delegatecall
consumes part of the sent msg.value, other calls might fail, if they expect the full msg.value
. Consider using a different design (possibly removing from loop), or fully document this to inform potential users.
The SemiFungiblePositionManager
is meant to be multicalled, but implements no function to recieve or withdraw ETH
.
contract SemiFungiblePositionManager is ERC1155, Multicall {
The Multicall
contract, through which the multicalls are made, is however marked payable.
function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
And the function is unprotected from any calls. Consequently, any ETH
sent during a multicall, will be locked in the contract with no way to retrieve it. Consider removing the payable parameter, or introducing a sweep function to collect any unused ETH
.
Incorrect comment - Note that this is just 2^128 not 2^160.
// Note that this is just 2**160 since 2**256 over the fixed denominator (2**128) equals 2**128 //@note prod0 |= prod1 * 2 ** 128;
safeBatchTransferFrom
should employ checks, to ensure that ids
and amounts
arrays have the same length, making it more EIP1155
compliant.
According to ERC1155 Multi token standard,
MUST revert if length of
_ids
is not the same as length of_values
. This can help prevent any unexpected behaviour.
if (ids.length != amounts.length) revert NotEnoughTokenBalance();
This check can also be included in the balanceOfBatch
function too, to ensure the owners
and the ids
are of the same length.
address[] calldata owners, uint256[] calldata ids
Also, the amount being transferred should also be checked to make sure it's <= balanceOf, e.g, also from EIP1155
standard,
MUST revert if any of the balance(s) of the holder(s) for token(s) in
_ids
is lower than the respective amount(s) in_values
sent to the recipient.
if (amount > balanceOf) revert NotEnoughTokenBalance();
From the provided readme, any erc20 token (except fee-on-transfer) is supported
Any compliant ERC20 token that is part of a Uniswap V3 pool and is not a fee-on-transfer token is supported, including ERC-777 tokens.
The issue is that Uniswap v3 does not properly support rebasing tokens so using these tokens with it might result funds getting stuck. From the docs
A negative rebasing token, the more common variant, deflates the balances of token owners. Because the rebasing is not triggered by transfers, the router cannot expect when or how a rebasing will happen. Once it does, the pair reserves will be unbalanced, and the next person to transact with the pair will bear the cost of the delta as a result of the rebasing. Positive rebasing tokens arbitrarily increase the balances of token holders. When a positive rebase happens, it creates a surplus that is unaccounted for in the trading pair. Because the extra tokens are unaccounted for in the trading pair, anyone can call skim() on the trading pair and effectively steal the positive difference from the rebalance.
These tokens types should be made allowances for, or their effects should be properly documented for potential users.
The swap, mint, burn and collect calls to the univ3 contract are all missing a "deadline" parameter. AMMs provide their users with an option to limit the execution of their pending actions, such as swaps or adding and removing liquidity. The most common solution is to include this deadline parameter. It protects users from bad trades and ensures that user's transactions cannot be "saved for later". The lack of deadline means that the can be withheld indefinitely at the advantage of a malicious miner. Consider updating functions below to include a user-input deadline parameter that should be passed along to univ3call
(int256 swap0, int256 swap1) = _univ3pool.swap(
(uint256 amount0, uint256 amount1) = univ3pool.mint(
(uint128 receivedAmount0, uint128 receivedAmount1) = univ3pool.collect(
(uint256 amount0, uint256 amount1) = univ3pool.burn
SemiFungiblePositionManager
contract implements no payable function or no other way to transfer ETH
, meaning ETH
is not usable in the protocol. This itself is not an issue. However, a large portion of the most popular uniswap v3 pools are pairs of a token with ETH
, 5 out of top 10 pools, as at time of auditing. The broader implication of this is that these ETH
pools may be initialized but the protocol will have issues interacting with any of these pools. For instance, users who choose to swap tokens for ETH
might not be able to withdraw it and will have lose their tokens. We reccommend fixing this.asTicks
function
The asTicks
function gets the option position's nth leg's (index legIndex
) tick ranges (lower, upper). But it contains a potential precision loss (due to division before multiplication) when calculating minTick
and maxTick
. This can lead to inaccurate results if the division does not yield an exact integer value.int24 minTick = (Constants.MIN_V3POOL_TICK / tickSpacing) * tickSpacing; int24 maxTick = (Constants.MAX_V3POOL_TICK / tickSpacing) * tickSpacing;
This will affects the comparison made with legLowerTick
and legUpperTick
which might cause the function to revert. The butterfly effect of this is seen in the getLiquidityChunk
and consequently the _createPositionInAMM
functions which are used in the _validateAndForwardToAMM
. The _validateAndForwardToAMM
is the source function for minting and burning tokenized positions.
To avoid cases like this, we recommend switching the operators' positions
int24 minTick = (Constants.MIN_V3POOL_TICK * tickSpacing) / tickSpacing; int24 maxTick = (Constants.MAX_V3POOL_TICK * tickSpacing) / tickSpacing;
#0 - c4-judge
2023-12-14T14:04:45Z
Picodes marked the issue as grade-b
🌟 Selected for report: Sathish9098
Also found by: 0xAadi, 0xHelium, 0xSmartContract, Bulletprime, K42, Raihan, ZanyBonzy, catellatech, fouzantanveer, foxb868, tala7985
54.8805 USDC - $54.88
In context of the audit, the Panoptic protocol comprises 13 smart contracts with a total codebase of 1676 SLoC. Its primary design strategy is composition, and it incorporates two external imports. The protocol is planned to be deployed on the Ethereum and pther EVM chains that support active UniswapV3 deployments. The SemiFungiblePositionManager
, the protocol's core contract, adheres to the ERC1155 token standard. It lacks an AMM, oracle, or fork with any well-known projects. The protocol leverages all ERC20 tokens, excluding fee-on-transfer tokens.
Similar to other codebases built using composition patterns, the Panoptic protocol's code presented was initially challenging to understand. Nonetheless, the code is thoroughly commented, providing detailed explanations for each function's functionality. However, the website documentation seems to be outdated, as it omits certain contracts included in the audit scope. We recommend updating the documentation to enhance user comprehension and streamline future audits.
The protocol demonstrates exemplary test coverage, a testament to the developers' dedication to quality assurance. Unit tests were rigorously executed for all contracts. Considering the intricate nature of certain codebases, we strongly advocate for the incorporation of invariant fuzzing tests. These tests possess the power to uncover rudimentary bugs, strengthen modularity, and elevate overall security measures.
The protocol also exhibits robust error handling practices. A blend of custom and require errors is implemented, with a preference for custom errors. To promote uniformity across the codebase, we recommend updating the Math.sol
contract with custom errors, as they offer superior gas efficiency. However, these custom errors currently lack descriptive explanations. Incorporating such descriptions would enhance code readability and simplify the process of pinpointing parameters responsible for errors.
In scope, the contracts are divided into five categories;
Core contract
NonfungiblePositionManager
. Through this contract, users can effortlessly initialize their chosen UniswapV3 pools, mint or burn their desired tokenized positions, and access vital information regarding a pool's liquidity and fee structure. Compliant with the ERC1155 standard, the contract seamlessly integrates UniswapV3 pool and factory interfaces, features UniswapV3 mint/burn callbacks, and comprises 666 SLoC.Token contract
ERC1155
contract and integrates the OZ ERC1155Holder
contract. The contract consists of 129 SLoC.Multicall contract
Type contracts
LeftRight.sol - is responsible for implementing a set of custom data types designed to store two 128-bit numbers. It comprises 91 SLoC.
LiquidityChunk.sol - is responsible for implementing a specialized data type tailored to represent a liquidity chunk of a given size within the Uniswap ecosystem. It comprises 45 SLoC.
TokenId.sol - is responsible for implementing the custom data type utilized by the SemiFungiblePositionManager
to encode position information into 256-bit ERC1155 tokenIds. It encapsulates a pool identifier and supports up to four comprehensive position legs. The contract comprises 241 SLoC.
Libraries
CallbackLib.sol - is the library for validating and decoding Uniswap callbacks. It comprises 36 SLoC.
Constants.sol - holds the constants used in the protocol contracts, including UniswapV3 min and max tick prices, uniswap hash init code, etc. It comprises 13 SLoC.
Errors.sol - contains all custom errors used in Panoptic's core contracts. It comprises 18 SLoC.
FeesCalc.sol - is the library for fee calculations. It comprises 52 SLoC.
Math.sol - is a concatenation of various UniswapV3 math contracts including TickMath, SafeCast, BitMath etc. It comprises 266 SLoC.
PanopticMath.sol - contains advanced Panoptic/Uniswap-specific functions. It comprises 82 SLoC.
SafeTransferLib.sol - is a modified version of Solmate's SafeTransferLib library. It comprises 19 SLoC.
Option Trading Risks - Engaging in options trading carries inherent risks, and the Panoptic protocol along with its users are not exempt from these risks. Potential issues include margin calls, liquidations (or the inability to liquidate due to insufficient liquidity), exceptionally high leverages and premiums, depegging of purportedly stablecoin assets, and so forth. These factors warrant careful consideration by protocol users.
ThirdParty dependencies - The Panoptic protocol relies on UniswapV3 contracts to operate effectively. As a result, any vulnerabilities or errors in the UniswapV3 protocol could potentially compromise the security and functionality of the Panoptic protocol. Additionally, despite being primarily composed through code composition, the Panoptic protocol incorporates and modifies certain OZ and Solmate contracts. Consequently, vulnerabilities in these external contracts could also introduce risks to the protocol.
Smart Contract vulnerabilities - Smart contract vulnerabilities pose a significant threat to the security and integrity of decentralized protocols. These vulnerabilities can manifest as critical security flaws, enabling malicious actors to exploit the protocol and steal funds or manipulate the system. Logical inconsistencies in the code can also lead to unexpected behavior and unintended consequences, potentially causing financial losses or compromising the protocol's overall functionality.
Non-standard ERC20 tokens risks - The protocol supports a wide range of ERC20 tokens, which offers users a greater choice of assets for trading and position management. However, this versatility also introduces potential risks associated with non-standard ERC20 tokens. These non-standard tokens deviate from the expected behavior of the ERC20 standard, potentially leading to unexpected behaviours or vulnerabilities that could be exploited by malicious actors. Important to note that although the audit doesn't consider the use of fee-on-transfer tokens, the documentation does not point this out. Potential users may be unaware of this and its broader consequences (UniswapV3 has issues with fee-on-transfer token suuport), and the protocol doesn't perform the needed balance checks.
Multichain Compatibility - Due to the inherent variations in blockchain implementations and their respective properties. Transactions that execute flawlessly on one chain may encounter errors or produce unintended consequences on other chains. This heterogeneity introduces risks for users interacting with the protocol on these susceptible chains. The panoptic protocol is planned to be deployed on all chains that support UniswapV3. The issue here is that tokens sometimes have different addresses on different chains, the same uniV3pool address might have different token orders, etc. That is the case for example on Arbitrum, where the pair is WETH/USDC while on Polygon it is USDC/WETH. These implementations should be watched out for before deployment.
Centralization Risks - No significant centralization risks were identified in the contracts in scope as the protocol appears to be permissionless.
We approached the audit in 3 general steps after which we generated our report.
Documentation review - We reviewed the docs, readme, video and explanations provided by the devs. While this was going on, we ran the contracts through slither and compared the generated reports to the bot report.
Manual code review - Here, we manually reviewed the codebase, ran provided tests, tested out various attack vectors. We looked for ways to DOS the system, ways a user can remove other users liquidity, pay more fees than thy're entiltled to and so on. We tested out the functions' logic to make sure they work as intended, as well as making sure the contracts comply with the needed EIPs.
Codebase comparisons - After the manual review, we used solodit to check for any similar protocol types so as to compare their implementations and tried to find any general vulnerabilities.
NonFungiblePositionManager
. Nevertheless, there remains potential for further improvement. We recommend cleaning up the codebase and mititgating any identified vulnerabilities before deployment. This proactive approach will ensure the protocol's continued robustness and security.Time spent: 30 hours
30 hours
#0 - c4-judge
2023-12-14T14:03:43Z
Picodes marked the issue as grade-b