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: 32/72
Findings: 2
Award: $74.70
🌟 Selected for report: 0
🚀 Solo Findings: 0
19.8173 USDC - $19.82
Possible Optimization 1 =
Here is the optimized code snippet:
function initializeAMMPool(address token0, address token1, uint24 fee) external { address univ3pool = FACTORY.getPool(token0, token1, fee); require(address(univ3pool) != address(0), "UniswapPoolNotInitialized"); // Replaced if check with require for clarity and gas efficiency require(s_AddrToPoolIdData[univ3pool] == 0, "PoolAlreadyInitialized"); // Replaced if check with require for clarity and gas efficiency uint64 poolId = PanopticMath.getPoolId(univ3pool); require(address(s_poolContext[poolId].pool) == address(0), "PoolContextExists"); // Remove while loop and add a require to ensure pool context does not exist s_poolContext[poolId] = PoolAddressAndLock({pool: IUniswapV3Pool(univ3pool), locked: false}); // Direct assignment without change s_AddrToPoolIdData[univ3pool] = uint256(poolId) + 2 ** 255; // Direct assignment without change emit PoolInitialized(univ3pool); // Emit event without change }
Possible Optimization 2 =
Here is the optimized code:
function afterTokenTransfer(address from, address to, uint256[] memory ids, uint256[] memory amounts) internal override { uint256 length = ids.length; // Cache the length of the ids array for (uint256 i = 0; i < length; ) { // Use cached length for loop condition registerTokenTransfer(from, to, ids[i], amounts[i]); unchecked { ++i; } } }
Possible Optimization 1 =
Here is the optimized code snippet:
function safeTransferFrom( address from, address to, uint256 id, uint256 amount, bytes calldata data ) public { if (!(msg.sender == from || isApprovedForAll[from][msg.sender])) revert NotAuthorized(); balanceOf[from][id] -= amount; // balance will never overflow unchecked { balanceOf[to][id] += amount; } afterTokenTransfer(from, to, id, amount); emit TransferSingle(msg.sender, from, to, id, amount); // Consolidate external call and condition check if (to.code.length != 0 && ERC1155Holder(to).onERC1155Received(msg.sender, from, id, amount, data) != ERC1155Holder.onERC1155Received.selector) { revert UnsafeRecipient(); } }
Possible Optimization 2 =
Here is the optimized code:
function balanceOfBatch( address[] calldata owners, uint256[] calldata ids ) public view returns (uint256[] memory balances) { uint256 length = owners.length; // Cache the length of the owners array balances = new uint256[](length); unchecked { for (uint256 i = 0; i < length; ++i) { // Use cached length for loop condition address owner = owners[i]; // Cache the owner address uint256 id = ids[i]; // Cache the token id balances[i] = balanceOf[owner][id]; // Use cached values for balance lookup } } }
SLOAD
operations. The exact savings depend on the length of the owners
array but could be significant for larger arrays.Possible Optimization =
uint256
.After Optimization:
function add(uint256 x, uint256 y) internal pure returns (uint256 z) { z = x + y; if (z < x) revert Errors.UnderOverFlow(); // Simplified overflow check // Removed the check for uint128 overflow as it's redundant // Solidity automatically checks for overflows in arithmetic operations }
Possible Optimization =
Here is the optimized code snippet:
function createChunk( uint256 self, int24 _tickLower, int24 _tickUpper, uint128 amount ) internal pure returns (uint256) { unchecked { // Directly construct the chunk with all components return uint256(amount) + (uint256(uint24(_tickLower)) << 232) + (uint256(uint24(_tickUpper)) << 208); } }
Possible Optimization =
After Optimization:
function validate(uint256 self) internal pure returns (uint64) { uint256 optionRatios = self & OPTION_RATIO_MASK; if (optionRatios == 0) revert Errors.InvalidTokenIdParameter(1); unchecked { for (uint256 i = 0; i < 4; ++i) { uint256 currentOptionRatio = self.optionRatio(i); if (currentOptionRatio == 0) { // Simplify the check for remaining bits if (self.clearLeg(i) != 0) revert Errors.InvalidTokenIdParameter(1); break; // we are done iterating over potential legs } // ... rest of the code } } return self.univ3pool(); }
Possible Optimization =
Here is the optimized code snippet:
function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { unchecked { uint256 absTick = uint256(tick < 0 ? -tick : tick); // Simplified absolute value calculation if (absTick > uint256(Constants.MAX_V3POOL_TICK)) revert Errors.InvalidTick(); // ... rest of the function with potential optimizations in bitwise operations } }
#0 - c4-judge
2023-12-14T17:12:27Z
Picodes marked the issue as grade-a
#1 - dyedm1
2023-12-15T18:35:31Z
Estimated gas saved = This optimization can save gas by reducing the number of SLOAD operations. The exact savings depend on the length of the ids array but could be significant for larger arrays. This change is safe as it does not alter the logic, only the computation method.
This is not a storage array.
#2 - c4-sponsor
2023-12-15T18:35:36Z
dyedm1 (sponsor) confirmed
#3 - c4-judge
2023-12-26T23:28:00Z
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
The SemiFungiblePositionManager
contract is a core component of the Panoptic protocol, extending the ERC1155 standard and integrating with Uniswap V3. It manages complex, multi-leg liquidity positions and offers functionalities like minting, burning, and transferring tokenized positions.
Pool Initialization and Management
PoolAddressAndLock
) to prevent reentrancy, a critical security feature for DeFi protocols.Tokenized Position Management
mintTokenizedPosition
and burnTokenizedPosition
handle the creation and destruction of tokenized positions, respectively.Callback Handling
uniswapV3MintCallback
, uniswapV3SwapCallback
) to facilitate liquidity operations.Token Transfer Logic
afterTokenTransfer
to handle the transfer of tokenized positions.Position Validation and Forwarding
_validateAndForwardToAMM
function validates position sizes and tick limits, then forwards the request to the AMM for execution.Swap Execution
swapInAMM
function executes swaps in Uniswap V3 pools, a necessary operation for adjusting positions in response to market movements.Liquidity Chunk Handling
LiquidityChunk
library to manage liquidity in discrete chunks, facilitating complex position management.Complex Interactions with Uniswap V3: The contract's heavy reliance on Uniswap V3 mechanics requires thorough testing, especially for edge cases in liquidity management and fee calculation.
Reentrancy Protection: While the contract employs a locking mechanism, it's crucial to ensure that all external calls are safeguarded against reentrancy attacks.
Token Transfer Consistency: The logic in afterTokenTransfer
must be robust to maintain consistent state across transfers, especially in multi-leg positions.
Callback Function Security: The callback functions must be tightly secured to prevent unauthorized calls, which could lead to manipulation or loss of funds.
Position Validation: Rigorous validation of tokenized positions is necessary to prevent incorrect or unauthorized position creation and destruction.
The ERC1155
contract is an abstract implementation of the ERC1155 standard, a multi-token standard allowing for the efficient transfer of multiple token types. It is a key component in the Panoptic protocol, enabling the management of diverse tokenized positions.
Event Emission
TransferSingle
and TransferBatch
events for single and batch token transfers, respectively, providing transparency and traceability in token movements.ApprovalForAll
event is emitted when an operator is approved to manage all tokens of a user, crucial for delegated token management.Error Handling
NotAuthorized
and UnsafeRecipient
, enhancing the clarity and efficiency of error reporting.Token Balance Management
balanceOf
) of token balances for each user, indexed by user and token ID, ensuring accurate tracking of token ownership.safeTransferFrom
and safeBatchTransferFrom
is designed to prevent overflows, ensuring the integrity of token balances.Approval Mechanism
isApprovedForAll
mapping and setApprovalForAll
function manage operator approvals, allowing for flexible and secure management of token permissions.Safe Transfer Functions
safeTransferFrom
and safeBatchTransferFrom
for single and batch transfers, including checks for authorization and recipient safety.Interface Support
supportsInterface
function indicates support for specific interfaces (like ERC1155 and ERC165), a key feature for interface detection and compatibility.Minting and Burning
_mint
and _burn
functions for token creation and destruction, essential for managing the token supply.Token Transfer Hooks
afterTokenTransfer
(for both single and batch transfers) allow for custom logic to be executed after token transfers, offering extensibility in token transfer handling.Complex Transfer Logic: The contract's transfer logic, especially in batch operations, is complex and requires thorough testing to prevent potential vulnerabilities or logic flaws.
Interface Compliance: Ensuring full compliance with the ERC1155 standard is crucial for interoperability with other contracts and platforms in the Ethereum ecosystem.
Recipient Contract Interaction: The contract interacts with recipient contracts (via ERC1155Holder
) during transfers. It's essential to ensure that these interactions are secure and do not introduce vulnerabilities.
Token Minting and Burning: The internal _mint
and _burn
functions must be used cautiously in derived contracts to prevent unauthorized manipulation of token supply.
The TokenId
library in the Panoptic protocol is a specialized utility for encoding and decoding token IDs in the context of Uniswap V3 positions. It plays a crucial role in managing complex tokenized positions and ensuring their correct representation and manipulation within the system.
Token ID Encoding and Decoding
univ3pool
, asset
, optionRatio
, isLong
, tokenType
, riskPartner
, strike
, width
, and more.Bitwise Operations
Validation and Error Handling
validate
function to ensure the integrity and correctness of a token ID. It checks for valid parameters and relationships between different components of a token ID.Utility Functions
countLegs
, clearLeg
, flipToBurnToken
, and others, providing essential tools for managing token IDs in various scenarios.Constants and Masks
LONG_MASK
, CLEAR_POOLID_MASK
, OPTION_RATIO_MASK
, etc.) to facilitate the encoding and decoding process.Complex Bitwise Logic: The extensive use of bitwise operations, while efficient, introduces complexity. It requires careful handling to avoid errors in encoding and decoding.
Data Integrity: Ensuring the integrity of encoded data is paramount. Rigorous testing and validation are needed to prevent any inconsistencies or inaccuracies in token ID representation.
Error Handling: The library should robustly handle any erroneous inputs or invalid states to prevent any adverse effects on the broader system.
Upgradability and Extensibility: As the protocol evolves, the library should be designed to accommodate future changes or extensions in the token ID structure.
Function interaction graphs I made for each contract for better visualization of function interactions:
Link to Graph for SemiFungiblePositionManager.sol.
Link to Graph for ERC1155Minimal.sol.
Link to Graph for LeftRight.sol.
Link to Graph for LiquidityChunk.sol.
Link to Graph for TokenId.sol.
Link to Graph for CallbackLib.sol.
Link to Graph for FeesCalc.sol.
Link to Graph for PanopticMath.sol.
Link to Graph for SafeTransferLib.sol.
Link to Graph for Multicall.sol.
20 hours
#0 - c4-judge
2023-12-14T17:17:12Z
Picodes marked the issue as grade-b