Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
# report outputs
output/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# Runtime data
pids
*.pid
Expand All@@ -15,7 +19,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
/coverage

# nyc test coverage
.nyc_output
Expand All@@ -29,14 +33,15 @@ bower_components
# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
/build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/

# Optional npm cache directory
Expand All@@ -56,4 +61,25 @@ typings/

# dotenv environment variables file
.env

.env.local
.env.development.local
.env.test.local
.env.production.local

# next.js build output
.next

#other
.lck


# OS generated files #
######################
.DS_Store
.DS_Store?
.idea
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
64 changes: 51 additions & 13 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
# Solidity Function Profiler

A command line tool that generates a human-consumable report listing a contract's functions. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.
A command line tool that generates a human readable report with contract information. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.

This version ditches the now deprecated [ConsenSys Solidity Parser](https://github.com/ConsenSys/solidity-parser) in favor of [solidity-parser-antlr](https://github.com/federicobond/solidity-parser-antlr).

Usage Example:

```
$ npm install
...
$ node index.js ~/contracts/mytoken.sol
.--------------------------------------------------------------------------------------------------------.
| ~/contracts/mytoken.sol |
|--------------------------------------------------------------------------------------------------------|
| Contract | Function | Visibility | Constant | Returns | Modifiers |
|---------------|-------------------------------|------------|----------|-----------|--------------------|
| MyToken | () | public | false | | payable |
| MyToken | initTokenHolder(address,uint) | public | false | | onlyOwner |
| MyToken | balance(address) | public | true | uint | |
| MyToken | transferAll(address,address) | external | false | | onlyTokenHolder |
| MyToken | kill() | internal | false | | |
'--------------------------------------------------------------------------------------------------------'

$ chmod +x cli.js

$ ./cli.js ~/contracts/mytoken.sol

You can also give a directory as an argument using the --dir flag, this will generate a report on all files ending in .sol the directory or its subdirectories.

$ ./cli.js --dir ~/contracts

```
This will produce a `.md` file on the `output` folder, for every contract scanned. e.g:

`~/output/myToken.sol.md` will have:

```

.------------------------------------------.
| pragma solidity 0.10.99 contract MyToken |
|------------------------------------------|
| ~/contracts/mytoken.sol |
'------------------------------------------'
.-----------------------------.
| imports |
|-----------------------------|
| ../import/FancyContract.sol |
'-----------------------------'
.----------------------------------------------------------------------.
| functions |
|----------------------------------------------------------------------|
| name | visibility | return | modifiers |
|------------------------------------|------------|--------|-----------|
| constructor(_param) payable | public | | |
| otherFunction(_param, _otherParam) | external | bool | onlyOwner |
| fancyPants(_pants) | external | | onlyOwner |
'----------------------------------------------------------------------'
.--------------.
| modifiers |
|--------------|
| onlyOwner() |
'--------------'
.-------------------------------------.
| events |
|-------------------------------------|
| MySpecialEvent(_param) |
'-------------------------------------'

```

multiple dir feature based on [this repo](https://github.com/maurelian/sol-function-profiler)
32 changes: 32 additions & 0 deletions cli.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const profiler = require('./index.js')
const argv = require('yargs').argv
const utils = require('./utils')

// recreate reporting output folder
const dirname = 'output'
utils.recreateFolder(dirname)

if (argv.dir) {
// is a directory?
profiler.generateReportForDir(argv.dir)
} else {
// is a single contract?
let arg_index = argv._.length - 1
let filepath = argv._[arg_index] // argv puts the final arguments in an array named "_"

if (!filepath) {
console.log('usage: --dir <dir> or path to .sol file')
process.exit(1)
}
let contract
try {
contract = fs.readFileSync(filepath, 'utf8')
} catch (e) {
throw e
}
profiler.generateReport(filepath, contract)
}
200 changes: 128 additions & 72 deletions index.js
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,143 @@
const parser = require("solidity-parser");
const asciiTable = require('ascii-table');
const fs = require('fs')
const path = require('path')
const asciiTable = require('ascii-table')
const parser = require('solidity-parser-antlr')
const utils = require('./utils.js')
const report = require('./parsers')

if(process.argv.length < 3) {
console.log("Error: Missing argument for sol file to scan");
process.exit(1);
}
let contract
let parsedContract

var target = process.argv[2],
contract = parser.parseFile(target);
function generateReportForDir (dir) {
const files = utils.getAllFiles(dir)
files
.filter(filepath => filepath.split('.').pop() === 'sol')
.forEach(filepath => {
try {
contract = fs.readFileSync(filepath, 'utf8')
generateReport(filepath, contract)
} catch (e) {
console.log(`Error reading contract ${contract} :`, e)
}
})
}

generateReport(target, contract);
function generateReport (filepath, contract) {
// write output to file
let filename = path.basename(filepath)
let reportName = `./output/${filename}.md`
let writeStream = fs.createWriteStream(reportName)

function generateReport(target, contract) {
var table = new asciiTable(target);
table.setHeading('Contract', 'Function', 'Visibility', 'Constant', 'Returns', 'Modifiers');
try {
parsedContract = parser.parse(contract)
} catch (e) {
if (e instanceof parser.ParserError) {
console.log(e.errors)
}
}

contract.body.forEach(function(contract) {
if(contract.type == 'ContractStatement') {
contract.body.forEach(function(part) {
if(part.type == "FunctionDeclaration" && part.is_abstract == false) {
var func = parseFunctionPart(contract, part);
table.addRow(func.contract, func.function, func.visibility, func.constant, func.returns, func.modifiers);
let functionRows = []
let eventRows = []
let modifierRows = []
let importRows = []
let contractInfo = ''
for (const node of parsedContract.children) {
const type = node.type
switch (type) {
// Contract solidity version
case 'PragmaDirective':
const contractPragma = node.name
const contractPragmaVersion = node.value
contractInfo += `pragma ${contractPragma} ${contractPragmaVersion}`
break
// Contract name and type
case 'ContractDefinition':
const contractName = node.name
const contractType = node.kind
contractInfo += ` ${contractType} ${contractName}`
for (const subNode of node.subNodes) {
switch (subNode.type) {
// Contract Functions
case 'FunctionDefinition':
functionRows.push(report.parseFunction(subNode))
break
// Contract Events
case 'EventDefinition':
eventRows.push(report.parseEvent(subNode))
break
// Contract Modifiers
case 'ModifierDefinition':
modifierRows.push(report.parseModifier(subNode))
break
}
}
})
break
// Contract Imports
case 'ImportDirective':
importRows.push(report.parseImport(node))
break
}
}

let generalInfoTable = asciiTable.factory({
heading: [contractInfo],
rows: [filepath]
})

let functionTable = asciiTable.factory({
title: 'functions',
heading: ['name', 'visibility', 'return', 'modifiers'],
rows: functionRows
})

let eventTable = asciiTable.factory({
heading: ['events'],
rows: eventRows
})

let modifierTable = asciiTable.factory({
heading: ['modifiers'],
rows: modifierRows
})
console.log(table.toString());
}

function parseFunctionPart(contract, part) {
var contractName = contract.name,
funcName = part.name || "",
params = [];

if(part.params) {
part.params.forEach(function(param) {
params.push(param.literal.literal);
});
funcName += "(" + params.join(',') + ")"
} else {
funcName += "()"
let importTable = asciiTable.factory({
heading: ['imports'],
rows: importRows
})

// very fancy markdown ;)
writeStream.write('```' + '\r\n')

writeStream.write(generalInfoTable.toString() + '\r\n')

if (importRows.length > 0) {
writeStream.write(importTable.toString() + '\r\n')
}

// Default is public
var visibility = "public"
isConstant = false,
returns = [],
custom = [];

if(part.modifiers) {
part.modifiers.forEach(function(mod) {
switch(mod.name) {
case "public":
break;
case "private":
visibility = "private";
break;
case "internal":
visibility = "internal";
break;
case "external":
visibility = "external";
break;
case "constant":
isConstant = true;
break;
case "returns":
mod.params.forEach(function(param) {
returns.push(param.name);
});
break;
default:
custom.push(mod.name);
}
});
if (functionRows.length > 0) {
writeStream.write(functionTable.toString() + '\r\n')
}

if (modifierRows.length > 0) {
writeStream.write(modifierTable.toString() + '\r\n')
}

return {
contract: contractName,
function: funcName,
visibility: visibility,
constant: isConstant,
returns: returns,
modifiers: custom
if (eventRows.length > 0) {
writeStream.write(eventTable.toString() + '\r\n')
}

writeStream.write('```' + '\r\n')

// the finish event is emitted when all data has been flushed from the stream
writeStream.on('finish', function () {
console.log(`written ${reportName}`)
})

// close the stream
writeStream.end()
}

module.exports = {
generateReport,
generateReportForDir
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Enhancements & Upgrades by agonzalezv · Pull Request #1 · EricR/sol-function-profiler · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
# report outputs
output/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# Runtime data
pids
*.pid
Expand All@@ -15,7 +19,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
/coverage

# nyc test coverage
.nyc_output
Expand All@@ -29,14 +33,15 @@ bower_components
# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
/build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/

# Optional npm cache directory
Expand All@@ -56,4 +61,25 @@ typings/

# dotenv environment variables file
.env

.env.local
.env.development.local
.env.test.local
.env.production.local

# next.js build output
.next

#other
.lck


# OS generated files #
######################
.DS_Store
.DS_Store?
.idea
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
64 changes: 51 additions & 13 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
# Solidity Function Profiler

A command line tool that generates a human-consumable report listing a contract's functions. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.
A command line tool that generates a human readable report with contract information. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.

This version ditches the now deprecated [ConsenSys Solidity Parser](https://github.com/ConsenSys/solidity-parser) in favor of [solidity-parser-antlr](https://github.com/federicobond/solidity-parser-antlr).

Usage Example:

```
$ npm install
...
$ node index.js ~/contracts/mytoken.sol
.--------------------------------------------------------------------------------------------------------.
| ~/contracts/mytoken.sol |
|--------------------------------------------------------------------------------------------------------|
| Contract | Function | Visibility | Constant | Returns | Modifiers |
|---------------|-------------------------------|------------|----------|-----------|--------------------|
| MyToken | () | public | false | | payable |
| MyToken | initTokenHolder(address,uint) | public | false | | onlyOwner |
| MyToken | balance(address) | public | true | uint | |
| MyToken | transferAll(address,address) | external | false | | onlyTokenHolder |
| MyToken | kill() | internal | false | | |
'--------------------------------------------------------------------------------------------------------'

$ chmod +x cli.js

$ ./cli.js ~/contracts/mytoken.sol

You can also give a directory as an argument using the --dir flag, this will generate a report on all files ending in .sol the directory or its subdirectories.

$ ./cli.js --dir ~/contracts

```
This will produce a `.md` file on the `output` folder, for every contract scanned. e.g:

`~/output/myToken.sol.md` will have:

```

.------------------------------------------.
| pragma solidity 0.10.99 contract MyToken |
|------------------------------------------|
| ~/contracts/mytoken.sol |
'------------------------------------------'
.-----------------------------.
| imports |
|-----------------------------|
| ../import/FancyContract.sol |
'-----------------------------'
.----------------------------------------------------------------------.
| functions |
|----------------------------------------------------------------------|
| name | visibility | return | modifiers |
|------------------------------------|------------|--------|-----------|
| constructor(_param) payable | public | | |
| otherFunction(_param, _otherParam) | external | bool | onlyOwner |
| fancyPants(_pants) | external | | onlyOwner |
'----------------------------------------------------------------------'
.--------------.
| modifiers |
|--------------|
| onlyOwner() |
'--------------'
.-------------------------------------.
| events |
|-------------------------------------|
| MySpecialEvent(_param) |
'-------------------------------------'

```

multiple dir feature based on [this repo](https://github.com/maurelian/sol-function-profiler)
32 changes: 32 additions & 0 deletions cli.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const profiler = require('./index.js')
const argv = require('yargs').argv
const utils = require('./utils')

// recreate reporting output folder
const dirname = 'output'
utils.recreateFolder(dirname)

if (argv.dir) {
// is a directory?
profiler.generateReportForDir(argv.dir)
} else {
// is a single contract?
let arg_index = argv._.length - 1
let filepath = argv._[arg_index] // argv puts the final arguments in an array named "_"

if (!filepath) {
console.log('usage: --dir <dir> or path to .sol file')
process.exit(1)
}
let contract
try {
contract = fs.readFileSync(filepath, 'utf8')
} catch (e) {
throw e
}
profiler.generateReport(filepath, contract)
}
200 changes: 128 additions & 72 deletions index.js
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,143 @@
const parser = require("solidity-parser");
const asciiTable = require('ascii-table');
const fs = require('fs')
const path = require('path')
const asciiTable = require('ascii-table')
const parser = require('solidity-parser-antlr')
const utils = require('./utils.js')
const report = require('./parsers')

if(process.argv.length < 3) {
console.log("Error: Missing argument for sol file to scan");
process.exit(1);
}
let contract
let parsedContract

var target = process.argv[2],
contract = parser.parseFile(target);
function generateReportForDir (dir) {
const files = utils.getAllFiles(dir)
files
.filter(filepath => filepath.split('.').pop() === 'sol')
.forEach(filepath => {
try {
contract = fs.readFileSync(filepath, 'utf8')
generateReport(filepath, contract)
} catch (e) {
console.log(`Error reading contract ${contract} :`, e)
}
})
}

generateReport(target, contract);
function generateReport (filepath, contract) {
// write output to file
let filename = path.basename(filepath)
let reportName = `./output/${filename}.md`
let writeStream = fs.createWriteStream(reportName)

function generateReport(target, contract) {
var table = new asciiTable(target);
table.setHeading('Contract', 'Function', 'Visibility', 'Constant', 'Returns', 'Modifiers');
try {
parsedContract = parser.parse(contract)
} catch (e) {
if (e instanceof parser.ParserError) {
console.log(e.errors)
}
}

contract.body.forEach(function(contract) {
if(contract.type == 'ContractStatement') {
contract.body.forEach(function(part) {
if(part.type == "FunctionDeclaration" && part.is_abstract == false) {
var func = parseFunctionPart(contract, part);
table.addRow(func.contract, func.function, func.visibility, func.constant, func.returns, func.modifiers);
let functionRows = []
let eventRows = []
let modifierRows = []
let importRows = []
let contractInfo = ''
for (const node of parsedContract.children) {
const type = node.type
switch (type) {
// Contract solidity version
case 'PragmaDirective':
const contractPragma = node.name
const contractPragmaVersion = node.value
contractInfo += `pragma ${contractPragma} ${contractPragmaVersion}`
break
// Contract name and type
case 'ContractDefinition':
const contractName = node.name
const contractType = node.kind
contractInfo += ` ${contractType} ${contractName}`
for (const subNode of node.subNodes) {
switch (subNode.type) {
// Contract Functions
case 'FunctionDefinition':
functionRows.push(report.parseFunction(subNode))
break
// Contract Events
case 'EventDefinition':
eventRows.push(report.parseEvent(subNode))
break
// Contract Modifiers
case 'ModifierDefinition':
modifierRows.push(report.parseModifier(subNode))
break
}
}
})
break
// Contract Imports
case 'ImportDirective':
importRows.push(report.parseImport(node))
break
}
}

let generalInfoTable = asciiTable.factory({
heading: [contractInfo],
rows: [filepath]
})

let functionTable = asciiTable.factory({
title: 'functions',
heading: ['name', 'visibility', 'return', 'modifiers'],
rows: functionRows
})

let eventTable = asciiTable.factory({
heading: ['events'],
rows: eventRows
})

let modifierTable = asciiTable.factory({
heading: ['modifiers'],
rows: modifierRows
})
console.log(table.toString());
}

function parseFunctionPart(contract, part) {
var contractName = contract.name,
funcName = part.name || "",
params = [];

if(part.params) {
part.params.forEach(function(param) {
params.push(param.literal.literal);
});
funcName += "(" + params.join(',') + ")"
} else {
funcName += "()"
let importTable = asciiTable.factory({
heading: ['imports'],
rows: importRows
})

// very fancy markdown ;)
writeStream.write('```' + '\r\n')

writeStream.write(generalInfoTable.toString() + '\r\n')

if (importRows.length > 0) {
writeStream.write(importTable.toString() + '\r\n')
}

// Default is public
var visibility = "public"
isConstant = false,
returns = [],
custom = [];

if(part.modifiers) {
part.modifiers.forEach(function(mod) {
switch(mod.name) {
case "public":
break;
case "private":
visibility = "private";
break;
case "internal":
visibility = "internal";
break;
case "external":
visibility = "external";
break;
case "constant":
isConstant = true;
break;
case "returns":
mod.params.forEach(function(param) {
returns.push(param.name);
});
break;
default:
custom.push(mod.name);
}
});
if (functionRows.length > 0) {
writeStream.write(functionTable.toString() + '\r\n')
}

if (modifierRows.length > 0) {
writeStream.write(modifierTable.toString() + '\r\n')
}

return {
contract: contractName,
function: funcName,
visibility: visibility,
constant: isConstant,
returns: returns,
modifiers: custom
if (eventRows.length > 0) {
writeStream.write(eventTable.toString() + '\r\n')
}

writeStream.write('```' + '\r\n')

// the finish event is emitted when all data has been flushed from the stream
writeStream.on('finish', function () {
console.log(`written ${reportName}`)
})

// close the stream
writeStream.end()
}

module.exports = {
generateReport,
generateReportForDir
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Enhancements & Upgrades by agonzalezv · Pull Request #1 · EricR/sol-function-profiler · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
# report outputs
output/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# Runtime data
pids
*.pid
Expand All@@ -15,7 +19,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
/coverage

# nyc test coverage
.nyc_output
Expand All@@ -29,14 +33,15 @@ bower_components
# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
/build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/

# Optional npm cache directory
Expand All@@ -56,4 +61,25 @@ typings/

# dotenv environment variables file
.env

.env.local
.env.development.local
.env.test.local
.env.production.local

# next.js build output
.next

#other
.lck


# OS generated files #
######################
.DS_Store
.DS_Store?
.idea
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
64 changes: 51 additions & 13 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
# Solidity Function Profiler

A command line tool that generates a human-consumable report listing a contract's functions. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.
A command line tool that generates a human readable report with contract information. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.

This version ditches the now deprecated [ConsenSys Solidity Parser](https://github.com/ConsenSys/solidity-parser) in favor of [solidity-parser-antlr](https://github.com/federicobond/solidity-parser-antlr).

Usage Example:

```
$ npm install
...
$ node index.js ~/contracts/mytoken.sol
.--------------------------------------------------------------------------------------------------------.
| ~/contracts/mytoken.sol |
|--------------------------------------------------------------------------------------------------------|
| Contract | Function | Visibility | Constant | Returns | Modifiers |
|---------------|-------------------------------|------------|----------|-----------|--------------------|
| MyToken | () | public | false | | payable |
| MyToken | initTokenHolder(address,uint) | public | false | | onlyOwner |
| MyToken | balance(address) | public | true | uint | |
| MyToken | transferAll(address,address) | external | false | | onlyTokenHolder |
| MyToken | kill() | internal | false | | |
'--------------------------------------------------------------------------------------------------------'

$ chmod +x cli.js

$ ./cli.js ~/contracts/mytoken.sol

You can also give a directory as an argument using the --dir flag, this will generate a report on all files ending in .sol the directory or its subdirectories.

$ ./cli.js --dir ~/contracts

```
This will produce a `.md` file on the `output` folder, for every contract scanned. e.g:

`~/output/myToken.sol.md` will have:

```

.------------------------------------------.
| pragma solidity 0.10.99 contract MyToken |
|------------------------------------------|
| ~/contracts/mytoken.sol |
'------------------------------------------'
.-----------------------------.
| imports |
|-----------------------------|
| ../import/FancyContract.sol |
'-----------------------------'
.----------------------------------------------------------------------.
| functions |
|----------------------------------------------------------------------|
| name | visibility | return | modifiers |
|------------------------------------|------------|--------|-----------|
| constructor(_param) payable | public | | |
| otherFunction(_param, _otherParam) | external | bool | onlyOwner |
| fancyPants(_pants) | external | | onlyOwner |
'----------------------------------------------------------------------'
.--------------.
| modifiers |
|--------------|
| onlyOwner() |
'--------------'
.-------------------------------------.
| events |
|-------------------------------------|
| MySpecialEvent(_param) |
'-------------------------------------'

```

multiple dir feature based on [this repo](https://github.com/maurelian/sol-function-profiler)
32 changes: 32 additions & 0 deletions cli.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const profiler = require('./index.js')
const argv = require('yargs').argv
const utils = require('./utils')

// recreate reporting output folder
const dirname = 'output'
utils.recreateFolder(dirname)

if (argv.dir) {
// is a directory?
profiler.generateReportForDir(argv.dir)
} else {
// is a single contract?
let arg_index = argv._.length - 1
let filepath = argv._[arg_index] // argv puts the final arguments in an array named "_"

if (!filepath) {
console.log('usage: --dir <dir> or path to .sol file')
process.exit(1)
}
let contract
try {
contract = fs.readFileSync(filepath, 'utf8')
} catch (e) {
throw e
}
profiler.generateReport(filepath, contract)
}
200 changes: 128 additions & 72 deletions index.js
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,143 @@
const parser = require("solidity-parser");
const asciiTable = require('ascii-table');
const fs = require('fs')
const path = require('path')
const asciiTable = require('ascii-table')
const parser = require('solidity-parser-antlr')
const utils = require('./utils.js')
const report = require('./parsers')

if(process.argv.length < 3) {
console.log("Error: Missing argument for sol file to scan");
process.exit(1);
}
let contract
let parsedContract

var target = process.argv[2],
contract = parser.parseFile(target);
function generateReportForDir (dir) {
const files = utils.getAllFiles(dir)
files
.filter(filepath => filepath.split('.').pop() === 'sol')
.forEach(filepath => {
try {
contract = fs.readFileSync(filepath, 'utf8')
generateReport(filepath, contract)
} catch (e) {
console.log(`Error reading contract ${contract} :`, e)
}
})
}

generateReport(target, contract);
function generateReport (filepath, contract) {
// write output to file
let filename = path.basename(filepath)
let reportName = `./output/${filename}.md`
let writeStream = fs.createWriteStream(reportName)

function generateReport(target, contract) {
var table = new asciiTable(target);
table.setHeading('Contract', 'Function', 'Visibility', 'Constant', 'Returns', 'Modifiers');
try {
parsedContract = parser.parse(contract)
} catch (e) {
if (e instanceof parser.ParserError) {
console.log(e.errors)
}
}

contract.body.forEach(function(contract) {
if(contract.type == 'ContractStatement') {
contract.body.forEach(function(part) {
if(part.type == "FunctionDeclaration" && part.is_abstract == false) {
var func = parseFunctionPart(contract, part);
table.addRow(func.contract, func.function, func.visibility, func.constant, func.returns, func.modifiers);
let functionRows = []
let eventRows = []
let modifierRows = []
let importRows = []
let contractInfo = ''
for (const node of parsedContract.children) {
const type = node.type
switch (type) {
// Contract solidity version
case 'PragmaDirective':
const contractPragma = node.name
const contractPragmaVersion = node.value
contractInfo += `pragma ${contractPragma} ${contractPragmaVersion}`
break
// Contract name and type
case 'ContractDefinition':
const contractName = node.name
const contractType = node.kind
contractInfo += ` ${contractType} ${contractName}`
for (const subNode of node.subNodes) {
switch (subNode.type) {
// Contract Functions
case 'FunctionDefinition':
functionRows.push(report.parseFunction(subNode))
break
// Contract Events
case 'EventDefinition':
eventRows.push(report.parseEvent(subNode))
break
// Contract Modifiers
case 'ModifierDefinition':
modifierRows.push(report.parseModifier(subNode))
break
}
}
})
break
// Contract Imports
case 'ImportDirective':
importRows.push(report.parseImport(node))
break
}
}

let generalInfoTable = asciiTable.factory({
heading: [contractInfo],
rows: [filepath]
})

let functionTable = asciiTable.factory({
title: 'functions',
heading: ['name', 'visibility', 'return', 'modifiers'],
rows: functionRows
})

let eventTable = asciiTable.factory({
heading: ['events'],
rows: eventRows
})

let modifierTable = asciiTable.factory({
heading: ['modifiers'],
rows: modifierRows
})
console.log(table.toString());
}

function parseFunctionPart(contract, part) {
var contractName = contract.name,
funcName = part.name || "",
params = [];

if(part.params) {
part.params.forEach(function(param) {
params.push(param.literal.literal);
});
funcName += "(" + params.join(',') + ")"
} else {
funcName += "()"
let importTable = asciiTable.factory({
heading: ['imports'],
rows: importRows
})

// very fancy markdown ;)
writeStream.write('```' + '\r\n')

writeStream.write(generalInfoTable.toString() + '\r\n')

if (importRows.length > 0) {
writeStream.write(importTable.toString() + '\r\n')
}

// Default is public
var visibility = "public"
isConstant = false,
returns = [],
custom = [];

if(part.modifiers) {
part.modifiers.forEach(function(mod) {
switch(mod.name) {
case "public":
break;
case "private":
visibility = "private";
break;
case "internal":
visibility = "internal";
break;
case "external":
visibility = "external";
break;
case "constant":
isConstant = true;
break;
case "returns":
mod.params.forEach(function(param) {
returns.push(param.name);
});
break;
default:
custom.push(mod.name);
}
});
if (functionRows.length > 0) {
writeStream.write(functionTable.toString() + '\r\n')
}

if (modifierRows.length > 0) {
writeStream.write(modifierTable.toString() + '\r\n')
}

return {
contract: contractName,
function: funcName,
visibility: visibility,
constant: isConstant,
returns: returns,
modifiers: custom
if (eventRows.length > 0) {
writeStream.write(eventTable.toString() + '\r\n')
}

writeStream.write('```' + '\r\n')

// the finish event is emitted when all data has been flushed from the stream
writeStream.on('finish', function () {
console.log(`written ${reportName}`)
})

// close the stream
writeStream.end()
}

module.exports = {
generateReport,
generateReportForDir
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Enhancements & Upgrades by agonzalezv · Pull Request #1 · EricR/sol-function-profiler · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
# report outputs
output/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# Runtime data
pids
*.pid
Expand All@@ -15,7 +19,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
/coverage

# nyc test coverage
.nyc_output
Expand All@@ -29,14 +33,15 @@ bower_components
# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
/build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/

# Optional npm cache directory
Expand All@@ -56,4 +61,25 @@ typings/

# dotenv environment variables file
.env

.env.local
.env.development.local
.env.test.local
.env.production.local

# next.js build output
.next

#other
.lck


# OS generated files #
######################
.DS_Store
.DS_Store?
.idea
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
64 changes: 51 additions & 13 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
# Solidity Function Profiler

A command line tool that generates a human-consumable report listing a contract's functions. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.
A command line tool that generates a human readable report with contract information. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.

This version ditches the now deprecated [ConsenSys Solidity Parser](https://github.com/ConsenSys/solidity-parser) in favor of [solidity-parser-antlr](https://github.com/federicobond/solidity-parser-antlr).

Usage Example:

```
$ npm install
...
$ node index.js ~/contracts/mytoken.sol
.--------------------------------------------------------------------------------------------------------.
| ~/contracts/mytoken.sol |
|--------------------------------------------------------------------------------------------------------|
| Contract | Function | Visibility | Constant | Returns | Modifiers |
|---------------|-------------------------------|------------|----------|-----------|--------------------|
| MyToken | () | public | false | | payable |
| MyToken | initTokenHolder(address,uint) | public | false | | onlyOwner |
| MyToken | balance(address) | public | true | uint | |
| MyToken | transferAll(address,address) | external | false | | onlyTokenHolder |
| MyToken | kill() | internal | false | | |
'--------------------------------------------------------------------------------------------------------'

$ chmod +x cli.js

$ ./cli.js ~/contracts/mytoken.sol

You can also give a directory as an argument using the --dir flag, this will generate a report on all files ending in .sol the directory or its subdirectories.

$ ./cli.js --dir ~/contracts

```
This will produce a `.md` file on the `output` folder, for every contract scanned. e.g:

`~/output/myToken.sol.md` will have:

```

.------------------------------------------.
| pragma solidity 0.10.99 contract MyToken |
|------------------------------------------|
| ~/contracts/mytoken.sol |
'------------------------------------------'
.-----------------------------.
| imports |
|-----------------------------|
| ../import/FancyContract.sol |
'-----------------------------'
.----------------------------------------------------------------------.
| functions |
|----------------------------------------------------------------------|
| name | visibility | return | modifiers |
|------------------------------------|------------|--------|-----------|
| constructor(_param) payable | public | | |
| otherFunction(_param, _otherParam) | external | bool | onlyOwner |
| fancyPants(_pants) | external | | onlyOwner |
'----------------------------------------------------------------------'
.--------------.
| modifiers |
|--------------|
| onlyOwner() |
'--------------'
.-------------------------------------.
| events |
|-------------------------------------|
| MySpecialEvent(_param) |
'-------------------------------------'

```

multiple dir feature based on [this repo](https://github.com/maurelian/sol-function-profiler)
32 changes: 32 additions & 0 deletions cli.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const profiler = require('./index.js')
const argv = require('yargs').argv
const utils = require('./utils')

// recreate reporting output folder
const dirname = 'output'
utils.recreateFolder(dirname)

if (argv.dir) {
// is a directory?
profiler.generateReportForDir(argv.dir)
} else {
// is a single contract?
let arg_index = argv._.length - 1
let filepath = argv._[arg_index] // argv puts the final arguments in an array named "_"

if (!filepath) {
console.log('usage: --dir <dir> or path to .sol file')
process.exit(1)
}
let contract
try {
contract = fs.readFileSync(filepath, 'utf8')
} catch (e) {
throw e
}
profiler.generateReport(filepath, contract)
}
200 changes: 128 additions & 72 deletions index.js
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,143 @@
const parser = require("solidity-parser");
const asciiTable = require('ascii-table');
const fs = require('fs')
const path = require('path')
const asciiTable = require('ascii-table')
const parser = require('solidity-parser-antlr')
const utils = require('./utils.js')
const report = require('./parsers')

if(process.argv.length < 3) {
console.log("Error: Missing argument for sol file to scan");
process.exit(1);
}
let contract
let parsedContract

var target = process.argv[2],
contract = parser.parseFile(target);
function generateReportForDir (dir) {
const files = utils.getAllFiles(dir)
files
.filter(filepath => filepath.split('.').pop() === 'sol')
.forEach(filepath => {
try {
contract = fs.readFileSync(filepath, 'utf8')
generateReport(filepath, contract)
} catch (e) {
console.log(`Error reading contract ${contract} :`, e)
}
})
}

generateReport(target, contract);
function generateReport (filepath, contract) {
// write output to file
let filename = path.basename(filepath)
let reportName = `./output/${filename}.md`
let writeStream = fs.createWriteStream(reportName)

function generateReport(target, contract) {
var table = new asciiTable(target);
table.setHeading('Contract', 'Function', 'Visibility', 'Constant', 'Returns', 'Modifiers');
try {
parsedContract = parser.parse(contract)
} catch (e) {
if (e instanceof parser.ParserError) {
console.log(e.errors)
}
}

contract.body.forEach(function(contract) {
if(contract.type == 'ContractStatement') {
contract.body.forEach(function(part) {
if(part.type == "FunctionDeclaration" && part.is_abstract == false) {
var func = parseFunctionPart(contract, part);
table.addRow(func.contract, func.function, func.visibility, func.constant, func.returns, func.modifiers);
let functionRows = []
let eventRows = []
let modifierRows = []
let importRows = []
let contractInfo = ''
for (const node of parsedContract.children) {
const type = node.type
switch (type) {
// Contract solidity version
case 'PragmaDirective':
const contractPragma = node.name
const contractPragmaVersion = node.value
contractInfo += `pragma ${contractPragma} ${contractPragmaVersion}`
break
// Contract name and type
case 'ContractDefinition':
const contractName = node.name
const contractType = node.kind
contractInfo += ` ${contractType} ${contractName}`
for (const subNode of node.subNodes) {
switch (subNode.type) {
// Contract Functions
case 'FunctionDefinition':
functionRows.push(report.parseFunction(subNode))
break
// Contract Events
case 'EventDefinition':
eventRows.push(report.parseEvent(subNode))
break
// Contract Modifiers
case 'ModifierDefinition':
modifierRows.push(report.parseModifier(subNode))
break
}
}
})
break
// Contract Imports
case 'ImportDirective':
importRows.push(report.parseImport(node))
break
}
}

let generalInfoTable = asciiTable.factory({
heading: [contractInfo],
rows: [filepath]
})

let functionTable = asciiTable.factory({
title: 'functions',
heading: ['name', 'visibility', 'return', 'modifiers'],
rows: functionRows
})

let eventTable = asciiTable.factory({
heading: ['events'],
rows: eventRows
})

let modifierTable = asciiTable.factory({
heading: ['modifiers'],
rows: modifierRows
})
console.log(table.toString());
}

function parseFunctionPart(contract, part) {
var contractName = contract.name,
funcName = part.name || "",
params = [];

if(part.params) {
part.params.forEach(function(param) {
params.push(param.literal.literal);
});
funcName += "(" + params.join(',') + ")"
} else {
funcName += "()"
let importTable = asciiTable.factory({
heading: ['imports'],
rows: importRows
})

// very fancy markdown ;)
writeStream.write('```' + '\r\n')

writeStream.write(generalInfoTable.toString() + '\r\n')

if (importRows.length > 0) {
writeStream.write(importTable.toString() + '\r\n')
}

// Default is public
var visibility = "public"
isConstant = false,
returns = [],
custom = [];

if(part.modifiers) {
part.modifiers.forEach(function(mod) {
switch(mod.name) {
case "public":
break;
case "private":
visibility = "private";
break;
case "internal":
visibility = "internal";
break;
case "external":
visibility = "external";
break;
case "constant":
isConstant = true;
break;
case "returns":
mod.params.forEach(function(param) {
returns.push(param.name);
});
break;
default:
custom.push(mod.name);
}
});
if (functionRows.length > 0) {
writeStream.write(functionTable.toString() + '\r\n')
}

if (modifierRows.length > 0) {
writeStream.write(modifierTable.toString() + '\r\n')
}

return {
contract: contractName,
function: funcName,
visibility: visibility,
constant: isConstant,
returns: returns,
modifiers: custom
if (eventRows.length > 0) {
writeStream.write(eventTable.toString() + '\r\n')
}

writeStream.write('```' + '\r\n')

// the finish event is emitted when all data has been flushed from the stream
writeStream.on('finish', function () {
console.log(`written ${reportName}`)
})

// close the stream
writeStream.end()
}

module.exports = {
generateReport,
generateReportForDir
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Enhancements & Upgrades by agonzalezv · Pull Request #1 · EricR/sol-function-profiler · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
# report outputs
output/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# Runtime data
pids
*.pid
Expand All@@ -15,7 +19,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
/coverage

# nyc test coverage
.nyc_output
Expand All@@ -29,14 +33,15 @@ bower_components
# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
/build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/

# Optional npm cache directory
Expand All@@ -56,4 +61,25 @@ typings/

# dotenv environment variables file
.env

.env.local
.env.development.local
.env.test.local
.env.production.local

# next.js build output
.next

#other
.lck


# OS generated files #
######################
.DS_Store
.DS_Store?
.idea
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
64 changes: 51 additions & 13 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
# Solidity Function Profiler

A command line tool that generates a human-consumable report listing a contract's functions. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.
A command line tool that generates a human readable report with contract information. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.

This version ditches the now deprecated [ConsenSys Solidity Parser](https://github.com/ConsenSys/solidity-parser) in favor of [solidity-parser-antlr](https://github.com/federicobond/solidity-parser-antlr).

Usage Example:

```
$ npm install
...
$ node index.js ~/contracts/mytoken.sol
.--------------------------------------------------------------------------------------------------------.
| ~/contracts/mytoken.sol |
|--------------------------------------------------------------------------------------------------------|
| Contract | Function | Visibility | Constant | Returns | Modifiers |
|---------------|-------------------------------|------------|----------|-----------|--------------------|
| MyToken | () | public | false | | payable |
| MyToken | initTokenHolder(address,uint) | public | false | | onlyOwner |
| MyToken | balance(address) | public | true | uint | |
| MyToken | transferAll(address,address) | external | false | | onlyTokenHolder |
| MyToken | kill() | internal | false | | |
'--------------------------------------------------------------------------------------------------------'

$ chmod +x cli.js

$ ./cli.js ~/contracts/mytoken.sol

You can also give a directory as an argument using the --dir flag, this will generate a report on all files ending in .sol the directory or its subdirectories.

$ ./cli.js --dir ~/contracts

```
This will produce a `.md` file on the `output` folder, for every contract scanned. e.g:

`~/output/myToken.sol.md` will have:

```

.------------------------------------------.
| pragma solidity 0.10.99 contract MyToken |
|------------------------------------------|
| ~/contracts/mytoken.sol |
'------------------------------------------'
.-----------------------------.
| imports |
|-----------------------------|
| ../import/FancyContract.sol |
'-----------------------------'
.----------------------------------------------------------------------.
| functions |
|----------------------------------------------------------------------|
| name | visibility | return | modifiers |
|------------------------------------|------------|--------|-----------|
| constructor(_param) payable | public | | |
| otherFunction(_param, _otherParam) | external | bool | onlyOwner |
| fancyPants(_pants) | external | | onlyOwner |
'----------------------------------------------------------------------'
.--------------.
| modifiers |
|--------------|
| onlyOwner() |
'--------------'
.-------------------------------------.
| events |
|-------------------------------------|
| MySpecialEvent(_param) |
'-------------------------------------'

```

multiple dir feature based on [this repo](https://github.com/maurelian/sol-function-profiler)
32 changes: 32 additions & 0 deletions cli.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const profiler = require('./index.js')
const argv = require('yargs').argv
const utils = require('./utils')

// recreate reporting output folder
const dirname = 'output'
utils.recreateFolder(dirname)

if (argv.dir) {
// is a directory?
profiler.generateReportForDir(argv.dir)
} else {
// is a single contract?
let arg_index = argv._.length - 1
let filepath = argv._[arg_index] // argv puts the final arguments in an array named "_"

if (!filepath) {
console.log('usage: --dir <dir> or path to .sol file')
process.exit(1)
}
let contract
try {
contract = fs.readFileSync(filepath, 'utf8')
} catch (e) {
throw e
}
profiler.generateReport(filepath, contract)
}
200 changes: 128 additions & 72 deletions index.js
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,143 @@
const parser = require("solidity-parser");
const asciiTable = require('ascii-table');
const fs = require('fs')
const path = require('path')
const asciiTable = require('ascii-table')
const parser = require('solidity-parser-antlr')
const utils = require('./utils.js')
const report = require('./parsers')

if(process.argv.length < 3) {
console.log("Error: Missing argument for sol file to scan");
process.exit(1);
}
let contract
let parsedContract

var target = process.argv[2],
contract = parser.parseFile(target);
function generateReportForDir (dir) {
const files = utils.getAllFiles(dir)
files
.filter(filepath => filepath.split('.').pop() === 'sol')
.forEach(filepath => {
try {
contract = fs.readFileSync(filepath, 'utf8')
generateReport(filepath, contract)
} catch (e) {
console.log(`Error reading contract ${contract} :`, e)
}
})
}

generateReport(target, contract);
function generateReport (filepath, contract) {
// write output to file
let filename = path.basename(filepath)
let reportName = `./output/${filename}.md`
let writeStream = fs.createWriteStream(reportName)

function generateReport(target, contract) {
var table = new asciiTable(target);
table.setHeading('Contract', 'Function', 'Visibility', 'Constant', 'Returns', 'Modifiers');
try {
parsedContract = parser.parse(contract)
} catch (e) {
if (e instanceof parser.ParserError) {
console.log(e.errors)
}
}

contract.body.forEach(function(contract) {
if(contract.type == 'ContractStatement') {
contract.body.forEach(function(part) {
if(part.type == "FunctionDeclaration" && part.is_abstract == false) {
var func = parseFunctionPart(contract, part);
table.addRow(func.contract, func.function, func.visibility, func.constant, func.returns, func.modifiers);
let functionRows = []
let eventRows = []
let modifierRows = []
let importRows = []
let contractInfo = ''
for (const node of parsedContract.children) {
const type = node.type
switch (type) {
// Contract solidity version
case 'PragmaDirective':
const contractPragma = node.name
const contractPragmaVersion = node.value
contractInfo += `pragma ${contractPragma} ${contractPragmaVersion}`
break
// Contract name and type
case 'ContractDefinition':
const contractName = node.name
const contractType = node.kind
contractInfo += ` ${contractType} ${contractName}`
for (const subNode of node.subNodes) {
switch (subNode.type) {
// Contract Functions
case 'FunctionDefinition':
functionRows.push(report.parseFunction(subNode))
break
// Contract Events
case 'EventDefinition':
eventRows.push(report.parseEvent(subNode))
break
// Contract Modifiers
case 'ModifierDefinition':
modifierRows.push(report.parseModifier(subNode))
break
}
}
})
break
// Contract Imports
case 'ImportDirective':
importRows.push(report.parseImport(node))
break
}
}

let generalInfoTable = asciiTable.factory({
heading: [contractInfo],
rows: [filepath]
})

let functionTable = asciiTable.factory({
title: 'functions',
heading: ['name', 'visibility', 'return', 'modifiers'],
rows: functionRows
})

let eventTable = asciiTable.factory({
heading: ['events'],
rows: eventRows
})

let modifierTable = asciiTable.factory({
heading: ['modifiers'],
rows: modifierRows
})
console.log(table.toString());
}

function parseFunctionPart(contract, part) {
var contractName = contract.name,
funcName = part.name || "",
params = [];

if(part.params) {
part.params.forEach(function(param) {
params.push(param.literal.literal);
});
funcName += "(" + params.join(',') + ")"
} else {
funcName += "()"
let importTable = asciiTable.factory({
heading: ['imports'],
rows: importRows
})

// very fancy markdown ;)
writeStream.write('```' + '\r\n')

writeStream.write(generalInfoTable.toString() + '\r\n')

if (importRows.length > 0) {
writeStream.write(importTable.toString() + '\r\n')
}

// Default is public
var visibility = "public"
isConstant = false,
returns = [],
custom = [];

if(part.modifiers) {
part.modifiers.forEach(function(mod) {
switch(mod.name) {
case "public":
break;
case "private":
visibility = "private";
break;
case "internal":
visibility = "internal";
break;
case "external":
visibility = "external";
break;
case "constant":
isConstant = true;
break;
case "returns":
mod.params.forEach(function(param) {
returns.push(param.name);
});
break;
default:
custom.push(mod.name);
}
});
if (functionRows.length > 0) {
writeStream.write(functionTable.toString() + '\r\n')
}

if (modifierRows.length > 0) {
writeStream.write(modifierTable.toString() + '\r\n')
}

return {
contract: contractName,
function: funcName,
visibility: visibility,
constant: isConstant,
returns: returns,
modifiers: custom
if (eventRows.length > 0) {
writeStream.write(eventTable.toString() + '\r\n')
}

writeStream.write('```' + '\r\n')

// the finish event is emitted when all data has been flushed from the stream
writeStream.on('finish', function () {
console.log(`written ${reportName}`)
})

// close the stream
writeStream.end()
}

module.exports = {
generateReport,
generateReportForDir
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Enhancements & Upgrades by agonzalezv · Pull Request #1 · EricR/sol-function-profiler · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
# report outputs
output/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# Runtime data
pids
*.pid
Expand All@@ -15,7 +19,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
/coverage

# nyc test coverage
.nyc_output
Expand All@@ -29,14 +33,15 @@ bower_components
# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
/build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/

# Optional npm cache directory
Expand All@@ -56,4 +61,25 @@ typings/

# dotenv environment variables file
.env

.env.local
.env.development.local
.env.test.local
.env.production.local

# next.js build output
.next

#other
.lck


# OS generated files #
######################
.DS_Store
.DS_Store?
.idea
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
64 changes: 51 additions & 13 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
# Solidity Function Profiler

A command line tool that generates a human-consumable report listing a contract's functions. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.
A command line tool that generates a human readable report with contract information. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.

This version ditches the now deprecated [ConsenSys Solidity Parser](https://github.com/ConsenSys/solidity-parser) in favor of [solidity-parser-antlr](https://github.com/federicobond/solidity-parser-antlr).

Usage Example:

```
$ npm install
...
$ node index.js ~/contracts/mytoken.sol
.--------------------------------------------------------------------------------------------------------.
| ~/contracts/mytoken.sol |
|--------------------------------------------------------------------------------------------------------|
| Contract | Function | Visibility | Constant | Returns | Modifiers |
|---------------|-------------------------------|------------|----------|-----------|--------------------|
| MyToken | () | public | false | | payable |
| MyToken | initTokenHolder(address,uint) | public | false | | onlyOwner |
| MyToken | balance(address) | public | true | uint | |
| MyToken | transferAll(address,address) | external | false | | onlyTokenHolder |
| MyToken | kill() | internal | false | | |
'--------------------------------------------------------------------------------------------------------'

$ chmod +x cli.js

$ ./cli.js ~/contracts/mytoken.sol

You can also give a directory as an argument using the --dir flag, this will generate a report on all files ending in .sol the directory or its subdirectories.

$ ./cli.js --dir ~/contracts

```
This will produce a `.md` file on the `output` folder, for every contract scanned. e.g:

`~/output/myToken.sol.md` will have:

```

.------------------------------------------.
| pragma solidity 0.10.99 contract MyToken |
|------------------------------------------|
| ~/contracts/mytoken.sol |
'------------------------------------------'
.-----------------------------.
| imports |
|-----------------------------|
| ../import/FancyContract.sol |
'-----------------------------'
.----------------------------------------------------------------------.
| functions |
|----------------------------------------------------------------------|
| name | visibility | return | modifiers |
|------------------------------------|------------|--------|-----------|
| constructor(_param) payable | public | | |
| otherFunction(_param, _otherParam) | external | bool | onlyOwner |
| fancyPants(_pants) | external | | onlyOwner |
'----------------------------------------------------------------------'
.--------------.
| modifiers |
|--------------|
| onlyOwner() |
'--------------'
.-------------------------------------.
| events |
|-------------------------------------|
| MySpecialEvent(_param) |
'-------------------------------------'

```

multiple dir feature based on [this repo](https://github.com/maurelian/sol-function-profiler)
32 changes: 32 additions & 0 deletions cli.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const profiler = require('./index.js')
const argv = require('yargs').argv
const utils = require('./utils')

// recreate reporting output folder
const dirname = 'output'
utils.recreateFolder(dirname)

if (argv.dir) {
// is a directory?
profiler.generateReportForDir(argv.dir)
} else {
// is a single contract?
let arg_index = argv._.length - 1
let filepath = argv._[arg_index] // argv puts the final arguments in an array named "_"

if (!filepath) {
console.log('usage: --dir <dir> or path to .sol file')
process.exit(1)
}
let contract
try {
contract = fs.readFileSync(filepath, 'utf8')
} catch (e) {
throw e
}
profiler.generateReport(filepath, contract)
}
200 changes: 128 additions & 72 deletions index.js
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,143 @@
const parser = require("solidity-parser");
const asciiTable = require('ascii-table');
const fs = require('fs')
const path = require('path')
const asciiTable = require('ascii-table')
const parser = require('solidity-parser-antlr')
const utils = require('./utils.js')
const report = require('./parsers')

if(process.argv.length < 3) {
console.log("Error: Missing argument for sol file to scan");
process.exit(1);
}
let contract
let parsedContract

var target = process.argv[2],
contract = parser.parseFile(target);
function generateReportForDir (dir) {
const files = utils.getAllFiles(dir)
files
.filter(filepath => filepath.split('.').pop() === 'sol')
.forEach(filepath => {
try {
contract = fs.readFileSync(filepath, 'utf8')
generateReport(filepath, contract)
} catch (e) {
console.log(`Error reading contract ${contract} :`, e)
}
})
}

generateReport(target, contract);
function generateReport (filepath, contract) {
// write output to file
let filename = path.basename(filepath)
let reportName = `./output/${filename}.md`
let writeStream = fs.createWriteStream(reportName)

function generateReport(target, contract) {
var table = new asciiTable(target);
table.setHeading('Contract', 'Function', 'Visibility', 'Constant', 'Returns', 'Modifiers');
try {
parsedContract = parser.parse(contract)
} catch (e) {
if (e instanceof parser.ParserError) {
console.log(e.errors)
}
}

contract.body.forEach(function(contract) {
if(contract.type == 'ContractStatement') {
contract.body.forEach(function(part) {
if(part.type == "FunctionDeclaration" && part.is_abstract == false) {
var func = parseFunctionPart(contract, part);
table.addRow(func.contract, func.function, func.visibility, func.constant, func.returns, func.modifiers);
let functionRows = []
let eventRows = []
let modifierRows = []
let importRows = []
let contractInfo = ''
for (const node of parsedContract.children) {
const type = node.type
switch (type) {
// Contract solidity version
case 'PragmaDirective':
const contractPragma = node.name
const contractPragmaVersion = node.value
contractInfo += `pragma ${contractPragma} ${contractPragmaVersion}`
break
// Contract name and type
case 'ContractDefinition':
const contractName = node.name
const contractType = node.kind
contractInfo += ` ${contractType} ${contractName}`
for (const subNode of node.subNodes) {
switch (subNode.type) {
// Contract Functions
case 'FunctionDefinition':
functionRows.push(report.parseFunction(subNode))
break
// Contract Events
case 'EventDefinition':
eventRows.push(report.parseEvent(subNode))
break
// Contract Modifiers
case 'ModifierDefinition':
modifierRows.push(report.parseModifier(subNode))
break
}
}
})
break
// Contract Imports
case 'ImportDirective':
importRows.push(report.parseImport(node))
break
}
}

let generalInfoTable = asciiTable.factory({
heading: [contractInfo],
rows: [filepath]
})

let functionTable = asciiTable.factory({
title: 'functions',
heading: ['name', 'visibility', 'return', 'modifiers'],
rows: functionRows
})

let eventTable = asciiTable.factory({
heading: ['events'],
rows: eventRows
})

let modifierTable = asciiTable.factory({
heading: ['modifiers'],
rows: modifierRows
})
console.log(table.toString());
}

function parseFunctionPart(contract, part) {
var contractName = contract.name,
funcName = part.name || "",
params = [];

if(part.params) {
part.params.forEach(function(param) {
params.push(param.literal.literal);
});
funcName += "(" + params.join(',') + ")"
} else {
funcName += "()"
let importTable = asciiTable.factory({
heading: ['imports'],
rows: importRows
})

// very fancy markdown ;)
writeStream.write('```' + '\r\n')

writeStream.write(generalInfoTable.toString() + '\r\n')

if (importRows.length > 0) {
writeStream.write(importTable.toString() + '\r\n')
}

// Default is public
var visibility = "public"
isConstant = false,
returns = [],
custom = [];

if(part.modifiers) {
part.modifiers.forEach(function(mod) {
switch(mod.name) {
case "public":
break;
case "private":
visibility = "private";
break;
case "internal":
visibility = "internal";
break;
case "external":
visibility = "external";
break;
case "constant":
isConstant = true;
break;
case "returns":
mod.params.forEach(function(param) {
returns.push(param.name);
});
break;
default:
custom.push(mod.name);
}
});
if (functionRows.length > 0) {
writeStream.write(functionTable.toString() + '\r\n')
}

if (modifierRows.length > 0) {
writeStream.write(modifierTable.toString() + '\r\n')
}

return {
contract: contractName,
function: funcName,
visibility: visibility,
constant: isConstant,
returns: returns,
modifiers: custom
if (eventRows.length > 0) {
writeStream.write(eventTable.toString() + '\r\n')
}

writeStream.write('```' + '\r\n')

// the finish event is emitted when all data has been flushed from the stream
writeStream.on('finish', function () {
console.log(`written ${reportName}`)
})

// close the stream
writeStream.end()
}

module.exports = {
generateReport,
generateReportForDir
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Enhancements & Upgrades by agonzalezv · Pull Request #1 · EricR/sol-function-profiler · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
# report outputs
output/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# Runtime data
pids
*.pid
Expand All@@ -15,7 +19,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
/coverage

# nyc test coverage
.nyc_output
Expand All@@ -29,14 +33,15 @@ bower_components
# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
/build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/

# Optional npm cache directory
Expand All@@ -56,4 +61,25 @@ typings/

# dotenv environment variables file
.env

.env.local
.env.development.local
.env.test.local
.env.production.local

# next.js build output
.next

#other
.lck


# OS generated files #
######################
.DS_Store
.DS_Store?
.idea
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
64 changes: 51 additions & 13 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
# Solidity Function Profiler

A command line tool that generates a human-consumable report listing a contract's functions. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.
A command line tool that generates a human readable report with contract information. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.

This version ditches the now deprecated [ConsenSys Solidity Parser](https://github.com/ConsenSys/solidity-parser) in favor of [solidity-parser-antlr](https://github.com/federicobond/solidity-parser-antlr).

Usage Example:

```
$ npm install
...
$ node index.js ~/contracts/mytoken.sol
.--------------------------------------------------------------------------------------------------------.
| ~/contracts/mytoken.sol |
|--------------------------------------------------------------------------------------------------------|
| Contract | Function | Visibility | Constant | Returns | Modifiers |
|---------------|-------------------------------|------------|----------|-----------|--------------------|
| MyToken | () | public | false | | payable |
| MyToken | initTokenHolder(address,uint) | public | false | | onlyOwner |
| MyToken | balance(address) | public | true | uint | |
| MyToken | transferAll(address,address) | external | false | | onlyTokenHolder |
| MyToken | kill() | internal | false | | |
'--------------------------------------------------------------------------------------------------------'

$ chmod +x cli.js

$ ./cli.js ~/contracts/mytoken.sol

You can also give a directory as an argument using the --dir flag, this will generate a report on all files ending in .sol the directory or its subdirectories.

$ ./cli.js --dir ~/contracts

```
This will produce a `.md` file on the `output` folder, for every contract scanned. e.g:

`~/output/myToken.sol.md` will have:

```

.------------------------------------------.
| pragma solidity 0.10.99 contract MyToken |
|------------------------------------------|
| ~/contracts/mytoken.sol |
'------------------------------------------'
.-----------------------------.
| imports |
|-----------------------------|
| ../import/FancyContract.sol |
'-----------------------------'
.----------------------------------------------------------------------.
| functions |
|----------------------------------------------------------------------|
| name | visibility | return | modifiers |
|------------------------------------|------------|--------|-----------|
| constructor(_param) payable | public | | |
| otherFunction(_param, _otherParam) | external | bool | onlyOwner |
| fancyPants(_pants) | external | | onlyOwner |
'----------------------------------------------------------------------'
.--------------.
| modifiers |
|--------------|
| onlyOwner() |
'--------------'
.-------------------------------------.
| events |
|-------------------------------------|
| MySpecialEvent(_param) |
'-------------------------------------'

```

multiple dir feature based on [this repo](https://github.com/maurelian/sol-function-profiler)
32 changes: 32 additions & 0 deletions cli.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const profiler = require('./index.js')
const argv = require('yargs').argv
const utils = require('./utils')

// recreate reporting output folder
const dirname = 'output'
utils.recreateFolder(dirname)

if (argv.dir) {
// is a directory?
profiler.generateReportForDir(argv.dir)
} else {
// is a single contract?
let arg_index = argv._.length - 1
let filepath = argv._[arg_index] // argv puts the final arguments in an array named "_"

if (!filepath) {
console.log('usage: --dir <dir> or path to .sol file')
process.exit(1)
}
let contract
try {
contract = fs.readFileSync(filepath, 'utf8')
} catch (e) {
throw e
}
profiler.generateReport(filepath, contract)
}
200 changes: 128 additions & 72 deletions index.js
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,143 @@
const parser = require("solidity-parser");
const asciiTable = require('ascii-table');
const fs = require('fs')
const path = require('path')
const asciiTable = require('ascii-table')
const parser = require('solidity-parser-antlr')
const utils = require('./utils.js')
const report = require('./parsers')

if(process.argv.length < 3) {
console.log("Error: Missing argument for sol file to scan");
process.exit(1);
}
let contract
let parsedContract

var target = process.argv[2],
contract = parser.parseFile(target);
function generateReportForDir (dir) {
const files = utils.getAllFiles(dir)
files
.filter(filepath => filepath.split('.').pop() === 'sol')
.forEach(filepath => {
try {
contract = fs.readFileSync(filepath, 'utf8')
generateReport(filepath, contract)
} catch (e) {
console.log(`Error reading contract ${contract} :`, e)
}
})
}

generateReport(target, contract);
function generateReport (filepath, contract) {
// write output to file
let filename = path.basename(filepath)
let reportName = `./output/${filename}.md`
let writeStream = fs.createWriteStream(reportName)

function generateReport(target, contract) {
var table = new asciiTable(target);
table.setHeading('Contract', 'Function', 'Visibility', 'Constant', 'Returns', 'Modifiers');
try {
parsedContract = parser.parse(contract)
} catch (e) {
if (e instanceof parser.ParserError) {
console.log(e.errors)
}
}

contract.body.forEach(function(contract) {
if(contract.type == 'ContractStatement') {
contract.body.forEach(function(part) {
if(part.type == "FunctionDeclaration" && part.is_abstract == false) {
var func = parseFunctionPart(contract, part);
table.addRow(func.contract, func.function, func.visibility, func.constant, func.returns, func.modifiers);
let functionRows = []
let eventRows = []
let modifierRows = []
let importRows = []
let contractInfo = ''
for (const node of parsedContract.children) {
const type = node.type
switch (type) {
// Contract solidity version
case 'PragmaDirective':
const contractPragma = node.name
const contractPragmaVersion = node.value
contractInfo += `pragma ${contractPragma} ${contractPragmaVersion}`
break
// Contract name and type
case 'ContractDefinition':
const contractName = node.name
const contractType = node.kind
contractInfo += ` ${contractType} ${contractName}`
for (const subNode of node.subNodes) {
switch (subNode.type) {
// Contract Functions
case 'FunctionDefinition':
functionRows.push(report.parseFunction(subNode))
break
// Contract Events
case 'EventDefinition':
eventRows.push(report.parseEvent(subNode))
break
// Contract Modifiers
case 'ModifierDefinition':
modifierRows.push(report.parseModifier(subNode))
break
}
}
})
break
// Contract Imports
case 'ImportDirective':
importRows.push(report.parseImport(node))
break
}
}

let generalInfoTable = asciiTable.factory({
heading: [contractInfo],
rows: [filepath]
})

let functionTable = asciiTable.factory({
title: 'functions',
heading: ['name', 'visibility', 'return', 'modifiers'],
rows: functionRows
})

let eventTable = asciiTable.factory({
heading: ['events'],
rows: eventRows
})

let modifierTable = asciiTable.factory({
heading: ['modifiers'],
rows: modifierRows
})
console.log(table.toString());
}

function parseFunctionPart(contract, part) {
var contractName = contract.name,
funcName = part.name || "",
params = [];

if(part.params) {
part.params.forEach(function(param) {
params.push(param.literal.literal);
});
funcName += "(" + params.join(',') + ")"
} else {
funcName += "()"
let importTable = asciiTable.factory({
heading: ['imports'],
rows: importRows
})

// very fancy markdown ;)
writeStream.write('```' + '\r\n')

writeStream.write(generalInfoTable.toString() + '\r\n')

if (importRows.length > 0) {
writeStream.write(importTable.toString() + '\r\n')
}

// Default is public
var visibility = "public"
isConstant = false,
returns = [],
custom = [];

if(part.modifiers) {
part.modifiers.forEach(function(mod) {
switch(mod.name) {
case "public":
break;
case "private":
visibility = "private";
break;
case "internal":
visibility = "internal";
break;
case "external":
visibility = "external";
break;
case "constant":
isConstant = true;
break;
case "returns":
mod.params.forEach(function(param) {
returns.push(param.name);
});
break;
default:
custom.push(mod.name);
}
});
if (functionRows.length > 0) {
writeStream.write(functionTable.toString() + '\r\n')
}

if (modifierRows.length > 0) {
writeStream.write(modifierTable.toString() + '\r\n')
}

return {
contract: contractName,
function: funcName,
visibility: visibility,
constant: isConstant,
returns: returns,
modifiers: custom
if (eventRows.length > 0) {
writeStream.write(eventTable.toString() + '\r\n')
}

writeStream.write('```' + '\r\n')

// the finish event is emitted when all data has been flushed from the stream
writeStream.on('finish', function () {
console.log(`written ${reportName}`)
})

// close the stream
writeStream.end()
}

module.exports = {
generateReport,
generateReportForDir
}
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Enhancements & Upgrades by agonzalezv · Pull Request #1 · EricR/sol-function-profiler · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions .gitignore
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
# report outputs
output/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*


# Runtime data
pids
*.pid
Expand All@@ -15,7 +19,7 @@ pids
lib-cov

# Coverage directory used by tools like istanbul
coverage
/coverage

# nyc test coverage
.nyc_output
Expand All@@ -29,14 +33,15 @@ bower_components
# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
# Compiled binary addons (https://nodejs.org/api/addons.html)
/build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
# TypeScript v1 declaration files
typings/

# Optional npm cache directory
Expand All@@ -56,4 +61,25 @@ typings/

# dotenv environment variables file
.env

.env.local
.env.development.local
.env.test.local
.env.production.local

# next.js build output
.next

#other
.lck


# OS generated files #
######################
.DS_Store
.DS_Store?
.idea
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
64 changes: 51 additions & 13 deletions README.md
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,60 @@
# Solidity Function Profiler

A command line tool that generates a human-consumable report listing a contract's functions. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.
A command line tool that generates a human readable report with contract information. This is useful during manual code review to understand what functions are made public, use which modifiers, and so on.

This version ditches the now deprecated [ConsenSys Solidity Parser](https://github.com/ConsenSys/solidity-parser) in favor of [solidity-parser-antlr](https://github.com/federicobond/solidity-parser-antlr).

Usage Example:

```
$ npm install
...
$ node index.js ~/contracts/mytoken.sol
.--------------------------------------------------------------------------------------------------------.
| ~/contracts/mytoken.sol |
|--------------------------------------------------------------------------------------------------------|
| Contract | Function | Visibility | Constant | Returns | Modifiers |
|---------------|-------------------------------|------------|----------|-----------|--------------------|
| MyToken | () | public | false | | payable |
| MyToken | initTokenHolder(address,uint) | public | false | | onlyOwner |
| MyToken | balance(address) | public | true | uint | |
| MyToken | transferAll(address,address) | external | false | | onlyTokenHolder |
| MyToken | kill() | internal | false | | |
'--------------------------------------------------------------------------------------------------------'

$ chmod +x cli.js

$ ./cli.js ~/contracts/mytoken.sol

You can also give a directory as an argument using the --dir flag, this will generate a report on all files ending in .sol the directory or its subdirectories.

$ ./cli.js --dir ~/contracts

```
This will produce a `.md` file on the `output` folder, for every contract scanned. e.g:

`~/output/myToken.sol.md` will have:

```

.------------------------------------------.
| pragma solidity 0.10.99 contract MyToken |
|------------------------------------------|
| ~/contracts/mytoken.sol |
'------------------------------------------'
.-----------------------------.
| imports |
|-----------------------------|
| ../import/FancyContract.sol |
'-----------------------------'
.----------------------------------------------------------------------.
| functions |
|----------------------------------------------------------------------|
| name | visibility | return | modifiers |
|------------------------------------|------------|--------|-----------|
| constructor(_param) payable | public | | |
| otherFunction(_param, _otherParam) | external | bool | onlyOwner |
| fancyPants(_pants) | external | | onlyOwner |
'----------------------------------------------------------------------'
.--------------.
| modifiers |
|--------------|
| onlyOwner() |
'--------------'
.-------------------------------------.
| events |
|-------------------------------------|
| MySpecialEvent(_param) |
'-------------------------------------'

```

multiple dir feature based on [this repo](https://github.com/maurelian/sol-function-profiler)
32 changes: 32 additions & 0 deletions cli.js
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
#! /usr/bin/env node

const fs = require('fs')
const path = require('path')
const profiler = require('./index.js')
const argv = require('yargs').argv
const utils = require('./utils')

// recreate reporting output folder
const dirname = 'output'
utils.recreateFolder(dirname)

if (argv.dir) {
// is a directory?
profiler.generateReportForDir(argv.dir)
} else {
// is a single contract?
let arg_index = argv._.length - 1
let filepath = argv._[arg_index] // argv puts the final arguments in an array named "_"

if (!filepath) {
console.log('usage: --dir <dir> or path to .sol file')
process.exit(1)
}
let contract
try {
contract = fs.readFileSync(filepath, 'utf8')
} catch (e) {
throw e
}
profiler.generateReport(filepath, contract)
}
200 changes: 128 additions & 72 deletions index.js
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,143 @@
const parser = require("solidity-parser");
const asciiTable = require('ascii-table');
const fs = require('fs')
const path = require('path')
const asciiTable = require('ascii-table')
const parser = require('solidity-parser-antlr')
const utils = require('./utils.js')
const report = require('./parsers')

if(process.argv.length < 3) {
console.log("Error: Missing argument for sol file to scan");
process.exit(1);
}
let contract
let parsedContract

var target = process.argv[2],
contract = parser.parseFile(target);
function generateReportForDir (dir) {
const files = utils.getAllFiles(dir)
files
.filter(filepath => filepath.split('.').pop() === 'sol')
.forEach(filepath => {
try {
contract = fs.readFileSync(filepath, 'utf8')
generateReport(filepath, contract)
} catch (e) {
console.log(`Error reading contract ${contract} :`, e)
}
})
}

generateReport(target, contract);
function generateReport (filepath, contract) {
// write output to file
let filename = path.basename(filepath)
let reportName = `./output/${filename}.md`
let writeStream = fs.createWriteStream(reportName)

function generateReport(target, contract) {
var table = new asciiTable(target);
table.setHeading('Contract', 'Function', 'Visibility', 'Constant', 'Returns', 'Modifiers');
try {
parsedContract = parser.parse(contract)
} catch (e) {
if (e instanceof parser.ParserError) {
console.log(e.errors)
}
}

contract.body.forEach(function(contract) {
if(contract.type == 'ContractStatement') {
contract.body.forEach(function(part) {
if(part.type == "FunctionDeclaration" && part.is_abstract == false) {
var func = parseFunctionPart(contract, part);
table.addRow(func.contract, func.function, func.visibility, func.constant, func.returns, func.modifiers);
let functionRows = []
let eventRows = []
let modifierRows = []
let importRows = []
let contractInfo = ''
for (const node of parsedContract.children) {
const type = node.type
switch (type) {
// Contract solidity version
case 'PragmaDirective':
const contractPragma = node.name
const contractPragmaVersion = node.value
contractInfo += `pragma ${contractPragma} ${contractPragmaVersion}`
break
// Contract name and type
case 'ContractDefinition':
const contractName = node.name
const contractType = node.kind
contractInfo += ` ${contractType} ${contractName}`
for (const subNode of node.subNodes) {
switch (subNode.type) {
// Contract Functions
case 'FunctionDefinition':
functionRows.push(report.parseFunction(subNode))
break
// Contract Events
case 'EventDefinition':
eventRows.push(report.parseEvent(subNode))
break
// Contract Modifiers
case 'ModifierDefinition':
modifierRows.push(report.parseModifier(subNode))
break
}
}
})
break
// Contract Imports
case 'ImportDirective':
importRows.push(report.parseImport(node))
break
}
}

let generalInfoTable = asciiTable.factory({
heading: [contractInfo],
rows: [filepath]
})

let functionTable = asciiTable.factory({
title: 'functions',
heading: ['name', 'visibility', 'return', 'modifiers'],
rows: functionRows
})

let eventTable = asciiTable.factory({
heading: ['events'],
rows: eventRows
})

let modifierTable = asciiTable.factory({
heading: ['modifiers'],
rows: modifierRows
})
console.log(table.toString());
}

function parseFunctionPart(contract, part) {
var contractName = contract.name,
funcName = part.name || "",
params = [];

if(part.params) {
part.params.forEach(function(param) {
params.push(param.literal.literal);
});
funcName += "(" + params.join(',') + ")"
} else {
funcName += "()"
let importTable = asciiTable.factory({
heading: ['imports'],
rows: importRows
})

// very fancy markdown ;)
writeStream.write('```' + '\r\n')

writeStream.write(generalInfoTable.toString() + '\r\n')

if (importRows.length > 0) {
writeStream.write(importTable.toString() + '\r\n')
}

// Default is public
var visibility = "public"
isConstant = false,
returns = [],
custom = [];

if(part.modifiers) {
part.modifiers.forEach(function(mod) {
switch(mod.name) {
case "public":
break;
case "private":
visibility = "private";
break;
case "internal":
visibility = "internal";
break;
case "external":
visibility = "external";
break;
case "constant":
isConstant = true;
break;
case "returns":
mod.params.forEach(function(param) {
returns.push(param.name);
});
break;
default:
custom.push(mod.name);
}
});
if (functionRows.length > 0) {
writeStream.write(functionTable.toString() + '\r\n')
}

if (modifierRows.length > 0) {
writeStream.write(modifierTable.toString() + '\r\n')
}

return {
contract: contractName,
function: funcName,
visibility: visibility,
constant: isConstant,
returns: returns,
modifiers: custom
if (eventRows.length > 0) {
writeStream.write(eventTable.toString() + '\r\n')
}

writeStream.write('```' + '\r\n')

// the finish event is emitted when all data has been flushed from the stream
writeStream.on('finish', function () {
console.log(`written ${reportName}`)
})

// close the stream
writeStream.end()
}

module.exports = {
generateReport,
generateReportForDir
}
Loading