fix: Security Audit - Dex223Pool.sol, Dex223PoolLib.sol & Dex223PoolDeployer.sol (17 vulnerabilities)#36
Open
rroland10 wants to merge 3 commits into
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>
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: Dex223Pool.sol, Dex223PoolLib.sol & Dex223PoolDeployer.sol
Scope
Full manual security audit of:
Executive Summary
17 vulnerabilities identified and fixed across three contracts, ranging from Critical to Informational severity.
The most severe issues centered around:
Part 1: Dex223PoolLib.sol (5 vulnerabilities + 5 design notes)
Findings Summary
Detailed Findings
V-LIB-03 — HIGH: Underflow in optimisticDelivery conversion deficit calculation
File: Dex223PoolLib.sol, optimisticDelivery function
Severity: High
Description:
When optimisticDelivery fails to transfer tokens directly and falls through to the conversion path, it computes
_amount - _balanceto determine how much to convert. In Solidity 0.7.6 (no built-in overflow protection), if_balance >= _amount(e.g., the transfer failed due to a paused token rather than insufficient balance), this subtraction silently underflows to a huge number (~2^256), which would attempt to drain the converter contract of all its tokens.Impact: An attacker could potentially drain the token converter of arbitrary amounts of tokens by triggering conversions with an underflowed deficit value.
Fix: Added
require(_amount > _balance, "LIB: NO_DEFICIT")before the subtraction and stored the result in a named_deficitvariable for clarity.V-LIB-04 — HIGH: safeIncreaseAllowance overflow on repeated approvals
File: Dex223PoolLib.sol, optimisticDelivery function
Severity: High
Description:
The original code called
SafeERC20.safeIncreaseAllowance(token, converter, 2**256 - 1)which internally computesnewAllowance = currentAllowance + (2**256 - 1). After the first successful approval (where currentAllowance becomes2**256 - 1), any subsequent call would overflow:(2**256 - 1) + (2**256 - 1)wraps around. Additionally, tokens like USDT require allowance to be reset to 0 before setting a new non-zero value.Impact: After the first conversion, all subsequent ERC-223 conversions in optimisticDelivery would revert due to the approval overflow, effectively bricking the token delivery mechanism for the affected token pair.
Fix: Replaced with the standard reset-then-approve pattern using
TransferHelper.safeApprove(token, converter, 0)followed byTransferHelper.safeApprove(token, converter, uint256(-1)). Removed the now-unused SafeERC20Limited import.V-LIB-09a — MEDIUM: Missing zero-address check on recipient in optimisticDelivery
File: Dex223PoolLib.sol, optimisticDelivery function
Severity: Medium
Description:
The optimisticDelivery function accepted address(0) as a valid recipient. Transferring tokens to address(0) permanently burns them with no way to recover.
Impact: Users could accidentally lose funds if a zero-address recipient is passed through collect, collectProtocol, or swap.
Fix: Added
require(_recipient != address(0), "LIB: ZERO_RECIPIENT")at the top of optimisticDelivery.V-LIB-10 — LOW: Unchecked transfer return value in ERC-223 conversion fallback
File: Dex223PoolLib.sol, optimisticDelivery function
Severity: Low
Description:
In the ERC-20-to-ERC-223 conversion path (the else branch in optimisticDelivery), the code used raw
IERC20Minimal(_token223).transfer(address(converter), ...)which ignores the return value. If the token returns false on failure instead of reverting, the conversion silently fails.Impact: Failed conversions produce misleading revert messages, making debugging difficult.
Fix: Replaced with
TransferHelper.safeTransfer(_token223, address(converter), _deficit)which properly checks the return value.V-LIB-01 — INFORMATIONAL: Reentrancy Protection Analysis
Description:
Dex223PoolLib functions (mint, burn, collect, collectProtocol, swap) lack individual lock modifiers. This was analyzed and determined to be by design: the Pool contract already holds the lock before delegatecalling into PoolLib. Adding a lock modifier to PoolLib would actually BREAK the system, since the Pool sets slot0.unlocked = false before the delegatecall, and PoolLib would then fail the
require(slot0.unlocked)check when operating on the Pool storage context.Direct calls to the PoolLib contract operate on the PoolLib own storage (which holds no funds, has no initialized state, and has no tokens), making this a non-issue. Comprehensive audit notes were added to document this design decision.
Part 2: Dex223Pool.sol (8 vulnerabilities)
Findings Summary
Detailed Findings
V1 — CRITICAL: Unrestricted delegatecall in tokenReceived
File: Dex223Pool.sol, tokenReceived function
The tokenReceived() function accepted arbitrary _data from any ERC-223 token sender and passed it directly to
address(this).delegatecall(_data). This allows an attacker to call any function in the pool context (includingset(),setFeeProtocol(), etc.), overwrite critical storage such as pool_lib, quote_lib, converter, factory, and token addresses, and hijack the entire pool.Fix: Added msg.sender validation (only pool tokens accepted) and function selector whitelist (only swap() and swapExactInput() allowed).
V2 — HIGH: swap() error handling masks revert reasons
When the delegatecall to pool_lib.swap() fails, the original code attempted
abi.decode(retdata, (string))which fails for non-ABI-encoded errors (Panic, custom errors).Fix: Replaced with raw assembly revert relay:
assembly { revert(add(retdata, 32), mload(retdata)) }.V3 — HIGH: swapExactInput missing adjustableSender
swapExactInput() was missing the adjustableSender modifier, making ERC-223 deposits unusable.
Fix: Added adjustableSender modifier.
V4 — MEDIUM: quoteSwap missing lock modifier
Fix: Added lock modifier to quoteSwap.
V5 — MEDIUM: Unrestricted receive()
Fix: Restricted to only accept ETH from pool token addresses (WETH).
V6 — MEDIUM: withdrawEther uses .transfer()
Fix: Replaced with .call{value:}() pattern.
V7 — MEDIUM: set() lacks validation
Fix: Added zero-address checks and re-initialization guard.
V8 — LOW: unwrapWETH9 dead code
Fix: Removed dead code, added zero-recipient and zero-amount checks.
Part 3: Dex223PoolDeployer.sol (4 vulnerabilities)
Findings Summary
Files Changed
Compilation Verification
All contracts compile successfully with
npx hardhat compile(Solidity 0.7.6, optimizer enabled, 5000 runs). No new warnings introduced by our changes.Test Plan
Dex223PoolLib.sol
Dex223Pool.sol
Dex223PoolDeployer.sol
General