fix: security audit fixes for NFTDescriptor.sol#42
Open
rroland10 wants to merge 1 commit into
Open
Conversation
- Fix uint8 overflow in escapeQuotes() loop counters and quote counter - Fix incorrect branding: 'Uniswap' -> 'Dex223' in generateName() - Add tickSpacing > 0 guard in tickToDecimalString() to prevent division by zero - Add decimal difference > 18 validation in adjustForDecimalPrecision() - Fix uint8 digit counter overflow and zero-value underflow in fixedPointToDecimalString() - Fix off-by-one in getCircleCoord(): modulo 255 -> 256 for full byte range - Update scale() input range from 255 to 256 to match corrected getCircleCoord - Add division-by-zero guard in scale() function - Widen numSigfigs from uint8 to uint256 in feeToPercentString() - Add compiler overrides for Revenue_old.sol and RevenueV1.sol in hardhat config 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:
NFTDescriptor.solAudited file:
contracts/dex-periphery/base/NFTDescriptor.solSeverity scale: Critical > High > Medium > Low > Informational
Vulnerability 1:
uint8Overflow inescapeQuotes()— Loop Counters and Quote CounterSeverity: High
Description: The
escapeQuotes()function usesuint8for both the loop iteratoriand thequotesCountvariable. Sinceuint8can only hold values 0–255, a token symbol longer than 255 bytes causes the loop counterito overflow back to 0, resulting in an infinite loop that exhausts all gas. Similarly, if a symbol contains more than 255 quote characters,quotesCountwraps to 0, silently skipping the escape logic and producing malformed JSON output that could break NFT metadata parsing or enable JSON injection.Attack vector: A malicious ERC-20 token can set its
symbol()to a string longer than 255 bytes (or with >255 quote chars). When a user mints a liquidity position with that token,constructTokenURI()enters an infinite loop, causing a denial-of-service that makestokenURI()permanently revert for that position.Fix: Changed
uint8 quotesCount→uint256 quotesCountand alluint8 iloop counters →uint256 i, eliminating the overflow risk for any input length.Vulnerability 2: Incorrect Protocol Branding — "Uniswap" in
generateName()Severity: Medium (Informational/Phishing Risk)
Description: The
generateName()function hardcodes'Uniswap - 'as the NFT name prefix. This is a Dex223 protocol, not Uniswap. This creates user confusion, potential phishing vectors (users may believe they are interacting with Uniswap), and brand/legal exposure. NFT marketplace displays (OpenSea, etc.) would show "Uniswap" as the position name despite the NFT belonging to Dex223.Fix: Changed
'Uniswap - '→'Dex223 - 'to reflect the correct protocol identity.Vulnerability 3: Division by Zero in
tickToDecimalString()WhentickSpacing == 0Severity: High
Description: The function performs
TickMath.MIN_TICK / tickSpacingandTickMath.MAX_TICK / tickSpacingwithout validating thattickSpacing > 0. IftickSpacingis 0 (due to a misconfigured pool or a crafted parameter), the EVM triggers a panic revert (division by zero), making the entiretokenURI()call permanently revert for that NFT position. While pools should always have non-zero tick spacing, this is a defense-in-depth issue — the library should not rely on upstream invariants.Fix: Added
require(tickSpacing > 0, 'NFTDesc: tickSpacing zero')at the start of the function for explicit early validation with a descriptive revert reason.Vulnerability 4: Silent Misbehavior in
adjustForDecimalPrecision()for Decimal Differences > 18Severity: Medium
Description: The function computes
difference = abs(baseTokenDecimals - quoteTokenDecimals). Ifdifference > 18, the function falls through to theelsebranch and returns the rawsqrtRatioX96without any adjustment. This silently produces wildly inaccurate price displays for exotic token pairs with extreme decimal differences (e.g., a token with 0 decimals paired with one that has 77 decimals — which is technically possible for non-standard ERC-20s). Rather than displaying wrong prices, the function should revert.Fix: Added
require(difference <= 18, 'NFTDesc: decimal diff > 18')to explicitly reject unsupported decimal configurations instead of silently producing garbage output.Vulnerability 5:
uint8Digit Counter Overflow and Zero-Value Underflow infixedPointToDecimalString()Severity: High
Description: The function uses
uint8 digitsto count the number of digits invalue. Two issues:valuehas more than 255 digits (theoretically possible for extreme sqrt price ratios multiplied by10**44), theuint8counter wraps around, producing an incorrect digit count and corrupting all subsequent buffer index calculations — leading to out-of-bounds writes or garbage output.value == 0, the while loop never executes,digitsremains 0, anddigits - 1underflows. In Solidity 0.7.x (no automatic underflow checks), this wraps to 255, causing completely wrong buffer allocations.Fix:
uint256 digitCountto prevent overflow.require(digitCount > 0, 'NFTDesc: zero value')before the subtraction to guard against underflow.uint8only after validation:uint8 digits = uint8(digitCount - 1).Vulnerability 6: Off-by-One Error in
getCircleCoord()—% 255Should Be% 256Severity: Low
Description: The function returns
(sliceTokenHex(tokenAddress, offset) * tokenId) % 255. SincesliceTokenHexreturns auint8(range 0–255), the product modulo 255 can never produce 255 as a result (since255 % 255 == 0). This means the output range is [0, 254] instead of the expected [0, 255], creating a subtle bias. Additionally, thescale()function divides byinMx - inMn = 255 - 0 = 255, but the actual range only reaches 254, meaning the maximum output coordinate can never actually reach the maximum of the output range — the SVG circles can never reach certain positions.Fix: Changed
% 255→% 256for full byte range [0, 255], and correspondingly updated allscale()calls frominMx = 255→inMx = 256to maintain correct mapping.Vulnerability 7: Missing Division-by-Zero Guard in
scale()Severity: Low
Description: The
scale()function computes.div(inMx.sub(inMn))without validating thatinMx > inMn. If called withinMx == inMn, the subtraction yields 0 and the division reverts with an opaque SafeMath error. While current callers always pass valid ranges, adding an explicit guard provides defense-in-depth and a clear revert message.Fix: Added
require(inMx > inMn, 'NFTDesc: invalid scale range')at the start of the function.Vulnerability 8:
uint8Overflow Risk infeeToPercentString()—numSigfigsCounterSeverity: Low
Description: The
numSigfigsvariable usesuint8, limiting it to 255. Whileuint24 feehas at most 7 digits (max value 16,777,215), making overflow practically impossible with current fee values, this is a defensive coding issue. If the fee type were ever widened or the function reused, theuint8would silently overflow. Additionally,numSigfigsis later used in SafeMath operations that expectuint256, requiring implicit widening conversions.Fix: Changed
uint8 numSigfigs→uint256 numSigfigsand added explicituint8()casts where needed for struct field assignments.Additional Fix: Hardhat Compiler Configuration
Severity: Informational
Description: Added Solidity 0.8.19 compiler overrides for
Revenue_old.solandRevenueV1.solinhardhat.config.ts. These files usepragma solidity ^0.8.13but no compiler was configured for them, causingHH606compilation failures that prevented the entire project from compiling.Summary Table
uint8overflow inescapeQuotes()loop/countertickToDecimalString()uint8digit counter overflow + zero-value underflow% 255→% 256ingetCircleCoord()scale()uint8overflow risk infeeToPercentString()Test Plan
npx hardhat compileescapeQuotes()with token symbol > 255 bytes — should not revert or loop infinitelyescapeQuotes()with symbol containing > 255 quote characters — should properly escape all quotesconstructTokenURI()produces JSON with "Dex223" branding (not "Uniswap")tickToDecimalString()withtickSpacing = 0— should revert with'NFTDesc: tickSpacing zero'adjustForDecimalPrecision()withbaseTokenDecimals = 0, quoteTokenDecimals = 77— should revert with'NFTDesc: decimal diff > 18'fixedPointToDecimalString()with edge-case sqrtRatioX96 values near MIN_SQRT_RATIO and MAX_SQRT_RATIOgetCircleCoord()can produce output value 255 (was impossible before)scale()correctly maps [0, 256] input range to output rangesfeeToPercentString()with standard fee tiers: 100, 500, 3000, 10000Made with Cursor