fix: security audit fixes for Dex223QuoteLib.sol#37
Open
rroland10 wants to merge 4 commits into
Open
Conversation
Comprehensive security audit of the Dex223Pool contract identifying and
fixing 8 vulnerabilities ranging from critical to low severity:
- V1 (Critical): Restrict tokenReceived delegatecall to whitelisted selectors
and validate caller is a pool token
- V2 (High): Fix swap() error propagation - raw revert relay instead of
broken abi.decode(string)
- V3 (High): Add missing adjustableSender modifier to swapExactInput
- V4 (Medium): Add lock modifier to quoteSwap to prevent reentrancy
- V5 (Medium): Restrict receive() to only accept ETH from pool tokens (WETH)
- V6 (Medium): Replace .transfer() with .call{value:} in withdrawEther
- V7 (Medium): Add zero-address validation and one-time-set guard to set()
- V8 (Low): Clean up unwrapWETH9 dead code and add input validation
Also fixes test compilation issues in MockTimeDex223Pool and
MickTimeDex223PoolLib, and adds missing Revenue file compiler overrides.
Co-authored-by: Cursor <cursoragent@cursor.com>
- V-DEPLOYER-02: Add zero-address validation for factory, token0, token1 - V-DEPLOYER-03: Add identical-token check (token0 != token1) - V-DEPLOYER-04: Restore NatSpec documentation (was commented out) - V-DEPLOYER-05: Rename contract UniswapV3PoolDeployer -> Dex223PoolDeployer (update Dex223Factory.sol inheritance accordingly) Co-authored-by: Cursor <cursoragent@cursor.com>
V-LIB-03: Added underflow protection in optimisticDelivery conversion path. The _amount - _balance subtraction could underflow in Solidity 0.7.6 when the transfer fails for reasons other than insufficient balance (e.g. paused token), producing a huge value that drains the converter. V-LIB-04: Replaced SafeERC20.safeIncreaseAllowance with TransferHelper.safeApprove reset-to-zero pattern. The old code computed currentAllowance + (2^256 - 1) which overflows when currentAllowance > 0 (after the first approval). V-LIB-09a: Added zero-address recipient check in optimisticDelivery to prevent permanent token burns. V-LIB-10: Replaced raw IERC20Minimal.transfer() with TransferHelper.safeTransfer() in the ERC-223 conversion fallback path to properly check return values. Also added comprehensive audit notes (V-LIB-01, V-LIB-02a-c, V-LIB-05a-b) documenting why lock modifiers are NOT added to PoolLib functions (the Pool lock modifier already protects via delegatecall, and adding lock here would break the delegatecall pattern). Co-authored-by: Cursor <cursoragent@cursor.com>
V-QL-02: Replace safeIncreaseAllowance with safeApprove(0)+safeApprove(max) to prevent uint256 overflow on repeated converter approvals. V-QL-03: Add underflow protection in optimisticDelivery conversion path when _balance >= _amount (e.g. paused token). V-QL-04: Add recipient zero-address check in optimisticDelivery to prevent permanent token burns. V-QL-05: Remove SafeERC20Limited import, use TransferHelper.safeTransfer for ERC-223 fallback path (consistent with Dex223PoolLib fix V-LIB-04). V-QL-01, V-QL-06, V-QL-07: Add audit notes documenting state-mutation+revert pattern safety, dead code analysis, and access control analysis. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security Audit Report: Dex223QuoteLib.sol
Scope
Full security audit of
contracts/dex-core/Dex223QuoteLib.sol— the delegatecall library used byDex223Pool.quoteSwap()to simulate swap execution and return estimated output amounts.Vulnerabilities Found & Fixes Applied
V-QL-02 — safeIncreaseAllowance Overflow on Repeated Approvals (HIGH)
Location:
optimisticDelivery(), line ~373 (original)Description:
The original code called
SafeERC20.safeIncreaseAllowance(token, converter, 2**256 - 1)to approve the token converter. ThesafeIncreaseAllowancefunction internally computesnewAllowance = currentAllowance + value. On the second invocation (whencurrentAllowance > 0), adding2**256 - 1causes a uint256 overflow. In Solidity 0.7.6 (which has no built-in overflow protection), this wraps silently, producing a near-zero or incorrect allowance value.Impact: After the first successful approval, subsequent conversion attempts will fail or set an incorrectly small allowance, potentially blocking token deliveries.
Fix: Replaced with
TransferHelper.safeApprove(token, converter, 0)followed byTransferHelper.safeApprove(token, converter, uint256(-1)). This reset-then-approve pattern is the standard approach and also handles tokens like USDT that require the allowance to be zero before setting a new value.V-QL-03 — Underflow in Conversion Path Deficit Calculation (MEDIUM)
Location:
optimisticDelivery(), line ~362 (original)Description:
When the initial
transfer()fails and the code enters the conversion fallback path, it computes_amount - _balanceto determine how many tokens need to be converted. If_balance >= _amount(which can happen if the transfer failed for reasons other than insufficient balance — e.g., a paused token, a blacklisted recipient, or a reentrancy guard), this subtraction underflows in Solidity 0.7.6, producing a huge value (close to2**256). This would attempt to convert an astronomical number of tokens from the converter, either draining it or reverting with a confusing error.Impact: Potential drain of the token converter contract, or opaque revert errors that are impossible to debug.
Fix: Added
require(_amount > _balance, "QLIB: NO_DEFICIT")before the subtraction, and stored the result in a named_deficitvariable for clarity.V-QL-04 — Missing Zero-Address Recipient Check in optimisticDelivery (MEDIUM)
Location:
optimisticDelivery(), beginning of functionDescription:
The
optimisticDeliveryfunction accepts an arbitrary_recipientaddress and transfers tokens to it. If_recipient == address(0), most ERC-20 tokens will accept the transfer but the tokens are permanently burned (sent to the zero address with no way to recover them).Impact: Permanent loss of pool funds if
optimisticDeliveryis called with a zero-address recipient (e.g., through a misconfigured swap call).Fix: Added
require(_recipient != address(0), "QLIB: ZERO_RECIPIENT")at the start of the function.V-QL-05 — Unsafe Raw transfer() in ERC-223 Conversion Fallback and Unnecessary Import (LOW)
Location:
optimisticDelivery(), ERC-223-to-ERC-20 conversion branch; import sectionDescription:
Two issues:
IERC20Minimal(_token223).transfer(address(converter), ...)which ignores the return value. If the ERC-223 token returnsfalseon failure instead of reverting, the conversion silently fails and the subsequentsafeTransferof the output token reverts with the opaque "ST" error.SafeERC20Limited.solimport is no longer needed sincesafeIncreaseAllowancewas replaced withTransferHelper.safeApprove.Impact: Silent failures in the conversion path lead to confusing error messages; unnecessary import increases bytecode size.
Fix: Replaced raw
transfer()withTransferHelper.safeTransfer()which checks the return value. Removed theSafeERC20Limitedimport. Both changes are consistent with the identical fixes applied inDex223PoolLib.sol(V-LIB-04, V-LIB-10).Audit Notes (Informational — No Code Changes Required)
V-QL-01 — State Mutation + Revert Pattern Safety Analysis (INFORMATIONAL)
Description:
quoteSwap()intentionally mutates storage state (slot0, liquidity, fee growth, protocol fees, ticks, oracle observations) during swap simulation, then reverts via inline assembly to return the computed amounts in the revert data. When called viadelegatecallfromDex223Pool.quoteSwap(), the revert rolls back ALL state changes within the delegatecall frame.Assessment: This pattern is safe as long as (a)
quoteSwapalways terminates with a revert (guaranteed by the assembly blocks at the end — bothifandelsebranches revert), and (b) the function is only invoked via delegatecall (enforced by the Pool contract). Direct calls operate on the QuoteLib's own uninitialized storage and always revert, so they are harmless.V-QL-06 — Dead Code Analysis (INFORMATIONAL)
Description:
Functions
_modifyPosition,_updatePosition,balance0,balance1,checkTicks, and_blockTimestampare defined in QuoteLib but are NOT used byquoteSwap(). TheoptimisticDeliveryfunction is also dead code in the context ofquoteSwap(the function reverts before reaching any token delivery).Assessment: These functions exist for storage layout consistency with
Dex223PoolLib.sol. Since QuoteLib is used viadelegatecall, its storage variable declarations must match the Pool's layout exactly. The functions are retained to maintain this correspondence and for potential future use. The fixes tooptimisticDeliverywere still applied since the function exists in the contract and could theoretically be called if the contract structure changes.V-QL-07 — No Access Control (INFORMATIONAL)
Description:
Anyone can call
quoteSwap()directly on the deployed QuoteLib contract (not via delegatecall from the Pool).Assessment: This is safe because (1) the function always reverts, so no state changes persist, and (2) direct calls operate on the QuoteLib's own uninitialized storage (not the Pool's). No access control is needed.
Summary of Changes
Test Plan
npx hardhat compile(confirmed: PASS)