From 9fe7122db66196a040928759ea12d8e3cd709f5f Mon Sep 17 00:00:00 2001 From: Qoder-Undefined <133337507+Nanafancy@users.noreply.github.com> Date: Thu, 25 Jun 2026 21:58:47 +0100 Subject: [PATCH] implemented the missing custom --- libs/engine/analyzers/solidity-analyzer.ts | 17 +++ .../errors/detect-missing-custom-errors.ts | 109 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 rules/optimization/errors/detect-missing-custom-errors.ts diff --git a/libs/engine/analyzers/solidity-analyzer.ts b/libs/engine/analyzers/solidity-analyzer.ts index bd9bd34..adda7b8 100644 --- a/libs/engine/analyzers/solidity-analyzer.ts +++ b/libs/engine/analyzers/solidity-analyzer.ts @@ -10,6 +10,7 @@ import { } from "../core/analyzer-interface"; import { detectStorageSlotCollisions } from "../../../rules/security/storage-layout/detect-storage-slot-collisions"; import { detectMissingImmutable } from "../../../rules/optimization/storage/detect-missing-immutable"; +import { detectMissingCustomErrors } from "../../../rules/optimization/errors/detect-missing-custom-errors"; export class SolidityAnalyzer extends BaseAnalyzer implements Analyzer { private rules: Rule[] = [ @@ -226,6 +227,22 @@ export class SolidityAnalyzer extends BaseAnalyzer implements Analyzer { }, documentationUrl: "https://docs.gasguard.dev/rules/sol-017", }, + { + id: "sol-018", + name: "Missing Custom Errors", + description: + "Detects revert strings that could be replaced with custom errors for gas efficiency", + severity: Severity.MEDIUM, + category: "gas-optimization", + enabled: true, + tags: ["errors", "revert", "gas", "deployment"], + estimatedGasImpact: { + min: 50, + max: 500, + typical: 150, + }, + documentationUrl: "https://docs.gasguard.dev/rules/sol-018", + }, ]; getName(): string; diff --git a/rules/optimization/errors/detect-missing-custom-errors.ts b/rules/optimization/errors/detect-missing-custom-errors.ts new file mode 100644 index 0000000..f74de33 --- /dev/null +++ b/rules/optimization/errors/detect-missing-custom-errors.ts @@ -0,0 +1,109 @@ +/** + * Detect Missing Custom Errors + * + * Detects contracts using revert strings instead of custom errors. + * Custom errors are more gas efficient and reduce deployment size. + */ + +export interface MissingCustomError { + line: number; + message: string; + revertString: string; +} + +export interface MissingCustomErrorsResult { + detected: boolean; + errors: MissingCustomError[]; + message: string; + suggestion: string; +} + +function stripComments(code: string): string { + let result = ''; + let inBlockComment = false; + const lines = code.split('\n'); + + for (const line of lines) { + if (!inBlockComment) { + const blockCommentStart = line.indexOf('/*'); + const blockCommentEnd = line.indexOf('*/'); + + if (blockCommentStart !== -1 && blockCommentEnd !== -1 && blockCommentStart < blockCommentEnd) { + result += line.substring(0, blockCommentStart) + line.substring(blockCommentEnd + 2); + } else if (blockCommentStart !== -1) { + inBlockComment = true; + result += line.substring(0, blockCommentStart); + } else if (line.trim().startsWith('//')) { + continue; + } else { + result += line; + } + } else { + const blockCommentEnd = line.indexOf('*/'); + if (blockCommentEnd !== -1) { + inBlockComment = false; + result += line.substring(blockCommentEnd + 2); + } + } + } + + return result; +} + +function hasCustomErrors(code: string): boolean { + return /error\s+[A-Za-z_$][\w$]*\s*\([^)]*\)\s*;/.test(code); +} + +function extractRevertStrings(code: string): MissingCustomError[] { + const findings: MissingCustomError[] = []; + const lines = code.split('\n'); + + const revertStringPattern = /(revert\s*\(\s*["'])([^"']+)(["']\s*\)|\.?)|\brequire\s*\(\s*[^,]+,\s*["']([^"']+)["']\s*\)/g; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const trimmed = line.trim(); + + if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) { + continue; + } + + let match; + while ((match = revertStringPattern.exec(line)) !== null) { + const revertString = match[2] || match[4]; + if (revertString) { + findings.push({ + line: i + 1, + message: `Revert string "${revertString}" detected. Consider using custom errors for gas efficiency.`, + revertString, + }); + } + } + } + + return findings; +} + +export function detectMissingCustomErrors(code: string): MissingCustomErrorsResult { + const strippedCode = stripComments(code); + const hasCustomErrorDefinitions = hasCustomErrors(strippedCode); + const revertFindings = extractRevertStrings(strippedCode); + + const revertStrings = revertFindings.filter(f => !hasCustomErrorDefinitions); + + if (revertStrings.length === 0) { + return { + detected: false, + errors: [], + message: 'No missing custom error opportunities detected.', + suggestion: '', + }; + } + + return { + detected: true, + errors: revertStrings, + message: `${revertStrings.length} revert string(s) detected. Consider using custom errors: ${revertStrings.map(e => e.revertString).join(', ')}.`, + suggestion: 'Define custom errors using the `error` keyword and use them with `revert CustomError()` instead of revert strings.', + }; +} \ No newline at end of file