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: 37/72
Findings: 1
Award: $54.88
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Sathish9098
Also found by: 0xAadi, 0xHelium, 0xSmartContract, Bulletprime, K42, Raihan, ZanyBonzy, catellatech, fouzantanveer, foxb868, tala7985
The SemiFungiblePositionManager is a gas-efficient alternative to Uniswap’s NonFungiblePositionManager that manages complex, multi-leg Uniswap positions encoded in ERC1155 tokenIds, performs swaps allowing users to mint positions with only one type of token, and, most crucially, supports the minting of both typical LP positions where liquidity is added to Uniswap and “long” positions where Uniswap liquidity is burnt.
In this analysis, a systematic and methodical approach was employed to break down and the approach following several key steps:
Contract Overview: Each contract was introduced with a brief summary of its purpose, functionality, and key components. This provided a high-level understanding of the contracts and their roles within a larger system.
Code Breakdown: The code for each contract was broken down into specific sections, highlighting key components, functions, and structures. This allowed for a detailed examination of the code's logic and design.
Functionality Analysis: Each significant function or section of the code was analyzed in terms of its purpose, how it interacts with other components, and its potential impact on the overall system. This involved explaining the functionality of various functions, data structures, and modifiers.
Security Considerations: Potential security flaws and considerations were identified and discussed for each contract. This included aspects such as reentrancy, complexity, integer overflows/underflows, access controls, and validation checks.
Conclusion: A summary and conclusion were provided for each contract, offering an overview of its strengths, potential vulnerabilities, and recommendations for further consideration or improvement.
Common Themes: Common themes and considerations, such as reentrancy protection, gas efficiency, and potential upgradeability challenges, were addressed across multiple contracts.
SemiFungiblePositionManager
which is intended to be a gas-efficient position manager for Uniswap V3 positions, wrapping them behind an ERC1155 token. It allows for the creation and management of positions with up to 4 legs (individual liquidity ranges within a Uniswap V3 pool).
Contract Inheritance: The contract inherits from ERC1155
and Multicall
, allowing it to manage semi-fungible tokens and batch calls, respectively.
Events: The contract defines events for pool initialization, position minting, and burning, which are emitted when these actions occur.
Types and Libraries: The contract uses several custom types and libraries to handle operations such as packing/unpacking values, math operations, and safe transfers.
Immutables and Storage: The contract defines immutable variables for the Uniswap V3 Factory address and constants for mint/burn flags. It also has storage mappings for pool IDs, context data, liquidity, premia, and base fees.
Reentrancy Lock: A modifier ReentrancyLock
is used to prevent reentrant calls for a specific pool, which is a critical security feature to prevent reentrancy attacks.
Initialization: The contract includes a constructor to set the Uniswap V3 Factory address and a function to initialize Uniswap V3 pools.
Callback Handlers: The contract includes callback functions for Uniswap V3 mint and swap operations, which handle the transfer of tokens required by the pool during these operations.
Mint/Burn Functions: Public functions mintTokenizedPosition
and burnTokenizedPosition
allow users to mint new positions or burn existing ones, handling the necessary interactions with the Uniswap V3 pool, including liquidity management and fee collection.
Transfer Hooks: The contract overrides afterTokenTransfer
hooks from the ERC1155 standard to handle updates to position data after transfers.
AMM Interaction Helpers: Internal helper functions manage the creation of positions in the AMM, including minting/burning liquidity and collecting fees.
Properties: The contract includes view functions to retrieve information about liquidity, premia, and fees associated with positions, as well as pool IDs.
Potential Security Flaws:
ERC1155.sol
ERC1155
that implements the ERC-1155 multi-token standard without metadata support. The contract includes basic functionality for token transfers, balance tracking, and operator approvals. It also includes hooks for extending the contract with additional logic.
Version and Import: The contract specifies the Solidity compiler version ^0.8.0
and imports ERC1155Holder
from OpenZeppelin, which is a utility contract to handle ERC1155 tokens.
Events: The contract declares events for single and batch token transfers (TransferSingle
and TransferBatch
) and for setting operator approvals (ApprovalForAll
).
Errors: Custom errors NotAuthorized
and UnsafeRecipient
are defined for unauthorized transfers and unsafe transfer recipients, respectively.
Storage: Two mappings are used to track token balances (balanceOf
) and operator approvals (isApprovedForAll
).
Approval Logic: The setApprovalForAll
function allows a token owner to approve an operator to manage all of their tokens.
Transfer Logic: The safeTransferFrom
and safeBatchTransferFrom
functions implement single and batch token transfers, respectively. They check for authorization, update balances, call the afterTokenTransfer
hook, emit the appropriate event, and perform a safety check on the recipient if it's a contract.
unchecked
keyword to avoid overflow checks, which is safe under the assumption that token balances will not exceed the maximum uint256
value. However, this assumes that the minting process is well-controlled and does not allow for such an overflow.Balance Querying: The balanceOfBatch
function allows querying multiple balances in a single call.
Interface Support: The supportsInterface
function signals support for ERC165 and ERC1155 interfaces.
Minting/Burning: Internal _mint
and _burn
functions are provided for minting and burning tokens. These functions are internal and meant to be called from derived contracts.
_mint
function includes a safety check for the recipient if it's a contract, similar to the transfer functions.Transfer Hooks: Two internal virtual functions afterTokenTransfer
are declared for single and batch transfers, allowing derived contracts to implement additional logic after transfers occur.
Potential Security Flaws:
unchecked
is appropriate here, but developers should ensure that minting functions cannot cause an overflow.ERC1155Receiver
interface correctly. This is a critical check to prevent tokens from being locked in contracts that do not support them.
SemiFungiblePositionManager
which is intended to be a gas-efficient position manager for Uniswap V3 positions, wrapping them behind an ERC1155 token. It allows for the creation and management of positions with up to 4 legs (individual liquidity ranges within a Uniswap V3 pool).
key components and potential security considerations:
Contract Inheritance: The contract inherits from ERC1155
and Multicall
, allowing it to manage semi-fungible tokens and batch calls, respectively.
Events: The contract defines events for pool initialization, position minting, and burning, which are emitted when these actions occur.
Types and Libraries: The contract uses several custom types and libraries to handle operations such as packing/unpacking values, math operations, and safe transfers.
Immutables and Storage: The contract defines immutable variables for the Uniswap V3 Factory address and constants for mint/burn flags. It also has storage mappings for pool IDs, context data, liquidity, premia, and base fees.
Reentrancy Lock: A modifier ReentrancyLock
is used to prevent reentrant calls for a specific pool, which is a critical security feature to prevent reentrancy attacks.
Initialization: The contract includes a constructor to set the Uniswap V3 Factory address and a function to initialize Uniswap V3 pools.
Callback Handlers: The contract includes callback functions for Uniswap V3 mint and swap operations, which handle the transfer of tokens required by the pool during these operations.
Mint/Burn Functions: Public functions mintTokenizedPosition
and burnTokenizedPosition
allow users to mint new positions or burn existing ones, handling the necessary interactions with the Uniswap V3 pool, including liquidity management and fee collection.
Transfer Hooks: The contract overrides afterTokenTransfer
hooks from the ERC1155 standard to handle updates to position data after transfers.
AMM Interaction Helpers: Internal helper functions manage the creation of positions in the AMM, including minting/burning liquidity and collecting fees.
Properties: The contract includes view functions to retrieve information about liquidity, premia, and fees associated with positions, as well as pool IDs.
Potential Security Flaws:
LeftRight
that is designed to work with 256-bit integers (both signed and unsigned) by splitting them into two 128-bit halves, referred to as the "left" and "right" slots. The library provides functions to extract, modify, and perform arithmetic on these halves. It is intended to optimize storage by packing two separate pieces of data into a single 256-bit storage slot.
key components and potential security considerations:
Slot Extraction: The rightSlot
and leftSlot
functions extract the right and left 128-bit halves of a 256-bit integer, respectively. These functions use type casting and bit shifting to achieve this.
Slot Writing: The toRightSlot
and toLeftSlot
functions write a 128-bit value into the right or left slot of a 256-bit integer. Notably, these functions add the new value to the existing value in the slot rather than overwriting it. This could lead to unexpected results if the slot is not cleared before writing to it.
Arithmetic Operations: The add
and sub
functions perform addition and subtraction on two 256-bit integers that have been encoded using the LeftRight packing method. They check for overflows and underflows to ensure safe arithmetic.
Safe Casting: The toInt128
, toUint128
, and toInt256
functions safely cast between different integer sizes, reverting on overflow or underflow.
Potential Security Flaws:
Unchecked Arithmetic: The library uses unchecked arithmetic in several places. While this is a deliberate choice to save gas, it assumes that the calling code will handle overflows and underflows. This could lead to vulnerabilities if not properly managed.
Assumption of Clear Slots: The library assumes that the slots are clear when writing to them. If this is not the case, the addition of new bits to existing bits could lead to incorrect data representation.
TokenId
that is used to encode and decode option position data into a single uint256
token ID. This token ID is used in an ERC1155 token contract to represent options positions in a system referred to as SFPM (presumably some form of options market or financial product marketplace).
The token ID encodes various pieces of information about an options position, including details about up to four "legs" of the position, as well as the associated Uniswap v3 pool address. Each leg can represent a part of the options position, such as a specific option contract with its own parameters like asset type, option ratio, long/short status, token type, risk partner, strike price, and width.
The library provides functions to:
univ3pool
, asset
, optionRatio
, isLong
, tokenType
, riskPartner
, strike
, width
).addUniv3pool
, addAsset
, addOptionRatio
, addIsLong
, addTokenType
, addRiskPartner
, addStrike
, addWidth
, addLeg
).flipToBurnToken
, countLongs
, asTicks
, countLegs
, clearLeg
, validate
).Potential security flaws and considerations:
Validation: The validate
function is critical for ensuring that the token ID represents a valid options position. It checks for common issues such as zero-width legs, invalid strike prices, and proper risk partner relationships. However, it relies on the assumption that the token ID has been constructed correctly. If the token ID is manipulated or constructed incorrectly outside of the provided functions, it could lead to invalid states that the validate
function might not catch.
Integer Overflow/Underflow: The code uses the unchecked
keyword, which disables overflow/underflow checks. While this can save gas, it also means that arithmetic operations could silently wrap around without error. The library assumes that inputs are well-formed and that operations will not overflow, which might not always be the case.
Bit Manipulation: The library heavily relies on bit manipulation to pack and unpack data within the token ID. This requires careful attention to ensure that data is correctly encoded and decoded without collision or loss of information. Any changes to the encoding scheme must be handled with extreme caution to avoid breaking compatibility or introducing bugs.
Magic Numbers: The code contains several "magic numbers" (e.g., bit masks, bit shifts) that are critical to the encoding and decoding logic. These numbers must be well-documented and understood, as any changes could have significant implications.
Reentrancy: While the library itself does not interact with external contracts or state, it is designed to be used in a larger system that likely does. It is important to ensure that the larger system uses these functions in a way that is not vulnerable to reentrancy attacks.
Gas Costs: The bit manipulation operations can be gas-intensive. It's important to consider the gas costs associated with using this library, especially since it is designed to be used within a contract that handles financial transactions.
Error Handling: The library uses custom errors (from Errors.sol
) for error handling. It is important to ensure that these errors are handled appropriately in the larger system to avoid unexpected behavior or loss of funds.
Upgradability: If the encoding scheme needs to be upgraded or changed, it could be challenging to migrate existing token IDs to a new format. This should be considered when designing the larger system that uses this library.
CallbackLib.sol
CallbackLib
that is intended to be used for verifying and decoding callbacks from Uniswap V3 pools. The library contains a structure to define the features of a Uniswap V3 pool (PoolFeatures
) and another structure to hold data sent by the pool in mint/swap callbacks (CallbackData
).
The validateCallback
function is the core of this library. It is used to verify that a callback has indeed originated from the canonical Uniswap pool with a specific set of features. This is important for security reasons, as it ensures that the callback is not being spoofed by a malicious actor.
key components and potential security considerations:
It takes three parameters: sender
, which is the address initiating the callback; factory
, which is the address of the Uniswap V3 factory; and features
, which are the claimed features of the pool by the sender
.
The function computes the expected address of the Uniswap pool by using the provided factory
address and the features
of the pool. It does this by encoding the features
and the factory
address, prepending a 0xff
byte, and hashing the result with keccak256
. This is then combined with the Constants.V3POOL_INIT_CODE_HASH
(which is a constant hash of the pool's initialization code) and hashed again. The resulting hash is cast to a uint256
, then to a uint160
, and finally to an address
.
The computed address is then compared to the sender
address. If they do not match, the function reverts with an Errors.InvalidUniswapCallback()
error, indicating that the callback did not come from the expected Uniswap pool.
Potential Security Flaws:
Constants.V3POOL_INIT_CODE_HASH
is correct and matches the actual init code hash used by the Uniswap V3 factory. If this constant is incorrect, the validation will fail.validateCallback
function is marked as pure
, which means it does not read from or modify the state. This is appropriate since the function only performs computations based on its inputs.factory
address. If the factory
address provided is incorrect or malicious, the validation will not be secure.
Constants
which contains a set of internal constants used in a project called Panoptic, authored by Axicon Labs Limited. The constants are related to Uniswap V3 pool parameters and include fixed point multipliers, minimum and maximum price ticks, sqrtPriceX96 values, and the init code hash for a Uniswap V3 pool.
key components and potential security considerations:
FP96
: A fixed point multiplier used for precision in calculations, set to 2^96.MIN_V3POOL_TICK
and MAX_V3POOL_TICK
: The minimum and maximum possible price ticks in a Uniswap V3 pool.MIN_V3POOL_SQRT_RATIO
and MAX_V3POOL_SQRT_RATIO
: The minimum and maximum possible sqrtPriceX96 values in a Uniswap V3 pool. The sqrtPriceX96 is a representation of the price used in Uniswap V3 which is the square root of the price ratio multiplied by 2^96.V3POOL_INIT_CODE_HASH
: The keccak256 hash of the init code for a Uniswap V3 pool, which is used for computing the deployed address of a pool. The init code is provided as a hex string and hashed at compile time.Potential Security Flaws:
V3POOL_INIT_CODE_HASH
is critical for calculating pool addresses. If this value is incorrect, it could lead to interactions with the wrong contract addresses, potentially resulting in loss of funds or other vulnerabilities.
Errors
that contains custom error types for a smart contract, presumably for a financial or trading application given the context of the errors. Each error type is associated with a specific condition that can occur within the smart contract, and they are intended to be used to revert transactions with a descriptive error when something goes wrong.
explanation of each error:
CastingError
: Indicates a failure when casting between different data types, such as converting a uint256
to a uint128
.InvalidTick
: The tick (price level) is not within the allowed range.InvalidUniswapCallback
: A callback from an address that is not the expected Uniswap V3 pool was attempted.InvalidTokenIdParameter
: An invalid parameter was passed, with the parameterType
indicating which one.LeftRightInputError
: There is an invalid input in a library called LeftRight
.NoLegsExercisable
: In an options contract, none of the legs that were forced to exercise are actually exercisable.PositionTooLarge
: A token amount for a position exceeds 128 bits.NotEnoughLiquidity
: There is insufficient liquidity to execute an option purchase.OptionsBalanceZero
: The user's option balance is zero or non-existent.
FeesCalc
that calculates the swap/trading fees accumulated for a specific liquidity position (referred to as a "liquidity chunk") within a Uniswap V3 pool. The code is designed to work with an options trading system where liquidity chunks represent legs of an options position.
key components and potential security considerations:
The library uses several custom types (LeftRight
, LiquidityChunk
, TokenId
) and a math library to perform its calculations.
The calculateAMMSwapFeesLiquidityChunk
function calculates the accumulated fees for a given liquidity chunk within a Uniswap V3 pool. It takes the current price tick, the starting liquidity of the position, and the liquidity chunk as parameters.
The _getAMMSwapFeesPerLiquidityCollected
internal function is used to determine the fee growth per unit of liquidity for both tokens in the liquidity chunk's price range. It does this by querying the Uniswap V3 pool's ticks
mapping for the fee growth outside the range of the liquidity chunk's upper and lower ticks.
The code uses unchecked blocks to avoid overflow checks, which is a common optimization technique in Solidity to save gas. However, this assumes that the arithmetic operations will not overflow, which could be a potential security risk if not properly assessed.
The code calculates the fees for each token by multiplying the fee growth per unit of liquidity by the starting liquidity and then adjusting the result to fit into an int128
type.
Potential security considerations and recommendations:
Unchecked Arithmetic: The use of unchecked arithmetic could lead to overflow/underflow issues if not properly managed. It's important to ensure that the values involved in these calculations will not cause overflows.
Access Control: The calculateAMMSwapFeesLiquidityChunk
function is marked as public
, which means anyone can call it. However, since it's a view
function (it doesn't modify state), this is not a direct security concern. Still, it's important to consider whether this function should be exposed publicly or restricted to certain contracts or addresses.
Integration with External Contracts: The library interacts with an external Uniswap V3 pool contract. It's crucial to ensure that the address of the Uniswap V3 pool contract is correct and that the contract is secure. Any vulnerabilities in the Uniswap V3 pool could potentially affect this library.
Correctness of Mathematical Models: The library assumes that the mathematical models and formulas used to calculate fees are correct. It's important to thoroughly test these calculations to ensure they accurately reflect the expected fees.
Gas Optimization: The library seems to be optimized for gas usage, which is important for on-chain calculations. However, gas optimization should not come at the expense of security.
Library Reentrancy: As a library, FeesCalc
is not directly susceptible to reentrancy attacks, but the contracts that use this library should be aware of reentrancy considerations, especially when interacting with external contracts like Uniswap V3 pools.
Math
that contains various mathematical functions and utilities, particularly for operations related to Uniswap's automated market maker (AMM) system. The library includes functions for calculating square roots of prices at given ticks, amounts of tokens for given liquidity, and casting and multiplication/division operations with high precision.
key components and potential security considerations:
General Math Helpers
absUint
: Calculates the absolute value of a signed integer.Tick Math
getSqrtRatioAtTick
: Calculates the square root of the price for a given tick, adjusted by a factor of 1.0001 to the power of half the tick value. This is used in Uniswap's AMM to determine the price at a specific tick.Liquidity Amounts (Strike+Width)
getAmount0ForLiquidity
: Calculates the amount of token0 that corresponds to a given liquidity chunk within a specified price range.getAmount1ForLiquidity
: Calculates the amount of token1 that corresponds to a given liquidity chunk within a specified price range.getLiquidityForAmount0
: Calculates the liquidity amount for a given amount of token0 and a liquidity chunk.getLiquidityForAmount1
: Calculates the liquidity amount for a given amount of token1 and a liquidity chunk.Casting
toUint128
: Safely casts a uint256
to a uint128
, reverting if the value doesn't fit in a uint128
.MulDiv Algorithms
mulDiv
: Performs a multiplication and division operation in a single step, with full precision, and checks for overflows.mulDiv64
, mulDiv96
, mulDiv128
, mulDiv192
: These functions perform similar operations as mulDiv
but divide by powers of 2 (specifically 2^64, 2^96, 2^128, and 2^192, respectively).Potential Security Flaws:
Unchecked Blocks: The use of unchecked
blocks can be risky as it suppresses overflow checks. However, in this context, it seems to be used intentionally to optimize gas costs, and the logic appears to be designed to avoid overflows.
Custom Errors: The library relies on custom errors defined elsewhere (e.g., Errors.InvalidTick()
and Errors.CastingError()
). It's important to ensure that these errors are properly defined and used consistently throughout the codebase.
External Dependencies: The library imports external contracts (Errors
and Constants
) and uses a custom type (LiquidityChunk
). It's crucial to review these dependencies for any potential vulnerabilities or inconsistencies.
Complexity: The mathematical operations, especially in the mulDiv
functions, are complex.
PanopticMath
which contains functions for performing various mathematical operations related to managing an Automated Market Maker (AMM) pool, specifically for a platform called Panoptic. The library seems to be designed to work with Uniswap v3 pools and includes functionality for calculating liquidity, converting token amounts based on pool prices, and encoding/decoding information related to options positions.
key components and potential security considerations:
getPoolId Function: This function takes a Uniswap v3 pool address and returns a 64-bit ID by shifting the address right by 96 bits. This is used to create a unique identifier for the pool. There are no apparent security issues with this function.
getFinalPoolId Function: This function generates a final pool ID by adding a base pool ID to a hashed combination of two token addresses and a fee. The hash is shifted right by 32 bits to fit into a 64-bit value. The unchecked block is used, which means that overflow checks are skipped. This is generally safe in this context since the result is cast to a uint64, but it's important to ensure that the overflow cannot be exploited in any way.
getLiquidityChunk Function: This function calculates the liquidity for a given leg of an options position. It uses custom types and libraries to calculate the liquidity based on the position size, tick range, and whether the asset is token0 or token1. The function contains complex financial logic and relies on external libraries (Math
, LeftRight
, LiquidityChunk
, TokenId
) for calculations. It's crucial that these external libraries are also audited for security vulnerabilities, as any issues there could affect the safety of this function.
convert0to1 and convert1to0 Functions: These functions convert amounts between token0 and token1 based on the square root of the price (sqrtPriceX96). They handle precision issues for high tick values by reducing the number of decimals. The unchecked block is used to skip overflow checks, which is a potential concern. However, the code seems to handle the precision reduction correctly, and the use of toInt256
suggests that the developers are aware of the potential for negative amounts. It's important to ensure that the inputs to these functions are validated elsewhere in the contract system to prevent any manipulation or unexpected behavior.
SafeTransferLib
that contains a single function safeTransferFrom
. This function is designed to safely transfer ERC20 tokens from one address to another, handling the case where the ERC20 token contract does not return a value.
key components and potential security considerations:
success
to store the outcome of the token transfer operation.call
to the ERC20 token contract. The flag ensures that the assembly code does not perform any unsafe memory operations.transferFrom
function of the ERC20 token contract. The function selector 0x23b872dd
Multicall
that allows multiple function calls to be made within a single transaction. This is particularly useful for batch operations, where multiple actions need to be performed atomically.
key components and potential security considerations:
It takes an array of bytes
called data
, where each element represents the calldata for a function call to be made on the contract that inherits from Multicall
.
It initializes an array of bytes
called results
to store the return data from each call.
It iterates over the data
array and performs a delegatecall
to the current contract's context (address(this)
) for each element. This means that the code of the called function will execute in the context of the calling contract, with the calling contract's storage, msg.sender, and msg.value.
If a call fails (i.e., success
is false
), it uses inline assembly to revert the transaction with the revert reason returned by the failed call. This is done by skipping the first 32 bytes of the result
(which is the length of the bytes array) and using the rest as the revert reason.
If the call succeeds, it stores the result in the results
array.
The function uses an unchecked
block to increment the loop counter, which means it does not check for overflows. This is safe here because the loop is bounded by the length of the data
array, which is a finite number.
Potential Security Flaws:
Reentrancy: The use of delegatecall
can potentially lead to reentrancy attacks if the inheriting contract has state-changing functions that are not properly protected. However, since this is an abstract contract, the responsibility lies with the implementer to ensure that reentrancy is not an issue.
Gas Limit: Each delegatecall
consumes gas, and there is a risk that if too many calls are included in the data
array, the transaction may run out of gas, causing all calls to fail. Callers should be aware of the block gas limit.
Error Propagation: The error handling mechanism replicates the revert reason as is. If the called function returns a revert reason that is not properly formatted or is maliciously crafted, it could potentially cause unexpected behavior.
Delegatecall to Arbitrary Functions: Since delegatecall
is used, any function of the contract can be called, including potentially sensitive administrative functions. The inheriting contract must manage permissions appropriately.
42 hours
#0 - c4-judge
2023-12-14T17:16:01Z
Picodes marked the issue as grade-b