Guardian's review of Limit Order Manager for Gamma Strategies, published July 2025. The report records 19 findings, including 2 high and 17 low.
- Published
- Review window
- July 10 to 18, 2025
- Language
- Solidity
- Chains
- Ethereum, Arbitrum, Optimism, Base, Polygon, BNB Chain
- Sector
- Yield and vaults
- 0 Critical
- 2 High
- 0 Medium
- 17 Low
- 0 Informational
Scope
Findings 19
-
H-01 High Missing External Pause/Unpause Functions Access Control Resolved
Description
Gamma has added
Pausableto the Limit Order Manager with the intention of pausing order creation when necessary. However, the contract does not expose any external functions to actually pause or unpause it.Pausableonly provides internal functions_pause()and_unpause()and expects the implementing contract to wrap them in access-controlled public or external functions.function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); } function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); }Recommendation
Add access-controlled external functions to allow authorized parties (e.g., the admin) to pause and unpause the contract.
-
H-02 High Potential Fee Theft Via Minting Rewards Resolved
Description
A malicious actor can mint a small position directly on the Algebra pool with the
recipientset to theLimitOrderManagercontract. This triggers a feeGrowth update for the position (keyed bytop,bottom,recipient) during themintcall.Later, when
LimitOrderManagerinvokesretrackPositionFee, it uses the updatedfeeGrowthInsidevalues, effectively skipping over the previously accumulated fees — thereby denying them to the legitimate order owners.The same vulnerability exists on Gamma’s Uniswap v4 Limit Order Manager as well.
Recommendation
Consider allowing addition of liquidity only if recipient is
msg.senderof the call viabeforeModifyPositionhook. -
L-01 Low Unused Variable
limitOrderPluginAddrBest Practices ResolvedDescription
The variable
limitOrderPluginAddris declared and is admin-writable, but it is never read in the contract. The same value can be fetched directly usingpool.plugin().Recommendation
Consider removing
limitOrderPluginAddrand its corresponding setter to reduce unnecessary storage and simplify the contract. -
L-02 Low Unused onlyKeeper Modifier Best Practices Resolved
Description
The
onlyKeepermodifier is defined in the contract but is never used. Instead, Gamma usesrequire(msg.sender == keeper, "...")directly wherever the keeper check is needed.Recommendation
Either remove the unused
onlyKeepermodifier to reduce dead code, or refactor the contract to consistently use the modifier instead of repeating therequirecheck. -
L-03 Low Duplicate Validation Call In Limit Orders Best Practices Resolved
Description
The function
validateScaleOrderSizes()is invoked even for limit orders. The function checks both the first and last order'samountagainstminRequired, which results in the same value being checked twice whenorders.length == 1(limit orders).if (orders[0].amount < minRequired) { revert MinimumAmountNotMet(orders[0].amount, minRequired); } if (orders[orders.length - 1].amount < minRequired) { revert MinimumAmountNotMet(orders[orders.length - 1].amount, minRequired); }Recommendation
Consider doing second validation only when
orders.length > 1 -
L-04 Low Repeated Encoding Of Callback Data Best Practices Resolved
Description
Gamma currently encodes
bytes memory callbackData = abi.encode(msg.sender);within each loop iteration, despite the encoded data being the same each time.Recommendation
Define
callbackDataonce before the loop and reuse it to avoid redundant encoding operations. -
L-05 Low Unused Errors Informational Resolved
Description
The
WrongTickRange,InvalidPrice, andPriceMustBeGreaterThanZeroerrors inTickLibraryare not used anywhere in the codebase and can be removed.Recommendation
Remove unused errors.
-
L-06 Low Risks Of Native ETH Handling Warning Resolved
Description
LimitOrderManageraccepts and handles native ETH.- Mint: Excess ETH is refunded immediately upon receipt.
- Claim: ETH is transferred back to the user.
However, ETH transfers hand over control to the user during the call, which can introduce subtle reentrancy paths or cause inconsistent state views.
Example – Mint:
createLimitOrderis called with excess ETH.- Ticks are validated such that both
bottomTickandtopTickare either above or below the current tick. - Excess ETH is refunded.
- User reenters the pool and performs a swap moving the tick from
bottomTick → currentTick → topTick. - This causes the mint to pull both tokens, instead of just one.
While this does not currently result in an exploit (since the contract doesn't hold tokens and they are pulled directly from users), it creates non-obvious and undesirable edge behavior.
Recommendation
- Ideally, remove native ETH support altogether and rely solely on WETH. This eliminates callback exposure and standardizes handling. An external wrapper can be used for ETH ↔ WETH conversion.
- Alternatively, at minimum, remove the refund logic in the mint path. Since the refund occurs before critical state changes, it introduces unnecessary risk.
Instead, enforce that
msg.valuemust exactly match the required amount. -
L-07 Low Inconsistent Reentrancy Protection Validation Resolved
Description
Some public/external functions in
LimitOrderManagerlack thenonReentrantmodifier, despite similar functions being protected.For example:
cancelPositionKeysis guarded withnonReentrant, whereascancelBatchOrderis not.- Inside
cancelOrder, during_handlePositionRemoval, before the user is removed frompositionContributors, andpositionStateis changed, if there is an ETH callback inclaim, one can reenter and use the unaltered state.
There are multiple such cases where one can reenter
LimitOrderManagerand use the stale or to be updated state.While we haven’t been able to exploit these cases profitably, we feel that allowing a set of possibilities where there is no benefit to the user is not ideal, since it leads to unnecessary exposure.
Recommendation
Consider applying
nonReentrantto all user-facing actions. -
L-08 Low Redundant Updates In _claimOrder Gas Optimization Resolved
Description
The
_claimOrderfunction updates the user's position. However the position is deleted right after. This is redundant and wastes a lot of gas.Recommendation
Consider not updating the state here, rely on the variables already in memory and be aware to not read from the not updated state variable.
-
L-09 Low Temporary Denial Of Service: Order Creation Warning Acknowledged
Description
A large swap can mark many tick ranges as
waitingForKeeper. While these ranges are queued for keeper execution, new orders that overlap these ticks cannot be placed. This behavior—though intentional—can block order creation across a wide range of ticks, creating a temporary denial-of-service condition for users and integrators.This mechanism can be unintuitive to third-party integrators or bots that expect open access to place limit orders at any time.
Recommendation
No changes to core logic are required. However, consider documenting this behavior explicitly**.**
-
L-10 Low Uninitialised Config Params Validation Acknowledged
Description
The
minAmount0andminAmount1params are zero right after deploying theLimitOrderManagercontract and the admin needs to set them manually with thesetMinAmountlater on.This might allow malicious actors to back run the deployment and create smaller positions than the admin would like to allow, which could lead to bad consequences.
Recommendation
Consider setting the parameters in the constructor or define hardcoded default value for them.
-
L-11 Low Cancel Flow Skips Nonce Update, Unlike Execute Path Unexpected Behavior Acknowledged
Description
In the
handlePositionRemovalcase of a cancel—wherepositionContributors's length is zero—thefeePerLiquidityfor the position is not reset, and the nonce is not updated.As a result, if a user creates an order, cancels it, and then creates it again with the same range, the
feePerLiquidityfrom the previous position persists and is reused in the nextcreateOrderfor that range.While we haven’t been able to exploit this (since the logic is additive), this behavior is inconsistent—especially considering the cancel flow mimics
execute(isActive = false,isWaitingForKeeper = false,removePositionFromTick).This inconsistency could lead to issues down the line.
Recommendation
Consider updating the nonce whenever
positionContributors's length is zero in cancel. -
L-12 Low Unnecessary Type Casting Informational Resolved
Description
On line 401 of the LimitOrderManager contract, the pool variable is cast to the IAlgebraPool interface.
IAlgebraPool(pool).burn(bottomTick, topTick, uint128(userLiquidity), "");However,
poolis already anIAlgebraPool, and the cast is unnecessary.Recommendation
Consider removing unnecessary casting
-
L-13 Low Inconsistency Risk Due To Mutable Tick Spacing Warning Resolved
Description
Algebra allows pool owners to modify
tickSpacingvia thesetTickSpacingfunction. However, Gamma’sLimitOrderManagertreats tick spacing as immutable and relies on it for all range-based calculations. IftickSpacingis changed on an Algebra pool after deployment, it will result in inconsistent and potentially incorrect behavior across Gamma's logic, including fee calculation, position placement, and execution.Recommendation
Although Algebra permits
tickSpacingupdates, such changes must not be performed on any pools associated with Gamma. This constraint should be explicitly documented for both internal developers and external integrators. -
L-14 Low Exposure To Read-Only Reentrancy Logical Error Resolved
Description
Gamma reads Algebra pool state directly from global storage slots (e.g.,
globalState()), which exposes it to read-only reentrancy attacks. Since Algebra uses locking to guard state consistency during critical operations, bypassing these locks by reading global state directly may lead to edge case inconsistencies — especially if exploited by another contract in the same transaction context.Recommendation
Replace direct calls to
globalState()withsafelyGetStateOfAMM, which respects the pool's internal locking mechanism. If additional state is needed beyond whatsafelyGetStateOfAMMprovides, first verify pool state viaisUnlocked()before proceeding with reads. -
L-15 Low Dust Can Be Lost In Claim Flow Rounding Resolved
Description
In the
_claimOrdera full share price calculation is performed twice to figure out the amount of fees that belong to the user and to the treasury.As both round down this could leave dust tokens stuck inside the contract.
Recommendation
Consider working with subtraction instead to be more precise.
-
L-16 Low Unused Imports Informational Resolved
Description
The following imports are not used in the codebase and can be removed:
console.solandConstants.solinLimitOrderManagerconsole.solinPositionManagement{console}fromTest.solandIAlgebraVirtualPool.solin LimitOrderPlugin
Recommendation
Consider removing unused imports.
-
L-17 Low sizeSkew Is Uncapped Best Practices Resolved
Description
Users can provide any
sizeSkewparam when calling thecreateScaleOrdersfunction except from 0. A very bigsizeSkewvalue leads to a buffer overflow Dos in thedenominator1calculation, in the_calculateOrderSizefunction.Recommendation
Consider capping the
sizeSkewparam.
No findings match.
More from Gamma Strategies
All 7 reports-
Unilaunch Launchpad and Limit Order Book
28 findings8 high 28 findings: 8 high, 8 medium, 5 low, 7 informational -
MultiPositionManager
83 findings1 high 83 findings: 1 high, 25 medium, 22 low, 35 informational -
Position Managers
58 findings5 high 58 findings: 5 high, 10 medium, 32 low, 11 informational -
PerpetualVault Mitigation Review
24 findings 24 findings: 7 medium, 17 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.
