diff --git a/libs/remix-analyzer/.eslintrc b/libs/remix-analyzer/.eslintrc index 71cbe7d7cd..cc6cabcbcd 100644 --- a/libs/remix-analyzer/.eslintrc +++ b/libs/remix-analyzer/.eslintrc @@ -2,7 +2,9 @@ "extends": "../../.eslintrc", "rules": { "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off" + "@typescript-eslint/no-unused-vars": "off", + "no-unused-vars": "off", + "dot-notation": "off" }, "ignorePatterns": ["!**/*"] } diff --git a/libs/remix-analyzer/src/index.ts b/libs/remix-analyzer/src/index.ts index 2639abeec9..38fadbab93 100644 --- a/libs/remix-analyzer/src/index.ts +++ b/libs/remix-analyzer/src/index.ts @@ -1 +1 @@ -export { default as CodeAnalysis} from './solidity-analyzer' +export { default as CodeAnalysis } from './solidity-analyzer' diff --git a/libs/remix-analyzer/src/solidity-analyzer/index.ts b/libs/remix-analyzer/src/solidity-analyzer/index.ts index b2ebd62d77..da6b5bb35e 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/index.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/index.ts @@ -9,7 +9,6 @@ type ModuleObj = { } export default class staticAnalysisRunner { - /** * Run analysis (Used by IDE) * @param compilationResult contract compilation result @@ -18,9 +17,9 @@ export default class staticAnalysisRunner { */ run (compilationResult: CompilationResult, toRun: number[], callback: ((reports: AnalysisReport[]) => void)): void { const modules: ModuleObj[] = toRun.map((i) => { - const module = this.modules()[i] - const m = new module() - return { 'name': m.name, 'mod': m } + const Module = this.modules()[i] + const m = new Module() + return { name: m.name, mod: m } }) this.runWithModuleList(compilationResult, modules, callback) } @@ -36,21 +35,21 @@ export default class staticAnalysisRunner { // Also provide convenience analysis via the AST walker. const walker = new AstWalker() for (const k in compilationResult.sources) { - walker.walkFull(compilationResult.sources[k].ast, + walker.walkFull(compilationResult.sources[k].ast, (node: any) => { - modules.map((item: ModuleObj) => { - if (item.mod.visit !== undefined) { - try { - item.mod.visit(node) - } catch (e) { - reports.push({ - name: item.name, report: [{ warning: 'INTERNAL ERROR in module ' + item.name + ' ' + e.message, error: e.stack }] - }) + modules.map((item: ModuleObj) => { + if (item.mod.visit !== undefined) { + try { + item.mod.visit(node) + } catch (e) { + reports.push({ + name: item.name, report: [{ warning: 'INTERNAL ERROR in module ' + item.name + ' ' + e.message, error: e.stack }] + }) + } } - } - }) - return true - } + }) + return true + } ) } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts index e6a7ac20af..0febc41dc1 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts @@ -1,9 +1,13 @@ -import { getStateVariableDeclarationsFromContractNode, getInheritsFromName, getContractName, - getFunctionOrModifierDefinitionParameterPart, getType, getDeclaredVariableName, - getFunctionDefinitionReturnParameterPart, getCompilerVersion } from './staticAnalysisCommon' +import { + getStateVariableDeclarationsFromContractNode, getInheritsFromName, getContractName, + getFunctionOrModifierDefinitionParameterPart, getType, getDeclaredVariableName, + getFunctionDefinitionReturnParameterPart, getCompilerVersion +} from './staticAnalysisCommon' import { AstWalker } from '@remix-project/remix-astwalker' -import { FunctionDefinitionAstNode, ParameterListAstNode, ModifierDefinitionAstNode, ContractHLAst, VariableDeclarationAstNode, - FunctionHLAst, ReportObj, ReportFunction, VisitFunction, ModifierHLAst, CompilationResult } from '../../types' +import { + FunctionDefinitionAstNode, ParameterListAstNode, ModifierDefinitionAstNode, ContractHLAst, VariableDeclarationAstNode, + FunctionHLAst, ReportObj, ReportFunction, VisitFunction, ModifierHLAst, CompilationResult +} from '../../types' type WrapFunction = ((contracts: ContractHLAst[], isSameName: boolean, version: string) => ReportObj[]) @@ -23,7 +27,7 @@ export default class abstractAstView { */ multipleContractsWithSameName = false -/** + /** * Builds a higher level AST view. I creates a list with each contract as an object in it. * Example contractsOut: * @@ -48,9 +52,10 @@ export default class abstractAstView { * @contractsOut {list} return list for high level AST view * @return {ASTNode -> void} returns a function that can be used as visit function for static analysis modules, to build up a higher level AST view for further analysis. */ + // eslint-disable-next-line camelcase build_visit (relevantNodeFilter: ((node:any) => boolean)): VisitFunction { return (node: any) => { - if (node.nodeType === "ContractDefinition") { + if (node.nodeType === 'ContractDefinition') { this.setCurrentContract({ node: node, functions: [], @@ -59,11 +64,11 @@ export default class abstractAstView { inheritsFrom: [], stateVariables: getStateVariableDeclarationsFromContractNode(node) }) - } else if (node.nodeType === "InheritanceSpecifier") { + } else if (node.nodeType === 'InheritanceSpecifier') { const currentContract: ContractHLAst = this.getCurrentContract() const inheritsFromName: string = getInheritsFromName(node) currentContract.inheritsFrom.push(inheritsFromName) - } else if (node.nodeType === "FunctionDefinition") { + } else if (node.nodeType === 'FunctionDefinition') { this.setCurrentFunction({ node: node, relevantNodes: [], @@ -78,14 +83,14 @@ export default class abstractAstView { this.getCurrentFunction().relevantNodes.push(item.node) } }) - } else if (node.nodeType === "ModifierDefinition") { + } else if (node.nodeType === 'ModifierDefinition') { this.setCurrentModifier({ node: node, relevantNodes: [], localVariables: this.getLocalVariables(node), parameters: this.getLocalParameters(node) }) - } else if (node.nodeType === "ModifierInvocation") { + } else if (node.nodeType === 'ModifierInvocation') { if (!this.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.') this.getCurrentFunction().modifierInvocations.push(node) } else if (relevantNodeFilter(node)) { @@ -102,6 +107,7 @@ export default class abstractAstView { } } + // eslint-disable-next-line camelcase build_report (wrap: WrapFunction): ReportFunction { // eslint-disable-next-line @typescript-eslint/no-unused-vars return (compilationResult: CompilationResult) => { @@ -176,7 +182,7 @@ export default class abstractAstView { private getLocalVariables (funcNode: ParameterListAstNode): VariableDeclarationAstNode[] { const locals: VariableDeclarationAstNode[] = [] new AstWalker().walkFull(funcNode, (node: any) => { - if (node.nodeType === "VariableDeclaration") locals.push(node) + if (node.nodeType === 'VariableDeclaration') locals.push(node) return true }) return locals diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts index 44525ec2e9..1abca17307 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts @@ -1,13 +1,15 @@ -import { default as category } from './categories' +import category from './categories' import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, BlockAstNode, IfStatementAstNode, - WhileStatementAstNode, ForStatementAstNode, CompilationResult, ExpressionStatementAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import { + AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, BlockAstNode, IfStatementAstNode, + WhileStatementAstNode, ForStatementAstNode, CompilationResult, ExpressionStatementAstNode, SupportedVersion +} from './../../types' export default class assignAndCompare implements AnalyzerModule { warningNodes: ExpressionStatementAstNode[] = [] - name = `Result not used: ` - description = `The result of an operation not used` + name = 'Result not used: ' + description = 'The result of an operation not used' category: ModuleCategory = category.MISC algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts index 142c04ed73..714e7f62dd 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' +import category from './categories' import { isBlockBlockHashAccess } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, FunctionCallAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, FunctionCallAstNode, SupportedVersion } from './../../types' export default class blockBlockhash implements AnalyzerModule { warningNodes: FunctionCallAstNode[] = [] - name = `Block hash: ` - description = `Can be influenced by miners` + name = 'Block hash: ' + description = 'Can be influenced by miners' category: ModuleCategory = category.SECURITY algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -30,4 +30,3 @@ export default class blockBlockhash implements AnalyzerModule { }) } } - diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts index a71c650105..be78b7e90b 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts @@ -1,23 +1,25 @@ -import { default as category } from './categories' +import category from './categories' import { isNowAccess, isBlockTimestampAccess, getCompilerVersion } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, IdentifierAstNode, - MemberAccessAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import { + AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, IdentifierAstNode, + MemberAccessAstNode, SupportedVersion +} from './../../types' export default class blockTimestamp implements AnalyzerModule { warningNowNodes: IdentifierAstNode[] = [] warningblockTimestampNodes: MemberAccessAstNode[] = [] - name = `Block timestamp: ` - description = `Can be influenced by miners` + name = 'Block timestamp: ' + description = 'Can be influenced by miners' category: ModuleCategory = category.SECURITY algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { start: '0.4.12' } - visit (node: IdentifierAstNode | MemberAccessAstNode ): void { - if (node.nodeType === "Identifier" && isNowAccess(node)) this.warningNowNodes.push(node) - else if (node.nodeType === "MemberAccess" && isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node) + visit (node: IdentifierAstNode | MemberAccessAstNode): void { + if (node.nodeType === 'Identifier' && isNowAccess(node)) this.warningNowNodes.push(node) + else if (node.nodeType === 'MemberAccess' && isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node) } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/categories.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/categories.ts index 478c355693..855d691b8c 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/categories.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/categories.ts @@ -1,6 +1,6 @@ export default { - SECURITY: {displayName: 'Security', id: 'SEC'}, - GAS: {displayName: 'Gas & Economy', id: 'GAS'}, - MISC: {displayName: 'Miscellaneous', id: 'MISC'}, - ERC: {displayName: 'ERC', id: 'ERC'} + SECURITY: { displayName: 'Security', id: 'SEC' }, + GAS: { displayName: 'Gas & Economy', id: 'GAS' }, + MISC: { displayName: 'Miscellaneous', id: 'MISC' }, + ERC: { displayName: 'ERC', id: 'ERC' } } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts index 41bf539ce9..e0cf9adac6 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts @@ -1,16 +1,20 @@ -import { default as category } from './categories' -import { isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfiedFuncDefinitionIdent, - isWriteOnStateVariable, isStorageVariableDeclaration, getFullQualifiedFunctionCallIdent, getCompilerVersion } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' +import category from './categories' +import { + isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfiedFuncDefinitionIdent, + isWriteOnStateVariable, isStorageVariableDeclaration, getFullQualifiedFunctionCallIdent, getCompilerVersion +} from './staticAnalysisCommon' +import algorithm from './algorithmCategories' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' -import AbstractAst from './abstractAstView' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VariableDeclarationAstNode, - FunctionHLAst, ContractCallGraph, Context, FunctionCallAstNode, AssignmentAstNode, UnaryOperationAstNode, - InlineAssemblyAstNode, ReportFunction, VisitFunction, FunctionCallGraph, SupportedVersion } from './../../types' +import AbstractAst from './abstractAstView' +import { + AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VariableDeclarationAstNode, + FunctionHLAst, ContractCallGraph, Context, FunctionCallAstNode, AssignmentAstNode, UnaryOperationAstNode, + InlineAssemblyAstNode, ReportFunction, VisitFunction, FunctionCallGraph, SupportedVersion +} from './../../types' export default class checksEffectsInteraction implements AnalyzerModule { - name = `Check-effects-interaction: ` - description = `Potential reentrancy bugs` + name = 'Check-effects-interaction: ' + description = 'Potential reentrancy bugs' category: ModuleCategory = category.SECURITY algorithm: ModuleAlgorithm = algorithm.HEURISTIC version: SupportedVersion = { @@ -20,11 +24,11 @@ export default class checksEffectsInteraction implements AnalyzerModule { abstractAst: AbstractAst = new AbstractAst() visit: VisitFunction = this.abstractAst.build_visit((node: FunctionCallAstNode | AssignmentAstNode | UnaryOperationAstNode | InlineAssemblyAstNode) => ( - node.nodeType === 'FunctionCall' && (isInteraction(node) || isLocalCallGraphRelevantNode(node))) || + node.nodeType === 'FunctionCall' && (isInteraction(node) || isLocalCallGraphRelevantNode(node))) || ((node.nodeType === 'Assignment' || node.nodeType === 'UnaryOperation' || node.nodeType === 'InlineAssembly') && isEffect(node))) report: ReportFunction = this.abstractAst.build_report(this._report.bind(this)) - + private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean, version: string): ReportObj[] { const warnings: ReportObj[] = [] const hasModifiers: boolean = contracts.some((item) => item.modifiers.length > 0) @@ -32,16 +36,16 @@ export default class checksEffectsInteraction implements AnalyzerModule { contracts.forEach((contract) => { contract.functions.forEach((func) => { func['changesState'] = this.checkIfChangesState( - getFullQuallyfiedFuncDefinitionIdent( - contract.node, - func.node, - func.parameters - ), - this.getContext( - callGraph, - contract, - func) - ) + getFullQuallyfiedFuncDefinitionIdent( + contract.node, + func.node, + func.parameters + ), + this.getContext( + callGraph, + contract, + func) + ) }) contract.functions.forEach((func: FunctionHLAst) => { if (this.isPotentialVulnerableFunction(func, this.getContext(callGraph, contract, func))) { @@ -50,7 +54,7 @@ export default class checksEffectsInteraction implements AnalyzerModule { comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : '' warnings.push({ warning: `Potential violation of Checks-Effects-Interaction pattern in ${funcName}: Could potentially lead to re-entrancy vulnerability. ${comments}`, - location: func.node['src'], + location: func.node.src, more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#re-entrancy` }) } @@ -92,4 +96,3 @@ export default class checksEffectsInteraction implements AnalyzerModule { return analyseCallGraph(context.callGraph, startFuncName, context, (node: any, context: Context) => isWriteOnStateVariable(node, context.stateVariables)) } } - diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts index ac6fac6690..abbdb91807 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts @@ -1,17 +1,21 @@ -import { default as category } from './categories' -import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode, isSelfdestructCall, - isDeleteUnaryOperation, isPayableFunction, isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody, - isConstantFunction, isWriteOnStateVariable, isStorageVariableDeclaration, isCallToNonConstLocalFunction, - getFullQualifiedFunctionCallIdent} from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' +import category from './categories' +import { + isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode, isSelfdestructCall, + isDeleteUnaryOperation, isPayableFunction, isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody, + isConstantFunction, isWriteOnStateVariable, isStorageVariableDeclaration, isCallToNonConstLocalFunction, + getFullQualifiedFunctionCallIdent +} from './staticAnalysisCommon' +import algorithm from './algorithmCategories' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' -import AbstractAst from './abstractAstView' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractCallGraph, Context, ContractHLAst, - FunctionHLAst, VariableDeclarationAstNode, FunctionCallGraph, FunctionCallAstNode, VisitFunction, ReportFunction, SupportedVersion} from './../../types' +import AbstractAst from './abstractAstView' +import { + AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractCallGraph, Context, ContractHLAst, + FunctionHLAst, VariableDeclarationAstNode, FunctionCallGraph, FunctionCallAstNode, VisitFunction, ReportFunction, SupportedVersion +} from './../../types' export default class constantFunctions implements AnalyzerModule { - name = `Constant/View/Pure functions: ` - description = `Potentially constant/view/pure functions` + name = 'Constant/View/Pure functions: ' + description = 'Potentially constant/view/pure functions' category: ModuleCategory = category.MISC algorithm: ModuleAlgorithm = algorithm.HEURISTIC version: SupportedVersion = { @@ -26,8 +30,8 @@ export default class constantFunctions implements AnalyzerModule { isExternalDirectCall(node) || isEffect(node) || isLocalCallGraphRelevantNode(node) || - node.nodeType === "InlineAssembly" || - node.nodeType === "NewExpression" || + node.nodeType === 'InlineAssembly' || + node.nodeType === 'NewExpression' || isSelfdestructCall(node) || isDeleteUnaryOperation(node) ) @@ -46,17 +50,17 @@ export default class constantFunctions implements AnalyzerModule { func['potentiallyshouldBeConst'] = false } else { func['potentiallyshouldBeConst'] = this.checkIfShouldBeConstant( - getFullQuallyfiedFuncDefinitionIdent( - contract.node, - func.node, - func.parameters - ), - this.getContext( - callGraph, - contract, - func - ) - ) + getFullQuallyfiedFuncDefinitionIdent( + contract.node, + func.node, + func.parameters + ), + this.getContext( + callGraph, + contract, + func + ) + ) } }) contract.functions.filter((func: FunctionHLAst) => hasFunctionBody(func.node)).forEach((func: FunctionHLAst) => { @@ -67,13 +71,13 @@ export default class constantFunctions implements AnalyzerModule { if (func['potentiallyshouldBeConst']) { warnings.push({ warning: `${funcName} : Potentially should be constant/view/pure but is not. ${comments}`, - location: func.node['src'], + location: func.node.src, more: `https://solidity.readthedocs.io/en/${version}/contracts.html#view-functions` }) } else { warnings.push({ warning: `${funcName} : Is constant but potentially should not be. ${comments}`, - location: func.node['src'], + location: func.node.src, more: `https://solidity.readthedocs.io/en/${version}/contracts.html#view-functions` }) } @@ -101,8 +105,8 @@ export default class constantFunctions implements AnalyzerModule { isTransfer(node) || this.isCallOnNonConstExternalInterfaceFunction(node, context) || isCallToNonConstLocalFunction(node) || - node.nodeType === "InlineAssembly" || - node.nodeType === "NewExpression" || + node.nodeType === 'InlineAssembly' || + node.nodeType === 'NewExpression' || isSelfdestructCall(node) || isDeleteUnaryOperation(node) } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts index b5c9831909..dd814b382e 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' +import category from './categories' import { isDeleteOfDynamicArray, getCompilerVersion } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, UnaryOperationAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, UnaryOperationAstNode, SupportedVersion } from './../../types' export default class deleteDynamicArrays implements AnalyzerModule { rel: UnaryOperationAstNode[] = [] - name = `Delete dynamic array: ` - description = `Use require/assert to ensure complete deletion` + name = 'Delete dynamic array: ' + description = 'Use require/assert to ensure complete deletion' category: ModuleCategory = category.GAS algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -22,7 +22,7 @@ export default class deleteDynamicArrays implements AnalyzerModule { const version = getCompilerVersion(compilationResults.contracts) 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.`, + warning: 'The "delete" operation when applied to a dynamically sized array in Solidity generates code to delete each of the elements contained. If the array is large, this operation can surpass the block gas limit and raise an OOG exception. Also nested dynamically sized objects can produce the same results.', location: node.src, more: `https://solidity.readthedocs.io/en/${version}/types.html#delete` } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts index c1db4da71b..714da95c65 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' -import { default as algorithm } from './algorithmCategories' +import category from './categories' +import algorithm from './algorithmCategories' import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, UnaryOperationAstNode, SupportedVersion} from './../../types' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, UnaryOperationAstNode, SupportedVersion } from './../../types' export default class deleteFromDynamicArray implements AnalyzerModule { relevantNodes: UnaryOperationAstNode[] = [] - name = `Delete from dynamic array: ` - description = `'delete' leaves a gap in array` + name = 'Delete from dynamic array: ' + description = '\'delete\' leaves a gap in array' category: ModuleCategory = category.MISC algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -21,7 +21,7 @@ export default class deleteFromDynamicArray implements AnalyzerModule { 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.`, + warning: 'Using "delete" on an array leaves a gap. The length of the array remains the same. If you want to remove the empty position you need to shift items manually and update the "length" property.', location: node.src, more: 'https://github.com/miguelmota/solidity-idiosyncrasies#examples' } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts index 24af05f215..c9264920c7 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts @@ -1,13 +1,15 @@ -import { default as category } from './categories' +import 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, VisitFunction, ReportFunction, ContractHLAst, - FunctionHLAst, VariableDeclarationAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import AbstractAst from './abstractAstView' +import { + AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, VisitFunction, ReportFunction, ContractHLAst, + FunctionHLAst, VariableDeclarationAstNode, SupportedVersion +} from './../../types' export default class erc20Decimals implements AnalyzerModule { - name = `ERC20: ` - description = `'decimals' should be 'uint8'` + name = 'ERC20: ' + description = '\'decimals\' should be \'uint8\'' category: ModuleCategory = category.ERC algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -34,11 +36,11 @@ export default class erc20Decimals implements AnalyzerModule { (f.returns.length === 0 || f.returns.length > 1) || (f.returns.length === 1 && (f.returns[0].type !== 'uint8' || f.node.visibility !== 'public')) ) - ) + ) if (decimalsVar.length > 0) { for (const node of decimalsVar) { warnings.push({ - warning: `ERC20 contract's "decimals" variable should be "uint8" type`, + warning: 'ERC20 contract\'s "decimals" variable should be "uint8" type', location: node.src, more: 'https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals' }) @@ -46,7 +48,7 @@ export default class erc20Decimals implements AnalyzerModule { } else if (decimalsFun.length > 0) { for (const fn of decimalsFun) { warnings.push({ - warning: `ERC20 contract's "decimals" function should have "uint8" as return type`, + warning: 'ERC20 contract\'s "decimals" function should have "uint8" as return type', location: fn.node.src, more: 'https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals' }) @@ -66,4 +68,3 @@ export default class erc20Decimals implements AnalyzerModule { funSignatures.includes('allowance(address,address)') } } - diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts index 3af43fa2be..a8f8ae1d55 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts @@ -1,40 +1,43 @@ -import { default as category } from './categories' -import { default as algorithm } from './algorithmCategories' +import category from './categories' +import algorithm from './algorithmCategories' import { isLoop, isTransfer, getCompilerVersion } from './staticAnalysisCommon' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode, - WhileStatementAstNode, ExpressionStatementAstNode, SupportedVersion} from './../../types' +import { + AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode, + WhileStatementAstNode, ExpressionStatementAstNode, SupportedVersion +} from './../../types' export default class etherTransferInLoop implements AnalyzerModule { relevantNodes: ExpressionStatementAstNode[] = [] - name = `Ether transfer in loop: ` - description = `Transferring Ether in a for/while/do-while loop` + name = 'Ether transfer in loop: ' + description = 'Transferring Ether in a for/while/do-while loop' category: ModuleCategory = category.GAS algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { start: '0.4.12' } - + visit (node: ForStatementAstNode | WhileStatementAstNode): void { - let transferNodes: ExpressionStatementAstNode[] = [] - if(isLoop(node)) { - if(node.body && node.body.nodeType === 'Block') - transferNodes = node.body.statements.filter(child => ( child.nodeType === 'ExpressionStatement' && - child.expression.nodeType === 'FunctionCall' && isTransfer(child.expression.expression))) - // When loop body is described without braces - else if(node.body && node.body.nodeType === 'ExpressionStatement' && node.body.expression.nodeType === 'FunctionCall' && isTransfer(node.body.expression.expression)) - transferNodes.push(node.body) - if (transferNodes.length > 0) { - this.relevantNodes.push(...transferNodes) - } + let transferNodes: ExpressionStatementAstNode[] = [] + if (isLoop(node)) { + if (node.body && node.body.nodeType === 'Block') { + transferNodes = node.body.statements.filter(child => + (child.nodeType === 'ExpressionStatement' && + child.expression.nodeType === 'FunctionCall' && + isTransfer(child.expression.expression))) + } else if (node.body && node.body.nodeType === 'ExpressionStatement' && node.body.expression.nodeType === 'FunctionCall' && isTransfer(node.body.expression.expression)) { transferNodes.push(node.body) } + // When loop body is described without braces + if (transferNodes.length > 0) { + this.relevantNodes.push(...transferNodes) } + } } - + // eslint-disable-next-line @typescript-eslint/no-unused-vars report (compilationResults: CompilationResult): ReportObj[] { const version = getCompilerVersion(compilationResults.contracts) 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.`, + warning: 'Ether payout should not be done in a loop: Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. If required then make sure that number of iterations are low and you trust each address involved.', location: node.src, more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#gas-limit-and-loops` } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts index f2f0c0dfcb..ff85469cf2 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' -import { default as algorithm } from './algorithmCategories' +import category from './categories' +import algorithm from './algorithmCategories' import { isDynamicArrayLengthAccess, getCompilerVersion } from './staticAnalysisCommon' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode, SupportedVersion} from './../../types' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode, SupportedVersion } from './../../types' export default class forLoopIteratesOverDynamicArray implements AnalyzerModule { relevantNodes: ForStatementAstNode[] = [] - name = `For loop over dynamic array: ` - description = `Iterations depend on dynamic array's size` + name = 'For loop over dynamic array: ' + description = 'Iterations depend on dynamic array\'s size' category: ModuleCategory = category.GAS algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -14,21 +14,21 @@ export default class forLoopIteratesOverDynamicArray implements AnalyzerModule { } visit (node: ForStatementAstNode): void { - const { condition } = node - // Check if condition is `i < array.length - 1` - if ((condition && condition.nodeType === "BinaryOperation" && condition.rightExpression.nodeType === "BinaryOperation" && isDynamicArrayLengthAccess(condition.rightExpression.leftExpression)) || + const { condition } = node + // Check if condition is `i < array.length - 1` + if ((condition && condition.nodeType === 'BinaryOperation' && condition.rightExpression.nodeType === 'BinaryOperation' && isDynamicArrayLengthAccess(condition.rightExpression.leftExpression)) || // or condition is `i < array.length` - (condition && condition.nodeType === "BinaryOperation" && isDynamicArrayLengthAccess(condition.rightExpression))) { - this.relevantNodes.push(node) - } + (condition && condition.nodeType === 'BinaryOperation' && isDynamicArrayLengthAccess(condition.rightExpression))) { + this.relevantNodes.push(node) + } } - + // eslint-disable-next-line @typescript-eslint/no-unused-vars report (compilationResults: CompilationResult): ReportObj[] { const version = getCompilerVersion(compilationResults.contracts) 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. \n Additionally, using unbounded loops incurs in a lot of avoidable gas costs. Carefully test how many items at maximum you can pass to such functions to make it successful.`, + warning: 'Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully. Due to the block gas limit, transactions can only consume a certain amount of gas. The number of iterations in a loop can grow beyond the block gas limit which can cause the complete contract to be stalled at a certain point. \n Additionally, using unbounded loops incurs in a lot of avoidable gas costs. Carefully test how many items at maximum you can pass to such functions to make it successful.', location: node.src, more: `https://solidity.readthedocs.io/en/${version}/security-considerations.html#gas-limit-and-loops` } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts index ae7b77a660..a83666d68b 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts @@ -1,14 +1,16 @@ 'use strict' -import { FunctionHLAst, ContractHLAst, FunctionCallGraph, ContractCallGraph, Context, FunctionCallAstNode } from "../../types" -import { isLocalCallGraphRelevantNode, isExternalDirectCall, getFullQualifiedFunctionCallIdent, - getFullQuallyfiedFuncDefinitionIdent, getContractName } from './staticAnalysisCommon' +import { FunctionHLAst, ContractHLAst, FunctionCallGraph, ContractCallGraph, Context, FunctionCallAstNode } from '../../types' +import { + isLocalCallGraphRelevantNode, isExternalDirectCall, getFullQualifiedFunctionCallIdent, + getFullQuallyfiedFuncDefinitionIdent, getContractName +} from './staticAnalysisCommon' type filterNodesFunction = (node: FunctionCallAstNode) => boolean type NodeIdentFunction = (node: FunctionCallAstNode) => string type FunDefIdentFunction = (node: FunctionHLAst) => string -function buildLocalFuncCallGraphInternal (functions: FunctionHLAst[], nodeFilter: filterNodesFunction , extractNodeIdent: NodeIdentFunction, extractFuncDefIdent: FunDefIdentFunction): Record { +function buildLocalFuncCallGraphInternal (functions: FunctionHLAst[], nodeFilter: filterNodesFunction, extractNodeIdent: NodeIdentFunction, extractFuncDefIdent: FunDefIdentFunction): Record { const callGraph: Record = {} functions.forEach((func: FunctionHLAst) => { const calls: string[] = func.relevantNodes @@ -76,7 +78,7 @@ function analyseCallGraphInternal (callGraph: Record, visited[funcName] = true return combinator(current.node.relevantNodes.reduce((acc, val) => combinator(acc, nodeCheck(val, context)), false), - current.calls.reduce((acc, val) => combinator(acc, analyseCallGraphInternal(callGraph, val, context, combinator, nodeCheck, visited)), false)) + current.calls.reduce((acc, val) => combinator(acc, analyseCallGraphInternal(callGraph, val, context, combinator, nodeCheck, visited)), false)) } export function resolveCallGraphSymbol (callGraph: Record, funcName: string): FunctionCallGraph | undefined { @@ -92,7 +94,7 @@ function resolveCallGraphSymbolInternal (callGraph: Record[] = this.warningNodes.map(node => { - let signature: string; - if(node.nodeType === 'FunctionDefinition'){ + const methodsWithSignature: Record[] = this.warningNodes.map(node => { + let signature: string + if (node.nodeType === 'FunctionDefinition') { const functionName: string = getFunctionDefinitionName(node) signature = helpers.buildAbiSignature(functionName, getMethodParamsSplittedTypeDesc(node, compilationResults.contracts)) - } - else - signature = node.name + '()' - + } else { signature = node.name + '()' } + return { name: node.name, src: node.src, @@ -42,8 +41,8 @@ export default class gasCosts implements AnalyzerModule { for (const contractName in compilationResults.contracts[filename]) { const contract: CompiledContract = compilationResults.contracts[filename][contractName] const methodGas: Record | undefined = this.checkMethodGas(contract, method.signature) - if(methodGas && methodGas.isInfinite) { - if(methodGas.isFallback) { + if (methodGas && methodGas.isInfinite) { + if (methodGas.isFallback) { report.push({ warning: `Fallback function of contract ${contractName} requires too much gas (${methodGas.msg}). If the fallback function requires more than 2300 gas, the contract cannot receive Ether.`, @@ -57,7 +56,7 @@ export default class gasCosts implements AnalyzerModule { (this includes clearing or copying arrays in storage)`, location: method.src }) - } + } } else continue } } @@ -65,17 +64,17 @@ export default class gasCosts implements AnalyzerModule { return report } - private checkMethodGas(contract: CompiledContract, methodSignature: string): Record | undefined { - if(contract.evm && contract.evm.gasEstimates && contract.evm.gasEstimates.external) { - if(methodSignature === '()') { + private checkMethodGas (contract: CompiledContract, methodSignature: string): Record | undefined { + if (contract.evm && contract.evm.gasEstimates && contract.evm.gasEstimates.external) { + if (methodSignature === '()') { const fallback: string = contract.evm.gasEstimates.external[''] - if (fallback !== undefined && (fallback === null || parseInt(fallback) >= 2100 || fallback === 'infinite')) { - return { - isInfinite: true, - isFallback: true, - msg: fallback - } - } + if (fallback !== undefined && (fallback === null || parseInt(fallback) >= 2100 || fallback === 'infinite')) { + return { + isInfinite: true, + isFallback: true, + msg: fallback + } + } } else { const gas: string = contract.evm.gasEstimates.external[methodSignature] const gasString: string = gas === null ? 'unknown or not constant' : 'is ' + gas @@ -85,8 +84,8 @@ export default class gasCosts implements AnalyzerModule { isFallback: false, msg: gasString } - } + } } } - } + } } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts index bd1c7e079c..b2c67fbef4 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' +import category from './categories' import { isRequireCall, isAssertCall, getCompilerVersion } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, FunctionCallAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, FunctionCallAstNode, SupportedVersion } from './../../types' export default class guardConditions implements AnalyzerModule { guards: FunctionCallAstNode[] = [] - name = `Guard conditions: ` - description = `Ensure appropriate use of require/assert` + name = 'Guard conditions: ' + description = 'Ensure appropriate use of require/assert' category: ModuleCategory = category.MISC algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -22,7 +22,7 @@ export default class guardConditions implements AnalyzerModule { const version = getCompilerVersion(compilationResults.contracts) return this.guards.map((node) => { return { - warning: `Use "assert(x)" if you never ever want x to be false, not in any circumstance (apart from a bug in your code). Use "require(x)" if x can be false, due to e.g. invalid input or a failing external component.`, + warning: 'Use "assert(x)" if you never ever want x to be false, not in any circumstance (apart from a bug in your code). Use "require(x)" if x can be false, due to e.g. invalid input or a failing external component.', location: node.src, more: `https://solidity.readthedocs.io/en/${version}/control-structures.html#error-handling-assert-require-revert-and-exceptions` } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/index.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/index.ts index 35e189932e..d76b94539a 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/index.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/index.ts @@ -18,4 +18,4 @@ export { default as stringBytesLength } from './stringBytesLength' export { default as intDivisionTruncate } from './intDivisionTruncate' export { default as etherTransferInLoop } from './etherTransferInLoop' export { default as deleteFromDynamicArray } from './deleteFromDynamicArray' -export { default as forLoopIteratesOverDynamicArray } from './forLoopIteratesOverDynamicArray' \ No newline at end of file +export { default as forLoopIteratesOverDynamicArray } from './forLoopIteratesOverDynamicArray' diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts index 52c78f89a5..851fc2f10d 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, InlineAssemblyAstNode, SupportedVersion} from './../../types' +import category from './categories' +import algorithm from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, InlineAssemblyAstNode, SupportedVersion } from './../../types' import { getCompilerVersion } from './staticAnalysisCommon' export default class inlineAssembly implements AnalyzerModule { inlineAssNodes: InlineAssemblyAstNode[] = [] - name = `Inline assembly: ` - description = `Inline assembly used` + name = 'Inline assembly: ' + description = 'Inline assembly used' category: ModuleCategory = category.SECURITY algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -14,7 +14,7 @@ export default class inlineAssembly implements AnalyzerModule { } visit (node: InlineAssemblyAstNode): void { - if(node.nodeType === 'InlineAssembly') this.inlineAssNodes.push(node) + if (node.nodeType === 'InlineAssembly') this.inlineAssNodes.push(node) } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts index e324aaa03b..b54053202b 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' +import category from './categories' import { isIntDivision } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, BinaryOperationAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, BinaryOperationAstNode, SupportedVersion } from './../../types' export default class intDivisionTruncate implements AnalyzerModule { warningNodes: BinaryOperationAstNode[] = [] - name = `Data truncated: ` - description = `Division on int/uint values truncates the result` + name = 'Data truncated: ' + description = 'Division on int/uint values truncates the result' category: ModuleCategory = category.MISC algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts index dba4c7221f..39ee3faa2d 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts @@ -1,7 +1,7 @@ -import { default as category } from './categories' +import category from './categories' import { isLLCall, isLLDelegatecall, isLLCallcode, isLLCall04, isLLDelegatecall04, isLLSend04, isLLSend, lowLevelCallTypes, getCompilerVersion } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion } from './../../types' interface llcNode { node: MemberAccessAstNode @@ -10,8 +10,8 @@ interface llcNode { export default class lowLevelCalls implements AnalyzerModule { llcNodes: llcNode[] = [] - name = `Low level calls: ` - description = `Should only be used by experienced devs` + name = 'Low level calls: ' + description = 'Should only be used by experienced devs' category: ModuleCategory = category.SECURITY algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -20,19 +20,19 @@ export default class lowLevelCalls implements AnalyzerModule { visit (node : MemberAccessAstNode): void { if (isLLCall(node)) { - this.llcNodes.push({node: node, type: lowLevelCallTypes.CALL}) + this.llcNodes.push({ node: node, type: lowLevelCallTypes.CALL }) } else if (isLLDelegatecall(node)) { - this.llcNodes.push({node: node, type: lowLevelCallTypes.DELEGATECALL}) + this.llcNodes.push({ node: node, type: lowLevelCallTypes.DELEGATECALL }) } else if (isLLSend(node)) { - this.llcNodes.push({node: node, type: lowLevelCallTypes.SEND}) + this.llcNodes.push({ node: node, type: lowLevelCallTypes.SEND }) } else if (isLLDelegatecall04(node)) { - this.llcNodes.push({node: node, type: lowLevelCallTypes.DELEGATECALL}) + this.llcNodes.push({ node: node, type: lowLevelCallTypes.DELEGATECALL }) } else if (isLLSend04(node)) { - this.llcNodes.push({node: node, type: lowLevelCallTypes.SEND}) + this.llcNodes.push({ node: node, type: lowLevelCallTypes.SEND }) } else if (isLLCall04(node)) { - this.llcNodes.push({node: node, type: lowLevelCallTypes.CALL}) + this.llcNodes.push({ node: node, type: lowLevelCallTypes.CALL }) } else if (isLLCallcode(node)) { - this.llcNodes.push({node: node, type: lowLevelCallTypes.CALLCODE}) + this.llcNodes.push({ node: node, type: lowLevelCallTypes.CALLCODE }) } } @@ -73,4 +73,3 @@ export default class lowLevelCalls implements AnalyzerModule { }) } } - diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts index 222fbaee5f..70588169d3 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts @@ -1,13 +1,15 @@ -import { default as category } from './categories' +import category from './categories' import { hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import AbstractAst from './abstractAstView' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst, - VisitFunction, ReportFunction, ReturnAstNode, AssignmentAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import AbstractAst from './abstractAstView' +import { + AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst, + VisitFunction, ReportFunction, ReturnAstNode, AssignmentAstNode, SupportedVersion +} from './../../types' export default class noReturn implements AnalyzerModule { - name = `No return: ` - description = `Function with 'returns' not returning` + name = 'No return: ' + description = 'Function with \'returns\' not returning' category: ModuleCategory = category.MISC algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -17,7 +19,7 @@ export default class noReturn implements AnalyzerModule { abstractAst: AbstractAst = new AbstractAst() visit: VisitFunction = this.abstractAst.build_visit( - (node: ReturnAstNode | AssignmentAstNode) => node.nodeType === "Return" || node.nodeType === "Assignment" + (node: ReturnAstNode | AssignmentAstNode) => node.nodeType === 'Return' || node.nodeType === 'Assignment' ) report: ReportFunction = this.abstractAst.build_report(this._report.bind(this)) @@ -30,12 +32,12 @@ export default class noReturn implements AnalyzerModule { if (this.hasNamedAndUnnamedReturns(func)) { warnings.push({ warning: `${funcName}: Mixing of named and unnamed return parameters is not advised.`, - location: func.node['src'] + location: func.node.src }) } else if (this.shouldReturn(func) && !(this.hasReturnStatement(func) || (this.hasNamedReturns(func) && this.hasAssignToAllNamedReturns(func)))) { warnings.push({ warning: `${funcName}: Defines a return type but never explicitly returns a value.`, - location: func.node['src'] + location: func.node.src }) } }) @@ -48,12 +50,12 @@ export default class noReturn implements AnalyzerModule { } private hasReturnStatement (func: FunctionHLAst): boolean { - return func.relevantNodes.filter(n => n.nodeType === "Return").length > 0 + return func.relevantNodes.filter(n => n.nodeType === 'Return').length > 0 } private hasAssignToAllNamedReturns (func: FunctionHLAst): boolean { const namedReturns: string[] = func.returns.filter(n => n.name.length > 0).map((n) => n.name) - const assignedVars: string[] = func.relevantNodes.filter(n => n.nodeType === "Assignment").map(getEffectedVariableName) + const assignedVars: string[] = func.relevantNodes.filter(n => n.nodeType === 'Assignment').map(getEffectedVariableName) const diff: string[] = namedReturns.filter(e => !assignedVars.includes(e)) return diff.length === 0 } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts index a5ac068cb8..e1ad68ecee 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' +import category from './categories' import { isStatement, isSelfdestructCall } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import AbstractAst from './abstractAstView' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VisitFunction, ReportFunction, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import AbstractAst from './abstractAstView' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VisitFunction, ReportFunction, SupportedVersion } from './../../types' export default class selfdestruct implements AnalyzerModule { - name = `Selfdestruct: ` - description = `Contracts using destructed contract can be broken` + name = 'Selfdestruct: ' + description = 'Contracts using destructed contract can be broken' category: ModuleCategory = category.SECURITY algorithm: ModuleAlgorithm = algorithm.HEURISTIC version: SupportedVersion = { @@ -16,7 +16,7 @@ export default class selfdestruct implements AnalyzerModule { abstractAst: AbstractAst = new AbstractAst() visit: VisitFunction = this.abstractAst.build_visit( - (node: any) => isStatement(node) || (node.nodeType=== 'FunctionCall' && isSelfdestructCall(node)) + (node: any) => isStatement(node) || (node.nodeType === 'FunctionCall' && isSelfdestructCall(node)) ) report: ReportFunction = this.abstractAst.build_report(this._report.bind(this)) @@ -30,7 +30,7 @@ export default class selfdestruct implements AnalyzerModule { func.relevantNodes.forEach((node) => { if (isSelfdestructCall(node)) { warnings.push({ - warning: `Use of selfdestruct: Can block calling contracts unexpectedly. Be especially careful if this contract is planned to be used by other contracts (i.e. library contracts, interactions). Selfdestruction of the callee contract can leave callers in an inoperable state.`, + warning: 'Use of selfdestruct: Can block calling contracts unexpectedly. Be especially careful if this contract is planned to be used by other contracts (i.e. library contracts, interactions). Selfdestruction of the callee contract can leave callers in an inoperable state.', location: node.src, more: 'https://paritytech.io/blog/security-alert.html' }) @@ -38,7 +38,7 @@ export default class selfdestruct implements AnalyzerModule { } if (isStatement(node) && hasSelf) { warnings.push({ - warning: `Use of selfdestruct: No code after selfdestruct is executed. Selfdestruct is a terminal.`, + warning: 'Use of selfdestruct: No code after selfdestruct is executed. Selfdestruct is a terminal.', location: node.src, more: `https://solidity.readthedocs.io/en/${version}/introduction-to-smart-contracts.html#deactivate-and-self-destruct` }) diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts index e56057d3ed..35591dc8b1 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts @@ -1,11 +1,11 @@ -import { default as category } from './categories' +import category from './categories' import { getDeclaredVariableName, getFullQuallyfiedFuncDefinitionIdent } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import AbstractAst from './abstractAstView' +import algorithm from './algorithmCategories' +import AbstractAst from './abstractAstView' import { get } from 'fast-levenshtein' import { util } from '@remix-project/remix-lib' import { AstWalker } from '@remix-project/remix-astwalker' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst, VariableDeclarationAstNode, VisitFunction, ReportFunction, SupportedVersion} from './../../types' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst, VariableDeclarationAstNode, VisitFunction, ReportFunction, SupportedVersion } from './../../types' interface SimilarRecord { var1: string @@ -14,8 +14,8 @@ interface SimilarRecord { } export default class similarVariableNames implements AnalyzerModule { - name = `Similar variable names: ` - description = `Variable names are too similar` + name = 'Similar variable names: ' + description = 'Variable names are too similar' category: ModuleCategory = category.MISC algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -47,17 +47,17 @@ export default class similarVariableNames implements AnalyzerModule { const vars: string[] = this.getFunctionVariables(contract, func).map(getDeclaredVariableName) this.findSimilarVarNames(vars).map((sim) => { // check if function is implemented - if(func.node.implemented) { + if (func.node.implemented) { const astWalker = new AstWalker() const functionBody: any = func.node.body // Walk through all statements of function astWalker.walk(functionBody, (node) => { // check if these is an identifier node which is one of the tracked similar variables - if ((node.nodeType === 'Identifier' || node.nodeType === 'VariableDeclaration') - && (node.name === sim.var1 || node.name === sim.var2)) { + if ((node.nodeType === 'Identifier' || node.nodeType === 'VariableDeclaration') && + (node.name === sim.var1 || node.name === sim.var2)) { warnings.push({ warning: `${funcName} : Variables have very similar names "${sim.var1}" and "${sim.var2}". ${hasModifiersComments} ${multipleContractsWithSameNameComments}`, - location: node['src'] + location: node.src }) } return true @@ -73,9 +73,9 @@ export default class similarVariableNames implements AnalyzerModule { const similar: SimilarRecord[] = [] const comb: Record = {} vars.map((varName1: string) => vars.map((varName2: string) => { - if (varName1.length > 1 && varName2.length > 1 && - varName2 !== varName1 && !this.isCommonPrefixedVersion(varName1, varName2) && - !this.isCommonNrSuffixVersion(varName1, 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: number = get(varName1, varName2) diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts index 4120f37412..6d258d5909 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts @@ -1,9 +1,11 @@ 'use strict' -import { FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode, - WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode, - MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode, - IdentifierAstNode, IndexAccessAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode, CompiledContractObj, ABIParameter, CompiledContract } from "../../types" +import { + FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode, + WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode, + MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode, + IdentifierAstNode, IndexAccessAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode, CompiledContractObj, ABIParameter, CompiledContract +} from '../../types' import { util } from '@remix-project/remix-lib' type SpecialObjDetail = { @@ -34,35 +36,35 @@ const nodeTypes: Record = { FUNCTIONTYPENAME: 'FunctionTypeName', MAPPING: 'Mapping', ARRAYTYPENAME: 'ArrayTypeName', - INLINEASSEMBLY: 'InlineAssembly', + INLINEASSEMBLY: 'InlineAssembly', BLOCK: 'Block', - PLACEHOLDERSTATEMENT: 'PlaceholderStatement', + PLACEHOLDERSTATEMENT: 'PlaceholderStatement', IFSTATEMENT: 'IfStatement', - TRYCATCHCLAUSE: 'TryCatchClause', - TRYSTATEMENT: 'TryStatement', + TRYCATCHCLAUSE: 'TryCatchClause', + TRYSTATEMENT: 'TryStatement', WHILESTATEMENT: 'WhileStatement', DOWHILESTATEMENT: 'DoWhileStatement', FORSTATEMENT: 'ForStatement', - CONTINUE: 'Continue', - BREAK: 'Break', - RETURN: 'Return', - THROW: 'Throw', - EMITSTATEMENT: 'EmitStatement', - VARIABLEDECLARATIONSTATEMENT: 'VariableDeclarationStatement', + CONTINUE: 'Continue', + BREAK: 'Break', + RETURN: 'Return', + THROW: 'Throw', + EMITSTATEMENT: 'EmitStatement', + VARIABLEDECLARATIONSTATEMENT: 'VariableDeclarationStatement', EXPRESSIONSTATEMENT: 'ExpressionStatement', - CONDITIONAL: 'Conditional', + CONDITIONAL: 'Conditional', ASSIGNMENT: 'Assignment', - TUPLEEXPRESSION: 'TupleExpression', + TUPLEEXPRESSION: 'TupleExpression', UNARYOPERATION: 'UnaryOperation', BINARYOPERATION: 'BinaryOperation', FUNCTIONCALL: 'FunctionCall', FUNCTIONCALLOPTIONS: 'FunctionCallOptions', - NEWEXPRESSION: 'NewExpression', - MEMBERACCESS: 'MemberAccess', - INDEXACCESS: 'IndexAccess', - INDEXRANGEACCESS: 'IndexRangeAccess', - ELEMENTARYTYPENAMEEXPRESSION: 'ElementaryTypeNameExpression', - LITERAL: 'Literal', + NEWEXPRESSION: 'NewExpression', + MEMBERACCESS: 'MemberAccess', + INDEXACCESS: 'IndexAccess', + INDEXRANGEACCESS: 'IndexRangeAccess', + ELEMENTARYTYPENAMEEXPRESSION: 'ElementaryTypeNameExpression', + LITERAL: 'Literal', IDENTIFIER: 'Identifier', STRUCTUREDDOCUMENTATION: 'StructuredDocumentation' } @@ -184,7 +186,7 @@ function getFunctionCallType (func: FunctionCallAstNode): string { */ function getEffectedVariableName (effectNode: AssignmentAstNode | UnaryOperationAstNode): string { if (!isEffect(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node') - if(effectNode.nodeType === 'Assignment' || effectNode.nodeType === 'UnaryOperation') { + if (effectNode.nodeType === 'Assignment' || effectNode.nodeType === 'UnaryOperation') { const IdentNode: IdentifierAstNode = findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER)) return IdentNode.name } else throw new Error('staticAnalysisCommon.js: wrong node type') @@ -334,7 +336,7 @@ function getDeclaredVariableType (varDeclNode: VariableDeclarationAstNode): stri * @return {list variable declaration} state variable node list */ function getStateVariableDeclarationsFromContractNode (contractNode: ContractDefinitionAstNode): VariableDeclarationAstNode[] { - return contractNode.nodes.filter(el => el.nodeType === "VariableDeclaration") + return contractNode.nodes.filter(el => el.nodeType === 'VariableDeclaration') } /** @@ -398,8 +400,7 @@ function getFunctionCallTypeParameterType (func: FunctionCallAstNode): string | function getLibraryCallContractName (node: FunctionCallAstNode): string | undefined { if (!isLibraryCall(node.expression)) throw new Error('staticAnalysisCommon.js: not a library call Node') const types: RegExpExecArray | null = new RegExp(basicRegex.LIBRARYTYPE).exec(node.expression.expression.typeDescriptions.typeString) - if(types) - return types[1] + if (types) { return types[1] } } /** @@ -444,29 +445,24 @@ function getFullQuallyfiedFuncDefinitionIdent (contract: ContractDefinitionAstNo function getUnAssignedTopLevelBinOps (subScope: BlockAstNode | IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): ExpressionStatementAstNode[] { let result: ExpressionStatementAstNode[] = [] - if(subScope && subScope.nodeType === 'Block') - result = subScope.statements.filter(isBinaryOpInExpression) + if (subScope && subScope.nodeType === 'Block') result = subScope.statements.filter(isBinaryOpInExpression) // for 'without braces' loops else if (subScope && subScope.nodeType && subScope.nodeType !== 'Block' && isSubScopeStatement(subScope)) { - if (subScope.nodeType === 'IfStatement'){ - if((subScope.trueBody && subScope.trueBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.trueBody))) - result.push(subScope.trueBody) - if (subScope.falseBody && subScope.falseBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.falseBody)) - result.push(subScope.falseBody) - } - else { - if(subScope.body && subScope.body.nodeType === "ExpressionStatement" && isBinaryOpInExpression(subScope.body)) - result.push(subScope.body) + if (subScope.nodeType === 'IfStatement') { + if ((subScope.trueBody && subScope.trueBody.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(subScope.trueBody))) { result.push(subScope.trueBody) } + if (subScope.falseBody && subScope.falseBody.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(subScope.falseBody)) { result.push(subScope.falseBody) } + } else { + if (subScope.body && subScope.body.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(subScope.body)) { result.push(subScope.body) } } } - return result + return result } // #################### Trivial Node Identification // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types function isStatement (node: any): boolean { - return nodeType(node, 'Statement$') || node.nodeType === "Block" || node.nodeType === "Return" + return nodeType(node, 'Statement$') || node.nodeType === 'Block' || node.nodeType === 'Return' } // #################### Complex Node Identification @@ -505,7 +501,7 @@ function isDynamicArrayAccess (node: IdentifierAstNode): boolean { */ function isDynamicArrayLengthAccess (node: MemberAccessAstNode): boolean { return (node.memberName === 'length') && // accessing 'length' member - node.expression['typeDescriptions']['typeString'].indexOf('[]') !== -1 // member is accessed from dynamic array, notice [] without any number + node.expression.typeDescriptions.typeString.indexOf('[]') !== -1 // member is accessed from dynamic array, notice [] without any number } /** @@ -550,7 +546,7 @@ function isBuiltinFunctionCall (node: FunctionCallAstNode): boolean { * @return {bool} */ function isAbiNamespaceCall (node: FunctionCallAstNode): boolean { - return Object.keys(abiNamespace).some((key) => Object.prototype.hasOwnProperty.call(abiNamespace,key) && node.expression && isSpecialVariableAccess(node.expression, abiNamespace[key])) + return Object.keys(abiNamespace).some((key) => Object.prototype.hasOwnProperty.call(abiNamespace, key) && node.expression && isSpecialVariableAccess(node.expression, abiNamespace[key])) } /** @@ -576,7 +572,7 @@ function isAssertCall (node: FunctionCallAstNode): boolean { * @node {ASTNode} some AstNode * @return {bool} */ -function isRequireCall (node: FunctionCallAstNode): boolean { +function isRequireCall (node: FunctionCallAstNode): boolean { return isBuiltinFunctionCall(node) && getLocalCallName(node) === 'require' } @@ -597,7 +593,7 @@ function isStorageVariableDeclaration (node: VariableDeclarationAstNode): boolea function isInteraction (node: FunctionCallAstNode): boolean { return isLLCall(node.expression) || isLLSend(node.expression) || isExternalDirectCall(node) || isTransfer(node.expression) || isLLCall04(node.expression) || isLLSend04(node.expression) || - // to cover case of address.call.value.gas , See: inheritance.sol + // to cover case of address.call.value.gas , See: inheritance.sol (node.expression && node.expression.expression && isLLCall(node.expression.expression)) || (node.expression && node.expression.expression && isLLCall04(node.expression.expression)) } @@ -608,9 +604,9 @@ function isInteraction (node: FunctionCallAstNode): boolean { * @return {bool} */ function isEffect (node: AssignmentAstNode | UnaryOperationAstNode | InlineAssemblyAstNode): boolean { - return node.nodeType === "Assignment" || - (node.nodeType === "UnaryOperation" && (isPlusPlusUnaryOperation(node) || isMinusMinusUnaryOperation(node))) || - node.nodeType === "InlineAssembly" + return node.nodeType === 'Assignment' || + (node.nodeType === 'UnaryOperation' && (isPlusPlusUnaryOperation(node) || isMinusMinusUnaryOperation(node))) || + node.nodeType === 'InlineAssembly' } /** @@ -620,7 +616,7 @@ function isEffect (node: AssignmentAstNode | UnaryOperationAstNode | InlineAssem * @return {bool} */ function isWriteOnStateVariable (effectNode: AssignmentAstNode | InlineAssemblyAstNode | UnaryOperationAstNode, stateVariables: VariableDeclarationAstNode[]): boolean { - return effectNode.nodeType === "InlineAssembly" || (isEffect(effectNode) && isStateVariable(getEffectedVariableName(effectNode), stateVariables)) + return effectNode.nodeType === 'InlineAssembly' || (isEffect(effectNode) && isStateVariable(getEffectedVariableName(effectNode), stateVariables)) } /** @@ -648,7 +644,7 @@ function isConstantFunction (node: FunctionDefinitionAstNode): boolean { * @return {bool} */ function isVariableTurnedIntoGetter (varDeclNode: VariableDeclarationAstNode): boolean { - return varDeclNode.stateVariable && varDeclNode.visibility === 'public'; + return varDeclNode.stateVariable && varDeclNode.visibility === 'public' } /** @@ -666,7 +662,7 @@ function isPayableFunction (node: FunctionDefinitionAstNode): boolean { * @return {bool} */ function isConstructor (node: FunctionDefinitionAstNode): boolean { - return node.kind === "constructor" + return node.kind === 'constructor' } /** @@ -684,24 +680,21 @@ function isIntDivision (node: BinaryOperationAstNode): boolean { * @return {bool} */ function isSubScopeWithTopLevelUnAssignedBinOp (node: BlockAstNode | IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): boolean | undefined { - if(node.nodeType === 'Block') - return node.statements.some(isBinaryOpInExpression) + if (node.nodeType === 'Block') return node.statements.some(isBinaryOpInExpression) // for 'without braces' loops else if (node && node.nodeType && isSubScopeStatement(node)) { - if (node.nodeType === 'IfStatement') - return (node.trueBody && node.trueBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.trueBody)) || - (node.falseBody && node.falseBody.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.falseBody)) - else - return node.body && node.body.nodeType === "ExpressionStatement" && isBinaryOpInExpression(node.body) - } + if (node.nodeType === 'IfStatement') { + return (node.trueBody && node.trueBody.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(node.trueBody)) || + (node.falseBody && node.falseBody.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(node.falseBody)) + } else { return node.body && node.body.nodeType === 'ExpressionStatement' && isBinaryOpInExpression(node.body) } + } } function isSubScopeStatement (node: IfStatementAstNode | WhileStatementAstNode | ForStatementAstNode): boolean { - if(node.nodeType === 'IfStatement') + if (node.nodeType === 'IfStatement') { return (node.trueBody && node.trueBody.nodeType && !nodeType(node.trueBody, exactMatch(nodeTypes.BLOCK))) || (node.falseBody && node.falseBody.nodeType && !nodeType(node.falseBody, exactMatch(nodeTypes.BLOCK))) - else - return node.body && node.body.nodeType && !nodeType(node.body, exactMatch(nodeTypes.BLOCK)) + } else { return node.body && node.body.nodeType && !nodeType(node.body, exactMatch(nodeTypes.BLOCK)) } } /** @@ -710,7 +703,7 @@ function isSubScopeStatement (node: IfStatementAstNode | WhileStatementAstNode | * @return {bool} */ function isBinaryOpInExpression (node: ExpressionStatementAstNode): boolean { - return node.nodeType === "ExpressionStatement" && node.expression.nodeType === "BinaryOperation" + return node.nodeType === 'ExpressionStatement' && node.expression.nodeType === 'BinaryOperation' } /** @@ -791,7 +784,7 @@ function isExternalDirectCall (node: FunctionCallAstNode): boolean { * @return {bool} */ function isNowAccess (node: IdentifierAstNode): boolean { - return node.name === "now" && typeDescription(node, exactMatch(basicTypes.UINT)) + return node.name === 'now' && typeDescription(node, exactMatch(basicTypes.UINT)) } /** @@ -818,7 +811,7 @@ function isBlockTimestampAccess (node: MemberAccessAstNode): boolean { * @return {bool} */ function isBlockBlockHashAccess (node: FunctionCallAstNode): boolean { - return ( isBuiltinFunctionCall(node) && getLocalCallName(node) === 'blockhash' ) || + return (isBuiltinFunctionCall(node) && getLocalCallName(node) === 'blockhash') || isSpecialVariableAccess(node.expression, specialVariables.BLOCKHASH) } @@ -846,7 +839,7 @@ function isSuperLocalCall (node: MemberAccessAstNode): boolean { * @return {bool} */ function isLocalCall (node: FunctionCallAstNode): boolean { - return node.nodeType === 'FunctionCall' && node.kind === 'functionCall' && + return node.nodeType === 'FunctionCall' && node.kind === 'functionCall' && node.expression.nodeType === 'Identifier' && expressionTypeDescription(node, basicRegex.FUNCTIONTYPE) && !expressionTypeDescription(node, basicRegex.EXTERNALFUNCTIONTYPE) } @@ -873,8 +866,8 @@ function isLowLevelCall (node: MemberAccessAstNode): boolean { */ function isLLSend04 (node: MemberAccessAstNode): boolean { return isMemberAccess(node, - exactMatch(util.escapeRegExp(lowLevelCallTypes.SEND.type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident)) + exactMatch(util.escapeRegExp(lowLevelCallTypes.SEND.type)), + undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident)) } /** @@ -884,8 +877,8 @@ function isLLSend04 (node: MemberAccessAstNode): boolean { */ function isLLSend (node: MemberAccessAstNode): boolean { return isMemberAccess(node, - exactMatch(util.escapeRegExp(lowLevelCallTypes.SEND.type)), - undefined, exactMatch(basicTypes.PAYABLE_ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident)) + exactMatch(util.escapeRegExp(lowLevelCallTypes.SEND.type)), + undefined, exactMatch(basicTypes.PAYABLE_ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident)) } /** @@ -895,8 +888,8 @@ function isLLSend (node: MemberAccessAstNode): boolean { */ function isLLCall (node: MemberAccessAstNode): boolean { return isMemberAccess(node, - exactMatch(util.escapeRegExp(lowLevelCallTypes.CALL.type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident)) || + exactMatch(util.escapeRegExp(lowLevelCallTypes.CALL.type)), + undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident)) || isMemberAccess(node, exactMatch(util.escapeRegExp(lowLevelCallTypes.CALL.type)), undefined, exactMatch(basicTypes.PAYABLE_ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident)) @@ -909,8 +902,8 @@ function isLLCall (node: MemberAccessAstNode): boolean { */ function isLLCall04 (node: MemberAccessAstNode): boolean { return isMemberAccess(node, - exactMatch(util.escapeRegExp(lowLevelCallTypes['CALL-0.4'].type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes['CALL-0.4'].ident)) + exactMatch(util.escapeRegExp(lowLevelCallTypes['CALL-0.4'].type)), + undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes['CALL-0.4'].ident)) } /** @@ -920,8 +913,8 @@ function isLLCall04 (node: MemberAccessAstNode): boolean { */ function isLLCallcode (node: MemberAccessAstNode): boolean { return isMemberAccess(node, - exactMatch(util.escapeRegExp(lowLevelCallTypes.CALLCODE.type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALLCODE.ident)) + exactMatch(util.escapeRegExp(lowLevelCallTypes.CALLCODE.type)), + undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALLCODE.ident)) } /** @@ -931,8 +924,8 @@ function isLLCallcode (node: MemberAccessAstNode): boolean { */ function isLLDelegatecall (node: MemberAccessAstNode): boolean { return isMemberAccess(node, - exactMatch(util.escapeRegExp(lowLevelCallTypes.DELEGATECALL.type)), - undefined, matches(basicTypes.PAYABLE_ADDRESS, basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.DELEGATECALL.ident)) + exactMatch(util.escapeRegExp(lowLevelCallTypes.DELEGATECALL.type)), + undefined, matches(basicTypes.PAYABLE_ADDRESS, basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.DELEGATECALL.ident)) } /** @@ -942,8 +935,8 @@ function isLLDelegatecall (node: MemberAccessAstNode): boolean { */ function isLLDelegatecall04 (node: MemberAccessAstNode): boolean { return isMemberAccess(node, - exactMatch(util.escapeRegExp(lowLevelCallTypes['DELEGATECALL-0.4'].type)), - undefined, matches(basicTypes.PAYABLE_ADDRESS, basicTypes.ADDRESS), exactMatch(lowLevelCallTypes['DELEGATECALL-0.4'].ident)) + exactMatch(util.escapeRegExp(lowLevelCallTypes['DELEGATECALL-0.4'].type)), + undefined, matches(basicTypes.PAYABLE_ADDRESS, basicTypes.ADDRESS), exactMatch(lowLevelCallTypes['DELEGATECALL-0.4'].ident)) } /** @@ -953,8 +946,8 @@ function isLLDelegatecall04 (node: MemberAccessAstNode): boolean { */ function isTransfer (node: MemberAccessAstNode): boolean { return isMemberAccess(node, - exactMatch(util.escapeRegExp(lowLevelCallTypes.TRANSFER.type)), - undefined, matches(basicTypes.ADDRESS, basicTypes.PAYABLE_ADDRESS), exactMatch(lowLevelCallTypes.TRANSFER.ident)) + exactMatch(util.escapeRegExp(lowLevelCallTypes.TRANSFER.type)), + undefined, matches(basicTypes.ADDRESS, basicTypes.PAYABLE_ADDRESS), exactMatch(lowLevelCallTypes.TRANSFER.ident)) } function isStringToBytesConversion (node: FunctionCallAstNode): boolean { @@ -962,7 +955,7 @@ function isStringToBytesConversion (node: FunctionCallAstNode): boolean { } function isExplicitCast (node: FunctionCallAstNode, castFromType: string, castToType: string): boolean { - return node.kind === "typeConversion" && + return node.kind === 'typeConversion' && nodeType(node.expression, exactMatch(nodeTypes.ELEMENTARYTYPENAMEEXPRESSION)) && node.expression.typeName === castToType && nodeType(node.arguments[0], exactMatch(nodeTypes.IDENTIFIER)) && typeDescription(node.arguments[0], castFromType) } @@ -976,7 +969,7 @@ function isBytesLengthCheck (node: MemberAccessAstNode): boolean { * @node {ASTNode} some AstNode * @return {bool} */ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types function isLoop (node: any): boolean { return nodeType(node, exactMatch(nodeTypes.FORSTATEMENT)) || nodeType(node, exactMatch(nodeTypes.WHILESTATEMENT)) || @@ -986,7 +979,7 @@ function isLoop (node: any): boolean { // #################### Complex Node Identification - Private function isMemberAccess (node: MemberAccessAstNode, retType: string, accessor: string| undefined, accessorType: string, memberName: string | undefined): boolean { - if(node && nodeType(node, exactMatch('MemberAccess'))) { + if (node && nodeType(node, exactMatch('MemberAccess'))) { const nodeTypeDef: boolean = typeDescription(node, retType) const nodeMemName: boolean = memName(node, memberName) const nodeExpMemName: boolean = memName(node.expression, accessor) @@ -1003,12 +996,12 @@ function isSpecialVariableAccess (node: MemberAccessAstNode, varType: SpecialObj // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types function expressionTypeDescription (node: any, typeRegex: string): boolean { - return new RegExp(typeRegex).test(node.expression.typeDescriptions.typeString) + return new RegExp(typeRegex).test(node.expression.typeDescriptions.typeString) } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types function typeDescription (node: any, typeRegex: string): boolean { - return new RegExp(typeRegex).test(node.typeDescriptions.typeString) + return new RegExp(typeRegex).test(node.typeDescriptions.typeString) } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -1018,7 +1011,7 @@ function nodeType (node: any, typeRegex: string): boolean { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types function nodeTypeIn (node: any, typeRegex: string[]): boolean { - return typeRegex.some((typeRegex) => nodeType (node, typeRegex)) + return typeRegex.some((typeRegex) => nodeType(node, typeRegex)) } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -1053,20 +1046,7 @@ function matches (...fnArgs: any[]): string { * Note: developed keeping identifier node search in mind to get first identifier node from left in subscope */ function findFirstSubNodeLTR (node: any, type: string): any { - if(node.nodeType && nodeType(node, type)) - return node - - else if(node.nodeType && nodeType(node, exactMatch('Assignment'))) - return findFirstSubNodeLTR(node.leftHandSide, type) - - else if(node.nodeType && nodeType(node, exactMatch('MemberAccess'))) - return findFirstSubNodeLTR(node.expression, type) - - else if(node.nodeType && nodeType(node, exactMatch('IndexAccess'))) - return findFirstSubNodeLTR(node.baseExpression, type) - - else if(node.nodeType && nodeType(node, exactMatch('UnaryOperation'))) - return findFirstSubNodeLTR(node.subExpression, type) + if (node.nodeType && nodeType(node, type)) { return node } else if (node.nodeType && nodeType(node, exactMatch('Assignment'))) { return findFirstSubNodeLTR(node.leftHandSide, type) } else if (node.nodeType && nodeType(node, exactMatch('MemberAccess'))) { return findFirstSubNodeLTR(node.expression, type) } else if (node.nodeType && nodeType(node, exactMatch('IndexAccess'))) { return findFirstSubNodeLTR(node.baseExpression, type) } else if (node.nodeType && nodeType(node, exactMatch('UnaryOperation'))) { return findFirstSubNodeLTR(node.subExpression, type) } } /** @@ -1087,45 +1067,41 @@ function buildAbiSignature (funName: string, paramTypes: any[]): string { } // To create the method signature similar to contract.evm.gasEstimates.external object -// For address payable, return address -function getMethodParamsSplittedTypeDesc(node: FunctionDefinitionAstNode, contracts: CompiledContractObj): string[] { +// For address payable, return address +function getMethodParamsSplittedTypeDesc (node: FunctionDefinitionAstNode, contracts: CompiledContractObj): string[] { return node.parameters.parameters.map((varNode, varIndex) => { - let finalTypeString; + let finalTypeString const typeString = varNode.typeDescriptions.typeString - if(typeString.includes('struct')) { + if (typeString.includes('struct')) { const fnName = node.name for (const filename in contracts) { for (const contractName in contracts[filename]) { const methodABI = contracts[filename][contractName].abi - .find(e => e.name === fnName && e.inputs?.length && - e.inputs[varIndex]['type'].includes('tuple') && + .find(e => e.name === fnName && e.inputs?.length && + e.inputs[varIndex]['type'].includes('tuple') && e.inputs[varIndex]['internalType'] === typeString) - if(methodABI && methodABI.inputs) { + if (methodABI && methodABI.inputs) { const inputs = methodABI.inputs[varIndex] const typeStr = getTypeStringFromComponents(inputs['components']) finalTypeString = typeStr + inputs['type'].replace('tuple', '') } } } - } else - finalTypeString = typeString.split(' ')[0] + } else { finalTypeString = typeString.split(' ')[0] } return finalTypeString }) } -function getTypeStringFromComponents(components: ABIParameter[]) { +function getTypeStringFromComponents (components: ABIParameter[]) { let typeString = '(' - for(let i=0; i < components.length; i++) { + for (let i = 0; i < components.length; i++) { const param = components[i] - if(param.type.includes('tuple') && param.components && param.components.length > 0){ + if (param.type.includes('tuple') && param.components && param.components.length > 0) { typeString = typeString + getTypeStringFromComponents(param.components) typeString = typeString + param.type.replace('tuple', '') - } - else - typeString = typeString + param.type + } else { typeString = typeString + param.type } - if(i !== components.length - 1) - typeString = typeString + ',' + if (i !== components.length - 1) { typeString = typeString + ',' } } typeString = typeString + ')' return typeString @@ -1136,18 +1112,17 @@ function getTypeStringFromComponents(components: ABIParameter[]) { * This is used to redirect the user to specific version of Solidity documentation * @param contractFiles compiled contract object */ -function getCompilerVersion(contractFiles: CompiledContractObj): string { +function getCompilerVersion (contractFiles: CompiledContractObj): string { let version = 'latest' const fileNames: string[] = Object.keys(contractFiles) const contracts = contractFiles[fileNames[0]] const contractNames: string[] = Object.keys(contracts) const contract: CompiledContract = contracts[contractNames[0]] // For some compiler/contract, metadata is "" - if(contract && contract.metadata) { + if (contract && contract.metadata) { const metadata = JSON.parse(contract.metadata) const compilerVersion: string = metadata.compiler.version - if(!compilerVersion.includes('nightly')) - version = 'v' + compilerVersion.split('+commit')[0] + if (!compilerVersion.includes('nightly')) { version = 'v' + compilerVersion.split('+commit')[0] } } return version } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts index 91a9ca4afc..57c02b84dd 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts @@ -1,11 +1,11 @@ -import { default as category } from './categories' -import { default as algorithm } from './algorithmCategories' +import category from './categories' +import algorithm from './algorithmCategories' import { isStringToBytesConversion, isBytesLengthCheck, getCompilerVersion } from './staticAnalysisCommon' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, FunctionCallAstNode, SupportedVersion} from './../../types' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, FunctionCallAstNode, SupportedVersion } from './../../types' export default class stringBytesLength implements AnalyzerModule { - name = `String length: ` - description = `Bytes length != String length` + name = 'String length: ' + description = 'Bytes length != String length' category: ModuleCategory = category.MISC algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -16,8 +16,8 @@ export default class stringBytesLength implements AnalyzerModule { bytesLengthChecks: MemberAccessAstNode[] = [] visit (node: FunctionCallAstNode | MemberAccessAstNode): void { - if (node.nodeType === "FunctionCall" && isStringToBytesConversion(node)) this.stringToBytesConversions.push(node) - else if (node.nodeType === "MemberAccess" && isBytesLengthCheck(node)) this.bytesLengthChecks.push(node) + if (node.nodeType === 'FunctionCall' && isStringToBytesConversion(node)) this.stringToBytesConversions.push(node) + else if (node.nodeType === 'MemberAccess' && isBytesLengthCheck(node)) this.bytesLengthChecks.push(node) } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -25,7 +25,7 @@ export default class stringBytesLength implements AnalyzerModule { const version = getCompilerVersion(compilationResults.contracts) if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) { return [{ - warning: `"bytes" and "string" lengths are not the same since strings are assumed to be UTF-8 encoded (according to the ABI defintion) therefore one character is not nessesarily encoded in one byte of data.`, + warning: '"bytes" and "string" lengths are not the same since strings are assumed to be UTF-8 encoded (according to the ABI defintion) therefore one character is not nessesarily encoded in one byte of data.', location: this.bytesLengthChecks[0].src, more: `https://solidity.readthedocs.io/en/${version}/abi-spec.html#argument-encoding` }] diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts index 3abcda5816..9c13b4c2ac 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' +import category from './categories' import { isThisLocalCall, getCompilerVersion } from './staticAnalysisCommon' -import { default as algorithm } from './algorithmCategories' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion} from './../../types' +import algorithm from './algorithmCategories' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion } from './../../types' export default class thisLocal implements AnalyzerModule { warningNodes: MemberAccessAstNode[] = [] - name = `This on local calls: ` - description = `Invocation of local functions via 'this'` + name = 'This on local calls: ' + description = 'Invocation of local functions via \'this\'' category: ModuleCategory = category.GAS algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -22,7 +22,7 @@ export default class thisLocal implements AnalyzerModule { const version = getCompilerVersion(compilationResults.contracts) 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.`, + warning: 'Use of "this" for local functions: Never use "this" to call functions in the same contract, it only consumes more gas than normal local calls.', location: item.src, more: `https://solidity.readthedocs.io/en/${version}/control-structures.html#external-function-calls` } diff --git a/libs/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts b/libs/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts index c4f6570387..1a7a0cd0d7 100644 --- a/libs/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts +++ b/libs/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts @@ -1,12 +1,12 @@ -import { default as category } from './categories' -import { default as algorithm } from './algorithmCategories' +import category from './categories' +import algorithm from './algorithmCategories' import { isTxOriginAccess, getCompilerVersion } from './staticAnalysisCommon' -import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion} from './../../types' +import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode, SupportedVersion } from './../../types' export default class txOrigin implements AnalyzerModule { txOriginNodes: MemberAccessAstNode[] = [] - name = `Transaction origin: ` - description = `'tx.origin' used` + name = 'Transaction origin: ' + description = '\'tx.origin\' used' category: ModuleCategory = category.SECURITY algorithm: ModuleAlgorithm = algorithm.EXACT version: SupportedVersion = { @@ -15,7 +15,6 @@ export default class txOrigin implements AnalyzerModule { visit (node: MemberAccessAstNode): void { if (isTxOriginAccess(node)) this.txOriginNodes.push(node) - } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/libs/remix-analyzer/src/types.ts b/libs/remix-analyzer/src/types.ts index 5eaccca5c9..eab758b04a 100644 --- a/libs/remix-analyzer/src/types.ts +++ b/libs/remix-analyzer/src/types.ts @@ -39,9 +39,9 @@ export interface ReportObj { // s:l:f -// Where, -// s is the byte-offset to the start of the range in the source file, -// l is the length of the source range in bytes and +// Where, +// s is the byte-offset to the start of the range in the source file, +// l is the length of the source range in bytes and // f is the source index mentioned above. export interface AnalysisReportObj { @@ -57,7 +57,7 @@ export type AnalysisReport = { } export interface CompilationResult { - error?: CompilationError, + 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 */ @@ -121,9 +121,9 @@ export interface ContractCallGraph { functions: Record } -///////////////////////////////////////////////////////////// -///////////// Specfic AST Nodes ///////////////////////////// -///////////////////////////////////////////////////////////// +/// ////////////////////////////////////////////////////////// +/// ////////// Specfic AST Nodes ///////////////////////////// +/// ////////////////////////////////////////////////////////// interface TypeDescription { typeIdentifier: string @@ -629,10 +629,9 @@ export interface CommonAstNode { [x: string]: any } - -///////////////////////////////////////////////////////// -///////////// YUL AST Nodes ///////////////////////////// -///////////////////////////////////////////////////////// +/// ////////////////////////////////////////////////////// +/// ////////// YUL AST Nodes ///////////////////////////// +/// ////////////////////////////////////////////////////// export interface YulTypedNameAstNode { name: string @@ -673,13 +672,12 @@ export interface CommonYulAstNode { src: string [x: string]: any } - - - /////////// - // ERROR // - /////////// - - export interface CompilationError { + +/// //////// +// ERROR // +/// //////// + +export interface CompilationError { /** Location within the source file */ sourceLocation?: { file: string @@ -696,7 +694,7 @@ export interface CommonYulAstNode { /** the message formatted with source location */ formattedMessage?: string } - + type CompilationErrorType = | 'JSONError' | 'IOError' @@ -711,21 +709,21 @@ export interface CommonYulAstNode { | 'CompilerError' | 'FatalError' | 'Warning' - - //////////// - // SOURCE // - //////////// - export interface CompilationSource { + +/// ///////// +// SOURCE // +/// ///////// +export interface CompilationSource { /** Identifier of the source (used in source maps) */ id: number /** The AST object */ ast: AstNode } - - ///////// - // AST // - ///////// - export interface AstNode { + +/// ////// +// AST // +/// ////// +export interface AstNode { absolutePath?: string exportedSymbols?: Record id: number @@ -739,8 +737,8 @@ export interface CommonYulAstNode { symbolAliases?: Array [x: string]: any } - - export interface AstNodeAtt { + +export interface AstNodeAtt { operator?: string string?: null type?: string @@ -753,11 +751,11 @@ export interface CommonYulAstNode { absolutePath?: string [x: string]: any } - - ////////////// - // CONTRACT // - ////////////// - export interface CompiledContract { + +/// /////////// +// 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) @@ -802,13 +800,13 @@ export interface CommonYulAstNode { wasm: string } } - - ///////// - // ABI // - ///////// - export type ABIDescription = FunctionDescription | EventDescription - - export interface FunctionDescription { + +/// ////// +// 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 */ @@ -824,8 +822,8 @@ export interface CommonYulAstNode { /** true if function is either pure or view, false otherwise. Default is false */ constant?: boolean } - - export interface EventDescription { + +export interface EventDescription { type: 'event' name: string inputs: ABIParameter & @@ -836,8 +834,8 @@ export interface CommonYulAstNode { /** true if the event was declared as anonymous. */ anonymous: boolean } - - export interface ABIParameter { + +export interface ABIParameter { internalType: string /** The name of the parameter */ name: string @@ -846,8 +844,8 @@ export interface CommonYulAstNode { /** Used for tuple types */ components?: ABIParameter[] } - - export type ABITypeParameter = + +export type ABITypeParameter = | 'uint' | 'uint[]' // TODO : add | 'int' @@ -868,38 +866,38 @@ export interface CommonYulAstNode { | 'tuple[]' | string // Fallback -/////////////////////////// - // NATURAL SPECIFICATION // - /////////////////////////// - - // Userdoc - export interface UserDocumentation { +/// //////////////////////// +// NATURAL SPECIFICATION // +/// //////////////////////// + +// Userdoc +export interface UserDocumentation { methods: UserMethodList notice: string } - - export type UserMethodList = { + +export type UserMethodList = { [functionIdentifier: string]: UserMethodDoc } & { 'constructor'?: string } - export interface UserMethodDoc { +export interface UserMethodDoc { notice: string } - - // Devdoc - export interface DeveloperDocumentation { + +// Devdoc +export interface DeveloperDocumentation { author: string title: string details: string methods: DevMethodList } - - export interface DevMethodList { + +export interface DevMethodList { [functionIdentifier: string]: DevMethodDoc } - - export interface DevMethodDoc { + +export interface DevMethodDoc { author: string details: string return: string @@ -907,11 +905,11 @@ export interface CommonYulAstNode { [param: string]: string } } - - ////////////// - // BYTECODE // - ////////////// - export interface BytecodeObject { + +/// /////////// +// BYTECODE // +/// /////////// +export interface BytecodeObject { /** The bytecode as a hex string. */ object: string /** Opcodes list */ @@ -925,4 +923,4 @@ export interface CommonYulAstNode { [library: string]: { start: number; length: number }[] } } - } \ No newline at end of file + } diff --git a/libs/remix-astwalker/.eslintrc b/libs/remix-astwalker/.eslintrc index 35e3aeb511..592e8bc3e9 100644 --- a/libs/remix-astwalker/.eslintrc +++ b/libs/remix-astwalker/.eslintrc @@ -2,7 +2,8 @@ "extends": "../../.eslintrc", "rules": { "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/prefer-namespace-keyword": "off" + "@typescript-eslint/prefer-namespace-keyword": "off", + "no-unused-vars": "off" }, "ignorePatterns": ["!**/*"] } diff --git a/libs/remix-astwalker/src/astWalker.ts b/libs/remix-astwalker/src/astWalker.ts index 82f19108fd..d784a6f378 100644 --- a/libs/remix-astwalker/src/astWalker.ts +++ b/libs/remix-astwalker/src/astWalker.ts @@ -1,15 +1,15 @@ -import { EventEmitter } from "events"; -import { Node, AstNode } from "./index"; +import { EventEmitter } from 'events' +import { Node, AstNode } from './index' export declare interface AstWalker { new(): EventEmitter; } -const isObject = function(obj: any): boolean { - return obj != null && obj.constructor.name === "Object" +const isObject = function (obj: any): boolean { + return obj != null && obj.constructor.name === 'Object' } -export function isAstNode(node: Record): boolean { +export function isAstNode (node: Record): boolean { return ( isObject(node) && 'id' in node && @@ -18,7 +18,7 @@ export function isAstNode(node: Record): boolean { ) } -export function isYulAstNode(node: Record): boolean { +export function isYulAstNode (node: Record): boolean { return ( isObject(node) && 'nodeType' in node && @@ -26,7 +26,6 @@ export function isYulAstNode(node: Record): boolean { ) } - /** * Crawl the given AST through the function walk(ast, callback) */ @@ -41,7 +40,7 @@ export function isYulAstNode(node: Record): boolean { * If no event for the current type, children are visited. */ export class AstWalker extends EventEmitter { - manageCallback( + manageCallback ( node: AstNode, callback: Record | Function // eslint-disable-line @typescript-eslint/ban-types ): any { @@ -51,178 +50,178 @@ export class AstWalker extends EventEmitter { // return that. if (node) { if ((node).name in callback) { - return callback[(node).name](node); + return callback[(node).name](node) } else { - return callback["*"](node); + return callback['*'](node) } } if (node) { if ((node).nodeType in callback) { /* istanbul ignore next */ - return callback[(node).nodeType](node); + return callback[(node).nodeType](node) } else { /* istanbul ignore next */ - return callback["*"](node); + return callback['*'](node) } } } - normalizeNodes(nodes: AstNode[]): AstNode[] { + normalizeNodes (nodes: AstNode[]): AstNode[] { // Remove null, undefined and empty elements if any nodes = nodes.filter(e => e) // If any element in nodes is array, extract its members const objNodes = [] - nodes.forEach(x => { + nodes.forEach(x => { if (Array.isArray(x)) objNodes.push(...x) else objNodes.push(x) - }); - + }) + // Filter duplicate nodes using id field const normalizedNodes = [] objNodes.forEach((element) => { const firstIndex = normalizedNodes.findIndex(e => e.id === element.id) - if(firstIndex == -1) normalizedNodes.push(element) + if (firstIndex === -1) normalizedNodes.push(element) }) return normalizedNodes } - getASTNodeChildren(ast: AstNode): AstNode[] { - - let nodes = ast.nodes // for ContractDefinition - || ast.body // for FunctionDefinition, ModifierDefinition, WhileStatement, DoWhileStatement, ForStatement - || ast.statements // for Block, YulBlock - || ast.members // for StructDefinition, EnumDefinition - || ast.overrides // for OverrideSpecifier - || ast.parameters // for ParameterList, EventDefinition - || ast.declarations // for VariableDeclarationStatement - || ast.expression // for Return, ExpressionStatement, FunctionCall, FunctionCallOptions, MemberAccess - || ast.components // for TupleExpression - || ast.subExpression // for UnaryOperation - || ast.eventCall // for EmitStatement - || [] - - // If 'nodes' is not an array, convert it into one, for example: ast.body - if(nodes && !Array.isArray(nodes)) { + getASTNodeChildren (ast: AstNode): AstNode[] { + let nodes = ast.nodes || // for ContractDefinition + ast.body || // for FunctionDefinition, ModifierDefinition, WhileStatement, DoWhileStatement, ForStatement + ast.statements || // for Block, YulBlock + ast.members || // for StructDefinition, EnumDefinition + ast.overrides || // for OverrideSpecifier + ast.parameters || // for ParameterList, EventDefinition + ast.declarations || // for VariableDeclarationStatement + ast.expression || // for Return, ExpressionStatement, FunctionCall, FunctionCallOptions, MemberAccess + ast.components || // for TupleExpression + ast.subExpression || // for UnaryOperation + ast.eventCall || // for EmitStatement + [] + + // If 'nodes' is not an array, convert it into one, for example: ast.body + if (nodes && !Array.isArray(nodes)) { const tempArr = [] tempArr.push(nodes) nodes = tempArr } - + // To break object referencing nodes = [...nodes] - if(ast.nodes && ast.baseContracts?.length) { // for ContractDefinition - nodes.push(...ast.baseContracts) + if (ast.nodes && ast.baseContracts?.length) { // for ContractDefinition + nodes.push(...ast.baseContracts) } else if (ast.body && ast.overrides && ast.parameters && ast.returnParameters && ast.modifiers) { // for FunctionDefinition - nodes.push(ast.overrides) - nodes.push(ast.parameters) - nodes.push(ast.returnParameters) - nodes.push(ast.modifiers) - } else if(ast.typeName) { // for VariableDeclaration, NewExpression, ElementaryTypeNameExpression - nodes.push(ast.typeName) + nodes.push(ast.overrides) + nodes.push(ast.parameters) + nodes.push(ast.returnParameters) + nodes.push(ast.modifiers) + } else if (ast.typeName) { // for VariableDeclaration, NewExpression, ElementaryTypeNameExpression + nodes.push(ast.typeName) } else if (ast.body && ast.overrides && ast.parameters) { // for ModifierDefinition - nodes.push(ast.overrides) - nodes.push(ast.parameters) + nodes.push(ast.overrides) + nodes.push(ast.parameters) } else if (ast.modifierName && ast.arguments) { // for ModifierInvocation - nodes.push(ast.modifierName) - nodes.push(ast.arguments) + nodes.push(ast.modifierName) + nodes.push(ast.arguments) } else if (ast.parameterTypes && ast.returnParameterTypes) { // for ModifierInvocation - nodes.push(ast.parameterTypes) - nodes.push(ast.returnParameterTypes) + nodes.push(ast.parameterTypes) + nodes.push(ast.returnParameterTypes) } else if (ast.keyType && ast.valueType) { // for Mapping - nodes.push(ast.keyType) - nodes.push(ast.valueType) + nodes.push(ast.keyType) + nodes.push(ast.valueType) } else if (ast.baseType && ast.length) { // for ArrayTypeName - nodes.push(ast.baseType) - nodes.push(ast.length) + nodes.push(ast.baseType) + nodes.push(ast.length) } else if (ast.AST) { // for InlineAssembly - nodes.push(ast.AST) + nodes.push(ast.AST) } else if (ast.condition && (ast.trueBody || ast.falseBody || ast.body)) { // for IfStatement, WhileStatement, DoWhileStatement - nodes.push(ast.condition) - nodes.push(ast.trueBody) - nodes.push(ast.falseBody) + nodes.push(ast.condition) + nodes.push(ast.trueBody) + nodes.push(ast.falseBody) } else if (ast.parameters && ast.block) { // for TryCatchClause - nodes.push(ast.block) + nodes.push(ast.block) } else if (ast.externalCall && ast.clauses) { // for TryStatement - nodes.push(ast.externalCall) - nodes.push(ast.clauses) + nodes.push(ast.externalCall) + nodes.push(ast.clauses) } else if (ast.body && ast.condition && ast.initializationExpression && ast.loopExpression) { // for ForStatement - nodes.push(ast.condition) - nodes.push(ast.initializationExpression) - nodes.push(ast.loopExpression) + nodes.push(ast.condition) + nodes.push(ast.initializationExpression) + nodes.push(ast.loopExpression) } else if (ast.declarations && ast.initialValue) { // for VariableDeclarationStatement - nodes.push(ast.initialValue) + nodes.push(ast.initialValue) } else if (ast.condition && (ast.trueExpression || ast.falseExpression)) { // for Conditional - nodes.push(ast.condition) - nodes.push(ast.trueExpression) - nodes.push(ast.falseExpression) + nodes.push(ast.condition) + nodes.push(ast.trueExpression) + nodes.push(ast.falseExpression) } else if (ast.leftHandSide && ast.rightHandSide) { // for Assignment - nodes.push(ast.leftHandSide) - nodes.push(ast.rightHandSide) + nodes.push(ast.leftHandSide) + nodes.push(ast.rightHandSide) } else if (ast.leftExpression && ast.rightExpression) { // for BinaryOperation - nodes.push(ast.leftExpression) - nodes.push(ast.rightExpression) + nodes.push(ast.leftExpression) + nodes.push(ast.rightExpression) } else if (ast.expression && (ast.arguments || ast.options)) { // for FunctionCall, FunctionCallOptions - nodes.push(ast.arguments ? ast.arguments : ast.options) + nodes.push(ast.arguments ? ast.arguments : ast.options) } else if (ast.baseExpression && (ast.indexExpression || (ast.startExpression && ast.endExpression))) { // for IndexAccess, IndexRangeAccess - nodes.push(ast.baseExpression) - if(ast.indexExpression) nodes.push(ast.indexExpression) - else { - nodes.push(ast.startExpression) - nodes.push(ast.endExpression) - } + nodes.push(ast.baseExpression) + if (ast.indexExpression) nodes.push(ast.indexExpression) + else { + nodes.push(ast.startExpression) + nodes.push(ast.endExpression) + } } return this.normalizeNodes(nodes) } - + // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types - walk(ast: AstNode, callback?: Function | Record) { + walk (ast: AstNode, callback?: Function | Record) { if (ast) { const children: AstNode[] = this.getASTNodeChildren(ast) if (callback) { if (callback instanceof Function) { - callback = Object({ "*": callback }); + callback = Object({ '*': callback }) } - if (!("*" in callback)) { - callback["*"] = function() { - return true; - }; + if (!('*' in callback)) { + callback['*'] = function () { + return true + } } if (this.manageCallback(ast, callback) && children?.length) { for (const k in children) { - const child = children[k]; - this.walk(child, callback); + const child = children[k] + this.walk(child, callback) } } - } else { - if (children?.length) { - for (const k in children) { - const child = children[k]; - this.emit("node", child); - this.walk(child); - } + } else { + if (children?.length) { + for (const k in children) { + const child = children[k] + this.emit('node', child) + this.walk(child) } } + } } } + // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types - walkFullInternal(ast: AstNode, callback: Function) { + walkFullInternal (ast: AstNode, callback: Function) { if (isAstNode(ast) || isYulAstNode(ast)) { // console.log(`XXX id ${ast.id}, nodeType: ${ast.nodeType}, src: ${ast.src}`); - callback(ast); + callback(ast) for (const k of Object.keys(ast)) { // Possible optimization: // if (k in ['id', 'src', 'nodeType']) continue; - const astItem = ast[k]; + const astItem = ast[k] if (Array.isArray(astItem)) { for (const child of astItem) { if (child) { - this.walkFullInternal(child, callback); + this.walkFullInternal(child, callback) } } } else { - this.walkFullInternal(astItem, callback); + this.walkFullInternal(astItem, callback) } } } @@ -230,19 +229,19 @@ export class AstWalker extends EventEmitter { // Normalizes parameter callback and calls walkFullInternal // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - walkFull(ast: AstNode, callback: any) { - if (isAstNode(ast) || isYulAstNode(ast)) return this.walkFullInternal(ast, callback); + walkFull (ast: AstNode, callback: any) { + if (isAstNode(ast) || isYulAstNode(ast)) return this.walkFullInternal(ast, callback) } // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types - walkAstList(sourcesList: Node, cb?: Function) { + walkAstList (sourcesList: Node, cb?: Function) { if (cb) { if (sourcesList.ast) { - this.walk(sourcesList.ast, cb); + this.walk(sourcesList.ast, cb) } } else { if (sourcesList.ast) { - this.walk(sourcesList.ast); + this.walk(sourcesList.ast) } } } diff --git a/libs/remix-astwalker/src/sourceMappings.ts b/libs/remix-astwalker/src/sourceMappings.ts index b95f707ba2..275b90f394 100644 --- a/libs/remix-astwalker/src/sourceMappings.ts +++ b/libs/remix-astwalker/src/sourceMappings.ts @@ -1,6 +1,6 @@ -import { isAstNode, isYulAstNode, AstWalker } from './astWalker'; -import { AstNode, LineColPosition, LineColRange, Location } from "./types"; -import { util } from "@remix-project/remix-lib"; +import { isAstNode, isYulAstNode, AstWalker } from './astWalker' +import { AstNode, LineColPosition, LineColRange, Location } from './types' +import { util } from '@remix-project/remix-lib' export declare interface SourceMappings { // eslint-disable-next-line @typescript-eslint/no-misused-new @@ -12,12 +12,12 @@ export declare interface SourceMappings { * * @param offset The character offset to convert. */ -export function lineColPositionFromOffset(offset: number, lineBreaks: Array): LineColPosition { - let line: number = util.findLowerBound(offset, lineBreaks); +export function lineColPositionFromOffset (offset: number, lineBreaks: Array): LineColPosition { + let line: number = util.findLowerBound(offset, lineBreaks) if (lineBreaks[line] !== offset) { - line += 1; + line += 1 } - const beginColumn = line === 0 ? 0 : (lineBreaks[line - 1] + 1); + const beginColumn = line === 0 ? 0 : (lineBreaks[line - 1] + 1) return { line: line + 1, character: (offset - beginColumn) + 1 @@ -30,11 +30,11 @@ export function lineColPositionFromOffset(offset: number, lineBreaks: Array{ start: parseInt(split[0], 10), @@ -59,20 +59,19 @@ export function sourceLocationFromSrc(src: string): Location { * includng "src' information. */ export class SourceMappings { - readonly source: string; readonly lineBreaks: Array; - constructor(source: string) { - this.source = source; + constructor (source: string) { + this.source = source // Create a list of line offsets which will be used to map between // character offset and line/column positions. - const lineBreaks: Array = []; + const lineBreaks: Array = [] for (let pos = source.indexOf('\n'); pos >= 0; pos = source.indexOf('\n', pos + 1)) { lineBreaks.push(pos) } - this.lineBreaks = lineBreaks; + this.lineBreaks = lineBreaks }; /** @@ -81,23 +80,23 @@ export class SourceMappings { * @param astNodeType Type of node to return or null. * @param position Character offset where AST node should be located. */ - nodesAtPosition(astNodeType: string | null, position: Location, ast: AstNode): Array { + nodesAtPosition (astNodeType: string | null, position: Location, ast: AstNode): Array { const astWalker = new AstWalker() - const found: Array = []; + const found: Array = [] - const callback = function(node: AstNode): boolean { - const nodeLocation = sourceLocationFromAstNode(node); + const callback = function (node: AstNode): boolean { + const nodeLocation = sourceLocationFromAstNode(node) if (nodeLocation && - nodeLocation.start == position.start && - nodeLocation.length == position.length) { + nodeLocation.start === position.start && + nodeLocation.length === position.length) { if (!astNodeType || astNodeType === node.nodeType) { found.push(node) } } - return true; + return true } - astWalker.walkFull(ast, callback); - return found; + astWalker.walkFull(ast, callback) + return found } /** @@ -106,25 +105,25 @@ export class SourceMappings { * @param astNodeType nodeType that a found ASTNode must be. Use "null" if any ASTNode can match. * @param sourceLocation "src" location that the AST node must match. */ - findNodeAtSourceLocation(astNodeType: string | undefined, sourceLocation: Location, ast: AstNode | null): AstNode | null { + findNodeAtSourceLocation (astNodeType: string | undefined, sourceLocation: Location, ast: AstNode | null): AstNode | null { const astWalker = new AstWalker() - let found = null; + let found = null /* FIXME: Looking at AST walker code, I don't understand a need to return a boolean. */ - const callback = function(node: AstNode) { - const nodeLocation = sourceLocationFromAstNode(node); + const callback = function (node: AstNode) { + const nodeLocation = sourceLocationFromAstNode(node) if (nodeLocation && - nodeLocation.start == sourceLocation.start && - nodeLocation.length == sourceLocation.length) { - if (astNodeType == undefined || astNodeType === node.nodeType) { - found = node; + nodeLocation.start === sourceLocation.start && + nodeLocation.length === sourceLocation.length) { + if (astNodeType === undefined || astNodeType === node.nodeType) { + found = node } } - return true; + return true } - astWalker.walkFull(ast, callback); - return found; + astWalker.walkFull(ast, callback) + return found } /** @@ -132,8 +131,8 @@ export class SourceMappings { * * @param src Solc "src" object containing attributes {source} and {length}. */ - srcToLineColumnRange(src: string): LineColRange { - const sourceLocation = sourceLocationFromSrc(src); + srcToLineColumnRange (src: string): LineColRange { + const sourceLocation = sourceLocationFromSrc(src) if (sourceLocation.start >= 0 && sourceLocation.length >= 0) { return { start: lineColPositionFromOffset(sourceLocation.start, this.lineBreaks), @@ -146,5 +145,4 @@ export class SourceMappings { } } } - } diff --git a/libs/remix-astwalker/src/types.ts b/libs/remix-astwalker/src/types.ts index 7ffc364249..784323d221 100644 --- a/libs/remix-astwalker/src/types.ts +++ b/libs/remix-astwalker/src/types.ts @@ -3,7 +3,7 @@ export interface Location { start: number; length: number; - file: number; // Would it be clearer to call this a file index? + file: number; // Would it be clearer to call this a file index? } // This is intended to be compatibile with VScode's Position. @@ -31,7 +31,7 @@ export interface Node { export interface AstNode { /* The following fields are essential, and indicates an that object is an AST node. */ - id: number; // This is unique across all nodes in an AST tree + id: number; // This is unique across all nodes in an AST tree nodeType: string; src: string; diff --git a/libs/remix-debug/.eslintrc b/libs/remix-debug/.eslintrc index d625f686ce..88ae470657 100644 --- a/libs/remix-debug/.eslintrc +++ b/libs/remix-debug/.eslintrc @@ -4,7 +4,7 @@ "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-this-alias": "off", - "camelcase": "off" + "dot-notation": "off" }, "env": { "browser": true, diff --git a/libs/remix-debug/src/code/breakpointManager.ts b/libs/remix-debug/src/code/breakpointManager.ts index 1e5abab48b..c2f72ff4ba 100644 --- a/libs/remix-debug/src/code/breakpointManager.ts +++ b/libs/remix-debug/src/code/breakpointManager.ts @@ -15,7 +15,7 @@ export class BreakpointManager { solidityProxy breakpoints locationToRowConverter - + /** * constructor * diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 53831e72f6..f909fa1215 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -12,7 +12,7 @@ export class Debugger { compilationResult debugger breakPointManager - step_manager + step_manager // eslint-disable-line camelcase vmDebuggerLogic constructor (options) { diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index 506b8f5be8..f798b7beff 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -277,7 +277,7 @@ async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, } } } - + // we check here if we are at the beginning inside a new function. // if that is the case, we have to add to locals tree the inputs and output params const functionDefinition = resolveFunctionDefinition(tree, previousSourceLocation, generatedSources) diff --git a/libs/remix-debug/src/solidity-decoder/types/RefType.ts b/libs/remix-debug/src/solidity-decoder/types/RefType.ts index 8339eee4a2..97ce46a6a0 100644 --- a/libs/remix-debug/src/solidity-decoder/types/RefType.ts +++ b/libs/remix-debug/src/solidity-decoder/types/RefType.ts @@ -16,12 +16,12 @@ export class RefType { this.basicType = 'RefType' } - decodeFromStorage(input1? : any, input2? : any) { - throw new Error('This method is abstract'); + decodeFromStorage (input1? : any, input2? : any) { + throw new Error('This method is abstract') } - decodeFromMemoryInternal(input1? : any, input2? : any, input3?: any) { - throw new Error('This method is abstract'); + decodeFromMemoryInternal (input1? : any, input2? : any, input3?: any) { + throw new Error('This method is abstract') } /** diff --git a/libs/remix-debug/src/solidity-decoder/types/ValueType.ts b/libs/remix-debug/src/solidity-decoder/types/ValueType.ts index 6d04a11d8a..61b4c65c54 100644 --- a/libs/remix-debug/src/solidity-decoder/types/ValueType.ts +++ b/libs/remix-debug/src/solidity-decoder/types/ValueType.ts @@ -14,8 +14,8 @@ export class ValueType { this.basicType = 'ValueType' } - decodeValue(input? : any) { - throw new Error('This method is abstract'); + decodeValue (input? : any) { + throw new Error('This method is abstract') } /** diff --git a/libs/remix-debug/src/trace/traceCache.ts b/libs/remix-debug/src/trace/traceCache.ts index 43e45a0373..313953b6cf 100644 --- a/libs/remix-debug/src/trace/traceCache.ts +++ b/libs/remix-debug/src/trace/traceCache.ts @@ -1,5 +1,6 @@ 'use strict' import { util } from '@remix-project/remix-lib' +// eslint-disable-next-line camelcase const { sha3_256 } = util export class TraceCache { diff --git a/libs/remix-lib/.eslintrc b/libs/remix-lib/.eslintrc index 384b2c57a7..6da901f7e1 100644 --- a/libs/remix-lib/.eslintrc +++ b/libs/remix-lib/.eslintrc @@ -1,13 +1,9 @@ { "extends": "../../.eslintrc", "rules": { - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-this-alias": "off", "standard/no-callback-literal": "off", - "camelcase": "off", - "no-unused-vars": "off" + "no-unused-vars": "off", + "dot-notation": "off" }, "env": { "browser": true, diff --git a/libs/remix-lib/src/util.ts b/libs/remix-lib/src/util.ts index 143d099a1d..5654b55234 100644 --- a/libs/remix-lib/src/util.ts +++ b/libs/remix-lib/src/util.ts @@ -163,6 +163,7 @@ export function buildCallPath (index, rootCall) { * @param {String} value - value to sha3 * @return {Object} - return sha3ied value */ +// eslint-disable-next-line camelcase export function sha3_256 (value) { if (typeof value === 'string' && value.indexOf('0x') !== 0) { value = '0x' + value diff --git a/libs/remix-simulator/.eslintrc b/libs/remix-simulator/.eslintrc index 323a044b79..8e04356c00 100644 --- a/libs/remix-simulator/.eslintrc +++ b/libs/remix-simulator/.eslintrc @@ -5,7 +5,8 @@ "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-unused-vars": "off", "camelcase": "off", - "dot-notation": "off" + "dot-notation": "off", + "no-unused-vars": "off" }, "env": { "browser": true, diff --git a/libs/remix-simulator/src/genesis.ts b/libs/remix-simulator/src/genesis.ts index c25ea42139..85795c0d83 100644 --- a/libs/remix-simulator/src/genesis.ts +++ b/libs/remix-simulator/src/genesis.ts @@ -18,4 +18,3 @@ export function generateBlock (executionContext) { executionContext.addBlock(block) }) } - diff --git a/libs/remix-simulator/src/methods/misc.ts b/libs/remix-simulator/src/methods/misc.ts index 12b6253aa9..498d4dbddf 100644 --- a/libs/remix-simulator/src/methods/misc.ts +++ b/libs/remix-simulator/src/methods/misc.ts @@ -1,5 +1,5 @@ -const version = require('../../package.json').version import Web3 from 'web3' +const version = require('../../package.json').version export function methods () { return { @@ -57,4 +57,3 @@ export function eth_compileLLL (payload, cb) { export function eth_compileSerpent (payload, cb) { cb(null, 'unsupported') } - diff --git a/libs/remix-simulator/src/methods/txProcess.ts b/libs/remix-simulator/src/methods/txProcess.ts index dba44f0e1c..8f8acb5b2f 100644 --- a/libs/remix-simulator/src/methods/txProcess.ts +++ b/libs/remix-simulator/src/methods/txProcess.ts @@ -1,4 +1,4 @@ -import { execution } from '@remix-project/remix-lib' +import { execution } from '@remix-project/remix-lib' const TxExecution = execution.txExecution const TxRunner = execution.txRunner diff --git a/libs/remix-simulator/src/provider.ts b/libs/remix-simulator/src/provider.ts index 4c8eed8ee1..afd6696a17 100644 --- a/libs/remix-simulator/src/provider.ts +++ b/libs/remix-simulator/src/provider.ts @@ -1,6 +1,5 @@ import { Blocks } from './methods/blocks' import { execution } from '@remix-project/remix-lib' -const { executionContext } = execution import { info } from './utils/logs' import merge from 'merge' @@ -12,6 +11,7 @@ import { methods as netMethods } from './methods/net' import { Transactions } from './methods/transactions' import { Debug } from './methods/debug' import { generateBlock } from './genesis' +const { executionContext } = execution export class Provider { options: Record @@ -79,13 +79,13 @@ export class Provider { isConnected () { return true } - + disconnect () { return false }; supportsSubscriptions () { - return true; + return true }; on (type, cb) { diff --git a/libs/remix-simulator/src/server.ts b/libs/remix-simulator/src/server.ts index 22b5c250c7..892ad25c74 100644 --- a/libs/remix-simulator/src/server.ts +++ b/libs/remix-simulator/src/server.ts @@ -1,13 +1,12 @@ import express from 'express' import cors from 'cors' import bodyParser from 'body-parser' -const app = express() import expressWs from 'express-ws' import { Provider } from './provider' import { log } from './utils/logs' +const app = express() class Server { - provider rpcOnly diff --git a/libs/remix-simulator/src/utils/logs.ts b/libs/remix-simulator/src/utils/logs.ts index c860e6e9bd..f51297056b 100644 --- a/libs/remix-simulator/src/utils/logs.ts +++ b/libs/remix-simulator/src/utils/logs.ts @@ -4,11 +4,11 @@ import gray from 'ansi-gray' import timestamp from 'time-stamp' import supportsColor from 'color-support' -function hasFlag(flag) { +function hasFlag (flag) { return ((typeof (process) !== 'undefined') && (process.argv.indexOf('--' + flag) !== -1)) } -function addColor(str) { +function addColor (str) { if (this.hasFlag('no-color')) { return str } @@ -24,50 +24,50 @@ function addColor(str) { return str } -function stdout(arg) { +function stdout (arg) { if (typeof (process) === 'undefined' || !process.stdout) return process.stdout.write(arg) } -function stderr(arg) { +function stderr (arg) { if (typeof (process) === 'undefined' || process.stderr) return process.stderr.write(arg) } -function getTimestamp() { +function getTimestamp () { const coloredTimestamp = this.addColor(timestamp('HH:mm:ss')) return '[' + coloredTimestamp + ']' } -export function log(...args: any[]) { +export function log (...args: any[]) { const time = this.getTimestamp() this.stdout(time + ' ') console.log(args) return this } -export function info(...args: any[]) { +export function info (...args: any[]) { const time = this.getTimestamp() this.stdout(time + ' ') console.info(args) return this } -export function dir(...args: any[]) { +export function dir (...args: any[]) { const time = this.getTimestamp() this.stdout(time + ' ') console.dir(args) return this } -export function warn(...args: any[]) { +export function warn (...args: any[]) { const time = this.getTimestamp() this.stderr(time + ' ') console.warn(args) return this } -export function error(...args: any[]) { +export function error (...args: any[]) { const time = this.getTimestamp() this.stderr(time + ' ') console.error(args) diff --git a/libs/remix-solidity/.eslintrc b/libs/remix-solidity/.eslintrc index e060220185..35c51f7ae7 100644 --- a/libs/remix-solidity/.eslintrc +++ b/libs/remix-solidity/.eslintrc @@ -1,9 +1,8 @@ { "extends": "../../.eslintrc", "rules": { - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-unused-vars": "off" + "dot-notation": "off", + "no-unused-vars": "off" }, "env": { "browser": true, diff --git a/libs/remix-solidity/src/compiler/compiler-input.ts b/libs/remix-solidity/src/compiler/compiler-input.ts index 30dfb3eda0..2baabecc79 100644 --- a/libs/remix-solidity/src/compiler/compiler-input.ts +++ b/libs/remix-solidity/src/compiler/compiler-input.ts @@ -14,8 +14,8 @@ export default (sources: Source, opts: CompilerInputOptions): string => { libraries: opts.libraries, outputSelection: { '*': { - '': [ 'ast' ], - '*': [ 'abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates', 'evm.assembly' ] + '': ['ast'], + '*': ['abi', 'metadata', 'devdoc', 'userdoc', 'evm.legacyAssembly', 'evm.bytecode', 'evm.deployedBytecode', 'evm.methodIdentifiers', 'evm.gasEstimates', 'evm.assembly'] } } } @@ -27,8 +27,7 @@ export default (sources: Source, opts: CompilerInputOptions): string => { o.language = opts.language } if (opts.language === 'Yul' && o.settings.optimizer.enabled) { - if (!o.settings.optimizer.details) - o.settings.optimizer.details = {} + if (!o.settings.optimizer.details) { o.settings.optimizer.details = {} } o.settings.optimizer.details.yul = true } return JSON.stringify(o) diff --git a/libs/remix-solidity/src/compiler/compiler-worker.ts b/libs/remix-solidity/src/compiler/compiler-worker.ts index d8c95b3cbe..7685a766f3 100644 --- a/libs/remix-solidity/src/compiler/compiler-worker.ts +++ b/libs/remix-solidity/src/compiler/compiler-worker.ts @@ -12,39 +12,39 @@ export default function (self) { // eslint-disable-line @typescript-eslint/expli const data: MessageToWorker = e.data switch (data.cmd) { case 'loadVersion': - { - delete self.Module - // NOTE: workaround some browsers? - self.Module = undefined - compileJSON = null - //importScripts() method of synchronously imports one or more scripts into the worker's scope - self.importScripts(data.data) - const compiler: solc = solc(self.Module) - compileJSON = (input) => { - try { - const missingInputsCallback = (path) => { - missingInputs.push(path) - return { 'error': 'Deferred import' } - } - return compiler.compile(input, { import: missingInputsCallback }) - } catch (exception) { - return JSON.stringify({ error: 'Uncaught JavaScript exception:\n' + exception }) + { + delete self.Module + // NOTE: workaround some browsers? + self.Module = undefined + compileJSON = null + // importScripts() method of synchronously imports one or more scripts into the worker's scope + self.importScripts(data.data) + const compiler: solc = solc(self.Module) + compileJSON = (input) => { + try { + const missingInputsCallback = (path) => { + missingInputs.push(path) + return { error: 'Deferred import' } } + return compiler.compile(input, { import: missingInputsCallback }) + } catch (exception) { + return JSON.stringify({ error: 'Uncaught JavaScript exception:\n' + exception }) } - self.postMessage({ - cmd: 'versionLoaded', - data: compiler.version() - }) - break } - + self.postMessage({ + cmd: 'versionLoaded', + data: compiler.version() + }) + break + } + case 'compile': missingInputs.length = 0 - if(data.input && compileJSON) { + if (data.input && compileJSON) { self.postMessage({ - cmd: 'compiled', - job: data.job, - data: compileJSON(data.input), + cmd: 'compiled', + job: data.job, + data: compileJSON(data.input), missingInputs: missingInputs }) } diff --git a/libs/remix-solidity/src/compiler/compiler.ts b/libs/remix-solidity/src/compiler/compiler.ts index 0c8f9faca0..8c0d546ca0 100644 --- a/libs/remix-solidity/src/compiler/compiler.ts +++ b/libs/remix-solidity/src/compiler/compiler.ts @@ -4,11 +4,13 @@ import { update } from 'solc/abi' import * as webworkify from 'webworkify-webpack' import compilerInput from './compiler-input' import EventManager from '../lib/eventManager' -import { default as txHelper } from './txHelper'; -import { Source, SourceWithTarget, MessageFromWorker, CompilerState, CompilationResult, - visitContractsCallbackParam, visitContractsCallbackInterface, CompilationError, - gatherImportsCallbackInterface, - isFunctionDescription } from './types' +import txHelper from './txHelper' +import { + Source, SourceWithTarget, MessageFromWorker, CompilerState, CompilationResult, + visitContractsCallbackParam, visitContractsCallbackInterface, CompilationError, + gatherImportsCallbackInterface, + isFunctionDescription +} from './types' /* trigger compilationFinished, compilerLoaded, compilationStarted, compilationDuration @@ -41,7 +43,7 @@ export class Compiler { } this.state.compilationStartTime = null }) - + this.event.register('compilationStarted', () => { this.state.compilationStartTime = new Date().getTime() }) @@ -53,7 +55,7 @@ export class Compiler { * @param value value of key in CompilerState */ - set (key: K, value: CompilerState[K]): void { + set (key: K, value: CompilerState[K]): void { this.state[key] = value if (key === 'runs') this.state['runs'] = parseInt(value) } @@ -68,9 +70,8 @@ export class Compiler { this.gatherImports(files, missingInputs, (error, input) => { if (error) { this.state.lastCompilationResult = null - this.event.trigger('compilationFinished', [false, {'error': { formattedMessage: error, severity: 'error' }}, files]) - } else if(this.state.compileJSON && input) - this.state.compileJSON(input) + this.event.trigger('compilationFinished', [false, { error: { formattedMessage: error, severity: 'error' } }, files]) + } else if (this.state.compileJSON && input) { this.state.compileJSON(input) } }) } @@ -102,7 +103,7 @@ export class Compiler { onInternalCompilerLoaded (): void { if (this.state.worker === null) { - const compiler: any = typeof (window) !== 'undefined' && window['Module'] ? require('solc/wrapper')(window['Module']) : require('solc') + const compiler: any = typeof (window) !== 'undefined' && window['Module'] ? require('solc/wrapper')(window['Module']) : require('solc') this.state.compileJSON = (source: SourceWithTarget) => { const missingInputs: string[] = [] const missingInputsCallback = (path: string) => { @@ -111,9 +112,9 @@ export class Compiler { } let result: CompilationResult = {} try { - if(source && source.sources) { - const {optimize, runs, evmVersion, language} = this.state - const input = compilerInput(source.sources, {optimize, runs, evmVersion, language}) + if (source && source.sources) { + const { optimize, runs, evmVersion, language } = this.state + const input = compilerInput(source.sources, { optimize, runs, evmVersion, language }) result = JSON.parse(compiler.compile(input, { import: missingInputsCallback })) } } catch (exception) { @@ -138,7 +139,7 @@ export class Compiler { const checkIfFatalError = (error: CompilationError) => { // Ignore warnings and the 'Deferred import' error as those are generated by us as a workaround const isValidError = (error.message && error.message.includes('Deferred import')) ? false : error.severity !== 'warning' - if(isValidError) noFatalErrors = false + if (isValidError) noFatalErrors = false } if (data.error) checkIfFatalError(data.error) if (data.errors) data.errors.forEach((err) => checkIfFatalError(err)) @@ -151,9 +152,8 @@ export class Compiler { this.internalCompile(source.sources, missingInputs) } else { data = this.updateInterface(data) - if(source) - { - source.target = this.state.target; + if (source) { + source.target = this.state.target this.state.lastCompilationResult = { data: data, source: source @@ -167,7 +167,7 @@ export class Compiler { * @dev Load compiler using given version (used by remix-tests CLI) * @param version compiler version */ - + loadRemoteVersion (version: string): void { console.log(`Loading remote solc version ${version} ...`) const compiler: any = require('solc') @@ -183,9 +183,9 @@ export class Compiler { } let result: CompilationResult = {} try { - if(source && source.sources) { - const {optimize, runs, evmVersion, language} = this.state - const input = compilerInput(source.sources, {optimize, runs, evmVersion, language}) + if (source && source.sources) { + const { optimize, runs, evmVersion, language } = this.state + const input = compilerInput(source.sources, { optimize, runs, evmVersion, language }) result = JSON.parse(remoteCompiler.compile(input, { import: missingInputsCallback })) } } catch (exception) { @@ -203,7 +203,7 @@ export class Compiler { * @param usingWorker if true, load compiler using worker * @param url URL to load compiler from */ - + loadVersion (usingWorker: boolean, url: string): void { console.log('Loading ' + url + ' ' + (usingWorker ? 'with worker' : 'without worker')) this.event.trigger('loadingCompiler', [url, usingWorker]) @@ -222,7 +222,7 @@ export class Compiler { * @dev Load compiler using 'script' element (without worker) * @param url URL to load compiler from */ - + loadInternal (url: string): void { delete window['Module'] // NOTE: workaround some browsers? @@ -257,16 +257,16 @@ export class Compiler { const data: MessageFromWorker = msg.data switch (data.cmd) { case 'versionLoaded': - if(data.data) this.onCompilerLoaded(data.data) + if (data.data) this.onCompilerLoaded(data.data) break - case 'compiled': + case 'compiled': { let result: CompilationResult - if(data.data && data.job !== undefined && data.job >= 0) { + if (data.data && data.job !== undefined && data.job >= 0) { try { result = JSON.parse(data.data) } catch (exception) { - result = { error : { formattedMessage: 'Invalid JSON output from the compiler: ' + exception }} + result = { error: { formattedMessage: 'Invalid JSON output from the compiler: ' + exception } } } let sources: SourceWithTarget = {} if (data.job in jobs !== undefined) { @@ -281,23 +281,23 @@ export class Compiler { }) this.state.worker.addEventListener('error', (msg: Record <'data', MessageFromWorker>) => { - this.onCompilationFinished({ error: { formattedMessage: 'Worker error: ' + msg.data }}) + this.onCompilationFinished({ error: { formattedMessage: 'Worker error: ' + msg.data } }) }) this.state.compileJSON = (source: SourceWithTarget) => { - if(source && source.sources) { - const {optimize, runs, evmVersion, language} = this.state - jobs.push({sources: source}) + if (source && source.sources) { + const { optimize, runs, evmVersion, language } = this.state + jobs.push({ sources: source }) this.state.worker.postMessage({ - cmd: 'compile', - job: jobs.length - 1, - input: compilerInput(source.sources, {optimize, runs, evmVersion, language}) + cmd: 'compile', + job: jobs.length - 1, + input: compilerInput(source.sources, { optimize, runs, evmVersion, language }) }) } } this.state.worker.postMessage({ - cmd: 'loadVersion', + cmd: 'loadVersion', data: url }) } @@ -340,8 +340,7 @@ export class Compiler { } return } - if(cb) - cb(null, { 'sources': files }) + if (cb) { cb(null, { sources: files }) } } /** @@ -353,7 +352,7 @@ export class Compiler { const tmp: RegExpExecArray | null = /^(\d+.\d+.\d+)/.exec(version) return tmp ? tmp[1] : version } - + /** * @dev Update ABI according to current compiler version * @param data Compilation result @@ -366,12 +365,12 @@ export class Compiler { // yul compiler does not return any abi, // we default to accept the fallback function (which expect raw data as argument). contract.object.abi.push({ - 'payable': true, - 'stateMutability': 'payable', - 'type': 'fallback' + payable: true, + stateMutability: 'payable', + type: 'fallback' }) } - if(data && data.contracts && this.state.currentVersion) { + if (data && data.contracts && this.state.currentVersion) { const version = this.truncateVersion(this.state.currentVersion) data.contracts[contract.file][contract.name].abi = update(version, contract.object.abi) // if "constant" , payable must not be true and stateMutability must be view. @@ -379,10 +378,10 @@ export class Compiler { for (const item of data.contracts[contract.file][contract.name].abi) { if (isFunctionDescription(item) && item.constant) { item.payable = false - item.stateMutability = 'view'; - } + item.stateMutability = 'view' + } } - } + } }) return data } @@ -392,7 +391,7 @@ export class Compiler { * @param name contract name */ - getContract (name: string): Record | null { + getContract (name: string): Record | null { if (this.state.lastCompilationResult && this.state.lastCompilationResult.data && this.state.lastCompilationResult.data.contracts) { return txHelper.getContract(name, this.state.lastCompilationResult.data.contracts) } @@ -457,4 +456,3 @@ export class Compiler { return null } } - diff --git a/libs/remix-solidity/src/compiler/txHelper.ts b/libs/remix-solidity/src/compiler/txHelper.ts index c361da126e..c50bf4c7e6 100644 --- a/libs/remix-solidity/src/compiler/txHelper.ts +++ b/libs/remix-solidity/src/compiler/txHelper.ts @@ -9,7 +9,7 @@ export default { * @param contracts 'contracts' object from last compilation result */ - getContract: (contractName: string, contracts: CompilationResult["contracts"]) : Record | null => { + getContract: (contractName: string, contracts: CompilationResult['contracts']) : Record | null => { for (const file in contracts) { if (contracts[file][contractName]) { return { object: contracts[file][contractName], file: file } @@ -23,14 +23,14 @@ export default { * @param contracts - 'contracts' object from last compilation result * @param cb - callback */ - - visitContracts: (contracts: CompilationResult["contracts"], cb: visitContractsCallbackInterface) : void => { + + visitContracts: (contracts: CompilationResult['contracts'], cb: visitContractsCallbackInterface) : void => { for (const file in contracts) { for (const name in contracts[file]) { - const param: visitContractsCallbackParam = { - name: name, - object: contracts[file][name], - file: file + const param: visitContractsCallbackParam = { + name: name, + object: contracts[file][name], + file: file } if (cb(param)) return } diff --git a/libs/remix-solidity/src/compiler/types.ts b/libs/remix-solidity/src/compiler/types.ts index b3f06e6e4c..4bf4a765d0 100644 --- a/libs/remix-solidity/src/compiler/types.ts +++ b/libs/remix-solidity/src/compiler/types.ts @@ -129,7 +129,6 @@ export interface CompilerInput { } } - export interface Source { [fileName: string]: { @@ -144,7 +143,7 @@ export interface Source { export interface CompilerInputOptions { optimize: boolean | number, runs: number, - libraries?: { + libraries?: { [fileName: string]: Record }, evmVersion?: EVMVersion, @@ -191,7 +190,7 @@ export interface MessageFromWorker { } export interface visitContractsCallbackParam { - name: string, + name: string, object: CompiledContract, file: string } @@ -205,7 +204,7 @@ export interface gatherImportsCallbackInterface { } export interface CompilationResult { - error?: CompilationError, + 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 */ @@ -220,12 +219,12 @@ export interface CompilationResult { } } } - - /////////// - // ERROR // - /////////// - - export interface CompilationError { + +/// //////// +// ERROR // +/// //////// + +export interface CompilationError { /** Location within the source file */ sourceLocation?: { file: string @@ -242,7 +241,7 @@ export interface CompilationResult { /** the message formatted with source location */ formattedMessage?: string } - + type CompilationErrorType = | 'JSONError' | 'IOError' @@ -257,21 +256,21 @@ export interface CompilationResult { | 'CompilerError' | 'FatalError' | 'Warning' - - //////////// - // SOURCE // - //////////// - export interface CompilationSource { + +/// ///////// +// SOURCE // +/// ///////// +export interface CompilationSource { /** Identifier of the source (used in source maps) */ id: number /** The AST object */ ast: AstNode } - - ///////// - // AST // - ///////// - export interface AstNode { + +/// ////// +// AST // +/// ////// +export interface AstNode { absolutePath?: string exportedSymbols?: Record id: number @@ -285,8 +284,8 @@ export interface CompilationResult { symbolAliases?: Array [x: string]: any } - - export interface AstNodeAtt { + +export interface AstNodeAtt { operator?: string string?: null type?: string @@ -299,11 +298,11 @@ export interface CompilationResult { absolutePath?: string [x: string]: any } - - ////////////// - // CONTRACT // - ////////////// - export interface CompiledContract { + +/// /////////// +// 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) @@ -348,19 +347,19 @@ export interface CompilationResult { wasm: string } } - - ///////// - // ABI // - ///////// - export type ABIDescription = FunctionDescription | EventDescription - - export const isFunctionDescription = (item: ABIDescription): item is FunctionDescription => - (item as FunctionDescription).stateMutability !== undefined; - - export const isEventDescription = (item: ABIDescription): item is EventDescription => - (item as EventDescription).type === 'event'; - - export interface FunctionDescription { + +/// ////// +// ABI // +/// ////// +export type ABIDescription = FunctionDescription | EventDescription + +export const isFunctionDescription = (item: ABIDescription): item is FunctionDescription => + (item as FunctionDescription).stateMutability !== undefined + +export const isEventDescription = (item: ABIDescription): item is EventDescription => + (item as EventDescription).type === 'event' + +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 */ @@ -376,8 +375,8 @@ export interface CompilationResult { /** true if function is either pure or view, false otherwise. Default is false */ constant?: boolean } - - export interface EventDescription { + +export interface EventDescription { type: 'event' name: string inputs: ABIParameter & @@ -388,8 +387,8 @@ export interface CompilationResult { /** true if the event was declared as anonymous. */ anonymous: boolean } - - export interface ABIParameter { + +export interface ABIParameter { /** The name of the parameter */ name: string /** The canonical type of the parameter */ @@ -397,8 +396,8 @@ export interface CompilationResult { /** Used for tuple types */ components?: ABIParameter[] } - - export type ABITypeParameter = + +export type ABITypeParameter = | 'uint' | 'uint[]' // TODO : add | 'int' @@ -418,39 +417,39 @@ export interface CompilationResult { | 'tuple' | 'tuple[]' | string // Fallback - - /////////////////////////// - // NATURAL SPECIFICATION // - /////////////////////////// - - // Userdoc - export interface UserDocumentation { + +/// //////////////////////// +// NATURAL SPECIFICATION // +/// //////////////////////// + +// Userdoc +export interface UserDocumentation { methods: UserMethodList notice: string } - - export type UserMethodList = { + +export type UserMethodList = { [functionIdentifier: string]: UserMethodDoc } & { 'constructor'?: string } - export interface UserMethodDoc { +export interface UserMethodDoc { notice: string } - - // Devdoc - export interface DeveloperDocumentation { + +// Devdoc +export interface DeveloperDocumentation { author: string title: string details: string methods: DevMethodList } - - export interface DevMethodList { + +export interface DevMethodList { [functionIdentifier: string]: DevMethodDoc } - - export interface DevMethodDoc { + +export interface DevMethodDoc { author: string details: string return: string @@ -458,11 +457,11 @@ export interface CompilationResult { [param: string]: string } } - - ////////////// - // BYTECODE // - ////////////// - export interface BytecodeObject { + +/// /////////// +// BYTECODE // +/// /////////// +export interface BytecodeObject { /** The bytecode as a hex string. */ object: string /** Opcodes list */ @@ -476,4 +475,4 @@ export interface CompilationResult { [library: string]: { start: number; length: number }[] } } - } \ No newline at end of file + } diff --git a/libs/remix-tests/.eslintrc b/libs/remix-tests/.eslintrc index 8a034e188f..35c51f7ae7 100644 --- a/libs/remix-tests/.eslintrc +++ b/libs/remix-tests/.eslintrc @@ -1,11 +1,8 @@ { "extends": "../../.eslintrc", "rules": { - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/ban-ts-comment": "off" + "dot-notation": "off", + "no-unused-vars": "off" }, "env": { "browser": true, diff --git a/libs/remix-tests/src/assertionEvents.ts b/libs/remix-tests/src/assertionEvents.ts index 6f9627d9b0..f0d0f19428 100644 --- a/libs/remix-tests/src/assertionEvents.ts +++ b/libs/remix-tests/src/assertionEvents.ts @@ -1,40 +1,40 @@ const assertionEvents = [ - { - name: 'AssertionEvent', - params: ['bool', 'string', 'string'] - }, - { - name: 'AssertionEventUint', - params: ['bool', 'string', 'string', 'uint256', 'uint256'] - }, - { - name: 'AssertionEventInt', - params: ['bool', 'string', 'string', 'int256', 'int256'] - }, - { - name: 'AssertionEventBool', - params: ['bool', 'string', 'string', 'bool', 'bool'] - }, - { - name: 'AssertionEventAddress', - params: ['bool', 'string', 'string', 'address', 'address'] - }, - { - name: 'AssertionEventBytes32', - params: ['bool', 'string', 'string', 'bytes32', 'bytes32'] - }, - { - name: 'AssertionEventString', - params: ['bool', 'string', 'string', 'string', 'string'] - }, - { - name: 'AssertionEventUintInt', - params: ['bool', 'string', 'string', 'uint256', 'int256'] - }, - { - name: 'AssertionEventIntUint', - params: ['bool', 'string', 'string', 'int256', 'uint256'] - } + { + name: 'AssertionEvent', + params: ['bool', 'string', 'string'] + }, + { + name: 'AssertionEventUint', + params: ['bool', 'string', 'string', 'uint256', 'uint256'] + }, + { + name: 'AssertionEventInt', + params: ['bool', 'string', 'string', 'int256', 'int256'] + }, + { + name: 'AssertionEventBool', + params: ['bool', 'string', 'string', 'bool', 'bool'] + }, + { + name: 'AssertionEventAddress', + params: ['bool', 'string', 'string', 'address', 'address'] + }, + { + name: 'AssertionEventBytes32', + params: ['bool', 'string', 'string', 'bytes32', 'bytes32'] + }, + { + name: 'AssertionEventString', + params: ['bool', 'string', 'string', 'string', 'string'] + }, + { + name: 'AssertionEventUintInt', + params: ['bool', 'string', 'string', 'uint256', 'int256'] + }, + { + name: 'AssertionEventIntUint', + params: ['bool', 'string', 'string', 'int256', 'uint256'] + } ] -export default assertionEvents \ No newline at end of file +export default assertionEvents diff --git a/libs/remix-tests/src/compiler.ts b/libs/remix-tests/src/compiler.ts index 1d791ca536..3cf91bd9fb 100644 --- a/libs/remix-tests/src/compiler.ts +++ b/libs/remix-tests/src/compiler.ts @@ -2,26 +2,26 @@ import fs from './fileSystem' import async from 'async' import path from 'path' import Log from './logger' -const logger = new Log() -const log = logger.logger import { Compiler as RemixCompiler } from '@remix-project/remix-solidity' import { SrcIfc, CompilerConfiguration, CompilationErrors } from './types' +const logger = new Log() +const log = logger.logger function regexIndexOf (inputString: string, regex: RegExp, startpos = 0) { - const indexOf = inputString.substring(startpos).search(regex) - return (indexOf >= 0) ? (indexOf + (startpos)) : indexOf + const indexOf = inputString.substring(startpos).search(regex) + return (indexOf >= 0) ? (indexOf + (startpos)) : indexOf } function writeTestAccountsContract (accounts: string[]) { - const testAccountContract = require('../sol/tests_accounts.sol') - let body = `address[${accounts.length}] memory accounts;` - if (!accounts.length) body += ';' - else { - accounts.map((address, index) => { - body += `\naccounts[${index}] = ${address};\n` - }) - } - return testAccountContract.replace('>accounts<', body) + const testAccountContract = require('../sol/tests_accounts.sol') + let body = `address[${accounts.length}] memory accounts;` + if (!accounts.length) body += ';' + else { + accounts.map((address, index) => { + body += `\naccounts[${index}] = ${address};\n` + }) + } + return testAccountContract.replace('>accounts<', body) } /** @@ -29,47 +29,46 @@ function writeTestAccountsContract (accounts: string[]) { * @param path file path to check */ -function isRemixTestFile(path: string) { - return ['tests.sol', 'remix_tests.sol', 'remix_accounts.sol'].some(name => path.includes(name)) +function isRemixTestFile (path: string) { + return ['tests.sol', 'remix_tests.sol', 'remix_accounts.sol'].some(name => path.includes(name)) } /** * @dev Process file to prepare sources object to be passed in solc compiler input - * + * * See: https://solidity.readthedocs.io/en/latest/using-the-compiler.html#input-description - * + * * @param filePath path of file to process - * @param sources existing 'sources' object in which keys are the "global" names of the source files and + * @param sources existing 'sources' object in which keys are the "global" names of the source files and * value is object containing content of corresponding file with under key 'content' * @param isRoot True, If file is a root test contract file which is getting processed, not an imported file */ -function processFile(filePath: string, sources: SrcIfc, isRoot = false) { - const importRegEx = /import ['"](.+?)['"];/g - let group: RegExpExecArray| null = null - const isFileAlreadyInSources: boolean = Object.keys(sources).includes(filePath) +function processFile (filePath: string, sources: SrcIfc, isRoot = false) { + const importRegEx = /import ['"](.+?)['"];/g + let group: RegExpExecArray| null = null + const isFileAlreadyInSources: boolean = Object.keys(sources).includes(filePath) - // Return if file is a remix test file or already processed - if(isRemixTestFile(filePath) || isFileAlreadyInSources) - return + // Return if file is a remix test file or already processed + if (isRemixTestFile(filePath) || isFileAlreadyInSources) { return } - let content: string = fs.readFileSync(filePath, { encoding: 'utf-8' }) - const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm + let content: string = fs.readFileSync(filePath, { encoding: 'utf-8' }) + const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm - // import 'remix_tests.sol', if file is a root test contract file and doesn't already have it - if (isRoot && filePath.endsWith('_test.sol') && regexIndexOf(content, testFileImportRegEx) < 0) { - const includeTestLibs = '\nimport \'remix_tests.sol\';\n' - content = includeTestLibs.concat(content) - } - sources[filePath] = {content} - importRegEx.exec('') // Resetting state of RegEx - - // Process each 'import' in file content - while ((group = importRegEx.exec(content))) { - const importedFile: string = group[1] - const importedFilePath: string = path.join(path.dirname(filePath), importedFile) - processFile(importedFilePath, sources) - } + // import 'remix_tests.sol', if file is a root test contract file and doesn't already have it + if (isRoot && filePath.endsWith('_test.sol') && regexIndexOf(content, testFileImportRegEx) < 0) { + const includeTestLibs = '\nimport \'remix_tests.sol\';\n' + content = includeTestLibs.concat(content) + } + sources[filePath] = { content } + importRegEx.exec('') // Resetting state of RegEx + + // Process each 'import' in file content + while ((group = importRegEx.exec(content))) { + const importedFile: string = group[1] + const importedFilePath: string = path.join(path.dirname(filePath), importedFile) + processFile(importedFilePath, sources) + } } const userAgent = (typeof (navigator) !== 'undefined') && navigator.userAgent ? navigator.userAgent.toLowerCase() : '-' @@ -81,88 +80,86 @@ const isBrowser = !(typeof (window) === 'undefined' || userAgent.indexOf(' elect * @param isDirectory True, if path is a directory * @param opts Options * @param cb Callback - * + * * TODO: replace this with remix's own compiler code */ -export function compileFileOrFiles(filename: string, isDirectory: boolean, opts: any, compilerConfig: CompilerConfiguration, cb): void { - let compiler: any - const accounts: string[] = opts.accounts || [] - const sources: SrcIfc = { - 'tests.sol': { content: require('../sol/tests.sol') }, - 'remix_tests.sol': { content: require('../sol/tests.sol') }, - 'remix_accounts.sol': { content: writeTestAccountsContract(accounts) } +export function compileFileOrFiles (filename: string, isDirectory: boolean, opts: any, compilerConfig: CompilerConfiguration, cb): void { + let compiler: any + const accounts: string[] = opts.accounts || [] + const sources: SrcIfc = { + 'tests.sol': { content: require('../sol/tests.sol') }, + 'remix_tests.sol': { content: require('../sol/tests.sol') }, + 'remix_accounts.sol': { content: writeTestAccountsContract(accounts) } + } + const filepath: string = (isDirectory ? filename : path.dirname(filename)) + try { + if (!isDirectory && fs.existsSync(filename)) { + if (filename.split('.').pop() === 'sol') { + processFile(filename, sources, true) + } else { + throw new Error('Not a solidity file') + } + } else { + // walkSync only if it is a directory + let testFileCount = 0 + fs.walkSync(filepath, (foundpath: string) => { + // only process .sol files + if (foundpath.split('.').pop() === 'sol' && foundpath.endsWith('_test.sol')) { + testFileCount++ + processFile(foundpath, sources, true) + } + }) + if (testFileCount > 0) { + log.info(`${testFileCount} Solidity test file${testFileCount === 1 ? '' : 's'} found`) + } else { + log.error('No Solidity test file found. Make sure your test file ends with \'_test.sol\'') + process.exit() + } } - const filepath: string = (isDirectory ? filename : path.dirname(filename)) - try { - if(!isDirectory && fs.existsSync(filename)) { - if (filename.split('.').pop() === 'sol') { - processFile(filename, sources, true) - } else { - throw new Error('Not a solidity file') - } - } else { - // walkSync only if it is a directory - let testFileCount = 0 - fs.walkSync(filepath, (foundpath: string) => { - // only process .sol files - if (foundpath.split('.').pop() === 'sol' && foundpath.endsWith('_test.sol')) { - testFileCount++ - processFile(foundpath, sources, true) - } + } catch (e) { // eslint-disable-line no-useless-catch + throw e + } finally { + async.waterfall([ + function loadCompiler (next) { + compiler = new RemixCompiler() + if (compilerConfig) { + const { currentCompilerUrl, evmVersion, optimize, runs } = compilerConfig + if (evmVersion) compiler.set('evmVersion', evmVersion) + if (optimize) compiler.set('optimize', optimize) + if (runs) compiler.set('runs', runs) + if (currentCompilerUrl) { + compiler.loadRemoteVersion(currentCompilerUrl) + compiler.event.register('compilerLoaded', this, function (version) { + next() }) - if(testFileCount > 0) { - log.info(`${testFileCount} Solidity test file${testFileCount===1?'':'s'} found`) - } - else { - log.error(`No Solidity test file found. Make sure your test file ends with '_test.sol'`) - process.exit() - } + } else { + compiler.onInternalCompilerLoaded() + next() + } + } else { + compiler.onInternalCompilerLoaded() + next() } - - } catch (e) { // eslint-disable-line no-useless-catch - throw e - } finally { - async.waterfall([ - function loadCompiler(next) { - compiler = new RemixCompiler() - if(compilerConfig) { - const {currentCompilerUrl, evmVersion, optimize, runs} = compilerConfig - evmVersion ? compiler.set('evmVersion', evmVersion) : null - optimize ? compiler.set('optimize', optimize) : null - runs ? compiler.set('runs', runs) : null - if(currentCompilerUrl) { - compiler.loadRemoteVersion(currentCompilerUrl) - compiler.event.register('compilerLoaded', this, function (version) { - next() - }) - } else { - compiler.onInternalCompilerLoaded() - next() - } - } else { - compiler.onInternalCompilerLoaded() - next() - } - }, - function doCompilation(next) { - // @ts-ignore - compiler.event.register('compilationFinished', this, (success, data, source) => { - next(null, data) - }) - compiler.compile(sources, filepath) - } - ], function (err: Error | null | undefined, result: any) { - const error: Error[] = [] - if (result.error) error.push(result.error) - const errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error') - if (errors.length > 0) { - if (!isBrowser) require('signale').fatal(errors) - return cb(new CompilationErrors(errors)) - } - cb(err, result.contracts, result.sources) //return callback with contract details & ASTs + }, + function doCompilation (next) { + // @ts-ignore + compiler.event.register('compilationFinished', this, (success, data, source) => { + next(null, data) }) - } + compiler.compile(sources, filepath) + } + ], function (err: Error | null | undefined, result: any) { + const error: Error[] = [] + if (result.error) error.push(result.error) + const errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error') + if (errors.length > 0) { + if (!isBrowser) require('signale').fatal(errors) + return cb(new CompilationErrors(errors)) + } + cb(err, result.contracts, result.sources) // return callback with contract details & ASTs + }) + } } /** @@ -173,53 +170,53 @@ export function compileFileOrFiles(filename: string, isDirectory: boolean, opts: * @param opts Options * @param cb Callback */ -export function compileContractSources(sources: SrcIfc, compilerConfig: CompilerConfiguration, importFileCb: any, opts: any, cb): void { - let compiler, filepath: string - const accounts: string[] = opts.accounts || [] - // Iterate over sources keys. Inject test libraries. Inject test library import statements. - if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) { - sources['tests.sol'] = { content: require('../sol/tests.sol.js') } - sources['remix_tests.sol'] = { content: require('../sol/tests.sol.js') } - sources['remix_accounts.sol'] = { content: writeTestAccountsContract(accounts) } +export function compileContractSources (sources: SrcIfc, compilerConfig: CompilerConfiguration, importFileCb: any, opts: any, cb): void { + let compiler, filepath: string + const accounts: string[] = opts.accounts || [] + // Iterate over sources keys. Inject test libraries. Inject test library import statements. + if (!('remix_tests.sol' in sources) && !('tests.sol' in sources)) { + sources['tests.sol'] = { content: require('../sol/tests.sol.js') } + sources['remix_tests.sol'] = { content: require('../sol/tests.sol.js') } + sources['remix_accounts.sol'] = { content: writeTestAccountsContract(accounts) } + } + const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm + + const includeTestLibs = '\nimport \'remix_tests.sol\';\n' + for (const file in sources) { + const c: string = sources[file].content + if (file.endsWith('_test.sol') && c && regexIndexOf(c, testFileImportRegEx) < 0) { + sources[file].content = includeTestLibs.concat(c) } - const testFileImportRegEx = /^(import)\s['"](remix_tests.sol|tests.sol)['"];/gm - - const includeTestLibs = '\nimport \'remix_tests.sol\';\n' - for (const file in sources) { - const c: string = sources[file].content - if (file.endsWith('_test.sol') && c && regexIndexOf(c, testFileImportRegEx) < 0) { - sources[file].content = includeTestLibs.concat(c) - } + } + + async.waterfall([ + function loadCompiler (next) { + const { currentCompilerUrl, evmVersion, optimize, runs, usingWorker } = compilerConfig + compiler = new RemixCompiler(importFileCb) + compiler.set('evmVersion', evmVersion) + compiler.set('optimize', optimize) + compiler.set('runs', runs) + compiler.loadVersion(usingWorker, currentCompilerUrl) + // @ts-ignore + compiler.event.register('compilerLoaded', this, (version) => { + next() + }) + }, + function doCompilation (next) { + // @ts-ignore + compiler.event.register('compilationFinished', this, (success, data, source) => { + next(null, data) + }) + compiler.compile(sources, filepath) } - - async.waterfall([ - function loadCompiler (next) { - const {currentCompilerUrl, evmVersion, optimize, runs, usingWorker} = compilerConfig - compiler = new RemixCompiler(importFileCb) - compiler.set('evmVersion', evmVersion) - compiler.set('optimize', optimize) - compiler.set('runs', runs) - compiler.loadVersion(usingWorker, currentCompilerUrl) - // @ts-ignore - compiler.event.register('compilerLoaded', this, (version) => { - next() - }) - }, - function doCompilation (next) { - // @ts-ignore - compiler.event.register('compilationFinished', this, (success, data, source) => { - next(null, data) - }) - compiler.compile(sources, filepath) - } - ], function (err: Error | null | undefined , result: any) { - const error: Error[] = [] - if (result.error) error.push(result.error) - const errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error') - if (errors.length > 0) { - if (!isBrowser) require('signale').fatal(errors) - return cb(new CompilationErrors(errors)) - } - cb(err, result.contracts, result.sources) // return callback with contract details & ASTs - }) + ], function (err: Error | null | undefined, result: any) { + const error: Error[] = [] + if (result.error) error.push(result.error) + const errors = (result.errors || error).filter((e) => e.type === 'Error' || e.severity === 'error') + if (errors.length > 0) { + if (!isBrowser) require('signale').fatal(errors) + return cb(new CompilationErrors(errors)) + } + cb(err, result.contracts, result.sources) // return callback with contract details & ASTs + }) } diff --git a/libs/remix-tests/src/deployer.ts b/libs/remix-tests/src/deployer.ts index 190cc262b8..c6e00456fa 100644 --- a/libs/remix-tests/src/deployer.ts +++ b/libs/remix-tests/src/deployer.ts @@ -11,108 +11,108 @@ import { compilationInterface } from './types' * @param callback Callback */ -export function deployAll(compileResult: compilationInterface, web3: Web3, withDoubleGas: boolean, callback) { - const compiledObject = {} - const contracts = {} - let accounts: string[] = [] +export function deployAll (compileResult: compilationInterface, web3: Web3, withDoubleGas: boolean, callback) { + const compiledObject = {} + const contracts = {} + let accounts: string[] = [] - async.waterfall([ - function getAccountList(next) { - web3.eth.getAccounts((_err, _accounts) => { - accounts = _accounts - next() - }) - }, - function getContractData(next) { - for (const contractFile in compileResult) { - for (const contractName in compileResult[contractFile]) { - const contract = compileResult[contractFile][contractName] + async.waterfall([ + function getAccountList (next) { + web3.eth.getAccounts((_err, _accounts) => { + accounts = _accounts + next() + }) + }, + function getContractData (next) { + for (const contractFile in compileResult) { + for (const contractName in compileResult[contractFile]) { + const contract = compileResult[contractFile][contractName] - const className = contractName - const filename = contractFile + const className = contractName + const filename = contractFile - const abi = contract.abi - const code = contract.evm.bytecode.object + const abi = contract.abi + const code = contract.evm.bytecode.object - compiledObject[className] = {} - compiledObject[className].abi = abi - compiledObject[className].code = code - compiledObject[className].filename = filename - compiledObject[className].className = className - compiledObject[className].raw = contract + compiledObject[className] = {} + compiledObject[className].abi = abi + compiledObject[className].code = code + compiledObject[className].filename = filename + compiledObject[className].className = className + compiledObject[className].raw = contract - if (contractFile.endsWith('_test.sol')) { - compiledObject[className].isTest = true - } - } - } - next() - }, - function determineContractsToDeploy(next) { - const contractsToDeploy: string[] = ['Assert'] - const allContracts = Object.keys(compiledObject) - - for (const contractName of allContracts) { - if (contractName === 'Assert') { - continue - } - if (compiledObject[contractName].isTest) { - contractsToDeploy.push(contractName) - } - } - next(null, contractsToDeploy) - }, - function deployContracts(contractsToDeploy: string[], next) { - const deployRunner = (deployObject, contractObject, contractName, filename, callback) => { - deployObject.estimateGas().then((gasValue) => { - const gasBase = Math.ceil(gasValue * 1.2) - const gas = withDoubleGas ? gasBase * 2 : gasBase - deployObject.send({ - from: accounts[0], - gas: gas - }).on('receipt', function (receipt) { - contractObject.options.address = receipt.contractAddress - contractObject.options.from = accounts[0] - contractObject.options.gas = 5000 * 1000 - compiledObject[contractName].deployedAddress = receipt.contractAddress + if (contractFile.endsWith('_test.sol')) { + compiledObject[className].isTest = true + } + } + } + next() + }, + function determineContractsToDeploy (next) { + const contractsToDeploy: string[] = ['Assert'] + const allContracts = Object.keys(compiledObject) - contracts[contractName] = contractObject - contracts[contractName].filename = filename + for (const contractName of allContracts) { + if (contractName === 'Assert') { + continue + } + if (compiledObject[contractName].isTest) { + contractsToDeploy.push(contractName) + } + } + next(null, contractsToDeploy) + }, + function deployContracts (contractsToDeploy: string[], next) { + const deployRunner = (deployObject, contractObject, contractName, filename, callback) => { + deployObject.estimateGas().then((gasValue) => { + const gasBase = Math.ceil(gasValue * 1.2) + const gas = withDoubleGas ? gasBase * 2 : gasBase + deployObject.send({ + from: accounts[0], + gas: gas + }).on('receipt', function (receipt) { + contractObject.options.address = receipt.contractAddress + contractObject.options.from = accounts[0] + contractObject.options.gas = 5000 * 1000 + compiledObject[contractName].deployedAddress = receipt.contractAddress - callback(null, { result: { createdAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM - }).on('error', function (err) { - console.error(err) - callback(err) - }) - }) - } + contracts[contractName] = contractObject + contracts[contractName].filename = filename - async.eachOfLimit(contractsToDeploy, 1, function (contractName, index, nextEach) { - const contract = compiledObject[contractName] - const encodeDataFinalCallback = (error, contractDeployData) => { - if (error) return nextEach(error) - const contractObject = new web3.eth.Contract(contract.abi) - const deployObject = contractObject.deploy({arguments: [], data: '0x' + contractDeployData.dataHex}) - deployRunner(deployObject, contractObject, contractName, contract.filename, (error) => { nextEach(error) }) - } + callback(null, { result: { createdAddress: receipt.contractAddress } }) // TODO this will only work with JavaScriptV VM + }).on('error', function (err) { + console.error(err) + callback(err) + }) + }) + } - const encodeDataStepCallback = (msg) => { console.dir(msg) } + async.eachOfLimit(contractsToDeploy, 1, function (contractName, index, nextEach) { + const contract = compiledObject[contractName] + const encodeDataFinalCallback = (error, contractDeployData) => { + if (error) return nextEach(error) + const contractObject = new web3.eth.Contract(contract.abi) + const deployObject = contractObject.deploy({ arguments: [], data: '0x' + contractDeployData.dataHex }) + deployRunner(deployObject, contractObject, contractName, contract.filename, (error) => { nextEach(error) }) + } - const encodeDataDeployLibraryCallback = (libData, callback) => { - const abi = compiledObject[libData.data.contractName].abi - const code = compiledObject[libData.data.contractName].code - const libraryObject = new web3.eth.Contract(abi) - const deployObject = libraryObject.deploy({arguments: [], data: '0x' + code}) - deployRunner(deployObject, libraryObject, libData.data.contractName, contract.filename, callback) - } + const encodeDataStepCallback = (msg) => { console.dir(msg) } - const funAbi = null // no need to set the abi for encoding the constructor - const params = '' // we suppose that the test contract does not have any param in the constructor - execution.txFormat.encodeConstructorCallAndDeployLibraries(contractName, contract.raw, compileResult, params, funAbi, encodeDataFinalCallback, encodeDataStepCallback, encodeDataDeployLibraryCallback) - }, function (err) { - if(err) next(err) - next(null, contracts) - }) + const encodeDataDeployLibraryCallback = (libData, callback) => { + const abi = compiledObject[libData.data.contractName].abi + const code = compiledObject[libData.data.contractName].code + const libraryObject = new web3.eth.Contract(abi) + const deployObject = libraryObject.deploy({ arguments: [], data: '0x' + code }) + deployRunner(deployObject, libraryObject, libData.data.contractName, contract.filename, callback) } - ], callback) + + const funAbi = null // no need to set the abi for encoding the constructor + const params = '' // we suppose that the test contract does not have any param in the constructor + execution.txFormat.encodeConstructorCallAndDeployLibraries(contractName, contract.raw, compileResult, params, funAbi, encodeDataFinalCallback, encodeDataStepCallback, encodeDataDeployLibraryCallback) + }, function (err) { + if (err) next(err) + next(null, contracts) + }) + } + ], callback) } diff --git a/libs/remix-tests/src/fileSystem.ts b/libs/remix-tests/src/fileSystem.ts index 6930ca576f..7b3729ebda 100644 --- a/libs/remix-tests/src/fileSystem.ts +++ b/libs/remix-tests/src/fileSystem.ts @@ -1,20 +1,20 @@ // Extend fs -const fs: any = require('fs') import path from 'path' +const fs: any = require('fs') // https://github.com/mikeal/node-utils/blob/master/file/lib/main.js fs.walkSync = function (start: string, callback) { - fs.readdirSync(start).forEach((name: string) => { - if (name === 'node_modules') { - return // hack - } - const abspath = path.join(start, name) - if (fs.statSync(abspath).isDirectory()) { - fs.walkSync(abspath, callback) - } else { - callback(abspath) - } - }) + fs.readdirSync(start).forEach((name: string) => { + if (name === 'node_modules') { + return // hack + } + const abspath = path.join(start, name) + if (fs.statSync(abspath).isDirectory()) { + fs.walkSync(abspath, callback) + } else { + callback(abspath) + } + }) } export = fs diff --git a/libs/remix-tests/src/logger.ts b/libs/remix-tests/src/logger.ts index 13d7a007e8..99baa17d8c 100644 --- a/libs/remix-tests/src/logger.ts +++ b/libs/remix-tests/src/logger.ts @@ -1,56 +1,57 @@ import colors from 'colors' import winston, { Logger, LoggerOptions } from 'winston' -import timestamp from 'time-stamp'; +import timestamp from 'time-stamp' import supportsColor from 'color-support' function hasFlag (flag: string) { - return ((typeof (process) !== 'undefined') && (process.argv.indexOf('--' + flag) !== -1)) + return ((typeof (process) !== 'undefined') && (process.argv.indexOf('--' + flag) !== -1)) } function addColor (str: string) { - if (hasFlag('no-color')) { - return str - } + if (hasFlag('no-color')) { + return str + } - if (hasFlag('color')) { - return colors.gray(str) - } + if (hasFlag('color')) { + return colors.gray(str) + } - if (supportsColor()) { - return colors.gray(str) - } + if (supportsColor()) { + return colors.gray(str) + } - return str + return str } function getTimestamp () { - return '[' + addColor(timestamp('HH:mm:ss')) + ']' + return '[' + addColor(timestamp('HH:mm:ss')) + ']' } // create winston logger format const logFmt = winston.format.printf((info) => { - return `${getTimestamp()} ${info.level}: ${info.message}` + return `${getTimestamp()} ${info.level}: ${info.message}` }) class Log { logger: Logger; constructor () { - this.logger = winston.createLogger({ - level: 'info', - transports: [new winston.transports.Console()], - format: winston.format.combine( - winston.format.colorize({ all: true }), - logFmt - ) - }) + this.logger = winston.createLogger({ + level: 'info', + transports: [new winston.transports.Console()], + format: winston.format.combine( + winston.format.colorize({ all: true }), + logFmt + ) + }) } - setVerbosity (v: LoggerOptions["level"]): void { - this.logger.configure({ - level: v, - transports: [new winston.transports.Console()], - format: winston.format.combine( - winston.format.colorize({ all: true }), - logFmt - ) - }) + + setVerbosity (v: LoggerOptions['level']): void { + this.logger.configure({ + level: v, + transports: [new winston.transports.Console()], + format: winston.format.combine( + winston.format.colorize({ all: true }), + logFmt + ) + }) } } diff --git a/libs/remix-tests/src/run.ts b/libs/remix-tests/src/run.ts index 38518b90dc..062016312b 100644 --- a/libs/remix-tests/src/run.ts +++ b/libs/remix-tests/src/run.ts @@ -7,29 +7,29 @@ import fs from './fileSystem' import { Provider } from '@remix-project/remix-simulator' import { CompilerConfiguration } from './types' import Log from './logger' +import colors from 'colors' const logger = new Log() const log = logger.logger -import colors from 'colors' // parse verbosity function mapVerbosity (v: number) { - const levels = { - 0: 'error', - 1: 'warn', - 2: 'info', - 3: 'verbose', - 4: 'debug', - 5: 'silly' - } - return levels[v] + const levels = { + 0: 'error', + 1: 'warn', + 2: 'info', + 3: 'verbose', + 4: 'debug', + 5: 'silly' + } + return levels[v] } function mapOptimize (v: string) { - const optimize = { - 'true': true, - 'false': false - } - return optimize[v]; + const optimize = { + true: true, + false: false + } + return optimize[v] } const version = require('../package.json').version @@ -37,92 +37,91 @@ const version = require('../package.json').version commander.version(version) commander.command('version').description('output the version number').action(function () { - console.log(version) + console.log(version) }) commander.command('help').description('output usage information').action(function () { - commander.help() + commander.help() }) // get current version commander - .option('-c, --compiler ', 'set compiler version (e.g: 0.6.1, 0.7.1 etc)') - .option('-e, --evm ', 'set EVM version (e.g: petersburg, istanbul etc)') - .option('-o, --optimize ', 'enable/disable optimization', mapOptimize) - .option('-r, --runs ', 'set runs (e.g: 150, 250 etc)') - .option('-v, --verbose ', 'set verbosity level (0 to 5)', mapVerbosity) - .action(async (testsPath) => { - - // Check if path exists - if (!fs.existsSync(testsPath)) { - log.error(testsPath + ' not found') - process.exit(1) - } - - // Check if path is for a directory - const isDirectory = fs.lstatSync(testsPath).isDirectory() - - // If path is for a file, file name must have `_test.sol` suffix - if(!isDirectory && !testsPath.endsWith('_test.sol')) { - log.error('Test filename should end with "_test.sol"') - process.exit() - } - - // Console message - console.log(colors.white('\n\t👁\t:: Running remix-tests - Unit testing for solidity ::\t👁\n')) - - // Set logger verbosity - if (commander.verbose) { - logger.setVerbosity(commander.verbose) - log.info('verbosity level set to ' + commander.verbose.blue) - } - - let compilerConfig = {} as CompilerConfiguration - if (commander.compiler) { - const compVersion = commander.compiler - const baseURL = 'https://binaries.soliditylang.org/wasm/' - const response: AxiosResponse = await axios.get(baseURL + 'list.json') - const { releases, latestRelease } = response.data - const compString = releases[compVersion] - if(!compString) { - log.error(`No compiler found in releases with version ${compVersion}`) - process.exit() - } else { - compilerConfig.currentCompilerUrl = compString.replace('soljson-', '').replace('.js', '') - log.info(`Compiler version set to ${compVersion}. Latest version is ${latestRelease}`) - } - } - - if (commander.evm) { - compilerConfig.evmVersion = commander.evm - log.info(`EVM set to ${compilerConfig.evmVersion}`) - } - - if (commander.optimize) { - compilerConfig.optimize = commander.optimize - log.info(`Optimization is ${compilerConfig.optimize ? 'enabled' : 'disabled'}`) - } - - if (commander.runs) { - if(!commander.optimize) { - log.error(`Optimization should be enabled for runs`) - process.exit() - } - compilerConfig.runs = commander.runs - log.info(`Runs set to ${compilerConfig.runs}`) - } - - const web3 = new Web3() - const provider: any = new Provider() - await provider.init() - web3.setProvider(provider) - - runTestFiles(path.resolve(testsPath), isDirectory, web3, compilerConfig) - }) + .option('-c, --compiler ', 'set compiler version (e.g: 0.6.1, 0.7.1 etc)') + .option('-e, --evm ', 'set EVM version (e.g: petersburg, istanbul etc)') + .option('-o, --optimize ', 'enable/disable optimization', mapOptimize) + .option('-r, --runs ', 'set runs (e.g: 150, 250 etc)') + .option('-v, --verbose ', 'set verbosity level (0 to 5)', mapVerbosity) + .action(async (testsPath) => { + // Check if path exists + if (!fs.existsSync(testsPath)) { + log.error(testsPath + ' not found') + process.exit(1) + } + + // Check if path is for a directory + const isDirectory = fs.lstatSync(testsPath).isDirectory() + + // If path is for a file, file name must have `_test.sol` suffix + if (!isDirectory && !testsPath.endsWith('_test.sol')) { + log.error('Test filename should end with "_test.sol"') + process.exit() + } + + // Console message + console.log(colors.white('\n\t👁\t:: Running remix-tests - Unit testing for solidity ::\t👁\n')) + + // Set logger verbosity + if (commander.verbose) { + logger.setVerbosity(commander.verbose) + log.info('verbosity level set to ' + commander.verbose.blue) + } + + const compilerConfig = {} as CompilerConfiguration + if (commander.compiler) { + const compVersion = commander.compiler + const baseURL = 'https://binaries.soliditylang.org/wasm/' + const response: AxiosResponse = await axios.get(baseURL + 'list.json') + const { releases, latestRelease } = response.data + const compString = releases[compVersion] + if (!compString) { + log.error(`No compiler found in releases with version ${compVersion}`) + process.exit() + } else { + compilerConfig.currentCompilerUrl = compString.replace('soljson-', '').replace('.js', '') + log.info(`Compiler version set to ${compVersion}. Latest version is ${latestRelease}`) + } + } + + if (commander.evm) { + compilerConfig.evmVersion = commander.evm + log.info(`EVM set to ${compilerConfig.evmVersion}`) + } + + if (commander.optimize) { + compilerConfig.optimize = commander.optimize + log.info(`Optimization is ${compilerConfig.optimize ? 'enabled' : 'disabled'}`) + } + + if (commander.runs) { + if (!commander.optimize) { + log.error('Optimization should be enabled for runs') + process.exit() + } + compilerConfig.runs = commander.runs + log.info(`Runs set to ${compilerConfig.runs}`) + } + + const web3 = new Web3() + const provider: any = new Provider() + await provider.init() + web3.setProvider(provider) + + runTestFiles(path.resolve(testsPath), isDirectory, web3, compilerConfig) + }) if (!process.argv.slice(2).length) { - log.error('Please specify a file or directory path') - process.exit() + log.error('Please specify a file or directory path') + process.exit() } commander.parse(process.argv) diff --git a/libs/remix-tests/src/runTestFiles.ts b/libs/remix-tests/src/runTestFiles.ts index 25564e69a6..6bde83e44e 100644 --- a/libs/remix-tests/src/runTestFiles.ts +++ b/libs/remix-tests/src/runTestFiles.ts @@ -3,7 +3,7 @@ import fs from './fileSystem' import { runTest } from './testRunner' import { TestResultInterface, ResultsInterface, CompilerConfiguration, compilationInterface, ASTInterface, Options, AstNode } from './types' import colors from 'colors' -import Web3 from 'web3'; +import Web3 from 'web3' import { compileFileOrFiles } from './compiler' import { deployAll } from './deployer' @@ -18,144 +18,143 @@ import { deployAll } from './deployer' */ // eslint-disable-next-line @typescript-eslint/no-empty-function -export function runTestFiles(filepath: string, isDirectory: boolean, web3: Web3, compilerConfig: CompilerConfiguration, finalCallback: any = () => {}, opts?: Options) { - opts = opts || {} - compilerConfig = compilerConfig || {} as CompilerConfiguration - const sourceASTs: any = {} - const { Signale } = require('signale') - // signale configuration - const options = { - types: { - result: { - badge: '\t✓', - label: '', - color: 'greenBright' - }, - name: { - badge: '\n\t◼', - label: '', - color: 'white' - }, - error: { - badge: '\t✘', - label: '', - color: 'redBright' - } - } +export function runTestFiles (filepath: string, isDirectory: boolean, web3: Web3, compilerConfig: CompilerConfiguration, finalCallback: any = () => {}, opts?: Options) { + opts = opts || {} + compilerConfig = compilerConfig || {} as CompilerConfiguration + const sourceASTs: any = {} + const { Signale } = require('signale') + // signale configuration + const options = { + types: { + result: { + badge: '\t✓', + label: '', + color: 'greenBright' + }, + name: { + badge: '\n\t◼', + label: '', + color: 'white' + }, + error: { + badge: '\t✘', + label: '', + color: 'redBright' + } } - const signale = new Signale(options) - let accounts = opts['accounts'] || null - async.waterfall([ - function getAccountList (next) { - if (accounts) return next(null) - web3.eth.getAccounts((_err: Error | null | undefined, _accounts) => { - accounts = _accounts - next(null) - }) - }, - function compile(next) { - compileFileOrFiles(filepath, isDirectory, { accounts }, compilerConfig, next) - }, - function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) { - // Extract AST of test contract file source - for(const filename in asts) { - if(filename.endsWith('_test.sol')) - sourceASTs[filename] = asts[filename].ast - } - deployAll(compilationResult, web3, false, (err, contracts) => { - if (err) { - next(err) - } - next(null, compilationResult, contracts) - }) - }, - function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) { - const contractsToTest: string[] = [] - const contractsToTestDetails: any[] = [] - const gatherContractsFrom = function(filename: string) { - if (!filename.endsWith('_test.sol')) { - return - } - try { - Object.keys(compilationResult[filename]).forEach(contractName => { - contractsToTest.push(contractName) - contractsToTestDetails.push(compilationResult[filename][contractName]) - }) - } catch (e) { - console.error(e) - } - } - if (isDirectory) { - fs.walkSync(filepath, (foundpath: string) => { - gatherContractsFrom(foundpath) - }) - } else { - gatherContractsFrom(filepath) - } - next(null, contractsToTest, contractsToTestDetails, contracts) - }, - function runTests(contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) { - let totalPassing = 0 - let totalFailing = 0 - let totalTime = 0 - const errors: any[] = [] + } + const signale = new Signale(options) + let accounts = opts['accounts'] || null + async.waterfall([ + function getAccountList (next) { + if (accounts) return next(null) + web3.eth.getAccounts((_err: Error | null | undefined, _accounts) => { + accounts = _accounts + next(null) + }) + }, + function compile (next) { + compileFileOrFiles(filepath, isDirectory, { accounts }, compilerConfig, next) + }, + function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) { + // Extract AST of test contract file source + for (const filename in asts) { + if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast } + } + deployAll(compilationResult, web3, false, (err, contracts) => { + if (err) { + next(err) + } + next(null, compilationResult, contracts) + }) + }, + function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) { + const contractsToTest: string[] = [] + const contractsToTestDetails: any[] = [] + const gatherContractsFrom = function (filename: string) { + if (!filename.endsWith('_test.sol')) { + return + } + try { + Object.keys(compilationResult[filename]).forEach(contractName => { + contractsToTest.push(contractName) + contractsToTestDetails.push(compilationResult[filename][contractName]) + }) + } catch (e) { + console.error(e) + } + } + if (isDirectory) { + fs.walkSync(filepath, (foundpath: string) => { + gatherContractsFrom(foundpath) + }) + } else { + gatherContractsFrom(filepath) + } + next(null, contractsToTest, contractsToTestDetails, contracts) + }, + function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) { + let totalPassing = 0 + let totalFailing = 0 + let totalTime = 0 + const errors: any[] = [] - const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) { - if(err) throw err; - if (result.type === 'contract') { - signale.name(result.value.white) - } else if (result.type === 'testPass') { - signale.result(result.value) - } else if (result.type === 'testFailure') { - signale.error(result.value.red) - errors.push(result) - } - } - const _resultsCallback = (_err: Error | null | undefined, result: ResultsInterface, cb) => { - totalPassing += result.passingNum - totalFailing += result.failureNum - totalTime += result.timePassed - cb() - } + const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) { + if (err) throw err + if (result.type === 'contract') { + signale.name(result.value.white) + } else if (result.type === 'testPass') { + signale.result(result.value) + } else if (result.type === 'testFailure') { + signale.error(result.value.red) + errors.push(result) + } + } + const _resultsCallback = (_err: Error | null | undefined, result: ResultsInterface, cb) => { + totalPassing += result.passingNum + totalFailing += result.failureNum + totalTime += result.timePassed + cb() + } - async.eachOfLimit(contractsToTest, 1, (contractName: string, index, cb) => { - try { - const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']] - runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts }, _testCallback, (err, result) => { - if (err) { - console.log(err) - return cb(err) - } - _resultsCallback(null, result, cb) - }) - } catch(e) { - console.error(e) - } - }, function (err) { - if (err) { - return next(err) - } + async.eachOfLimit(contractsToTest, 1, (contractName: string, index, cb) => { + try { + const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']] + runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts }, _testCallback, (err, result) => { + if (err) { + console.log(err) + return cb(err) + } + _resultsCallback(null, result, cb) + }) + } catch (e) { + console.error(e) + } + }, function (err) { + if (err) { + return next(err) + } - console.log('\n') - if (totalPassing > 0) { - console.log(colors.green(totalPassing + ' passing ') + colors.grey('(' + totalTime + 's)')) - } - if (totalFailing > 0) { - console.log(colors.red(totalFailing + ' failing')) - } - console.log('') + console.log('\n') + if (totalPassing > 0) { + console.log(colors.green(totalPassing + ' passing ') + colors.grey('(' + totalTime + 's)')) + } + if (totalFailing > 0) { + console.log(colors.red(totalFailing + ' failing')) + } + console.log('') - errors.forEach((error, index) => { - console.log(' ' + (index + 1) + ') ' + colors.bold(error.context + ': ') + error.value) - console.log('') - console.log(colors.red('\t error: ' + error.errMsg)) - console.log(colors.green('\t expected value to be '+ error.assertMethod + ' to: ' + error.expected)) - console.log(colors.red('\t returned: ' + error.returned)) - }) - console.log('') + errors.forEach((error, index) => { + console.log(' ' + (index + 1) + ') ' + colors.bold(error.context + ': ') + error.value) + console.log('') + console.log(colors.red('\t error: ' + error.errMsg)) + console.log(colors.green('\t expected value to be ' + error.assertMethod + ' to: ' + error.expected)) + console.log(colors.red('\t returned: ' + error.returned)) + }) + console.log('') - next() - }) - } - ], finalCallback) + next() + }) + } + ], finalCallback) } diff --git a/libs/remix-tests/src/runTestSources.ts b/libs/remix-tests/src/runTestSources.ts index 88d0f4b27a..adc9be14b0 100644 --- a/libs/remix-tests/src/runTestSources.ts +++ b/libs/remix-tests/src/runTestSources.ts @@ -1,21 +1,23 @@ import async, { ErrorCallback } from 'async' -require('colors') import { compileContractSources } from './compiler' import { deployAll } from './deployer' import { runTest } from './testRunner' -import Web3 from 'web3'; +import Web3 from 'web3' import { Provider } from '@remix-project/remix-simulator' -import { FinalResult, SrcIfc, compilationInterface, ASTInterface, Options, - TestResultInterface, AstNode, CompilerConfiguration } from './types' +import { + FinalResult, SrcIfc, compilationInterface, ASTInterface, Options, + TestResultInterface, AstNode, CompilerConfiguration +} from './types' +require('colors') const createWeb3Provider = async function () { - const web3 = new Web3() - const provider: any = new Provider() - await provider.init() - web3.setProvider(provider) - return web3 + const web3 = new Web3() + const provider: any = new Provider() + await provider.init() + web3.setProvider(provider) + return web3 } /** @@ -28,110 +30,107 @@ const createWeb3Provider = async function () { * @param importFileCb Import file callback * @param opts Options */ -export async function runTestSources(contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) { - opts = opts || {} - const sourceASTs: any = {} - const web3 = opts.web3 || await createWeb3Provider() - let accounts: string[] | null = opts.accounts || null - async.waterfall([ - function getAccountList (next) { - if (accounts) return next() - web3.eth.getAccounts((_err, _accounts) => { - accounts = _accounts - next() - }) - }, - function compile (next) { - compileContractSources(contractSources, compilerConfig, importFileCb, { accounts }, next) - }, - function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) { - for(const filename in asts) { - if(filename.endsWith('_test.sol')) - sourceASTs[filename] = asts[filename].ast - } - deployAll(compilationResult, web3, false, (err, contracts) => { - if (err) { - // If contract deployment fails because of 'Out of Gas' error, try again with double gas - // This is temporary, should be removed when remix-tests will have a dedicated UI to - // accept deployment params from UI - if(err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) { - deployAll(compilationResult, web3, true, (error, contracts) => { - if (error) next([{message: 'contract deployment failed after trying twice: ' + error.message, severity: 'error'}]) // IDE expects errors in array - else next(null, compilationResult, contracts) - }) - } else - next([{message: 'contract deployment failed: ' + err.message, severity: 'error'}]) // IDE expects errors in array - } else - next(null, compilationResult, contracts) +export async function runTestSources (contractSources: SrcIfc, compilerConfig: CompilerConfiguration, testCallback, resultCallback, finalCallback: any, importFileCb, opts: Options) { + opts = opts || {} + const sourceASTs: any = {} + const web3 = opts.web3 || await createWeb3Provider() + let accounts: string[] | null = opts.accounts || null + async.waterfall([ + function getAccountList (next) { + if (accounts) return next() + web3.eth.getAccounts((_err, _accounts) => { + accounts = _accounts + next() + }) + }, + function compile (next) { + compileContractSources(contractSources, compilerConfig, importFileCb, { accounts }, next) + }, + function deployAllContracts (compilationResult: compilationInterface, asts: ASTInterface, next) { + for (const filename in asts) { + if (filename.endsWith('_test.sol')) { sourceASTs[filename] = asts[filename].ast } + } + deployAll(compilationResult, web3, false, (err, contracts) => { + if (err) { + // If contract deployment fails because of 'Out of Gas' error, try again with double gas + // This is temporary, should be removed when remix-tests will have a dedicated UI to + // accept deployment params from UI + if (err.message.includes('The contract code couldn\'t be stored, please check your gas limit')) { + deployAll(compilationResult, web3, true, (error, contracts) => { + if (error) next([{ message: 'contract deployment failed after trying twice: ' + error.message, severity: 'error' }]) // IDE expects errors in array + else next(null, compilationResult, contracts) }) - }, - function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) { - const contractsToTest: string[] = [] - const contractsToTestDetails: any[] = [] + } else { next([{ message: 'contract deployment failed: ' + err.message, severity: 'error' }]) } // IDE expects errors in array + } else { next(null, compilationResult, contracts) } + }) + }, + function determineTestContractsToRun (compilationResult: compilationInterface, contracts: any, next) { + const contractsToTest: string[] = [] + const contractsToTestDetails: any[] = [] - for (const filename in compilationResult) { - if (!filename.endsWith('_test.sol')) { - continue - } - Object.keys(compilationResult[filename]).forEach(contractName => { - contractsToTestDetails.push(compilationResult[filename][contractName]) - contractsToTest.push(contractName) - }) - } - next(null, contractsToTest, contractsToTestDetails, contracts) - }, - function runTests(contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) { - let totalPassing = 0 - let totalFailing = 0 - let totalTime = 0 - const errors: any[] = [] - - const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) { - if (result.type === 'testFailure') { - errors.push(result) - } - testCallback(result) - } + for (const filename in compilationResult) { + if (!filename.endsWith('_test.sol')) { + continue + } + Object.keys(compilationResult[filename]).forEach(contractName => { + contractsToTestDetails.push(compilationResult[filename][contractName]) + contractsToTest.push(contractName) + }) + } + next(null, contractsToTest, contractsToTestDetails, contracts) + }, + function runTests (contractsToTest: string[], contractsToTestDetails: any[], contracts: any, next) { + let totalPassing = 0 + let totalFailing = 0 + let totalTime = 0 + const errors: any[] = [] + // eslint-disable-next-line handle-callback-err + const _testCallback = function (err: Error | null | undefined, result: TestResultInterface) { + if (result.type === 'testFailure') { + errors.push(result) + } + testCallback(result) + } - const _resultsCallback = function (_err, result, cb) { - resultCallback(_err, result, () => {}) //eslint-disable-line @typescript-eslint/no-empty-function - totalPassing += result.passingNum - totalFailing += result.failureNum - totalTime += result.timePassed - cb() - } + const _resultsCallback = function (_err, result, cb) { + resultCallback(_err, result, () => {}) // eslint-disable-line @typescript-eslint/no-empty-function + totalPassing += result.passingNum + totalFailing += result.failureNum + totalTime += result.timePassed + cb() + } - async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => { - const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']] - runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts }, _testCallback, (err, result) => { - if (err) { - return cb(err) - } - _resultsCallback(null, result, cb) - }) - }, function (err) { - if (err) { - return next(err) - } + async.eachOfLimit(contractsToTest, 1, (contractName: string, index: string | number, cb: ErrorCallback) => { + const fileAST: AstNode = sourceASTs[contracts[contractName]['filename']] + runTest(contractName, contracts[contractName], contractsToTestDetails[index], fileAST, { accounts }, _testCallback, (err, result) => { + if (err) { + return cb(err) + } + _resultsCallback(null, result, cb) + }) + }, function (err) { + if (err) { + return next(err) + } - const finalResults: FinalResult = { - totalPassing: 0, - totalFailing: 0, - totalTime: 0, - errors: [], - } + const finalResults: FinalResult = { + totalPassing: 0, + totalFailing: 0, + totalTime: 0, + errors: [] + } - finalResults.totalPassing = totalPassing || 0 - finalResults.totalFailing = totalFailing || 0 - finalResults.totalTime = totalTime || 0 - finalResults.errors = [] + finalResults.totalPassing = totalPassing || 0 + finalResults.totalFailing = totalFailing || 0 + finalResults.totalTime = totalTime || 0 + finalResults.errors = [] - errors.forEach((error, _index) => { - finalResults.errors.push({context: error.context, value: error.value, message: error.errMsg}) - }) + errors.forEach((error, _index) => { + finalResults.errors.push({ context: error.context, value: error.value, message: error.errMsg }) + }) - next(null, finalResults) - }) - } - ], finalCallback) + next(null, finalResults) + }) + } + ], finalCallback) } diff --git a/libs/remix-tests/src/testRunner.ts b/libs/remix-tests/src/testRunner.ts index 965d57758b..d64398b390 100644 --- a/libs/remix-tests/src/testRunner.ts +++ b/libs/remix-tests/src/testRunner.ts @@ -1,9 +1,11 @@ import async from 'async' import * as changeCase from 'change-case' -import Web3 from 'web3'; +import Web3 from 'web3' import assertionEvents from './assertionEvents' -import { RunListInterface, TestCbInterface, TestResultInterface, ResultCbInterface, - CompiledContract, AstNode, Options, FunctionDescription, UserDocumentation } from './types' +import { + RunListInterface, TestCbInterface, TestResultInterface, ResultCbInterface, + CompiledContract, AstNode, Options, FunctionDescription, UserDocumentation +} from './types' /** * @dev Get function name using method signature @@ -12,12 +14,12 @@ import { RunListInterface, TestCbInterface, TestResultInterface, ResultCbInterfa */ function getFunctionFullName (signature: string, methodIdentifiers: Record ): string | null { - for (const method in methodIdentifiers) { - if (signature.replace('0x', '') === methodIdentifiers[method].replace('0x', '')) { - return method - } + for (const method in methodIdentifiers) { + if (signature.replace('0x', '') === methodIdentifiers[method].replace('0x', '')) { + return method } - return null + } + return null } /** @@ -25,8 +27,8 @@ function getFunctionFullName (signature: string, methodIdentifiers: Record ): string | null { - const fullName: string | null = getFunctionFullName(signature, methodIdentifiers) - const senderRegex = /#sender: account-+(\d)/g - const accountIndex: RegExpExecArray | null = fullName && userdoc.methods[fullName] ? senderRegex.exec(userdoc.methods[fullName].notice) : null - return fullName && accountIndex ? accountIndex[1] : null + const fullName: string | null = getFunctionFullName(signature, methodIdentifiers) + const senderRegex = /#sender: account-+(\d)/g + const accountIndex: RegExpExecArray | null = fullName && userdoc.methods[fullName] ? senderRegex.exec(userdoc.methods[fullName].notice) : null + return fullName && accountIndex ? accountIndex[1] : null } /** @@ -90,10 +92,10 @@ function getOverridedSender (userdoc: UserDocumentation, signature: string, meth */ function getProvidedValue (userdoc: UserDocumentation, signature: string, methodIdentifiers: Record ): string | null { - const fullName: string | null = getFunctionFullName(signature, methodIdentifiers) - const valueRegex = /#value: (\d+)/g - const value: RegExpExecArray | null = fullName && userdoc.methods[fullName] ? valueRegex.exec(userdoc.methods[fullName].notice) : null - return fullName && value ? value[1] : null + const fullName: string | null = getFunctionFullName(signature, methodIdentifiers) + const valueRegex = /#value: (\d+)/g + const value: RegExpExecArray | null = fullName && userdoc.methods[fullName] ? valueRegex.exec(userdoc.methods[fullName].notice) : null + return fullName && value ? value[1] : null } /** @@ -103,36 +105,36 @@ function getProvidedValue (userdoc: UserDocumentation, signature: string, method */ function getAvailableFunctions (fileAST: AstNode, testContractName: string): string[] { - let funcList: string[] = [] - if(fileAST.nodes && fileAST.nodes.length > 0) { - const contractAST: AstNode[] = fileAST.nodes.filter(node => isNodeName(node, testContractName) && isNodeType(node, 'ContractDefinition')) - if(contractAST.length > 0 && contractAST[0].nodes) { - const funcNodes: AstNode[] = contractAST[0].nodes.filter(node => ((node.kind === "function" && isNodeType(node, 'FunctionDefinition')) || isNodeType(node, 'FunctionDefinition'))) - funcList = funcNodes.map(node => node.name) - } + let funcList: string[] = [] + if (fileAST.nodes && fileAST.nodes.length > 0) { + const contractAST: AstNode[] = fileAST.nodes.filter(node => isNodeName(node, testContractName) && isNodeType(node, 'ContractDefinition')) + if (contractAST.length > 0 && contractAST[0].nodes) { + const funcNodes: AstNode[] = contractAST[0].nodes.filter(node => ((node.kind === 'function' && isNodeType(node, 'FunctionDefinition')) || isNodeType(node, 'FunctionDefinition'))) + funcList = funcNodes.map(node => node.name) } - return funcList; + } + return funcList } function getAssertMethodLocation (fileAST: AstNode, testContractName: string, functionName: string, assertMethod: string): string { - if(fileAST.nodes?.length) { - const contractAST: AstNode = fileAST.nodes.find(node => isNodeName(node, testContractName) && isNodeType(node, 'ContractDefinition')) - if(contractAST?.nodes?.length) { - const funcNode: AstNode = contractAST.nodes.find(node => isNodeName(node, functionName) && isNodeType(node, 'FunctionDefinition')) - // Check if statement nodeType is 'ExpressionStatement' or 'Return', for examples: - // Assert.equal(foo.get(), 100, "initial value is not correct"); - // return Assert.equal(foo.get(), 100, "initial value is not correct"); - const expressions = funcNode.body.statements.filter(s => - isNodeTypeIn(s, ['ExpressionStatement', 'Return']) - && isNodeType(s.expression, 'FunctionCall')) - const assetExpression = expressions.find(e => e.expression.expression - && isNodeType(e.expression.expression, 'MemberAccess') - && e.expression.expression.memberName === assertMethod - && isNodeName(e.expression.expression.expression, 'Assert') - ) - return assetExpression?.expression?.src - } + if (fileAST.nodes?.length) { + const contractAST: AstNode = fileAST.nodes.find(node => isNodeName(node, testContractName) && isNodeType(node, 'ContractDefinition')) + if (contractAST?.nodes?.length) { + const funcNode: AstNode = contractAST.nodes.find(node => isNodeName(node, functionName) && isNodeType(node, 'FunctionDefinition')) + // Check if statement nodeType is 'ExpressionStatement' or 'Return', for examples: + // Assert.equal(foo.get(), 100, "initial value is not correct"); + // return Assert.equal(foo.get(), 100, "initial value is not correct"); + const expressions = funcNode.body.statements.filter(s => + isNodeTypeIn(s, ['ExpressionStatement', 'Return']) && + isNodeType(s.expression, 'FunctionCall')) + const assetExpression = expressions.find(e => e.expression.expression && + isNodeType(e.expression.expression, 'MemberAccess') && + e.expression.expression.memberName === assertMethod && + isNodeName(e.expression.expression.expression, 'Assert') + ) + return assetExpression?.expression?.src } + } } /** @@ -142,15 +144,15 @@ function getAssertMethodLocation (fileAST: AstNode, testContractName: string, fu */ function getTestFunctionsInterface (jsonInterface: FunctionDescription[], funcList: string[]): FunctionDescription[] { - const functionsInterface: FunctionDescription[] = [] - const specialFunctions: string[] = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'] - for(const func of funcList){ - if(!specialFunctions.includes(func)) { - const funcInterface: FunctionDescription | undefined = jsonInterface.find(node => node.type === 'function' && node.name === func) - if(funcInterface) functionsInterface.push(funcInterface) - } + const functionsInterface: FunctionDescription[] = [] + const specialFunctions: string[] = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'] + for (const func of funcList) { + if (!specialFunctions.includes(func)) { + const funcInterface: FunctionDescription | undefined = jsonInterface.find(node => node.type === 'function' && node.name === func) + if (funcInterface) functionsInterface.push(funcInterface) } - return functionsInterface + } + return functionsInterface } /** @@ -159,15 +161,15 @@ function getTestFunctionsInterface (jsonInterface: FunctionDescription[], funcLi */ function getSpecialFunctionsInterface (jsonInterface: FunctionDescription[]): Record { - const specialFunctionsInterface: Record = {} - const funcList: string[] = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'] - for(const func of funcList){ - const funcInterface: FunctionDescription | undefined = jsonInterface.find(node => node.type === 'function' && node.name === func) - if(funcInterface) { - specialFunctionsInterface[func] = funcInterface - } + const specialFunctionsInterface: Record = {} + const funcList: string[] = ['beforeAll', 'beforeEach', 'afterAll', 'afterEach'] + for (const func of funcList) { + const funcInterface: FunctionDescription | undefined = jsonInterface.find(node => node.type === 'function' && node.name === func) + if (funcInterface) { + specialFunctionsInterface[func] = funcInterface } - return specialFunctionsInterface + } + return specialFunctionsInterface } /** @@ -178,184 +180,182 @@ function getSpecialFunctionsInterface (jsonInterface: FunctionDescription[]): Re */ function createRunList (jsonInterface: FunctionDescription[], fileAST: AstNode, testContractName: string): RunListInterface[] { - const availableFunctions: string[] = getAvailableFunctions(fileAST, testContractName) - const testFunctionsInterface: FunctionDescription[] = getTestFunctionsInterface(jsonInterface, availableFunctions) - const specialFunctionsInterface: Record = getSpecialFunctionsInterface(jsonInterface) - const runList: RunListInterface[] = [] - - if (availableFunctions.includes('beforeAll')) { - const func = specialFunctionsInterface['beforeAll'] - runList.push({ name: 'beforeAll', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) + const availableFunctions: string[] = getAvailableFunctions(fileAST, testContractName) + const testFunctionsInterface: FunctionDescription[] = getTestFunctionsInterface(jsonInterface, availableFunctions) + const specialFunctionsInterface: Record = getSpecialFunctionsInterface(jsonInterface) + const runList: RunListInterface[] = [] + + if (availableFunctions.includes('beforeAll')) { + const func = specialFunctionsInterface['beforeAll'] + runList.push({ name: 'beforeAll', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) + } + + for (const func of testFunctionsInterface) { + if (availableFunctions.includes('beforeEach')) { + const func = specialFunctionsInterface['beforeEach'] + runList.push({ name: 'beforeEach', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) } - - for (const func of testFunctionsInterface) { - if (availableFunctions.includes('beforeEach')) { - const func = specialFunctionsInterface['beforeEach'] - runList.push({ name: 'beforeEach', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) - } - if(func.name && func.inputs) runList.push({ name: func.name, inputs: func.inputs, signature: func.signature, type: 'test', constant: isConstant(func), payable: isPayable(func) }) - if (availableFunctions.indexOf('afterEach') >= 0) { - const func = specialFunctionsInterface['afterEach'] - runList.push({ name: 'afterEach', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) - } + if (func.name && func.inputs) runList.push({ name: func.name, inputs: func.inputs, signature: func.signature, type: 'test', constant: isConstant(func), payable: isPayable(func) }) + if (availableFunctions.indexOf('afterEach') >= 0) { + const func = specialFunctionsInterface['afterEach'] + runList.push({ name: 'afterEach', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) } + } - if (availableFunctions.indexOf('afterAll') >= 0) { - const func = specialFunctionsInterface['afterAll'] - runList.push({ name: 'afterAll', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) - } + if (availableFunctions.indexOf('afterAll') >= 0) { + const func = specialFunctionsInterface['afterAll'] + runList.push({ name: 'afterAll', inputs: func.inputs, signature: func.signature, type: 'internal', constant: isConstant(func), payable: isPayable(func) }) + } - return runList + return runList } export function runTest (testName: string, testObject: any, contractDetails: CompiledContract, fileAST: AstNode, opts: Options, testCallback: TestCbInterface, resultsCallback: ResultCbInterface): void { - let passingNum = 0 - let failureNum = 0 - let timePassed = 0 - const isJSONInterfaceAvailable = testObject && testObject.options && testObject.options.jsonInterface - if(!isJSONInterfaceAvailable) - return resultsCallback(new Error('Contract interface not available'), { passingNum, failureNum, timePassed }) - const runList: RunListInterface[] = createRunList(testObject.options.jsonInterface, fileAST, testName) - const web3 = new Web3() - const accts: TestResultInterface = { - type: 'accountList', - value: opts.accounts - } - testCallback(undefined, accts) - - const resp: TestResultInterface = { - type: 'contract', - value: testName, - filename: testObject.filename + let passingNum = 0 + let failureNum = 0 + let timePassed = 0 + const isJSONInterfaceAvailable = testObject && testObject.options && testObject.options.jsonInterface + if (!isJSONInterfaceAvailable) { return resultsCallback(new Error('Contract interface not available'), { passingNum, failureNum, timePassed }) } + const runList: RunListInterface[] = createRunList(testObject.options.jsonInterface, fileAST, testName) + const web3 = new Web3() + const accts: TestResultInterface = { + type: 'accountList', + value: opts.accounts + } + testCallback(undefined, accts) + + const resp: TestResultInterface = { + type: 'contract', + value: testName, + filename: testObject.filename + } + testCallback(undefined, resp) + async.eachOfLimit(runList, 1, function (func, index, next) { + let sender: string | null = null + if (func.signature) { + sender = getOverridedSender(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers) + if (opts.accounts && sender) { + sender = opts.accounts[sender] + } } - testCallback(undefined, resp) - async.eachOfLimit(runList, 1, function (func, index, next) { - let sender: string | null = null - if (func.signature) { - sender = getOverridedSender(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers) - if (opts.accounts && sender) { - sender = opts.accounts[sender] - } - } - let sendParams: Record | null = null - if (sender) sendParams = { from: sender } - if(func.inputs && func.inputs.length > 0) - return resultsCallback(new Error(`Method '${func.name}' can not have parameters inside a test contract`), { passingNum, failureNum, timePassed }) - const method = testObject.methods[func.name].apply(testObject.methods[func.name], []) - const startTime = Date.now() - if (func.constant) { - method.call(sendParams).then((result) => { - const time = (Date.now() - startTime) / 1000.0 - if (result) { - const resp: TestResultInterface = { - type: 'testPass', - value: changeCase.sentenceCase(func.name), - filename: testObject.filename, - time: time, - context: testName - } - testCallback(undefined, resp) - passingNum += 1 - timePassed += time - } else { - const resp: TestResultInterface = { - type: 'testFailure', - value: changeCase.sentenceCase(func.name), - filename: testObject.filename, - time: time, - errMsg: 'function returned false', - context: testName - } - testCallback(undefined, resp) - failureNum += 1 - timePassed += time - } - next() - }) + let sendParams: Record | null = null + if (sender) sendParams = { from: sender } + if (func.inputs && func.inputs.length > 0) { return resultsCallback(new Error(`Method '${func.name}' can not have parameters inside a test contract`), { passingNum, failureNum, timePassed }) } + const method = testObject.methods[func.name].apply(testObject.methods[func.name], []) + const startTime = Date.now() + if (func.constant) { + method.call(sendParams).then((result) => { + const time = (Date.now() - startTime) / 1000.0 + if (result) { + const resp: TestResultInterface = { + type: 'testPass', + value: changeCase.sentenceCase(func.name), + filename: testObject.filename, + time: time, + context: testName + } + testCallback(undefined, resp) + passingNum += 1 + timePassed += time } else { - if(func.payable) { - const value = getProvidedValue(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers) - if(value) { - if(sendParams) sendParams.value = value - else sendParams = { value } - } - } - method.send(sendParams).on('receipt', (receipt) => { - try { - const time: number = (Date.now() - startTime) / 1000.0 - const assertionEventHashes = assertionEvents.map(e => Web3.utils.sha3(e.name + '(' + e.params.join() + ')') ) - let testPassed = false - for (const i in receipt.events) { - let events = receipt.events[i] - if (!Array.isArray(events)) events = [events] - for (const event of events) { - const eIndex = assertionEventHashes.indexOf(event.raw.topics[0]) // event name topic will always be at index 0 - if (eIndex >= 0) { - const testEvent = web3.eth.abi.decodeParameters(assertionEvents[eIndex].params, event.raw.data) - if (!testEvent[0]) { - const assertMethod = testEvent[2] - if (assertMethod === 'ok') { // for 'Assert.ok' method - testEvent[3] = 'false' - testEvent[4] = 'true' - } - const location = getAssertMethodLocation(fileAST, testName, func.name, assertMethod) - const resp: TestResultInterface = { - type: 'testFailure', - value: changeCase.sentenceCase(func.name), - filename: testObject.filename, - time: time, - errMsg: testEvent[1], - context: testName, - assertMethod, - returned: testEvent[3], - expected: testEvent[4], - location - }; - testCallback(undefined, resp) - failureNum += 1 - timePassed += time - return next() - } - testPassed = true - } - } - } - - if (testPassed) { - const resp: TestResultInterface = { - type: 'testPass', - value: changeCase.sentenceCase(func.name), - filename: testObject.filename, - time: time, - context: testName - } - testCallback(undefined, resp) - passingNum += 1 - timePassed += time - } - - return next() - } catch (err) { - console.error(err) - return next(err) - } - }).on('error', function (err: Error) { - const time: number = (Date.now() - startTime) / 1000.0 - const resp: TestResultInterface = { + const resp: TestResultInterface = { + type: 'testFailure', + value: changeCase.sentenceCase(func.name), + filename: testObject.filename, + time: time, + errMsg: 'function returned false', + context: testName + } + testCallback(undefined, resp) + failureNum += 1 + timePassed += time + } + next() + }) + } else { + if (func.payable) { + const value = getProvidedValue(contractDetails.userdoc, func.signature, contractDetails.evm.methodIdentifiers) + if (value) { + if (sendParams) sendParams.value = value + else sendParams = { value } + } + } + method.send(sendParams).on('receipt', (receipt) => { + try { + const time: number = (Date.now() - startTime) / 1000.0 + const assertionEventHashes = assertionEvents.map(e => Web3.utils.sha3(e.name + '(' + e.params.join() + ')')) + let testPassed = false + for (const i in receipt.events) { + let events = receipt.events[i] + if (!Array.isArray(events)) events = [events] + for (const event of events) { + const eIndex = assertionEventHashes.indexOf(event.raw.topics[0]) // event name topic will always be at index 0 + if (eIndex >= 0) { + const testEvent = web3.eth.abi.decodeParameters(assertionEvents[eIndex].params, event.raw.data) + if (!testEvent[0]) { + const assertMethod = testEvent[2] + if (assertMethod === 'ok') { // for 'Assert.ok' method + testEvent[3] = 'false' + testEvent[4] = 'true' + } + const location = getAssertMethodLocation(fileAST, testName, func.name, assertMethod) + const resp: TestResultInterface = { type: 'testFailure', value: changeCase.sentenceCase(func.name), filename: testObject.filename, time: time, - errMsg: err.message, - context: testName - }; + errMsg: testEvent[1], + context: testName, + assertMethod, + returned: testEvent[3], + expected: testEvent[4], + location + } testCallback(undefined, resp) failureNum += 1 timePassed += time - return next() - }) + return next() + } + testPassed = true + } + } + } + + if (testPassed) { + const resp: TestResultInterface = { + type: 'testPass', + value: changeCase.sentenceCase(func.name), + filename: testObject.filename, + time: time, + context: testName + } + testCallback(undefined, resp) + passingNum += 1 + timePassed += time + } + + return next() + } catch (err) { + console.error(err) + return next(err) } - }, function(error) { - resultsCallback(error, { passingNum, failureNum, timePassed }) - }) + }).on('error', function (err: Error) { + const time: number = (Date.now() - startTime) / 1000.0 + const resp: TestResultInterface = { + type: 'testFailure', + value: changeCase.sentenceCase(func.name), + filename: testObject.filename, + time: time, + errMsg: err.message, + context: testName + } + testCallback(undefined, resp) + failureNum += 1 + timePassed += time + return next() + }) + } + }, function (error) { + resultsCallback(error, { passingNum, failureNum, timePassed }) + }) } diff --git a/libs/remix-tests/src/types.ts b/libs/remix-tests/src/types.ts index e0953d0a7c..197d929192 100644 --- a/libs/remix-tests/src/types.ts +++ b/libs/remix-tests/src/types.ts @@ -51,8 +51,8 @@ export interface Options { export interface CompilerConfiguration { currentCompilerUrl: string, - evmVersion: string, - optimize: boolean, + evmVersion: string, + optimize: boolean, usingWorker?: boolean, runs: number } @@ -64,7 +64,7 @@ export interface CompilationErrors { } export class CompilationErrors extends Error { - constructor(errors: Array) { + constructor (errors: Array) { const mapError = errors.map((e) => { return e.formattedMessage || e.message }) super(mapError.join('\n')) this.errors = errors @@ -74,9 +74,9 @@ export class CompilationErrors extends Error { /** sources object with name of the file and content **/ -//////////// +/// ///////// // SOURCE // -//////////// +/// ///////// export interface CompilationSource { /** Identifier of the source (used in source maps) */ id: number @@ -162,9 +162,9 @@ export interface CompiledContract { } } -///////// +/// ////// // ABI // -///////// +/// ////// export type ABIDescription = FunctionDescription | EventDescription export interface FunctionDescription { @@ -227,9 +227,9 @@ export type ABITypeParameter = | 'tuple[]' | string // Fallback -/////////////////////////// +/// //////////////////////// // NATURAL SPECIFICATION // -/////////////////////////// +/// //////////////////////// // Userdoc export interface UserDocumentation { @@ -267,9 +267,9 @@ export interface DevMethodDoc { } } -////////////// +/// /////////// // BYTECODE // -////////////// +/// /////////// export interface BytecodeObject { /** The bytecode as a hex string. */ object: string diff --git a/libs/remix-url-resolver/.eslintrc b/libs/remix-url-resolver/.eslintrc index a9c68a76de..4177c24ed7 100644 --- a/libs/remix-url-resolver/.eslintrc +++ b/libs/remix-url-resolver/.eslintrc @@ -1,11 +1,6 @@ { "extends": "../../.eslintrc", - "rules": { - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/explicit-module-boundary-types": "off" - }, + "rules": {}, "env": { "browser": true, "amd": true, diff --git a/libs/remix-url-resolver/src/resolve.ts b/libs/remix-url-resolver/src/resolve.ts index 7aa4646bf4..13029cefbd 100644 --- a/libs/remix-url-resolver/src/resolve.ts +++ b/libs/remix-url-resolver/src/resolve.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line no-unused-vars import axios, { AxiosResponse } from 'axios' import { BzzNode as Bzz } from '@erebos/bzz-node' @@ -27,9 +28,9 @@ export class RemixURLResolver { gistAccessToken: string protocol: string - constructor(gistToken?: string, protocol = 'http:') { + constructor (gistToken?: string, protocol = 'http:') { this.previouslyHandled = {} - this.gistAccessToken = gistToken ? gistToken : '' + this.gistAccessToken = gistToken || '' this.protocol = protocol } @@ -38,24 +39,21 @@ export class RemixURLResolver { * @param root The root of the github import statement * @param filePath path of the file in github */ - async handleGithubCall(root: string, filePath: string): Promise { - let param = '?' - param += this.gistAccessToken ? 'access_token=' + this.gistAccessToken : '' + async handleGithubCall (root: string, filePath: string): Promise { const regex = filePath.match(/blob\/([^/]+)\/(.*)/) let reference = 'master' if (regex) { // if we have /blob/master/+path we extract the branch name "master" and add it as a parameter to the github api // the ref can be branch name, tag, commit id reference = regex[1] - param += '&ref=' + reference filePath = filePath.replace(`blob/${reference}/`, '') } - //eslint-disable-next-line no-useless-catch + // eslint-disable-next-line no-useless-catch try { const req: string = `https://raw.githubusercontent.com/${root}/${reference}/${filePath}` const response: AxiosResponse = await axios.get(req) return { content: response.data, cleanUrl: root + '/' + filePath } - } catch(e) { + } catch (e) { throw e } } @@ -65,12 +63,12 @@ export class RemixURLResolver { * @param url The url of the import statement * @param cleanUrl */ - async handleHttp(url: string, cleanUrl: string): Promise { - //eslint-disable-next-line no-useless-catch + async handleHttp (url: string, cleanUrl: string): Promise { + // eslint-disable-next-line no-useless-catch try { const response: AxiosResponse = await axios.get(url) - return { content: response.data, cleanUrl} - } catch(e) { + return { content: response.data, cleanUrl } + } catch (e) { throw e } } @@ -80,24 +78,24 @@ export class RemixURLResolver { * @param url The url of the import statement * @param cleanUrl */ - async handleHttps(url: string, cleanUrl: string): Promise { - //eslint-disable-next-line no-useless-catch + async handleHttps (url: string, cleanUrl: string): Promise { + // eslint-disable-next-line no-useless-catch try { const response: AxiosResponse = await axios.get(url) return { content: response.data, cleanUrl } - } catch(e) { + } catch (e) { throw e } } - async handleSwarm(url: string, cleanUrl: string): Promise { - //eslint-disable-next-line no-useless-catch + async handleSwarm (url: string, cleanUrl: string): Promise { + // eslint-disable-next-line no-useless-catch try { - const bzz = new Bzz({url: this.protocol + '//swarm-gateways.net'}); - const url = bzz.getDownloadURL(cleanUrl, {mode: 'raw'}) + const bzz = new Bzz({ url: this.protocol + '//swarm-gateways.net' }) + const url = bzz.getDownloadURL(cleanUrl, { mode: 'raw' }) const response: AxiosResponse = await axios.get(url) return { content: response.data, cleanUrl } - } catch(e) { + } catch (e) { throw e } } @@ -106,10 +104,10 @@ export class RemixURLResolver { * Handle an import statement based on IPFS * @param url The url of the IPFS import statement */ - async handleIPFS(url: string): Promise { + async handleIPFS (url: string): Promise { // replace ipfs:// with /ipfs/ url = url.replace(/^ipfs:\/\/?/, 'ipfs/') - //eslint-disable-next-line no-useless-catch + // eslint-disable-next-line no-useless-catch try { const req = 'https://ipfsgw.komputing.org/' + url // If you don't find greeter.sol on ipfs gateway use local @@ -121,11 +119,11 @@ export class RemixURLResolver { } } - /** + /** * Handle an import statement based on NPM * @param url The url of the NPM import statement */ - async handleNpmImport(url: string): Promise { + async handleNpmImport (url: string): Promise { // eslint-disable-next-line no-useless-catch try { const req = 'https://unpkg.com/' + url @@ -136,7 +134,7 @@ export class RemixURLResolver { } } - getHandlers(): Handler[] { + getHandlers (): Handler[] { return [ { type: 'github', @@ -171,9 +169,9 @@ export class RemixURLResolver { ] } - public async resolve(filePath: string, customHandlers?: Handler[]): Promise { + public async resolve (filePath: string, customHandlers?: Handler[]): Promise { let imported: Imported = this.previouslyHandled[filePath] - if(imported) { + if (imported) { return imported } const builtinHandlers: Handler[] = this.getHandlers() @@ -184,7 +182,7 @@ export class RemixURLResolver { const { content, cleanUrl } = await handler.handle(match) imported = { content, - cleanUrl : cleanUrl ? cleanUrl : filePath, + cleanUrl: cleanUrl || filePath, type: handler.type } this.previouslyHandled[filePath] = imported diff --git a/package.json b/package.json index 2fa081708a..ed1b4ca18f 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "workspace-schematic": "nx workspace-schematic", "dep-graph": "nx dep-graph", "help": "nx help", - "lint:libs": "nx run-many --target=lint --projects=remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui", + "lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-file-explorer,remix-ui-debugger-ui", "build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd", "publish:libs": "npm run build:libs & lerna publish --skip-git & npm run bumpVersion:libs", diff --git a/workspace.json b/workspace.json index f65a571d09..3b1e4d5880 100644 --- a/workspace.json +++ b/workspace.json @@ -347,7 +347,7 @@ "linter": "eslint", "config": "libs/remix-tests/.eslintrc", "tsConfig": ["libs/remix-tests/tsconfig.lib.json"], - "exclude": ["**/node_modules/**", "libs/remix-tests/tests/**/*"] + "exclude": ["**/node_modules/**", "libs/remix-tests/tests/**/*", "**/dist/**"] } }, "test": {