diff --git a/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts b/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts index 2b9ef55c17..eafb19c207 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts @@ -1,19 +1,20 @@ import { default as category } from './categories' import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class assignAndCompare { - warningNodes: any[] = [] - name = 'Result not used: ' - description = 'The result of an operation was not used.' - category = category.MISC - algorithm = algorithm.EXACT +export default class assignAndCompare implements AnalyzerModule { + warningNodes: AstNodeLegacy[] = [] + name: string = 'Result not used: ' + description: string = 'The result of an operation was not used.' + category: ModuleCategory = category.MISC + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isSubScopeWithTopLevelUnAssignedBinOp(node)) getUnAssignedTopLevelBinOps(node).forEach((n) => this.warningNodes.push(n)) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.warningNodes.map((item, i) => { return { warning: 'A binary operation yields a value that is not used in the following. This is often caused by confusing assignment (=) and comparison (==).', diff --git a/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts b/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts index ea73d05d40..35e2dfdf1f 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts @@ -1,19 +1,20 @@ import { default as category } from './categories' import { isBlockBlockHashAccess } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class blockBlockhash { - warningNodes: any[] = [] - name = 'Block.blockhash usage: ' - desc = 'Semantics maybe unclear' - categories = category.SECURITY - algorithm = algorithm.EXACT +export default class blockBlockhash implements AnalyzerModule { + warningNodes: AstNodeLegacy[] = [] + name: string = 'Block.blockhash usage: ' + description: string = 'Semantics maybe unclear' + category: ModuleCategory = category.SECURITY + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isBlockBlockHashAccess(node)) this.warningNodes.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.warningNodes.map((item, i) => { return { warning: `use of "block.blockhash": "block.blockhash" is used to access the last 256 block hashes. diff --git a/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts b/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts index 0b40bcc314..951cce3610 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts @@ -1,21 +1,22 @@ import { default as category } from './categories' import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class blockTimestamp { - warningNowNodes: any[] = [] - warningblockTimestampNodes: any[] = [] - name = 'Block timestamp: ' - desc = 'Semantics maybe unclear' - categories = category.SECURITY - algorithm = algorithm.EXACT +export default class blockTimestamp implements AnalyzerModule { + warningNowNodes: AstNodeLegacy[] = [] + warningblockTimestampNodes: AstNodeLegacy[] = [] + name: string = 'Block timestamp: ' + description: string = 'Semantics maybe unclear' + category: ModuleCategory = category.SECURITY + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isNowAccess(node)) this.warningNowNodes.push(node) else if (isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.warningNowNodes.map((item, i) => { return { warning: `use of "now": "now" does not mean current time. Now is an alias for block.timestamp. diff --git a/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts b/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts index 330be24632..70bbd338e8 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts @@ -4,22 +4,22 @@ import { isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfie import { default as algorithm } from './algorithmCategories' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' import AbstractAst from './abstractAstView' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class checksEffectsInteraction { - - name = 'Check effects: ' - desc = 'Avoid potential reentrancy bugs' - categories = category.SECURITY - algorithm = algorithm.HEURISTIC +export default class checksEffectsInteraction implements AnalyzerModule { + name: string = 'Check effects: ' + description: string = 'Avoid potential reentrancy bugs' + category: ModuleCategory = category.SECURITY + algorithm: ModuleAlgorithm = algorithm.HEURISTIC - abstractAst = new AbstractAst() + abstractAst: AbstractAst = new AbstractAst() - visit = this.abstractAst.build_visit((node) => isInteraction(node) || isEffect(node) || isLocalCallGraphRelevantNode(node)) + visit = this.abstractAst.build_visit((node: AstNodeLegacy) => isInteraction(node) || isEffect(node) || isLocalCallGraphRelevantNode(node)) report = this.abstractAst.build_report(this._report.bind(this)) - private _report (contracts, multipleContractsWithSameName) { - const warnings: any[] = [] + private _report (contracts, multipleContractsWithSameName): ReportObj[] { + const warnings: ReportObj[] = [] const hasModifiers = contracts.some((item) => item.modifiers.length > 0) const callGraph = buildGlobalFuncCallGraph(contracts) contracts.forEach((contract) => { diff --git a/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts b/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts index 4d9e1dd87b..e176495927 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts @@ -6,17 +6,18 @@ import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCall import { default as algorithm } from './algorithmCategories' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' import AbstractAst from './abstractAstView' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class constantFunctions { - name = 'Constant functions: ' - desc = 'Check for potentially constant functions' - categories = category.MISC - algorithm = algorithm.HEURISTIC +export default class constantFunctions implements AnalyzerModule { + name: string = 'Constant functions: ' + description: string = 'Check for potentially constant functions' + category: ModuleCategory = category.MISC + algorithm: ModuleAlgorithm = algorithm.HEURISTIC - abstractAst = new AbstractAst() + abstractAst: AbstractAst = new AbstractAst() visit = this.abstractAst.build_visit( - (node) => isLowLevelCall(node) || + (node: AstNodeLegacy) => isLowLevelCall(node) || isTransfer(node) || isExternalDirectCall(node) || isEffect(node) || @@ -29,8 +30,8 @@ export default class constantFunctions { report = this.abstractAst.build_report(this._report.bind(this)) - private _report (contracts, multipleContractsWithSameName) { - const warnings: any = [] + private _report (contracts, multipleContractsWithSameName): ReportObj[] { + const warnings: ReportObj[] = [] const hasModifiers = contracts.some((item) => item.modifiers.length > 0) const callGraph = buildGlobalFuncCallGraph(contracts) diff --git a/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts b/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts index 231bfd6cee..11428a78f4 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts @@ -1,19 +1,20 @@ import { default as category } from './categories' import { isDeleteOfDynamicArray } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class deleteDynamicArrays { - rel: any = [] - name = 'Delete on dynamic Array: ' - desc = 'Use require and appropriately' - categories = category.GAS - algorithm = algorithm.EXACT +export default class deleteDynamicArrays implements AnalyzerModule { + rel: AstNodeLegacy[] = [] + name: string = 'Delete on dynamic Array: ' + description: string = 'Use require and appropriately' + category: ModuleCategory = category.GAS + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isDeleteOfDynamicArray(node)) this.rel.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.rel.map((node) => { return { warning: 'The “delete” operation when applied to a dynamically sized array in Solidity generates code to delete each of the elements contained. If the array is large, this operation can surpass the block gas limit and raise an OOG exception. Also nested dynamically sized objects can produce the same results.', diff --git a/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts b/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts index 3ebfb94034..f8410cc112 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts @@ -1,17 +1,20 @@ import { default as category } from './categories' +import { default as algorithm } from './algorithmCategories' import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class deleteFromDynamicArray { - relevantNodes: any[] = [] - name = 'Delete from dynamic Array: ' - desc = 'Using delete on an array leaves a gap' - categories = category.MISC +export default class deleteFromDynamicArray implements AnalyzerModule { + relevantNodes: AstNodeLegacy[] = [] + name: string = 'Delete from dynamic Array: ' + description: string = 'Using delete on an array leaves a gap' + category: ModuleCategory = category.MISC + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { - if (isDeleteFromDynamicArray(node) && !isMappingIndexAccess(node.children[0])) this.relevantNodes.push(node) + visit (node: AstNodeLegacy): void { + if (isDeleteFromDynamicArray(node) && node.children && !isMappingIndexAccess(node.children[0])) this.relevantNodes.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.relevantNodes.map((node) => { return { warning: 'Using delete on an array leaves a gap. The length of the array remains the same. If you want to remove the empty position you need to shift items manually and update the length property.\n', diff --git a/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts b/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts index d8fc4b9616..a5cd5548a0 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts @@ -2,19 +2,20 @@ import { default as category } from './categories' import { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' import AbstractAst from './abstractAstView' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class erc20Decimals { - name = 'ERC20: ' - desc = 'Decimal should be uint8' - categories = category.ERC - algorithm = algorithm.EXACT +export default class erc20Decimals implements AnalyzerModule { + name: string = 'ERC20: ' + description: string = 'Decimal should be uint8' + category: ModuleCategory = category.ERC + algorithm: ModuleAlgorithm = algorithm.EXACT - abstractAst = new AbstractAst() - visit = this.abstractAst.build_visit((node) => false) + abstractAst: AbstractAst = new AbstractAst() + visit = this.abstractAst.build_visit((node: AstNodeLegacy) => false) report = this.abstractAst.build_report(this._report.bind(this)) - private _report (contracts, multipleContractsWithSameName) { - const warnings: any = [] + private _report (contracts, multipleContractsWithSameName): ReportObj[] { + const warnings: ReportObj[] = [] contracts.forEach((contract) => { const contractAbiSignatures = contract.functions.map((f) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters)) @@ -41,7 +42,7 @@ export default class erc20Decimals { return warnings } - private isERC20 (funSignatures) { + private isERC20 (funSignatures: string[]): boolean { return funSignatures.includes('totalSupply()') && funSignatures.includes('balanceOf(address)') && funSignatures.includes('transfer(address,uint256)') && diff --git a/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts b/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts index bb690e3d86..bf15be2c5f 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts @@ -1,21 +1,30 @@ import { default as category } from './categories' +import { default as algorithm } from './algorithmCategories' import { isLoop, isBlock, getLoopBlockStartIndex, isExpressionStatement, isTransfer } from './staticAnalysisCommon' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class etherTransferInLoop { - relevantNodes: any[] = [] - name = 'Ether transfer in a loop: ' - desc = 'Avoid transferring Ether to multiple addresses in a loop' - category = category.GAS +export default class etherTransferInLoop implements AnalyzerModule { + relevantNodes: AstNodeLegacy[] = [] + name: string = 'Ether transfer in a loop: ' + description: string = 'Avoid transferring Ether to multiple addresses in a loop' + category: ModuleCategory = category.GAS + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isLoop(node)) { - let transferNodes = [] - const loopBlockStartIndex = getLoopBlockStartIndex(node) - if (loopBlockStartIndex && isBlock(node.children[loopBlockStartIndex])) { - transferNodes = node.children[loopBlockStartIndex].children - .filter(child => (isExpressionStatement(child) && - child.children[0].name === 'FunctionCall' && - isTransfer(child.children[0].children[0]))) + let transferNodes: AstNodeLegacy[] = [] + const loopBlockStartIndex: number | undefined = getLoopBlockStartIndex(node) + if (loopBlockStartIndex && node.children && isBlock(node.children[loopBlockStartIndex])) { + const childrenNodes: AstNodeLegacy[] | undefined = node.children[loopBlockStartIndex].children + if(childrenNodes) + transferNodes = childrenNodes.filter(child => ( + isExpressionStatement(child) && + child.children && + child.children[0].name === 'FunctionCall' && + child.children[0].children && + isTransfer(child.children[0].children[0]) + ) + ) if (transferNodes.length > 0) { this.relevantNodes.push(...transferNodes) } @@ -23,7 +32,7 @@ export default class etherTransferInLoop { } } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.relevantNodes.map((node) => { return { warning: 'Ether payout should not be done in a loop: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. If required then make sure that number of iterations are low and you trust each address involved.', diff --git a/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts b/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts index 2e7dfd4e5e..f33fd05eaf 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts @@ -1,20 +1,26 @@ import { default as category } from './categories' -const { isForLoop, isDynamicArrayLengthAccess, isBinaryOperation } = require('./staticAnalysisCommon') +import { default as algorithm } from './algorithmCategories' +import { isForLoop, isDynamicArrayLengthAccess, isBinaryOperation } from './staticAnalysisCommon' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class forLoopIteratesOverDynamicArray { - relevantNodes: any[] = [] - name = 'For loop iterates over dynamic array: ' - desc = 'The number of \'for\' loop iterations depends on dynamic array\'s size' - categories = category.GAS +export default class forLoopIteratesOverDynamicArray implements AnalyzerModule { + relevantNodes: AstNodeLegacy[] = [] + name: string = 'For loop iterates over dynamic array: ' + description: string = 'The number of \'for\' loop iterations depends on dynamic array\'s size' + category: ModuleCategory = category.GAS + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { - if (isForLoop(node)) { + visit (node: AstNodeLegacy): void { + if (isForLoop(node) && node.children) { + let conditionChildrenNode: AstNodeLegacy | null = null // Access 'condition' node of 'for' loop statement - const forLoopConditionNode = node.children[1] + const forLoopConditionNode: AstNodeLegacy = node.children[1] // Access right side of condition as its children - const conditionChildrenNode = forLoopConditionNode.children[1] + if(forLoopConditionNode && forLoopConditionNode.children){ + conditionChildrenNode = forLoopConditionNode.children[1] + } // Check if it is a binary operation. if yes, check if its children node access length of dynamic array - if (isBinaryOperation(conditionChildrenNode) && isDynamicArrayLengthAccess(conditionChildrenNode.children[0])) { + if (conditionChildrenNode && conditionChildrenNode.children && isBinaryOperation(conditionChildrenNode) && isDynamicArrayLengthAccess(conditionChildrenNode.children[0])) { this.relevantNodes.push(node) } else if (isDynamicArrayLengthAccess(conditionChildrenNode)) { // else check if condition node itself access length of dynamic array this.relevantNodes.push(node) @@ -22,7 +28,7 @@ export default class forLoopIteratesOverDynamicArray { } } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.relevantNodes.map((node) => { return { warning: 'Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. Additionally, using unbounded loops incurs in a lot of avoidable gas costs. Carefully test how many items at maximum you can pass to such functions to make it successful.', diff --git a/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts b/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts index 8e1677144c..a1981d73a3 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts @@ -1,19 +1,20 @@ import { default as category } from './categories' import { isRequireCall, isAssertCall } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class guardConditions { - guards: any[] = [] - name = 'Guard Conditions: ' - desc = 'Use require and appropriately' - categories = category.MISC - algorithm = algorithm.EXACT +export default class guardConditions implements AnalyzerModule { + guards: AstNodeLegacy[] = [] + name: string = 'Guard Conditions: ' + description: string = 'Use require and appropriately' + category: ModuleCategory = category.MISC + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isRequireCall(node) || isAssertCall(node)) this.guards.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { if (this.guards.length > 0) { return [{ warning: 'Use assert(x) if you never ever want x to be false, not in any circumstance (apart from a bug in your code). Use require(x) if x can be false, due to e.g. invalid input or a failing external component.', diff --git a/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts b/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts index c40a51f381..df7e253fa4 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts @@ -1,19 +1,20 @@ import { default as category } from './categories' import { isInlineAssembly } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class inlineAssembly { - inlineAssNodes: any[] = [] - name = 'Inline assembly: ' - desc = 'Use of Inline Assembly' - categories = category.SECURITY - algorithm = algorithm.EXACT +export default class inlineAssembly implements AnalyzerModule { + inlineAssNodes: AstNodeLegacy[] = [] + name: string = 'Inline assembly: ' + description: string = 'Use of Inline Assembly' + category: ModuleCategory = category.SECURITY + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isInlineAssembly(node)) this.inlineAssNodes.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.inlineAssNodes.map((node) => { return { warning: `CAUTION: The Contract uses inline assembly, this is only advised in rare cases. diff --git a/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts b/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts index 312ea02b22..e0ef80a269 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts @@ -1,19 +1,20 @@ import { default as category } from './categories' import { isIntDivision } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class intDivitionTruncate { - warningNodes: any[] = [] - name = 'Data Trucated: ' - desc = 'Division on int/uint values truncates the result.' - categories = category.MISC - algorithm = algorithm.EXACT +export default class intDivisionTruncate implements AnalyzerModule { + warningNodes: AstNodeLegacy[] = [] + name: string = 'Data Truncated: ' + description: string = 'Division on int/uint values truncates the result.' + category: ModuleCategory = category.MISC + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isIntDivision(node)) this.warningNodes.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.warningNodes.map((item, i) => { return { warning: 'Division of integer values yields an integer value again. That means e.g. 10 / 100 = 0 instead of 0.1 since the result is an integer again. This does not hold for division of (only) literal values since those yield rational constants.', diff --git a/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts b/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts index 284a25f34b..409e99a08d 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts @@ -2,15 +2,24 @@ import { default as category } from './categories' import { isLowLevelCallInst, isLowLevelCallInst050, isLowLevelCallcodeInst, isLowLevelDelegatecallInst, isLowLevelSendInst, isLowLevelSendInst050, isLLDelegatecallInst050, lowLevelCallTypes } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class lowLevelCalls { - llcNodes: any[] = [] - name = 'Low level calls: ' - desc = 'Semantics maybe unclear' - categories = category.SECURITY - algorithm = algorithm.EXACT +interface llcNode { + node: AstNodeLegacy + type: { + ident: string, + type: string + } +} + +export default class lowLevelCalls implements AnalyzerModule { + llcNodes: llcNode[] = [] + name: string = 'Low level calls: ' + description: string = 'Semantics maybe unclear' + category: ModuleCategory = category.SECURITY + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node : AstNodeLegacy): void { if (isLowLevelCallInst(node)) { this.llcNodes.push({node: node, type: lowLevelCallTypes.CALL}) } else if (isLowLevelCallInst050(node)) { @@ -28,7 +37,7 @@ export default class lowLevelCalls { } } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.llcNodes.map((item, i) => { let text = '' let morehref: any = null diff --git a/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts b/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts index 8284a059ac..eaa747f0a7 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts @@ -2,21 +2,22 @@ import { default as category } from './categories' import { isReturn, isAssignment, hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' import AbstractAst from './abstractAstView' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class noReturn { - name = 'no return: ' - desc = 'Function with return type is not returning' - categories = category.MISC - algorithm = algorithm.EXACT +export default class noReturn implements AnalyzerModule { + name: string = 'no return: ' + description: string = 'Function with return type is not returning' + category: ModuleCategory = category.MISC + algorithm: ModuleAlgorithm = algorithm.EXACT - abstractAst = new AbstractAst() + abstractAst: AbstractAst = new AbstractAst() visit = this.abstractAst.build_visit( - (node) => isReturn(node) || isAssignment(node) + (node: AstNodeLegacy) => isReturn(node) || isAssignment(node) ) report = this.abstractAst.build_report(this._report.bind(this)) - private _report (contracts, multipleContractsWithSameName) { + private _report (contracts, multipleContractsWithSameName): ReportObj[] { const warnings: any[] = [] contracts.forEach((contract) => { @@ -39,26 +40,26 @@ export default class noReturn { return warnings } - private shouldReturn (func) { + private shouldReturn (func): boolean { return func.returns.length > 0 } - private hasReturnStatement (func) { + private hasReturnStatement (func): boolean { return func.relevantNodes.filter(isReturn).length > 0 } - private hasAssignToAllNamedReturns (func) { + private hasAssignToAllNamedReturns (func): boolean { const namedReturns = func.returns.filter((n) => n.name.length > 0).map((n) => n.name) const assignedVars = func.relevantNodes.filter(isAssignment).map(getEffectedVariableName) const diff = namedReturns.filter(e => !assignedVars.includes(e)) return diff.length === 0 } - private hasNamedReturns (func) { + private hasNamedReturns (func): boolean { return func.returns.filter((n) => n.name.length > 0).length > 0 } - private hasNamedAndUnnamedReturns (func) { + private hasNamedAndUnnamedReturns (func): boolean { return func.returns.filter((n) => n.name.length === 0).length > 0 && this.hasNamedReturns(func) } diff --git a/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts b/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts index a3f5c603ec..635cc0518e 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts @@ -2,23 +2,24 @@ import { default as category } from './categories' import { isStatement, isSelfdestructCall } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' import AbstractAst from './abstractAstView' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class selfdestruct { - name = 'Selfdestruct: ' - desc = 'Be aware of caller contracts.' - categories = category.SECURITY - algorithm = algorithm.HEURISTIC +export default class selfdestruct implements AnalyzerModule { + name: string = 'Selfdestruct: ' + description: string = 'Be aware of caller contracts.' + category: ModuleCategory = category.SECURITY + algorithm: ModuleAlgorithm = algorithm.HEURISTIC abstractAst = new AbstractAst() visit = this.abstractAst.build_visit( - (node) => isStatement(node) || + (node: AstNodeLegacy) => isStatement(node) || isSelfdestructCall(node) ) report = this.abstractAst.build_report(this._report.bind(this)) - private _report (contracts, multipleContractsWithSameName) { - const warnings: any[] = [] + private _report (contracts, multipleContractsWithSameName): ReportObj[] { + const warnings: ReportObj[] = [] contracts.forEach((contract) => { contract.functions.forEach((func) => { diff --git a/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts b/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts index 48175b15fd..ddeff8ce4a 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts @@ -4,22 +4,22 @@ import { default as algorithm } from './algorithmCategories' import AbstractAst from './abstractAstView' import { get } from 'fast-levenshtein' import { util } from 'remix-lib' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class similarVariableNames { - name = 'Similar variable names: ' - desc = 'Check if variable names are too similar' - abstractAst = new AbstractAst() - categories = category.MISC - algorithm = algorithm.EXACT +export default class similarVariableNames implements AnalyzerModule { + name: string = 'Similar variable names: ' + description: string = 'Check if variable names are too similar' + category: ModuleCategory = category.MISC + algorithm: ModuleAlgorithm = algorithm.EXACT - visit = this.abstractAst.build_visit( - (node) => false - ) + abstractAst:AbstractAst = new AbstractAst() + + visit = this.abstractAst.build_visit((node: AstNodeLegacy) => false) report = this.abstractAst.build_report(this._report.bind(this)) - private _report (contracts, multipleContractsWithSameName) { - const warnings: any[] = [] + private _report (contracts, multipleContractsWithSameName): ReportObj[] { + const warnings: ReportObj[] = [] const hasModifiers = contracts.some((item) => item.modifiers.length > 0) contracts.forEach((contract) => { @@ -48,29 +48,29 @@ export default class similarVariableNames { return warnings } - private findSimilarVarNames (vars) { - const similar: any[] = [] - const comb = {} + private findSimilarVarNames (vars: string[]): Record[] { + const similar: Record[] = [] + const comb: Record = {} vars.map((varName1) => vars.map((varName2) => { if (varName1.length > 1 && varName2.length > 1 && varName2 !== varName1 && !this.isCommonPrefixedVersion(varName1, varName2) && !this.isCommonNrSuffixVersion(varName1, varName2) && !(comb[varName1 + ';' + varName2] || comb[varName2 + ';' + varName1])) { comb[varName1 + ';' + varName2] = true - const distance = get(varName1, varName2) + const distance: number = get(varName1, varName2) if (distance <= 2) similar.push({ var1: varName1, var2: varName2, distance: distance }) } })) return similar } - private isCommonPrefixedVersion (varName1, varName2) { + private isCommonPrefixedVersion (varName1: string, varName2: string): boolean { return (varName1.startsWith('_') && varName1.slice(1) === varName2) || (varName2.startsWith('_') && varName2.slice(1) === varName1) } - private isCommonNrSuffixVersion (varName1, varName2) { - const ref = '^' + util.escapeRegExp(varName1.slice(0, -1)) + '[0-9]*$' + private isCommonNrSuffixVersion (varName1: string, varName2: string): boolean { + const ref: string = '^' + util.escapeRegExp(varName1.slice(0, -1)) + '[0-9]*$' return varName2.match(ref) != null } - private getFunctionVariables (contract, func) { + private getFunctionVariables (contract, func): string[] { return contract.stateVariables.concat(func.localVariables) } } diff --git a/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts b/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts index f15fae7b8f..e9f73205ee 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts @@ -1,21 +1,24 @@ import { default as category } from './categories' +import { default as algorithm } from './algorithmCategories' import { isStringToBytesConversion, isBytesLengthCheck } from './staticAnalysisCommon' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class stringBytesLength { - name = 'String Length: ' - desc = 'Bytes length != String length' - categories = category.MISC +export default class stringBytesLength implements AnalyzerModule { + name: string = 'String Length: ' + description: string = 'Bytes length != String length' + category: ModuleCategory = category.MISC + algorithm: ModuleAlgorithm = algorithm.EXACT - stringToBytesConversions: any[] = [] - bytesLengthChecks: any[] = [] + stringToBytesConversions: AstNodeLegacy[] = [] + bytesLengthChecks: AstNodeLegacy[] = [] - visit (node) { + visit (node: AstNodeLegacy): void { if (isStringToBytesConversion(node)) this.stringToBytesConversions.push(node) else if (isBytesLengthCheck(node)) this.bytesLengthChecks.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) { return [{ warning: 'Bytes and string length are not the same since strings are assumed to be UTF-8 encoded (according to the ABI defintion) therefore one character is not nessesarily encoded in one byte of data.', diff --git a/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts b/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts index 9a6f05afc4..76ebdcb1d8 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts @@ -1,19 +1,20 @@ import { default as category } from './categories' import { isThisLocalCall } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class thisLocal { - warningNodes: any[] = [] - name = 'This on local calls: ' - desc = 'Invocation of local functions via this' - categories = category.GAS - algorithm = algorithm.EXACT +export default class thisLocal implements AnalyzerModule { + warningNodes: AstNodeLegacy[] = [] + name: string = 'This on local calls: ' + description: string = 'Invocation of local functions via this' + category: ModuleCategory = category.GAS + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { + visit (node: AstNodeLegacy): void { if (isThisLocalCall(node)) this.warningNodes.push(node) } - report (compilationResults) { + report (compilationResults: CompilationResult): ReportObj[] { return this.warningNodes.map(function (item, i) { return { warning: 'Use of "this" for local functions: Never use this to call functions in the same contract, it only consumes more gas than normal local calls.', diff --git a/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts b/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts index b3a568d6bd..fcf8e15ebc 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts @@ -1,25 +1,26 @@ import { default as category } from './categories' import { default as algorithm } from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types' -export default class txOrigin { - txOriginNodes: any[] = [] - name = 'Transaction origin: ' - desc = 'Warn if tx.origin is used' - categories = category.SECURITY - algorithm = algorithm.EXACT +export default class txOrigin implements AnalyzerModule { + txOriginNodes: AstNodeLegacy[] = [] + name: string = 'Transaction origin: ' + description: string = 'Warn if tx.origin is used' + category: ModuleCategory = category.SECURITY + algorithm: ModuleAlgorithm = algorithm.EXACT - visit (node) { - if (node.name === 'MemberAccess' && + visit (node: AstNodeLegacy): void { + if (node.name === 'MemberAccess' && node.attributes && node.attributes.member_name === 'origin' && (node.attributes.type === 'address' || node.attributes.type === 'address payable') && - node.children && node.children.length && + node.children && node.children.length && node.children[0].attributes && node.children[0].attributes.type === 'tx' && node.children[0].attributes.value === 'tx') { this.txOriginNodes.push(node) } } - report () { + report (compilationResults: CompilationResult): ReportObj[] { return this.txOriginNodes.map((item, i) => { return { warning: `Use of tx.origin: "tx.origin" is useful only in very exceptional cases. diff --git a/remix-analyzer/src/types.ts b/remix-analyzer/src/types.ts new file mode 100644 index 0000000000..95f362f88a --- /dev/null +++ b/remix-analyzer/src/types.ts @@ -0,0 +1,303 @@ +export interface AnalyzerModule { + name: string, + description: string, + category: ModuleCategory + algorithm: ModuleAlgorithm + visit: Function + report: Function +} + +export interface ModuleAlgorithm { + hasFalsePositives: boolean, + hasFalseNegatives: boolean, + id: string +} + +export interface ModuleCategory { + displayName: string, + id: string +} + +export interface ReportObj { + warning: string, + location?: string | null, + more?: string +} + +export interface CompilationResult { + error?: CompilationError, + /** not present if no errors/warnings were encountered */ + errors?: CompilationError[] + /** This contains the file-level outputs. In can be limited/filtered by the outputSelection settings */ + sources?: { + [contractName: string]: CompilationSource + } + /** This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings */ + contracts?: { + /** If the language used has no contract names, this field should equal to an empty string. */ + [fileName: string]: { + [contract: string]: CompiledContract + } + } + } + + /////////// + // ERROR // + /////////// + + export interface CompilationError { + /** Location within the source file */ + sourceLocation?: { + file: string + start: number + end: number + } + /** Error type */ + type?: CompilationErrorType + /** Component where the error originated, such as "general", "ewasm", etc. */ + component?: 'general' | 'ewasm' | string + severity?: 'error' | 'warning' + message?: string + mode?: 'panic' + /** the message formatted with source location */ + formattedMessage?: string + } + + type CompilationErrorType = + | 'JSONError' + | 'IOError' + | 'ParserError' + | 'DocstringParsingError' + | 'SyntaxError' + | 'DeclarationError' + | 'TypeError' + | 'UnimplementedFeatureError' + | 'InternalCompilerError' + | 'Exception' + | 'CompilerError' + | 'FatalError' + | 'Warning' + + //////////// + // SOURCE // + //////////// + export interface CompilationSource { + /** Identifier of the source (used in source maps) */ + id: number + /** The AST object */ + ast: AstNode + /** The legacy AST object */ + legacyAST: AstNodeLegacy + } + + ///////// + // AST // + ///////// + export interface AstNode { + absolutePath?: string + exportedSymbols?: Object + id: number + nodeType: string + nodes?: Array + src: string + literals?: Array + file?: string + scope?: number + sourceUnit?: number + symbolAliases?: Array + [x: string]: any + } + + export interface AstNodeLegacy { + id: number + name: string + src: string + children?: Array + attributes?: AstNodeAtt + } + + export interface AstNodeAtt { + operator?: string + string?: null + type?: string + value?: string + constant?: boolean + name?: string + public?: boolean + exportedSymbols?: Object + argumentTypes?: null + absolutePath?: string + [x: string]: any + } + + ////////////// + // CONTRACT // + ////////////// + export interface CompiledContract { + /** The Ethereum Contract ABI. If empty, it is represented as an empty array. */ + abi: ABIDescription[] + // See the Metadata Output documentation (serialised JSON string) + metadata: string + /** User documentation (natural specification) */ + userdoc: UserDocumentation + /** Developer documentation (natural specification) */ + devdoc: DeveloperDocumentation + /** Intermediate representation (string) */ + ir: string + /** EVM-related outputs */ + evm: { + assembly: string + legacyAssembly: {} + /** Bytecode and related details. */ + bytecode: BytecodeObject + deployedBytecode: BytecodeObject + /** The list of function hashes */ + methodIdentifiers: { + [functionIdentifier: string]: string + } + // Function gas estimates + gasEstimates: { + creation: { + codeDepositCost: string + executionCost: 'infinite' | string + totalCost: 'infinite' | string + } + external: { + [functionIdentifier: string]: string + } + internal: { + [functionIdentifier: string]: 'infinite' | string + } + } + } + /** eWASM related outputs */ + ewasm: { + /** S-expressions format */ + wast: string + /** Binary format (hex string) */ + wasm: string + } + } + + ///////// + // ABI // + ///////// + export type ABIDescription = FunctionDescription | EventDescription + + export interface FunctionDescription { + /** Type of the method. default is 'function' */ + type?: 'function' | 'constructor' | 'fallback' | 'receive' + /** The name of the function. Constructor and fallback function never have name */ + name?: string + /** List of parameters of the method. Fallback function doesn’t have inputs. */ + inputs?: ABIParameter[] + /** List of the outputs parameters for the method, if any */ + outputs?: ABIParameter[] + /** State mutability of the method */ + stateMutability: 'pure' | 'view' | 'nonpayable' | 'payable' + /** true if function accepts Ether, false otherwise. Default is false */ + payable?: boolean + /** true if function is either pure or view, false otherwise. Default is false */ + constant?: boolean + } + + export interface EventDescription { + type: 'event' + name: string + inputs: ABIParameter & + { + /** true if the field is part of the log’s topics, false if it one of the log’s data segment. */ + indexed: boolean + }[] + /** true if the event was declared as anonymous. */ + anonymous: boolean + } + + export interface ABIParameter { + /** The name of the parameter */ + name: string + /** The canonical type of the parameter */ + type: ABITypeParameter + /** Used for tuple types */ + components?: ABIParameter[] + } + + export type ABITypeParameter = + | 'uint' + | 'uint[]' // TODO : add + | 'int' + | 'int[]' // TODO : add + | 'address' + | 'address[]' + | 'bool' + | 'bool[]' + | 'fixed' + | 'fixed[]' // TODO : add + | 'ufixed' + | 'ufixed[]' // TODO : add + | 'bytes' + | 'bytes[]' // TODO : add + | 'function' + | 'function[]' + | 'tuple' + | 'tuple[]' + | string // Fallback + +/////////////////////////// + // NATURAL SPECIFICATION // + /////////////////////////// + + // Userdoc + export interface UserDocumentation { + methods: UserMethodList + notice: string + } + + export type UserMethodList = { + [functionIdentifier: string]: UserMethodDoc + } & { + 'constructor'?: string + } + export interface UserMethodDoc { + notice: string + } + + // Devdoc + export interface DeveloperDocumentation { + author: string + title: string + details: string + methods: DevMethodList + } + + export interface DevMethodList { + [functionIdentifier: string]: DevMethodDoc + } + + export interface DevMethodDoc { + author: string + details: string + return: string + params: { + [param: string]: string + } + } + + ////////////// + // BYTECODE // + ////////////// + export interface BytecodeObject { + /** The bytecode as a hex string. */ + object: string + /** Opcodes list */ + opcodes: string + /** The source mapping as a string. See the source mapping definition. */ + sourceMap: string + /** If given, this is an unlinked object. */ + linkReferences?: { + [contractName: string]: { + /** Byte offsets into the bytecode. */ + [library: string]: { start: number; length: number }[] + } + } + } \ No newline at end of file