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. 35
      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 { default as category } from './categories'
import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon' import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class assignAndCompare { export default class assignAndCompare implements AnalyzerModule {
warningNodes: any[] = [] warningNodes: AstNodeLegacy[] = []
name = 'Result not used: ' name: string = 'Result not used: '
description = 'The result of an operation was not used.' description: string = 'The result of an operation was not used.'
category = category.MISC category: ModuleCategory = category.MISC
algorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isSubScopeWithTopLevelUnAssignedBinOp(node)) getUnAssignedTopLevelBinOps(node).forEach((n) => this.warningNodes.push(n)) if (isSubScopeWithTopLevelUnAssignedBinOp(node)) getUnAssignedTopLevelBinOps(node).forEach((n) => this.warningNodes.push(n))
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNodes.map((item, i) => { return this.warningNodes.map((item, i) => {
return { return {
warning: 'A binary operation yields a value that is not used in the following. This is often caused by confusing assignment (=) and comparison (==).', 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 { default as category } from './categories'
import { isBlockBlockHashAccess } from './staticAnalysisCommon' import { isBlockBlockHashAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class blockBlockhash { export default class blockBlockhash implements AnalyzerModule {
warningNodes: any[] = [] warningNodes: AstNodeLegacy[] = []
name = 'Block.blockhash usage: ' name: string = 'Block.blockhash usage: '
desc = 'Semantics maybe unclear' description: string = 'Semantics maybe unclear'
categories = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isBlockBlockHashAccess(node)) this.warningNodes.push(node) if (isBlockBlockHashAccess(node)) this.warningNodes.push(node)
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNodes.map((item, i) => { return this.warningNodes.map((item, i) => {
return { return {
warning: `use of "block.blockhash": "block.blockhash" is used to access the last 256 block hashes. 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 { default as category } from './categories'
import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon' import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class blockTimestamp { export default class blockTimestamp implements AnalyzerModule {
warningNowNodes: any[] = [] warningNowNodes: AstNodeLegacy[] = []
warningblockTimestampNodes: any[] = [] warningblockTimestampNodes: AstNodeLegacy[] = []
name = 'Block timestamp: ' name: string = 'Block timestamp: '
desc = 'Semantics maybe unclear' description: string = 'Semantics maybe unclear'
categories = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isNowAccess(node)) this.warningNowNodes.push(node) if (isNowAccess(node)) this.warningNowNodes.push(node)
else if (isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node) else if (isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node)
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNowNodes.map((item, i) => { return this.warningNowNodes.map((item, i) => {
return { return {
warning: `use of "now": "now" does not mean current time. Now is an alias for block.timestamp. 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 { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class checksEffectsInteraction { export default class checksEffectsInteraction implements AnalyzerModule {
name: string = 'Check effects: '
description: string = 'Avoid potential reentrancy bugs'
category: ModuleCategory = category.SECURITY
algorithm: ModuleAlgorithm = algorithm.HEURISTIC
name = 'Check effects: ' abstractAst: AbstractAst = new AbstractAst()
desc = 'Avoid potential reentrancy bugs'
categories = category.SECURITY
algorithm = algorithm.HEURISTIC
abstractAst = new AbstractAst() visit = this.abstractAst.build_visit((node: AstNodeLegacy) => isInteraction(node) || isEffect(node) || isLocalCallGraphRelevantNode(node))
visit = this.abstractAst.build_visit((node) => isInteraction(node) || isEffect(node) || isLocalCallGraphRelevantNode(node))
report = this.abstractAst.build_report(this._report.bind(this)) report = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName) { private _report (contracts, multipleContractsWithSameName): ReportObj[] {
const warnings: any[] = [] const warnings: ReportObj[] = []
const hasModifiers = contracts.some((item) => item.modifiers.length > 0) const hasModifiers = contracts.some((item) => item.modifiers.length > 0)
const callGraph = buildGlobalFuncCallGraph(contracts) const callGraph = buildGlobalFuncCallGraph(contracts)
contracts.forEach((contract) => { contracts.forEach((contract) => {

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

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

@ -1,17 +1,20 @@
import { default as category } from './categories' import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon' import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class deleteFromDynamicArray { export default class deleteFromDynamicArray implements AnalyzerModule {
relevantNodes: any[] = [] relevantNodes: AstNodeLegacy[] = []
name = 'Delete from dynamic Array: ' name: string = 'Delete from dynamic Array: '
desc = 'Using delete on an array leaves a gap' description: string = 'Using delete on an array leaves a gap'
categories = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isDeleteFromDynamicArray(node) && !isMappingIndexAccess(node.children[0])) this.relevantNodes.push(node) 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 this.relevantNodes.map((node) => {
return { return {
warning: 'Using delete on an array leaves a gap. The length of the array remains the same. If you want to remove the empty position you need to shift items manually and update the length property.\n', 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 { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class erc20Decimals { export default class erc20Decimals implements AnalyzerModule {
name = 'ERC20: ' name: string = 'ERC20: '
desc = 'Decimal should be uint8' description: string = 'Decimal should be uint8'
categories = category.ERC category: ModuleCategory = category.ERC
algorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
abstractAst = new AbstractAst() abstractAst: AbstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node) => false) visit = this.abstractAst.build_visit((node: AstNodeLegacy) => false)
report = this.abstractAst.build_report(this._report.bind(this)) report = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName) { private _report (contracts, multipleContractsWithSameName): ReportObj[] {
const warnings: any = [] const warnings: ReportObj[] = []
contracts.forEach((contract) => { contracts.forEach((contract) => {
const contractAbiSignatures = contract.functions.map((f) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters)) const contractAbiSignatures = contract.functions.map((f) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters))
@ -41,7 +42,7 @@ export default class erc20Decimals {
return warnings return warnings
} }
private isERC20 (funSignatures) { private isERC20 (funSignatures: string[]): boolean {
return funSignatures.includes('totalSupply()') && return funSignatures.includes('totalSupply()') &&
funSignatures.includes('balanceOf(address)') && funSignatures.includes('balanceOf(address)') &&
funSignatures.includes('transfer(address,uint256)') && funSignatures.includes('transfer(address,uint256)') &&

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

@ -1,20 +1,26 @@
import { default as category } from './categories' 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 { export default class forLoopIteratesOverDynamicArray implements AnalyzerModule {
relevantNodes: any[] = [] relevantNodes: AstNodeLegacy[] = []
name = 'For loop iterates over dynamic array: ' name: string = 'For loop iterates over dynamic array: '
desc = 'The number of \'for\' loop iterations depends on dynamic array\'s size' description: string = 'The number of \'for\' loop iterations depends on dynamic array\'s size'
categories = category.GAS category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isForLoop(node)) { if (isForLoop(node) && node.children) {
let conditionChildrenNode: AstNodeLegacy | null = null
// Access 'condition' node of 'for' loop statement // 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 // 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 // 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) this.relevantNodes.push(node)
} else if (isDynamicArrayLengthAccess(conditionChildrenNode)) { // else check if condition node itself access length of dynamic array } else if (isDynamicArrayLengthAccess(conditionChildrenNode)) { // else check if condition node itself access length of dynamic array
this.relevantNodes.push(node) this.relevantNodes.push(node)
@ -22,7 +28,7 @@ export default class forLoopIteratesOverDynamicArray {
} }
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
return this.relevantNodes.map((node) => { return this.relevantNodes.map((node) => {
return { return {
warning: 'Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. Additionally, using unbounded loops incurs in a lot of avoidable gas costs. Carefully test how many items at maximum you can pass to such functions to make it successful.', warning: 'Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. 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 { default as category } from './categories'
import { isRequireCall, isAssertCall } from './staticAnalysisCommon' import { isRequireCall, isAssertCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class guardConditions { export default class guardConditions implements AnalyzerModule {
guards: any[] = [] guards: AstNodeLegacy[] = []
name = 'Guard Conditions: ' name: string = 'Guard Conditions: '
desc = 'Use require and appropriately' description: string = 'Use require and appropriately'
categories = category.MISC category: ModuleCategory = category.MISC
algorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isRequireCall(node) || isAssertCall(node)) this.guards.push(node) if (isRequireCall(node) || isAssertCall(node)) this.guards.push(node)
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
if (this.guards.length > 0) { if (this.guards.length > 0) {
return [{ return [{
warning: 'Use assert(x) if you never ever want x to be false, not in any circumstance (apart from a bug in your code). Use require(x) if x can be false, due to e.g. invalid input or a failing external component.', warning: 'Use assert(x) if you never ever want x to be false, not in any circumstance (apart from a bug in your code). Use require(x) if x can be false, due to e.g. invalid input or a failing external component.',

@ -1,19 +1,20 @@
import { default as category } from './categories' import { default as category } from './categories'
import { isInlineAssembly } from './staticAnalysisCommon' import { isInlineAssembly } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class inlineAssembly { export default class inlineAssembly implements AnalyzerModule {
inlineAssNodes: any[] = [] inlineAssNodes: AstNodeLegacy[] = []
name = 'Inline assembly: ' name: string = 'Inline assembly: '
desc = 'Use of Inline Assembly' description: string = 'Use of Inline Assembly'
categories = category.SECURITY category: ModuleCategory = category.SECURITY
algorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isInlineAssembly(node)) this.inlineAssNodes.push(node) if (isInlineAssembly(node)) this.inlineAssNodes.push(node)
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
return this.inlineAssNodes.map((node) => { return this.inlineAssNodes.map((node) => {
return { return {
warning: `CAUTION: The Contract uses inline assembly, this is only advised in rare cases. 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 { default as category } from './categories'
import { isIntDivision } from './staticAnalysisCommon' import { isIntDivision } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class intDivitionTruncate { export default class intDivisionTruncate implements AnalyzerModule {
warningNodes: any[] = [] warningNodes: AstNodeLegacy[] = []
name = 'Data Trucated: ' name: string = 'Data Truncated: '
desc = 'Division on int/uint values truncates the result.' description: string = 'Division on int/uint values truncates the result.'
categories = category.MISC category: ModuleCategory = category.MISC
algorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isIntDivision(node)) this.warningNodes.push(node) if (isIntDivision(node)) this.warningNodes.push(node)
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNodes.map((item, i) => { return this.warningNodes.map((item, i) => {
return { 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.', 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, import { isLowLevelCallInst, isLowLevelCallInst050, isLowLevelCallcodeInst, isLowLevelDelegatecallInst,
isLowLevelSendInst, isLowLevelSendInst050, isLLDelegatecallInst050, lowLevelCallTypes } from './staticAnalysisCommon' isLowLevelSendInst, isLowLevelSendInst050, isLLDelegatecallInst050, lowLevelCallTypes } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class lowLevelCalls { interface llcNode {
llcNodes: any[] = [] node: AstNodeLegacy
name = 'Low level calls: ' type: {
desc = 'Semantics maybe unclear' ident: string,
categories = category.SECURITY type: string
algorithm = algorithm.EXACT }
}
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)) { if (isLowLevelCallInst(node)) {
this.llcNodes.push({node: node, type: lowLevelCallTypes.CALL}) this.llcNodes.push({node: node, type: lowLevelCallTypes.CALL})
} else if (isLowLevelCallInst050(node)) { } else if (isLowLevelCallInst050(node)) {
@ -28,7 +37,7 @@ export default class lowLevelCalls {
} }
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
return this.llcNodes.map((item, i) => { return this.llcNodes.map((item, i) => {
let text = '' let text = ''
let morehref: any = null let morehref: any = null

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

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

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

@ -1,21 +1,24 @@
import { default as category } from './categories' import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
import { isStringToBytesConversion, isBytesLengthCheck } from './staticAnalysisCommon' import { isStringToBytesConversion, isBytesLengthCheck } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class stringBytesLength { export default class stringBytesLength implements AnalyzerModule {
name = 'String Length: ' name: string = 'String Length: '
desc = 'Bytes length != String length' description: string = 'Bytes length != String length'
categories = category.MISC category: ModuleCategory = category.MISC
algorithm: ModuleAlgorithm = algorithm.EXACT
stringToBytesConversions: any[] = [] stringToBytesConversions: AstNodeLegacy[] = []
bytesLengthChecks: any[] = [] bytesLengthChecks: AstNodeLegacy[] = []
visit (node) { visit (node: AstNodeLegacy): void {
if (isStringToBytesConversion(node)) this.stringToBytesConversions.push(node) if (isStringToBytesConversion(node)) this.stringToBytesConversions.push(node)
else if (isBytesLengthCheck(node)) this.bytesLengthChecks.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) { if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) {
return [{ 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.', 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 { default as category } from './categories'
import { isThisLocalCall } from './staticAnalysisCommon' import { isThisLocalCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
export default class thisLocal { export default class thisLocal implements AnalyzerModule {
warningNodes: any[] = [] warningNodes: AstNodeLegacy[] = []
name = 'This on local calls: ' name: string = 'This on local calls: '
desc = 'Invocation of local functions via this' description: string = 'Invocation of local functions via this'
categories = category.GAS category: ModuleCategory = category.GAS
algorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node) { visit (node: AstNodeLegacy): void {
if (isThisLocalCall(node)) this.warningNodes.push(node) if (isThisLocalCall(node)) this.warningNodes.push(node)
} }
report (compilationResults) { report (compilationResults: CompilationResult): ReportObj[] {
return this.warningNodes.map(function (item, i) { return this.warningNodes.map(function (item, i) {
return { return {
warning: 'Use of "this" for local functions: Never use this to call functions in the same contract, it only consumes more gas than normal local calls.', warning: 'Use of "this" for local functions: Never use this to call functions in the same contract, it only consumes more gas than normal local calls.',

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