Commit 0bdf8214 authored by aniket-engg's avatar aniket-engg

libs linting fix

parent 2e6d289a
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
"extends": "../../.eslintrc", "extends": "../../.eslintrc",
"rules": { "rules": {
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off" "@typescript-eslint/no-unused-vars": "off",
"no-unused-vars": "off",
"dot-notation": "off"
}, },
"ignorePatterns": ["!**/*"] "ignorePatterns": ["!**/*"]
} }
export { default as CodeAnalysis} from './solidity-analyzer' export { default as CodeAnalysis } from './solidity-analyzer'
...@@ -9,7 +9,6 @@ type ModuleObj = { ...@@ -9,7 +9,6 @@ type ModuleObj = {
} }
export default class staticAnalysisRunner { export default class staticAnalysisRunner {
/** /**
* Run analysis (Used by IDE) * Run analysis (Used by IDE)
* @param compilationResult contract compilation result * @param compilationResult contract compilation result
...@@ -18,9 +17,9 @@ export default class staticAnalysisRunner { ...@@ -18,9 +17,9 @@ export default class staticAnalysisRunner {
*/ */
run (compilationResult: CompilationResult, toRun: number[], callback: ((reports: AnalysisReport[]) => void)): void { run (compilationResult: CompilationResult, toRun: number[], callback: ((reports: AnalysisReport[]) => void)): void {
const modules: ModuleObj[] = toRun.map((i) => { const modules: ModuleObj[] = toRun.map((i) => {
const module = this.modules()[i] const Module = this.modules()[i]
const m = new module() const m = new Module()
return { 'name': m.name, 'mod': m } return { name: m.name, mod: m }
}) })
this.runWithModuleList(compilationResult, modules, callback) this.runWithModuleList(compilationResult, modules, callback)
} }
......
import { getStateVariableDeclarationsFromContractNode, getInheritsFromName, getContractName, import {
getStateVariableDeclarationsFromContractNode, getInheritsFromName, getContractName,
getFunctionOrModifierDefinitionParameterPart, getType, getDeclaredVariableName, getFunctionOrModifierDefinitionParameterPart, getType, getDeclaredVariableName,
getFunctionDefinitionReturnParameterPart, getCompilerVersion } from './staticAnalysisCommon' getFunctionDefinitionReturnParameterPart, getCompilerVersion
} from './staticAnalysisCommon'
import { AstWalker } from '@remix-project/remix-astwalker' import { AstWalker } from '@remix-project/remix-astwalker'
import { FunctionDefinitionAstNode, ParameterListAstNode, ModifierDefinitionAstNode, ContractHLAst, VariableDeclarationAstNode, import {
FunctionHLAst, ReportObj, ReportFunction, VisitFunction, ModifierHLAst, CompilationResult } from '../../types' FunctionDefinitionAstNode, ParameterListAstNode, ModifierDefinitionAstNode, ContractHLAst, VariableDeclarationAstNode,
FunctionHLAst, ReportObj, ReportFunction, VisitFunction, ModifierHLAst, CompilationResult
} from '../../types'
type WrapFunction = ((contracts: ContractHLAst[], isSameName: boolean, version: string) => ReportObj[]) type WrapFunction = ((contracts: ContractHLAst[], isSameName: boolean, version: string) => ReportObj[])
...@@ -23,7 +27,7 @@ export default class abstractAstView { ...@@ -23,7 +27,7 @@ export default class abstractAstView {
*/ */
multipleContractsWithSameName = false multipleContractsWithSameName = false
/** /**
* Builds a higher level AST view. I creates a list with each contract as an object in it. * Builds a higher level AST view. I creates a list with each contract as an object in it.
* Example contractsOut: * Example contractsOut:
* *
...@@ -48,9 +52,10 @@ export default class abstractAstView { ...@@ -48,9 +52,10 @@ export default class abstractAstView {
* @contractsOut {list} return list for high level AST view * @contractsOut {list} return list for high level AST view
* @return {ASTNode -> void} returns a function that can be used as visit function for static analysis modules, to build up a higher level AST view for further analysis. * @return {ASTNode -> void} returns a function that can be used as visit function for static analysis modules, to build up a higher level AST view for further analysis.
*/ */
// eslint-disable-next-line camelcase
build_visit (relevantNodeFilter: ((node:any) => boolean)): VisitFunction { build_visit (relevantNodeFilter: ((node:any) => boolean)): VisitFunction {
return (node: any) => { return (node: any) => {
if (node.nodeType === "ContractDefinition") { if (node.nodeType === 'ContractDefinition') {
this.setCurrentContract({ this.setCurrentContract({
node: node, node: node,
functions: [], functions: [],
...@@ -59,11 +64,11 @@ export default class abstractAstView { ...@@ -59,11 +64,11 @@ export default class abstractAstView {
inheritsFrom: [], inheritsFrom: [],
stateVariables: getStateVariableDeclarationsFromContractNode(node) stateVariables: getStateVariableDeclarationsFromContractNode(node)
}) })
} else if (node.nodeType === "InheritanceSpecifier") { } else if (node.nodeType === 'InheritanceSpecifier') {
const currentContract: ContractHLAst = this.getCurrentContract() const currentContract: ContractHLAst = this.getCurrentContract()
const inheritsFromName: string = getInheritsFromName(node) const inheritsFromName: string = getInheritsFromName(node)
currentContract.inheritsFrom.push(inheritsFromName) currentContract.inheritsFrom.push(inheritsFromName)
} else if (node.nodeType === "FunctionDefinition") { } else if (node.nodeType === 'FunctionDefinition') {
this.setCurrentFunction({ this.setCurrentFunction({
node: node, node: node,
relevantNodes: [], relevantNodes: [],
...@@ -78,14 +83,14 @@ export default class abstractAstView { ...@@ -78,14 +83,14 @@ export default class abstractAstView {
this.getCurrentFunction().relevantNodes.push(item.node) this.getCurrentFunction().relevantNodes.push(item.node)
} }
}) })
} else if (node.nodeType === "ModifierDefinition") { } else if (node.nodeType === 'ModifierDefinition') {
this.setCurrentModifier({ this.setCurrentModifier({
node: node, node: node,
relevantNodes: [], relevantNodes: [],
localVariables: this.getLocalVariables(node), localVariables: this.getLocalVariables(node),
parameters: this.getLocalParameters(node) parameters: this.getLocalParameters(node)
}) })
} else if (node.nodeType === "ModifierInvocation") { } else if (node.nodeType === 'ModifierInvocation') {
if (!this.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.') if (!this.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.')
this.getCurrentFunction().modifierInvocations.push(node) this.getCurrentFunction().modifierInvocations.push(node)
} else if (relevantNodeFilter(node)) { } else if (relevantNodeFilter(node)) {
...@@ -102,6 +107,7 @@ export default class abstractAstView { ...@@ -102,6 +107,7 @@ export default class abstractAstView {
} }
} }
// eslint-disable-next-line camelcase
build_report (wrap: WrapFunction): ReportFunction { build_report (wrap: WrapFunction): ReportFunction {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
return (compilationResult: CompilationResult) => { return (compilationResult: CompilationResult) => {
...@@ -176,7 +182,7 @@ export default class abstractAstView { ...@@ -176,7 +182,7 @@ export default class abstractAstView {
private getLocalVariables (funcNode: ParameterListAstNode): VariableDeclarationAstNode[] { private getLocalVariables (funcNode: ParameterListAstNode): VariableDeclarationAstNode[] {
const locals: VariableDeclarationAstNode[] = [] const locals: VariableDeclarationAstNode[] = []
new AstWalker().walkFull(funcNode, (node: any) => { new AstWalker().walkFull(funcNode, (node: any) => {
if (node.nodeType === "VariableDeclaration") locals.push(node) if (node.nodeType === 'VariableDeclaration') locals.push(node)
return true return true
}) })
return locals return locals
......
import { default as category } from './categories' import category from './categories'
import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon' import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, BlockAstNode, IfStatementAstNode, import {
WhileStatementAstNode, ForStatementAstNode, CompilationResult, ExpressionStatementAstNode, SupportedVersion} from './../../types' AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, BlockAstNode, IfStatementAstNode,
WhileStatementAstNode, ForStatementAstNode, CompilationResult, ExpressionStatementAstNode, SupportedVersion
} from './../../types'
export default class assignAndCompare implements AnalyzerModule { export default class assignAndCompare implements AnalyzerModule {
warningNodes: ExpressionStatementAstNode[] = [] warningNodes: ExpressionStatementAstNode[] = []
name = `Result not used: ` name = 'Result not used: '
description = `The result of an operation not used` description = 'The result of an operation not used'
category: ModuleCategory = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
......
import { default as category } from './categories' import category from './categories'
import { isBlockBlockHashAccess } from './staticAnalysisCommon' import { isBlockBlockHashAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, FunctionCallAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, FunctionCallAstNode, SupportedVersion } from './../../types'
export default class blockBlockhash implements AnalyzerModule { export default class blockBlockhash implements AnalyzerModule {
warningNodes: FunctionCallAstNode[] = [] warningNodes: FunctionCallAstNode[] = []
name = `Block hash: ` name = 'Block hash: '
description = `Can be influenced by miners` description = 'Can be influenced by miners'
category: ModuleCategory = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -30,4 +30,3 @@ export default class blockBlockhash implements AnalyzerModule { ...@@ -30,4 +30,3 @@ export default class blockBlockhash implements AnalyzerModule {
}) })
} }
} }
import { default as category } from './categories' import category from './categories'
import { isNowAccess, isBlockTimestampAccess, getCompilerVersion } from './staticAnalysisCommon' import { isNowAccess, isBlockTimestampAccess, getCompilerVersion } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, IdentifierAstNode, import {
MemberAccessAstNode, SupportedVersion} from './../../types' AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, IdentifierAstNode,
MemberAccessAstNode, SupportedVersion
} from './../../types'
export default class blockTimestamp implements AnalyzerModule { export default class blockTimestamp implements AnalyzerModule {
warningNowNodes: IdentifierAstNode[] = [] warningNowNodes: IdentifierAstNode[] = []
warningblockTimestampNodes: MemberAccessAstNode[] = [] warningblockTimestampNodes: MemberAccessAstNode[] = []
name = `Block timestamp: ` name = 'Block timestamp: '
description = `Can be influenced by miners` description = 'Can be influenced by miners'
category: ModuleCategory = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
start: '0.4.12' start: '0.4.12'
} }
visit (node: IdentifierAstNode | MemberAccessAstNode ): void { visit (node: IdentifierAstNode | MemberAccessAstNode): void {
if (node.nodeType === "Identifier" && isNowAccess(node)) this.warningNowNodes.push(node) if (node.nodeType === 'Identifier' && isNowAccess(node)) this.warningNowNodes.push(node)
else if (node.nodeType === "MemberAccess" && isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node) else if (node.nodeType === 'MemberAccess' && isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node)
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
......
export default { export default {
SECURITY: {displayName: 'Security', id: 'SEC'}, SECURITY: { displayName: 'Security', id: 'SEC' },
GAS: {displayName: 'Gas & Economy', id: 'GAS'}, GAS: { displayName: 'Gas & Economy', id: 'GAS' },
MISC: {displayName: 'Miscellaneous', id: 'MISC'}, MISC: { displayName: 'Miscellaneous', id: 'MISC' },
ERC: {displayName: 'ERC', id: 'ERC'} ERC: { displayName: 'ERC', id: 'ERC' }
} }
import { default as category } from './categories' import category from './categories'
import { isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfiedFuncDefinitionIdent, import {
isWriteOnStateVariable, isStorageVariableDeclaration, getFullQualifiedFunctionCallIdent, getCompilerVersion } from './staticAnalysisCommon' isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfiedFuncDefinitionIdent,
import { default as algorithm } from './algorithmCategories' isWriteOnStateVariable, isStorageVariableDeclaration, getFullQualifiedFunctionCallIdent, getCompilerVersion
} from './staticAnalysisCommon'
import algorithm from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VariableDeclarationAstNode, import {
AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VariableDeclarationAstNode,
FunctionHLAst, ContractCallGraph, Context, FunctionCallAstNode, AssignmentAstNode, UnaryOperationAstNode, FunctionHLAst, ContractCallGraph, Context, FunctionCallAstNode, AssignmentAstNode, UnaryOperationAstNode,
InlineAssemblyAstNode, ReportFunction, VisitFunction, FunctionCallGraph, SupportedVersion } from './../../types' InlineAssemblyAstNode, ReportFunction, VisitFunction, FunctionCallGraph, SupportedVersion
} from './../../types'
export default class checksEffectsInteraction implements AnalyzerModule { export default class checksEffectsInteraction implements AnalyzerModule {
name = `Check-effects-interaction: ` name = 'Check-effects-interaction: '
description = `Potential reentrancy bugs` description = 'Potential reentrancy bugs'
category: ModuleCategory = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.HEURISTIC algorithm: ModuleAlgorithm = algorithm.HEURISTIC
version: SupportedVersion = { version: SupportedVersion = {
...@@ -50,7 +54,7 @@ export default class checksEffectsInteraction implements AnalyzerModule { ...@@ -50,7 +54,7 @@ export default class checksEffectsInteraction implements AnalyzerModule {
comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : '' comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : ''
warnings.push({ warnings.push({
warning: `Potential violation of Checks-Effects-Interaction pattern in ${funcName}: Could potentially lead to re-entrancy vulnerability. ${comments}`, warning: `Potential violation of Checks-Effects-Interaction pattern in ${funcName}: Could potentially lead to re-entrancy vulnerability. ${comments}`,
location: func.node['src'], location: func.node.src,
more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#re-entrancy` more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#re-entrancy`
}) })
} }
...@@ -92,4 +96,3 @@ export default class checksEffectsInteraction implements AnalyzerModule { ...@@ -92,4 +96,3 @@ export default class checksEffectsInteraction implements AnalyzerModule {
return analyseCallGraph(context.callGraph, startFuncName, context, (node: any, context: Context) => isWriteOnStateVariable(node, context.stateVariables)) return analyseCallGraph(context.callGraph, startFuncName, context, (node: any, context: Context) => isWriteOnStateVariable(node, context.stateVariables))
} }
} }
import { default as category } from './categories' import category from './categories'
import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode, isSelfdestructCall, import {
isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode, isSelfdestructCall,
isDeleteUnaryOperation, isPayableFunction, isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody, isDeleteUnaryOperation, isPayableFunction, isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody,
isConstantFunction, isWriteOnStateVariable, isStorageVariableDeclaration, isCallToNonConstLocalFunction, isConstantFunction, isWriteOnStateVariable, isStorageVariableDeclaration, isCallToNonConstLocalFunction,
getFullQualifiedFunctionCallIdent} from './staticAnalysisCommon' getFullQualifiedFunctionCallIdent
import { default as algorithm } from './algorithmCategories' } from './staticAnalysisCommon'
import algorithm from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractCallGraph, Context, ContractHLAst, import {
FunctionHLAst, VariableDeclarationAstNode, FunctionCallGraph, FunctionCallAstNode, VisitFunction, ReportFunction, SupportedVersion} from './../../types' AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractCallGraph, Context, ContractHLAst,
FunctionHLAst, VariableDeclarationAstNode, FunctionCallGraph, FunctionCallAstNode, VisitFunction, ReportFunction, SupportedVersion
} from './../../types'
export default class constantFunctions implements AnalyzerModule { export default class constantFunctions implements AnalyzerModule {
name = `Constant/View/Pure functions: ` name = 'Constant/View/Pure functions: '
description = `Potentially constant/view/pure functions` description = 'Potentially constant/view/pure functions'
category: ModuleCategory = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.HEURISTIC algorithm: ModuleAlgorithm = algorithm.HEURISTIC
version: SupportedVersion = { version: SupportedVersion = {
...@@ -26,8 +30,8 @@ export default class constantFunctions implements AnalyzerModule { ...@@ -26,8 +30,8 @@ export default class constantFunctions implements AnalyzerModule {
isExternalDirectCall(node) || isExternalDirectCall(node) ||
isEffect(node) || isEffect(node) ||
isLocalCallGraphRelevantNode(node) || isLocalCallGraphRelevantNode(node) ||
node.nodeType === "InlineAssembly" || node.nodeType === 'InlineAssembly' ||
node.nodeType === "NewExpression" || node.nodeType === 'NewExpression' ||
isSelfdestructCall(node) || isSelfdestructCall(node) ||
isDeleteUnaryOperation(node) isDeleteUnaryOperation(node)
) )
...@@ -67,13 +71,13 @@ export default class constantFunctions implements AnalyzerModule { ...@@ -67,13 +71,13 @@ export default class constantFunctions implements AnalyzerModule {
if (func['potentiallyshouldBeConst']) { if (func['potentiallyshouldBeConst']) {
warnings.push({ warnings.push({
warning: `${funcName} : Potentially should be constant/view/pure but is not. ${comments}`, warning: `${funcName} : Potentially should be constant/view/pure but is not. ${comments}`,
location: func.node['src'], location: func.node.src,
more: `https://solidity.readthedocs.io/en/${version}/contracts.html#view-functions` more: `https://solidity.readthedocs.io/en/${version}/contracts.html#view-functions`
}) })
} else { } else {
warnings.push({ warnings.push({
warning: `${funcName} : Is constant but potentially should not be. ${comments}`, warning: `${funcName} : Is constant but potentially should not be. ${comments}`,
location: func.node['src'], location: func.node.src,
more: `https://solidity.readthedocs.io/en/${version}/contracts.html#view-functions` more: `https://solidity.readthedocs.io/en/${version}/contracts.html#view-functions`
}) })
} }
...@@ -101,8 +105,8 @@ export default class constantFunctions implements AnalyzerModule { ...@@ -101,8 +105,8 @@ export default class constantFunctions implements AnalyzerModule {
isTransfer(node) || isTransfer(node) ||
this.isCallOnNonConstExternalInterfaceFunction(node, context) || this.isCallOnNonConstExternalInterfaceFunction(node, context) ||
isCallToNonConstLocalFunction(node) || isCallToNonConstLocalFunction(node) ||
node.nodeType === "InlineAssembly" || node.nodeType === 'InlineAssembly' ||
node.nodeType === "NewExpression" || node.nodeType === 'NewExpression' ||
isSelfdestructCall(node) || isSelfdestructCall(node) ||
isDeleteUnaryOperation(node) isDeleteUnaryOperation(node)
} }
......
import { default as category } from './categories' import category from './categories'
import { isDeleteOfDynamicArray, getCompilerVersion } from './staticAnalysisCommon' import { isDeleteOfDynamicArray, getCompilerVersion } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, UnaryOperationAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, UnaryOperationAstNode, SupportedVersion } from './../../types'
export default class deleteDynamicArrays implements AnalyzerModule { export default class deleteDynamicArrays implements AnalyzerModule {
rel: UnaryOperationAstNode[] = [] rel: UnaryOperationAstNode[] = []
name = `Delete dynamic array: ` name = 'Delete dynamic array: '
description = `Use require/assert to ensure complete deletion` description = 'Use require/assert to ensure complete deletion'
category: ModuleCategory = category.GAS category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -22,7 +22,7 @@ export default class deleteDynamicArrays implements AnalyzerModule { ...@@ -22,7 +22,7 @@ export default class deleteDynamicArrays implements AnalyzerModule {
const version = getCompilerVersion(compilationResults.contracts) const version = getCompilerVersion(compilationResults.contracts)
return this.rel.map((node) => { return this.rel.map((node) => {
return { 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.`, 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.',
location: node.src, location: node.src,
more: `https://solidity.readthedocs.io/en/${version}/types.html#delete` more: `https://solidity.readthedocs.io/en/${version}/types.html#delete`
} }
......
import { default as category } from './categories' import category from './categories'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon' import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, UnaryOperationAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, UnaryOperationAstNode, SupportedVersion } from './../../types'
export default class deleteFromDynamicArray implements AnalyzerModule { export default class deleteFromDynamicArray implements AnalyzerModule {
relevantNodes: UnaryOperationAstNode[] = [] relevantNodes: UnaryOperationAstNode[] = []
name = `Delete from dynamic array: ` name = 'Delete from dynamic array: '
description = `'delete' leaves a gap in array` description = '\'delete\' leaves a gap in array'
category: ModuleCategory = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -21,7 +21,7 @@ export default class deleteFromDynamicArray implements AnalyzerModule { ...@@ -21,7 +21,7 @@ export default class deleteFromDynamicArray implements AnalyzerModule {
report (compilationResults: CompilationResult): ReportObj[] { report (compilationResults: CompilationResult): ReportObj[] {
return this.relevantNodes.map((node) => { return this.relevantNodes.map((node) => {
return { 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.`, 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.',
location: node.src, location: node.src,
more: 'https://github.com/miguelmota/solidity-idiosyncrasies#examples' more: 'https://github.com/miguelmota/solidity-idiosyncrasies#examples'
} }
......
import { default as category } from './categories' import category from './categories'
import { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon' import { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, VisitFunction, ReportFunction, ContractHLAst, import {
FunctionHLAst, VariableDeclarationAstNode, SupportedVersion} from './../../types' AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, VisitFunction, ReportFunction, ContractHLAst,
FunctionHLAst, VariableDeclarationAstNode, SupportedVersion
} from './../../types'
export default class erc20Decimals implements AnalyzerModule { export default class erc20Decimals implements AnalyzerModule {
name = `ERC20: ` name = 'ERC20: '
description = `'decimals' should be 'uint8'` description = '\'decimals\' should be \'uint8\''
category: ModuleCategory = category.ERC category: ModuleCategory = category.ERC
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -38,7 +40,7 @@ export default class erc20Decimals implements AnalyzerModule { ...@@ -38,7 +40,7 @@ export default class erc20Decimals implements AnalyzerModule {
if (decimalsVar.length > 0) { if (decimalsVar.length > 0) {
for (const node of decimalsVar) { for (const node of decimalsVar) {
warnings.push({ warnings.push({
warning: `ERC20 contract's "decimals" variable should be "uint8" type`, warning: 'ERC20 contract\'s "decimals" variable should be "uint8" type',
location: node.src, location: node.src,
more: 'https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals' more: 'https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals'
}) })
...@@ -46,7 +48,7 @@ export default class erc20Decimals implements AnalyzerModule { ...@@ -46,7 +48,7 @@ export default class erc20Decimals implements AnalyzerModule {
} else if (decimalsFun.length > 0) { } else if (decimalsFun.length > 0) {
for (const fn of decimalsFun) { for (const fn of decimalsFun) {
warnings.push({ warnings.push({
warning: `ERC20 contract's "decimals" function should have "uint8" as return type`, warning: 'ERC20 contract\'s "decimals" function should have "uint8" as return type',
location: fn.node.src, location: fn.node.src,
more: 'https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals' more: 'https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals'
}) })
...@@ -66,4 +68,3 @@ export default class erc20Decimals implements AnalyzerModule { ...@@ -66,4 +68,3 @@ export default class erc20Decimals implements AnalyzerModule {
funSignatures.includes('allowance(address,address)') funSignatures.includes('allowance(address,address)')
} }
} }
import { default as category } from './categories' import category from './categories'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { isLoop, isTransfer, getCompilerVersion } from './staticAnalysisCommon' import { isLoop, isTransfer, getCompilerVersion } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode, import {
WhileStatementAstNode, ExpressionStatementAstNode, SupportedVersion} from './../../types' AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode,
WhileStatementAstNode, ExpressionStatementAstNode, SupportedVersion
} from './../../types'
export default class etherTransferInLoop implements AnalyzerModule { export default class etherTransferInLoop implements AnalyzerModule {
relevantNodes: ExpressionStatementAstNode[] = [] relevantNodes: ExpressionStatementAstNode[] = []
name = `Ether transfer in loop: ` name = 'Ether transfer in loop: '
description = `Transferring Ether in a for/while/do-while loop` description = 'Transferring Ether in a for/while/do-while loop'
category: ModuleCategory = category.GAS category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -16,13 +18,14 @@ export default class etherTransferInLoop implements AnalyzerModule { ...@@ -16,13 +18,14 @@ export default class etherTransferInLoop implements AnalyzerModule {
visit (node: ForStatementAstNode | WhileStatementAstNode): void { visit (node: ForStatementAstNode | WhileStatementAstNode): void {
let transferNodes: ExpressionStatementAstNode[] = [] let transferNodes: ExpressionStatementAstNode[] = []
if(isLoop(node)) { if (isLoop(node)) {
if(node.body && node.body.nodeType === 'Block') if (node.body && node.body.nodeType === 'Block') {
transferNodes = node.body.statements.filter(child => ( child.nodeType === 'ExpressionStatement' && transferNodes = node.body.statements.filter(child =>
child.expression.nodeType === 'FunctionCall' && isTransfer(child.expression.expression))) (child.nodeType === 'ExpressionStatement' &&
child.expression.nodeType === 'FunctionCall' &&
isTransfer(child.expression.expression)))
} else if (node.body && node.body.nodeType === 'ExpressionStatement' && node.body.expression.nodeType === 'FunctionCall' && isTransfer(node.body.expression.expression)) { transferNodes.push(node.body) }
// When loop body is described without braces // When loop body is described without braces
else if(node.body && node.body.nodeType === 'ExpressionStatement' && node.body.expression.nodeType === 'FunctionCall' && isTransfer(node.body.expression.expression))
transferNodes.push(node.body)
if (transferNodes.length > 0) { if (transferNodes.length > 0) {
this.relevantNodes.push(...transferNodes) this.relevantNodes.push(...transferNodes)
} }
...@@ -34,7 +37,7 @@ export default class etherTransferInLoop implements AnalyzerModule { ...@@ -34,7 +37,7 @@ export default class etherTransferInLoop implements AnalyzerModule {
const version = getCompilerVersion(compilationResults.contracts) const version = getCompilerVersion(compilationResults.contracts)
return this.relevantNodes.map((node) => { return this.relevantNodes.map((node) => {
return { 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.`, 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.',
location: node.src, location: node.src,
more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#gas-limit-and-loops` more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#gas-limit-and-loops`
} }
......
import { default as category } from './categories' import category from './categories'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { isDynamicArrayLengthAccess, getCompilerVersion } from './staticAnalysisCommon' import { isDynamicArrayLengthAccess, getCompilerVersion } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode, SupportedVersion } from './../../types'
export default class forLoopIteratesOverDynamicArray implements AnalyzerModule { export default class forLoopIteratesOverDynamicArray implements AnalyzerModule {
relevantNodes: ForStatementAstNode[] = [] relevantNodes: ForStatementAstNode[] = []
name = `For loop over dynamic array: ` name = 'For loop over dynamic array: '
description = `Iterations depend on dynamic array's size` description = 'Iterations depend on dynamic array\'s size'
category: ModuleCategory = category.GAS category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -16,9 +16,9 @@ export default class forLoopIteratesOverDynamicArray implements AnalyzerModule { ...@@ -16,9 +16,9 @@ export default class forLoopIteratesOverDynamicArray implements AnalyzerModule {
visit (node: ForStatementAstNode): void { visit (node: ForStatementAstNode): void {
const { condition } = node const { condition } = node
// Check if condition is `i < array.length - 1` // Check if condition is `i < array.length - 1`
if ((condition && condition.nodeType === "BinaryOperation" && condition.rightExpression.nodeType === "BinaryOperation" && isDynamicArrayLengthAccess(condition.rightExpression.leftExpression)) || if ((condition && condition.nodeType === 'BinaryOperation' && condition.rightExpression.nodeType === 'BinaryOperation' && isDynamicArrayLengthAccess(condition.rightExpression.leftExpression)) ||
// or condition is `i < array.length` // or condition is `i < array.length`
(condition && condition.nodeType === "BinaryOperation" && isDynamicArrayLengthAccess(condition.rightExpression))) { (condition && condition.nodeType === 'BinaryOperation' && isDynamicArrayLengthAccess(condition.rightExpression))) {
this.relevantNodes.push(node) this.relevantNodes.push(node)
} }
} }
...@@ -28,7 +28,7 @@ export default class forLoopIteratesOverDynamicArray implements AnalyzerModule { ...@@ -28,7 +28,7 @@ export default class forLoopIteratesOverDynamicArray implements AnalyzerModule {
const version = getCompilerVersion(compilationResults.contracts) const version = getCompilerVersion(compilationResults.contracts)
return this.relevantNodes.map((node) => { return this.relevantNodes.map((node) => {
return { 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. \n 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.`, 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. \n 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.',
location: node.src, location: node.src,
more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#gas-limit-and-loops` more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#gas-limit-and-loops`
} }
......
'use strict' 'use strict'
import { FunctionHLAst, ContractHLAst, FunctionCallGraph, ContractCallGraph, Context, FunctionCallAstNode } from "../../types" import { FunctionHLAst, ContractHLAst, FunctionCallGraph, ContractCallGraph, Context, FunctionCallAstNode } from '../../types'
import { isLocalCallGraphRelevantNode, isExternalDirectCall, getFullQualifiedFunctionCallIdent, import {
getFullQuallyfiedFuncDefinitionIdent, getContractName } from './staticAnalysisCommon' isLocalCallGraphRelevantNode, isExternalDirectCall, getFullQualifiedFunctionCallIdent,
getFullQuallyfiedFuncDefinitionIdent, getContractName
} from './staticAnalysisCommon'
type filterNodesFunction = (node: FunctionCallAstNode) => boolean type filterNodesFunction = (node: FunctionCallAstNode) => boolean
type NodeIdentFunction = (node: FunctionCallAstNode) => string type NodeIdentFunction = (node: FunctionCallAstNode) => string
type FunDefIdentFunction = (node: FunctionHLAst) => string type FunDefIdentFunction = (node: FunctionHLAst) => string
function buildLocalFuncCallGraphInternal (functions: FunctionHLAst[], nodeFilter: filterNodesFunction , extractNodeIdent: NodeIdentFunction, extractFuncDefIdent: FunDefIdentFunction): Record<string, FunctionCallGraph> { function buildLocalFuncCallGraphInternal (functions: FunctionHLAst[], nodeFilter: filterNodesFunction, extractNodeIdent: NodeIdentFunction, extractFuncDefIdent: FunDefIdentFunction): Record<string, FunctionCallGraph> {
const callGraph: Record<string, FunctionCallGraph> = {} const callGraph: Record<string, FunctionCallGraph> = {}
functions.forEach((func: FunctionHLAst) => { functions.forEach((func: FunctionHLAst) => {
const calls: string[] = func.relevantNodes const calls: string[] = func.relevantNodes
...@@ -108,6 +110,5 @@ function resolveCallGraphSymbolInternal (callGraph: Record<string, ContractCallG ...@@ -108,6 +110,5 @@ function resolveCallGraphSymbolInternal (callGraph: Record<string, ContractCallG
throw new Error('functionCallGraph.js: function does not have full qualified name.') throw new Error('functionCallGraph.js: function does not have full qualified name.')
} }
if (current === undefined && !silent) console.log(`static analysis functionCallGraph.js: ${funcName} not found in function call graph.`) if (current === undefined && !silent) console.log(`static analysis functionCallGraph.js: ${funcName} not found in function call graph.`)
if(current !== null) if (current !== null) { return current }
return current
} }
import { default as category } from './categories' import category from './categories'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { getFunctionDefinitionName, helpers, isVariableTurnedIntoGetter, getMethodParamsSplittedTypeDesc } from './staticAnalysisCommon' import { getFunctionDefinitionName, helpers, isVariableTurnedIntoGetter, getMethodParamsSplittedTypeDesc } from './staticAnalysisCommon'
import { ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, CompiledContract, AnalyzerModule, import {
FunctionDefinitionAstNode, VariableDeclarationAstNode, SupportedVersion } from './../../types' ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, CompiledContract, AnalyzerModule,
FunctionDefinitionAstNode, VariableDeclarationAstNode, SupportedVersion
} from './../../types'
export default class gasCosts implements AnalyzerModule { export default class gasCosts implements AnalyzerModule {
name = `Gas costs: ` name = 'Gas costs: '
description = `Too high gas requirement of functions` description = 'Too high gas requirement of functions'
category: ModuleCategory = category.GAS category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -16,20 +18,17 @@ export default class gasCosts implements AnalyzerModule { ...@@ -16,20 +18,17 @@ export default class gasCosts implements AnalyzerModule {
warningNodes: any[] = [] warningNodes: any[] = []
visit (node: FunctionDefinitionAstNode | VariableDeclarationAstNode): void { visit (node: FunctionDefinitionAstNode | VariableDeclarationAstNode): void {
if ((node.nodeType === 'FunctionDefinition' && node.kind !== 'constructor' && node.implemented) || if ((node.nodeType === 'FunctionDefinition' && node.kind !== 'constructor' && node.implemented) ||
(node.nodeType === 'VariableDeclaration' && isVariableTurnedIntoGetter(node))) (node.nodeType === 'VariableDeclaration' && isVariableTurnedIntoGetter(node))) { this.warningNodes.push(node) }
this.warningNodes.push(node)
} }
report (compilationResults: CompilationResult): ReportObj[] { report (compilationResults: CompilationResult): ReportObj[] {
const report: ReportObj[] = [] const report: ReportObj[] = []
const methodsWithSignature: Record<string, string>[] = this.warningNodes.map(node => { const methodsWithSignature: Record<string, string>[] = this.warningNodes.map(node => {
let signature: string; let signature: string
if(node.nodeType === 'FunctionDefinition'){ if (node.nodeType === 'FunctionDefinition') {
const functionName: string = getFunctionDefinitionName(node) const functionName: string = getFunctionDefinitionName(node)
signature = helpers.buildAbiSignature(functionName, getMethodParamsSplittedTypeDesc(node, compilationResults.contracts)) signature = helpers.buildAbiSignature(functionName, getMethodParamsSplittedTypeDesc(node, compilationResults.contracts))
} } else { signature = node.name + '()' }
else
signature = node.name + '()'
return { return {
name: node.name, name: node.name,
...@@ -42,8 +41,8 @@ export default class gasCosts implements AnalyzerModule { ...@@ -42,8 +41,8 @@ export default class gasCosts implements AnalyzerModule {
for (const contractName in compilationResults.contracts[filename]) { for (const contractName in compilationResults.contracts[filename]) {
const contract: CompiledContract = compilationResults.contracts[filename][contractName] const contract: CompiledContract = compilationResults.contracts[filename][contractName]
const methodGas: Record<string, any> | undefined = this.checkMethodGas(contract, method.signature) const methodGas: Record<string, any> | undefined = this.checkMethodGas(contract, method.signature)
if(methodGas && methodGas.isInfinite) { if (methodGas && methodGas.isInfinite) {
if(methodGas.isFallback) { if (methodGas.isFallback) {
report.push({ report.push({
warning: `Fallback function of contract ${contractName} requires too much gas (${methodGas.msg}). warning: `Fallback function of contract ${contractName} requires too much gas (${methodGas.msg}).
If the fallback function requires more than 2300 gas, the contract cannot receive Ether.`, If the fallback function requires more than 2300 gas, the contract cannot receive Ether.`,
...@@ -65,9 +64,9 @@ export default class gasCosts implements AnalyzerModule { ...@@ -65,9 +64,9 @@ export default class gasCosts implements AnalyzerModule {
return report return report
} }
private checkMethodGas(contract: CompiledContract, methodSignature: string): Record<string, any> | undefined { private checkMethodGas (contract: CompiledContract, methodSignature: string): Record<string, any> | undefined {
if(contract.evm && contract.evm.gasEstimates && contract.evm.gasEstimates.external) { if (contract.evm && contract.evm.gasEstimates && contract.evm.gasEstimates.external) {
if(methodSignature === '()') { if (methodSignature === '()') {
const fallback: string = contract.evm.gasEstimates.external[''] const fallback: string = contract.evm.gasEstimates.external['']
if (fallback !== undefined && (fallback === null || parseInt(fallback) >= 2100 || fallback === 'infinite')) { if (fallback !== undefined && (fallback === null || parseInt(fallback) >= 2100 || fallback === 'infinite')) {
return { return {
......
import { default as category } from './categories' import category from './categories'
import { isRequireCall, isAssertCall, getCompilerVersion } from './staticAnalysisCommon' import { isRequireCall, isAssertCall, getCompilerVersion } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, FunctionCallAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, FunctionCallAstNode, SupportedVersion } from './../../types'
export default class guardConditions implements AnalyzerModule { export default class guardConditions implements AnalyzerModule {
guards: FunctionCallAstNode[] = [] guards: FunctionCallAstNode[] = []
name = `Guard conditions: ` name = 'Guard conditions: '
description = `Ensure appropriate use of require/assert` description = 'Ensure appropriate use of require/assert'
category: ModuleCategory = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -22,7 +22,7 @@ export default class guardConditions implements AnalyzerModule { ...@@ -22,7 +22,7 @@ export default class guardConditions implements AnalyzerModule {
const version = getCompilerVersion(compilationResults.contracts) const version = getCompilerVersion(compilationResults.contracts)
return this.guards.map((node) => { return this.guards.map((node) => {
return { 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.`, 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.',
location: node.src, location: node.src,
more: `https://solidity.readthedocs.io/en/${version}/control-structures.html#error-handling-assert-require-revert-and-exceptions` more: `https://solidity.readthedocs.io/en/${version}/control-structures.html#error-handling-assert-require-revert-and-exceptions`
} }
......
import { default as category } from './categories' import category from './categories'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, InlineAssemblyAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, InlineAssemblyAstNode, SupportedVersion } from './../../types'
import { getCompilerVersion } from './staticAnalysisCommon' import { getCompilerVersion } from './staticAnalysisCommon'
export default class inlineAssembly implements AnalyzerModule { export default class inlineAssembly implements AnalyzerModule {
inlineAssNodes: InlineAssemblyAstNode[] = [] inlineAssNodes: InlineAssemblyAstNode[] = []
name = `Inline assembly: ` name = 'Inline assembly: '
description = `Inline assembly used` description = 'Inline assembly used'
category: ModuleCategory = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -14,7 +14,7 @@ export default class inlineAssembly implements AnalyzerModule { ...@@ -14,7 +14,7 @@ export default class inlineAssembly implements AnalyzerModule {
} }
visit (node: InlineAssemblyAstNode): void { visit (node: InlineAssemblyAstNode): void {
if(node.nodeType === 'InlineAssembly') this.inlineAssNodes.push(node) if (node.nodeType === 'InlineAssembly') this.inlineAssNodes.push(node)
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
......
import { default as category } from './categories' import category from './categories'
import { isIntDivision } from './staticAnalysisCommon' import { isIntDivision } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, BinaryOperationAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, BinaryOperationAstNode, SupportedVersion } from './../../types'
export default class intDivisionTruncate implements AnalyzerModule { export default class intDivisionTruncate implements AnalyzerModule {
warningNodes: BinaryOperationAstNode[] = [] warningNodes: BinaryOperationAstNode[] = []
name = `Data truncated: ` name = 'Data truncated: '
description = `Division on int/uint values truncates the result` description = 'Division on int/uint values truncates the result'
category: ModuleCategory = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
......
import { default as category } from './categories' import category from './categories'
import { isLLCall, isLLDelegatecall, isLLCallcode, isLLCall04, isLLDelegatecall04, isLLSend04, isLLSend, lowLevelCallTypes, getCompilerVersion } from './staticAnalysisCommon' import { isLLCall, isLLDelegatecall, isLLCallcode, isLLCall04, isLLDelegatecall04, isLLSend04, isLLSend, lowLevelCallTypes, getCompilerVersion } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion } from './../../types'
interface llcNode { interface llcNode {
node: MemberAccessAstNode node: MemberAccessAstNode
...@@ -10,8 +10,8 @@ interface llcNode { ...@@ -10,8 +10,8 @@ interface llcNode {
export default class lowLevelCalls implements AnalyzerModule { export default class lowLevelCalls implements AnalyzerModule {
llcNodes: llcNode[] = [] llcNodes: llcNode[] = []
name = `Low level calls: ` name = 'Low level calls: '
description = `Should only be used by experienced devs` description = 'Should only be used by experienced devs'
category: ModuleCategory = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -20,19 +20,19 @@ export default class lowLevelCalls implements AnalyzerModule { ...@@ -20,19 +20,19 @@ export default class lowLevelCalls implements AnalyzerModule {
visit (node : MemberAccessAstNode): void { visit (node : MemberAccessAstNode): void {
if (isLLCall(node)) { if (isLLCall(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.CALL}) this.llcNodes.push({ node: node, type: lowLevelCallTypes.CALL })
} else if (isLLDelegatecall(node)) { } else if (isLLDelegatecall(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.DELEGATECALL}) this.llcNodes.push({ node: node, type: lowLevelCallTypes.DELEGATECALL })
} else if (isLLSend(node)) { } else if (isLLSend(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.SEND}) this.llcNodes.push({ node: node, type: lowLevelCallTypes.SEND })
} else if (isLLDelegatecall04(node)) { } else if (isLLDelegatecall04(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.DELEGATECALL}) this.llcNodes.push({ node: node, type: lowLevelCallTypes.DELEGATECALL })
} else if (isLLSend04(node)) { } else if (isLLSend04(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.SEND}) this.llcNodes.push({ node: node, type: lowLevelCallTypes.SEND })
} else if (isLLCall04(node)) { } else if (isLLCall04(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.CALL}) this.llcNodes.push({ node: node, type: lowLevelCallTypes.CALL })
} else if (isLLCallcode(node)) { } else if (isLLCallcode(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.CALLCODE}) this.llcNodes.push({ node: node, type: lowLevelCallTypes.CALLCODE })
} }
} }
...@@ -73,4 +73,3 @@ export default class lowLevelCalls implements AnalyzerModule { ...@@ -73,4 +73,3 @@ export default class lowLevelCalls implements AnalyzerModule {
}) })
} }
} }
import { default as category } from './categories' import category from './categories'
import { hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon' import { hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst, import {
VisitFunction, ReportFunction, ReturnAstNode, AssignmentAstNode, SupportedVersion} from './../../types' AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst,
VisitFunction, ReportFunction, ReturnAstNode, AssignmentAstNode, SupportedVersion
} from './../../types'
export default class noReturn implements AnalyzerModule { export default class noReturn implements AnalyzerModule {
name = `No return: ` name = 'No return: '
description = `Function with 'returns' not returning` description = 'Function with \'returns\' not returning'
category: ModuleCategory = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -17,7 +19,7 @@ export default class noReturn implements AnalyzerModule { ...@@ -17,7 +19,7 @@ export default class noReturn implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst() abstractAst: AbstractAst = new AbstractAst()
visit: VisitFunction = this.abstractAst.build_visit( visit: VisitFunction = this.abstractAst.build_visit(
(node: ReturnAstNode | AssignmentAstNode) => node.nodeType === "Return" || node.nodeType === "Assignment" (node: ReturnAstNode | AssignmentAstNode) => node.nodeType === 'Return' || node.nodeType === 'Assignment'
) )
report: ReportFunction = this.abstractAst.build_report(this._report.bind(this)) report: ReportFunction = this.abstractAst.build_report(this._report.bind(this))
...@@ -30,12 +32,12 @@ export default class noReturn implements AnalyzerModule { ...@@ -30,12 +32,12 @@ export default class noReturn implements AnalyzerModule {
if (this.hasNamedAndUnnamedReturns(func)) { if (this.hasNamedAndUnnamedReturns(func)) {
warnings.push({ warnings.push({
warning: `${funcName}: Mixing of named and unnamed return parameters is not advised.`, warning: `${funcName}: Mixing of named and unnamed return parameters is not advised.`,
location: func.node['src'] location: func.node.src
}) })
} else if (this.shouldReturn(func) && !(this.hasReturnStatement(func) || (this.hasNamedReturns(func) && this.hasAssignToAllNamedReturns(func)))) { } else if (this.shouldReturn(func) && !(this.hasReturnStatement(func) || (this.hasNamedReturns(func) && this.hasAssignToAllNamedReturns(func)))) {
warnings.push({ warnings.push({
warning: `${funcName}: Defines a return type but never explicitly returns a value.`, warning: `${funcName}: Defines a return type but never explicitly returns a value.`,
location: func.node['src'] location: func.node.src
}) })
} }
}) })
...@@ -48,12 +50,12 @@ export default class noReturn implements AnalyzerModule { ...@@ -48,12 +50,12 @@ export default class noReturn implements AnalyzerModule {
} }
private hasReturnStatement (func: FunctionHLAst): boolean { private hasReturnStatement (func: FunctionHLAst): boolean {
return func.relevantNodes.filter(n => n.nodeType === "Return").length > 0 return func.relevantNodes.filter(n => n.nodeType === 'Return').length > 0
} }
private hasAssignToAllNamedReturns (func: FunctionHLAst): boolean { private hasAssignToAllNamedReturns (func: FunctionHLAst): boolean {
const namedReturns: string[] = func.returns.filter(n => n.name.length > 0).map((n) => n.name) const namedReturns: string[] = func.returns.filter(n => n.name.length > 0).map((n) => n.name)
const assignedVars: string[] = func.relevantNodes.filter(n => n.nodeType === "Assignment").map(getEffectedVariableName) const assignedVars: string[] = func.relevantNodes.filter(n => n.nodeType === 'Assignment').map(getEffectedVariableName)
const diff: string[] = namedReturns.filter(e => !assignedVars.includes(e)) const diff: string[] = namedReturns.filter(e => !assignedVars.includes(e))
return diff.length === 0 return diff.length === 0
} }
......
import { default as category } from './categories' import category from './categories'
import { isStatement, isSelfdestructCall } from './staticAnalysisCommon' import { isStatement, isSelfdestructCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VisitFunction, ReportFunction, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VisitFunction, ReportFunction, SupportedVersion } from './../../types'
export default class selfdestruct implements AnalyzerModule { export default class selfdestruct implements AnalyzerModule {
name = `Selfdestruct: ` name = 'Selfdestruct: '
description = `Contracts using destructed contract can be broken` description = 'Contracts using destructed contract can be broken'
category: ModuleCategory = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.HEURISTIC algorithm: ModuleAlgorithm = algorithm.HEURISTIC
version: SupportedVersion = { version: SupportedVersion = {
...@@ -16,7 +16,7 @@ export default class selfdestruct implements AnalyzerModule { ...@@ -16,7 +16,7 @@ export default class selfdestruct implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst() abstractAst: AbstractAst = new AbstractAst()
visit: VisitFunction = this.abstractAst.build_visit( visit: VisitFunction = this.abstractAst.build_visit(
(node: any) => isStatement(node) || (node.nodeType=== 'FunctionCall' && isSelfdestructCall(node)) (node: any) => isStatement(node) || (node.nodeType === 'FunctionCall' && isSelfdestructCall(node))
) )
report: ReportFunction = this.abstractAst.build_report(this._report.bind(this)) report: ReportFunction = this.abstractAst.build_report(this._report.bind(this))
...@@ -30,7 +30,7 @@ export default class selfdestruct implements AnalyzerModule { ...@@ -30,7 +30,7 @@ export default class selfdestruct implements AnalyzerModule {
func.relevantNodes.forEach((node) => { func.relevantNodes.forEach((node) => {
if (isSelfdestructCall(node)) { if (isSelfdestructCall(node)) {
warnings.push({ warnings.push({
warning: `Use of selfdestruct: Can block calling contracts unexpectedly. Be especially careful if this contract is planned to be used by other contracts (i.e. library contracts, interactions). Selfdestruction of the callee contract can leave callers in an inoperable state.`, warning: 'Use of selfdestruct: Can block calling contracts unexpectedly. Be especially careful if this contract is planned to be used by other contracts (i.e. library contracts, interactions). Selfdestruction of the callee contract can leave callers in an inoperable state.',
location: node.src, location: node.src,
more: 'https://paritytech.io/blog/security-alert.html' more: 'https://paritytech.io/blog/security-alert.html'
}) })
...@@ -38,7 +38,7 @@ export default class selfdestruct implements AnalyzerModule { ...@@ -38,7 +38,7 @@ export default class selfdestruct implements AnalyzerModule {
} }
if (isStatement(node) && hasSelf) { if (isStatement(node) && hasSelf) {
warnings.push({ warnings.push({
warning: `Use of selfdestruct: No code after selfdestruct is executed. Selfdestruct is a terminal.`, warning: 'Use of selfdestruct: No code after selfdestruct is executed. Selfdestruct is a terminal.',
location: node.src, location: node.src,
more: `https://solidity.readthedocs.io/en/${version}/introduction-to-smart-contracts.html#deactivate-and-self-destruct` more: `https://solidity.readthedocs.io/en/${version}/introduction-to-smart-contracts.html#deactivate-and-self-destruct`
}) })
......
import { default as category } from './categories' import category from './categories'
import { getDeclaredVariableName, getFullQuallyfiedFuncDefinitionIdent } from './staticAnalysisCommon' import { getDeclaredVariableName, getFullQuallyfiedFuncDefinitionIdent } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { get } from 'fast-levenshtein' import { get } from 'fast-levenshtein'
import { util } from '@remix-project/remix-lib' import { util } from '@remix-project/remix-lib'
import { AstWalker } from '@remix-project/remix-astwalker' import { AstWalker } from '@remix-project/remix-astwalker'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst, VariableDeclarationAstNode, VisitFunction, ReportFunction, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst, VariableDeclarationAstNode, VisitFunction, ReportFunction, SupportedVersion } from './../../types'
interface SimilarRecord { interface SimilarRecord {
var1: string var1: string
...@@ -14,8 +14,8 @@ interface SimilarRecord { ...@@ -14,8 +14,8 @@ interface SimilarRecord {
} }
export default class similarVariableNames implements AnalyzerModule { export default class similarVariableNames implements AnalyzerModule {
name = `Similar variable names: ` name = 'Similar variable names: '
description = `Variable names are too similar` description = 'Variable names are too similar'
category: ModuleCategory = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -47,17 +47,17 @@ export default class similarVariableNames implements AnalyzerModule { ...@@ -47,17 +47,17 @@ export default class similarVariableNames implements AnalyzerModule {
const vars: string[] = this.getFunctionVariables(contract, func).map(getDeclaredVariableName) const vars: string[] = this.getFunctionVariables(contract, func).map(getDeclaredVariableName)
this.findSimilarVarNames(vars).map((sim) => { this.findSimilarVarNames(vars).map((sim) => {
// check if function is implemented // check if function is implemented
if(func.node.implemented) { if (func.node.implemented) {
const astWalker = new AstWalker() const astWalker = new AstWalker()
const functionBody: any = func.node.body const functionBody: any = func.node.body
// Walk through all statements of function // Walk through all statements of function
astWalker.walk(functionBody, (node) => { astWalker.walk(functionBody, (node) => {
// check if these is an identifier node which is one of the tracked similar variables // check if these is an identifier node which is one of the tracked similar variables
if ((node.nodeType === 'Identifier' || node.nodeType === 'VariableDeclaration') if ((node.nodeType === 'Identifier' || node.nodeType === 'VariableDeclaration') &&
&& (node.name === sim.var1 || node.name === sim.var2)) { (node.name === sim.var1 || node.name === sim.var2)) {
warnings.push({ warnings.push({
warning: `${funcName} : Variables have very similar names "${sim.var1}" and "${sim.var2}". ${hasModifiersComments} ${multipleContractsWithSameNameComments}`, warning: `${funcName} : Variables have very similar names "${sim.var1}" and "${sim.var2}". ${hasModifiersComments} ${multipleContractsWithSameNameComments}`,
location: node['src'] location: node.src
}) })
} }
return true return true
......
'use strict' 'use strict'
import { FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode, import {
FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode,
WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode, WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode,
MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode, MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode,
IdentifierAstNode, IndexAccessAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode, CompiledContractObj, ABIParameter, CompiledContract } from "../../types" IdentifierAstNode, IndexAccessAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode, CompiledContractObj, ABIParameter, CompiledContract
} from '../../types'
import { util } from '@remix-project/remix-lib' import { util } from '@remix-project/remix-lib'
type SpecialObjDetail = { type SpecialObjDetail = {
...@@ -184,7 +186,7 @@ function getFunctionCallType (func: FunctionCallAstNode): string { ...@@ -184,7 +186,7 @@ function getFunctionCallType (func: FunctionCallAstNode): string {
*/ */
function getEffectedVariableName (effectNode: AssignmentAstNode | UnaryOperationAstNode): string { function getEffectedVariableName (effectNode: AssignmentAstNode | UnaryOperationAstNode): string {
if (!isEffect(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node') if (!isEffect(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node')
if(effectNode.nodeType === 'Assignment' || effectNode.nodeType === 'UnaryOperation') { if (effectNode.nodeType === 'Assignment' || effectNode.nodeType === 'UnaryOperation') {
const IdentNode: IdentifierAstNode = findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER)) const IdentNode: IdentifierAstNode = findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER))
return IdentNode.name return IdentNode.name
} else throw new Error('staticAnalysisCommon.js: wrong node type') } else throw new Error('staticAnalysisCommon.js: wrong node type')
...@@ -334,7 +336,7 @@ function getDeclaredVariableType (varDeclNode: VariableDeclarationAstNode): stri ...@@ -334,7 +336,7 @@ function getDeclaredVariableType (varDeclNode: VariableDeclarationAstNode): stri
* @return {list variable declaration} state variable node list * @return {list variable declaration} state variable node list
*/ */
function getStateVariableDeclarationsFromContractNode (contractNode: ContractDefinitionAstNode): VariableDeclarationAstNode[] { function getStateVariableDeclarationsFromContractNode (contractNode: ContractDefinitionAstNode): VariableDeclarationAstNode[] {
return contractNode.nodes.filter(el => el.nodeType === "VariableDeclaration") return contractNode.nodes.filter(el => el.nodeType === 'VariableDeclaration')
} }
/** /**
...@@ -398,8 +400,7 @@ function getFunctionCallTypeParameterType (func: FunctionCallAstNode): string | ...@@ -398,8 +400,7 @@ function getFunctionCallTypeParameterType (func: FunctionCallAstNode): string |
function getLibraryCallContractName (node: FunctionCallAstNode): string | undefined { function getLibraryCallContractName (node: FunctionCallAstNode): string | undefined {
if (!isLibraryCall(node.expression)) throw new Error('staticAnalysisCommon.js: not a library call Node') if (!isLibraryCall(node.expression)) throw new Error('staticAnalysisCommon.js: not a library call Node')
const types: RegExpExecArray | null = new RegExp(basicRegex.LIBRARYTYPE).exec(node.expression.expression.typeDescriptions.typeString) const types: RegExpExecArray | null = new RegExp(basicRegex.LIBRARYTYPE).exec(node.expression.expression.typeDescriptions.typeString)
if(types) if (types) { return types[1] }
return types[1]
} }
/** /**
...@@ -444,19 +445,14 @@ function getFullQuallyfiedFuncDefinitionIdent (contract: ContractDefinitionAstNo ...@@ -444,19 +445,14 @@ function getFullQuallyfiedFuncDefinitionIdent (contract: ContractDefinitionAstNo
function getUnAssignedTopLevelBinOps (subScope: BlockAstNode | IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): ExpressionStatementAstNode[] { function getUnAssignedTopLevelBinOps (subScope: BlockAstNode | IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): ExpressionStatementAstNode[] {
let result: ExpressionStatementAstNode[] = [] let result: ExpressionStatementAstNode[] = []
if(subScope && subScope.nodeType === 'Block') if (subScope && subScope.nodeType === 'Block') result = subScope.statements.filter(isBinaryOpInExpression)
result = subScope.statements.filter(isBinaryOpInExpression)
// for 'without braces' loops // for 'without braces' loops
else if (subScope && subScope.nodeType && subScope.nodeType !== 'Block' && isSubScopeStatement(subScope)) { else if (subScope && subScope.nodeType && subScope.nodeType !== 'Block' && isSubScopeStatement(subScope)) {
if (subScope.nodeType === 'IfStatement'){ if (subScope.nodeType === 'IfStatement') {
if((subScope.trueBody && subScope.trueBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.trueBody))) if ((subScope.trueBody && subScope.trueBody.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(subScope.trueBody))) { result.push(subScope.trueBody) }
result.push(subScope.trueBody) if (subScope.falseBody && subScope.falseBody.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(subScope.falseBody)) { result.push(subScope.falseBody) }
if (subScope.falseBody && subScope.falseBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.falseBody)) } else {
result.push(subScope.falseBody) if (subScope.body && subScope.body.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(subScope.body)) { result.push(subScope.body) }
}
else {
if(subScope.body && subScope.body.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.body))
result.push(subScope.body)
} }
} }
return result return result
...@@ -466,7 +462,7 @@ function getUnAssignedTopLevelBinOps (subScope: BlockAstNode | IfStatementAstNod ...@@ -466,7 +462,7 @@ function getUnAssignedTopLevelBinOps (subScope: BlockAstNode | IfStatementAstNod
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
function isStatement (node: any): boolean { function isStatement (node: any): boolean {
return nodeType(node, 'Statement$') || node.nodeType === "Block" || node.nodeType === "Return" return nodeType(node, 'Statement$') || node.nodeType === 'Block' || node.nodeType === 'Return'
} }
// #################### Complex Node Identification // #################### Complex Node Identification
...@@ -505,7 +501,7 @@ function isDynamicArrayAccess (node: IdentifierAstNode): boolean { ...@@ -505,7 +501,7 @@ function isDynamicArrayAccess (node: IdentifierAstNode): boolean {
*/ */
function isDynamicArrayLengthAccess (node: MemberAccessAstNode): boolean { function isDynamicArrayLengthAccess (node: MemberAccessAstNode): boolean {
return (node.memberName === 'length') && // accessing 'length' member return (node.memberName === 'length') && // accessing 'length' member
node.expression['typeDescriptions']['typeString'].indexOf('[]') !== -1 // member is accessed from dynamic array, notice [] without any number node.expression.typeDescriptions.typeString.indexOf('[]') !== -1 // member is accessed from dynamic array, notice [] without any number
} }
/** /**
...@@ -550,7 +546,7 @@ function isBuiltinFunctionCall (node: FunctionCallAstNode): boolean { ...@@ -550,7 +546,7 @@ function isBuiltinFunctionCall (node: FunctionCallAstNode): boolean {
* @return {bool} * @return {bool}
*/ */
function isAbiNamespaceCall (node: FunctionCallAstNode): boolean { function isAbiNamespaceCall (node: FunctionCallAstNode): boolean {
return Object.keys(abiNamespace).some((key) => Object.prototype.hasOwnProperty.call(abiNamespace,key) && node.expression && isSpecialVariableAccess(node.expression, abiNamespace[key])) return Object.keys(abiNamespace).some((key) => Object.prototype.hasOwnProperty.call(abiNamespace, key) && node.expression && isSpecialVariableAccess(node.expression, abiNamespace[key]))
} }
/** /**
...@@ -608,9 +604,9 @@ function isInteraction (node: FunctionCallAstNode): boolean { ...@@ -608,9 +604,9 @@ function isInteraction (node: FunctionCallAstNode): boolean {
* @return {bool} * @return {bool}
*/ */
function isEffect (node: AssignmentAstNode | UnaryOperationAstNode | InlineAssemblyAstNode): boolean { function isEffect (node: AssignmentAstNode | UnaryOperationAstNode | InlineAssemblyAstNode): boolean {
return node.nodeType === "Assignment" || return node.nodeType === 'Assignment' ||
(node.nodeType === "UnaryOperation" && (isPlusPlusUnaryOperation(node) || isMinusMinusUnaryOperation(node))) || (node.nodeType === 'UnaryOperation' && (isPlusPlusUnaryOperation(node) || isMinusMinusUnaryOperation(node))) ||
node.nodeType === "InlineAssembly" node.nodeType === 'InlineAssembly'
} }
/** /**
...@@ -620,7 +616,7 @@ function isEffect (node: AssignmentAstNode | UnaryOperationAstNode | InlineAssem ...@@ -620,7 +616,7 @@ function isEffect (node: AssignmentAstNode | UnaryOperationAstNode | InlineAssem
* @return {bool} * @return {bool}
*/ */
function isWriteOnStateVariable (effectNode: AssignmentAstNode | InlineAssemblyAstNode | UnaryOperationAstNode, stateVariables: VariableDeclarationAstNode[]): boolean { function isWriteOnStateVariable (effectNode: AssignmentAstNode | InlineAssemblyAstNode | UnaryOperationAstNode, stateVariables: VariableDeclarationAstNode[]): boolean {
return effectNode.nodeType === "InlineAssembly" || (isEffect(effectNode) && isStateVariable(getEffectedVariableName(effectNode), stateVariables)) return effectNode.nodeType === 'InlineAssembly' || (isEffect(effectNode) && isStateVariable(getEffectedVariableName(effectNode), stateVariables))
} }
/** /**
...@@ -648,7 +644,7 @@ function isConstantFunction (node: FunctionDefinitionAstNode): boolean { ...@@ -648,7 +644,7 @@ function isConstantFunction (node: FunctionDefinitionAstNode): boolean {
* @return {bool} * @return {bool}
*/ */
function isVariableTurnedIntoGetter (varDeclNode: VariableDeclarationAstNode): boolean { function isVariableTurnedIntoGetter (varDeclNode: VariableDeclarationAstNode): boolean {
return varDeclNode.stateVariable && varDeclNode.visibility === 'public'; return varDeclNode.stateVariable && varDeclNode.visibility === 'public'
} }
/** /**
...@@ -666,7 +662,7 @@ function isPayableFunction (node: FunctionDefinitionAstNode): boolean { ...@@ -666,7 +662,7 @@ function isPayableFunction (node: FunctionDefinitionAstNode): boolean {
* @return {bool} * @return {bool}
*/ */
function isConstructor (node: FunctionDefinitionAstNode): boolean { function isConstructor (node: FunctionDefinitionAstNode): boolean {
return node.kind === "constructor" return node.kind === 'constructor'
} }
/** /**
...@@ -684,24 +680,21 @@ function isIntDivision (node: BinaryOperationAstNode): boolean { ...@@ -684,24 +680,21 @@ function isIntDivision (node: BinaryOperationAstNode): boolean {
* @return {bool} * @return {bool}
*/ */
function isSubScopeWithTopLevelUnAssignedBinOp (node: BlockAstNode | IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): boolean | undefined { function isSubScopeWithTopLevelUnAssignedBinOp (node: BlockAstNode | IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): boolean | undefined {
if(node.nodeType === 'Block') if (node.nodeType === 'Block') return node.statements.some(isBinaryOpInExpression)
return node.statements.some(isBinaryOpInExpression)
// for 'without braces' loops // for 'without braces' loops
else if (node && node.nodeType && isSubScopeStatement(node)) { else if (node && node.nodeType && isSubScopeStatement(node)) {
if (node.nodeType === 'IfStatement') if (node.nodeType === 'IfStatement') {
return (node.trueBody && node.trueBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.trueBody)) || return (node.trueBody && node.trueBody.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(node.trueBody)) ||
(node.falseBody && node.falseBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.falseBody)) (node.falseBody && node.falseBody.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(node.falseBody))
else } else { return node.body && node.body.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(node.body) }
return node.body && node.body.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.body)
} }
} }
function isSubScopeStatement (node: IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): boolean { function isSubScopeStatement (node: IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): boolean {
if(node.nodeType === 'IfStatement') if (node.nodeType === 'IfStatement') {
return (node.trueBody && node.trueBody.nodeType && !nodeType(node.trueBody, exactMatch(nodeTypes.BLOCK))) || return (node.trueBody && node.trueBody.nodeType && !nodeType(node.trueBody, exactMatch(nodeTypes.BLOCK))) ||
(node.falseBody && node.falseBody.nodeType && !nodeType(node.falseBody, exactMatch(nodeTypes.BLOCK))) (node.falseBody && node.falseBody.nodeType && !nodeType(node.falseBody, exactMatch(nodeTypes.BLOCK)))
else } else { return node.body && node.body.nodeType && !nodeType(node.body, exactMatch(nodeTypes.BLOCK)) }
return node.body && node.body.nodeType && !nodeType(node.body, exactMatch(nodeTypes.BLOCK))
} }
/** /**
...@@ -710,7 +703,7 @@ function isSubScopeStatement (node: IfStatementAstNode | WhileStatementAstNode | ...@@ -710,7 +703,7 @@ function isSubScopeStatement (node: IfStatementAstNode | WhileStatementAstNode |
* @return {bool} * @return {bool}
*/ */
function isBinaryOpInExpression (node: ExpressionStatementAstNode): boolean { function isBinaryOpInExpression (node: ExpressionStatementAstNode): boolean {
return node.nodeType === "ExpressionStatement" && node.expression.nodeType === "BinaryOperation" return node.nodeType === 'ExpressionStatement' && node.expression.nodeType === 'BinaryOperation'
} }
/** /**
...@@ -791,7 +784,7 @@ function isExternalDirectCall (node: FunctionCallAstNode): boolean { ...@@ -791,7 +784,7 @@ function isExternalDirectCall (node: FunctionCallAstNode): boolean {
* @return {bool} * @return {bool}
*/ */
function isNowAccess (node: IdentifierAstNode): boolean { function isNowAccess (node: IdentifierAstNode): boolean {
return node.name === "now" && typeDescription(node, exactMatch(basicTypes.UINT)) return node.name === 'now' && typeDescription(node, exactMatch(basicTypes.UINT))
} }
/** /**
...@@ -818,7 +811,7 @@ function isBlockTimestampAccess (node: MemberAccessAstNode): boolean { ...@@ -818,7 +811,7 @@ function isBlockTimestampAccess (node: MemberAccessAstNode): boolean {
* @return {bool} * @return {bool}
*/ */
function isBlockBlockHashAccess (node: FunctionCallAstNode): boolean { function isBlockBlockHashAccess (node: FunctionCallAstNode): boolean {
return ( isBuiltinFunctionCall(node) && getLocalCallName(node) === 'blockhash' ) || return (isBuiltinFunctionCall(node) && getLocalCallName(node) === 'blockhash') ||
isSpecialVariableAccess(node.expression, specialVariables.BLOCKHASH) isSpecialVariableAccess(node.expression, specialVariables.BLOCKHASH)
} }
...@@ -962,7 +955,7 @@ function isStringToBytesConversion (node: FunctionCallAstNode): boolean { ...@@ -962,7 +955,7 @@ function isStringToBytesConversion (node: FunctionCallAstNode): boolean {
} }
function isExplicitCast (node: FunctionCallAstNode, castFromType: string, castToType: string): boolean { function isExplicitCast (node: FunctionCallAstNode, castFromType: string, castToType: string): boolean {
return node.kind === "typeConversion" && return node.kind === 'typeConversion' &&
nodeType(node.expression, exactMatch(nodeTypes.ELEMENTARYTYPENAMEEXPRESSION)) && node.expression.typeName === castToType && nodeType(node.expression, exactMatch(nodeTypes.ELEMENTARYTYPENAMEEXPRESSION)) && node.expression.typeName === castToType &&
nodeType(node.arguments[0], exactMatch(nodeTypes.IDENTIFIER)) && typeDescription(node.arguments[0], castFromType) nodeType(node.arguments[0], exactMatch(nodeTypes.IDENTIFIER)) && typeDescription(node.arguments[0], castFromType)
} }
...@@ -976,7 +969,7 @@ function isBytesLengthCheck (node: MemberAccessAstNode): boolean { ...@@ -976,7 +969,7 @@ function isBytesLengthCheck (node: MemberAccessAstNode): boolean {
* @node {ASTNode} some AstNode * @node {ASTNode} some AstNode
* @return {bool} * @return {bool}
*/ */
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
function isLoop (node: any): boolean { function isLoop (node: any): boolean {
return nodeType(node, exactMatch(nodeTypes.FORSTATEMENT)) || return nodeType(node, exactMatch(nodeTypes.FORSTATEMENT)) ||
nodeType(node, exactMatch(nodeTypes.WHILESTATEMENT)) || nodeType(node, exactMatch(nodeTypes.WHILESTATEMENT)) ||
...@@ -986,7 +979,7 @@ function isLoop (node: any): boolean { ...@@ -986,7 +979,7 @@ function isLoop (node: any): boolean {
// #################### Complex Node Identification - Private // #################### Complex Node Identification - Private
function isMemberAccess (node: MemberAccessAstNode, retType: string, accessor: string| undefined, accessorType: string, memberName: string | undefined): boolean { function isMemberAccess (node: MemberAccessAstNode, retType: string, accessor: string| undefined, accessorType: string, memberName: string | undefined): boolean {
if(node && nodeType(node, exactMatch('MemberAccess'))) { if (node && nodeType(node, exactMatch('MemberAccess'))) {
const nodeTypeDef: boolean = typeDescription(node, retType) const nodeTypeDef: boolean = typeDescription(node, retType)
const nodeMemName: boolean = memName(node, memberName) const nodeMemName: boolean = memName(node, memberName)
const nodeExpMemName: boolean = memName(node.expression, accessor) const nodeExpMemName: boolean = memName(node.expression, accessor)
...@@ -1018,7 +1011,7 @@ function nodeType (node: any, typeRegex: string): boolean { ...@@ -1018,7 +1011,7 @@ function nodeType (node: any, typeRegex: string): boolean {
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
function nodeTypeIn (node: any, typeRegex: string[]): boolean { function nodeTypeIn (node: any, typeRegex: string[]): boolean {
return typeRegex.some((typeRegex) => nodeType (node, typeRegex)) return typeRegex.some((typeRegex) => nodeType(node, typeRegex))
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
...@@ -1053,20 +1046,7 @@ function matches (...fnArgs: any[]): string { ...@@ -1053,20 +1046,7 @@ function matches (...fnArgs: any[]): string {
* Note: developed keeping identifier node search in mind to get first identifier node from left in subscope * Note: developed keeping identifier node search in mind to get first identifier node from left in subscope
*/ */
function findFirstSubNodeLTR (node: any, type: string): any { function findFirstSubNodeLTR (node: any, type: string): any {
if(node.nodeType && nodeType(node, type)) if (node.nodeType && nodeType(node, type)) { return node } else if (node.nodeType && nodeType(node, exactMatch('Assignment'))) { return findFirstSubNodeLTR(node.leftHandSide, type) } else if (node.nodeType && nodeType(node, exactMatch('MemberAccess'))) { return findFirstSubNodeLTR(node.expression, type) } else if (node.nodeType && nodeType(node, exactMatch('IndexAccess'))) { return findFirstSubNodeLTR(node.baseExpression, type) } else if (node.nodeType && nodeType(node, exactMatch('UnaryOperation'))) { return findFirstSubNodeLTR(node.subExpression, type) }
return node
else if(node.nodeType && nodeType(node, exactMatch('Assignment')))
return findFirstSubNodeLTR(node.leftHandSide, type)
else if(node.nodeType && nodeType(node, exactMatch('MemberAccess')))
return findFirstSubNodeLTR(node.expression, type)
else if(node.nodeType && nodeType(node, exactMatch('IndexAccess')))
return findFirstSubNodeLTR(node.baseExpression, type)
else if(node.nodeType && nodeType(node, exactMatch('UnaryOperation')))
return findFirstSubNodeLTR(node.subExpression, type)
} }
/** /**
...@@ -1088,11 +1068,11 @@ function buildAbiSignature (funName: string, paramTypes: any[]): string { ...@@ -1088,11 +1068,11 @@ function buildAbiSignature (funName: string, paramTypes: any[]): string {
// To create the method signature similar to contract.evm.gasEstimates.external object // To create the method signature similar to contract.evm.gasEstimates.external object
// For address payable, return address // For address payable, return address
function getMethodParamsSplittedTypeDesc(node: FunctionDefinitionAstNode, contracts: CompiledContractObj): string[] { function getMethodParamsSplittedTypeDesc (node: FunctionDefinitionAstNode, contracts: CompiledContractObj): string[] {
return node.parameters.parameters.map((varNode, varIndex) => { return node.parameters.parameters.map((varNode, varIndex) => {
let finalTypeString; let finalTypeString
const typeString = varNode.typeDescriptions.typeString const typeString = varNode.typeDescriptions.typeString
if(typeString.includes('struct')) { if (typeString.includes('struct')) {
const fnName = node.name const fnName = node.name
for (const filename in contracts) { for (const filename in contracts) {
for (const contractName in contracts[filename]) { for (const contractName in contracts[filename]) {
...@@ -1100,32 +1080,28 @@ function getMethodParamsSplittedTypeDesc(node: FunctionDefinitionAstNode, contra ...@@ -1100,32 +1080,28 @@ function getMethodParamsSplittedTypeDesc(node: FunctionDefinitionAstNode, contra
.find(e => e.name === fnName && e.inputs?.length && .find(e => e.name === fnName && e.inputs?.length &&
e.inputs[varIndex]['type'].includes('tuple') && e.inputs[varIndex]['type'].includes('tuple') &&
e.inputs[varIndex]['internalType'] === typeString) e.inputs[varIndex]['internalType'] === typeString)
if(methodABI && methodABI.inputs) { if (methodABI && methodABI.inputs) {
const inputs = methodABI.inputs[varIndex] const inputs = methodABI.inputs[varIndex]
const typeStr = getTypeStringFromComponents(inputs['components']) const typeStr = getTypeStringFromComponents(inputs['components'])
finalTypeString = typeStr + inputs['type'].replace('tuple', '') finalTypeString = typeStr + inputs['type'].replace('tuple', '')
} }
} }
} }
} else } else { finalTypeString = typeString.split(' ')[0] }
finalTypeString = typeString.split(' ')[0]
return finalTypeString return finalTypeString
}) })
} }
function getTypeStringFromComponents(components: ABIParameter[]) { function getTypeStringFromComponents (components: ABIParameter[]) {
let typeString = '(' let typeString = '('
for(let i=0; i < components.length; i++) { for (let i = 0; i < components.length; i++) {
const param = components[i] const param = components[i]
if(param.type.includes('tuple') && param.components && param.components.length > 0){ if (param.type.includes('tuple') && param.components && param.components.length > 0) {
typeString = typeString + getTypeStringFromComponents(param.components) typeString = typeString + getTypeStringFromComponents(param.components)
typeString = typeString + param.type.replace('tuple', '') typeString = typeString + param.type.replace('tuple', '')
} } else { typeString = typeString + param.type }
else
typeString = typeString + param.type
if(i !== components.length - 1) if (i !== components.length - 1) { typeString = typeString + ',' }
typeString = typeString + ','
} }
typeString = typeString + ')' typeString = typeString + ')'
return typeString return typeString
...@@ -1136,18 +1112,17 @@ function getTypeStringFromComponents(components: ABIParameter[]) { ...@@ -1136,18 +1112,17 @@ function getTypeStringFromComponents(components: ABIParameter[]) {
* This is used to redirect the user to specific version of Solidity documentation * This is used to redirect the user to specific version of Solidity documentation
* @param contractFiles compiled contract object * @param contractFiles compiled contract object
*/ */
function getCompilerVersion(contractFiles: CompiledContractObj): string { function getCompilerVersion (contractFiles: CompiledContractObj): string {
let version = 'latest' let version = 'latest'
const fileNames: string[] = Object.keys(contractFiles) const fileNames: string[] = Object.keys(contractFiles)
const contracts = contractFiles[fileNames[0]] const contracts = contractFiles[fileNames[0]]
const contractNames: string[] = Object.keys(contracts) const contractNames: string[] = Object.keys(contracts)
const contract: CompiledContract = contracts[contractNames[0]] const contract: CompiledContract = contracts[contractNames[0]]
// For some compiler/contract, metadata is "" // For some compiler/contract, metadata is ""
if(contract && contract.metadata) { if (contract && contract.metadata) {
const metadata = JSON.parse(contract.metadata) const metadata = JSON.parse(contract.metadata)
const compilerVersion: string = metadata.compiler.version const compilerVersion: string = metadata.compiler.version
if(!compilerVersion.includes('nightly')) if (!compilerVersion.includes('nightly')) { version = 'v' + compilerVersion.split('+commit')[0] }
version = 'v' + compilerVersion.split('+commit')[0]
} }
return version return version
} }
......
import { default as category } from './categories' import category from './categories'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { isStringToBytesConversion, isBytesLengthCheck, getCompilerVersion } from './staticAnalysisCommon' import { isStringToBytesConversion, isBytesLengthCheck, getCompilerVersion } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, FunctionCallAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, FunctionCallAstNode, SupportedVersion } from './../../types'
export default class stringBytesLength implements AnalyzerModule { export default class stringBytesLength implements AnalyzerModule {
name = `String length: ` name = 'String length: '
description = `Bytes length != String length` description = 'Bytes length != String length'
category: ModuleCategory = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -16,8 +16,8 @@ export default class stringBytesLength implements AnalyzerModule { ...@@ -16,8 +16,8 @@ export default class stringBytesLength implements AnalyzerModule {
bytesLengthChecks: MemberAccessAstNode[] = [] bytesLengthChecks: MemberAccessAstNode[] = []
visit (node: FunctionCallAstNode | MemberAccessAstNode): void { visit (node: FunctionCallAstNode | MemberAccessAstNode): void {
if (node.nodeType === "FunctionCall" && isStringToBytesConversion(node)) this.stringToBytesConversions.push(node) if (node.nodeType === 'FunctionCall' && isStringToBytesConversion(node)) this.stringToBytesConversions.push(node)
else if (node.nodeType === "MemberAccess" && isBytesLengthCheck(node)) this.bytesLengthChecks.push(node) else if (node.nodeType === 'MemberAccess' && isBytesLengthCheck(node)) this.bytesLengthChecks.push(node)
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
...@@ -25,7 +25,7 @@ export default class stringBytesLength implements AnalyzerModule { ...@@ -25,7 +25,7 @@ export default class stringBytesLength implements AnalyzerModule {
const version = getCompilerVersion(compilationResults.contracts) const version = getCompilerVersion(compilationResults.contracts)
if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) { if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) {
return [{ return [{
warning: `"bytes" and "string" lengths 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.`, warning: '"bytes" and "string" lengths 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.',
location: this.bytesLengthChecks[0].src, location: this.bytesLengthChecks[0].src,
more: `https://solidity.readthedocs.io/en/${version}/abi-spec.html#argument-encoding` more: `https://solidity.readthedocs.io/en/${version}/abi-spec.html#argument-encoding`
}] }]
......
import { default as category } from './categories' import category from './categories'
import { isThisLocalCall, getCompilerVersion } from './staticAnalysisCommon' import { isThisLocalCall, getCompilerVersion } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion } from './../../types'
export default class thisLocal implements AnalyzerModule { export default class thisLocal implements AnalyzerModule {
warningNodes: MemberAccessAstNode[] = [] warningNodes: MemberAccessAstNode[] = []
name = `This on local calls: ` name = 'This on local calls: '
description = `Invocation of local functions via 'this'` description = 'Invocation of local functions via \'this\''
category: ModuleCategory = category.GAS category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -22,7 +22,7 @@ export default class thisLocal implements AnalyzerModule { ...@@ -22,7 +22,7 @@ export default class thisLocal implements AnalyzerModule {
const version = getCompilerVersion(compilationResults.contracts) const version = getCompilerVersion(compilationResults.contracts)
return this.warningNodes.map(function (item, i) { return this.warningNodes.map(function (item, i) {
return { 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.`, 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.',
location: item.src, location: item.src,
more: `https://solidity.readthedocs.io/en/${version}/control-structures.html#external-function-calls` more: `https://solidity.readthedocs.io/en/${version}/control-structures.html#external-function-calls`
} }
......
import { default as category } from './categories' import category from './categories'
import { default as algorithm } from './algorithmCategories' import algorithm from './algorithmCategories'
import { isTxOriginAccess, getCompilerVersion } from './staticAnalysisCommon' import { isTxOriginAccess, getCompilerVersion } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion } from './../../types'
export default class txOrigin implements AnalyzerModule { export default class txOrigin implements AnalyzerModule {
txOriginNodes: MemberAccessAstNode[] = [] txOriginNodes: MemberAccessAstNode[] = []
name = `Transaction origin: ` name = 'Transaction origin: '
description = `'tx.origin' used` description = '\'tx.origin\' used'
category: ModuleCategory = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
version: SupportedVersion = { version: SupportedVersion = {
...@@ -15,7 +15,6 @@ export default class txOrigin implements AnalyzerModule { ...@@ -15,7 +15,6 @@ export default class txOrigin implements AnalyzerModule {
visit (node: MemberAccessAstNode): void { visit (node: MemberAccessAstNode): void {
if (isTxOriginAccess(node)) this.txOriginNodes.push(node) if (isTxOriginAccess(node)) this.txOriginNodes.push(node)
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
......
...@@ -121,9 +121,9 @@ export interface ContractCallGraph { ...@@ -121,9 +121,9 @@ export interface ContractCallGraph {
functions: Record<string, FunctionCallGraph> functions: Record<string, FunctionCallGraph>
} }
///////////////////////////////////////////////////////////// /// //////////////////////////////////////////////////////////
///////////// Specfic AST Nodes ///////////////////////////// /// ////////// Specfic AST Nodes /////////////////////////////
///////////////////////////////////////////////////////////// /// //////////////////////////////////////////////////////////
interface TypeDescription { interface TypeDescription {
typeIdentifier: string typeIdentifier: string
...@@ -629,10 +629,9 @@ export interface CommonAstNode { ...@@ -629,10 +629,9 @@ export interface CommonAstNode {
[x: string]: any [x: string]: any
} }
/// //////////////////////////////////////////////////////
///////////////////////////////////////////////////////// /// ////////// YUL AST Nodes /////////////////////////////
///////////// YUL AST Nodes ///////////////////////////// /// //////////////////////////////////////////////////////
/////////////////////////////////////////////////////////
export interface YulTypedNameAstNode { export interface YulTypedNameAstNode {
name: string name: string
...@@ -674,12 +673,11 @@ export interface CommonYulAstNode { ...@@ -674,12 +673,11 @@ export interface CommonYulAstNode {
[x: string]: any [x: string]: any
} }
/// ////////
// ERROR //
/// ////////
/////////// export interface CompilationError {
// ERROR //
///////////
export interface CompilationError {
/** Location within the source file */ /** Location within the source file */
sourceLocation?: { sourceLocation?: {
file: string file: string
...@@ -712,20 +710,20 @@ export interface CommonYulAstNode { ...@@ -712,20 +710,20 @@ export interface CommonYulAstNode {
| 'FatalError' | 'FatalError'
| 'Warning' | 'Warning'
//////////// /// /////////
// SOURCE // // SOURCE //
//////////// /// /////////
export interface CompilationSource { export interface CompilationSource {
/** Identifier of the source (used in source maps) */ /** Identifier of the source (used in source maps) */
id: number id: number
/** The AST object */ /** The AST object */
ast: AstNode ast: AstNode
} }
///////// /// //////
// AST // // AST //
///////// /// //////
export interface AstNode { export interface AstNode {
absolutePath?: string absolutePath?: string
exportedSymbols?: Record<string, unknown> exportedSymbols?: Record<string, unknown>
id: number id: number
...@@ -740,7 +738,7 @@ export interface CommonYulAstNode { ...@@ -740,7 +738,7 @@ export interface CommonYulAstNode {
[x: string]: any [x: string]: any
} }
export interface AstNodeAtt { export interface AstNodeAtt {
operator?: string operator?: string
string?: null string?: null
type?: string type?: string
...@@ -754,10 +752,10 @@ export interface CommonYulAstNode { ...@@ -754,10 +752,10 @@ export interface CommonYulAstNode {
[x: string]: any [x: string]: any
} }
////////////// /// ///////////
// CONTRACT // // CONTRACT //
////////////// /// ///////////
export interface CompiledContract { export interface CompiledContract {
/** The Ethereum Contract ABI. If empty, it is represented as an empty array. */ /** The Ethereum Contract ABI. If empty, it is represented as an empty array. */
abi: ABIDescription[] abi: ABIDescription[]
// See the Metadata Output documentation (serialised JSON string) // See the Metadata Output documentation (serialised JSON string)
...@@ -803,12 +801,12 @@ export interface CommonYulAstNode { ...@@ -803,12 +801,12 @@ export interface CommonYulAstNode {
} }
} }
///////// /// //////
// ABI // // ABI //
///////// /// //////
export type ABIDescription = FunctionDescription | EventDescription export type ABIDescription = FunctionDescription | EventDescription
export interface FunctionDescription { export interface FunctionDescription {
/** Type of the method. default is 'function' */ /** Type of the method. default is 'function' */
type?: 'function' | 'constructor' | 'fallback' | 'receive' type?: 'function' | 'constructor' | 'fallback' | 'receive'
/** The name of the function. Constructor and fallback function never have name */ /** The name of the function. Constructor and fallback function never have name */
...@@ -825,7 +823,7 @@ export interface CommonYulAstNode { ...@@ -825,7 +823,7 @@ export interface CommonYulAstNode {
constant?: boolean constant?: boolean
} }
export interface EventDescription { export interface EventDescription {
type: 'event' type: 'event'
name: string name: string
inputs: ABIParameter & inputs: ABIParameter &
...@@ -837,7 +835,7 @@ export interface CommonYulAstNode { ...@@ -837,7 +835,7 @@ export interface CommonYulAstNode {
anonymous: boolean anonymous: boolean
} }
export interface ABIParameter { export interface ABIParameter {
internalType: string internalType: string
/** The name of the parameter */ /** The name of the parameter */
name: string name: string
...@@ -847,7 +845,7 @@ export interface CommonYulAstNode { ...@@ -847,7 +845,7 @@ export interface CommonYulAstNode {
components?: ABIParameter[] components?: ABIParameter[]
} }
export type ABITypeParameter = export type ABITypeParameter =
| 'uint' | 'uint'
| 'uint[]' // TODO : add <M> | 'uint[]' // TODO : add <M>
| 'int' | 'int'
...@@ -868,38 +866,38 @@ export interface CommonYulAstNode { ...@@ -868,38 +866,38 @@ export interface CommonYulAstNode {
| 'tuple[]' | 'tuple[]'
| string // Fallback | string // Fallback
/////////////////////////// /// ////////////////////////
// NATURAL SPECIFICATION // // NATURAL SPECIFICATION //
/////////////////////////// /// ////////////////////////
// Userdoc // Userdoc
export interface UserDocumentation { export interface UserDocumentation {
methods: UserMethodList methods: UserMethodList
notice: string notice: string
} }
export type UserMethodList = { export type UserMethodList = {
[functionIdentifier: string]: UserMethodDoc [functionIdentifier: string]: UserMethodDoc
} & { } & {
'constructor'?: string 'constructor'?: string
} }
export interface UserMethodDoc { export interface UserMethodDoc {
notice: string notice: string
} }
// Devdoc // Devdoc
export interface DeveloperDocumentation { export interface DeveloperDocumentation {
author: string author: string
title: string title: string
details: string details: string
methods: DevMethodList methods: DevMethodList
} }
export interface DevMethodList { export interface DevMethodList {
[functionIdentifier: string]: DevMethodDoc [functionIdentifier: string]: DevMethodDoc
} }
export interface DevMethodDoc { export interface DevMethodDoc {
author: string author: string
details: string details: string
return: string return: string
...@@ -908,10 +906,10 @@ export interface CommonYulAstNode { ...@@ -908,10 +906,10 @@ export interface CommonYulAstNode {
} }
} }
////////////// /// ///////////
// BYTECODE // // BYTECODE //
////////////// /// ///////////
export interface BytecodeObject { export interface BytecodeObject {
/** The bytecode as a hex string. */ /** The bytecode as a hex string. */
object: string object: string
/** Opcodes list */ /** Opcodes list */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment