all modules in ts, tests running partially

pull/7/head
aniket-engg 5 years ago committed by Aniket
parent 5029935a0b
commit a91d82b21a
  1. 5
      remix-analyzer/package.json
  2. 2
      remix-analyzer/src/solidity-analyzer/index.ts
  3. 37
      remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts
  4. 2
      remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts
  5. 2
      remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts
  6. 2
      remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts
  7. 90
      remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.js
  8. 89
      remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts
  9. 110
      remix-analyzer/src/solidity-analyzer/modules/constantFunctions.js
  10. 114
      remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts
  11. 2
      remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts
  12. 2
      remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts
  13. 63
      remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.js
  14. 54
      remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts
  15. 4
      remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts
  16. 2
      remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts
  17. 13
      remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts
  18. 67
      remix-analyzer/src/solidity-analyzer/modules/gasCosts.js
  19. 63
      remix-analyzer/src/solidity-analyzer/modules/gasCosts.ts
  20. 2
      remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts
  21. 2
      remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts
  22. 2
      remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts
  23. 21
      remix-analyzer/src/solidity-analyzer/modules/list.js
  24. 41
      remix-analyzer/src/solidity-analyzer/modules/list.ts
  25. 2
      remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts
  26. 75
      remix-analyzer/src/solidity-analyzer/modules/noReturn.js
  27. 66
      remix-analyzer/src/solidity-analyzer/modules/noReturn.ts
  28. 59
      remix-analyzer/src/solidity-analyzer/modules/selfdestruct.js
  29. 50
      remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts
  30. 87
      remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.js
  31. 77
      remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts
  32. 240
      remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts
  33. 33
      remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.js
  34. 30
      remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts
  35. 31
      remix-analyzer/src/solidity-analyzer/modules/thisLocal.js
  36. 26
      remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts
  37. 37
      remix-analyzer/src/solidity-analyzer/modules/txOrigin.js
  38. 32
      remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts
  39. 10
      remix-analyzer/test/analysis/staticAnalysisCommon-test.js
  40. 46
      remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.js
  41. 46
      remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.js
  42. 4
      remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.js
  43. 4
      remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.js
  44. 2
      remix-analyzer/tsconfig.json

@ -25,11 +25,12 @@
"scripts": {
"build": "tsc",
"lint": "standard",
"test": "npm run lint && tape ./test/tests.js"
"test": "tsc && npm run lint && tape ./test/tests.js"
},
"standard": {
"ignore": [
"node_modules/*"
"node_modules/*",
"dist/"
],
"parser": "babel-eslint"
},

@ -7,7 +7,7 @@ export class staticAnalysisRunner {
run (compilationResult, toRun, callback) {
const modules = toRun.map((i) => {
const m = this.modules()[i]
return { 'name': m.name, 'mod': new m.Module() }
return { 'name': m.name, 'mod': m }
})
this.runWithModuleList(compilationResult, modules, callback)

@ -1,7 +1,10 @@
import common from './staticAnalysisCommon'
import { isContractDefinition, getStateVariableDeclarationsFormContractNode, isInheritanceSpecifier,
getInheritsFromName, isFunctionDefinition, isModifierDefinition, isModifierInvocation, getContractName,
getFunctionOrModifierDefinitionParameterPart, getType, getDeclaredVariableName, isVariableDeclaration,
getFunctionOrModifierDefinitionReturnParameterPart } from './staticAnalysisCommon'
import { AstWalker } from 'remix-astwalker'
export class abstractAstView {
export default class abstractAstView {
contracts = []
currentContractIndex = null
currentFunctionIndex = null
@ -46,20 +49,20 @@ export class abstractAstView {
build_visit (relevantNodeFilter) {
var that = this
return function (node) {
if (common.isContractDefinition(node)) {
if (isContractDefinition(node)) {
that.setCurrentContract(that, {
node: node,
functions: [],
relevantNodes: [],
modifiers: [],
inheritsFrom: [],
stateVariables: common.getStateVariableDeclarationsFormContractNode(node)
stateVariables: getStateVariableDeclarationsFormContractNode(node)
})
} else if (common.isInheritanceSpecifier(node)) {
} else if (isInheritanceSpecifier(node)) {
const currentContract = that.getCurrentContract(that)
const inheritsFromName = common.getInheritsFromName(node)
const inheritsFromName = getInheritsFromName(node)
currentContract.inheritsFrom.push(inheritsFromName)
} else if (common.isFunctionDefinition(node)) {
} else if (isFunctionDefinition(node)) {
that.setCurrentFunction(that, {
node: node,
relevantNodes: [],
@ -74,14 +77,14 @@ export class abstractAstView {
that.getCurrentFunction(that).relevantNodes.push(item.node)
}
})
} else if (common.isModifierDefinition(node)) {
} else if (isModifierDefinition(node)) {
that.setCurrentModifier(that, {
node: node,
relevantNodes: [],
localVariables: that.getLocalVariables(node),
parameters: that.getLocalParameters(node)
})
} else if (common.isModifierInvocation(node)) {
} else if (isModifierInvocation(node)) {
if (!that.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.')
that.getCurrentFunction(that).modifierInvocations.push(node)
} else if (relevantNodeFilter(node)) {
@ -115,7 +118,7 @@ export class abstractAstView {
private resolveStateVariablesInHierarchyForContract (currentContract, contracts) {
currentContract.inheritsFrom.map((inheritsFromName) => {
// add variables from inherited contracts
const inheritsFrom = contracts.find((contract) => common.getContractName(contract.node) === inheritsFromName)
const inheritsFrom = contracts.find((contract) => getContractName(contract.node) === inheritsFromName)
if (inheritsFrom) {
currentContract.stateVariables = currentContract.stateVariables.concat(inheritsFrom.stateVariables)
} else {
@ -125,8 +128,8 @@ export class abstractAstView {
}
private setCurrentContract (that, contract) {
const name = common.getContractName(contract.node)
if (that.contracts.map((c) => common.getContractName(c.node)).filter((n) => n === name).length > 0) {
const name = getContractName(contract.node)
if (that.contracts.map((c) => getContractName(c.node)).filter((n) => n === name).length > 0) {
console.log('abstractAstView.js: two or more contracts with the same name dectected, import aliases not supported at the moment')
that.multipleContractsWithSameName = true
}
@ -156,14 +159,14 @@ export class abstractAstView {
}
private getLocalParameters (funcNode) {
return this.getLocalVariables(common.getFunctionOrModifierDefinitionParameterPart(funcNode)).map(common.getType)
return this.getLocalVariables(getFunctionOrModifierDefinitionParameterPart(funcNode)).map(getType)
}
private getReturnParameters (funcNode) {
return this.getLocalVariables(common.getFunctionOrModifierDefinitionReturnParameterPart(funcNode)).map((n) => {
return this.getLocalVariables(getFunctionOrModifierDefinitionReturnParameterPart(funcNode)).map((n) => {
return {
type: common.getType(n),
name: common.getDeclaredVariableName(n)
type: getType(n),
name: getDeclaredVariableName(n)
}
})
}
@ -171,7 +174,7 @@ export class abstractAstView {
private getLocalVariables (funcNode) {
const locals: any[] = []
new AstWalker().walk(funcNode, {'*': function (node) {
if (common.isVariableDeclaration(node)) locals.push(node)
if (isVariableDeclaration(node)) locals.push(node)
return true
}})
return locals

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export class assignAndCompare {
export default class assignAndCompare {
warningNodes: any[] = []
name = 'Result not used: '
description = 'The result of an operation was not used.'

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isBlockBlockHashAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export class blockBlockhash {
export default class blockBlockhash {
warningNodes: any[] = []
name = 'Block.blockhash usage: '
desc = 'Semantics maybe unclear'

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export class blockTimestamp {
export default class blockTimestamp {
warningNowNodes: any[] = []
warningblockTimestampNodes: any[] = []
name = 'Block timestamp: '

@ -1,90 +0,0 @@
const name = 'Check effects: '
const desc = 'Avoid potential reentrancy bugs'
const categories = require('./categories')
const common = require('./staticAnalysisCommon')
const fcallGraph = require('./functionCallGraph')
const AbstractAst = require('./abstractAstView')
const algo = require('./algorithmCategories')
function checksEffectsInteraction () {
this.abstractAst = new AbstractAst()
this.visit = this.abstractAst.build_visit(
(node) => common.isInteraction(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node)
)
this.report = this.abstractAst.build_report(report)
}
checksEffectsInteraction.prototype.visit = function () { throw new Error('checksEffectsInteraction.js no visit function set upon construction') }
checksEffectsInteraction.prototype.report = function () { throw new Error('checksEffectsInteraction.js no report function set upon construction') }
function report (contracts, multipleContractsWithSameName) {
const warnings = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
const callGraph = fcallGraph.buildGlobalFuncCallGraph(contracts)
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
func.changesState = checkIfChangesState(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters),
getContext(callGraph, contract, func))
})
contract.functions.forEach((func) => {
if (isPotentialVulnerableFunction(func, getContext(callGraph, contract, func))) {
const funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : ''
comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : ''
warnings.push({
warning: `Potential Violation of Checks-Effects-Interaction pattern in ${funcName}: Could potentially lead to re-entrancy vulnerability. ${comments}`,
location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/security-considerations.html#re-entrancy'
})
}
})
})
return warnings
}
function getContext (callGraph, currentContract, func) {
return { callGraph: callGraph, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) }
}
function getStateVariables (contract, func) {
return contract.stateVariables.concat(func.localVariables.filter(common.isStorageVariableDeclaration))
}
function isPotentialVulnerableFunction (func, context) {
let isPotentialVulnerable = false
let interaction = false
func.relevantNodes.forEach((node) => {
if (common.isInteraction(node)) {
interaction = true
} else if (interaction && (common.isWriteOnStateVariable(node, context.stateVariables) || isLocalCallWithStateChange(node, context))) {
isPotentialVulnerable = true
}
})
return isPotentialVulnerable
}
function isLocalCallWithStateChange (node, context) {
if (common.isLocalCallGraphRelevantNode(node)) {
const func = fcallGraph.resolveCallGraphSymbol(context.callGraph, common.getFullQualifiedFunctionCallIdent(context.currentContract.node, node))
return !func || (func && func.node.changesState)
}
return false
}
function checkIfChangesState (startFuncName, context) {
return fcallGraph.analyseCallGraph(context.callGraph, startFuncName, context, (node, context) => common.isWriteOnStateVariable(node, context.stateVariables))
}
module.exports = {
name: name,
description: desc,
category: categories.SECURITY,
algorithm: algo.HEURISTIC,
Module: checksEffectsInteraction
}

@ -0,0 +1,89 @@
import { default as category } from './categories'
import { isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfiedFuncDefinitionIdent,
isWriteOnStateVariable, isStorageVariableDeclaration, getFullQualifiedFunctionCallIdent } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView'
export default class checksEffectsInteraction {
name = 'Check effects: '
desc = 'Avoid potential reentrancy bugs'
categories = category.SECURITY
algorithm = algorithm.HEURISTIC
Module = this
abstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node) => isInteraction(node) || isEffect(node) || isLocalCallGraphRelevantNode(node))
report = this.abstractAst.build_report(this._report)
private _report (contracts, multipleContractsWithSameName) {
const warnings: any = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
const callGraph = buildGlobalFuncCallGraph(contracts)
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
func.changesState = this.checkIfChangesState(
getFullQuallyfiedFuncDefinitionIdent(
contract.node,
func.node,
func.parameters
),
this.getContext(
callGraph,
contract,
func)
)
})
contract.functions.forEach((func) => {
if (this.isPotentialVulnerableFunction(func, this.getContext(callGraph, contract, func))) {
const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : ''
comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : ''
warnings.push({
warning: `Potential Violation of Checks-Effects-Interaction pattern in ${funcName}: Could potentially lead to re-entrancy vulnerability. ${comments}`,
location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/security-considerations.html#re-entrancy'
})
}
})
})
return warnings
}
private getContext (callGraph, currentContract, func) {
return { callGraph: callGraph, currentContract: currentContract, stateVariables: this.getStateVariables(currentContract, func) }
}
private getStateVariables (contract, func) {
return contract.stateVariables.concat(func.localVariables.filter(isStorageVariableDeclaration))
}
private isPotentialVulnerableFunction (func, context) {
let isPotentialVulnerable = false
let interaction = false
func.relevantNodes.forEach((node) => {
if (isInteraction(node)) {
interaction = true
} else if (interaction && (isWriteOnStateVariable(node, context.stateVariables) || this.isLocalCallWithStateChange(node, context))) {
isPotentialVulnerable = true
}
})
return isPotentialVulnerable
}
private isLocalCallWithStateChange (node, context) {
if (isLocalCallGraphRelevantNode(node)) {
const func = resolveCallGraphSymbol(context.callGraph, getFullQualifiedFunctionCallIdent(context.currentContract.node, node))
return !func || (func && func.node.changesState)
}
return false
}
private checkIfChangesState (startFuncName, context) {
return analyseCallGraph(context.callGraph, startFuncName, context, (node, context) => isWriteOnStateVariable(node, context.stateVariables))
}
}

@ -1,110 +0,0 @@
const name = 'Constant functions: '
const desc = 'Check for potentially constant functions'
const categories = require('./categories')
const common = require('./staticAnalysisCommon')
const fcallGraph = require('./functionCallGraph')
const AbstractAst = require('./abstractAstView')
const algo = require('./algorithmCategories')
function constantFunctions () {
this.abstractAst = new AbstractAst()
this.visit = this.abstractAst.build_visit(
(node) => common.isLowLevelCall(node) ||
common.isTransfer(node) ||
common.isExternalDirectCall(node) ||
common.isEffect(node) ||
common.isLocalCallGraphRelevantNode(node) ||
common.isInlineAssembly(node) ||
common.isNewExpression(node) ||
common.isSelfdestructCall(node) ||
common.isDeleteUnaryOperation(node)
)
this.report = this.abstractAst.build_report(report)
}
constantFunctions.prototype.visit = function () { throw new Error('constantFunctions.js no visit function set upon construction') }
constantFunctions.prototype.report = function () { throw new Error('constantFunctions.js no report function set upon construction') }
function report (contracts, multipleContractsWithSameName) {
const warnings = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
const callGraph = fcallGraph.buildGlobalFuncCallGraph(contracts)
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
if (common.isPayableFunction(func.node) || common.isConstructor(func.node)) {
func.potentiallyshouldBeConst = false
} else {
func.potentiallyshouldBeConst = checkIfShouldBeConstant(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters),
getContext(callGraph, contract, func))
}
})
contract.functions.filter((func) => common.hasFunctionBody(func.node)).forEach((func) => {
if (common.isConstantFunction(func.node) !== func.potentiallyshouldBeConst) {
const funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : ''
comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : ''
if (func.potentiallyshouldBeConst) {
warnings.push({
warning: `${funcName} : Potentially should be constant but is not. ${comments}`,
location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions'
})
} else {
warnings.push({
warning: `${funcName} : Is constant but potentially should not be. ${comments}`,
location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions'
})
}
}
})
})
return warnings
}
function getContext (callGraph, currentContract, func) {
return { callGraph: callGraph, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) }
}
function getStateVariables (contract, func) {
return contract.stateVariables.concat(func.localVariables.filter(common.isStorageVariableDeclaration))
}
function checkIfShouldBeConstant (startFuncName, context) {
return !fcallGraph.analyseCallGraph(context.callGraph, startFuncName, context, isConstBreaker)
}
function isConstBreaker (node, context) {
return common.isWriteOnStateVariable(node, context.stateVariables) ||
common.isLowLevelCall(node) ||
common.isTransfer(node) ||
isCallOnNonConstExternalInterfaceFunction(node, context) ||
common.isCallToNonConstLocalFunction(node) ||
common.isInlineAssembly(node) ||
common.isNewExpression(node) ||
common.isSelfdestructCall(node) ||
common.isDeleteUnaryOperation(node)
}
function isCallOnNonConstExternalInterfaceFunction (node, context) {
if (common.isExternalDirectCall(node)) {
const func = fcallGraph.resolveCallGraphSymbol(context.callGraph, common.getFullQualifiedFunctionCallIdent(context.currentContract, node))
return !func || (func && !common.isConstantFunction(func.node.node))
}
return false
}
module.exports = {
name: name,
description: desc,
category: categories.MISC,
algorithm: algo.HEURISTIC,
Module: constantFunctions
}

@ -0,0 +1,114 @@
import { default as category } from './categories'
import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode,
isInlineAssembly, isNewExpression, isSelfdestructCall, isDeleteUnaryOperation, isPayableFunction,
isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody, isConstantFunction, isWriteOnStateVariable,
isStorageVariableDeclaration, isCallToNonConstLocalFunction, getFullQualifiedFunctionCallIdent} from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView'
export default class constantFunctions {
name = 'Constant functions: '
desc = 'Check for potentially constant functions'
categories = category.MISC
algorithm = algorithm.HEURISTIC
Module = this
abstractAst = new AbstractAst()
visit = this.abstractAst.build_visit(
(node) => isLowLevelCall(node) ||
isTransfer(node) ||
isExternalDirectCall(node) ||
isEffect(node) ||
isLocalCallGraphRelevantNode(node) ||
isInlineAssembly(node) ||
isNewExpression(node) ||
isSelfdestructCall(node) ||
isDeleteUnaryOperation(node)
)
report = this.abstractAst.build_report(this._report)
private _report (contracts, multipleContractsWithSameName) {
const warnings: any = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
const callGraph = buildGlobalFuncCallGraph(contracts)
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
if (isPayableFunction(func.node) || isConstructor(func.node)) {
func.potentiallyshouldBeConst = false
} else {
func.potentiallyshouldBeConst = this.checkIfShouldBeConstant(
getFullQuallyfiedFuncDefinitionIdent(
contract.node,
func.node,
func.parameters
),
this.getContext(
callGraph,
contract,
func
)
)
}
})
contract.functions.filter((func) => hasFunctionBody(func.node)).forEach((func) => {
if (isConstantFunction(func.node) !== func.potentiallyshouldBeConst) {
const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : ''
comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : ''
if (func.potentiallyshouldBeConst) {
warnings.push({
warning: `${funcName} : Potentially should be constant but is not. ${comments}`,
location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions'
})
} else {
warnings.push({
warning: `${funcName} : Is constant but potentially should not be. ${comments}`,
location: func.src,
more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions'
})
}
}
})
})
return warnings
}
private getContext (callGraph, currentContract, func) {
return { callGraph: callGraph, currentContract: currentContract, stateVariables: this.getStateVariables(currentContract, func) }
}
private getStateVariables (contract, func) {
return contract.stateVariables.concat(func.localVariables.filter(isStorageVariableDeclaration))
}
private checkIfShouldBeConstant (startFuncName, context) {
return !analyseCallGraph(context.callGraph, startFuncName, context, this.isConstBreaker)
}
private isConstBreaker (node, context) {
return isWriteOnStateVariable(node, context.stateVariables) ||
isLowLevelCall(node) ||
isTransfer(node) ||
this.isCallOnNonConstExternalInterfaceFunction(node, context) ||
isCallToNonConstLocalFunction(node) ||
isInlineAssembly(node) ||
isNewExpression(node) ||
isSelfdestructCall(node) ||
isDeleteUnaryOperation(node)
}
private isCallOnNonConstExternalInterfaceFunction (node, context) {
if (isExternalDirectCall(node)) {
const func = resolveCallGraphSymbol(context.callGraph, getFullQualifiedFunctionCallIdent(context.currentContract, node))
return !func || (func && !isConstantFunction(func.node.node))
}
return false
}
}

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isDeleteOfDynamicArray } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export class deleteDynamicArrays {
export default class deleteDynamicArrays {
rel: any = []
name = 'Delete on dynamic Array: '
desc = 'Use require and appropriately'

@ -1,7 +1,7 @@
import { default as category } from './categories'
import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon'
export class deleteFromDynamicArray {
export default class deleteFromDynamicArray {
relevantNodes: any[] = []
name = 'Delete from dynamic Array: '
desc = 'Using delete on an array leaves a gap'

@ -1,63 +0,0 @@
const name = 'ERC20: '
const desc = 'Decimal should be uint8'
const categories = require('./categories')
const common = require('./staticAnalysisCommon')
const AbstractAst = require('./abstractAstView')
const algo = require('./algorithmCategories')
function erc20Decimals () {
this.abstractAst = new AbstractAst()
this.visit = this.abstractAst.build_visit(
(node) => false
)
this.report = this.abstractAst.build_report(report)
}
erc20Decimals.prototype.visit = function () { throw new Error('erc20Decimals.js no visit function set upon construction') }
erc20Decimals.prototype.report = function () { throw new Error('erc20Decimals.js no report function set upon construction') }
function report (contracts, multipleContractsWithSameName) {
const warnings = []
contracts.forEach((contract) => {
const contractAbiSignatures = contract.functions.map((f) => common.helpers.buildAbiSignature(common.getFunctionDefinitionName(f.node), f.parameters))
if (isERC20(contractAbiSignatures)) {
const decimalsVar = contract.stateVariables.filter((stateVar) => common.getDeclaredVariableName(stateVar) === 'decimals' && (common.getDeclaredVariableType(stateVar) !== 'uint8' || stateVar.attributes.visibility !== 'public'))
const decimalsFun = contract.functions.filter((f) => common.getFunctionDefinitionName(f.node) === 'decimals' &&
(
(f.returns.length === 0 || f.returns.length > 1) ||
(f.returns.length === 1 && (f.returns[0].type !== 'uint8' || f.node.attributes.visibility !== 'public'))
)
)
if (decimalsVar.length > 0 || decimalsFun.length > 0) {
warnings.push({
warning: 'ERC20 Contracts decimals function should have uint8 as return type',
location: null,
more: ' https://eips.ethereum.org/EIPS/eip-20'
})
}
}
})
return warnings
}
function isERC20 (funSignatures) {
return funSignatures.includes('totalSupply()') &&
funSignatures.includes('balanceOf(address)') &&
funSignatures.includes('transfer(address,uint256)') &&
funSignatures.includes('transferFrom(address,address,uint256)') &&
funSignatures.includes('approve(address,uint256)') &&
funSignatures.includes('allowance(address,address)')
}
module.exports = {
name: name,
description: desc,
category: categories.ERC,
algorithm: algo.EXACT,
Module: erc20Decimals
}

@ -0,0 +1,54 @@
import { default as category } from './categories'
import { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
export default class erc20Decimals {
name = 'ERC20: '
desc = 'Decimal should be uint8'
categories = category.ERC
algorithm = algorithm.EXACT
Module = this
abstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node) => false)
report = this.abstractAst.build_report(this._report)
private _report (contracts, multipleContractsWithSameName) {
const warnings: any = []
contracts.forEach((contract) => {
const contractAbiSignatures = contract.functions.map((f) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters))
if (this.isERC20(contractAbiSignatures)) {
const decimalsVar = contract.stateVariables.filter((stateVar) => getDeclaredVariableName(stateVar) === 'decimals' && (getDeclaredVariableType(stateVar) !== 'uint8' || stateVar.attributes.visibility !== 'public'))
const decimalsFun = contract.functions.filter((f) => getFunctionDefinitionName(f.node) === 'decimals' &&
(
(f.returns.length === 0 || f.returns.length > 1) ||
(f.returns.length === 1 && (f.returns[0].type !== 'uint8' || f.node.attributes.visibility !== 'public'))
)
)
if (decimalsVar.length > 0 || decimalsFun.length > 0) {
warnings.push({
warning: 'ERC20 Contracts decimals function should have uint8 as return type',
location: null,
more: ' https://eips.ethereum.org/EIPS/eip-20'
})
}
}
})
return warnings
}
private isERC20 (funSignatures) {
return funSignatures.includes('totalSupply()') &&
funSignatures.includes('balanceOf(address)') &&
funSignatures.includes('transfer(address,uint256)') &&
funSignatures.includes('transferFrom(address,address,uint256)') &&
funSignatures.includes('approve(address,uint256)') &&
funSignatures.includes('allowance(address,address)')
}
}

@ -1,7 +1,7 @@
import { default as category } from './categories'
import { isLoop, isBlock, getLoopBlockStartIndex, isExpressionStatement, isTransfer } from './staticAnalysisCommon'
export class etherTransferInLoop {
export default class etherTransferInLoop {
relevantNodes: any[] = []
name = 'Ether transfer in a loop: '
desc = 'Avoid transferring Ether to multiple addresses in a loop'
@ -12,7 +12,7 @@ export class etherTransferInLoop {
if (isLoop(node)) {
let transferNodes = []
const loopBlockStartIndex = getLoopBlockStartIndex(node)
if (isBlock(node.children[loopBlockStartIndex])) {
if (loopBlockStartIndex && isBlock(node.children[loopBlockStartIndex])) {
transferNodes = node.children[loopBlockStartIndex].children
.filter(child => (isExpressionStatement(child) &&
child.children[0].name === 'FunctionCall' &&

@ -1,7 +1,7 @@
import { default as category } from './categories'
const { isForLoop, isDynamicArrayLengthAccess, isBinaryOperation } = require('./staticAnalysisCommon')
export class forLoopIteratesOverDynamicArray {
export default class forLoopIteratesOverDynamicArray {
relevantNodes: any[] = []
name = 'For loop iterates over dynamic array: '
desc = 'The number of \'for\' loop iterations depends on dynamic array\'s size'

@ -12,7 +12,6 @@ function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIden
callGraph[extractFuncDefIdent(func)] = { node: func, calls: calls }
})
return callGraph
}
@ -40,7 +39,7 @@ function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIden
* @contracts {list contracts} Expects as input the contract structure defined in abstractAstView.js
* @return {map (string -> Contract Call Graph)} returns map from contract name to contract call graph
*/
function buildGlobalFuncCallGraph (contracts) {
export function buildGlobalFuncCallGraph (contracts) {
const callGraph = {}
contracts.forEach((contract) => {
const filterNodes = (node) => { return common.isLocalCallGraphRelevantNode(node) || common.isExternalDirectCall(node) }
@ -61,7 +60,7 @@ function buildGlobalFuncCallGraph (contracts) {
* @nodeCheck {(ASTNode, context) -> bool} applied on every relevant node in the call graph
* @return {bool} returns map from contract name to contract call graph
*/
function analyseCallGraph (callGraph, funcName, context, nodeCheck) {
export function analyseCallGraph (callGraph, funcName, context, nodeCheck) {
return analyseCallGraphInternal(callGraph, funcName, context, (a, b) => a || b, nodeCheck, {})
}
@ -75,7 +74,7 @@ function analyseCallGraphInternal (callGraph, funcName, context, combinator, nod
current.calls.reduce((acc, val) => combinator(acc, analyseCallGraphInternal(callGraph, val, context, combinator, nodeCheck, visited)), false))
}
function resolveCallGraphSymbol (callGraph, funcName) {
export function resolveCallGraphSymbol (callGraph, funcName) {
return resolveCallGraphSymbolInternal(callGraph, funcName, false)
}
@ -106,9 +105,3 @@ function resolveCallGraphSymbolInternal (callGraph, funcName, silent) {
if (current === undefined && !silent) console.log(`static analysis functionCallGraph.js: ${funcName} not found in function call graph.`)
return current
}
module.exports = {
analyseCallGraph: analyseCallGraph,
buildGlobalFuncCallGraph: buildGlobalFuncCallGraph,
resolveCallGraphSymbol: resolveCallGraphSymbol
}

@ -1,67 +0,0 @@
const name = 'Gas costs: '
const desc = 'Warn if the gas requirements of functions are too high.'
const categories = require('./categories')
const algo = require('./algorithmCategories')
function gasCosts () {
}
/**
* call the given @arg cb (function) for all the contracts. Uses last compilation result
* stop visiting when cb return true
* @param {Function} cb - callback
*/
// @TODO has been copied from remix-ide repo ! should fix that soon !
function visitContracts (contracts, cb) {
for (let file in contracts) {
for (let name in contracts[file]) {
if (cb({ name: name, object: contracts[file][name], file: file })) return
}
}
}
gasCosts.prototype.report = function (compilationResults) {
const report = []
visitContracts(compilationResults.contracts, (contract) => {
if (
!contract.object.evm.gasEstimates ||
!contract.object.evm.gasEstimates.external
) {
return
}
const fallback = contract.object.evm.gasEstimates.external['']
if (fallback !== undefined) {
if (fallback === null || fallback >= 2100 || fallback === 'infinite') {
report.push({
warning: `Fallback function of contract ${contract.name} requires too much gas (${fallback}).
If the fallback function requires more than 2300 gas, the contract cannot receive Ether.`
})
}
}
for (var functionName in contract.object.evm.gasEstimates.external) {
if (functionName === '') {
continue
}
const gas = contract.object.evm.gasEstimates.external[functionName]
const gasString = gas === null ? 'unknown or not constant' : 'high: ' + gas
if (gas === null || gas >= 3000000 || gas === 'infinite') {
report.push({
warning: `Gas requirement of function ${contract.name}.${functionName} ${gasString}.
If the gas requirement of a function is higher than the block gas limit, it cannot be executed.
Please avoid loops in your functions or actions that modify large areas of storage
(this includes clearing or copying arrays in storage)`
})
}
}
})
return report
}
module.exports = {
name: name,
description: desc,
category: categories.GAS,
algorithm: algo.EXACT,
Module: gasCosts
}

@ -0,0 +1,63 @@
import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
export default class gasCosts {
name = 'Gas costs: '
desc = 'Warn if the gas requirements of functions are too high.'
categories = category.GAS
algorithm = algorithm.EXACT
Module = this
/**
* call the given @arg cb (function) for all the contracts. Uses last compilation result
* stop visiting when cb return true
* @param {Function} cb - callback
*/
// @TODO has been copied from remix-ide repo ! should fix that soon !
visitContracts (contracts, cb) {
for (let file in contracts) {
for (let name in contracts[file]) {
if (cb({ name: name, object: contracts[file][name], file: file })) return
}
}
}
report (compilationResults) {
const report: any[] = []
this.visitContracts(compilationResults.contracts, (contract) => {
if (
!contract.object.evm.gasEstimates ||
!contract.object.evm.gasEstimates.external
) {
return
}
const fallback = contract.object.evm.gasEstimates.external['']
if (fallback !== undefined) {
if (fallback === null || fallback >= 2100 || fallback === 'infinite') {
report.push({
warning: `Fallback function of contract ${contract.name} requires too much gas (${fallback}).
If the fallback function requires more than 2300 gas, the contract cannot receive Ether.`
})
}
}
for (var functionName in contract.object.evm.gasEstimates.external) {
if (functionName === '') {
continue
}
const gas = contract.object.evm.gasEstimates.external[functionName]
const gasString = gas === null ? 'unknown or not constant' : 'high: ' + gas
if (gas === null || gas >= 3000000 || gas === 'infinite') {
report.push({
warning: `Gas requirement of function ${contract.name}.${functionName} ${gasString}.
If the gas requirement of a function is higher than the block gas limit, it cannot be executed.
Please avoid loops in your functions or actions that modify large areas of storage
(this includes clearing or copying arrays in storage)`
})
}
}
})
return report
}
}

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isRequireCall, isAssertCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export class guardConditions {
export default class guardConditions {
guards: any[] = []
name = 'Guard Conditions: '
desc = 'Use require and appropriately'

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isInlineAssembly } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export class inlineAssembly {
export default class inlineAssembly {
inlineAssNodes: any[] = []
name = 'Inline assembly: '
desc = 'Use of Inline Assembly'

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isIntDivision } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export class intDivitionTruncate {
export default class intDivitionTruncate {
warningNodes: any[] = []
name = 'Data Trucated: '
desc = 'Division on int/uint values truncates the result.'

@ -1,21 +0,0 @@
module.exports = [
require('./txOrigin'),
require('./gasCosts'),
require('./thisLocal'),
require('./checksEffectsInteraction'),
require('./constantFunctions'),
require('./similarVariableNames.js'),
require('./inlineAssembly'),
require('./blockTimestamp'),
require('./lowLevelCalls'),
require('./blockBlockhash'),
require('./noReturn'),
require('./selfdestruct'),
require('./guardConditions'),
require('./deleteDynamicArrays'),
require('./assignAndCompare'),
require('./erc20Decimals'),
require('./stringBytesLength'),
require('./deleteFromDynamicArray'),
require('./forLoopIteratesOverDynamicArray')
]

@ -0,0 +1,41 @@
import txOrigin from './txOrigin'
import gasCosts from './gasCosts'
import thisLocal from './thisLocal'
import checksEffectsInteraction from './checksEffectsInteraction'
import constantFunctions from './constantFunctions'
import similarVariableNames from './similarVariableNames'
import inlineAssembly from './inlineAssembly'
import blockTimestamp from './blockTimestamp'
import lowLevelCalls from './lowLevelCalls'
import blockBlockhash from './blockBlockhash'
import noReturn from './noReturn'
import selfdestruct from './selfdestruct'
import guardConditions from './guardConditions'
import deleteDynamicArrays from './deleteDynamicArrays'
import assignAndCompare from './assignAndCompare'
import erc20Decimals from './erc20Decimals'
import stringBytesLength from './stringBytesLength'
import deleteFromDynamicArray from './deleteFromDynamicArray'
import forLoopIteratesOverDynamicArray from './forLoopIteratesOverDynamicArray'
export default [
new txOrigin(),
new gasCosts(),
new thisLocal(),
new checksEffectsInteraction(),
new constantFunctions(),
new similarVariableNames(),
new inlineAssembly(),
new blockTimestamp(),
new lowLevelCalls(),
new blockBlockhash(),
new noReturn(),
new selfdestruct(),
new guardConditions(),
new deleteDynamicArrays(),
new assignAndCompare(),
new erc20Decimals(),
new stringBytesLength(),
new deleteFromDynamicArray(),
new forLoopIteratesOverDynamicArray()
]

@ -3,7 +3,7 @@ import { isLowLevelCallInst, isLowLevelCallInst050, isLowLevelCallcodeInst, isLo
isLowLevelSendInst, isLowLevelSendInst050, isLLDelegatecallInst050, lowLevelCallTypes } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export class lowLevelCalls {
export default class lowLevelCalls {
llcNodes: any[] = []
name = 'Low level calls: '
desc = 'Semantics maybe unclear'

@ -1,75 +0,0 @@
const name = 'no return: '
const desc = 'Function with return type is not returning'
const categories = require('./categories')
const common = require('./staticAnalysisCommon')
const AbstractAst = require('./abstractAstView')
const algo = require('./algorithmCategories')
function noReturn () {
this.abstractAst = new AbstractAst()
this.visit = this.abstractAst.build_visit(
(node) => common.isReturn(node) || common.isAssignment(node)
)
this.report = this.abstractAst.build_report(report)
}
noReturn.prototype.visit = function () { throw new Error('noReturn.js no visit function set upon construction') }
noReturn.prototype.report = function () { throw new Error('noReturn.js no report function set upon construction') }
function report (contracts, multipleContractsWithSameName) {
const warnings = []
contracts.forEach((contract) => {
contract.functions.filter((func) => common.hasFunctionBody(func.node)).forEach((func) => {
const funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
if (hasNamedAndUnnamedReturns(func)) {
warnings.push({
warning: `${funcName}: Mixing of named and unnamed return parameters is not advised.`,
location: func.src
})
} else if (shouldReturn(func) && !(hasReturnStatement(func) || (hasNamedReturns(func) && hasAssignToAllNamedReturns(func)))) {
warnings.push({
warning: `${funcName}: Defines a return type but never explicitly returns a value.`,
location: func.src
})
}
})
})
return warnings
}
function shouldReturn (func) {
return func.returns.length > 0
}
function hasReturnStatement (func) {
return func.relevantNodes.filter(common.isReturn).length > 0
}
function hasAssignToAllNamedReturns (func) {
const namedReturns = func.returns.filter((n) => n.name.length > 0).map((n) => n.name)
const assignedVars = func.relevantNodes.filter(common.isAssignment).map(common.getEffectedVariableName)
const diff = namedReturns.filter(e => !assignedVars.includes(e))
return diff.length === 0
}
function hasNamedReturns (func) {
return func.returns.filter((n) => n.name.length > 0).length > 0
}
function hasNamedAndUnnamedReturns (func) {
return func.returns.filter((n) => n.name.length === 0).length > 0 &&
hasNamedReturns(func)
}
module.exports = {
name: name,
description: desc,
category: categories.MISC,
algorithm: algo.EXACT,
Module: noReturn
}

@ -0,0 +1,66 @@
import { default as category } from './categories'
import { isReturn, isAssignment, hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
export default class noReturn {
name = 'no return: '
desc = 'Function with return type is not returning'
categories = category.MISC
algorithm = algorithm.EXACT
Module = this
abstractAst = new AbstractAst()
visit = this.abstractAst.build_visit(
(node) => isReturn(node) || isAssignment(node)
)
report = this.abstractAst.build_report(this._report)
private _report (contracts, multipleContractsWithSameName) {
const warnings: any[] = []
contracts.forEach((contract) => {
contract.functions.filter((func) => hasFunctionBody(func.node)).forEach((func) => {
const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
if (this.hasNamedAndUnnamedReturns(func)) {
warnings.push({
warning: `${funcName}: Mixing of named and unnamed return parameters is not advised.`,
location: func.src
})
} else if (this.shouldReturn(func) && !(this.hasReturnStatement(func) || (this.hasNamedReturns(func) && this.hasAssignToAllNamedReturns(func)))) {
warnings.push({
warning: `${funcName}: Defines a return type but never explicitly returns a value.`,
location: func.src
})
}
})
})
return warnings
}
private shouldReturn (func) {
return func.returns.length > 0
}
private hasReturnStatement (func) {
return func.relevantNodes.filter(isReturn).length > 0
}
private hasAssignToAllNamedReturns (func) {
const namedReturns = func.returns.filter((n) => n.name.length > 0).map((n) => n.name)
const assignedVars = func.relevantNodes.filter(isAssignment).map(getEffectedVariableName)
const diff = namedReturns.filter(e => !assignedVars.includes(e))
return diff.length === 0
}
private hasNamedReturns (func) {
return func.returns.filter((n) => n.name.length > 0).length > 0
}
private hasNamedAndUnnamedReturns (func) {
return func.returns.filter((n) => n.name.length === 0).length > 0 &&
this.hasNamedReturns(func)
}
}

@ -1,59 +0,0 @@
const name = 'Selfdestruct: '
const desc = 'Be aware of caller contracts.'
const categories = require('./categories')
const common = require('./staticAnalysisCommon')
const AbstractAst = require('./abstractAstView')
const algo = require('./algorithmCategories')
function selfdestruct () {
this.abstractAst = new AbstractAst()
this.visit = this.abstractAst.build_visit(
(node) => common.isStatement(node) ||
common.isSelfdestructCall(node)
)
this.report = this.abstractAst.build_report(report)
}
selfdestruct.prototype.visit = function () { throw new Error('selfdestruct.js no visit function set upon construction') }
selfdestruct.prototype.report = function () { throw new Error('selfdestruct.js no report function set upon construction') }
function report (contracts, multipleContractsWithSameName) {
const warnings = []
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
let hasSelf = false
func.relevantNodes.forEach((node) => {
if (common.isSelfdestructCall(node)) {
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.',
location: node.src,
more: 'https://paritytech.io/blog/security-alert.html'
})
hasSelf = true
}
if (common.isStatement(node) && hasSelf) {
warnings.push({
warning: 'Use of selfdestruct: No code after selfdestruct is executed. Selfdestruct is a terminal.',
location: node.src,
more: 'http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html#self-destruct'
})
hasSelf = false
}
})
})
})
return warnings
}
module.exports = {
name: name,
description: desc,
category: categories.SECURITY,
algorithm: algo.HEURISTIC,
Module: selfdestruct
}

@ -0,0 +1,50 @@
import { default as category } from './categories'
import { isStatement, isSelfdestructCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
export default class selfdestruct {
name = 'Selfdestruct: '
desc = 'Be aware of caller contracts.'
categories = category.SECURITY
algorithm = algorithm.HEURISTIC
Module = this
abstractAst = new AbstractAst()
visit = this.abstractAst.build_visit(
(node) => isStatement(node) ||
isSelfdestructCall(node)
)
report = this.abstractAst.build_report(this._report)
private _report (contracts, multipleContractsWithSameName) {
const warnings: any[] = []
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
let hasSelf = false
func.relevantNodes.forEach((node) => {
if (isSelfdestructCall(node)) {
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.',
location: node.src,
more: 'https://paritytech.io/blog/security-alert.html'
})
hasSelf = true
}
if (isStatement(node) && hasSelf) {
warnings.push({
warning: 'Use of selfdestruct: No code after selfdestruct is executed. Selfdestruct is a terminal.',
location: node.src,
more: 'http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html#self-destruct'
})
hasSelf = false
}
})
})
})
return warnings
}
}

@ -1,87 +0,0 @@
const name = 'Similar variable names: '
const desc = 'Check if variable names are too similar'
const categories = require('./categories')
const common = require('./staticAnalysisCommon')
const AbstractAst = require('./abstractAstView')
const levenshtein = require('fast-levenshtein')
const remixLib = require('remix-lib')
const util = remixLib.util
const algo = require('./algorithmCategories')
function similarVariableNames () {
this.abstractAst = new AbstractAst()
this.visit = this.abstractAst.build_visit(
(node) => false
)
this.report = this.abstractAst.build_report(report)
}
similarVariableNames.prototype.visit = function () { throw new Error('similarVariableNames.js no visit function set upon construction') }
similarVariableNames.prototype.report = function () { throw new Error('similarVariableNames.js no report function set upon construction') }
function report (contracts, multipleContractsWithSameName) {
const warnings = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
const funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let hasModifiersComments = ''
if (hasModifiers) {
hasModifiersComments = 'Note: Modifiers are currently not considered by this static analysis.'
}
let multipleContractsWithSameNameComments = ''
if (multipleContractsWithSameName) {
multipleContractsWithSameNameComments = 'Note: Import aliases are currently not supported by this static analysis.'
}
const vars = getFunctionVariables(contract, func).map(common.getDeclaredVariableName)
findSimilarVarNames(vars).map((sim) => {
warnings.push({
warning: `${funcName} : Variables have very similar names ${sim.var1} and ${sim.var2}. ${hasModifiersComments} ${multipleContractsWithSameNameComments}`,
location: func.src
})
})
})
})
return warnings
}
function findSimilarVarNames (vars) {
const similar = []
const comb = {}
vars.map((varName1) => vars.map((varName2) => {
if (varName1.length > 1 && varName2.length > 1 && varName2 !== varName1 && !isCommonPrefixedVersion(varName1, varName2) && !isCommonNrSuffixVersion(varName1, varName2) && !(comb[varName1 + ';' + varName2] || comb[varName2 + ';' + varName1])) {
comb[varName1 + ';' + varName2] = true
const distance = levenshtein.get(varName1, varName2)
if (distance <= 2) similar.push({ var1: varName1, var2: varName2, distance: distance })
}
}))
return similar
}
function isCommonPrefixedVersion (varName1, varName2) {
return (varName1.startsWith('_') && varName1.slice(1) === varName2) || (varName2.startsWith('_') && varName2.slice(1) === varName1)
}
function isCommonNrSuffixVersion (varName1, varName2) {
const ref = '^' + util.escapeRegExp(varName1.slice(0, -1)) + '[0-9]*$'
return varName2.match(ref) != null
}
function getFunctionVariables (contract, func) {
return contract.stateVariables.concat(func.localVariables)
}
module.exports = {
name: name,
description: desc,
category: categories.MISC,
algorithm: algo.EXACT,
Module: similarVariableNames
}

@ -0,0 +1,77 @@
import { default as category } from './categories'
import { getDeclaredVariableName, getFullQuallyfiedFuncDefinitionIdent } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { get } from 'fast-levenshtein'
import { util } from 'remix-lib'
export default class similarVariableNames {
name = 'Similar variable names: '
desc = 'Check if variable names are too similar'
abstractAst = new AbstractAst()
categories = category.MISC
algorithm = algorithm.EXACT
Module = this
visit = this.abstractAst.build_visit(
(node) => false
)
report = this.abstractAst.build_report(this._report)
private _report (contracts, multipleContractsWithSameName) {
const warnings: any[] = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let hasModifiersComments = ''
if (hasModifiers) {
hasModifiersComments = 'Note: Modifiers are currently not considered by this static analysis.'
}
let multipleContractsWithSameNameComments = ''
if (multipleContractsWithSameName) {
multipleContractsWithSameNameComments = 'Note: Import aliases are currently not supported by this static analysis.'
}
const vars = this.getFunctionVariables(contract, func).map(getDeclaredVariableName)
this.findSimilarVarNames(vars).map((sim) => {
warnings.push({
warning: `${funcName} : Variables have very similar names ${sim.var1} and ${sim.var2}. ${hasModifiersComments} ${multipleContractsWithSameNameComments}`,
location: func.src
})
})
})
})
return warnings
}
private findSimilarVarNames (vars) {
const similar: any[] = []
const comb = {}
vars.map((varName1) => vars.map((varName2) => {
if (varName1.length > 1 && varName2.length > 1 && varName2 !== varName1 && !this.isCommonPrefixedVersion(varName1, varName2) && !this.isCommonNrSuffixVersion(varName1, varName2) && !(comb[varName1 + ';' + varName2] || comb[varName2 + ';' + varName1])) {
comb[varName1 + ';' + varName2] = true
const distance = get(varName1, varName2)
if (distance <= 2) similar.push({ var1: varName1, var2: varName2, distance: distance })
}
}))
return similar
}
private isCommonPrefixedVersion (varName1, varName2) {
return (varName1.startsWith('_') && varName1.slice(1) === varName2) || (varName2.startsWith('_') && varName2.slice(1) === varName1)
}
private isCommonNrSuffixVersion (varName1, varName2) {
const ref = '^' + util.escapeRegExp(varName1.slice(0, -1)) + '[0-9]*$'
return varName2.match(ref) != null
}
private getFunctionVariables (contract, func) {
return contract.stateVariables.concat(func.localVariables)
}
}

@ -363,7 +363,9 @@ function getFunctionCallTypeParameterType (func) {
*/
function getLibraryCallContractName (funcCall) {
if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an this library call Node')
return new RegExp(basicRegex.LIBRARYTYPE).exec(funcCall.children[0].attributes.type)[1]
const types = new RegExp(basicRegex.LIBRARYTYPE).exec(funcCall.children[0].attributes.type)
if(types)
return types[1]
}
/**
@ -807,7 +809,7 @@ function isExternalDirectCall (node) {
function isNowAccess (node) {
return nodeType(node, exactMatch(nodeTypes.IDENTIFIER)) &&
expressionType(node, exactMatch(basicTypes.UINT)) &&
name(node, exactMatch('now'))
memName(node, exactMatch('now'))
}
/**
@ -969,7 +971,7 @@ function isStringToBytesConversion (node) {
function isExplicitCast (node, castFromType, castToType) {
return nodeType(node, exactMatch(nodeTypes.FUNCTIONCALL)) && nrOfChildren(node, 2) &&
nodeType(node.children[0], exactMatch(nodeTypes.ELEMENTARYTYPENAMEEXPRESSION)) && name(node.children[0], castToType) &&
nodeType(node.children[0], exactMatch(nodeTypes.ELEMENTARYTYPENAMEEXPRESSION)) && memName(node.children[0], castToType) &&
nodeType(node.children[1], exactMatch(nodeTypes.IDENTIFIER)) && expressionType(node.children[1], castFromType)
}
@ -1002,9 +1004,9 @@ function isForLoop (node) {
function isMemberAccess (node, retType, accessor, accessorType, memberName) {
return nodeType(node, exactMatch(nodeTypes.MEMBERACCESS)) &&
expressionType(node, retType) &&
name(node, memberName) &&
memName(node, memberName) &&
nrOfChildren(node, 1) &&
name(node.children[0], accessor) &&
memName(node.children[0], accessor) &&
expressionType(node.children[0], accessorType)
}
@ -1030,9 +1032,9 @@ function nodeType (node, typeRegex) {
return (node && !typeRegex) || (node && new RegExp(typeRegex).test(node.name))
}
function name (node, nameRegex) {
const regex = new RegExp(nameRegex)
return (node && !nameRegex) || (node && node.attributes && (regex.test(node.attributes.value) || regex.test(node.attributes.member_name)))
function memName (node, memNameRegex) {
const regex = new RegExp(memNameRegex)
return (node && !memNameRegex) || (node && node.attributes && (regex.test(node.attributes.value) || regex.test(node.attributes.member_name)))
}
function operator (node, opRegex) {
@ -1045,10 +1047,10 @@ function exactMatch (regexStr) {
return '^' + regexStr + '$'
}
function matches () {
const args = []
for (let k = 0; k < arguments.length; k++) {
args.push(arguments[k])
function matches (...fnArgs) {
const args: any[] = []
for (let k = 0; k < fnArgs.length; k++) {
args.push(fnArgs[k])
}
return '(' + args.join('|') + ')'
}
@ -1061,7 +1063,7 @@ function matches () {
* list of return type names
* @return {Boolean} isPayable
*/
function buildFunctionSignature (paramTypes, returnTypes, isPayable, additionalMods) {
function buildFunctionSignature (paramTypes, returnTypes, isPayable, additionalMods?) {
return 'function (' + util.concatWithSeperator(paramTypes, ',') + ')' + ((isPayable) ? ' payable' : '') + ((additionalMods) ? ' ' + additionalMods : '') + ((returnTypes.length) ? ' returns (' + util.concatWithSeperator(returnTypes, ',') + ')' : '')
}
@ -1088,117 +1090,119 @@ function findFirstSubNodeLTR (node, type) {
return null
}
module.exports = {
const helpers = {
nrOfChildren,
minNrOfChildren,
expressionType,
nodeType,
memName,
operator,
buildFunctionSignature,
buildAbiSignature
}
export {
// #################### Trivial Getters
getType: getType,
getType,
// #################### Complex Getters
getThisLocalCallName: getThisLocalCallName,
getSuperLocalCallName: getSuperLocalCallName,
getFunctionCallType: getFunctionCallType,
getContractName: getContractName,
getEffectedVariableName: getEffectedVariableName,
getDeclaredVariableName: getDeclaredVariableName,
getDeclaredVariableType: getDeclaredVariableType,
getLocalCallName: getLocalCallName,
getInheritsFromName: getInheritsFromName,
getExternalDirectCallContractName: getExternalDirectCallContractName,
getThisLocalCallContractName: getThisLocalCallContractName,
getExternalDirectCallMemberName: getExternalDirectCallMemberName,
getFunctionDefinitionName: getFunctionDefinitionName,
getFunctionCallTypeParameterType: getFunctionCallTypeParameterType,
getLibraryCallContractName: getLibraryCallContractName,
getLibraryCallMemberName: getLibraryCallMemberName,
getFullQualifiedFunctionCallIdent: getFullQualifiedFunctionCallIdent,
getFullQuallyfiedFuncDefinitionIdent: getFullQuallyfiedFuncDefinitionIdent,
getStateVariableDeclarationsFormContractNode: getStateVariableDeclarationsFormContractNode,
getFunctionOrModifierDefinitionParameterPart: getFunctionOrModifierDefinitionParameterPart,
getFunctionOrModifierDefinitionReturnParameterPart: getFunctionOrModifierDefinitionReturnParameterPart,
getUnAssignedTopLevelBinOps: getUnAssignedTopLevelBinOps,
getLoopBlockStartIndex: getLoopBlockStartIndex,
getThisLocalCallName,
getSuperLocalCallName,
getFunctionCallType,
getContractName,
getEffectedVariableName,
getDeclaredVariableName,
getDeclaredVariableType,
getLocalCallName,
getInheritsFromName,
getExternalDirectCallContractName,
getThisLocalCallContractName,
getExternalDirectCallMemberName,
getFunctionDefinitionName,
getFunctionCallTypeParameterType,
getLibraryCallContractName,
getLibraryCallMemberName,
getFullQualifiedFunctionCallIdent,
getFullQuallyfiedFuncDefinitionIdent,
getStateVariableDeclarationsFormContractNode,
getFunctionOrModifierDefinitionParameterPart,
getFunctionOrModifierDefinitionReturnParameterPart,
getUnAssignedTopLevelBinOps,
getLoopBlockStartIndex,
// #################### Complex Node Identification
isDeleteOfDynamicArray: isDeleteOfDynamicArray,
isDeleteFromDynamicArray: isDeleteFromDynamicArray,
isAbiNamespaceCall: isAbiNamespaceCall,
isSpecialVariableAccess: isSpecialVariableAccess,
isDynamicArrayAccess: isDynamicArrayAccess,
isDynamicArrayLengthAccess: isDynamicArrayLengthAccess,
isIndexAccess: isIndexAccess,
isMappingIndexAccess: isMappingIndexAccess,
isSubScopeWithTopLevelUnAssignedBinOp: isSubScopeWithTopLevelUnAssignedBinOp,
hasFunctionBody: hasFunctionBody,
isInteraction: isInteraction,
isEffect: isEffect,
isNowAccess: isNowAccess,
isBlockTimestampAccess: isBlockTimestampAccess,
isBlockBlockHashAccess: isBlockBlockHashAccess,
isThisLocalCall: isThisLocalCall,
isSuperLocalCall: isSuperLocalCall,
isLibraryCall: isLibraryCall,
isLocalCallGraphRelevantNode: isLocalCallGraphRelevantNode,
isLocalCall: isLocalCall,
isWriteOnStateVariable: isWriteOnStateVariable,
isStateVariable: isStateVariable,
isTransfer: isTransfer,
isLowLevelCall: isLowLevelCall,
isLowLevelCallInst: isLLCall,
isLowLevelCallInst050: isLLCall050,
isLowLevelSendInst050: isLLSend050,
isLLDelegatecallInst050: isLLDelegatecall050,
isLowLevelCallcodeInst: isLLCallcode,
isLowLevelDelegatecallInst: isLLDelegatecall,
isLowLevelSendInst: isLLSend,
isExternalDirectCall: isExternalDirectCall,
isFullyImplementedContract: isFullyImplementedContract,
isLibrary: isLibrary,
isCallToNonConstLocalFunction: isCallToNonConstLocalFunction,
isPlusPlusUnaryOperation: isPlusPlusUnaryOperation,
isMinusMinusUnaryOperation: isMinusMinusUnaryOperation,
isBuiltinFunctionCall: isBuiltinFunctionCall,
isSelfdestructCall: isSelfdestructCall,
isAssertCall: isAssertCall,
isRequireCall: isRequireCall,
isIntDivision: isIntDivision,
isStringToBytesConversion: isStringToBytesConversion,
isBytesLengthCheck: isBytesLengthCheck,
isLoop: isLoop,
isForLoop: isForLoop,
isDeleteOfDynamicArray,
isDeleteFromDynamicArray,
isAbiNamespaceCall,
isSpecialVariableAccess,
isDynamicArrayAccess,
isDynamicArrayLengthAccess,
isIndexAccess,
isMappingIndexAccess,
isSubScopeWithTopLevelUnAssignedBinOp,
hasFunctionBody,
isInteraction,
isEffect,
isNowAccess,
isBlockTimestampAccess,
isBlockBlockHashAccess,
isThisLocalCall,
isSuperLocalCall,
isLibraryCall,
isLocalCallGraphRelevantNode,
isLocalCall,
isWriteOnStateVariable,
isStateVariable,
isTransfer,
isLowLevelCall,
isLLCall as isLowLevelCallInst,
isLLCall050 as isLowLevelCallInst050,
isLLSend050 as isLowLevelSendInst050,
isLLDelegatecall050 as isLLDelegatecallInst050,
isLLCallcode as isLowLevelCallcodeInst,
isLLDelegatecall as isLowLevelDelegatecallInst,
isLLSend as isLowLevelSendInst,
isExternalDirectCall,
isFullyImplementedContract,
isLibrary,
isCallToNonConstLocalFunction,
isPlusPlusUnaryOperation,
isMinusMinusUnaryOperation,
isBuiltinFunctionCall,
isSelfdestructCall,
isAssertCall,
isRequireCall,
isIntDivision,
isStringToBytesConversion,
isBytesLengthCheck,
isLoop,
isForLoop,
// #################### Trivial Node Identification
isDeleteUnaryOperation: isDeleteUnaryOperation,
isFunctionDefinition: isFunctionDefinition,
isModifierDefinition: isModifierDefinition,
isInheritanceSpecifier: isInheritanceSpecifier,
isModifierInvocation: isModifierInvocation,
isVariableDeclaration: isVariableDeclaration,
isStorageVariableDeclaration: isStorageVariableDeclaration,
isAssignment: isAssignment,
isContractDefinition: isContractDefinition,
isConstantFunction: isConstantFunction,
isPayableFunction: isPayableFunction,
isConstructor: isConstructor,
isInlineAssembly: isInlineAssembly,
isNewExpression: isNewExpression,
isReturn: isReturn,
isStatement: isStatement,
isExpressionStatement: isExpressionStatement,
isBlock: isBlock,
isBinaryOperation: isBinaryOperation,
isDeleteUnaryOperation,
isFunctionDefinition,
isModifierDefinition,
isInheritanceSpecifier,
isModifierInvocation,
isVariableDeclaration,
isStorageVariableDeclaration,
isAssignment,
isContractDefinition,
isConstantFunction,
isPayableFunction,
isConstructor,
isInlineAssembly,
isNewExpression,
isReturn,
isStatement,
isExpressionStatement,
isBlock,
isBinaryOperation,
// #################### Constants
nodeTypes: nodeTypes,
basicTypes: basicTypes,
basicFunctionTypes: basicFunctionTypes,
lowLevelCallTypes: lowLevelCallTypes,
specialVariables: specialVariables,
helpers: {
nrOfChildren: nrOfChildren,
minNrOfChildren: minNrOfChildren,
expressionType: expressionType,
nodeType: nodeType,
name: name,
operator: operator,
buildFunctionSignature: buildFunctionSignature,
buildAbiSignature: buildAbiSignature
}
nodeTypes,
basicTypes,
basicFunctionTypes,
lowLevelCallTypes,
specialVariables,
helpers
}

@ -1,33 +0,0 @@
const name = 'String Length: '
const desc = 'Bytes length != String length'
const categories = require('./categories')
const common = require('./staticAnalysisCommon')
function stringBytesLength () {
this.stringToBytesConversions = []
this.bytesLengthChecks = []
}
stringBytesLength.prototype.visit = function (node) {
if (common.isStringToBytesConversion(node)) this.stringToBytesConversions.push(node)
else if (common.isBytesLengthCheck(node)) this.bytesLengthChecks.push(node)
}
stringBytesLength.prototype.report = function (compilationResults) {
if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) {
return [{
warning: 'Bytes and string length are not the same since strings are assumed to be UTF-8 encoded (according to the ABI defintion) therefore one character is not nessesarily encoded in one byte of data.',
location: this.bytesLengthChecks[0].src,
more: 'https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#argument-encoding'
}]
} else {
return []
}
}
module.exports = {
name: name,
description: desc,
category: categories.MISC,
Module: stringBytesLength
}

@ -0,0 +1,30 @@
import { default as category } from './categories'
import { isStringToBytesConversion, isBytesLengthCheck } from './staticAnalysisCommon'
export default class stringBytesLength {
name = 'String Length: '
desc = 'Bytes length != String length'
categories = category.MISC
Module = this
stringToBytesConversions: any[] = []
bytesLengthChecks: any[] = []
visit (node) {
if (isStringToBytesConversion(node)) this.stringToBytesConversions.push(node)
else if (isBytesLengthCheck(node)) this.bytesLengthChecks.push(node)
}
report (compilationResults) {
if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) {
return [{
warning: 'Bytes and string length are not the same since strings are assumed to be UTF-8 encoded (according to the ABI defintion) therefore one character is not nessesarily encoded in one byte of data.',
location: this.bytesLengthChecks[0].src,
more: 'https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#argument-encoding'
}]
} else {
return []
}
}
}

@ -1,31 +0,0 @@
const name = 'This on local calls: '
const desc = 'Invocation of local functions via this'
const categories = require('./categories')
const common = require('./staticAnalysisCommon')
const algo = require('./algorithmCategories')
function thisLocal () {
this.warningNodes = []
}
thisLocal.prototype.visit = function (node) {
if (common.isThisLocalCall(node)) this.warningNodes.push(node)
}
thisLocal.prototype.report = function (compilationResults) {
return this.warningNodes.map(function (item, i) {
return {
warning: 'Use of "this" for local functions: Never use this to call functions in the same contract, it only consumes more gas than normal local calls.',
location: item.src,
more: 'http://solidity.readthedocs.io/en/develop/control-structures.html#external-function-calls'
}
})
}
module.exports = {
name: name,
description: desc,
category: categories.GAS,
algorithm: algo.EXACT,
Module: thisLocal
}

@ -0,0 +1,26 @@
import { default as category } from './categories'
import { isThisLocalCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
export default class thisLocal {
warningNodes: any[] = []
name = 'This on local calls: '
desc = 'Invocation of local functions via this'
categories = category.GAS
algorithm = algorithm.EXACT
Module = this
visit (node) {
if (isThisLocalCall(node)) this.warningNodes.push(node)
}
report (compilationResults) {
return this.warningNodes.map(function (item, i) {
return {
warning: 'Use of "this" for local functions: Never use this to call functions in the same contract, it only consumes more gas than normal local calls.',
location: item.src,
more: 'http://solidity.readthedocs.io/en/develop/control-structures.html#external-function-calls'
}
})
}
}

@ -1,37 +0,0 @@
const name = 'Transaction origin: '
const desc = 'Warn if tx.origin is used'
const categories = require('./categories')
const algo = require('./algorithmCategories')
function txOrigin () {
this.txOriginNodes = []
}
txOrigin.prototype.visit = function (node) {
if (node.name === 'MemberAccess' &&
node.attributes.member_name === 'origin' &&
(node.attributes.type === 'address' || node.attributes.type === 'address payable') &&
node.children && node.children.length &&
node.children[0].attributes.type === 'tx' &&
node.children[0].attributes.value === 'tx') {
this.txOriginNodes.push(node)
}
}
txOrigin.prototype.report = function () {
return this.txOriginNodes.map((item, i) => {
return {
warning: `Use of tx.origin: "tx.origin" is useful only in very exceptional cases.
If you use it for authentication, you usually want to replace it by "msg.sender", because otherwise any contract you call can act on your behalf.`,
location: item.src
}
})
}
module.exports = {
name: name,
description: desc,
category: categories.SECURITY,
algorithm: algo.EXACT,
Module: txOrigin
}

@ -0,0 +1,32 @@
import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
export default class txOrigin {
txOriginNodes: any[] = []
name = 'Transaction origin: '
desc = 'Warn if tx.origin is used'
categories = category.SECURITY
algorithm = algorithm.EXACT
Module = this
visit (node) {
if (node.name === 'MemberAccess' &&
node.attributes.member_name === 'origin' &&
(node.attributes.type === 'address' || node.attributes.type === 'address payable') &&
node.children && node.children.length &&
node.children[0].attributes.type === 'tx' &&
node.children[0].attributes.value === 'tx') {
this.txOriginNodes.push(node)
}
}
report () {
return this.txOriginNodes.map((item, i) => {
return {
warning: `Use of tx.origin: "tx.origin" is useful only in very exceptional cases.
If you use it for authentication, you usually want to replace it by "msg.sender", because otherwise any contract you call can act on your behalf.`,
location: item.src
}
})
}
}

@ -1,5 +1,5 @@
var test = require('tape')
var common = require('../../src/solidity-analyzer/modules/staticAnalysisCommon')
var common = require('../../dist/src/solidity-analyzer/modules/staticAnalysisCommon')
var { localCall, thisLocalCall, libCall, externalDirect, superLocal, assignment,
inlineAssembly, forLoopNode, whileLoopNode, doWhileLoopNode, stateVariableContractNode,
functionDefinition, fullyQualifiedFunctionDefinition, selfdestruct, storageVariableNodes,
@ -56,11 +56,11 @@ test('staticAnalysisCommon.helpers.name', function (t) {
var node = { attributes: { value: 'now' } }
var node2 = { attributes: { member_name: 'call' } }
t.ok(common.helpers.name(node, 'now'), 'should work for values')
t.ok(common.helpers.name(node2, 'call'), 'should work for member_name')
t.ok(common.helpers.name(node2, '.all'), 'regex should work')
t.ok(common.helpers.memName(node, 'now'), 'should work for values')
t.ok(common.helpers.memName(node2, 'call'), 'should work for member_name')
t.ok(common.helpers.memName(node2, '.all'), 'regex should work')
lowlevelAccessersCommon(t, common.helpers.name, node)
lowlevelAccessersCommon(t, common.helpers.memName, node)
})
test('staticAnalysisCommon.helpers.operator', function (t) {

@ -1,7 +1,7 @@
var test = require('tape')
var remixLib = require('remix-lib')
var StatRunner = require('../../src/solidity-analyzer')
var StatRunner = require('../../dist/src/solidity-analyzer')
var compilerInput = remixLib.helpers.compiler.compilerInput
const niv = require('npm-install-version')
@ -51,7 +51,7 @@ testFiles.forEach((fileName) => {
test('Integration test thisLocal.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/thisLocal')
var module = require('../../dist/src/solidity-analyzer/modules/thisLocal')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -90,7 +90,7 @@ test('Integration test thisLocal.js', function (t) {
test('Integration test checksEffectsInteraction.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/checksEffectsInteraction')
var module = require('../../dist/src/solidity-analyzer/modules/checksEffectsInteraction')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 1,
@ -129,7 +129,7 @@ test('Integration test checksEffectsInteraction.js', function (t) {
test('Integration test constantFunctions.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/constantFunctions')
var module = require('../../dist/src/solidity-analyzer/modules/constantFunctions')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -168,7 +168,7 @@ test('Integration test constantFunctions.js', function (t) {
test('Integration test inlineAssembly.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/inlineAssembly')
var module = require('../../dist/src/solidity-analyzer/modules/inlineAssembly')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -207,7 +207,7 @@ test('Integration test inlineAssembly.js', function (t) {
test('Integration test txOrigin.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/txOrigin')
var module = require('../../dist/src/solidity-analyzer/modules/txOrigin')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -246,7 +246,7 @@ test('Integration test txOrigin.js', function (t) {
test('Integration test gasCosts.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/gasCosts')
var module = require('../../dist/src/solidity-analyzer/modules/gasCosts')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 2,
@ -285,7 +285,7 @@ test('Integration test gasCosts.js', function (t) {
test('Integration test similarVariableNames.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/similarVariableNames')
var module = require('../../dist/src/solidity-analyzer/modules/similarVariableNames')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -324,7 +324,7 @@ test('Integration test similarVariableNames.js', function (t) {
test('Integration test inlineAssembly.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/inlineAssembly')
var module = require('../../dist/src/solidity-analyzer/modules/inlineAssembly')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -363,7 +363,7 @@ test('Integration test inlineAssembly.js', function (t) {
test('Integration test blockTimestamp.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/blockTimestamp')
var module = require('../../dist/src/solidity-analyzer/modules/blockTimestamp')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 1,
@ -402,7 +402,7 @@ test('Integration test blockTimestamp.js', function (t) {
test('Integration test lowLevelCalls.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/lowLevelCalls')
var module = require('../../dist/src/solidity-analyzer/modules/lowLevelCalls')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 1,
@ -441,7 +441,7 @@ test('Integration test lowLevelCalls.js', function (t) {
test('Integration test blockBlockhash.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/blockBlockhash')
var module = require('../../dist/src/solidity-analyzer/modules/blockBlockhash')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -480,7 +480,7 @@ test('Integration test blockBlockhash.js', function (t) {
test('Integration test noReturn.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/noReturn')
var module = require('../../dist/src/solidity-analyzer/modules/noReturn')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -519,7 +519,7 @@ test('Integration test noReturn.js', function (t) {
test('Integration test selfdestruct.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/selfdestruct')
var module = require('../../dist/src/solidity-analyzer/modules/selfdestruct')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -558,7 +558,7 @@ test('Integration test selfdestruct.js', function (t) {
test('Integration test guardConditions.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/guardConditions')
var module = require('../../dist/src/solidity-analyzer/modules/guardConditions')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -597,7 +597,7 @@ test('Integration test guardConditions.js', function (t) {
test('Integration test deleteDynamicArrays.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/deleteDynamicArrays')
var module = require('../../dist/src/solidity-analyzer/modules/deleteDynamicArrays')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -636,7 +636,7 @@ test('Integration test deleteDynamicArrays.js', function (t) {
test('Integration test deleteFromDynamicArray.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/deleteFromDynamicArray')
var module = require('../../dist/src/solidity-analyzer/modules/deleteFromDynamicArray')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -675,7 +675,7 @@ test('Integration test deleteFromDynamicArray.js', function (t) {
test('Integration test assignAndCompare.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/assignAndCompare')
var module = require('../../dist/src/solidity-analyzer/modules/assignAndCompare')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -714,7 +714,7 @@ test('Integration test assignAndCompare.js', function (t) {
test('Integration test intDivisionTruncate.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/intDivisionTruncate')
var module = require('../../dist/src/solidity-analyzer/modules/intDivisionTruncate')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -753,7 +753,7 @@ test('Integration test intDivisionTruncate.js', function (t) {
test('Integration test erc20Decimal.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/erc20Decimals')
var module = require('../../dist/src/solidity-analyzer/modules/erc20Decimals')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -792,7 +792,7 @@ test('Integration test erc20Decimal.js', function (t) {
test('Integration test stringBytesLength.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/stringBytesLength')
var module = require('../../dist/src/solidity-analyzer/modules/stringBytesLength')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -831,7 +831,7 @@ test('Integration test stringBytesLength.js', function (t) {
test('Integration test etherTransferInLoop.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/etherTransferInLoop')
var module = require('../../dist/src/solidity-analyzer/modules/etherTransferInLoop')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -870,7 +870,7 @@ test('Integration test etherTransferInLoop.js', function (t) {
test('Integration test forLoopIteratesOverDynamicArray.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray')
var module = require('../../dist/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,

@ -1,7 +1,7 @@
var test = require('tape')
var remixLib = require('remix-lib')
var StatRunner = require('../../src/solidity-analyzer')
var StatRunner = require('../../dist/src/solidity-analyzer')
var compilerInput = remixLib.helpers.compiler.compilerInput
const niv = require('npm-install-version')
@ -51,7 +51,7 @@ testFiles.forEach((fileName) => {
test('Integration test thisLocal.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/thisLocal')
var module = require('../../dist/src/solidity-analyzer/modules/thisLocal')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -90,7 +90,7 @@ test('Integration test thisLocal.js', function (t) {
test('Integration test checksEffectsInteraction.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/checksEffectsInteraction')
var module = require('../../dist/src/solidity-analyzer/modules/checksEffectsInteraction')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 1,
@ -129,7 +129,7 @@ test('Integration test checksEffectsInteraction.js', function (t) {
test('Integration test constantFunctions.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/constantFunctions')
var module = require('../../dist/src/solidity-analyzer/modules/constantFunctions')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -168,7 +168,7 @@ test('Integration test constantFunctions.js', function (t) {
test('Integration test inlineAssembly.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/inlineAssembly')
var module = require('../../dist/src/solidity-analyzer/modules/inlineAssembly')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -207,7 +207,7 @@ test('Integration test inlineAssembly.js', function (t) {
test('Integration test txOrigin.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/txOrigin')
var module = require('../../dist/src/solidity-analyzer/modules/txOrigin')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -246,7 +246,7 @@ test('Integration test txOrigin.js', function (t) {
test('Integration test gasCosts.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/gasCosts')
var module = require('../../dist/src/solidity-analyzer/modules/gasCosts')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 2,
@ -285,7 +285,7 @@ test('Integration test gasCosts.js', function (t) {
test('Integration test similarVariableNames.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/similarVariableNames')
var module = require('../../dist/src/solidity-analyzer/modules/similarVariableNames')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -324,7 +324,7 @@ test('Integration test similarVariableNames.js', function (t) {
test('Integration test inlineAssembly.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/inlineAssembly')
var module = require('../../dist/src/solidity-analyzer/modules/inlineAssembly')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -363,7 +363,7 @@ test('Integration test inlineAssembly.js', function (t) {
test('Integration test blockTimestamp.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/blockTimestamp')
var module = require('../../dist/src/solidity-analyzer/modules/blockTimestamp')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 1,
@ -402,7 +402,7 @@ test('Integration test blockTimestamp.js', function (t) {
test('Integration test lowLevelCalls.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/lowLevelCalls')
var module = require('../../dist/src/solidity-analyzer/modules/lowLevelCalls')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 1,
@ -441,7 +441,7 @@ test('Integration test lowLevelCalls.js', function (t) {
test('Integration test blockBlockhash.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/blockBlockhash')
var module = require('../../dist/src/solidity-analyzer/modules/blockBlockhash')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -484,7 +484,7 @@ test('Integration test blockBlockhash.js', function (t) {
test('Integration test noReturn.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/noReturn')
var module = require('../../dist/src/solidity-analyzer/modules/noReturn')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -523,7 +523,7 @@ test('Integration test noReturn.js', function (t) {
test('Integration test selfdestruct.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/selfdestruct')
var module = require('../../dist/src/solidity-analyzer/modules/selfdestruct')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -562,7 +562,7 @@ test('Integration test selfdestruct.js', function (t) {
test('Integration test guardConditions.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/guardConditions')
var module = require('../../dist/src/solidity-analyzer/modules/guardConditions')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -601,7 +601,7 @@ test('Integration test guardConditions.js', function (t) {
test('Integration test deleteDynamicArrays.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/deleteDynamicArrays')
var module = require('../../dist/src/solidity-analyzer/modules/deleteDynamicArrays')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -640,7 +640,7 @@ test('Integration test deleteDynamicArrays.js', function (t) {
test('Integration test deleteFromDynamicArray.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/deleteFromDynamicArray')
var module = require('../../dist/src/solidity-analyzer/modules/deleteFromDynamicArray')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -679,7 +679,7 @@ test('Integration test deleteFromDynamicArray.js', function (t) {
test('Integration test assignAndCompare.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/assignAndCompare')
var module = require('../../dist/src/solidity-analyzer/modules/assignAndCompare')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -718,7 +718,7 @@ test('Integration test assignAndCompare.js', function (t) {
test('Integration test intDivisionTruncate.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/intDivisionTruncate')
var module = require('../../dist/src/solidity-analyzer/modules/intDivisionTruncate')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -757,7 +757,7 @@ test('Integration test intDivisionTruncate.js', function (t) {
test('Integration test erc20Decimal.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/erc20Decimals')
var module = require('../../dist/src/solidity-analyzer/modules/erc20Decimals')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -796,7 +796,7 @@ test('Integration test erc20Decimal.js', function (t) {
test('Integration test stringBytesLength.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/stringBytesLength')
var module = require('../../dist/src/solidity-analyzer/modules/stringBytesLength')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -835,7 +835,7 @@ test('Integration test stringBytesLength.js', function (t) {
test('Integration test etherTransferInLoop.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/etherTransferInLoop')
var module = require('../../dist/src/solidity-analyzer/modules/etherTransferInLoop')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,
@ -874,7 +874,7 @@ test('Integration test etherTransferInLoop.js', function (t) {
test('Integration test forLoopIteratesOverDynamicArray.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray')
var module = require('../../dist/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray')
var lengthCheck = {
'KingOfTheEtherThrone.sol': 0,

@ -1,7 +1,7 @@
var test = require('tape')
var remixLib = require('remix-lib')
var StatRunner = require('../../src/solidity-analyzer')
var StatRunner = require('../../dist/src/solidity-analyzer')
var compilerInput = remixLib.helpers.compiler.compilerInput
const niv = require('npm-install-version')
@ -22,7 +22,7 @@ test('staticAnalysisIssues.functionParameterPassingError', function (t) {
t.plan(2)
var res = compile('functionParameters.sol')
var module = require('../../src/solidity-analyzer/modules/checksEffectsInteraction')
var module = require('../../dist/src/solidity-analyzer/modules/checksEffectsInteraction')
var statRunner = new StatRunner()

@ -1,7 +1,7 @@
var test = require('tape')
var remixLib = require('remix-lib')
var StatRunner = require('../../src/solidity-analyzer')
var StatRunner = require('../../dist/src/solidity-analyzer')
var compilerInput = remixLib.helpers.compiler.compilerInput
const niv = require('npm-install-version')
@ -22,7 +22,7 @@ test('staticAnalysisIssues.functionParameterPassingError', function (t) {
t.plan(2)
var res = compile('functionParameters.sol')
var module = require('../../src/solidity-analyzer/modules/checksEffectsInteraction')
var module = require('../../dist/src/solidity-analyzer/modules/checksEffectsInteraction')
var statRunner = new StatRunner()

@ -12,7 +12,7 @@
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
/* Module Resolution Options */
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
"paths": { "remix-solidity": ["./"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"paths": { "remix-analyzer": ["./"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
"typeRoots": [
"./@types",
"./node_modules/@types"

Loading…
Cancel
Save