Skip to content
$1,000,000 in security audit grants are live now, Apply here →

Security review · August 2025

Tokenized Aerodrome Position

for Impermax

Guardian's review of Tokenized Aerodrome Position for Impermax, published August 2025. The report records 11 findings, including 1 high and 2 medium.

Published
Review window
July 30 to August 4, 2025
Language
Solidity
Chains
Arbitrum, Base, Unichain, Hyperliquid
Sector
Lending
  • 0 Critical
  • 1 High
  • 2 Medium
  • 6 Low
  • 2 Informational

3 resolved · 8 acknowledged

Scope

1 file in scope · 187 nSLOC
FilenSLOCLines
contracts/extensions/TokenizedAeroCLPosition.sol187257

Findings 11

  1. H-01 High ETH Refund Can Revert Critical Functions DoS Resolved
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:167

    Description

    The increaseLiquidity function in the TokenizedAeroCLPosition contract allows users to add liquidity to an existing position. It does so by withdrawing the user’s position from the gauge, calling increaseLiquidity on the NonfungiblePositionManager with the original and additional amounts, and then redepositing the position.

    However, the NonfungiblePositionManager’s increaseLiquidity function ends by calling refundETH, which sends any residual ETH in the contract back to msg.sender. In this context, msg.sender is the TokenizedAeroCLPosition contract itself. Since the contract lacks a fallback function, the refund fails and causes the entire transaction to revert.

    A malicious actor could exploit this by sending a small amount of ETH to the NonfungiblePositionManager contract before a increaseLiquidity call, ensuring that the refundETH call fails and reverts the transaction.

    The same issue exists in the split function, which also calls mint on the NonfungiblePositionManager, triggering a similar refund. This is more critical since split is used in liquidation flows—meaning an attacker could block or delay liquidations by intentionally triggering a refund failure.

    Recommendation

    Consider adding a fallback function to ensure the contract can safely receive ETH refunds and avoid unexpected reverts.

  2. M-01 Medium Full Split Causes Revert Logical Error Resolved
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:167

    Description

    The split function allows users to split their position by a specified percentage, with 100% being the maximum. However, if a user attempts a full (100%) split, the decreaseAndMint function in the NfpmAeroInteractions library burns the original tokenId, since no liquidity remains in the original position.

    The issue arises when the split function subsequently attempts to redeposit the now-burned tokenId into the gauge. This causes a revert, making a full split impossible. Additionally, because the original token is burned without claiming any pending fees from the gauge, those rewards are lost for the user.

    A similar issue occurs when a user passes in 0% to split — the function attempts to mint a new position with no liquidity, which results in the new NFT never being minted.

    Recommendation

    Prevent 0% and 100% splits by modifying the logic to require percentage > 0 && percentage < 1e18, or alternatively, add checks to ensure fees are claimed and that the original token is not redeposited after being burned.

  3. M-02 Medium getPositionData Will Revert For Certain Prices DoS Acknowledged
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:108

    Description

    The getPositionData function is intended to return price and liquidity information for a given position. It computes values such as currentPrice, lowestPrice, and highestPrice using the current price and a user-defined safety margin. These values are constrained using the safe160 helper to ensure they fit within a uint160.

    However, in certain token pairs where the price can near the maximum limit representable in Uniswap V3, the computed highestPrice can overflow the uint160 range. When this occurs, the safe160 check will revert the transaction, even though the position itself remains valid within Uniswap.

    This is particularly problematic because getPositionData is used in several critical functions, including liquidation checks. A revert in this context could block or delay important protocol operations.

    Recommendation

    Ensure newly added token pairs do not result in price ranges exceeding the uint160 max, as this can disrupt core functions.

  4. L-01 Low Unclaimed Fees Are Lost After mint Logical Error Acknowledged
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:133

    Description

    When mint is called, it deposits into the gauge, which triggers a collection of any accrued LP fees. These fees are transferred to TokenizedAeroCLPosition. However, unless the caller explicitly invokes skim, these tokens remain unclaimed and can be taken by anyone. As a result, the user who called mint may unintentionally forfeit their accrued LP fees if they do not immediately follow up with a skim.

    Recommendation

    Consider calling skim(msg.sender) at the end of the mint function.

  5. L-02 Low Lack Of Slippage Protection MEV Acknowledged
    Location
    contracts/extensions/libraries/NfpmAeroInteractions.sol:49

    Description

    The increaseLiquidity function enables users to add liquidity to their existing position. However, both amount0Min and amount1Min are hardcoded to zero, meaning no slippage protection is applied during the minting process. This exposes users to unfavorable price movements or MEV attacks.

    The same issue exists in the split function, which reduces a position by a specified percentage and mints a new position with the withdrawn liquidity. Here too, amount0Min and amount1Min are set to zero, exposing users to similar risks.

    Recommendation

    Consider adding slippage protection by allowing user-defined amount0Min and amount1Min values or by setting reasonable minimum thresholds to reduce the risk of poor execution or MEV exploitation.

  6. L-03 Low ecrecover Allows Signature Malleability Signatures Acknowledged
    Location
    contracts/ImpermaxERC721.sol:160

    Description

    ImpermaxERC721.sol uses vanilla ecrecover for signer recovery, which is susceptible to malleability due to signature variations.

    This does not cause any immediate damage since the signed permit itself does not change. However, this should still be considered for improvement.

    Recommendation

    Consider using OpenZeppelin's ECDSA library for signer recovery to mitigate malleability risks.

  7. L-04 Low Unclaimed Dust After split Warning Acknowledged
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:167

    Description

    split removes some liquidity from the current LP position and mints a new one. Due to Aero rounding during minting, small residual (dust) token amounts can remain in the TokenizedAeroCLPosition contract. These residual tokens left behind can be skimmed by anyone, If not reclaimed, the owner of the original position loses these tokens.

    Recommendation

    Considering calling skim to the position owner after split.

  8. L-05 Low Rewards Cannot Be Claimed By EOA Unexpected Behavior Acknowledged
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:215

    Description

    In the claim function, _checkAuthorizedCollateral obtains owner of the tokenId from the Collateral contract:

    address collateral = _requireOwned(tokenId);
    address owner = IERC721(collateral).ownerOf(tokenId);
    

    This assumes that every user holding the wrapper NFT will deposit into the Collateral contract. If the wrapper NFT is still in a EOA wallet or separate contract, then the ownerOf call will revert. This blocks anyone from calling claim when the wrapper NFT is not being used as collateral.

    Recommendation

    Consider if this is expected behavior and document this risk for users.

  9. L-06 Low Unused Tokens Not Returned Logical Error Acknowledged
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:190

    Description

    The increaseLiquidity function in the TokenizedAeroCLPosition contract allows users to add liquidity to an existing position. The user must first transfer token0 and token1 to the contract, then call increaseLiquidity. The function withdraws the user’s position from the gauge, attempts to increase its liquidity using the transferred amounts, and then redeposits the position.

    However, if the provided token amounts are unbalanced, the actual liquidity added may use only part of the transferred tokens. Any remaining tokens stay in the contract without being returned to the user. These leftover tokens can later be claimed by anyone through the skim function, resulting in a potential loss of funds for the user.

    Recommendation

    Consider modifying increaseLiquidity to automatically return unused tokens to the user. Alternatively, clearly document this behaviour and ensure users are advised to call skim immediately after increaseLiquidity to recover any remaining tokens.

  10. I-01 Informational Naming Convention For _addGauge Informational Resolved
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:232

    Description

    The function _addGauge is declared external but is named with a leading underscore, a convention usually reserved for private or internal functions.

    Recommendation

    Rename _addGauge to addGauge to align with standard Solidity conventions:.

  11. I-02 Informational Gas Optimization For nonReentrant Modifier Gas Optimization Acknowledged
    Location
    contracts/extensions/TokenizedAeroCLPosition.sol:262

    Description

    The nonReentrant modifier in TokenizedAeroCLPosition.sol currently relies on a bool flag (_notEntered) to prevent reentrancy. However, this approach incurs high gas costs because each call involves a storage write from zero to nonzero (and vice versa).

    Recommendation

    Update the nonReentrant modifier to use a uint256 two-state pattern (as recommended by OpenZeppelin), which avoids zero-value writes and reduces gas usage by approximately 10,000–15,000 per call.

    // storage
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED     = 2;
    uint256 private _status;
    
    // in your constructor or initialize:
    _status = _NOT_ENTERED;
    
    // modifier
    modifier nonReentrant() {
      require(_status != _ENTERED, "Impermax: REENTERED");
      _status = _ENTERED;
      _;
      _status = _NOT_ENTERED;
    }
    

More from Impermax

  1. Impermax V3

    32 findings1 critical · 3 high 32 findings: 1 critical, 3 high, 6 medium, 22 low

Put your code through the same review.

This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.

Get a quote