pull/5370/head
aniket-engg 5 years ago committed by Aniket
parent 0e1b07e903
commit c0086c936e
  1. 17
      remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts
  2. 17
      remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts
  3. 19
      remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts
  4. 20
      remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts
  5. 19
      remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts
  6. 17
      remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts
  7. 19
      remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts
  8. 21
      remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts
  9. 37
      remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts
  10. 30
      remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts
  11. 17
      remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts
  12. 17
      remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts
  13. 17
      remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts
  14. 25
      remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts
  15. 27
      remix-analyzer/src/solidity-analyzer/modules/noReturn.ts
  16. 17
      remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts
  17. 38
      remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts
  18. 19
      remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts
  19. 17
      remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts
  20. 21
      remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts
  21. 303
      remix-analyzer/src/types.ts

@ -1,19 +1,20 @@
import { default as category } from './categories'
import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class assignAndCompare {
warningNodes: any[] = []
name = 'Result not used: '
description = 'The result of an operation was not used.'
category = category.MISC
algorithm = algorithm.EXACT
export default class assignAndCompare implements AnalyzerModule {
warningNodes: AstNodeLegacy[] = []
name: string = 'Result not used: '
description: string = 'The result of an operation was not used.'
category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isSubScopeWithTopLevelUnAssignedBinOp(node)) getUnAssignedTopLevelBinOps(node).forEach((n) => this.warningNodes.push(n))
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNodes.map((item, i) => {
return {
warning: 'A binary operation yields a value that is not used in the following. This is often caused by confusing assignment (=) and comparison (==).',

@ -1,19 +1,20 @@
import { default as category } from './categories'
import { isBlockBlockHashAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class blockBlockhash {
warningNodes: any[] = []
name = 'Block.blockhash usage: '
desc = 'Semantics maybe unclear'
categories = category.SECURITY
algorithm = algorithm.EXACT
export default class blockBlockhash implements AnalyzerModule {
warningNodes: AstNodeLegacy[] = []
name: string = 'Block.blockhash usage: '
description: string = 'Semantics maybe unclear'
category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isBlockBlockHashAccess(node)) this.warningNodes.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNodes.map((item, i) => {
return {
warning: `use of "block.blockhash": "block.blockhash" is used to access the last 256 block hashes.

@ -1,21 +1,22 @@
import { default as category } from './categories'
import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class blockTimestamp {
warningNowNodes: any[] = []
warningblockTimestampNodes: any[] = []
name = 'Block timestamp: '
desc = 'Semantics maybe unclear'
categories = category.SECURITY
algorithm = algorithm.EXACT
export default class blockTimestamp implements AnalyzerModule {
warningNowNodes: AstNodeLegacy[] = []
warningblockTimestampNodes: AstNodeLegacy[] = []
name: string = 'Block timestamp: '
description: string = 'Semantics maybe unclear'
category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isNowAccess(node)) this.warningNowNodes.push(node)
else if (isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNowNodes.map((item, i) => {
return {
warning: `use of "now": "now" does not mean current time. Now is an alias for block.timestamp.

@ -4,22 +4,22 @@ import { isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfie
import { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class checksEffectsInteraction {
name = 'Check effects: '
desc = 'Avoid potential reentrancy bugs'
categories = category.SECURITY
algorithm = algorithm.HEURISTIC
export default class checksEffectsInteraction implements AnalyzerModule {
name: string = 'Check effects: '
description: string = 'Avoid potential reentrancy bugs'
category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.HEURISTIC
abstractAst = new AbstractAst()
abstractAst: AbstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node) => isInteraction(node) || isEffect(node) || isLocalCallGraphRelevantNode(node))
visit = this.abstractAst.build_visit((node: AstNodeLegacy) => isInteraction(node) || isEffect(node) || isLocalCallGraphRelevantNode(node))
report = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName) {
const warnings: any[] = []
private _report (contracts, multipleContractsWithSameName): ReportObj[] {
const warnings: ReportObj[] = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
const callGraph = buildGlobalFuncCallGraph(contracts)
contracts.forEach((contract) => {

@ -6,17 +6,18 @@ import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCall
import { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class constantFunctions {
name = 'Constant functions: '
desc = 'Check for potentially constant functions'
categories = category.MISC
algorithm = algorithm.HEURISTIC
export default class constantFunctions implements AnalyzerModule {
name: string = 'Constant functions: '
description: string = 'Check for potentially constant functions'
category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.HEURISTIC
abstractAst = new AbstractAst()
abstractAst: AbstractAst = new AbstractAst()
visit = this.abstractAst.build_visit(
(node) => isLowLevelCall(node) ||
(node: AstNodeLegacy) => isLowLevelCall(node) ||
isTransfer(node) ||
isExternalDirectCall(node) ||
isEffect(node) ||
@ -29,8 +30,8 @@ export default class constantFunctions {
report = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName) {
const warnings: any = []
private _report (contracts, multipleContractsWithSameName): ReportObj[] {
const warnings: ReportObj[] = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
const callGraph = buildGlobalFuncCallGraph(contracts)

@ -1,19 +1,20 @@
import { default as category } from './categories'
import { isDeleteOfDynamicArray } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class deleteDynamicArrays {
rel: any = []
name = 'Delete on dynamic Array: '
desc = 'Use require and appropriately'
categories = category.GAS
algorithm = algorithm.EXACT
export default class deleteDynamicArrays implements AnalyzerModule {
rel: AstNodeLegacy[] = []
name: string = 'Delete on dynamic Array: '
description: string = 'Use require and appropriately'
category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isDeleteOfDynamicArray(node)) this.rel.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.rel.map((node) => {
return {
warning: 'The “delete” operation when applied to a dynamically sized array in Solidity generates code to delete each of the elements contained. If the array is large, this operation can surpass the block gas limit and raise an OOG exception. Also nested dynamically sized objects can produce the same results.',

@ -1,17 +1,20 @@
import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class deleteFromDynamicArray {
relevantNodes: any[] = []
name = 'Delete from dynamic Array: '
desc = 'Using delete on an array leaves a gap'
categories = category.MISC
export default class deleteFromDynamicArray implements AnalyzerModule {
relevantNodes: AstNodeLegacy[] = []
name: string = 'Delete from dynamic Array: '
description: string = 'Using delete on an array leaves a gap'
category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
if (isDeleteFromDynamicArray(node) && !isMappingIndexAccess(node.children[0])) this.relevantNodes.push(node)
visit (node: AstNodeLegacy): void {
if (isDeleteFromDynamicArray(node) && node.children && !isMappingIndexAccess(node.children[0])) this.relevantNodes.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.relevantNodes.map((node) => {
return {
warning: 'Using delete on an array leaves a gap. The length of the array remains the same. If you want to remove the empty position you need to shift items manually and update the length property.\n',

@ -2,19 +2,20 @@ import { default as category } from './categories'
import { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class erc20Decimals {
name = 'ERC20: '
desc = 'Decimal should be uint8'
categories = category.ERC
algorithm = algorithm.EXACT
export default class erc20Decimals implements AnalyzerModule {
name: string = 'ERC20: '
description: string = 'Decimal should be uint8'
category: ModuleCategory = category.ERC
algorithm: ModuleAlgorithm = algorithm.EXACT
abstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node) => false)
abstractAst: AbstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node: AstNodeLegacy) => false)
report = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName) {
const warnings: any = []
private _report (contracts, multipleContractsWithSameName): ReportObj[] {
const warnings: ReportObj[] = []
contracts.forEach((contract) => {
const contractAbiSignatures = contract.functions.map((f) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters))
@ -41,7 +42,7 @@ export default class erc20Decimals {
return warnings
}
private isERC20 (funSignatures) {
private isERC20 (funSignatures: string[]): boolean {
return funSignatures.includes('totalSupply()') &&
funSignatures.includes('balanceOf(address)') &&
funSignatures.includes('transfer(address,uint256)') &&

@ -1,21 +1,30 @@
import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
import { isLoop, isBlock, getLoopBlockStartIndex, isExpressionStatement, isTransfer } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class etherTransferInLoop {
relevantNodes: any[] = []
name = 'Ether transfer in a loop: '
desc = 'Avoid transferring Ether to multiple addresses in a loop'
category = category.GAS
export default class etherTransferInLoop implements AnalyzerModule {
relevantNodes: AstNodeLegacy[] = []
name: string = 'Ether transfer in a loop: '
description: string = 'Avoid transferring Ether to multiple addresses in a loop'
category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isLoop(node)) {
let transferNodes = []
const loopBlockStartIndex = getLoopBlockStartIndex(node)
if (loopBlockStartIndex && isBlock(node.children[loopBlockStartIndex])) {
transferNodes = node.children[loopBlockStartIndex].children
.filter(child => (isExpressionStatement(child) &&
child.children[0].name === 'FunctionCall' &&
isTransfer(child.children[0].children[0])))
let transferNodes: AstNodeLegacy[] = []
const loopBlockStartIndex: number | undefined = getLoopBlockStartIndex(node)
if (loopBlockStartIndex && node.children && isBlock(node.children[loopBlockStartIndex])) {
const childrenNodes: AstNodeLegacy[] | undefined = node.children[loopBlockStartIndex].children
if(childrenNodes)
transferNodes = childrenNodes.filter(child => (
isExpressionStatement(child) &&
child.children &&
child.children[0].name === 'FunctionCall' &&
child.children[0].children &&
isTransfer(child.children[0].children[0])
)
)
if (transferNodes.length > 0) {
this.relevantNodes.push(...transferNodes)
}
@ -23,7 +32,7 @@ export default class etherTransferInLoop {
}
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.relevantNodes.map((node) => {
return {
warning: 'Ether payout should not be done in a loop: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. If required then make sure that number of iterations are low and you trust each address involved.',

@ -1,20 +1,26 @@
import { default as category } from './categories'
const { isForLoop, isDynamicArrayLengthAccess, isBinaryOperation } = require('./staticAnalysisCommon')
import { default as algorithm } from './algorithmCategories'
import { isForLoop, isDynamicArrayLengthAccess, isBinaryOperation } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class forLoopIteratesOverDynamicArray {
relevantNodes: any[] = []
name = 'For loop iterates over dynamic array: '
desc = 'The number of \'for\' loop iterations depends on dynamic array\'s size'
categories = category.GAS
export default class forLoopIteratesOverDynamicArray implements AnalyzerModule {
relevantNodes: AstNodeLegacy[] = []
name: string = 'For loop iterates over dynamic array: '
description: string = 'The number of \'for\' loop iterations depends on dynamic array\'s size'
category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
if (isForLoop(node)) {
visit (node: AstNodeLegacy): void {
if (isForLoop(node) && node.children) {
let conditionChildrenNode: AstNodeLegacy | null = null
// Access 'condition' node of 'for' loop statement
const forLoopConditionNode = node.children[1]
const forLoopConditionNode: AstNodeLegacy = node.children[1]
// Access right side of condition as its children
const conditionChildrenNode = forLoopConditionNode.children[1]
if(forLoopConditionNode && forLoopConditionNode.children){
conditionChildrenNode = forLoopConditionNode.children[1]
}
// Check if it is a binary operation. if yes, check if its children node access length of dynamic array
if (isBinaryOperation(conditionChildrenNode) && isDynamicArrayLengthAccess(conditionChildrenNode.children[0])) {
if (conditionChildrenNode && conditionChildrenNode.children && isBinaryOperation(conditionChildrenNode) && isDynamicArrayLengthAccess(conditionChildrenNode.children[0])) {
this.relevantNodes.push(node)
} else if (isDynamicArrayLengthAccess(conditionChildrenNode)) { // else check if condition node itself access length of dynamic array
this.relevantNodes.push(node)
@ -22,7 +28,7 @@ export default class forLoopIteratesOverDynamicArray {
}
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.relevantNodes.map((node) => {
return {
warning: 'Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. Additionally, using unbounded loops incurs in a lot of avoidable gas costs. Carefully test how many items at maximum you can pass to such functions to make it successful.',

@ -1,19 +1,20 @@
import { default as category } from './categories'
import { isRequireCall, isAssertCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class guardConditions {
guards: any[] = []
name = 'Guard Conditions: '
desc = 'Use require and appropriately'
categories = category.MISC
algorithm = algorithm.EXACT
export default class guardConditions implements AnalyzerModule {
guards: AstNodeLegacy[] = []
name: string = 'Guard Conditions: '
description: string = 'Use require and appropriately'
category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isRequireCall(node) || isAssertCall(node)) this.guards.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
if (this.guards.length > 0) {
return [{
warning: 'Use assert(x) if you never ever want x to be false, not in any circumstance (apart from a bug in your code). Use require(x) if x can be false, due to e.g. invalid input or a failing external component.',

@ -1,19 +1,20 @@
import { default as category } from './categories'
import { isInlineAssembly } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class inlineAssembly {
inlineAssNodes: any[] = []
name = 'Inline assembly: '
desc = 'Use of Inline Assembly'
categories = category.SECURITY
algorithm = algorithm.EXACT
export default class inlineAssembly implements AnalyzerModule {
inlineAssNodes: AstNodeLegacy[] = []
name: string = 'Inline assembly: '
description: string = 'Use of Inline Assembly'
category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isInlineAssembly(node)) this.inlineAssNodes.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.inlineAssNodes.map((node) => {
return {
warning: `CAUTION: The Contract uses inline assembly, this is only advised in rare cases.

@ -1,19 +1,20 @@
import { default as category } from './categories'
import { isIntDivision } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class intDivitionTruncate {
warningNodes: any[] = []
name = 'Data Trucated: '
desc = 'Division on int/uint values truncates the result.'
categories = category.MISC
algorithm = algorithm.EXACT
export default class intDivisionTruncate implements AnalyzerModule {
warningNodes: AstNodeLegacy[] = []
name: string = 'Data Truncated: '
description: string = 'Division on int/uint values truncates the result.'
category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isIntDivision(node)) this.warningNodes.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNodes.map((item, i) => {
return {
warning: 'Division of integer values yields an integer value again. That means e.g. 10 / 100 = 0 instead of 0.1 since the result is an integer again. This does not hold for division of (only) literal values since those yield rational constants.',

@ -2,15 +2,24 @@ import { default as category } from './categories'
import { isLowLevelCallInst, isLowLevelCallInst050, isLowLevelCallcodeInst, isLowLevelDelegatecallInst,
isLowLevelSendInst, isLowLevelSendInst050, isLLDelegatecallInst050, lowLevelCallTypes } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class lowLevelCalls {
llcNodes: any[] = []
name = 'Low level calls: '
desc = 'Semantics maybe unclear'
categories = category.SECURITY
algorithm = algorithm.EXACT
interface llcNode {
node: AstNodeLegacy
type: {
ident: string,
type: string
}
}
export default class lowLevelCalls implements AnalyzerModule {
llcNodes: llcNode[] = []
name: string = 'Low level calls: '
description: string = 'Semantics maybe unclear'
category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node : AstNodeLegacy): void {
if (isLowLevelCallInst(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.CALL})
} else if (isLowLevelCallInst050(node)) {
@ -28,7 +37,7 @@ export default class lowLevelCalls {
}
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.llcNodes.map((item, i) => {
let text = ''
let morehref: any = null

@ -2,21 +2,22 @@ import { default as category } from './categories'
import { isReturn, isAssignment, hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class noReturn {
name = 'no return: '
desc = 'Function with return type is not returning'
categories = category.MISC
algorithm = algorithm.EXACT
export default class noReturn implements AnalyzerModule {
name: string = 'no return: '
description: string = 'Function with return type is not returning'
category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
abstractAst = new AbstractAst()
abstractAst: AbstractAst = new AbstractAst()
visit = this.abstractAst.build_visit(
(node) => isReturn(node) || isAssignment(node)
(node: AstNodeLegacy) => isReturn(node) || isAssignment(node)
)
report = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName) {
private _report (contracts, multipleContractsWithSameName): ReportObj[] {
const warnings: any[] = []
contracts.forEach((contract) => {
@ -39,26 +40,26 @@ export default class noReturn {
return warnings
}
private shouldReturn (func) {
private shouldReturn (func): boolean {
return func.returns.length > 0
}
private hasReturnStatement (func) {
private hasReturnStatement (func): boolean {
return func.relevantNodes.filter(isReturn).length > 0
}
private hasAssignToAllNamedReturns (func) {
private hasAssignToAllNamedReturns (func): boolean {
const namedReturns = func.returns.filter((n) => n.name.length > 0).map((n) => n.name)
const assignedVars = func.relevantNodes.filter(isAssignment).map(getEffectedVariableName)
const diff = namedReturns.filter(e => !assignedVars.includes(e))
return diff.length === 0
}
private hasNamedReturns (func) {
private hasNamedReturns (func): boolean {
return func.returns.filter((n) => n.name.length > 0).length > 0
}
private hasNamedAndUnnamedReturns (func) {
private hasNamedAndUnnamedReturns (func): boolean {
return func.returns.filter((n) => n.name.length === 0).length > 0 &&
this.hasNamedReturns(func)
}

@ -2,23 +2,24 @@ import { default as category } from './categories'
import { isStatement, isSelfdestructCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class selfdestruct {
name = 'Selfdestruct: '
desc = 'Be aware of caller contracts.'
categories = category.SECURITY
algorithm = algorithm.HEURISTIC
export default class selfdestruct implements AnalyzerModule {
name: string = 'Selfdestruct: '
description: string = 'Be aware of caller contracts.'
category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.HEURISTIC
abstractAst = new AbstractAst()
visit = this.abstractAst.build_visit(
(node) => isStatement(node) ||
(node: AstNodeLegacy) => isStatement(node) ||
isSelfdestructCall(node)
)
report = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName) {
const warnings: any[] = []
private _report (contracts, multipleContractsWithSameName): ReportObj[] {
const warnings: ReportObj[] = []
contracts.forEach((contract) => {
contract.functions.forEach((func) => {

@ -4,22 +4,22 @@ import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { get } from 'fast-levenshtein'
import { util } from 'remix-lib'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class similarVariableNames {
name = 'Similar variable names: '
desc = 'Check if variable names are too similar'
abstractAst = new AbstractAst()
categories = category.MISC
algorithm = algorithm.EXACT
export default class similarVariableNames implements AnalyzerModule {
name: string = 'Similar variable names: '
description: string = 'Check if variable names are too similar'
category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
visit = this.abstractAst.build_visit(
(node) => false
)
abstractAst:AbstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node: AstNodeLegacy) => false)
report = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName) {
const warnings: any[] = []
private _report (contracts, multipleContractsWithSameName): ReportObj[] {
const warnings: ReportObj[] = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
contracts.forEach((contract) => {
@ -48,29 +48,29 @@ export default class similarVariableNames {
return warnings
}
private findSimilarVarNames (vars) {
const similar: any[] = []
const comb = {}
private findSimilarVarNames (vars: string[]): Record<string, any>[] {
const similar: Record<string, any>[] = []
const comb: Record<string, boolean> = {}
vars.map((varName1) => vars.map((varName2) => {
if (varName1.length > 1 && varName2.length > 1 && varName2 !== varName1 && !this.isCommonPrefixedVersion(varName1, varName2) && !this.isCommonNrSuffixVersion(varName1, varName2) && !(comb[varName1 + ';' + varName2] || comb[varName2 + ';' + varName1])) {
comb[varName1 + ';' + varName2] = true
const distance = get(varName1, varName2)
const distance: number = get(varName1, varName2)
if (distance <= 2) similar.push({ var1: varName1, var2: varName2, distance: distance })
}
}))
return similar
}
private isCommonPrefixedVersion (varName1, varName2) {
private isCommonPrefixedVersion (varName1: string, varName2: string): boolean {
return (varName1.startsWith('_') && varName1.slice(1) === varName2) || (varName2.startsWith('_') && varName2.slice(1) === varName1)
}
private isCommonNrSuffixVersion (varName1, varName2) {
const ref = '^' + util.escapeRegExp(varName1.slice(0, -1)) + '[0-9]*$'
private isCommonNrSuffixVersion (varName1: string, varName2: string): boolean {
const ref: string = '^' + util.escapeRegExp(varName1.slice(0, -1)) + '[0-9]*$'
return varName2.match(ref) != null
}
private getFunctionVariables (contract, func) {
private getFunctionVariables (contract, func): string[] {
return contract.stateVariables.concat(func.localVariables)
}
}

@ -1,21 +1,24 @@
import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
import { isStringToBytesConversion, isBytesLengthCheck } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class stringBytesLength {
name = 'String Length: '
desc = 'Bytes length != String length'
categories = category.MISC
export default class stringBytesLength implements AnalyzerModule {
name: string = 'String Length: '
description: string = 'Bytes length != String length'
category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
stringToBytesConversions: any[] = []
bytesLengthChecks: any[] = []
stringToBytesConversions: AstNodeLegacy[] = []
bytesLengthChecks: AstNodeLegacy[] = []
visit (node) {
visit (node: AstNodeLegacy): void {
if (isStringToBytesConversion(node)) this.stringToBytesConversions.push(node)
else if (isBytesLengthCheck(node)) this.bytesLengthChecks.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) {
return [{
warning: 'Bytes and string length are not the same since strings are assumed to be UTF-8 encoded (according to the ABI defintion) therefore one character is not nessesarily encoded in one byte of data.',

@ -1,19 +1,20 @@
import { default as category } from './categories'
import { isThisLocalCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class thisLocal {
warningNodes: any[] = []
name = 'This on local calls: '
desc = 'Invocation of local functions via this'
categories = category.GAS
algorithm = algorithm.EXACT
export default class thisLocal implements AnalyzerModule {
warningNodes: AstNodeLegacy[] = []
name: string = 'This on local calls: '
description: string = 'Invocation of local functions via this'
category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
visit (node: AstNodeLegacy): void {
if (isThisLocalCall(node)) this.warningNodes.push(node)
}
report (compilationResults) {
report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNodes.map(function (item, i) {
return {
warning: 'Use of "this" for local functions: Never use this to call functions in the same contract, it only consumes more gas than normal local calls.',

@ -1,25 +1,26 @@
import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class txOrigin {
txOriginNodes: any[] = []
name = 'Transaction origin: '
desc = 'Warn if tx.origin is used'
categories = category.SECURITY
algorithm = algorithm.EXACT
export default class txOrigin implements AnalyzerModule {
txOriginNodes: AstNodeLegacy[] = []
name: string = 'Transaction origin: '
description: string = 'Warn if tx.origin is used'
category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) {
if (node.name === 'MemberAccess' &&
visit (node: AstNodeLegacy): void {
if (node.name === 'MemberAccess' && node.attributes &&
node.attributes.member_name === 'origin' &&
(node.attributes.type === 'address' || node.attributes.type === 'address payable') &&
node.children && node.children.length &&
node.children && node.children.length && node.children[0].attributes &&
node.children[0].attributes.type === 'tx' &&
node.children[0].attributes.value === 'tx') {
this.txOriginNodes.push(node)
}
}
report () {
report (compilationResults: CompilationResult): ReportObj[] {
return this.txOriginNodes.map((item, i) => {
return {
warning: `Use of tx.origin: "tx.origin" is useful only in very exceptional cases.

@ -0,0 +1,303 @@
export interface AnalyzerModule {
name: string,
description: string,
category: ModuleCategory
algorithm: ModuleAlgorithm
visit: Function
report: Function
}
export interface ModuleAlgorithm {
hasFalsePositives: boolean,
hasFalseNegatives: boolean,
id: string
}
export interface ModuleCategory {
displayName: string,
id: string
}
export interface ReportObj {
warning: string,
location?: string | null,
more?: string
}
export interface CompilationResult {
error?: CompilationError,
/** not present if no errors/warnings were encountered */
errors?: CompilationError[]
/** This contains the file-level outputs. In can be limited/filtered by the outputSelection settings */
sources?: {
[contractName: string]: CompilationSource
}
/** This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings */
contracts?: {
/** If the language used has no contract names, this field should equal to an empty string. */
[fileName: string]: {
[contract: string]: CompiledContract
}
}
}
///////////
// ERROR //
///////////
export interface CompilationError {
/** Location within the source file */
sourceLocation?: {
file: string
start: number
end: number
}
/** Error type */
type?: CompilationErrorType
/** Component where the error originated, such as "general", "ewasm", etc. */
component?: 'general' | 'ewasm' | string
severity?: 'error' | 'warning'
message?: string
mode?: 'panic'
/** the message formatted with source location */
formattedMessage?: string
}
type CompilationErrorType =
| 'JSONError'
| 'IOError'
| 'ParserError'
| 'DocstringParsingError'
| 'SyntaxError'
| 'DeclarationError'
| 'TypeError'
| 'UnimplementedFeatureError'
| 'InternalCompilerError'
| 'Exception'
| 'CompilerError'
| 'FatalError'
| 'Warning'
////////////
// SOURCE //
////////////
export interface CompilationSource {
/** Identifier of the source (used in source maps) */
id: number
/** The AST object */
ast: AstNode
/** The legacy AST object */
legacyAST: AstNodeLegacy
}
/////////
// AST //
/////////
export interface AstNode {
absolutePath?: string
exportedSymbols?: Object
id: number
nodeType: string
nodes?: Array<AstNode>
src: string
literals?: Array<string>
file?: string
scope?: number
sourceUnit?: number
symbolAliases?: Array<string>
[x: string]: any
}
export interface AstNodeLegacy {
id: number
name: string
src: string
children?: Array<AstNodeLegacy>
attributes?: AstNodeAtt
}
export interface AstNodeAtt {
operator?: string
string?: null
type?: string
value?: string
constant?: boolean
name?: string
public?: boolean
exportedSymbols?: Object
argumentTypes?: null
absolutePath?: string
[x: string]: any
}
//////////////
// CONTRACT //
//////////////
export interface CompiledContract {
/** The Ethereum Contract ABI. If empty, it is represented as an empty array. */
abi: ABIDescription[]
// See the Metadata Output documentation (serialised JSON string)
metadata: string
/** User documentation (natural specification) */
userdoc: UserDocumentation
/** Developer documentation (natural specification) */
devdoc: DeveloperDocumentation
/** Intermediate representation (string) */
ir: string
/** EVM-related outputs */
evm: {
assembly: string
legacyAssembly: {}
/** Bytecode and related details. */
bytecode: BytecodeObject
deployedBytecode: BytecodeObject
/** The list of function hashes */
methodIdentifiers: {
[functionIdentifier: string]: string
}
// Function gas estimates
gasEstimates: {
creation: {
codeDepositCost: string
executionCost: 'infinite' | string
totalCost: 'infinite' | string
}
external: {
[functionIdentifier: string]: string
}
internal: {
[functionIdentifier: string]: 'infinite' | string
}
}
}
/** eWASM related outputs */
ewasm: {
/** S-expressions format */
wast: string
/** Binary format (hex string) */
wasm: string
}
}
/////////
// ABI //
/////////
export type ABIDescription = FunctionDescription | EventDescription
export interface FunctionDescription {
/** Type of the method. default is 'function' */
type?: 'function' | 'constructor' | 'fallback' | 'receive'
/** The name of the function. Constructor and fallback function never have name */
name?: string
/** List of parameters of the method. Fallback function doesn’t have inputs. */
inputs?: ABIParameter[]
/** List of the outputs parameters for the method, if any */
outputs?: ABIParameter[]
/** State mutability of the method */
stateMutability: 'pure' | 'view' | 'nonpayable' | 'payable'
/** true if function accepts Ether, false otherwise. Default is false */
payable?: boolean
/** true if function is either pure or view, false otherwise. Default is false */
constant?: boolean
}
export interface EventDescription {
type: 'event'
name: string
inputs: ABIParameter &
{
/** true if the field is part of the log’s topics, false if it one of the log’s data segment. */
indexed: boolean
}[]
/** true if the event was declared as anonymous. */
anonymous: boolean
}
export interface ABIParameter {
/** The name of the parameter */
name: string
/** The canonical type of the parameter */
type: ABITypeParameter
/** Used for tuple types */
components?: ABIParameter[]
}
export type ABITypeParameter =
| 'uint'
| 'uint[]' // TODO : add <M>
| 'int'
| 'int[]' // TODO : add <M>
| 'address'
| 'address[]'
| 'bool'
| 'bool[]'
| 'fixed'
| 'fixed[]' // TODO : add <M>
| 'ufixed'
| 'ufixed[]' // TODO : add <M>
| 'bytes'
| 'bytes[]' // TODO : add <M>
| 'function'
| 'function[]'
| 'tuple'
| 'tuple[]'
| string // Fallback
///////////////////////////
// NATURAL SPECIFICATION //
///////////////////////////
// Userdoc
export interface UserDocumentation {
methods: UserMethodList
notice: string
}
export type UserMethodList = {
[functionIdentifier: string]: UserMethodDoc
} & {
'constructor'?: string
}
export interface UserMethodDoc {
notice: string
}
// Devdoc
export interface DeveloperDocumentation {
author: string
title: string
details: string
methods: DevMethodList
}
export interface DevMethodList {
[functionIdentifier: string]: DevMethodDoc
}
export interface DevMethodDoc {
author: string
details: string
return: string
params: {
[param: string]: string
}
}
//////////////
// BYTECODE //
//////////////
export interface BytecodeObject {
/** The bytecode as a hex string. */
object: string
/** Opcodes list */
opcodes: string
/** The source mapping as a string. See the source mapping definition. */
sourceMap: string
/** If given, this is an unlinked object. */
linkReferences?: {
[contractName: string]: {
/** Byte offsets into the bytecode. */
[library: string]: { start: number; length: number }[]
}
}
}
Loading…
Cancel
Save