Skip to content

[AUD-029] [INFO] Signed conversion has no slow-reference differential fuzz (unsigned has LibParseDecimalSlow) #99

Closed

Description

@thedavidmeister

Whole-repo audit 2026-08-24 at commit 3e76bab (audit skill 0.35.0). Pass 2 status: ledgered (audit/mutation-test-scans.json, scan of 2026-08-24T08:55:39Z at 35496d8).

test/src/lib/parse/LibParseDecimal.unsafeDecimalStringToSignedInt.t.sol:17-255

The unsigned conversion has a spec-derived slow reference (LibParseDecimalSlow) fuzzed over arbitrary and digit-dense bytes, but the signed conversion has none — its coverage is a targeted branch matrix (all branches are individually discriminated: sign consumption, both overflow boundaries including int256 min, error propagation for all three selectors, lone '-', '+', '--'). The asymmetry means a future edit to the signed wrapper is protected only by the enumerated cases, not by an oracle over arbitrary input.

Proposed fix

Extend test/lib/parse/LibParseDecimalSlow.sol with a signed reference and fuzz it from the existing signed test contract:

function decimalStringToSignedIntSlow(bytes memory data) internal pure returns (bytes4, int256) {
    if (data.length == 0) {
        return (ParseEmptyDecimalString.selector, 0);
    }
    bool isNeg = data[0] == "-";
    bytes memory digits = data;
    if (isNeg) {
        digits = new bytes(data.length - 1);
        for (uint256 i = 0; i < digits.length; i++) {
            digits[i] = data[i + 1];
        }
    }
    (bytes4 errorSelector, uint256 value) = decimalStringToIntSlow(digits);
    if (errorSelector != bytes4(0)) {
        return (errorSelector, 0);
    }
    if (isNeg) {
        if (value > uint256(type(int256).max) + 1) {
            return (ParseDecimalOverflow.selector, 0);
        }
        if (value == uint256(type(int256).max) + 1) {
            return (bytes4(0), type(int256).min);
        }
        // forge-lint: disable-next-line(unsafe-typecast)
        return (bytes4(0), -int256(value));
    }
    if (value > uint256(type(int256).max)) {
        return (ParseDecimalOverflow.selector, 0);
    }
    // forge-lint: disable-next-line(unsafe-typecast)
    return (bytes4(0), int256(value));
}

// In TestLibParseDecimalUnsafeDecimalStringToSignedInt:
function testUnsafeStrToSignedIntReference(bytes memory data) external pure {
    (bytes4 slowSelector, int256 slowValue) = LibParseDecimalSlow.decimalStringToSignedIntSlow(data);
    (bytes4 errorSelector, int256 result) = LibParseDecimal.unsafeDecimalStringToSignedInt(
        Pointer.unwrap(data.dataPointer()), Pointer.unwrap(data.endDataPointer())
    );
    assertEq(errorSelector, slowSelector);
    assertEq(result, slowValue);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

auditAudit findinginfoAudit findingpass2Audit finding

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions