Merge pull request #1487 from ethereum/gasCosts

gas costs analysis using AST
pull/5370/head
yann300 5 years ago committed by GitHub
commit e793a28f5b
  1. 117
      remix-analyzer/src/solidity-analyzer/modules/gasCosts.ts
  2. 58
      remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts
  3. 12
      remix-analyzer/src/types.ts
  4. 171
      remix-analyzer/test/analysis/astBlocks/funcDefForComplexParams.json
  5. 3
      remix-analyzer/test/analysis/astBlocks/index.js
  6. 84
      remix-analyzer/test/analysis/compilationDetails/CompiledContractObj.json
  7. 11
      remix-analyzer/test/analysis/staticAnalysisCommon-test.ts
  8. 4
      remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.ts
  9. 2
      remix-analyzer/test/analysis/test-contracts/solidity-v0.5/ballot.sol

@ -1,13 +1,8 @@
import { default as category } from './categories' import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView' import { getFunctionDefinitionName, helpers, isVariableTurnedIntoGetter, getMethodParamsSplittedTypeDesc } from './staticAnalysisCommon'
import { ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, CompiledContractObj, CompiledContract, VisitFunction, AnalyzerModule} from './../../types' import { ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, CompiledContract, AnalyzerModule,
FunctionDefinitionAstNode, VariableDeclarationAstNode } from './../../types'
interface VisitedContract {
name: string
object: CompiledContract
file: string
}
export default class gasCosts implements AnalyzerModule { export default class gasCosts implements AnalyzerModule {
name: string = `Gas costs: ` name: string = `Gas costs: `
@ -15,58 +10,80 @@ export default class gasCosts implements AnalyzerModule {
category: ModuleCategory = category.GAS category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
abstractAst: AbstractAst = new AbstractAst() warningNodes: any[] = []
visit: VisitFunction = this.abstractAst.build_visit((node: any) => false) visit (node: FunctionDefinitionAstNode | VariableDeclarationAstNode): void {
if ((node.nodeType === 'FunctionDefinition' && node.kind !== 'constructor' && node.implemented) ||
(node.nodeType === 'VariableDeclaration' && isVariableTurnedIntoGetter(node)))
this.warningNodes.push(node)
}
report (compilationResults: CompilationResult): ReportObj[] { report (compilationResults: CompilationResult): ReportObj[] {
const report: ReportObj[] = [] const report: ReportObj[] = []
this.visitContracts(compilationResults.contracts, (contract: VisitedContract) => { const methodsWithSignature: Record<string, string>[] = this.warningNodes.map(node => {
if ( let signature: string;
!contract.object.evm.gasEstimates || if(node.nodeType === 'FunctionDefinition'){
!contract.object.evm.gasEstimates.external const functionName: string = getFunctionDefinitionName(node)
) { signature = helpers.buildAbiSignature(functionName, getMethodParamsSplittedTypeDesc(node, compilationResults.contracts))
return
} }
const fallback: string = contract.object.evm.gasEstimates.external[''] else
if (fallback !== undefined) { signature = node.name + '()'
if (fallback === null || parseInt(fallback) >= 2100 || fallback === 'infinite') {
report.push({ return {
warning: `Fallback function of contract ${contract.name} requires too much gas (${fallback}). name: node.name,
If the fallback function requires more than 2300 gas, the contract cannot receive Ether.` src: node.src,
}) signature: signature
}
} }
})
for (var functionName in contract.object.evm.gasEstimates.external) { for (const method of methodsWithSignature) {
if (functionName === '') { for (const filename in compilationResults.contracts) {
continue for (const contractName in compilationResults.contracts[filename]) {
} const contract: CompiledContract = compilationResults.contracts[filename][contractName]
const gas: string = contract.object.evm.gasEstimates.external[functionName] const methodGas: Record<string, any> | undefined = this.checkMethodGas(contract, method.signature)
const gasString: string = gas === null ? 'unknown or not constant' : 'high: ' + gas if(methodGas && methodGas.isInfinite) {
if (gas === null || parseInt(gas) >= 3000000 || gas === 'infinite') { if(methodGas.isFallback) {
report.push({ report.push({
warning: `Gas requirement of function ${contract.name}.${functionName} ${gasString}. warning: `Fallback function of contract ${contractName} requires too much gas (${methodGas.msg}).
If the gas requirement of a function is higher than the block gas limit, it cannot be executed. If the fallback function requires more than 2300 gas, the contract cannot receive Ether.`,
Please avoid loops in your functions or actions that modify large areas of storage location: method.src
(this includes clearing or copying arrays in storage)` })
}) } else {
report.push({
warning: `Gas requirement of function ${contractName}.${method.name} ${methodGas.msg}.
If the gas requirement of a function is higher than the block gas limit, it cannot be executed.
Please avoid loops in your functions or actions that modify large areas of storage
(this includes clearing or copying arrays in storage)`,
location: method.src
})
}
} else continue
} }
} }
}) }
return report return report
} }
/** private checkMethodGas(contract: CompiledContract, methodSignature: string): Record<string, any> | undefined {
* call the given @arg cb (function) for all the contracts. Uses last compilation result if(contract.evm && contract.evm.gasEstimates && contract.evm.gasEstimates.external) {
* stop visiting when cb return true if(methodSignature === '()') {
* @param {Function} cb - callback const fallback: string = contract.evm.gasEstimates.external['']
*/ if (fallback !== undefined && (fallback === null || parseInt(fallback) >= 2100 || fallback === 'infinite')) {
// @TODO has been copied from remix-ide repo ! should fix that soon ! return {
private visitContracts (contracts: CompiledContractObj | undefined, cb: ((contract: VisitedContract) => void | undefined)): void { isInfinite: true,
for (let file in contracts) { isFallback: true,
for (let name in contracts[file]) { msg: fallback
if (cb({ name: name, object: contracts[file][name], file: file })) return }
}
} else {
const gas: string = contract.evm.gasEstimates.external[methodSignature]
const gasString: string = gas === null ? 'unknown or not constant' : 'high: ' + gas
if (gas === null || parseInt(gas) >= 3000000 || gas === 'infinite') {
return {
isInfinite: true,
isFallback: false,
msg: gasString
}
}
} }
} }
} }
} }

@ -3,7 +3,7 @@
import { FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode, import { FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode,
WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode, WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode,
MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode, MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode,
IdentifierAstNode, IndexAccessAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode } from "types" IdentifierAstNode, IndexAccessAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode, CompiledContractObj, ABIParameter } from "types"
import { util } from 'remix-lib' import { util } from 'remix-lib'
type SpecialObjDetail = { type SpecialObjDetail = {
@ -641,6 +641,15 @@ function isConstantFunction (node: FunctionDefinitionAstNode): boolean {
return node.stateMutability === 'view' || node.stateMutability === 'pure' return node.stateMutability === 'view' || node.stateMutability === 'pure'
} }
/**
* True if variable decalaration is converted into a getter method
* @node {ASTNode} variable declaration AstNode
* @return {bool}
*/
function isVariableTurnedIntoGetter (varDeclNode: VariableDeclarationAstNode): boolean {
return varDeclNode.stateVariable && varDeclNode.visibility === 'public';
}
/** /**
* True if is function defintion has payable modifier * True if is function defintion has payable modifier
* @node {ASTNode} some AstNode * @node {ASTNode} some AstNode
@ -1068,6 +1077,51 @@ function buildAbiSignature (funName: string, paramTypes: any[]): string {
return funName + '(' + util.concatWithSeperator(paramTypes, ',') + ')' return funName + '(' + util.concatWithSeperator(paramTypes, ',') + ')'
} }
// To create the method signature similar to contract.evm.gasEstimates.external object
// For address payable, return address
function getMethodParamsSplittedTypeDesc(node: FunctionDefinitionAstNode, contracts: CompiledContractObj): string[] {
return node.parameters.parameters.map((varNode, varIndex) => {
let finalTypeString;
const typeString = varNode.typeDescriptions.typeString
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') &&
e.inputs[varIndex]['internalType'] === typeString)
if(methodABI && methodABI.inputs) {
const inputs = methodABI.inputs[varIndex]
let typeStr = getTypeStringFromComponents(inputs['components'])
finalTypeString = typeStr + inputs['type'].replace('tuple', '')
}
}
}
} else
finalTypeString = typeString.split(' ')[0]
return finalTypeString
})
}
function getTypeStringFromComponents(components: ABIParameter[]) {
let typeString = '('
for(var i=0; i < components.length; i++) {
const param = components[i]
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
if(i !== components.length - 1)
typeString = typeString + ','
}
typeString = typeString + ')'
return typeString
}
const helpers = { const helpers = {
expressionTypeDescription, expressionTypeDescription,
nodeType, nodeType,
@ -1103,12 +1157,14 @@ export {
getFunctionOrModifierDefinitionParameterPart, getFunctionOrModifierDefinitionParameterPart,
getFunctionDefinitionReturnParameterPart, getFunctionDefinitionReturnParameterPart,
getUnAssignedTopLevelBinOps, getUnAssignedTopLevelBinOps,
getMethodParamsSplittedTypeDesc,
// #################### Complex Node Identification // #################### Complex Node Identification
isDeleteOfDynamicArray, isDeleteOfDynamicArray,
isDeleteFromDynamicArray, isDeleteFromDynamicArray,
isAbiNamespaceCall, isAbiNamespaceCall,
isSpecialVariableAccess, isSpecialVariableAccess,
isVariableTurnedIntoGetter,
isDynamicArrayAccess, isDynamicArrayAccess,
isDynamicArrayLengthAccess, isDynamicArrayLengthAccess,
isMappingIndexAccess, isMappingIndexAccess,

@ -20,11 +20,11 @@ export interface ModuleCategory {
export interface ReportObj { export interface ReportObj {
warning: string, warning: string,
location?: string | null, location: string,
more?: string more?: string
} }
// Regarding location, she source mappings inside the AST use the following notation: // Regarding location, the source mappings inside the AST use the following notation:
// s:l:f // s:l:f
@ -33,7 +33,10 @@ export interface ReportObj {
// l is the length of the source range in bytes and // l is the length of the source range in bytes and
// f is the source index mentioned above. // f is the source index mentioned above.
export interface AnalysisReportObj extends ReportObj { export interface AnalysisReportObj {
warning: string,
location?: string,
more?: string
error? : string error? : string
} }
@ -51,7 +54,7 @@ export interface CompilationResult {
[contractName: string]: CompilationSource [contractName: string]: CompilationSource
} }
/** This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings */ /** This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings */
contracts?: CompiledContractObj /** If the language used has no contract names, this field should equal to an empty string. */ contracts: CompiledContractObj /** If the language used has no contract names, this field should equal to an empty string. */
} }
export interface CompiledContractObj { export interface CompiledContractObj {
@ -834,6 +837,7 @@ export interface CommonYulAstNode {
} }
export interface ABIParameter { export interface ABIParameter {
internalType: string
/** The name of the parameter */ /** The name of the parameter */
name: string name: string
/** The canonical type of the parameter */ /** The canonical type of the parameter */

@ -0,0 +1,171 @@
{
"nestedStruct": {
"body":
{ "id": 330,
"nodeType": "Block",
"src": "5031:2:0",
"statements": []
},
"documentation": null,
"id": 331,
"implemented": true,
"kind": "function",
"modifiers": [],
"name": "testWithArrayOfStruct",
"nodeType": "FunctionDefinition",
"parameters": {
"id": 328,
"nodeType": "ParameterList",
"parameters":
[
{
"constant": false,
"id": 327,
"name": "param",
"nodeType": "VariableDeclaration",
"scope": 331,
"src": "4996:26:0",
"stateVariable": false,
"storageLocation": "memory",
"typeDescriptions": {
"typeIdentifier": "t_array$_t_array$_t_struct$_Proposal1_$30_memory_ptr_$dyn_memory_ptr_$dyn_memory_ptr",
"typeString": "struct Ballot.Proposal1[][]"
},
"typeName": {
"baseType": {
"baseType": {
"contractScope": null,
"id": 332,
"name": "Proposal1",
"nodeType": "UserDefinedTypeName",
"referencedDeclaration": 30,
"src": "4996:9:0",
"typeDescriptions": {
"typeIdentifier": "t_struct$_Proposal1_$30_storage_ptr",
"typeString": "struct Ballot.Proposal1"
}
},
"id": 333,
"length": null,
"nodeType": "ArrayTypeName",
"src": "4996:11:0",
"typeDescriptions": {
"typeIdentifier": "t_array$_t_struct$_Proposal1_$30_storage_$dyn_storage_ptr",
"typeString": "struct Ballot.Proposal1[]"
}
},
"id": 334,
"length": null,
"nodeType": "ArrayTypeName",
"src": "4996:13:0",
"typeDescriptions": {
"typeIdentifier": "t_array$_t_array$_t_struct$_Proposal1_$30_storage_$dyn_storage_$dyn_storage_ptr",
"typeString": "struct Ballot.Proposal1[][]"
},
"value": null,
"visibility": "internal"
}
}
],
"src": "4995:28:0"
},
"returnParameters":
{ "id": 329,
"nodeType": "ParameterList",
"parameters": [],
"src": "5031:0:0" },
"scope": 332,
"src": "4964:69:0",
"stateMutability": "nonpayable",
"superFunction": null,
"visibility": "public"
},
"bytesArray": {
"body":
{
"id": 287,
"nodeType": "Block",
"src": "4988:2:0",
"statements": []
},
"documentation": null,
"id": 288,
"implemented": true,
"kind": "function",
"modifiers": [],
"name": "testWithArray",
"nodeType": "FunctionDefinition",
"parameters":
{ "id": 285,
"nodeType": "ParameterList",
"parameters": [
{
"constant": false,
"id": 284,
"name": "param",
"nodeType": "VariableDeclaration",
"scope": 288,
"src": "4957:22:0",
"stateVariable": false,
"storageLocation": "memory",
"typeDescriptions":
{ "typeIdentifier": "t_array$_t_bytes32_$dyn_memory_ptr",
"typeString": "bytes32[]" },
"typeName":
{ "baseType": "[Object]",
"id": 283,
"length": null,
"nodeType": "ArrayTypeName",
"src": "4957:9:0",
"typeDescriptions": "[Object]"
},
"value": null,
"visibility": "internal"
}
],
"src": "4956:24:0"
},
"returnParameters":
{ "id": 286,
"nodeType": "ParameterList",
"parameters": [],
"src": "4988:0:0" },
"scope": 289,
"src": "4933:57:0",
"stateMutability": "nonpayable",
"superFunction": null,
"visibility": "public"
},
"withoutParams" : {
"body":
{
"id": 280,
"nodeType": "Block",
"src": "4864:63:0",
"statements": "[ [Object] ]"
},
"documentation": null,
"id": 281,
"implemented": true,
"kind": "function",
"modifiers": [],
"name": "winnerName",
"nodeType": "FunctionDefinition",
"parameters":
{ "id": 268,
"nodeType": "ParameterList",
"parameters": [],
"src": "4804:2:0"
},
"returnParameters":
{ "id": 271,
"nodeType": "ParameterList",
"parameters": "[ [Object] ]",
"src": "4839:20:0"
},
"scope": 289,
"src": "4785:142:0",
"stateMutability": "view",
"superFunction": null,
"visibility": "public" }
}

@ -25,5 +25,6 @@ module.exports = {
parameterFunction: require('./parameterFunction.json'), parameterFunction: require('./parameterFunction.json'),
parameterFunctionCall: require('./parameterFunctionCall.json'), parameterFunctionCall: require('./parameterFunctionCall.json'),
inheritance: require('./inheritance.json'), inheritance: require('./inheritance.json'),
blockHashAccess: require('./blockHashAccess.json') blockHashAccess: require('./blockHashAccess.json'),
funcDefForComplexParams: require('./funcDefForComplexParams.json')
} }

@ -0,0 +1,84 @@
{ "test.sol":
{ "Ballot":
{ "abi": [
{"inputs":[{"internalType":"bytes32[]","name":"proposalNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},
{"constant":true,"inputs":[],"name":"chairperson","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},
{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"delegate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"internalType":"address","name":"voter","type":"address"}],"name":"giveRightToVote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"proposals","outputs":[{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint256","name":"voteCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
{"constant":false,"inputs":[{"internalType":"bytes32[]","name":"param","type":"bytes32[]"}],"name":"testWithArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"components":[{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint256","name":"voteCount","type":"uint256"},{"internalType":"uint256[]","name":"arr","type":"uint256[]"},{"internalType":"address payable","name":"sender","type":"address"},{"components":[{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint256","name":"voteCount","type":"uint256"}],"internalType":"struct Ballot.Proposal[]","name":"p","type":"tuple[]"}],"internalType":"struct Ballot.Proposal1[][]","name":"param","type":"tuple[][]"}],"name":"testWithArrayOfStruct","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"internalType":"uint256","name":"proposal","type":"uint256"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"voters","outputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"bool","name":"voted","type":"bool"},{"internalType":"address","name":"delegate","type":"address"},{"internalType":"uint256","name":"vote","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
{"constant":true,"inputs":[],"name":"winnerName","outputs":[{"internalType":"bytes32","name":"winnerName_","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},
{"constant":true,"inputs":[],"name":"winningProposal","outputs":[{"internalType":"uint256","name":"winningProposal_","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}
],
"evm": "[Object]",
"metadata":
{"compiler":
{
"version":"0.5.17+commit.d19bba13"
},
"language":"Solidity",
"output":{
"abi":[
{"inputs":[{"internalType":"bytes32[]","name":"proposalNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},
{"constant":true,"inputs":[],"name":"chairperson","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},
{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"delegate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"internalType":"address","name":"voter","type":"address"}],"name":"giveRightToVote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":true,"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"proposals","outputs":[{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint256","name":"voteCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
{"constant":false,"inputs":[{"internalType":"bytes32[]","name":"param","type":"bytes32[]"}],"name":"testWithArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"components":[{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint256","name":"voteCount","type":"uint256"},{"internalType":"uint256[]","name":"arr","type":"uint256[]"},{"internalType":"address payable","name":"sender","type":"address"},{"components":[{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint256","name":"voteCount","type":"uint256"}],"internalType":"struct Ballot.Proposal[]","name":"p","type":"tuple[]"}],"internalType":"struct Ballot.Proposal1[][]","name":"param","type":"tuple[][]"}],"name":"testWithArrayOfStruct","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":false,"inputs":[{"internalType":"uint256","name":"proposal","type":"uint256"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"voters","outputs":[{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"bool","name":"voted","type":"bool"},{"internalType":"address","name":"delegate","type":"address"},{"internalType":"uint256","name":"vote","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
{"constant":true,"inputs":[],"name":"winnerName","outputs":[{"internalType":"bytes32","name":"winnerName_","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},
{"constant":true,"inputs":[],"name":"winningProposal","outputs":[{"internalType":"uint256","name":"winningProposal_","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}
],
"devdoc":{
"details":"Implements voting process along with vote delegation",
"methods":{
"constructor":{
"details":"Create a new ballot to choose one of proposalNames",
"params":{"proposalNames":"names of proposals"}
},
"delegate(address)":{
"details":"Delegate your vote to the voter",
"params":{"to":"address to which vote is delegated"}
},
"giveRightToVote(address)":{
"details":"Give voter the right to vote on this ballot. May only be called by chairperson",
"params":{"voter":"address of voter"}
},
"vote(uint256)":{
"details":"Give your vote (including votes delegated to you) to proposal proposals[proposal].name",
"params":{"proposal":"index of proposal in the proposals array"}
},
"winnerName()":{
"details":"Calls winningProposal() function to get the index of the winner contained in the proposals array and then",
"return":"winnerName_ the name of the winner"
},
"winningProposal()":{
"details":"Computes the winning proposal taking all previous votes into account.",
"return":"winningProposal_ index of winning proposal in the proposals array"}
},
"title":"Ballot"
},
"userdoc":{"methods":{}}},
"settings":{
"compilationTarget":{"test.sol":"Ballot"},
"evmVersion":"istanbul",
"libraries":{},
"optimizer":{"enabled":false,"runs":200},
"remappings":[]},
"sources":{
"test.sol":{
"keccak256":"0x1008aa4339f4493c8b48dc3d4217403c26a19b1524d1cd79f43d8963e376357e",
"urls":["bzz-raw://0576de9b9e96c2c26296ac3fd9db440d42816bcf3ff03fe50c0323578e089398",
"dweb:/ipfs/QmZ3uBGpFbQ5VsdfVyG8R2KBvwn2oW4SeuFHi1sq9zuMFE"]
}
},
"version":1
}
}
}
}

@ -3,8 +3,10 @@ import * as common from '../../src/solidity-analyzer/modules/staticAnalysisCommo
const { localCall, thisLocalCall, libCall, externalDirect, superLocal, assignment, abiNamespaceCallNodes, const { localCall, thisLocalCall, libCall, externalDirect, superLocal, assignment, abiNamespaceCallNodes,
inlineAssembly, unaryOperation, nowAst, blockTimestamp, stateVariableContractNode, inlineAssembly, unaryOperation, nowAst, blockTimestamp, stateVariableContractNode,
functionDefinition, requireCall, selfdestruct, storageVariableNodes, dynamicDeleteUnaryOp, functionDefinition, requireCall, selfdestruct, storageVariableNodes, dynamicDeleteUnaryOp,
lowlevelCall, parameterFunction, parameterFunctionCall, inheritance, blockHashAccess, contractDefinition } = require('./astBlocks') lowlevelCall, parameterFunction, parameterFunctionCall, inheritance, blockHashAccess, contractDefinition, funcDefForComplexParams } = require('./astBlocks')
const compiledContractObj = require('./compilationDetails/CompiledContractObj.json')
function escapeRegExp (str) { function escapeRegExp (str) {
return str.replace(/[-[\]/{}()+?.\\^$|]/g, '\\$&') return str.replace(/[-[\]/{}()+?.\\^$|]/g, '\\$&')
} }
@ -289,6 +291,13 @@ test('staticAnalysisCommon.getFullQuallyfiedFuncDefinitionIdent', function (t) {
t.throws(() => common.getFullQuallyfiedFuncDefinitionIdent(parameterFunctionCall, functionDefinition, ['uint256', 'bool']), new RegExp('staticAnalysisCommon.js: not a ContractDefinition Node'), 'throws on wrong nodes') t.throws(() => common.getFullQuallyfiedFuncDefinitionIdent(parameterFunctionCall, functionDefinition, ['uint256', 'bool']), new RegExp('staticAnalysisCommon.js: not a ContractDefinition Node'), 'throws on wrong nodes')
}) })
test('staticAnalysisCommon.getSplittedTypeDesc', function (t) {
t.plan(3)
t.ok(common.getMethodParamsSplittedTypeDesc(funcDefForComplexParams.withoutParams, compiledContractObj).length === 0, 'no params, no params type signature')
t.ok(common.getMethodParamsSplittedTypeDesc(funcDefForComplexParams.bytesArray, compiledContractObj)[0] === 'bytes32[]', 'creates right params type signature')
t.ok(common.getMethodParamsSplittedTypeDesc(funcDefForComplexParams.nestedStruct, compiledContractObj)[0] === '(bytes32,uint256,uint256[],address,(bytes32,uint256)[])[][]', 'creates right params type signature')
})
// #################### Complex Node Identification // #################### Complex Node Identification
test('staticAnalysisCommon.isBuiltinFunctionCall', function (t) { test('staticAnalysisCommon.isBuiltinFunctionCall', function (t) {

@ -125,7 +125,7 @@ test('Integration test constantFunctions module', function (t: test.Test) {
const lengthCheck: Record<string, number> = { const lengthCheck: Record<string, number> = {
'KingOfTheEtherThrone.sol': 0, 'KingOfTheEtherThrone.sol': 0,
'assembly.sol': 0, 'assembly.sol': 0,
'ballot.sol': 0, 'ballot.sol': 1,
'ballot_reentrant.sol': 0, 'ballot_reentrant.sol': 0,
'ballot_withoutWarnings.sol': 0, 'ballot_withoutWarnings.sol': 0,
'cross_contract.sol': 0, 'cross_contract.sol': 0,
@ -233,7 +233,7 @@ test('Integration test gasCosts module', function (t: test.Test) {
const lengthCheck: Record<string, number> = { const lengthCheck: Record<string, number> = {
'KingOfTheEtherThrone.sol': 2, 'KingOfTheEtherThrone.sol': 2,
'assembly.sol': 2, 'assembly.sol': 2,
'ballot.sol': 3, 'ballot.sol': 4,
'ballot_reentrant.sol': 2, 'ballot_reentrant.sol': 2,
'ballot_withoutWarnings.sol': 0, 'ballot_withoutWarnings.sol': 0,
'cross_contract.sol': 1, 'cross_contract.sol': 1,

@ -141,5 +141,7 @@ contract Ballot {
{ {
winnerName = proposals[winningProposal()].name; winnerName = proposals[winningProposal()].name;
} }
function testWithArray (bytes32[] memory param) public {}
} }

Loading…
Cancel
Save