Add gas checker.

pull/1/head
chriseth 8 years ago
parent 40bc8f6070
commit 999b91930a
  1. 3
      assets/css/browser-solidity.css
  2. 2
      package.json
  3. 9
      src/app/formalVerification.js
  4. 20
      src/app/renderer.js
  5. 50
      src/app/staticanalysis/modules/gasCosts.js
  6. 3
      src/app/staticanalysis/modules/list.js
  7. 17
      src/app/staticanalysis/modules/txOrigin.js
  8. 26
      src/app/staticanalysis/staticAnalysisRunner.js
  9. 33
      src/app/staticanalysis/staticAnalysisView.js

@ -299,6 +299,9 @@ body {
margin-right: 1em; margin-right: 1em;
cursor: pointer; cursor: pointer;
} }
#staticanalysismodules label {
display: block;
}
#header .origin, #header .origin,
#header #executionContext { #header #executionContext {

@ -10,7 +10,7 @@
"browser-test-remote-ie": "nightwatch --config nightwatch.js --env ie", "browser-test-remote-ie": "nightwatch --config nightwatch.js --env ie",
"browser-test-remote-safari": "nightwatch --config nightwatch.js --env safari", "browser-test-remote-safari": "nightwatch --config nightwatch.js --env safari",
"browser-test-remote-parallel": "nightwatch --config nightwatch.js --env safari,ie,default,chrome", "browser-test-remote-parallel": "nightwatch --config nightwatch.js --env safari,ie,default,chrome",
"build": "mkdir -p build; browserify src/index.js -g yo-yoify -o build/app.js; babel build/app.js --out-file build/app.js", "build": "mkdir -p build; browserify src/index.js -g yo-yoify -o build/app.js; babel build/app.js -s --out-file build/app.js",
"csslint": "csslint --ignore=order-alphabetical --errors='errors,duplicate-properties,empty-rules' --exclude-list='assets/css/font-awesome.min.css' assets/css/", "csslint": "csslint --ignore=order-alphabetical --errors='errors,duplicate-properties,empty-rules' --exclude-list='assets/css/font-awesome.min.css' assets/css/",
"downloadsolc": "rm soljson.js; wget https://ethereum.github.io/solc-bin/soljson.js", "downloadsolc": "rm soljson.js; wget https://ethereum.github.io/solc-bin/soljson.js",
"lint": "standard", "lint": "standard",

