diff --git a/.gitignore b/.gitignore index 9e72fb7..98d8286 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ report.md .vscode/tasks.json scope.txt repos -reports \ No newline at end of file +reports +.DS_Store \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index e6bc7fb..9d7a720 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,6 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "editor.codeActionsOnSave": { - "source.fixAll": true + "source.fixAll": "explicit" } } diff --git a/README.md b/README.md index 2911e83..924a032 100644 --- a/README.md +++ b/README.md @@ -15,20 +15,61 @@ - [Installation](#installation) - [Contributing](#contributing) -## Usage +### Installing 4naly3er -```bash -yarn analyze +#### Prerequisites: + +You must have `node` and `yarn` installed on your system. + +#### Installation: + +``` +git clone [https://github.com/cb-elileers/analyzer](https://github.cbhq.net/security/solidity-analyzer) +cd analyzer +npm i --force --save-dev +yarn +``` + +## Using 4naly3er -# Example -yarn analyze contracts scope.example.txt +#### Basic Usage + +``` +yarn analyze ``` -- `BASE_PATH` is a relative path to the folder containing the smart contracts. -- `SCOPE_FILE` is an optional file containing a specific smart contracts scope (see [scope.example.txt](./scope.example.txt)) -- `GITHUB_URL` is an optional url to generate links to github in the report -- For remappings, add `remappings.txt` to `BASE_PATH`. -- The output will be saved in a `report.md` file. +For example: `yarn analyze ~/Documents/op-enclave/` + +**Where Options Are:** + +- BASE_PATH is a **required** parameter which points to the folder containing the smart contract project. +- '-s, --scope scopeFile' .txt file containing the contest scope +- '-g, --github githubURL' github url to generate links to code +- '-o, --out reportPath' Path for Markdown report +- '-l, --listfiles' List analyzed files in Markdown Report +- '--legacyscope scopeFile' Path for legacy scope file +- '--sarif [outputPath]' Generate SARIF report, optionally include path to report. Default is analyzer.sarif +- '--skip-info' Skip info issues +- '--skip-gas' Skip gas issues +- '--skip-low' Skip low issues +- '--skip-medium' Skip medium issues +- '--skip-high' Skip high issues +- '--skip, --skip-detectors detectorID' Skip specific detectors by id + +For any remappings, Forge can generate, or you can add, remappings.txt to the BASE_PATH and 4naly3er will use them accordingly. + +Output from the tool is stored in **report.md** within the 4naly3er folder. To keep all documents related to a project together, it is advisable to run `mv report.md ` to deposit the report into the smart contract project's folder. (Click here to see an example report)[https://gist.github.com/Picodes/e9f1bb87ae832695694175abd8f9797f] + +#### Scope File Generation + +Sometimes, we only want to run our tooling on certain contracts within a repository. To do so, we define those contracts we want to be in scope in a **scope.txt** file. + +To autogenerate a scope.txt file that excludes dependencies, we can use the below script: + +``` +cd +find . | grep "\.sol" | grep -v "\./lib/" | grep -v typechain | grep -v node_modules | grep -v artifacts | grep -v "\.t\.sol">scope.txt +``` ## Example Reports diff --git a/src/analyze.ts b/src/analyze.ts index 322f18c..52fdbc1 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -1,4 +1,4 @@ -import { InputType, Instance, Issue, IssueTypes } from './types'; +import { InputType, Instance, Issue, Analysis } from './types'; import { lineFromIndex } from './utils'; const issueTypesTitles = { @@ -13,9 +13,9 @@ const issueTypesTitles = { * @notice Runs the given issues on files and generate the report markdown string * @param githubLink optional url to generate links */ -const analyze = (files: InputType, issues: Issue[], githubLink?: string): string => { +const analyze = (files: InputType, issues: Issue[], githubLink?: string): Analysis[] => { let result = ''; - let analyze: { issue: Issue; instances: Instance[] }[] = []; + let analyze: Analysis[] = []; for (const issue of issues) { let instances: Instance[] = []; // If issue is a regex @@ -61,68 +61,7 @@ const analyze = (files: InputType, issues: Issue[], githubLink?: string): string } } - /** Summary */ - let c = 0; - if (analyze.length > 0) { - result += `\n## ${issueTypesTitles[analyze[0].issue.type]}\n\n`; - result += '\n| |Issue|Instances|\n|-|:-|:-:|\n'; - for (const { issue, instances } of analyze) { - c++; - result += `| [${issue.type}-${c}](#${issue.type}-${c}) | ${issue.title} | ${instances.length} |\n`; - } - } - - /** Issue breakdown */ - c = 0; - for (const { issue, instances } of analyze) { - c++; - result += `### [${issue.type}-${c}] ${issue.title}\n`; - if (!!issue.description) { - result += `${issue.description}\n`; - } - if (!!issue.impact) { - result += '\n#### Impact:\n'; - result += `${issue.impact}\n`; - } - result += `\n*Instances (${instances.length})*:\n`; - let previousFileName = ''; - for (const o of instances.sort((a, b) => { - if (a.fileName < b.fileName) return -1; - if (a.fileName > b.fileName) return 1; - return !!a.line && !!b.line && a.line < b.line ? -1 : 1; - })) { - if (o.fileName !== previousFileName) { - if (previousFileName !== '') { - result += `\n${'```'}\n`; - if (!!githubLink) { - result += `[Link to code](${githubLink + previousFileName})\n`; - } - result += `\n`; - } - result += `${'```'}solidity\nFile: ${o.fileName}\n`; - previousFileName = o.fileName; - } - - // Insert code snippet - const lineSplit = o.fileContent?.split('\n'); - const offset = o.line.toString().length; - result += `\n${o.line}: ${lineSplit[o.line - 1]}\n`; - if (!!o.endLine) { - let currentLine = o.line + 1; - while (currentLine <= o.endLine) { - result += `${' '.repeat(offset)} ${lineSplit[currentLine - 1]}\n`; - currentLine++; - } - } - } - result += `\n${'```'}\n`; - if (!!githubLink) { - result += `[Link to code](${githubLink + previousFileName})\n`; - } - result += `\n`; - } - - return result; + return analyze; }; export default analyze; diff --git a/src/index.ts b/src/index.ts index cade67e..9902721 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,12 +9,49 @@ import main from './main'; // ================================= PARAMETERS ================================ -const basePath = - process.argv.length > 2 ? (process.argv[2].endsWith('/') ? process.argv[2] : process.argv[2] + '/') : 'contracts/'; -const scopeFile = process.argv.length > 3 && process.argv[3].endsWith('txt') ? process.argv[3] : null; -const githubLink = process.argv.length > 4 && process.argv[4] ? process.argv[4] : null; -const out = 'report.md'; +import { program } from 'commander'; +import { IssueTypes } from './types'; -// ============================== GENERATE REPORT ============================== +program + .argument('[basePath]', 'Path were the contracts lies') + .option('-s, --scope ', '.txt file containing the contest scope') + .option('-g, --github ', 'github url to generate links to code') + .option('-o, --out ', 'Path for Markdown report') + .option('-l, --listfiles', 'List analyzed files in Markdown Report') + .option('--legacyscope ', 'Path for legacy scope file') + .option('--sarif [outputPath]', 'Generate SARIF report, optionally include path to report. Default is analyzer.sarif') + .option('--skip-info', 'Skip info issues') + .option('--skip-gas', 'Skip gas issues') + .option('--skip-low', 'Skip low issues') + .option('--skip-medium', 'Skip medium issues') + .option('--skip-high', 'Skip high issues') + .option('--skip, --skip-detectors ', 'Skip specific detectors by id') + .action((basePath:string, options) => { + basePath = basePath ||'contracts/'; + basePath = basePath.endsWith('/') ? basePath : `${basePath}/`; -main(basePath, scopeFile, githubLink, out); + const sarif = options.sarif === true ? 'analyzer.sarif' : options.sarif; + + const severityToRun: IssueTypes[] = []; + if(!options.skipInfo) severityToRun.push(IssueTypes.NC); + if(!options.skipGas) severityToRun.push(IssueTypes.GAS); + if(!options.skipLow) severityToRun.push(IssueTypes.L); + if(!options.skipMedium) severityToRun.push(IssueTypes.M); + if(!options.skipHigh) severityToRun.push(IssueTypes.H); + + const skipDetectors = options.skipDetectors || []; + const skipDetectorsLower = skipDetectors.map(detector => detector.toLowerCase()); // Convert to lowercase to avoid case-sensitive issues + + console.log(`basePath: ${basePath}`); + console.log(`scope: ${options.scope||'----'}`); + console.log(`github: ${options.github||'----'}`); + console.log(`out: ${options.out||'report.md'}`); + console.log(`legacyScope: ${options.legacyscope||'----'}`); + console.log(`sarif: ${options.sarif||'----'}`); + console.log('Severity to run: ', severityToRun); + console.log('Skipping detectors: ', skipDetectorsLower); + + console.log('*****************************') + // ============================== RUN ANALYZER ============================== + main(basePath, options.scope, options.github, options.out || 'report.md', severityToRun, skipDetectorsLower, options.legacyscope, options.sarif, options.listfiles); + }).parse(); diff --git a/src/issues/GAS/ERC721A.ts b/src/issues/GAS/ERC721A.ts index db9dfc2..ea988ab 100644 --- a/src/issues/GAS/ERC721A.ts +++ b/src/issues/GAS/ERC721A.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'erc721A', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Use ERC721A instead ERC721', diff --git a/src/issues/GAS/_msgSender.ts b/src/issues/GAS/_msgSender.ts index bebd52c..449a567 100644 --- a/src/issues/GAS/_msgSender.ts +++ b/src/issues/GAS/_msgSender.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: '_msgSender', regexOrAST: 'Regex', type: IssueTypes.GAS, title: "Don't use `_msgSender()` if not supporting EIP-2771", diff --git a/src/issues/GAS/addPlusEqual.ts b/src/issues/GAS/addPlusEqual.ts index 4d03b69..a6b6757 100644 --- a/src/issues/GAS/addPlusEqual.ts +++ b/src/issues/GAS/addPlusEqual.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'addPlusEqual', regexOrAST: 'Regex', type: IssueTypes.GAS, title: '`a = a + b` is more gas effective than `a += b` for state variables (excluding arrays and mappings)', diff --git a/src/issues/GAS/addressZero.ts b/src/issues/GAS/addressZero.ts index a6851ad..060aff2 100644 --- a/src/issues/GAS/addressZero.ts +++ b/src/issues/GAS/addressZero.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import { Expression, SourceUnit } from 'solidity-ast'; const issue: ASTIssue = { + id: 'addressZero', regexOrAST: 'AST', type: IssueTypes.GAS, title: 'Use assembly to check for `address(0)`', diff --git a/src/issues/GAS/assignUpdateArray.ts b/src/issues/GAS/assignUpdateArray.ts index 47f90b9..7e54e56 100644 --- a/src/issues/GAS/assignUpdateArray.ts +++ b/src/issues/GAS/assignUpdateArray.ts @@ -3,6 +3,7 @@ import { findAll } from 'solidity-ast/utils'; import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'assignUpdateArray', regexOrAST: 'AST', type: IssueTypes.GAS, title: '`array[index] += amount` is cheaper than `array[index] = array[index] + amount` (or related variants)', diff --git a/src/issues/GAS/boolCompare.ts b/src/issues/GAS/boolCompare.ts index aa2c1d8..565a1c5 100644 --- a/src/issues/GAS/boolCompare.ts +++ b/src/issues/GAS/boolCompare.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'boolCompare', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Comparing to a Boolean constant', diff --git a/src/issues/GAS/boolIncursOverhead.ts b/src/issues/GAS/boolIncursOverhead.ts index 6010583..af02d50 100644 --- a/src/issues/GAS/boolIncursOverhead.ts +++ b/src/issues/GAS/boolIncursOverhead.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'boolIncursOverhead', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Using bools for storage incurs overhead', diff --git a/src/issues/GAS/bytesConstantsVsString.ts b/src/issues/GAS/bytesConstantsVsString.ts new file mode 100644 index 0000000..75d8b24 --- /dev/null +++ b/src/issues/GAS/bytesConstantsVsString.ts @@ -0,0 +1,11 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +const issue: RegexIssue = { + id: 'bytesConstantsVsString', + regexOrAST: 'Regex', + type: IssueTypes.GAS, + title: 'Bytes constants are more efficient than string constants', + regex: /string.+constant/g, +}; + +export default issue; diff --git a/src/issues/GAS/cacheArrayLength.ts b/src/issues/GAS/cacheArrayLength.ts index 8ea4316..ef189e4 100644 --- a/src/issues/GAS/cacheArrayLength.ts +++ b/src/issues/GAS/cacheArrayLength.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'cacheArrayLength', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Cache array length outside of loop', diff --git a/src/issues/GAS/cacheVariable.ts b/src/issues/GAS/cacheVariable.ts index 9c70a4a..0345ba6 100644 --- a/src/issues/GAS/cacheVariable.ts +++ b/src/issues/GAS/cacheVariable.ts @@ -4,6 +4,7 @@ import { getStorageVariable, instanceFromSRC } from '../../utils'; import { Identifier } from 'solidity-ast'; const issue: ASTIssue = { + id: 'cacheVariable', regexOrAST: 'AST', type: IssueTypes.GAS, title: 'State variables should be cached in stack variables rather than re-reading them from storage', diff --git a/src/issues/GAS/calldataViewFunctions.ts b/src/issues/GAS/calldataViewFunctions.ts index a264ba5..13636b4 100644 --- a/src/issues/GAS/calldataViewFunctions.ts +++ b/src/issues/GAS/calldataViewFunctions.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'calldataViewFunctions', regexOrAST: 'AST', type: IssueTypes.GAS, title: 'Use calldata instead of memory for function arguments that do not get mutated', diff --git a/src/issues/GAS/canUseUnchecked.ts b/src/issues/GAS/canUseUnchecked.ts index d9c0a6e..88d4588 100644 --- a/src/issues/GAS/canUseUnchecked.ts +++ b/src/issues/GAS/canUseUnchecked.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'canUseUnchecked', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'For Operations that will not overflow, you could use unchecked', diff --git a/src/issues/GAS/customErrors.ts b/src/issues/GAS/customErrors.ts index 6f1de28..49807fc 100644 --- a/src/issues/GAS/customErrors.ts +++ b/src/issues/GAS/customErrors.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'customErrors', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Use Custom Errors instead of Revert Strings to save Gas', diff --git a/src/issues/GAS/delegatecallAddressCheck.ts b/src/issues/GAS/delegatecallAddressCheck.ts index 9b4f36d..1e05693 100644 --- a/src/issues/GAS/delegatecallAddressCheck.ts +++ b/src/issues/GAS/delegatecallAddressCheck.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'delegatecallAddressCheck', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Avoid contract existence checks by using low level calls', diff --git a/src/issues/GAS/dontCacheIfUsedOnce.ts b/src/issues/GAS/dontCacheIfUsedOnce.ts index 88e3503..bd25f07 100644 --- a/src/issues/GAS/dontCacheIfUsedOnce.ts +++ b/src/issues/GAS/dontCacheIfUsedOnce.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'dontCacheIfUsedOnce', regexOrAST: 'AST', type: IssueTypes.GAS, title: 'Stack variable used as a cheaper cache for a state variable is only used once', diff --git a/src/issues/GAS/immutableConstructor.ts b/src/issues/GAS/immutableConstructor.ts index 3556a02..a208ec8 100644 --- a/src/issues/GAS/immutableConstructor.ts +++ b/src/issues/GAS/immutableConstructor.ts @@ -4,6 +4,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC, topLevelFiles, getStorageVariable } from '../../utils'; const issue: ASTIssue = { + id: 'immutableConstructor', regexOrAST: 'AST', type: IssueTypes.GAS, title: 'State variables only set in the constructor should be declared `immutable`', diff --git a/src/issues/GAS/initializeDefaultValue.ts b/src/issues/GAS/initializeDefaultValue.ts index 7ed425f..e2649c5 100644 --- a/src/issues/GAS/initializeDefaultValue.ts +++ b/src/issues/GAS/initializeDefaultValue.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'initializeDefaultValue', regexOrAST: 'Regex', type: IssueTypes.GAS, title: "Don't initialize variables with default value", diff --git a/src/issues/GAS/longRevertString.ts b/src/issues/GAS/longRevertString.ts index f98aa69..50e5b4b 100644 --- a/src/issues/GAS/longRevertString.ts +++ b/src/issues/GAS/longRevertString.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'longRevertString', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Reduce the size of error messages (Long revert Strings)', diff --git a/src/issues/GAS/payableFunctions.ts b/src/issues/GAS/payableFunctions.ts index cef54af..69172b6 100644 --- a/src/issues/GAS/payableFunctions.ts +++ b/src/issues/GAS/payableFunctions.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'payableFunctions', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Functions guaranteed to revert when called by normal users can be marked `payable`', diff --git a/src/issues/GAS/postIncrement.ts b/src/issues/GAS/postIncrement.ts index ef91d4b..614adc2 100644 --- a/src/issues/GAS/postIncrement.ts +++ b/src/issues/GAS/postIncrement.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'postIncrement', regexOrAST: 'Regex', type: IssueTypes.GAS, title: '`++i` costs less gas compared to `i++` or `i += 1` (same for `--i` vs `i--` or `i -= 1`)', diff --git a/src/issues/GAS/privateForConstants.ts b/src/issues/GAS/privateForConstants.ts index 0267a11..232863f 100644 --- a/src/issues/GAS/privateForConstants.ts +++ b/src/issues/GAS/privateForConstants.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'privateForConstants', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Using `private` rather than `public` for constants, saves gas', diff --git a/src/issues/GAS/shiftInsteadOfDiv.ts b/src/issues/GAS/shiftInsteadOfDiv.ts index d0467dd..152efbb 100644 --- a/src/issues/GAS/shiftInsteadOfDiv.ts +++ b/src/issues/GAS/shiftInsteadOfDiv.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'shiftInsteadOfDiv', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Use shift right/left instead of division/multiplication if possible', diff --git a/src/issues/GAS/smallUintIncrement.ts b/src/issues/GAS/smallUintIncrement.ts new file mode 100644 index 0000000..bf7d40a --- /dev/null +++ b/src/issues/GAS/smallUintIncrement.ts @@ -0,0 +1,11 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +const issue: RegexIssue = { + id: 'smallUintIncrement', + regexOrAST: 'Regex', + type: IssueTypes.GAS, + title: 'Incrementing with a smaller type than `uint256` incurs overhead', + regex: /for.+uint(?!(256| ))/g, +}; + +export default issue; diff --git a/src/issues/GAS/splitRequireStatement.ts b/src/issues/GAS/splitRequireStatement.ts index b0f705e..02fcce0 100644 --- a/src/issues/GAS/splitRequireStatement.ts +++ b/src/issues/GAS/splitRequireStatement.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'splitRequireStatement', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Splitting require() statements that use && saves gas', diff --git a/src/issues/GAS/storageVsMemoryStructArray.ts b/src/issues/GAS/storageVsMemoryStructArray.ts new file mode 100644 index 0000000..57d006d --- /dev/null +++ b/src/issues/GAS/storageVsMemoryStructArray.ts @@ -0,0 +1,14 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +const issue: RegexIssue = { + id: 'storageVsMemoryStructArray', + regexOrAST: 'Regex', + type: IssueTypes.GAS, + title: 'Use `storage` instead of `memory` for structs/arrays', + description: + 'Using `memory` copies the struct or array in memory. Use `storage` to save the location in storage and have cheaper reads:', + regex: /memory.+\=/g, + startLineModifier: 1, +}; + +export default issue; diff --git a/src/issues/GAS/superfluousEventField.ts b/src/issues/GAS/superfluousEventField.ts index b4339d7..088b3ed 100644 --- a/src/issues/GAS/superfluousEventField.ts +++ b/src/issues/GAS/superfluousEventField.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'superfluousEventField', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Superfluous event fields', diff --git a/src/issues/GAS/thisExternal.ts b/src/issues/GAS/thisExternal.ts index ef42ac0..929df17 100644 --- a/src/issues/GAS/thisExternal.ts +++ b/src/issues/GAS/thisExternal.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'thisExternal', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Use of `this` instead of marking as `public` an `external` function', diff --git a/src/issues/GAS/uintBoolBitMaps.ts b/src/issues/GAS/uintBoolBitMaps.ts index 0681ed8..7c26970 100644 --- a/src/issues/GAS/uintBoolBitMaps.ts +++ b/src/issues/GAS/uintBoolBitMaps.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'uintBoolBitMaps', regexOrAST: 'Regex', type: IssueTypes.GAS, title: '`uint256` to `bool` `mapping`: Utilizing Bitmaps to dramatically save on Gas', diff --git a/src/issues/GAS/uncheckedIncrement.ts b/src/issues/GAS/uncheckedIncrement.ts new file mode 100644 index 0000000..29e97cd --- /dev/null +++ b/src/issues/GAS/uncheckedIncrement.ts @@ -0,0 +1,11 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +const issue: RegexIssue = { + id: 'uncheckedIncrement', + regexOrAST: 'Regex', + type: IssueTypes.GAS, + title: 'Increments can be `unchecked` in for-loops', + regex: /for.+\+\+/g, +}; + +export default issue; diff --git a/src/issues/GAS/uncheckedIncrements.ts b/src/issues/GAS/uncheckedIncrements.ts index e85c4fd..236c6ea 100644 --- a/src/issues/GAS/uncheckedIncrements.ts +++ b/src/issues/GAS/uncheckedIncrements.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'uncheckedIncrements', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Increments/decrements can be unchecked in for-loops', diff --git a/src/issues/GAS/unsignedComparison.ts b/src/issues/GAS/unsignedComparison.ts index 6884d4f..f66b6f1 100644 --- a/src/issues/GAS/unsignedComparison.ts +++ b/src/issues/GAS/unsignedComparison.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'unsignedComparison', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'Use != 0 instead of > 0 for unsigned integer comparison', diff --git a/src/issues/GAS/uselessInternal.ts b/src/issues/GAS/uselessInternal.ts index 0763fb1..b9695dc 100644 --- a/src/issues/GAS/uselessInternal.ts +++ b/src/issues/GAS/uselessInternal.ts @@ -4,6 +4,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC, topLevelFiles } from '../../utils'; const issue: ASTIssue = { + id: 'uselessInternal', regexOrAST: 'AST', type: IssueTypes.GAS, title: '`internal` functions not called by the contract should be removed', diff --git a/src/issues/GAS/wethHardcode.ts b/src/issues/GAS/wethHardcode.ts index 6c817a5..f2370fb 100644 --- a/src/issues/GAS/wethHardcode.ts +++ b/src/issues/GAS/wethHardcode.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'wethHardcode', regexOrAST: 'Regex', type: IssueTypes.GAS, title: 'WETH address definition can be use directly', diff --git a/src/issues/H/comparisonOutsideCondition.ts b/src/issues/H/comparisonOutsideCondition.ts index 6768d65..ac200a4 100644 --- a/src/issues/H/comparisonOutsideCondition.ts +++ b/src/issues/H/comparisonOutsideCondition.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'comparisonOutsideCondition', regexOrAST: 'Regex', type: IssueTypes.H, title: 'Incorrect comparison implementation', diff --git a/src/issues/H/delegateCallInLoop.ts b/src/issues/H/delegateCallInLoop.ts index 895f518..724ffe5 100644 --- a/src/issues/H/delegateCallInLoop.ts +++ b/src/issues/H/delegateCallInLoop.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'delegateCallInLoop', regexOrAST: 'Regex', type: IssueTypes.H, title: 'Using `delegatecall` inside a loop', diff --git a/src/issues/H/get_dy_underlyingFlashLoan.ts b/src/issues/H/get_dy_underlyingFlashLoan.ts index dc6f37f..1cf5764 100644 --- a/src/issues/H/get_dy_underlyingFlashLoan.ts +++ b/src/issues/H/get_dy_underlyingFlashLoan.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'getDyUnderlyingFlashLoan', regexOrAST: 'Regex', type: IssueTypes.H, title: '`get_dy_underlying()` is not a flash-loan-resistant price', diff --git a/src/issues/H/msg.valueInLoop.ts b/src/issues/H/msg.valueInLoop.ts index aa2f8aa..b713392 100644 --- a/src/issues/H/msg.valueInLoop.ts +++ b/src/issues/H/msg.valueInLoop.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC, lineFromIndex } from '../../utils'; const issue: ASTIssue = { + id: 'msgValueInLoop', regexOrAST: 'AST', type: IssueTypes.H, title: 'Using `msg.value` in a loop', diff --git a/src/issues/H/wstETHPriceStEth.ts b/src/issues/H/wstETHPriceStEth.ts index c943b1f..6837320 100644 --- a/src/issues/H/wstETHPriceStEth.ts +++ b/src/issues/H/wstETHPriceStEth.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'wstEthPriceStEth', regexOrAST: 'Regex', type: IssueTypes.H, title: "`wstETH`'s functions operate on units of stEth, not Eth", diff --git a/src/issues/L/2stepowner.ts b/src/issues/L/2stepowner.ts index e51d2b6..0bcfcd7 100644 --- a/src/issues/L/2stepowner.ts +++ b/src/issues/L/2stepowner.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: '2stepowner', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Use a 2-step ownership transfer pattern', diff --git a/src/issues/L/DivissionPrecissionLossASTParser.ts b/src/issues/L/DivissionPrecissionLossASTParser.ts new file mode 100644 index 0000000..bd0da2b --- /dev/null +++ b/src/issues/L/DivissionPrecissionLossASTParser.ts @@ -0,0 +1,35 @@ +import { InputType, IssueTypes, Instance, ASTIssue } from '../../types'; +import { findAll } from 'solidity-ast/utils'; +import { instanceFromSRC } from '../../utils'; + +const issue: ASTIssue = { + id: 'divisionPrecissionLossASTParser', + regexOrAST: 'AST', + type: IssueTypes.L, + title: + 'Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.', + description: + 'Division by large numbers may result in the result being zero, due to Solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator.', + detector: (files: InputType): Instance[] => { + let instances: Instance[] = []; + + for (const file of files) { + if (!!file.ast) { + for (const node of findAll('BinaryOperation', file.ast)) { + // Look for Address(X).balance + if ( + node.nodeType === 'BinaryOperation' && + node.operator === '/' + // node.expression.nodeType === 'FunctionCall' && + // node.expression.typeDescriptions.typeString === 'address' + ) { + instances.push(instanceFromSRC(file, node.src)); + } + } + } + } + return instances; + }, +}; + +export default issue; \ No newline at end of file diff --git a/src/issues/L/MultiplicationBeforeDivision.ts b/src/issues/L/MultiplicationBeforeDivision.ts index 8a51b65..0812acb 100644 --- a/src/issues/L/MultiplicationBeforeDivision.ts +++ b/src/issues/L/MultiplicationBeforeDivision.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes } from '../../types'; import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'multiplicationBeforeDivision', regexOrAST: 'AST', type: IssueTypes.L, title: 'Precision Loss due to Division before Multiplication', diff --git a/src/issues/L/NFTNoHandleHardForks.ts b/src/issues/L/NFTNoHandleHardForks.ts index 04d2618..25fc914 100644 --- a/src/issues/L/NFTNoHandleHardForks.ts +++ b/src/issues/L/NFTNoHandleHardForks.ts @@ -4,6 +4,7 @@ import { instanceFromSRC, lineFromIndex } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'nftNoHandleHardForks', regexOrAST: 'AST', type: IssueTypes.L, title: 'NFT doesn\'t handle hard forks', diff --git a/src/issues/L/SomeERC20TransferRevertsOn0.ts b/src/issues/L/SomeERC20TransferRevertsOn0.ts index 7195619..f9b7c81 100644 --- a/src/issues/L/SomeERC20TransferRevertsOn0.ts +++ b/src/issues/L/SomeERC20TransferRevertsOn0.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'someERC20TransferRevertsOn0', regexOrAST: 'AST', type: IssueTypes.L, title: 'Some tokens may revert when zero value transfers are made', diff --git a/src/issues/L/address0Check.ts b/src/issues/L/address0Check.ts index 8acf299..ece3e69 100644 --- a/src/issues/L/address0Check.ts +++ b/src/issues/L/address0Check.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { getStorageVariable, instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'address0Check', regexOrAST: 'AST', type: IssueTypes.L, title: 'Missing checks for `address(0)` when assigning values to address state variables', diff --git a/src/issues/L/avoidEcrecover.ts b/src/issues/L/avoidEcrecover.ts index 187cb14..4fa1789 100644 --- a/src/issues/L/avoidEcrecover.ts +++ b/src/issues/L/avoidEcrecover.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'avoidEcrecover', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Use of `ecrecover` is susceptible to signature malleability', diff --git a/src/issues/L/avoidEncodePacked.ts b/src/issues/L/avoidEncodePacked.ts index 590ce7a..b7379f5 100644 --- a/src/issues/L/avoidEncodePacked.ts +++ b/src/issues/L/avoidEncodePacked.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'avoidEncodePacked', regexOrAST: 'AST', type: IssueTypes.L, title: diff --git a/src/issues/L/avoidTxOrigin.ts b/src/issues/L/avoidTxOrigin.ts index f5b36a5..7e72961 100644 --- a/src/issues/L/avoidTxOrigin.ts +++ b/src/issues/L/avoidTxOrigin.ts @@ -1,6 +1,7 @@ import {IssueTypes} from "../../types"; const issue = { + id: 'avoidTxOrigin', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Use of `tx.origin` is unsafe in almost every context', diff --git a/src/issues/L/calc_token_amountAlreadyHasSlippage.ts b/src/issues/L/calc_token_amountAlreadyHasSlippage.ts index 40c2268..24c0495 100644 --- a/src/issues/L/calc_token_amountAlreadyHasSlippage.ts +++ b/src/issues/L/calc_token_amountAlreadyHasSlippage.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'calcTokenAmountSlippage', regexOrAST: 'Regex', type: IssueTypes.L, title: '`calc_token_amount()` has slippage added on top of Curve\'s calculated slippage', diff --git a/src/issues/L/decimalsNotERC20.ts b/src/issues/L/decimalsNotERC20.ts index b1f536b..ecd8942 100644 --- a/src/issues/L/decimalsNotERC20.ts +++ b/src/issues/L/decimalsNotERC20.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'decimalsNotERC20', regexOrAST: 'Regex', type: IssueTypes.L, title: '`decimals()` is not a part of the ERC-20 standard', diff --git a/src/issues/L/decimalsNotUint8.ts b/src/issues/L/decimalsNotUint8.ts index e577512..1283d79 100644 --- a/src/issues/L/decimalsNotUint8.ts +++ b/src/issues/L/decimalsNotUint8.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'decimalsNotUint8', regexOrAST: 'Regex', type: IssueTypes.L, title: '`decimals()` should be of type `uint8`', diff --git a/src/issues/L/deprecatedApprove.ts b/src/issues/L/deprecatedApprove.ts index 7585617..5debbfb 100644 --- a/src/issues/L/deprecatedApprove.ts +++ b/src/issues/L/deprecatedApprove.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'deprecatedApprove', regexOrAST: 'AST', type: IssueTypes.L, title: 'Deprecated approve() function', diff --git a/src/issues/L/deprecatedFunctions.ts b/src/issues/L/deprecatedFunctions.ts index c6376be..130b233 100644 --- a/src/issues/L/deprecatedFunctions.ts +++ b/src/issues/L/deprecatedFunctions.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'deprecatedFunctions', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Do not use deprecated library functions', diff --git a/src/issues/L/deprecatedSafeApprove.ts b/src/issues/L/deprecatedSafeApprove.ts index e6e152c..2ad1614 100644 --- a/src/issues/L/deprecatedSafeApprove.ts +++ b/src/issues/L/deprecatedSafeApprove.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'deprecatedSafeApprove', regexOrAST: 'Regex', type: IssueTypes.L, title: '`safeApprove()` is deprecated', diff --git a/src/issues/L/deprecatedSetupRole.ts b/src/issues/L/deprecatedSetupRole.ts index cabc6cf..84fe44f 100644 --- a/src/issues/L/deprecatedSetupRole.ts +++ b/src/issues/L/deprecatedSetupRole.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'deprecatedSetupRole', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Deprecated _setupRole() function', diff --git a/src/issues/L/disableInitImpl.ts b/src/issues/L/disableInitImpl.ts index 052852a..92c794e 100644 --- a/src/issues/L/disableInitImpl.ts +++ b/src/issues/L/disableInitImpl.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'disableInitImpl', regexOrAST: 'AST', type: IssueTypes.L, title: 'Do not leave an implementation contract uninitialized', diff --git a/src/issues/L/div0NotPrevented.ts b/src/issues/L/div0NotPrevented.ts index fcd61d5..dde109e 100644 --- a/src/issues/L/div0NotPrevented.ts +++ b/src/issues/L/div0NotPrevented.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'div0NotPrevented', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Division by zero not prevented', diff --git a/src/issues/L/domainSeparatorReplayAttack.ts b/src/issues/L/domainSeparatorReplayAttack.ts index c160264..0e07d34 100644 --- a/src/issues/L/domainSeparatorReplayAttack.ts +++ b/src/issues/L/domainSeparatorReplayAttack.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'domainSeparatorReplayAttack', regexOrAST: 'Regex', type: IssueTypes.L, title: '`domainSeparator()` isn\'t protected against replay attacks in case of a future chain split ', diff --git a/src/issues/L/duplicateImports.ts b/src/issues/L/duplicateImports.ts index 348d20e..c56a28d 100644 --- a/src/issues/L/duplicateImports.ts +++ b/src/issues/L/duplicateImports.ts @@ -5,6 +5,7 @@ import util from 'util'; // regex: /(\{\})|(\{ \})/gi, const issue: ASTIssue = { + id: 'duplicateImports', regexOrAST: 'AST', type: IssueTypes.L, title: 'Duplicate import statements', diff --git a/src/issues/L/emptyBody.ts b/src/issues/L/emptyBody.ts index be7e2ab..a4f9576 100644 --- a/src/issues/L/emptyBody.ts +++ b/src/issues/L/emptyBody.ts @@ -5,6 +5,7 @@ import util from 'util'; // regex: /(\{\})|(\{ \})/gi, const issue: ASTIssue = { + id: 'emptyBody', regexOrAST: 'AST', type: IssueTypes.L, title: 'Empty Function Body - Consider commenting why', diff --git a/src/issues/L/emptyFallbackRequire.ts b/src/issues/L/emptyFallbackRequire.ts index 74234e2..29603c4 100644 --- a/src/issues/L/emptyFallbackRequire.ts +++ b/src/issues/L/emptyFallbackRequire.ts @@ -5,6 +5,7 @@ import util from 'util'; // regex: /(\{\})|(\{ \})/gi, const issue: ASTIssue = { + id: 'emptyFallbackRequire', regexOrAST: 'AST', type: IssueTypes.L, title: 'Empty `receive()/payable fallback()` function does not authenticate requests', diff --git a/src/issues/L/extCallLoop.ts b/src/issues/L/extCallLoop.ts index 653b046..d6f86c9 100644 --- a/src/issues/L/extCallLoop.ts +++ b/src/issues/L/extCallLoop.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { getStorageVariable, instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'extCallLoop', regexOrAST: 'AST', type: IssueTypes.L, title: 'External calls in an un-bounded `for-`loop may result in a DOS', diff --git a/src/issues/L/extCallRecipientGas.ts b/src/issues/L/extCallRecipientGas.ts index 6819d0c..f79c530 100644 --- a/src/issues/L/extCallRecipientGas.ts +++ b/src/issues/L/extCallRecipientGas.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'extCallRecipientGas', regexOrAST: 'Regex', type: IssueTypes.L, title: 'External call recipient may consume all transaction gas', diff --git a/src/issues/L/fallBackLackPayable.ts b/src/issues/L/fallBackLackPayable.ts index 26d3611..27a34c1 100644 --- a/src/issues/L/fallBackLackPayable.ts +++ b/src/issues/L/fallBackLackPayable.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'fallBackLackPayable', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Fallback lacking `payable`', diff --git a/src/issues/L/frontRunnableInitializer.ts b/src/issues/L/frontRunnableInitializer.ts index ce39dcb..87adfbe 100644 --- a/src/issues/L/frontRunnableInitializer.ts +++ b/src/issues/L/frontRunnableInitializer.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'frontRunnableInitializer', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Initializers could be front-run', diff --git a/src/issues/L/includeTimestampAtDeadline.ts b/src/issues/L/includeTimestampAtDeadline.ts index 6d2d631..cf2f8f4 100644 --- a/src/issues/L/includeTimestampAtDeadline.ts +++ b/src/issues/L/includeTimestampAtDeadline.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'includeTimestampAtDeadline', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Signature use at deadlines should be allowed', diff --git a/src/issues/L/lackSlippage.ts b/src/issues/L/lackSlippage.ts index a2f9862..0958abf 100644 --- a/src/issues/L/lackSlippage.ts +++ b/src/issues/L/lackSlippage.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'lackSlippage', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Lack of Slippage check', diff --git a/src/issues/L/lowLevelCallsToCustomAddress.ts b/src/issues/L/lowLevelCallsToCustomAddress.ts new file mode 100644 index 0000000..7940005 --- /dev/null +++ b/src/issues/L/lowLevelCallsToCustomAddress.ts @@ -0,0 +1,17 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +const issue: RegexIssue = { + id: 'lowLevelCallsToCustomAddress', + regexOrAST: 'Regex', + type: IssueTypes.L, + title: + ' Low Level Calls to Custom Addresses', + description: + "Contracts should avoid making low-level calls to custom addresses, especially if these calls are based on address parameters in the function. Such behavior can lead to unexpected execution of untrusted code. Instead, consider using Solidity's high-level function calls or contract interactions.", + regex: /\(\w*\)[.]call\{\w*:\s+\w*\}\(\w*""\)/g, + +}; + +export default issue; +// This is my implementation of the issue M-01 Low level call to Custom address https://github.com/code-423n4/2023-10-ethena/blob/main/bot-report.md?fbclid=IwAR3IkbR6BhKSliDi2r-yRIU4tkGGbIPpBGHX7IA8NOAlMyatBN8o0BGF61I#l-08-function-parameters-in-public-accessible-functions-need-address0-check +//File: contracts/EthenaMinting.sol (bool success,) = (beneficiary).call{value: amount}(""); \ No newline at end of file diff --git a/src/issues/L/mathmaxInt.ts b/src/issues/L/mathmaxInt.ts index fd16f80..f1390fd 100644 --- a/src/issues/L/mathmaxInt.ts +++ b/src/issues/L/mathmaxInt.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'mathmaxInt', regexOrAST: 'Regex', type: IssueTypes.L, title: '`Math.max(,0)` used with `int` cast to `uint`', diff --git a/src/issues/L/mintAndBurnAddress0.ts b/src/issues/L/mintAndBurnAddress0.ts index 13ee6b0..16532d7 100644 --- a/src/issues/L/mintAndBurnAddress0.ts +++ b/src/issues/L/mintAndBurnAddress0.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'mintAndBurnAddress0', regexOrAST: 'AST', type: IssueTypes.L, title: 'Prevent accidentally burning tokens', diff --git a/src/issues/L/nftOwnershipHardfork.ts b/src/issues/L/nftOwnershipHardfork.ts index b911476..d720973 100644 --- a/src/issues/L/nftOwnershipHardfork.ts +++ b/src/issues/L/nftOwnershipHardfork.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'nftOwnershipHardfork', regexOrAST: 'AST', type: IssueTypes.L, title: 'NFT ownership doesn\'t support hard forks', diff --git a/src/issues/L/ownerRenounceDuringPause.ts b/src/issues/L/ownerRenounceDuringPause.ts index 85909d8..7cbde85 100644 --- a/src/issues/L/ownerRenounceDuringPause.ts +++ b/src/issues/L/ownerRenounceDuringPause.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'ownerRenounceDuringPause', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Owner can renounce while system is paused', diff --git a/src/issues/L/possibleRounding.ts b/src/issues/L/possibleRounding.ts index db2edb0..18e36ba 100644 --- a/src/issues/L/possibleRounding.ts +++ b/src/issues/L/possibleRounding.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'possibleRounding', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Possible rounding issue', diff --git a/src/issues/L/pragmaExpDeprecated.ts b/src/issues/L/pragmaExpDeprecated.ts index 1eb7b7b..00c124e 100644 --- a/src/issues/L/pragmaExpDeprecated.ts +++ b/src/issues/L/pragmaExpDeprecated.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'pragmaExpDeprecated', regexOrAST: 'Regex', type: IssueTypes.L, title: '`pragma experimental ABIEncoderV2` is deprecated', diff --git a/src/issues/L/precisionLoss.ts b/src/issues/L/precisionLoss.ts index bd4747c..8f1d569 100644 --- a/src/issues/L/precisionLoss.ts +++ b/src/issues/L/precisionLoss.ts @@ -2,6 +2,7 @@ import { IssueTypes, RegexIssue } from '../../types'; //@note there will be false positives const issue: RegexIssue = { + id: 'precisionLoss', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Loss of precision', diff --git a/src/issues/L/push0Solc0820.ts b/src/issues/L/push0Solc0820.ts index 5afdace..d71c60f 100644 --- a/src/issues/L/push0Solc0820.ts +++ b/src/issues/L/push0Solc0820.ts @@ -3,6 +3,7 @@ import { findAll } from 'solidity-ast/utils'; import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'push0Solc0820', regexOrAST: 'AST', type: IssueTypes.L, title: 'Solidity version 0.8.20+ may not work on other chains due to `PUSH0`', diff --git a/src/issues/L/safeTransferOwnership.ts b/src/issues/L/safeTransferOwnership.ts index ae90414..235f1a7 100644 --- a/src/issues/L/safeTransferOwnership.ts +++ b/src/issues/L/safeTransferOwnership.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'safeTransferOwnership', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Use `Ownable2Step.transferOwnership` instead of `Ownable.transferOwnership`', diff --git a/src/issues/L/solc1314OptimizerBug.ts b/src/issues/L/solc1314OptimizerBug.ts index 2b5ebd0..e029940 100644 --- a/src/issues/L/solc1314OptimizerBug.ts +++ b/src/issues/L/solc1314OptimizerBug.ts @@ -3,6 +3,7 @@ import { findAll } from 'solidity-ast/utils'; import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'solc1314OptimizerBug', regexOrAST: 'AST', type: IssueTypes.L, title: 'File allows a version of solidity that is susceptible to an assembly optimizer bug', diff --git a/src/issues/L/sweepingRescuing.ts b/src/issues/L/sweepingRescuing.ts index 6e47f2f..68ef690 100644 --- a/src/issues/L/sweepingRescuing.ts +++ b/src/issues/L/sweepingRescuing.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'sweepingRescuing', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Sweeping may break accounting if tokens with multiple addresses are used', diff --git a/src/issues/L/symbolNotERC20.ts b/src/issues/L/symbolNotERC20.ts index 18aad60..cd63081 100644 --- a/src/issues/L/symbolNotERC20.ts +++ b/src/issues/L/symbolNotERC20.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'symbolNotERC20', regexOrAST: 'Regex', type: IssueTypes.L, title: '`symbol()` is not a part of the ERC-20 standard', diff --git a/src/issues/L/timestampReliance.ts b/src/issues/L/timestampReliance.ts new file mode 100644 index 0000000..5871761 --- /dev/null +++ b/src/issues/L/timestampReliance.ts @@ -0,0 +1,13 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +const issue: RegexIssue = { + id: 'timestampReliance', + regexOrAST: 'Regex', + type: IssueTypes.M, + title: 'Timestamp Manipulation', + impact: 'On proof-of-work chains, miners can change the block timestamp. If critical operations depend upon these timestamps, or they are meant to be used as a source of pseudro-randmoness, they may be unsafe to use on PoW chains.', + regex: /(block\.timestamp|now|block\.blockhash\(block\.timestamp\))/g + , +}; + +export default issue; \ No newline at end of file diff --git a/src/issues/L/unsafeCasting.ts b/src/issues/L/unsafeCasting.ts index b082f63..3ae18fb 100644 --- a/src/issues/L/unsafeCasting.ts +++ b/src/issues/L/unsafeCasting.ts @@ -5,6 +5,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'unsafeCasting', regexOrAST: 'AST', type: IssueTypes.L, title: diff --git a/src/issues/L/unsafeERC20Operations.ts b/src/issues/L/unsafeERC20Operations.ts index 0f241d2..c78d280 100644 --- a/src/issues/L/unsafeERC20Operations.ts +++ b/src/issues/L/unsafeERC20Operations.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'unsafeERC20Operations', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Unsafe ERC20 operation(s)', diff --git a/src/issues/L/unsafeReturnBomb.ts b/src/issues/L/unsafeReturnBomb.ts index 66b513f..29a76f0 100644 --- a/src/issues/L/unsafeReturnBomb.ts +++ b/src/issues/L/unsafeReturnBomb.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'unsafeReturnBomb', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Unsafe solidity low-level call can cause gas grief attack', diff --git a/src/issues/L/unspecifiedPragma.ts b/src/issues/L/unspecifiedPragma.ts index a653699..dc81e49 100644 --- a/src/issues/L/unspecifiedPragma.ts +++ b/src/issues/L/unspecifiedPragma.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'unspecifiedPragma', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Unspecific compiler version pragma', diff --git a/src/issues/L/upgradeableMissingGap.ts b/src/issues/L/upgradeableMissingGap.ts index 9a770f9..a5f6587 100644 --- a/src/issues/L/upgradeableMissingGap.ts +++ b/src/issues/L/upgradeableMissingGap.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'upgradeableMissingGap', regexOrAST: 'Regex', type: IssueTypes.L, title: diff --git a/src/issues/L/upgradeableNotInit.ts b/src/issues/L/upgradeableNotInit.ts index b626617..6aab8b1 100644 --- a/src/issues/L/upgradeableNotInit.ts +++ b/src/issues/L/upgradeableNotInit.ts @@ -2,6 +2,7 @@ import { IssueTypes, RegexIssue } from '../../types'; //@audit-issue TODO - lots of false positives const issue: RegexIssue = { + id: 'upgradeableNotInit', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Upgradeable contract not initialized', diff --git a/src/issues/L/useOfEcrecover.ts b/src/issues/L/useOfEcrecover.ts index c871808..e737e54 100644 --- a/src/issues/L/useOfEcrecover.ts +++ b/src/issues/L/useOfEcrecover.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'useOfEcrecover', regexOrAST: 'Regex', type: IssueTypes.L, title: "Use of ecrecover is susceptible to signature malleability", diff --git a/src/issues/L/useOnlyInitializing.ts b/src/issues/L/useOnlyInitializing.ts index e844413..9d47bc1 100644 --- a/src/issues/L/useOnlyInitializing.ts +++ b/src/issues/L/useOnlyInitializing.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'useOnlyInitializing', regexOrAST: 'AST', type: IssueTypes.L, title: 'Use `initializer` for public-facing functions only. Replace with `onlyInitializing` on internal functions.', diff --git a/src/issues/L/year365.ts b/src/issues/L/year365.ts index 21d47e0..c26db78 100644 --- a/src/issues/L/year365.ts +++ b/src/issues/L/year365.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'year365', regexOrAST: 'Regex', type: IssueTypes.L, title: 'A year is not always 365 days', diff --git a/src/issues/M/FoTTokens.ts b/src/issues/M/FoTTokens.ts index 1dad0d2..1555c92 100644 --- a/src/issues/M/FoTTokens.ts +++ b/src/issues/M/FoTTokens.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'foTTokens', regexOrAST: 'AST', type: IssueTypes.M, title: 'Contracts are vulnerable to fee-on-transfer accounting-related issues', diff --git a/src/issues/M/NFTRedefinesMint.ts b/src/issues/M/NFTRedefinesMint.ts index 47c0c19..3c2c796 100644 --- a/src/issues/M/NFTRedefinesMint.ts +++ b/src/issues/M/NFTRedefinesMint.ts @@ -4,6 +4,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC, topLevelFiles, getStorageVariable } from '../../utils'; const issue: ASTIssue = { + id: 'nftRedefinesMint', regexOrAST: 'AST', type: IssueTypes.L, title: "NFT contract redefines `_mint()`/`_safeMint()`, but not both", diff --git a/src/issues/M/approve0first.ts b/src/issues/M/approve0first.ts index 6df546d..193a906 100644 --- a/src/issues/M/approve0first.ts +++ b/src/issues/M/approve0first.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'approve0first', regexOrAST: 'Regex', type: IssueTypes.L, title: "`approve()`/`safeApprove()` may revert if the current approval is not zero", diff --git a/src/issues/M/avoidTx.origin.ts b/src/issues/M/avoidTx.origin.ts index dc03020..b281111 100644 --- a/src/issues/M/avoidTx.origin.ts +++ b/src/issues/M/avoidTx.origin.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'avoidTxOrigin', regexOrAST: 'Regex', type: IssueTypes.L, title: 'Use of `tx.origin` is unsafe in almost every context', diff --git a/src/issues/M/blockNumberL2.ts b/src/issues/M/blockNumberL2.ts index a19103d..5f01279 100644 --- a/src/issues/M/blockNumberL2.ts +++ b/src/issues/M/blockNumberL2.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'blockNumberL2', regexOrAST: 'Regex', type: IssueTypes.M, title: diff --git a/src/issues/M/centralizationRisk.ts b/src/issues/M/centralizationRisk.ts index 26071a6..773862d 100644 --- a/src/issues/M/centralizationRisk.ts +++ b/src/issues/M/centralizationRisk.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'centralizationRisk', regexOrAST: 'Regex', type: IssueTypes.M, title: 'Centralization Risk for trusted owners', diff --git a/src/issues/M/deprecatedChainlinkFunction.ts b/src/issues/M/deprecatedChainlinkFunction.ts index 92aaefc..b446c34 100644 --- a/src/issues/M/deprecatedChainlinkFunction.ts +++ b/src/issues/M/deprecatedChainlinkFunction.ts @@ -1,6 +1,7 @@ import {IssueTypes} from "../../types"; const issue = { + id: 'deprecatedChainlinkFunction', regexOrAST: 'Regex', type: IssueTypes.M, title: 'Use of deprecated chainlink function: `latestAnswer()`', diff --git a/src/issues/M/deprecatedTransfer.ts b/src/issues/M/deprecatedTransfer.ts index 4219157..1802905 100644 --- a/src/issues/M/deprecatedTransfer.ts +++ b/src/issues/M/deprecatedTransfer.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'deprecatedTransfer', regexOrAST: 'AST', type: IssueTypes.M, title: '`call()` should be used instead of `transfer()` on an `address payable`', diff --git a/src/issues/M/erc721safeMint.ts b/src/issues/M/erc721safeMint.ts index 39ec7c0..560ce6e 100644 --- a/src/issues/M/erc721safeMint.ts +++ b/src/issues/M/erc721safeMint.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'erc721safeMint', regexOrAST: 'Regex', type: IssueTypes.M, title: '`_safeMint()` should be used rather than `_mint()` wherever possible', diff --git a/src/issues/M/erc721safeTransferFrom.ts b/src/issues/M/erc721safeTransferFrom.ts index 76f221d..3de57eb 100644 --- a/src/issues/M/erc721safeTransferFrom.ts +++ b/src/issues/M/erc721safeTransferFrom.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'erc721safeTransferFrom', regexOrAST: 'Regex', type: IssueTypes.M, title: 'Using `transferFrom` on ERC721 tokens', diff --git a/src/issues/M/feeOver100.ts b/src/issues/M/feeOver100.ts index 23d346f..d66c5ad 100644 --- a/src/issues/M/feeOver100.ts +++ b/src/issues/M/feeOver100.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'feeOver100', regexOrAST: 'AST', type: IssueTypes.M, title: 'Fees can be set to be greater than 100%.', diff --git a/src/issues/M/increaseAllowanceUSDT.ts b/src/issues/M/increaseAllowanceUSDT.ts index f4b4eab..0dda6d1 100644 --- a/src/issues/M/increaseAllowanceUSDT.ts +++ b/src/issues/M/increaseAllowanceUSDT.ts @@ -4,6 +4,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC, topLevelFiles, getStorageVariable } from '../../utils'; const issue: RegexIssue = { + id: 'increaseAllowanceUSDT', regexOrAST: 'Regex', type: IssueTypes.M, title: "`increaseAllowance/decreaseAllowance` won't work on mainnet for USDT", diff --git a/src/issues/M/keccakOnStructOrArray.ts b/src/issues/M/keccakOnStructOrArray.ts index ad44cc8..78cd8a1 100644 --- a/src/issues/M/keccakOnStructOrArray.ts +++ b/src/issues/M/keccakOnStructOrArray.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'keccakOnStructOrArray', regexOrAST: 'AST', type: IssueTypes.M, title: 'Lack of EIP-712 compliance: using `keccak256()` directly on an array or struct variable', diff --git a/src/issues/M/libraryPublicFunction.ts b/src/issues/M/libraryPublicFunction.ts index f39ff52..50f41ce 100644 --- a/src/issues/M/libraryPublicFunction.ts +++ b/src/issues/M/libraryPublicFunction.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'libraryPublicFunction', regexOrAST: 'AST', type: IssueTypes.M, title: 'Library function isn\'t `internal` or `private`', diff --git a/src/issues/M/msgValueWithoutPayable.ts b/src/issues/M/msgValueWithoutPayable.ts new file mode 100644 index 0000000..5965944 --- /dev/null +++ b/src/issues/M/msgValueWithoutPayable.ts @@ -0,0 +1,31 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +// Reference: https://github.com/sherlock-audit/2023-04-jojo/blob/6090fef68932b5577abf6b5aa26eb1e579353c57/smart-contract-EVM/contracts/subaccount/Subaccount.sol#L45-L58 + +// function execute(address to, bytes calldata data, uint256 value) external onlyOwner returns (bytes memory){ +// require(to != address(0)); +// (bool success, bytes memory returnData) = to.call{value: value}(data); +// if (!success) { +// assembly { +// let ptr := mload(0x40) +// let size := returndatasize() +// returndatacopy(ptr, 0, size) +// revert(ptr, size) +// } +// } +// emit ExecuteTransaction(owner, address(this), to, data, value); +// return returnData; +// } + +const issue: RegexIssue = { + id: 'msgValueWithoutPayable', + regexOrAST: 'Regex', + type: IssueTypes.M, + title: 'Function uses `call{value}` but does not have the `payable` modifier', + impact: + 'The function uses msg.value indirectly through a `call{value}`, but does not have the payable modifier, which is required for any function that handles ether transfers.', + regex: + /function\s+(\w+)\s*\([^)]*\)\s*(external\s+)?(public\s+)?(internal\s+)?(private\s+)?(view\s+)?(pure\s+)?override\s*?(?!\s*payable)[^{]*\{\s*(?:[^}]*\.call\s*\{[^}]*value:[^}]*\}|[^}]*msg\.value[^}]*)[^}]*\}/g, +}; + +export default issue; \ No newline at end of file diff --git a/src/issues/M/selfdestruct.ts b/src/issues/M/selfdestruct.ts new file mode 100644 index 0000000..3ae8985 --- /dev/null +++ b/src/issues/M/selfdestruct.ts @@ -0,0 +1,13 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +const issue: RegexIssue = { + id: 'selfdestruct', + regexOrAST: 'Regex', + type: IssueTypes.M, + title: 'Used selfdestruct', + impact: 'Any contract that depends on another smart contract must account for the fact that the other can vanish at any time. Moreover, the SELFDESTRUCT opcode is deprecated and is recommended to no longer be used.', + regex: /selfdestruct\([a-zA-Z0-9\.\(\)]+\)/g + , +}; + +export default issue; \ No newline at end of file diff --git a/src/issues/M/slippageValidation.ts b/src/issues/M/slippageValidation.ts new file mode 100644 index 0000000..ed5fdc0 --- /dev/null +++ b/src/issues/M/slippageValidation.ts @@ -0,0 +1,18 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +// Reference: https://github.com/jbx-protocol/juice-buyback/blob/d0ea50010b2f01d1602522e7b3b2e3c9b927b49c/contracts/JBXBuybackDelegate.sol#L196C12-L197 + +// (,, uint256 _quote, uint256 _slippage) = abi.decode(_data.metadata, (bytes32, bytes32, uint256, uint256)); +// uint256 _minimumReceivedFromSwap = _quote - (_quote * _slippage / SLIPPAGE_DENOMINATOR); + +const issue: RegexIssue = { + id: 'slippageValidation', + regexOrAST: 'Regex', + type: IssueTypes.M, + title: 'Lack of slippage validation can lead to user loss of funds', + impact: + 'The slippage parameter should be validated against its denominator in order to prevent user mistake and potential loss of funds.', + regex: /function[^{]+\{[^}]*?\b(\w*slippage\w*)\b[^}]*?(?!require\s*\(\s*\1\s*<=)[^}]*?\}/gis, +}; + +export default issue; \ No newline at end of file diff --git a/src/issues/M/soladySafeTransferLib.ts b/src/issues/M/soladySafeTransferLib.ts index dbedc4b..4a7360c 100644 --- a/src/issues/M/soladySafeTransferLib.ts +++ b/src/issues/M/soladySafeTransferLib.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'soladySafeTransferLib', regexOrAST: 'Regex', type: IssueTypes.M, title: diff --git a/src/issues/M/solmateSafeTransferLib.ts b/src/issues/M/solmateSafeTransferLib.ts index b951276..5ee5977 100644 --- a/src/issues/M/solmateSafeTransferLib.ts +++ b/src/issues/M/solmateSafeTransferLib.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'solmateSafeTransferLib', regexOrAST: 'Regex', type: IssueTypes.M, title: diff --git a/src/issues/M/staleOracleData.ts b/src/issues/M/staleOracleData.ts index 8e9a144..06da01d 100644 --- a/src/issues/M/staleOracleData.ts +++ b/src/issues/M/staleOracleData.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC, lineFromIndex } from '../../utils'; const issue: ASTIssue = { + id: 'staleOracleData', regexOrAST: 'AST', type: IssueTypes.M, title: "Chainlink's `latestRoundData` might return stale or incorrect results", diff --git a/src/issues/M/staleOracleDataL2Sequencer.ts b/src/issues/M/staleOracleDataL2Sequencer.ts index 0522ad3..c0ff2de 100644 --- a/src/issues/M/staleOracleDataL2Sequencer.ts +++ b/src/issues/M/staleOracleDataL2Sequencer.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC, lineFromIndex } from '../../utils'; const issue: ASTIssue = { + id: 'staleOracleDataL2Sequencer', regexOrAST: 'AST', type: IssueTypes.M, title: "Missing checks for whether the L2 Sequencer is active", diff --git a/src/issues/M/supportInterface.ts b/src/issues/M/supportInterface.ts index 8b7809c..c69a04e 100644 --- a/src/issues/M/supportInterface.ts +++ b/src/issues/M/supportInterface.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'supportInterface', regexOrAST: 'Regex', type: IssueTypes.M, title: 'Direct `supportsInterface()` calls may cause caller to revert', diff --git a/src/issues/M/supportsInterface.ts b/src/issues/M/supportsInterface.ts new file mode 100644 index 0000000..0547397 --- /dev/null +++ b/src/issues/M/supportsInterface.ts @@ -0,0 +1,21 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +// Reference: https://github.com/jbx-protocol/juice-buyback/blob/d0ea50010b2f01d1602522e7b3b2e3c9b927b49c/contracts/JBXBuybackDelegate.sol + +// function supportsInterface(bytes4 _interfaceId) external pure override returns (bool) { +// return _interfaceId == type(IJBFundingCycleDataSource).interfaceId +// || _interfaceId == type(IJBPayDelegate).interfaceId; +// } + +const issue: RegexIssue = { + id: 'supportsInterface', + regexOrAST: 'Regex', + type: IssueTypes.M, + title: 'Contract is not compliant with ERC-165', + impact: + 'In order to properly implement `supportsInterface` in accordance with the ERC-165, the function MUST return true for the interface ID parameter `0x01ffc9a7` (calculated from `bytes4(keccak256("supportsInterface(bytes4)"))`), or simply `type(ERC165).interfaceId`.', + regex: + /function\s+supportsInterface\((bytes4\s+)?.*\)\s+(external\s+)?(public\s+)?(pure\s+|view\s+)?override\s+returns\s*\(bool\)\s*\{(?![^}]*(.*\s*==\s*0x01ffc9a7|.*\s*==\s*type\(ERC165\)\.interfaceId))[^}]*\}/g, +}; + +export default issue; \ No newline at end of file diff --git a/src/issues/M/suspiciousSelfAssignment.ts b/src/issues/M/suspiciousSelfAssignment.ts new file mode 100644 index 0000000..30a42df --- /dev/null +++ b/src/issues/M/suspiciousSelfAssignment.ts @@ -0,0 +1,60 @@ +import { InputType, IssueTypes, Instance, ASTIssue } from '../../types'; +import { findAll } from 'solidity-ast/utils'; +import { instanceFromSRC } from '../../utils'; +import { Expression } from 'solidity-ast'; + +// Iterative compare function to compare left and right nodes. +function compare(left: Expression | null | undefined, right: Expression | null | undefined): boolean { + // If null/undefined, return false + if (left == null || right == null) return false; + // If nodeType Not equal, return false + if (left.nodeType != right.nodeType) return false; + + // Literal + if (left.nodeType == "Literal" && right.nodeType == "Literal") { + return left.value == right.value; + } + + // Identifier + if (left.nodeType == "Identifier" && right.nodeType == "Identifier") { + return left.name == right.name; + } + + // IndexAccess + if (left.nodeType == "IndexAccess" && right.nodeType == "IndexAccess") { + let base = compare(left.baseExpression,right.baseExpression) + let index = compare(left.indexExpression,right.indexExpression); + return base && index; + } + + return false; +} + +const issue: ASTIssue = { + id: 'suspiciousSelfAssignment', + regexOrAST: 'AST', + type: IssueTypes.M, + title: 'Suspicious Self Assignment', + description: 'A self-assignment occurs when a variable or state is assigned a value that is already held by that variable or state itself. This situation often indicates a potential issue in the code, which can be redundant or incorrect. Specifically, self-assignment might suggest that the value assignment does not change the state of the variable, or it could be a sign of a logical error.', + detector: (files: InputType): Instance[] => { + let instances: Instance[] = []; + + for (const file of files) { + if (!!file.ast) { + for (const node of findAll('Assignment', file.ast)) { + let hit = false; + + hit = compare(node.leftHandSide,node.rightHandSide); + + if (hit) { + instances.push(instanceFromSRC(file, node.src)); + } + + } + } + } + return instances; + }, +}; + +export default issue; diff --git a/src/issues/M/uncheckedCallValueSuccess.ts b/src/issues/M/uncheckedCallValueSuccess.ts new file mode 100644 index 0000000..111988e --- /dev/null +++ b/src/issues/M/uncheckedCallValueSuccess.ts @@ -0,0 +1,13 @@ +import { IssueTypes, RegexIssue } from '../../types'; + +const issue: RegexIssue = { + id: 'uncheckedCallValueSuccess', + regexOrAST: 'Regex', + type: IssueTypes.M, + title: + 'Call.value fallback function reversion risk', + description: "Make sure the return boolean of the call function with value is true, otherwise contract risks assuming value has left when not if the fallback function of the destination reverts or is not payable.", + regex: /(?).max` should be used instead of `uint(-1)`', diff --git a/src/issues/NC/maxUint256.ts b/src/issues/NC/maxUint256.ts index dbbbc78..91914ce 100644 --- a/src/issues/NC/maxUint256.ts +++ b/src/issues/NC/maxUint256.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'maxUint256', regexOrAST: 'Regex', type: IssueTypes.NC, title: '`type(uint256).max` should be used instead of `2 ** 256 - 1`', diff --git a/src/issues/NC/missingEventSetters.ts b/src/issues/NC/missingEventSetters.ts index a3fed1d..07f9d4c 100644 --- a/src/issues/NC/missingEventSetters.ts +++ b/src/issues/NC/missingEventSetters.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'missingEventSetters', regexOrAST: 'AST', type: IssueTypes.NC, title: 'Missing Event for critical parameters change', diff --git a/src/issues/NC/missingNatspec.ts b/src/issues/NC/missingNatspec.ts index 7287db6..03d9994 100644 --- a/src/issues/NC/missingNatspec.ts +++ b/src/issues/NC/missingNatspec.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'missingNatspec', regexOrAST: 'AST', type: IssueTypes.NC, title: 'NatSpec is completely non-existent on functions that should have them', diff --git a/src/issues/NC/missingNatspecParam.ts b/src/issues/NC/missingNatspecParam.ts index 8fa4fcc..6eb654a 100644 --- a/src/issues/NC/missingNatspecParam.ts +++ b/src/issues/NC/missingNatspecParam.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'missingNatspecParam', regexOrAST: 'AST', type: IssueTypes.NC, title: 'Incomplete NatSpec: `@param` is missing on actually documented functions', diff --git a/src/issues/NC/missingNatspecReturn.ts b/src/issues/NC/missingNatspecReturn.ts index 91d0152..13e7303 100644 --- a/src/issues/NC/missingNatspecReturn.ts +++ b/src/issues/NC/missingNatspecReturn.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'missingNatspecReturn', regexOrAST: 'AST', type: IssueTypes.NC, title: 'Incomplete NatSpec: `@return` is missing on actually documented functions', diff --git a/src/issues/NC/missingSPDX.ts b/src/issues/NC/missingSPDX.ts index 8bc91e6..670a404 100644 --- a/src/issues/NC/missingSPDX.ts +++ b/src/issues/NC/missingSPDX.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'missingSPDX', regexOrAST: 'AST', type: IssueTypes.NC, title: 'File\'s first line is not an SPDX Identifier', diff --git a/src/issues/NC/modifierForMsgSender.ts b/src/issues/NC/modifierForMsgSender.ts index d30e471..1fec9af 100644 --- a/src/issues/NC/modifierForMsgSender.ts +++ b/src/issues/NC/modifierForMsgSender.ts @@ -2,6 +2,7 @@ import { IssueTypes, RegexIssue } from '../../types'; // Also catches the checks in modifiers and should be an AST detector const issue: RegexIssue = { + id: 'modifierForMsgSender', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Use a `modifier` instead of a `require/if` statement for a special `msg.sender` actor', diff --git a/src/issues/NC/multipleConstantDeclaration.ts b/src/issues/NC/multipleConstantDeclaration.ts index cf4aff3..34cce8e 100644 --- a/src/issues/NC/multipleConstantDeclaration.ts +++ b/src/issues/NC/multipleConstantDeclaration.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'multipleConstantDeclaration', regexOrAST: 'AST', type: IssueTypes.NC, title: 'Constant state variables defined more than once', diff --git a/src/issues/NC/namedMappings.ts b/src/issues/NC/namedMappings.ts index db3ea3b..96e4fcc 100644 --- a/src/issues/NC/namedMappings.ts +++ b/src/issues/NC/namedMappings.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'namedMappings', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Consider using named mappings', diff --git a/src/issues/NC/noAddressHardcode.ts b/src/issues/NC/noAddressHardcode.ts index 07e165c..41679d7 100644 --- a/src/issues/NC/noAddressHardcode.ts +++ b/src/issues/NC/noAddressHardcode.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'noAddressHardcode', regexOrAST: 'Regex', type: IssueTypes.NC, title: '`address`s shouldn\'t be hard-coded', diff --git a/src/issues/NC/nonReentrantBeforeModifiers.ts b/src/issues/NC/nonReentrantBeforeModifiers.ts index 0a47d49..bc11173 100644 --- a/src/issues/NC/nonReentrantBeforeModifiers.ts +++ b/src/issues/NC/nonReentrantBeforeModifiers.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'nonReentrantBeforeModifiers', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'The `nonReentrant` `modifier` should occur before all other modifiers', diff --git a/src/issues/NC/numericTimeDays.ts b/src/issues/NC/numericTimeDays.ts index a6d30ca..c557ffd 100644 --- a/src/issues/NC/numericTimeDays.ts +++ b/src/issues/NC/numericTimeDays.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'numericTimeDays', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Numeric values having to do with time should use time units for readability', diff --git a/src/issues/NC/onlyConstantsInUppercase.ts b/src/issues/NC/onlyConstantsInUppercase.ts index d66b92b..cb16471 100644 --- a/src/issues/NC/onlyConstantsInUppercase.ts +++ b/src/issues/NC/onlyConstantsInUppercase.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'onlyConstantsInUppercase', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Variable names that consist of all capital letters should be reserved for `constant`/`immutable` variables', diff --git a/src/issues/NC/ownerCanRenounceWhilePaused.ts b/src/issues/NC/ownerCanRenounceWhilePaused.ts index d1a5aa6..a1047a2 100644 --- a/src/issues/NC/ownerCanRenounceWhilePaused.ts +++ b/src/issues/NC/ownerCanRenounceWhilePaused.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'ownerCanRenounceWhilePaused', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Owner can renounce while system is paused', diff --git a/src/issues/NC/redundantNamedReturn.ts b/src/issues/NC/redundantNamedReturn.ts index fd7534c..bcee59f 100644 --- a/src/issues/NC/redundantNamedReturn.ts +++ b/src/issues/NC/redundantNamedReturn.ts @@ -5,6 +5,7 @@ import { instanceFromSRC } from '../../utils'; //@note trop fier de moi const issue: ASTIssue = { + id: 'redundantNamedReturn', regexOrAST: 'AST', type: IssueTypes.NC, title: 'Adding a `return` statement when the function defines a named return variable, is redundant', diff --git a/src/issues/NC/requireWithString.ts b/src/issues/NC/requireWithString.ts index 5a5c795..43d962a 100644 --- a/src/issues/NC/requireWithString.ts +++ b/src/issues/NC/requireWithString.ts @@ -3,6 +3,7 @@ import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../typ import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'requireWithString', regexOrAST: 'AST', type: IssueTypes.NC, title: '`require()` / `revert()` statements should have descriptive reason strings', diff --git a/src/issues/NC/revertWithoutArgument.ts b/src/issues/NC/revertWithoutArgument.ts index c86738c..6175074 100644 --- a/src/issues/NC/revertWithoutArgument.ts +++ b/src/issues/NC/revertWithoutArgument.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'revertWithoutArgument', regexOrAST: 'Regex', type: IssueTypes.NC, title: "Take advantage of Custom Error's return value property", diff --git a/src/issues/NC/safeMath08.ts b/src/issues/NC/safeMath08.ts index 7eb1228..addff7d 100644 --- a/src/issues/NC/safeMath08.ts +++ b/src/issues/NC/safeMath08.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'safeMath08', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Deprecated library used for Solidity `>= 0.8` : SafeMath', diff --git a/src/issues/NC/scientificNotationExponent.ts b/src/issues/NC/scientificNotationExponent.ts index 7d2bfdf..2d1f45a 100644 --- a/src/issues/NC/scientificNotationExponent.ts +++ b/src/issues/NC/scientificNotationExponent.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'scientificNotationExponent', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Use scientific notation (e.g. `1e18`) rather than exponentiation (e.g. `10**18`)', diff --git a/src/issues/NC/scientificNotationZeros.ts b/src/issues/NC/scientificNotationZeros.ts index 9b3e97c..2db53ed 100644 --- a/src/issues/NC/scientificNotationZeros.ts +++ b/src/issues/NC/scientificNotationZeros.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'scientificNotationZeros', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Use scientific notation for readability reasons for large multiples of ten', diff --git a/src/issues/NC/sensitiveTerms.ts b/src/issues/NC/sensitiveTerms.ts index 75495b5..fde1037 100644 --- a/src/issues/NC/sensitiveTerms.ts +++ b/src/issues/NC/sensitiveTerms.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'sensitiveTerms', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Avoid the use of sensitive terms', diff --git a/src/issues/NC/stringQuote.ts b/src/issues/NC/stringQuote.ts index 6b233d5..896eefd 100644 --- a/src/issues/NC/stringQuote.ts +++ b/src/issues/NC/stringQuote.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'stringQuote', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Strings should use double quotes rather than single quotes', diff --git a/src/issues/NC/style.ts b/src/issues/NC/style.ts index 6e49610..0356e46 100644 --- a/src/issues/NC/style.ts +++ b/src/issues/NC/style.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'style', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Function writing that does not comply with the Solidity Style Guide', diff --git a/src/issues/NC/styleGuide.ts b/src/issues/NC/styleGuide.ts index 7d060b6..96874d6 100644 --- a/src/issues/NC/styleGuide.ts +++ b/src/issues/NC/styleGuide.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'styleGuide', regexOrAST: 'AST', type: IssueTypes.NC, title: "Contract does not follow the Solidity style guide's suggested layout ordering", diff --git a/src/issues/NC/todoLeftInTheCode.ts b/src/issues/NC/todoLeftInTheCode.ts index e72ae96..88ff253 100644 --- a/src/issues/NC/todoLeftInTheCode.ts +++ b/src/issues/NC/todoLeftInTheCode.ts @@ -3,6 +3,7 @@ import { IssueTypes, RegexIssue } from '../../types'; // TODO: This finding won't work until we add a flag to specify findings // that either also search in commentsOr only search in comments const issue: RegexIssue = { + id: 'todoLeftInTheCode', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'TODO Left in the code', diff --git a/src/issues/NC/unclearRequire.ts b/src/issues/NC/unclearRequire.ts index cebb67b..1947c10 100644 --- a/src/issues/NC/unclearRequire.ts +++ b/src/issues/NC/unclearRequire.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'unclearRequire', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Some require descriptions are not clear', diff --git a/src/issues/NC/underscoreDecimals.ts b/src/issues/NC/underscoreDecimals.ts index e1e01ab..213fdf7 100644 --- a/src/issues/NC/underscoreDecimals.ts +++ b/src/issues/NC/underscoreDecimals.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'underscoreDecimals', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Use Underscores for Number Literals (add an underscore every 3 digits)', diff --git a/src/issues/NC/underscoreInternal.ts b/src/issues/NC/underscoreInternal.ts index a2a4243..c5e20e7 100644 --- a/src/issues/NC/underscoreInternal.ts +++ b/src/issues/NC/underscoreInternal.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'underscoreInternal', regexOrAST: 'AST', type: IssueTypes.NC, title: 'Internal and private variables and functions names should begin with an underscore', diff --git a/src/issues/NC/unindexedEvent.ts b/src/issues/NC/unindexedEvent.ts index 01f1799..90d4db3 100644 --- a/src/issues/NC/unindexedEvent.ts +++ b/src/issues/NC/unindexedEvent.ts @@ -4,6 +4,7 @@ import util from 'util'; import { instanceFromSRC } from '../../utils'; const issue: ASTIssue = { + id: 'unindexedEvent', regexOrAST: 'AST', type: IssueTypes.NC, title: 'Event is missing `indexed` fields', diff --git a/src/issues/NC/useConstants.ts b/src/issues/NC/useConstants.ts index 4b31a57..d458230 100644 --- a/src/issues/NC/useConstants.ts +++ b/src/issues/NC/useConstants.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'useConstants', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Constants should be defined rather than using magic numbers', diff --git a/src/issues/NC/uselessOverride.ts b/src/issues/NC/uselessOverride.ts deleted file mode 100644 index aea1929..0000000 --- a/src/issues/NC/uselessOverride.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { findAll } from 'solidity-ast/utils'; -import { ASTIssue, InputType, Instance, IssueTypes, RegexIssue } from '../../types'; -import { instanceFromSRC } from '../../utils'; -import util from 'util'; - -const issue: ASTIssue = { - regexOrAST: 'AST', - type: IssueTypes.NC, - title: - '`override` function arguments that are unused should have the variable name removed or commented out to avoid compiler warnings', - detector: (files: InputType): Instance[] => { - let instances: Instance[] = []; - - for (const file of files) { - for (const cd of findAll('ContractDefinition', file.ast)) { - if (cd.contractKind == 'interface' || cd.abstract) { - continue; - } - for (const a of findAll('FunctionDefinition', file.ast)) { - if (a.kind == 'constructor' || a.virtual || a.kind == 'fallback' || a.kind == 'receive') { - continue; - } - if (!a.body) { - continue; - } - if (!!a.overrides) { - for (const b of findAll('ParameterList', a)) { - if (a.returnParameters && a.returnParameters.id == b.id) { - continue; - } - for (const b1 of findAll('VariableDeclaration', b)) { - let usesParam = false; - if (!b1.name) { - continue; - } - for (const c of findAll('Block', a)) { - for (const d of findAll('Identifier', c)) { - if (b1.name == d.name) { - usesParam = true; - break; - } - } - if (usesParam) { - break; - } - } - - if (!usesParam) { - instances.push(instanceFromSRC(file, b1.src)); - } - } - } - } - } - } - } - return instances; - }, -}; - -export default issue; diff --git a/src/issues/NC/uselessPublic.ts b/src/issues/NC/uselessPublic.ts index 7156da3..c5df7e2 100644 --- a/src/issues/NC/uselessPublic.ts +++ b/src/issues/NC/uselessPublic.ts @@ -4,6 +4,7 @@ import { instanceFromSRC } from '../../utils'; import util from 'util'; const issue: ASTIssue = { + id: 'uselessPublic', regexOrAST: 'AST', type: IssueTypes.NC, title: '`public` functions not called by the contract should be declared `external` instead', diff --git a/src/issues/NC/uselessZeroInit.ts b/src/issues/NC/uselessZeroInit.ts index 7017397..11f9c9e 100644 --- a/src/issues/NC/uselessZeroInit.ts +++ b/src/issues/NC/uselessZeroInit.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'uselessZeroInit', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'Variables need not be initialized to zero', diff --git a/src/issues/NC/v2728check.ts b/src/issues/NC/v2728check.ts index 51bbca2..21c96d9 100644 --- a/src/issues/NC/v2728check.ts +++ b/src/issues/NC/v2728check.ts @@ -1,6 +1,7 @@ import { IssueTypes, RegexIssue } from '../../types'; const issue: RegexIssue = { + id: 'v2728check', regexOrAST: 'Regex', type: IssueTypes.NC, title: 'No need to check that `v == 27` or `v == 28` with `ecrecover`', diff --git a/src/main.ts b/src/main.ts index 81fa57a..cde1011 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,10 @@ -import fs from 'fs'; +import * as fs from 'fs'; import analyze from './analyze'; +import sarif from './sarif'; import compileAndBuildAST from './compile'; import issues from './issues'; -import { InputType, IssueTypes } from './types'; +import markdown from './markdown'; +import { InputType, IssueTypes, Analysis, AnalysisResults, ReportTypes, Issue } from './types'; import { recursiveExploration } from './utils'; /* .---. ,--. ,-- / ,---. ,--. ,--.' ,-. .----. ,------.,------, @@ -20,15 +22,20 @@ import { recursiveExploration } from './utils'; * @param githubLink github url to generate links to code * @param out where to save the report * @param scope optional text containing the .sol files in scope. Replaces `scopeFile` + * @param sarif optional string with location to save sarif report. If undefined, SARIF generation is skipped. + * @param listFiles optional boolean, if true list the analyzed files in the markdown report. */ const main = async ( basePath: string, scopeFile: string | null, githubLink: string | null, out: string, + severityToRun: IssueTypes[], + skipDetectors: string[], scope?: string, + sarifOut?: string, + listFiles?: boolean, ) => { - let result = '# Report\n\n'; let fileNames: string[] = []; if (!!scopeFile || !!scope) { @@ -47,13 +54,6 @@ const main = async ( console.log('Scope: ', fileNames); - // Uncomment next lines to have the list of analyzed files in the report - - // result += '## Files analyzed\n\n'; - // fileNames.forEach(fileName => { - // result += ` - ${fileName}\n`; - // }); - // Read file contents and build AST const files: InputType = []; const asts = await compileAndBuildAST(basePath, fileNames); @@ -65,15 +65,34 @@ const main = async ( }); }); - for (const t of Object.values(IssueTypes)) { - result += analyze( + let analysisResultsObj: AnalysisResults = {}; + + for (const t of severityToRun) { + let analyses: Analysis[] = analyze( files, - issues.filter(i => i.type === t), + issues.filter(i => i.type === t && !skipDetectors.includes(i.id.toLowerCase())), !!githubLink ? githubLink : undefined, ); + + // add analyze results for this issue type to the results object + analysisResultsObj[t] = analyses; } - fs.writeFileSync(out, result); + // Do markdown conversion + let markdownResult = markdown(analysisResultsObj, listFiles ? fileNames : undefined, githubLink ?? undefined); + fs.writeFileSync(out, markdownResult); + + if(sarifOut){ + let sarifAnalyses: Analysis[] = []; + + for (const t of Object.values(IssueTypes)) { + sarifAnalyses = sarifAnalyses.concat((analysisResultsObj[t] ?? [])); + } + + let sarifResult = sarif(sarifAnalyses); + + fs.writeFileSync(sarifOut, JSON.stringify(sarifResult, null, 2), 'utf-8'); + } }; export default main; diff --git a/src/markdown.ts b/src/markdown.ts new file mode 100644 index 0000000..88eef9e --- /dev/null +++ b/src/markdown.ts @@ -0,0 +1,109 @@ +import { InputType, Instance, Issue, IssueTypes, Analysis, AnalysisResults } from './types'; + +// Titles for different issue types +const issueTypesTitles = { + GAS: 'Gas Optimizations', + NC: 'Informational Issues', + L: 'Low Issues', + M: 'Medium Issues', + H: 'High Issues', +}; + + +const markdown = (results: AnalysisResults, fileNames?: string[], githubLink?: string): string => { + let report = '# Report\n\n'; + + fileNames ??= []; + + if (fileNames.length > 0) { + report += files(fileNames); + } + + for (const t of Object.values(IssueTypes)) { + let analyses: Analysis[] = results[t] ?? []; + if(analyses.length > 0) { + report += markdownPerType(analyses, githubLink); + } + } + + return report; +} + +const files = (fileNames: string[]): string => { + let result = '' + if (fileNames.length > 0){ + result = '## Files analyzed\n\n'; + fileNames.forEach(fileName => { + result += ` - ${fileName}\n`; + }); + } + return result; +} + +const markdownPerType = (analyze: Analysis[], githubLink?: string): string => { + let result = ""; + + /** Summary */ + let c = 0; + + result += `\n## ${issueTypesTitles[analyze[0].issue.type]}\n\n`; + result += '\n| |Issue|Instances|\n|-|:-|:-:|\n'; + for (const { issue, instances } of analyze) { + c++; + result += `| [${issue.type}-${c}](#${issue.type}-${c}) | ${issue.title} | ${instances.length} |\n`; + } + + /** Issue breakdown */ + c = 0; + for (const { issue, instances } of analyze) { + c++; + result += `### [${issue.type}-${c}] ${issue.title}\n`; + if (!!issue.description) { + result += `${issue.description}\n`; + } + if (!!issue.impact) { + result += '\n#### Impact:\n'; + result += `${issue.impact}\n`; + } + result += `\n*Instances (${instances.length})*:\n`; + let previousFileName = ''; + for (const o of instances.sort((a, b) => { + if (a.fileName < b.fileName) return -1; + if (a.fileName > b.fileName) return 1; + return !!a.line && !!b.line && a.line < b.line ? -1 : 1; + })) { + if (o.fileName !== previousFileName) { + if (previousFileName !== '') { + result += `\n${'```'}\n`; + if (!!githubLink) { + result += `[Link to code](${githubLink + previousFileName})\n`; + } + result += `\n`; + } + result += `${'```'}solidity\nFile: ${o.fileName}\n`; + previousFileName = o.fileName; + } + + // Insert code snippet + const lineSplit = o.fileContent?.split('\n'); + const offset = o.line.toString().length; + result += `\n${o.line}: ${lineSplit[o.line - 1]}\n`; + if (!!o.endLine) { + let currentLine = o.line + 1; + while (currentLine <= o.endLine) { + result += `${' '.repeat(offset)} ${lineSplit[currentLine - 1]}\n`; + currentLine++; + } + } + } + result += `\n${'```'}\n`; + if (!!githubLink) { + result += `[Link to code](${githubLink + previousFileName})\n`; + } + result += `\n`; + } + + return result; +} + +export default markdown; \ No newline at end of file diff --git a/src/sarif.ts b/src/sarif.ts new file mode 100644 index 0000000..ef3fd4d --- /dev/null +++ b/src/sarif.ts @@ -0,0 +1,209 @@ +import { Analysis, SARIFResult, Instance, Location } from './types'; + + +// Titles for different issue types +const issueTypesTitles = { + GAS: 'Gas Optimizations', + NC: 'Informational Issues', + L: 'Low Issues', + M: 'Medium Issues', + H: 'High Issues', +}; + +// Function to convert a string to Pascal-case +const toPascalCase = (str: string) => { + return str + .replace(/[^a-zA-Z0-9 ]/g, '') // Remove non-alphanumeric characters + .replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()) // Capitalize each word + .replace(/\s+/g, ''); // Remove spaces +}; + +const sanitizeFileName = (fileName: string) => { + return fileName.replace(/^\.?\//, ''); +}; + +// Function to sanitize file names by removing leading "." and "/" +const sanitizeFileNames = (fileName: string) => { + return fileName.replace(/^\.?\//, ''); +}; + +// Function to map issue types to SARIF severity levels +const mapSeverity = (type: string) => { + switch (type) { + case 'NC': + return 'note'; + case 'G': + return 'note'; + case 'GAS': + return 'note'; + case 'L': + return 'warning'; + case 'M': + return 'warning'; + case 'H': + return 'error'; + default: + return 'note'; + } +}; + +const mapSecuritySeverity = (type: string) => { + switch (type) { + case 'NC': + return '0.0'; // No Severity + case 'G': + return '2.5'; // Low + case 'GAS': + return '2.5'; // Low + case 'L': + return '5.0'; // Medium + case 'M': + return '7.5'; // High + case 'H': + return '10.0'; // Critical + default: + return '10.0'; // Everything should be define + } +}; + +// Function to map issue types to descriptive tags +const mapTag = (type: string) => { + switch (type) { + case 'NC': + return 'Informational'; + case 'G': + return 'Gas Optimization'; + case 'GAS': + return 'Gas Optimization'; + case 'L': + return 'Low'; + case 'M': + return 'Medium'; + case 'H': + return 'High'; + default: + return 'Informational'; + } +}; + +const generateResults = (analyses: Analysis[]) => { + const results = analyses.flatMap((item, index) => { + if(item.issue.regexOrAST === 'Regex') { + // Regex Results need to be deduplicated for matching files + + let RegexResults: SARIFResult[] = []; + + const FileIssueMap: { [key: string]: Instance[] } = {}; // Map of file names to instances + item.instances.forEach(issue => { // Populate the map + if (!FileIssueMap[issue.fileName]) { + FileIssueMap[issue.fileName] = []; + } + FileIssueMap[issue.fileName].push(issue); + }); + + Object.keys(FileIssueMap).forEach(key => { // For each file, create a SARIFResult + let locations: Location[] = []; + FileIssueMap[key].forEach(issue => { + locations.push({ + physicalLocation: { + artifactLocation: { + uri: sanitizeFileNames(issue.fileName), // Sanitize the file name to remove leading "." + }, + region: { + startLine: issue.line, + startColumn: 1, + ...(issue.endLine && { endLine: issue.endLine }) // Add endLine if it exists + } + } + }); + }); + let fileResult: SARIFResult = { + ruleId: `rule${index + 1}`, + message: { + text: item.issue.title + }, + locations: locations, + level: mapSeverity(item.issue.type), // Map issue type to SARIF severity level + }; + RegexResults.push(fileResult); + }); + + return RegexResults; + + } else if (item.issue.regexOrAST === 'AST') { // AST Results use default locations + return item.instances.map(instance => ({ + ruleId: `rule${index + 1}`, + message: { + text: item.issue.title + }, + locations: [ + { + physicalLocation: { + artifactLocation: { + uri: sanitizeFileNames(instance.fileName), // Sanitize the file name to remove leading "." + }, + region: { + startLine: instance.line, + startColumn: 1, + ...(instance.endLine && { endLine: instance.endLine }) // Add endLine if it exists + } + } + } + ], + level: mapSeverity(item.issue.type), // Map issue type to SARIF severity level + })); + } + } + ); + + return results; +} + +const sarif = (analyses: Analysis[]): Object => { + // Convert the JSON data to SARIF format + console.log(analyses.length); + + const sarifDetails = { + version: "2.1.0", + $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + runs: [ + { + tool: { + driver: { + name: "4naly3er", + fullName: "4naly3er Static Smart Contract Code Analyzer", + informationUri: "https://github.com/cb-elileers/analyzer", + version: "0.2", // Update the version number + rules: analyses.map((item, index) => ({ + id: `rule${index + 1}`, + name: item.issue.title, + shortDescription: { + text: item.issue.title + }, + fullDescription: { + text: item.issue.description || item.issue.title + }, + helpUri: "https://github.com/cb-elileers/analyzer/blob/analytic/detectors.md", + help: { + text: item.issue.description || item.issue.title + }, + properties: { + tags: [mapTag(item.issue.type)], // Map issue type to descriptive tag + "security-severity": mapSecuritySeverity(item.issue.type), // Map issue type to SARIF severity level + "problem.severity": mapSecuritySeverity(item.issue.type) // Map issue type to SARIF severity level + } + })) + } + }, + automationDetails: { + id: "4naly3er" + }, + results: generateResults(analyses) + } + ] + }; + + return sarifDetails; +} + +export default sarif; diff --git a/src/types.ts b/src/types.ts index e8c9ee1..323ba70 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,6 +8,10 @@ export enum IssueTypes { H = 'H', } +export enum ReportTypes { + markdown = 'markdown' +} + // List of solidity files with content and name export type InputType = { content: string; name: string; ast: SourceUnit }[]; @@ -23,6 +27,7 @@ export type Issue = RegexIssue | ASTIssue; // Type to follow to add an issue detected by a regex export type RegexIssue = { + id: string; type: IssueTypes; title: string; regex: RegExp; @@ -36,6 +41,7 @@ export type RegexIssue = { // Type to follow to add an issue detected by AST analysis export type ASTIssue = { + id: string; type: IssueTypes; title: string; impact?: string; @@ -43,3 +49,39 @@ export type ASTIssue = { detector: (files: InputType) => Instance[]; // Function analyzing the AST and returning instances of the issue regexOrAST: 'AST'; }; + +// Type to bundle Issues and Instances for splitting Analysis and Printing steps +export type Analysis = { + issue: Issue; + instances: Instance[]; +}; + +export type AnalysisResults = { + GAS?: Analysis[]; + NC?: Analysis[]; + L?: Analysis[]; + M?: Analysis[]; + H?: Analysis[]; +} + +export type SARIFResult = { + ruleId: string; + message: { + text: string; + }; + locations: Location[]; + level: string; +}; + +export type Location = { + physicalLocation: { + artifactLocation: { + uri: string; + }; + region: { + startLine: number; + startColumn: number; + endLine?: number; + }; + }; +}; \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 3cde76b..0ede1c7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -45,7 +45,7 @@ export async function sh(cmd: string) { * @notice Returns all file contained in a folder * @dev Works with a queue, could be done with a recursive function */ -export const recursiveExploration = (basePath: string, extension = '.sol'): string[] => { +export const recursiveExploration = (basePath: string, extension = '.sol', skipDirs: string[] = ['test', 'lib', 'script', 'node_modules', '.git']): string[] => { let fileNames: string[] = []; let directoryQueue = ['']; while (directoryQueue.length > 0) { @@ -53,10 +53,12 @@ export const recursiveExploration = (basePath: string, extension = '.sol'): stri let tempFileNames = fs.readdirSync(`${basePath}${dir}`); for (let fileName of tempFileNames) { fileName = `${dir}${fileName}`; - if (fileName.endsWith(extension)) { + if (fs.statSync(`${basePath}${fileName}`).isDirectory()) { + if (!skipDirs.includes(fileName)) { // skip directories from skipDirs array + directoryQueue.push(fileName + '/'); + } + } else if (fileName.endsWith(extension)) { fileNames.push(fileName); - } else if (fs.statSync(`${basePath}${fileName}`).isDirectory()) { - directoryQueue.push(fileName + '/'); } } } diff --git a/tsconfig.json b/tsconfig.json index e9c8ff5..8d300e5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,5 +5,6 @@ }, "compilerOptions": { "noImplicitAny": false - } + }, + "js/ts.implicitProjectConfig.target": "ES2024" } diff --git a/yarn.lock b/yarn.lock index 5f8b273..c9ed7e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -327,6 +327,11 @@ commander@3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== +commander@^11.0.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" + integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== + commander@^8.1.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -1235,6 +1240,11 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +n@^9.2.0: + version "9.2.3" + resolved "https://registry.yarnpkg.com/n/-/n-9.2.3.tgz#dc63618449e40474c74d412d0c14950013c8273c" + integrity sha512-iKU/fMITB+bJe0wQ6Q6THSWBpfF/f/g4elhr23Mp4TywSY0rJvaHVdkMhL9uWEdwdipAPidmTIqafI0BTJj/lQ== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -1883,7 +1893,7 @@ slash@^3.0.0: semver "^5.5.0" tmp "0.0.33" -"solc-0.8.17@npm:solc@0.8.17", solc@^0.8.17: +"solc-0.8.17@npm:solc@0.8.17": version "0.8.17" resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.17.tgz#c748fec6a64bf029ec406aa9b37e75938d1115ae" integrity sha512-Dtidk2XtTTmkB3IKdyeg6wLYopJnBVxdoykN8oP8VY3PQjN16BScYoUJTXFm2OP7P0hXNAqWiJNmmfuELtLf8g== @@ -1896,6 +1906,100 @@ slash@^3.0.0: semver "^5.5.0" tmp "0.0.33" +"solc-0.8.18@npm:solc@0.8.18": + version "0.8.18" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.18.tgz#a05ce8918540eda5f10aa91f0f52f239b9645dad" + integrity sha512-wVAa2Y3BYd64Aby5LsgS3g6YC2NvZ3bJ+A8TAIAukfVuQb3AjyGrLZpyxQk5YLn14G35uZtSnIgHEpab9klOLQ== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + +"solc-0.8.19@npm:solc@0.8.19": + version "0.8.19" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.19.tgz#cac6541106ae3cff101c740042c7742aa56a2ed3" + integrity sha512-yqurS3wzC4LdEvmMobODXqprV4MYJcVtinuxgrp61ac8K2zz40vXA0eSAskSHPgv8dQo7Nux39i3QBsHx4pqyA== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + +"solc-0.8.1@npm:solc@0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.1.tgz#cbdba8fea6fbfae463f382cecb59c51de0269472" + integrity sha512-rqB8wlL20I/k+ibCgNGqXaoXBngKTUb3JCsCazwEvxJ9C9RcXNGRsiqgB1xZs37IjINMp0E3xGiuMjGYTBQnUg== + dependencies: + command-exists "^1.2.8" + commander "3.0.2" + follow-redirects "^1.12.1" + fs-extra "^0.30.0" + js-sha3 "0.8.0" + memorystream "^0.3.1" + require-from-string "^2.0.0" + semver "^5.5.0" + tmp "0.0.33" + +"solc-0.8.20@npm:solc@0.8.20": + version "0.8.20" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.20.tgz#b49151cf5ecc8de088d3d32b0afb607b3522ba8d" + integrity sha512-fPRnGspIEqmhu63RFO3pc79sLA7ZmzO0Uy0L5l6hEt2wAsq0o7UV6pXkAp3Mfv9IBhg7Px/oTu3a+y4gs3BWrQ== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + +"solc-0.8.21@npm:solc@0.8.21": + version "0.8.21" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.21.tgz#c3cd505c360ea2fa0eaa5ab574ef96bffb1a2766" + integrity sha512-N55ogy2dkTRwiONbj4e6wMZqUNaLZkiRcjGyeafjLYzo/tf/IvhHY5P5wpe+H3Fubh9idu071i8eOGO31s1ylg== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + +"solc-0.8.22@npm:solc@0.8.22": + version "0.8.22" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.22.tgz#6df0bb688b9a58bbf10932730301374a6ccfb862" + integrity sha512-bA2tMZXx93R8L5LUH7TlB/f+QhkVyxrrY6LmgJnFFZlRknrhYVlBK1e3uHIdKybwoFabOFSzeaZjPeL/GIpFGQ== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + +"solc-0.8.23@npm:solc@0.8.23": + version "0.8.23" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.23.tgz#bfbb8ac7f7fcc7db95faf5d709edf15c4f006d89" + integrity sha512-uqe69kFWfJc3cKdxj+Eg9CdW1CP3PLZDPeyJStQVWL8Q9jjjKD0VuRAKBFR8mrWiq5A7gJqERxJFYJsklrVsfA== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + n "^9.2.0" + semver "^5.5.0" + tmp "0.0.33" + "solc-0.8.2@npm:solc@0.8.2": version "0.8.2" resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.2.tgz#6033d75c6166fd0feb7fe08eddc198aaf52025da" @@ -1971,6 +2075,21 @@ slash@^3.0.0: semver "^5.5.0" tmp "0.0.33" +"solc-0.8.7@npm:solc@0.8.7": + version "0.8.7" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.7.tgz#54434959cc3d69a569ad8853c0d9ee1338da0aab" + integrity sha512-p8Zi+YcGN22P3Stb6KJhNypD9xSnNF3D6eIw6LyxZpMIVpcwrG8fTaXeEbKITmlp14DC1iZ4BC4aV7r7gJ/EJw== + dependencies: + command-exists "^1.2.8" + commander "3.0.2" + follow-redirects "^1.12.1" + fs-extra "^0.30.0" + js-sha3 "0.8.0" + memorystream "^0.3.1" + require-from-string "^2.0.0" + semver "^5.5.0" + tmp "0.0.33" + "solc-0.8.8@npm:solc@0.8.8": version "0.8.8" resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.8.tgz#3902c496cc200fc83bf96ff42d798f3f41c9c489" @@ -2001,10 +2120,23 @@ slash@^3.0.0: semver "^5.5.0" tmp "0.0.33" -solidity-ast@^0.4.35: - version "0.4.35" - resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.35.tgz#82e064b14dc989338123264bde2235cad751f128" - integrity sha512-F5bTDLh3rmDxRmLSrs3qt3nvxJprWSEkS7h2KmuXDx7XTfJ6ZKVTV1rtPIYCqJAuPsU/qa8YUeFn7jdOAZcTPA== +solc@^0.8.17: + version "0.8.17" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.8.17.tgz#c748fec6a64bf029ec406aa9b37e75938d1115ae" + integrity sha512-Dtidk2XtTTmkB3IKdyeg6wLYopJnBVxdoykN8oP8VY3PQjN16BScYoUJTXFm2OP7P0hXNAqWiJNmmfuELtLf8g== + dependencies: + command-exists "^1.2.8" + commander "^8.1.0" + follow-redirects "^1.12.1" + js-sha3 "0.8.0" + memorystream "^0.3.1" + semver "^5.5.0" + tmp "0.0.33" + +solidity-ast@^0.4.53: + version "0.4.59" + resolved "https://registry.yarnpkg.com/solidity-ast/-/solidity-ast-0.4.59.tgz#290a2815aef70a61092591ab3e991da080ae5931" + integrity sha512-I+CX0wrYUN9jDfYtcgWSe+OAowaXy8/1YQy7NS4ni5IBDmIYBq7ZzaP/7QqouLjzZapmQtvGLqCaYgoUWqBo5g== spdx-correct@^3.0.0: version "3.1.1"