Merge pull request #167 from yann300/refactoring_event

Refactoring:: add events
pull/1/head
chriseth 8 years ago committed by GitHub
commit 0611230d62
  1. 65
      src/app.js
  2. 50
      src/app/compiler.js
  3. 10
      src/app/debugger.js
  4. 35
      src/app/execution-context.js
  5. 37
      src/app/formalVerification.js
  6. 350
      src/app/renderer.js
  7. 161
      src/app/ui-helper.js
  8. 64
      src/lib/eventManager.js
  9. 89
      src/universal-dapp.js
  10. 4
      test/compiler-test.js

@ -13,9 +13,10 @@ var Editor = require('./app/editor');
var Renderer = require('./app/renderer');
var Compiler = require('./app/compiler');
var ExecutionContext = require('./app/execution-context');
var UniversalDApp = require('./universal-dapp.js');
var Debugger = require('./app/debugger');
var FormalVerification = require('./app/formalVerification');
var EthJSVM = require('ethereumjs-vm');
var EventManager = require('./lib/eventManager');
// The event listener needs to be registered as early as possible, because the
// parent will send the message upon the "load" event.
@ -26,8 +27,12 @@ window.addEventListener('message', function (ev) {
loadFilesCallback(ev.data[1]);
}
}, false);
/*
trigger tabChanged
*/
var run = function () {
var self = this;
this.event = new EventManager();
var storage = new Storage(updateFiles);
function loadFiles (files) {
@ -108,6 +113,7 @@ var run = function () {
el.removeClass('active');
$('#optionViews').removeClass(cls);
}
self.event.trigger('tabChanged', [cls]);
};
// ------------------ gist publish --------------
@ -199,7 +205,7 @@ var run = function () {
$fileNameInputEl.off('keyup');
if (newName !== originalName && confirm(
storage.exists(utils.fileKey(newName))
storage.exists(utils.fileKey(newName))
? 'Are you sure you want to overwrite: ' + newName + ' with ' + originalName + '?'
: 'Are you sure you want to rename: ' + originalName + ' to ' + newName + '?')) {
storage.rename(utils.fileKey(originalName), utils.fileKey(newName));
@ -279,7 +285,7 @@ var run = function () {
}
// function widthOfHidden () {
// return ($filesWrapper.outerWidth() - widthOfList() - getLeftPosi());
// return ($filesWrapper.outerWidth() - widthOfList() - getLeftPosi())
// }
function widthOfVisible () {
@ -405,11 +411,8 @@ var run = function () {
setEditorSize(hidingRHP ? 0 : storage.getEditorSize());
$('.toggleRHP i').toggleClass('fa-angle-double-right', !hidingRHP);
$('.toggleRHP i').toggleClass('fa-angle-double-left', hidingRHP);
if (!hidingRHP) compiler.compile();
});
function getHidingRHP () { return hidingRHP; }
// ----------------- editor resize ---------------
function onResize () {
@ -432,20 +435,46 @@ var run = function () {
$('#output').append($('<div/>').append($('<pre/>').text('Loading github.com/' + root + '/' + path + ' ...')));
return $.getJSON('https://api.github.com/repos/' + root + '/contents/' + path, cb);
}
var transactionDebugger = new Debugger('#debugger');
var vm = new EthJSVM(null, null, { activatePrecompiles: true, enableHomestead: true });
vm.stateManager.checkpoint();
transactionDebugger.addProvider('VM', vm);
var executionContext = new ExecutionContext();
var transactionDebugger = new Debugger('#debugger', executionContext.event);
transactionDebugger.addProvider('VM', executionContext.vm());
transactionDebugger.switchProvider('VM');
var executionContext = new ExecutionContext(transactionDebugger);
transactionDebugger.addProvider('INTERNAL', executionContext.web3());
transactionDebugger.addProvider('EXTERNAL', executionContext.web3());
transactionDebugger.onDebugRequested = function () {
selectTab($('ul#options li.debugView'));
};
var renderer = new Renderer(editor, executionContext, updateFiles, transactionDebugger, vm);
var formalVerification = new FormalVerification($('#verificationView'), renderer);
var compiler = new Compiler(editor, renderer, queryParams, handleGithubCall, $('#output'), getHidingRHP, formalVerification, updateFiles);
executionContext.setCompiler(compiler);
var udapp = new UniversalDApp(executionContext, {
removable: false,
removable_instances: true
}, transactionDebugger);
udapp.event.register('debugRequested', this, function (data) {
transactionDebugger.debug(data);
});
var compiler = new Compiler(editor, queryParams, handleGithubCall, updateFiles);
var formalVerification = new FormalVerification($('#verificationView'), compiler.event);
var renderer = new Renderer(editor, executionContext.web3(), updateFiles, udapp, executionContext, formalVerification.event, compiler.event); // eslint-disable-line
executionContext.event.register('contextChanged', this, function (context) {
compiler.compile();
});
executionContext.event.register('web3EndpointChanged', this, function (context) {
compiler.compile();
});
executionContext.event.register('compilerLoaded', this, function (context) {
compiler.compile();
});
compiler.event.register('compilerLoaded', this, function (version) {
setVersionText(version);
compiler.compile();
});
function setVersionText (text) {
$('#version').text(text);
@ -462,9 +491,9 @@ var run = function () {
// Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case.
compiler.loadVersion(true, version, setVersionText);
compiler.loadVersion(true, version);
} else {
compiler.loadVersion(false, version, setVersionText);
compiler.loadVersion(false, version);
}
};

@ -5,7 +5,15 @@ var utils = require('./utils');
var Base64 = require('js-base64').Base64;
function Compiler (editor, renderer, queryParams, handleGithubCall, outputField, hidingRHP, formalVerification, updateFiles) {
var EventManager = require('../lib/eventManager');
/*
trigger compilationFinished, compilerLoaded, compilationStarted
*/
function Compiler (editor, queryParams, handleGithubCall, updateFiles) {
var self = this;
this.event = new EventManager();
var compileJSON;
var compilerAcceptsMultipleFiles;
@ -36,19 +44,15 @@ function Compiler (editor, renderer, queryParams, handleGithubCall, outputField,
var compile = function (missingInputs) {
editor.clearAnnotations();
outputField.empty();
if (formalVerification) {
formalVerification.compiling();
}
self.event.trigger('compilationStarted', []);
var input = editor.getValue();
editor.setCacheFileContent(input);
var files = {};
files[utils.fileNameFromKey(editor.getCacheFile())] = input;
gatherImports(files, missingInputs, function (input, error) {
outputField.empty();
if (input === null) {
renderer.error(error);
this.event.trigger('compilationFinished', [false, error, editor.getValue()]);
} else {
var optimize = queryParams.get().optimize;
compileJSON(input, optimize ? 1 : 0);
@ -62,13 +66,12 @@ function Compiler (editor, renderer, queryParams, handleGithubCall, outputField,
}
this.setCompileJSON = setCompileJSON; // this is exposed for testing
function onCompilerLoaded (setVersionText, version) {
setVersionText(version);
function onCompilerLoaded (version) {
previousInput = '';
onChange();
self.event.trigger('compilerLoaded', [version]);
}
function onInternalCompilerLoaded (setVersionText) {
function onInternalCompilerLoaded () {
if (worker === null) {
var compiler = solc(window.Module);
@ -91,7 +94,7 @@ function Compiler (editor, renderer, queryParams, handleGithubCall, outputField,
compilationFinished(result, missingInputs);
};
onCompilerLoaded(setVersionText, compiler.version());
onCompilerLoaded(compiler.version());
}
}
@ -99,14 +102,14 @@ function Compiler (editor, renderer, queryParams, handleGithubCall, outputField,
var noFatalErrors = true; // ie warnings are ok
if (data['error'] !== undefined) {
renderer.error(data['error']);
self.event.trigger('compilationFinished', [false, [data['error']], editor.getValue()]);
if (utils.errortype(data['error']) !== 'warning') {
noFatalErrors = false;
}
}
if (data['errors'] !== undefined) {
self.event.trigger('compilationFinished', [false, data['errors'], editor.getValue()]);
data['errors'].forEach(function (err) {
renderer.error(err);
if (utils.errortype(err) !== 'warning') {
noFatalErrors = false;
}
@ -115,13 +118,12 @@ function Compiler (editor, renderer, queryParams, handleGithubCall, outputField,
if (missingInputs !== undefined && missingInputs.length > 0) {
compile(missingInputs);
} else if (noFatalErrors && !hidingRHP()) {
renderer.contracts(data, editor.getValue());
formalVerification.compilationFinished(data);
} else if (noFatalErrors) {
self.event.trigger('compilationFinished', [true, data, editor.getValue()]);
}
}
this.loadVersion = function (usingWorker, version, setVersionText) {
this.loadVersion = function (usingWorker, version) {
var url;
if (version !== 'soljson.js') {
url = 'https://ethereum.github.io/solc-bin/bin/' + version;
@ -131,13 +133,13 @@ function Compiler (editor, renderer, queryParams, handleGithubCall, outputField,
console.log('Loading ' + url + ' ' + (usingWorker ? 'with worker' : 'without worker'));
if (usingWorker) {
loadWorker(url, setVersionText);
loadWorker(url);
} else {
loadInternal(url, setVersionText);
loadInternal(url);
}
};
function loadInternal (url, setVersionText) {
function loadInternal (url) {
delete window.Module;
// Set a safe fallback until the new one is loaded
setCompileJSON(function (source, optimize) { compilationFinished('{}'); });
@ -151,11 +153,11 @@ function Compiler (editor, renderer, queryParams, handleGithubCall, outputField,
return;
}
window.clearInterval(check);
onInternalCompilerLoaded(setVersionText);
onInternalCompilerLoaded();
}, 200);
}
function loadWorker (url, setVersionText) {
function loadWorker (url) {
if (worker !== null) {
worker.terminate();
}
@ -165,7 +167,7 @@ function Compiler (editor, renderer, queryParams, handleGithubCall, outputField,
switch (data.cmd) {
case 'versionLoaded':
compilerAcceptsMultipleFiles = !!data.acceptsMultipleFiles;
onCompilerLoaded(setVersionText, data.data);
onCompilerLoaded(data.data);
break;
case 'compiled':
var result;

@ -1,9 +1,17 @@
var remix = require('ethereum-remix');
function Debugger (id) {
function Debugger (id, executionContextEvent) {
this.el = document.querySelector(id);
this.debugger = new remix.ui.Debugger();
this.el.appendChild(this.debugger.render());
var self = this;
executionContextEvent.register('contextChanged', this, function (context) {
context = context === 'vm' ? 'VM' : context;
context = context === 'injected' ? 'EXTERNAL' : context;
context = context === 'web3' ? 'INTERNAL' : context;
self.switchProvider(context);
});
}
Debugger.prototype.debug = function (receipt) {

@ -2,6 +2,8 @@
var $ = require('jquery');
var Web3 = require('web3');
var EventManager = require('../lib/eventManager');
var EthJSVM = require('ethereumjs-vm');
var injectedProvider;
@ -13,14 +15,17 @@ if (typeof window.web3 !== 'undefined') {
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));
}
function ExecutionContext (_txDebugger) {
var txDebugger = _txDebugger;
var compiler;
var executionContext = injectedProvider ? 'injected' : 'vm';
var vm = new EthJSVM(null, null, { activatePrecompiles: true, enableHomestead: true });
vm.stateManager.checkpoint();
this.setCompiler = function (_compiler) {
compiler = _compiler;
};
/*
trigger contextChanged, web3EndpointChanged
*/
function ExecutionContext () {
var self = this;
this.event = new EventManager();
var executionContext = injectedProvider ? 'injected' : 'vm';
this.isVM = function () {
return executionContext === 'vm';
@ -30,6 +35,10 @@ function ExecutionContext (_txDebugger) {
return web3;
};
this.vm = function () {
return vm;
};
var $injectedToggle = $('#injected-mode');
var $vmToggle = $('#vm-mode');
var $web3Toggle = $('#web3-mode');
@ -47,7 +56,7 @@ function ExecutionContext (_txDebugger) {
$web3endpoint.on('change', function () {
setProviderFromEndpoint();
if (executionContext === 'web3') {
compiler.compile();
self.event.trigger('web3EndpointChanged');
}
});
@ -60,15 +69,17 @@ function ExecutionContext (_txDebugger) {
executionContext = ev.target.value;
if (executionContext === 'web3') {
setProviderFromEndpoint();
txDebugger.switchProvider('EXTERNAL');
self.event.trigger('contextChanged', ['web3']);
} else if (executionContext === 'injected') {
web3.setProvider(injectedProvider);
txDebugger.switchProvider('EXTERNAL');
self.event.trigger('contextChanged', ['injected']);
} else if (executionContext === 'vm') {
txDebugger.switchProvider('VM');
vm.stateManager.revert(function () {
vm.stateManager.checkpoint();
});
self.event.trigger('contextChanged', ['vm']);
}
}
compiler.compile();
}
function setProviderFromEndpoint () {

@ -1,37 +1,44 @@
var $ = require('jquery');
var EventManager = require('../lib/eventManager');
function FormalVerification (outputElement, renderer) {
/*
trigger compilationFinished
*/
function FormalVerification (outputElement, compilerEvent) {
this.event = new EventManager();
this.outputElement = outputElement;
this.renderer = renderer;
}
FormalVerification.prototype.compiling = function () {
$('#formalVerificationInput', this.outputElement)
var self = this;
compilerEvent.register('compilationFinished', this, function (success, data, source) {
if (success) {
self.compilationFinished(data);
}
});
compilerEvent.register('compilationStarted', this, function () {
$('#formalVerificationInput', self.outputElement)
.val('')
.hide();
$('#formalVerificationErrors').empty();
};
$('#formalVerificationErrors').empty();
});
}
FormalVerification.prototype.compilationFinished = function (compilationResult) {
if (compilationResult.formal === undefined) {
this.renderer.error(
'Formal verification not supported by this compiler version.',
$('#formalVerificationErrors'),
true
);
this.event.trigger('compilationFinished', [false, 'Formal verification not supported by this compiler version.', $('#formalVerificationErrors'), true]);
} else {
if (compilationResult.formal['why3'] !== undefined) {
$('#formalVerificationInput', this.outputElement).val(
'(* copy this to http://why3.lri.fr/try/ *)' +
compilationResult.formal['why3']
)
.show();
.show();
}
if (compilationResult.formal.errors !== undefined) {
var errors = compilationResult.formal.errors;
for (var i = 0; i < errors.length; i++) {
this.renderer.error(errors[i], $('#formalVerificationErrors'), true);
this.event.trigger('compilationFinished', [false, errors[i], $('#formalVerificationErrors'), true]);
}
} else {
this.event.trigger('compilationFinished', [true, null, null, true]);
}
}
};

@ -1,276 +1,134 @@
var $ = require('jquery');
var UniversalDApp = require('../universal-dapp.js');
var utils = require('./utils');
function Renderer (editor, executionContext, updateFiles, transactionDebugger, vm) {
var detailsOpen = {};
function renderError (message, container, noAnnotations) {
var type = utils.errortype(message);
var $pre = $('<pre />').text(message);
var $error = $('<div class="sol ' + type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre);
if (container === undefined) {
container = $('#output');
}
container.append($error);
var err = message.match(/^([^:]*):([0-9]*):(([0-9]*):)? /);
if (err) {
var errFile = err[1];
var errLine = parseInt(err[2], 10) - 1;
var errCol = err[4] ? parseInt(err[4], 10) : 0;
if (!noAnnotations && (errFile === '' || errFile === utils.fileNameFromKey(editor.getCacheFile()))) {
editor.addAnnotation({
row: errLine,
column: errCol,
text: message,
type: type
});
}
$error.click(function (ev) {
if (errFile !== '' && errFile !== utils.fileNameFromKey(editor.getCacheFile()) && editor.hasFile(errFile)) {
// Switch to file
editor.setCacheFile(utils.fileKey(errFile));
updateFiles();
// @TODO could show some error icon in files with errors
}
editor.handleErrorClick(errLine, errCol);
});
$error.find('.close').click(function (ev) {
ev.preventDefault();
$error.remove();
return false;
var uiHelper = require('./ui-helper');
function Renderer (editor, web3, updateFiles, udapp, executionContext, formalVerificationEvent, compilerEvent) {
this.editor = editor;
this.web3 = web3;
this.updateFiles = updateFiles;
this.udapp = udapp;
this.executionContext = executionContext;
var self = this;
formalVerificationEvent.register('compilationFinished', this, function (success, message, container, noAnnotations) {
if (!success) {
self.error(message, container, noAnnotations);
}
});
compilerEvent.register('compilationFinished', this, function (success, data, source) {
$('#output').empty();
if (success) {
self.contracts(data, source);
} else {
data.forEach(function (err) {
self.error(err);
});
}
}
this.error = renderError;
var combined = function (contractName, jsonInterface, bytecode) {
return JSON.stringify([{ name: contractName, interface: jsonInterface, bytecode: bytecode }]);
};
});
}
function renderContracts (data, source) {
var udappContracts = [];
for (var contractName in data.contracts) {
var contract = data.contracts[contractName];
udappContracts.push({
name: contractName,
interface: contract['interface'],
bytecode: contract.bytecode
Renderer.prototype.error = function (message, container, noAnnotations) {
var type = utils.errortype(message);
var $pre = $('<pre />').text(message);
var $error = $('<div class="sol ' + type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre);
if (container === undefined) {
container = $('#output');
}
container.append($error);
var err = message.match(/^([^:]*):([0-9]*):(([0-9]*):)? /);
if (err) {
var errFile = err[1];
var errLine = parseInt(err[2], 10) - 1;
var errCol = err[4] ? parseInt(err[4], 10) : 0;
if (!noAnnotations && (errFile === '' || errFile === utils.fileNameFromKey(this.editor.getCacheFile()))) {
this.editor.addAnnotation({
row: errLine,
column: errCol,
text: message,
type: type
});
}
vm.stateManager.revert(function () {
vm.stateManager.checkpoint();
});
var dapp = new UniversalDApp(udappContracts, {
mode: executionContext.isVM() ? 'vm' : 'web3',
web3: executionContext.web3(),
removable: false,
getAddress: function () { return $('#txorigin').val(); },
getValue: function () {
var comp = $('#value').val().split(' ');
return executionContext.web3().toWei(comp[0], comp.slice(1).join(' '));
},
getGasLimit: function () { return $('#gasLimit').val(); },
removable_instances: true,
renderOutputModifier: function (contractName, $contractOutput) {
var contract = data.contracts[contractName];
if (contract.bytecode) {
$contractOutput.append(textRow('Bytecode', contract.bytecode));
}
$contractOutput.append(textRow('Interface', contract['interface']));
if (contract.bytecode) {
$contractOutput.append(textRow('Web3 deploy', gethDeploy(contractName.toLowerCase(), contract['interface'], contract.bytecode), 'deploy'));
$contractOutput.append(textRow('uDApp', combined(contractName, contract['interface'], contract.bytecode), 'deploy'));
}
return $contractOutput.append(getDetails(contract, source, contractName));
$error.click(function (ev) {
if (errFile !== '' && errFile !== utils.fileNameFromKey(this.editor.getCacheFile()) && this.editor.hasFile(errFile)) {
// Switch to file
this.editor.setCacheFile(utils.fileKey(errFile));
this.updateFiles();
// @TODO could show some error icon in files with errors
}
}, transactionDebugger, vm);
var $contractOutput = dapp.render();
var $txOrigin = $('#txorigin');
function renderAccounts (err, accounts) {
if (err) {
renderError(err.message);
}
if (accounts && accounts[0]) {
$txOrigin.empty();
for (var a in accounts) { $txOrigin.append($('<option />').val(accounts[a]).text(accounts[a])); }
$txOrigin.val(accounts[0]);
} else {
$txOrigin.val('unknown');
}
}
dapp.getAccounts(renderAccounts);
$contractOutput.find('.title').click(function (ev) { $(this).closest('.contract').toggleClass('hide'); });
$('#output').append($contractOutput);
$('.col2 input,textarea').click(function () { this.select(); });
this.editor.handleErrorClick(errLine, errCol);
});
$error.find('.close').click(function (ev) {
ev.preventDefault();
$error.remove();
return false;
});
}
};
Renderer.prototype.contracts = function (data, source) {
var udappContracts = [];
for (var contractName in data.contracts) {
var contract = data.contracts[contractName];
udappContracts.push({
name: contractName,
interface: contract['interface'],
bytecode: contract.bytecode
});
}
this.contracts = renderContracts;
var tableRowItems = function (first, second, cls) {
return $('<div class="crow"/>')
.addClass(cls)
.append($('<div class="col1">').append(first))
.append($('<div class="col2">').append(second));
};
var tableRow = function (description, data) {
return tableRowItems(
$('<span/>').text(description),
$('<input readonly="readonly"/>').val(data));
};
var textRow = function (description, data, cls) {
return tableRowItems(
$('<strong/>').text(description),
$('<textarea readonly="readonly" class="gethDeployText"/>').val(data),
cls);
// rendering function used by udapp. they need data and source
var combined = function (contractName, jsonInterface, bytecode) {
return JSON.stringify([{ name: contractName, interface: jsonInterface, bytecode: bytecode }]);
};
var getDetails = function (contract, source, contractName) {
var button = $('<button>Toggle Details</button>');
var details = $('<div style="display: none;"/>')
.append(tableRow('Solidity Interface', contract.solidity_interface));
if (contract.opcodes !== '') {
details.append(tableRow('Opcodes', contract.opcodes));
var renderOutputModifier = function (contractName, $contractOutput) {
var contract = data.contracts[contractName];
if (contract.bytecode) {
$contractOutput.append(uiHelper.textRow('Bytecode', contract.bytecode));
}
var funHashes = '';
for (var fun in contract.functionHashes) {
funHashes += contract.functionHashes[fun] + ' ' + fun + '\n';
}
details.append($('<span class="col1">Functions</span>'));
details.append($('<pre/>').text(funHashes));
var gasEstimates = formatGasEstimates(contract.gasEstimates);
if (gasEstimates) {
details.append($('<span class="col1">Gas Estimates</span>'));
details.append($('<pre/>').text(gasEstimates));
}
$contractOutput.append(uiHelper.textRow('Interface', contract['interface']));
if (contract.runtimeBytecode && contract.runtimeBytecode.length > 0) {
details.append(tableRow('Runtime Bytecode', contract.runtimeBytecode));
if (contract.bytecode) {
$contractOutput.append(uiHelper.textRow('Web3 deploy', uiHelper.gethDeploy(contractName.toLowerCase(), contract['interface'], contract.bytecode), 'deploy'));
$contractOutput.append(uiHelper.textRow('uDApp', combined(contractName, contract['interface'], contract.bytecode), 'deploy'));
}
return $contractOutput.append(uiHelper.getDetails(contract, source, contractName));
};
// //
var self = this;
if (contract.assembly !== null) {
details.append($('<span class="col1">Assembly</span>'));
var assembly = $('<pre/>').text(formatAssemblyText(contract.assembly, '', source));
details.append(assembly);
}
var getAddress = function () { return $('#txorigin').val(); };
button.click(function () { detailsOpen[contractName] = !detailsOpen[contractName]; details.toggle(); });
if (detailsOpen[contractName]) {
details.show();
}
return $('<div class="contractDetails"/>').append(button).append(details);
var getValue = function () {
var comp = $('#value').val().split(' ');
return self.executionContext.web3().toWei(comp[0], comp.slice(1).join(' '));
};
var formatGasEstimates = function (data) {
// FIXME: the whole gasEstimates object should be nil instead
if (data.creation === undefined && data.external === undefined && data.internal === undefined) {
return;
}
var gasToText = function (g) { return g === null ? 'unknown' : g; };
var text = '';
var fun;
if ('creation' in data) {
text += 'Creation: ' + gasToText(data.creation[0]) + ' + ' + gasToText(data.creation[1]) + '\n';
}
var getGasLimit = function () { return $('#gasLimit').val(); };
if ('external' in data) {
text += 'External:\n';
for (fun in data.external) {
text += ' ' + fun + ': ' + gasToText(data.external[fun]) + '\n';
}
}
this.udapp.reset(udappContracts, getAddress, getValue, getGasLimit, renderOutputModifier);
if ('internal' in data) {
text += 'Internal:\n';
for (fun in data.internal) {
text += ' ' + fun + ': ' + gasToText(data.internal[fun]) + '\n';
}
}
var $contractOutput = this.udapp.render();
return text;
};
var $txOrigin = $('#txorigin');
var formatAssemblyText = function (asm, prefix, source) {
if (typeof asm === typeof '' || asm === null || asm === undefined) {
return prefix + asm + '\n';
this.udapp.getAccounts(function (err, accounts) {
if (err) {
self.error(err.message);
}
var text = prefix + '.code\n';
$.each(asm['.code'], function (i, item) {
var v = item.value === undefined ? '' : item.value;
var src = '';
if (item.begin !== undefined && item.end !== undefined) {
src = source.slice(item.begin, item.end).replace('\n', '\\n', 'g');
}
if (src.length > 30) {
src = src.slice(0, 30) + '...';
}
if (item.name !== 'tag') {
text += ' ';
}
text += prefix + item.name + ' ' + v + '\t\t\t' + src + '\n';
});
text += prefix + '.data\n';
if (asm['.data']) {
$.each(asm['.data'], function (i, item) {
text += ' ' + prefix + '' + i + ':\n';
text += formatAssemblyText(item, prefix + ' ', source);
});
if (accounts && accounts[0]) {
$txOrigin.empty();
for (var a in accounts) { $txOrigin.append($('<option />').val(accounts[a]).text(accounts[a])); }
$txOrigin.val(accounts[0]);
} else {
$txOrigin.val('unknown');
}
});
return text;
};
function gethDeploy (contractName, jsonInterface, bytecode) {
var code = '';
var funABI = getConstructorInterface(JSON.parse(jsonInterface));
funABI.inputs.forEach(function (inp) {
code += 'var ' + inp.name + ' = /* var of type ' + inp.type + ' here */ ;\n';
});
code += 'var ' + contractName + 'Contract = web3.eth.contract(' + jsonInterface.replace('\n', '') + ');' +
'\nvar ' + contractName + ' = ' + contractName + 'Contract.new(';
funABI.inputs.forEach(function (inp) {
code += '\n ' + inp.name + ',';
});
code += '\n {' +
'\n from: web3.eth.accounts[0], ' +
'\n data: \'' + bytecode + '\', ' +
'\n gas: 4700000' +
'\n }, function (e, contract){' +
'\n console.log(e, contract);' +
'\n if (typeof contract.address !== \'undefined\') {' +
'\n console.log(\'Contract mined! address: \' + contract.address + \' transactionHash: \' + contract.transactionHash);' +
'\n }' +
'\n })';
return code;
}
function getConstructorInterface (abi) {
var funABI = { 'name': '', 'inputs': [], 'type': 'constructor', 'outputs': [] };
for (var i = 0; i < abi.length; i++) {
if (abi[i].type === 'constructor') {
funABI.inputs = abi[i].inputs || [];
break;
}
}
return funABI;
}
}
$contractOutput.find('.title').click(function (ev) { $(this).closest('.contract').toggleClass('hide'); });
$('#output').append($contractOutput);
$('.col2 input,textarea').click(function () { this.select(); });
};
module.exports = Renderer;

@ -0,0 +1,161 @@
var $ = require('jquery');
module.exports = {
tableRowItems: function (first, second, cls) {
return $('<div class="crow"/>')
.addClass(cls)
.append($('<div class="col1">').append(first))
.append($('<div class="col2">').append(second));
},
tableRow: function (description, data) {
return this.tableRowItems(
$('<span/>').text(description),
$('<input readonly="readonly"/>').val(data));
},
textRow: function (description, data, cls) {
return this.tableRowItems(
$('<strong/>').text(description),
$('<textarea readonly="readonly" class="gethDeployText"/>').val(data),
cls);
},
formatAssemblyText: function (asm, prefix, source) {
var self = this;
if (typeof asm === typeof '' || asm === null || asm === undefined) {
return prefix + asm + '\n';
}
var text = prefix + '.code\n';
$.each(asm['.code'], function (i, item) {
var v = item.value === undefined ? '' : item.value;
var src = '';
if (item.begin !== undefined && item.end !== undefined) {
src = source.slice(item.begin, item.end).replace('\n', '\\n', 'g');
}
if (src.length > 30) {
src = src.slice(0, 30) + '...';
}
if (item.name !== 'tag') {
text += ' ';
}
text += prefix + item.name + ' ' + v + '\t\t\t' + src + '\n';
});
text += prefix + '.data\n';
if (asm['.data']) {
$.each(asm['.data'], function (i, item) {
text += ' ' + prefix + '' + i + ':\n';
text += self.formatAssemblyText(item, prefix + ' ', source);
});
}
return text;
},
gethDeploy: function (contractName, jsonInterface, bytecode) {
var code = '';
var funABI = this.getConstructorInterface(JSON.parse(jsonInterface));
funABI.inputs.forEach(function (inp) {
code += 'var ' + inp.name + ' = /* var of type ' + inp.type + ' here */ ;\n';
});
code += 'var ' + contractName + 'Contract = web3.eth.contract(' + jsonInterface.replace('\n', '') + ');' +
'\nvar ' + contractName + ' = ' + contractName + 'Contract.new(';
funABI.inputs.forEach(function (inp) {
code += '\n ' + inp.name + ',';
});
code += '\n {' +
'\n from: web3.eth.accounts[0], ' +
"\n data: '" + bytecode + "', " +
'\n gas: 4700000' +
'\n }, function (e, contract){' +
'\n console.log(e, contract);' +
"\n if (typeof contract.address !== 'undefined') {" +
"\n console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);" +
'\n }' +
'\n })';
return code;
},
getConstructorInterface: function (abi) {
var funABI = { 'name': '', 'inputs': [], 'type': 'constructor', 'outputs': [] };
for (var i = 0; i < abi.length; i++) {
if (abi[i].type === 'constructor') {
funABI.inputs = abi[i].inputs || [];
break;
}
}
return funABI;
},
formatGasEstimates: function (data) {
// FIXME: the whole gasEstimates object should be nil instead
if (data.creation === undefined && data.external === undefined && data.internal === undefined) {
return;
}
var gasToText = function (g) { return g === null ? 'unknown' : g; };
var text = '';
var fun;
if ('creation' in data) {
text += 'Creation: ' + gasToText(data.creation[0]) + ' + ' + gasToText(data.creation[1]) + '\n';
}
if ('external' in data) {
text += 'External:\n';
for (fun in data.external) {
text += ' ' + fun + ': ' + gasToText(data.external[fun]) + '\n';
}
}
if ('internal' in data) {
text += 'Internal:\n';
for (fun in data.internal) {
text += ' ' + fun + ': ' + gasToText(data.internal[fun]) + '\n';
}
}
return text;
},
detailsOpen: {},
getDetails: function (contract, source, contractName) {
var button = $('<button>Toggle Details</button>');
var details = $('<div style="display: none;"/>')
.append(this.tableRow('Solidity Interface', contract.solidity_interface));
if (contract.opcodes !== '') {
details.append(this.tableRow('Opcodes', contract.opcodes));
}
var funHashes = '';
for (var fun in contract.functionHashes) {
funHashes += contract.functionHashes[fun] + ' ' + fun + '\n';
}
details.append($('<span class="col1">Functions</span>'));
details.append($('<pre/>').text(funHashes));
var gasEstimates = this.formatGasEstimates(contract.gasEstimates);
if (gasEstimates) {
details.append($('<span class="col1">Gas Estimates</span>'));
details.append($('<pre/>').text(gasEstimates));
}
if (contract.runtimeBytecode && contract.runtimeBytecode.length > 0) {
details.append(this.tableRow('Runtime Bytecode', contract.runtimeBytecode));
}
if (contract.assembly !== null) {
details.append($('<span class="col1">Assembly</span>'));
var assembly = $('<pre/>').text(this.formatAssemblyText(contract.assembly, '', source));
details.append(assembly);
}
button.click(function () { this.detailsOpen[contractName] = !this.detailsOpen[contractName]; details.toggle(); });
if (this.detailsOpen[contractName]) {
details.show();
}
return $('<div class="contractDetails"/>').append(button).append(details);
}
};

@ -0,0 +1,64 @@
'use strict';
function eventManager () {
this.registered = {};
}
/*
* Unregister a listenner.
* Note that if obj is a function. the unregistration will be applied to the dummy obj {}.
*
* @param {String} eventName - the event name
* @param {Object or Func} obj - object that will listen on this event
* @param {Func} func - function of the listenners that will be executed
*/
eventManager.prototype.unregister = function (eventName, obj, func) {
if (obj instanceof Function) {
func = obj;
obj = {};
}
for (var reg in this.registered[eventName]) {
if (this.registered[eventName][reg] &&
this.registered[eventName][reg].obj === obj && (!func || this.registered[eventName][reg].func === func)) {
this.registered[eventName].splice(reg, 1);
return;
}
}
};
/*
* Register a new listenner.
* Note that if obj is a function, the function registration will be associated with the dummy object {}
*
* @param {String} eventName - the event name
* @param {Object or Func} obj - object that will listen on this event
* @param {Func} func - function of the listenners that will be executed
*/
eventManager.prototype.register = function (eventName, obj, func) {
if (!this.registered[eventName]) {
this.registered[eventName] = [];
}
if (obj instanceof Function) {
func = obj;
obj = {};
}
this.registered[eventName].push({
obj: obj,
func: func
});
};
/*
* trigger event.
* Every listenner have their associated function executed
*
* @param {String} eventName - the event name
* @param {Array}j - argument that will be passed to the exectued function.
*/
eventManager.prototype.trigger = function (eventName, args) {
for (var listener in this.registered[eventName]) {
var l = this.registered[eventName][listener];
l.func.apply(l.obj, args);
}
};
module.exports = eventManager;

@ -6,31 +6,46 @@ var EthJSTX = require('ethereumjs-tx');
var ethJSABI = require('ethereumjs-abi');
var EthJSBlock = require('ethereumjs-block');
var BN = ethJSUtil.BN;
var EventManager = require('./lib/eventManager');
function UniversalDApp (contracts, options, transactionDebugger, vm) {
/*
trigger debugRequested
*/
function UniversalDApp (executionContext, options, txdebugger) {
this.event = new EventManager();
var self = this;
self.options = options || {};
self.$el = $('<div class="udapp" />');
self.contracts = contracts;
self.renderOutputModifier = options.renderOutputModifier || function (name, content) { return content; };
self.web3 = options.web3;
self.transactionDebugger = transactionDebugger;
if (options.mode === 'vm') {
// FIXME: use `options.vm` or `self.vm` consistently
options.vm = true;
self.accounts = {};
self.vm = vm;
self.addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511');
self.addAccount('2ac6c190b09897cd8987869cc7b918cfea07ee82038d492abce033c75c1b1d0c');
} else if (options.mode !== 'web3') {
throw new Error('Either VM or Web3 mode must be selected');
}
self.contracts;
self.getAddress;
self.getValue;
self.getGasLimit;
self.txdebugger = txdebugger; // temporary: will not be needed anymore when we'll add memory support to the VM
var defaultRenderOutputModifier = function (name, content) { return content; };
self.renderOutputModifier = defaultRenderOutputModifier;
self.web3 = executionContext.web3();
self.vm = executionContext.vm();
self.executionContext = executionContext;
self.executionContext.event.register('contextChanged', this, function (context) {
self.reset(self.contracts);
});
}
UniversalDApp.prototype.reset = function (contracts, getAddress, getValue, getGasLimit, renderer) {
this.$el.empty();
this.contracts = contracts;
this.getAddress = getAddress;
this.getValue = getValue;
this.getGasLimit = getGasLimit;
this.renderOutputModifier = renderer;
this.accounts = {};
if (this.executionContext.isVM()) {
this.addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511');
this.addAccount('2ac6c190b09897cd8987869cc7b918cfea07ee82038d492abce033c75c1b1d0c');
}
};
UniversalDApp.prototype.addAccount = function (privateKey, balance) {
var self = this;
@ -48,7 +63,7 @@ UniversalDApp.prototype.addAccount = function (privateKey, balance) {
UniversalDApp.prototype.getAccounts = function (cb) {
var self = this;
if (!self.vm) {
if (!self.executionContext.isVM()) {
self.web3.eth.getAccounts(cb);
} else {
if (!self.accounts) {
@ -64,7 +79,7 @@ UniversalDApp.prototype.getBalance = function (address, cb) {
address = ethJSUtil.stripHexPrefix(address);
if (!self.vm) {
if (!self.executionContext.isVM()) {
self.web3.eth.getBalance(address, function (err, res) {
if (err) {
cb(err);
@ -194,7 +209,7 @@ UniversalDApp.prototype.getInstanceInterface = function (contract, address, $tar
$close.click(function () { $instance.remove(); });
$instance.append($close);
}
var context = self.options.vm ? 'memory' : 'blockchain';
var context = self.executionContext.isVM() ? 'memory' : 'blockchain';
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex');
var $title = $('<span class="title"/>').text(contract.name + ' at ' + address + ' (' + context + ')');
@ -221,7 +236,7 @@ UniversalDApp.prototype.getInstanceInterface = function (contract, address, $tar
$events.append($event);
};
if (self.options.vm) {
if (self.executionContext.isVM()) {
// FIXME: support indexed events
var eventABI = {};
@ -354,7 +369,7 @@ UniversalDApp.prototype.getCallButton = function (args) {
var $debugTx = $('<div class="debugTx">');
var $button = $('<button title="Launch Debugger" class="debug"><i class="fa fa-bug"></i></button>');
$button.click(function () {
self.transactionDebugger.debug(result);
self.event.trigger('debugRequested', [result]);
});
$debugTx.append($button);
return $debugTx;
@ -364,7 +379,7 @@ UniversalDApp.prototype.getCallButton = function (args) {
var $debugTx = $('<div class="debugCall">');
var $button = $('<button title="Launch Debugger" class="debug"><i class="fa fa-bug"></i></button>');
$button.click(function () {
self.transactionDebugger.debug(result);
self.event.trigger('debugRequested', [result]);
});
$debugTx.append($button);
return $debugTx;
@ -504,16 +519,16 @@ UniversalDApp.prototype.getCallButton = function (args) {
if (err) {
replaceOutput($result, $('<span/>').text(err).addClass('error'));
// VM only
} else if (self.options.vm && result.vm.exception === 0 && result.vm.exceptionError) {
} else if (self.executionContext.isVM() && result.vm.exception === 0 && result.vm.exceptionError) {
replaceOutput($result, $('<span/>').text('VM Exception: ' + result.vm.exceptionError).addClass('error'));
// VM only
} else if (self.options.vm && result.vm.return === undefined) {
} else if (self.executionContext.isVM() && result.vm.return === undefined) {
replaceOutput($result, $('<span/>').text('Exception during execution.').addClass('error'));
} else if (isConstructor) {
replaceOutput($result, getGasUsedOutput(result, result.vm));
$result.append(getDebugTransaction(result));
args.appendFunctions(self.options.vm ? result.createdAddress : result.contractAddress);
} else if (self.options.vm) {
args.appendFunctions(self.executionContext.isVM() ? result.createdAddress : result.contractAddress);
} else if (self.executionContext.isVM()) {
var outputObj = '0x' + result.vm.return.toString('hex');
clearOutput($result);
$result.append(getReturnOutput(outputObj)).append(getGasUsedOutput(result, result.vm));
@ -611,7 +626,7 @@ UniversalDApp.prototype.deployLibrary = function (contractName, cb) {
if (err) {
return cb(err);
}
var address = self.options.vm ? result.createdAddress : result.contractAddress;
var address = self.executionContext.isVM() ? result.createdAddress : result.contractAddress;
self.getContractByName(contractName).address = address;
cb(err, address);
});
@ -644,27 +659,27 @@ UniversalDApp.prototype.runTx = function (data, args, cb) {
}
var gasLimit = 3000000;
if (self.options.getGasLimit) {
if (self.getGasLimit) {
try {
gasLimit = self.options.getGasLimit();
gasLimit = self.getGasLimit();
} catch (e) {
return cb(e);
}
}
var value = 0;
if (self.options.getValue) {
if (self.getValue) {
try {
value = self.options.getValue();
value = self.getValue();
} catch (e) {
return cb(e);
}
}
var tx;
if (!self.vm) {
if (!self.executionContext.isVM()) {
tx = {
from: self.options.getAddress ? self.options.getAddress() : self.web3.eth.accounts[0],
from: self.getAddress ? self.getAddress() : self.web3.eth.accounts[0],
to: to,
data: data,
value: value
@ -695,7 +710,7 @@ UniversalDApp.prototype.runTx = function (data, args, cb) {
}
} else {
try {
var address = self.options.getAddress ? self.options.getAddress() : self.getAccounts()[0];
var address = self.getAddress ? self.getAddress() : Object.keys(self.accounts)[0];
var account = self.accounts[address];
tx = new EthJSTX({
nonce: new BN(account.nonce++),
@ -715,7 +730,7 @@ UniversalDApp.prototype.runTx = function (data, args, cb) {
uncleHeaders: []
});
self.vm.runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) {
result.transactionHash = self.transactionDebugger.web3().releaseCurrentHash(); // used to keep track of the transaction
result.transactionHash = self.txdebugger.web3().releaseCurrentHash(); // used to keep track of the transaction
cb(err, result);
});
} catch (e) {

@ -1,6 +1,7 @@
var test = require('tape');
var Compiler = require('../src/app/compiler');
var EventManager = require('../src/lib/eventManager');
test('compiler.compile smoke', function (t) {
t.plan(1);
@ -8,9 +9,8 @@ test('compiler.compile smoke', function (t) {
var noop = function () {};
var getCacheFile = function () { return 'fakeCacheFile'; };
var fakeEditor = {onChangeSetup: noop, clearAnnotations: noop, getValue: noop, setCacheFileContent: noop, getCacheFile: getCacheFile};
var fakeOutputField = {empty: noop};
var fakeQueryParams = {get: function () { return {}; }};
var compiler = new Compiler(fakeEditor, null, fakeQueryParams, null, fakeOutputField);
var compiler = new Compiler(fakeEditor, fakeQueryParams, null, null, new EventManager());
compiler.setCompileJSON(noop);
compiler.compile();
t.ok(compiler);

Loading…
Cancel
Save