@ -25,7 +25,10 @@ function FormalVerification (outputElement, compilerEvent) {
FormalVerification.prototype.compilationFinished = function (compilationResult) { FormalVerification.prototype.compilationFinished = function (compilationResult) {
if (compilationResult.formal === undefined) { if (compilationResult.formal === undefined) {
this.event.trigger('compilationFinished', [false, 'Formal verification not supported by this compiler version.', $('#formalVerificationErrors'), true]) this.event.trigger(
'compilationFinished',
[false, 'Formal verification not supported by this compiler version.', $('#formalVerificationErrors'), {noAnnotations: true}]
)
} else { } else {
if (compilationResult.formal['why3'] !== undefined) { if (compilationResult.formal['why3'] !== undefined) {
$('#formalVerificationInput', this.outputElement).val( $('#formalVerificationInput', this.outputElement).val(
@ -37,10 +40,10 @@ FormalVerification.prototype.compilationFinished = function (compilationResult)
if (compilationResult.formal.errors !== undefined) { if (compilationResult.formal.errors !== undefined) {
var errors = compilationResult.formal.errors var errors = compilationResult.formal.errors
for (var i = 0; i < errors.length; i++) { for (var i = 0; i < errors.length; i++) {
this.event.trigger('compilationFinished', [false, errors[i], $('#formalVerificationErrors'), true]) this.event.trigger('compilationFinished', [false, errors[i], $('#formalVerificationErrors'), {noAnnotations: true}])
} }
} else { } else {
this.event.trigger('compilationFinished', [true, null, null, true]) this.event.trigger('compilationFinished', [true, null, null, {noAnnotations: true}])
} }
} }
} }

@ -11,9 +11,9 @@ function Renderer (editor, updateFiles, udapp, executionContext, formalVerificat
this.udapp = udapp this.udapp = udapp
this.executionContext = executionContext this.executionContext = executionContext
var self = this var self = this
formalVerificationEvent.register('compilationFinished', this, function (success, message, container, noAnnotations) { formalVerificationEvent.register('compilationFinished', this, function (success, message, container, options) {
if (!success) { if (!success) {
self.error(message, container, noAnnotations) self.error(message, container, options)
} }
}) })
compilerEvent.register('compilationFinished', this, function (success, data, source) { compilerEvent.register('compilationFinished', this, function (success, data, source) {
@ -34,13 +34,14 @@ function Renderer (editor, updateFiles, udapp, executionContext, formalVerificat
}) })
} }
Renderer.prototype.error = function (message, container, noAnnotations, type) { Renderer.prototype.error = function (message, container, options) {
var self = this var self = this
if (!type) { var opt = options || {}
type = utils.errortype(message) if (!opt.type) {
opt.type = utils.errortype(message)
} }
var $pre = $('<pre />').text(message) var $pre = $(opt.useSpan ? '<span />' : '<pre />').text(message)
var $error = $('<div class="sol ' + type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre) var $error = $('<div class="sol ' + opt.type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre)
if (container === undefined) { if (container === undefined) {
container = $('#output') container = $('#output')
} }
@ -50,12 +51,12 @@ Renderer.prototype.error = function (message, container, noAnnotations, type) {
var errFile = err[1] var errFile = err[1]
var errLine = parseInt(err[2], 10) - 1 var errLine = parseInt(err[2], 10) - 1
var errCol = err[4] ? parseInt(err[4], 10) : 0 var errCol = err[4] ? parseInt(err[4], 10) : 0
if (!noAnnotations && (errFile === '' || errFile === utils.fileNameFromKey(self.editor.getCacheFile()))) { if (!opt.noAnnotations && (errFile === '' || errFile === utils.fileNameFromKey(self.editor.getCacheFile()))) {
self.editor.addAnnotation({ self.editor.addAnnotation({
row: errLine, row: errLine,
column: errCol, column: errCol,
text: message, text: message,
type: type type: opt.type
}) })
} }
$error.click(function (ev) { $error.click(function (ev) {
@ -63,7 +64,6 @@ Renderer.prototype.error = function (message, container, noAnnotations, type) {
// Switch to file // Switch to file
self.editor.setCacheFile(utils.fileKey(errFile)) self.editor.setCacheFile(utils.fileKey(errFile))
self.updateFiles() self.updateFiles()
// @TODO could show some error icon in files with errors
} }
self.editor.handleErrorClick(errLine, errCol) self.editor.handleErrorClick(errLine, errCol)
}) })

@ -0,0 +1,50 @@
var name = 'gas costs'
var desc = 'Warn if the gas requiremets of functions are too high.'
function gasCosts () {
}
gasCosts.prototype.report = function (compilationResults) {
var report = []
for (var contractName in compilationResults.contracts) {
var contract = compilationResults.contracts[contractName]
if (
contract.gasEstimates === undefined ||
contract.gasEstimates.external === undefined
) {
continue
}
var fallback = contract.gasEstimates.external['']
if (fallback !== undefined) {
if (fallback === null || fallback >= 2100) {
report.push({
warning: `Fallback function of contract ${contractName} requires too much gas (${fallback}).\n
If the fallback function requires too much gas, the contract cannot receive Ether.`
})
}
}
for (var functionName in contract.gasEstimates.external) {
if (functionName === '') {
continue
}
var gas = contract.gasEstimates.external[functionName]
var gasString = gas === null ? 'unknown or not constant' : 'high: ' + gas
if (gas === null || gas >= 3000000) {
report.push({
warning: `Gas requirement of function ${contractName}.${functionName} ${gasString}.\n
If the gas requirement of a function is higher than the block gas limit, it cannot be executed.
Please avoid loops in your functions or actions that modify large areas of storage
(this includes clearing or copying arrays in storage)`
})
}
}
}
return report
}
module.exports = {
name: name,
description: desc,
Module: gasCosts
}

@ -1,3 +1,4 @@
module.exports = [ module.exports = [
require('./txOrigin') require('./txOrigin'),
require('./gasCosts')
] ]

@ -2,7 +2,7 @@ var name = 'tx origin'
var desc = 'warn if tx.origin is used' var desc = 'warn if tx.origin is used'
function txOrigin () { function txOrigin () {
this.txOriginNode = [] this.txOriginNodes = []
} }
txOrigin.prototype.visit = function (node) { txOrigin.prototype.visit = function (node) {
@ -12,23 +12,18 @@ txOrigin.prototype.visit = function (node) {
node.children && node.children.length && node.children && node.children.length &&
node.children[0].attributes.type === 'tx' && node.children[0].attributes.type === 'tx' &&
node.children[0].attributes.value === 'tx') { node.children[0].attributes.value === 'tx') {
this.txOriginNode.push(node) this.txOriginNodes.push(node)
} }
} }
txOrigin.prototype.report = function (node) { txOrigin.prototype.report = function () {
var report = [] return this.txOriginNodes.map(function (item, i) {
this.txOriginNode.map(function (item, i) { return {
report.push({
warning: `use of tx.origin: "tx.origin" is useful only in very exceptional cases.\n warning: `use of tx.origin: "tx.origin" is useful only in very exceptional cases.\n
If you use it for authentication, you usually want to replace it by "msg.sender", because otherwise any contract you call can act on your behalf.`, If you use it for authentication, you usually want to replace it by "msg.sender", because otherwise any contract you call can act on your behalf.`,
location: item.src location: item.src
}) }
}) })
return {
name: name,
report: report
}
} }
module.exports = { module.exports = {

@ -5,20 +5,30 @@ var list = require('./modules/list')
function staticAnalysisRunner () { function staticAnalysisRunner () {
} }
staticAnalysisRunner.prototype.run = function (ast, toRun, callback) { staticAnalysisRunner.prototype.run = function (compilationResult, toRun, callback) {
var self = this
var modules = toRun.map(function (i) {
var m = self.modules()[i]
return { 'name': m.name, 'mod': new m.Module() }
})
// Also provide convenience analysis via the AST walker.
var walker = new AstWalker() var walker = new AstWalker()
for (var k in ast) { for (var k in compilationResult.sources) {
walker.walk(ast[k].AST, {'*': function (node) { walker.walk(compilationResult.sources[k].AST, {'*': function (node) {
toRun.map(function (item, i) { modules.map(function (item, i) {
item.visit(node) if (item.mod.visit !== undefined) {
item.mod.visit(node)
}
}) })
return true return true
}}) }})
} }
var reports = [] // Here, modules can just collect the results from the AST walk,
toRun.map(function (item, i) { // but also perform new analysis.
reports.push(item.report()) var reports = modules.map(function (item, i) {
return { name: item.name, report: item.mod.report(compilationResult) }
}) })
callback(reports) callback(reports)
} }

@ -28,8 +28,10 @@ staticAnalysisView.prototype.render = function () {
var self = this var self = this
var view = yo`<div> var view = yo`<div>
<strong>Static Analysis</strong> <strong>Static Analysis</strong>
<div>Select analyser to run against current compiled contracts <label><input id="autorunstaticanalysis" type="checkbox" checked="true">Auto run Static Analysis</label></div> <label for="autorunstaticanalysis"><input id="autorunstaticanalysis" type="checkbox" checked="true">Auto run</label>
<div id="staticanalysismodules">
${this.modulesView} ${this.modulesView}
</div>
<div> <div>
<button onclick=${function () { self.run() }} >Run</button> <button onclick=${function () { self.run() }} >Run</button>
</div> </div>
@ -45,14 +47,10 @@ staticAnalysisView.prototype.selectedModules = function () {
if (!this.view) { if (!this.view) {
return [] return []
} }
var selected = this.view.querySelectorAll('[name="staticanalysismodule"]') var selected = this.view.querySelectorAll('[name="staticanalysismodule"]:checked')
var toRun = [] var toRun = []
for (var i = 0; i < selected.length; i++) { for (var i = 0; i < selected.length; i++) {
var el = selected[i] toRun.push(selected[i].attributes['index'].value)
if (el.checked) {
var analyser = this.runner.modules()[el.attributes['index'].value]
toRun.push(new analyser.Module())
}
} }
return toRun return toRun
} }
@ -66,18 +64,21 @@ staticAnalysisView.prototype.run = function () {
warningContainer.empty() warningContainer.empty()
if (this.lastCompilationResult) { if (this.lastCompilationResult) {
var self = this var self = this
this.runner.run(this.lastCompilationResult.sources, selected, function (results) { this.runner.run(this.lastCompilationResult, selected, function (results) {
results.map(function (result, i) { results.map(function (result, i) {
result.report.map(function (item, i) { result.report.map(function (item, i) {
var split = item.location.split(':') var location = ''
var file = split[2] if (item.location !== undefined) {
var location = { var split = item.location.split(':')
start: parseInt(split[0]), var file = split[2]
length: parseInt(split[1]) location = {
start: parseInt(split[0]),
length: parseInt(split[1])
}
location = self.offsetToColumnConverter.offsetToLineColumn(location, file, self.editor, self.lastCompilationResult)
location = self.lastCompilationResult.sourceList[file] + ':' + (location.start.line + 1) + ':' + (location.start.column + 1) + ':'
} }
location = self.offsetToColumnConverter.offsetToLineColumn(location, file, self.editor, self.lastCompilationResult) self.renderer.error(location + ' ' + item.warning, warningContainer, {type: 'warning', useSpan: true})
location = self.lastCompilationResult.sourceList[file] + ':' + (location.start.line + 1) + ':' + (location.start.column + 1) + ':'
self.renderer.error(location + ' ' + item.warning, warningContainer, false, 'warning')
}) })
}) })
if (warningContainer.html() === '') { if (warningContainer.html() === '') {

Loading…
Cancel
Save