add event manager

pull/1/head
yann300 8 years ago
parent 63743b221a
commit e6adf1da19
  1. 80
      src/app.js
  2. 50
      src/app/compiler.js
  3. 35
      src/app/execution-context.js
  4. 18
      src/app/formalVerification.js
  5. 334
      src/app/renderer.js
  6. 161
      src/app/ui-helper.js
  7. 41
      src/lib/eventManager.js
  8. 14
      src/lib/util.js
  9. 86
      src/universal-dapp.js

@ -13,9 +13,10 @@ var Editor = require('./app/editor');
var Renderer = require('./app/renderer'); var Renderer = require('./app/renderer');
var Compiler = require('./app/compiler'); var Compiler = require('./app/compiler');
var ExecutionContext = require('./app/execution-context'); var ExecutionContext = require('./app/execution-context');
var UniversalDApp = require('./universal-dapp.js');
var Debugger = require('./app/debugger'); var Debugger = require('./app/debugger');
var FormalVerification = require('./app/formalVerification'); var FormalVerification = require('./app/formalVerification');
var EthJSVM = require('ethereumjs-vm'); var util = require('./lib/util');
// The event listener needs to be registered as early as possible, because the // The event listener needs to be registered as early as possible, because the
// parent will send the message upon the "load" event. // parent will send the message upon the "load" event.
@ -26,8 +27,12 @@ window.addEventListener('message', function (ev) {
loadFilesCallback(ev.data[1]); loadFilesCallback(ev.data[1]);
} }
}, false); }, false);
/*
trigger selectTab
*/
var run = function () { var run = function () {
var self = this;
util.makeEventCapable(this);
var storage = new Storage(updateFiles); var storage = new Storage(updateFiles);
function loadFiles (files) { function loadFiles (files) {
@ -108,6 +113,7 @@ var run = function () {
el.removeClass('active'); el.removeClass('active');
$('#optionViews').removeClass(cls); $('#optionViews').removeClass(cls);
} }
self.event.trigger('tabChanged', [cls]);
}; };
// ------------------ gist publish -------------- // ------------------ gist publish --------------
@ -199,7 +205,7 @@ var run = function () {
$fileNameInputEl.off('keyup'); $fileNameInputEl.off('keyup');
if (newName !== originalName && confirm( 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 overwrite: ' + newName + ' with ' + originalName + '?'
: 'Are you sure you want to rename: ' + originalName + ' to ' + newName + '?')) { : 'Are you sure you want to rename: ' + originalName + ' to ' + newName + '?')) {
storage.rename(utils.fileKey(originalName), utils.fileKey(newName)); storage.rename(utils.fileKey(originalName), utils.fileKey(newName));
@ -279,7 +285,7 @@ var run = function () {
} }
// function widthOfHidden () { // function widthOfHidden () {
// return ($filesWrapper.outerWidth() - widthOfList() - getLeftPosi()); // return ($filesWrapper.outerWidth() - widthOfList() - getLeftPosi())
// } // }
function widthOfVisible () { function widthOfVisible () {
@ -408,8 +414,6 @@ var run = function () {
if (!hidingRHP) compiler.compile(); if (!hidingRHP) compiler.compile();
}); });
function getHidingRHP () { return hidingRHP; }
// ----------------- editor resize --------------- // ----------------- editor resize ---------------
function onResize () { function onResize () {
@ -433,19 +437,63 @@ var run = function () {
return $.getJSON('https://api.github.com/repos/' + root + '/contents/' + path, cb); return $.getJSON('https://api.github.com/repos/' + root + '/contents/' + path, cb);
} }
var transactionDebugger = new Debugger('#debugger'); var transactionDebugger = new Debugger('#debugger');
var vm = new EthJSVM(null, null, { activatePrecompiles: true, enableHomestead: true }); var executionContext = new ExecutionContext();
vm.stateManager.checkpoint();
transactionDebugger.addProvider('VM', vm); transactionDebugger.addProvider('VM', executionContext.vm());
transactionDebugger.switchProvider('VM'); transactionDebugger.switchProvider('VM');
var executionContext = new ExecutionContext(transactionDebugger); transactionDebugger.addProvider('INTERNAL', executionContext.web3());
transactionDebugger.addProvider('EXTERNAL', executionContext.web3()); transactionDebugger.addProvider('EXTERNAL', executionContext.web3());
transactionDebugger.onDebugRequested = function () { transactionDebugger.onDebugRequested = function () {
selectTab($('ul#options li.debugView')); selectTab($('ul#options li.debugView'));
}; };
var renderer = new Renderer(editor, executionContext, updateFiles, transactionDebugger, vm);
var formalVerification = new FormalVerification($('#verificationView'), renderer); var udapp = new UniversalDApp(executionContext, {
var compiler = new Compiler(editor, renderer, queryParams, handleGithubCall, $('#output'), getHidingRHP, formalVerification, updateFiles); removable: false,
executionContext.setCompiler(compiler); removable_instances: true
}, transactionDebugger);
udapp.event.register('debugRequested', this, function (data) {
transactionDebugger.debug(data);
});
var renderer = new Renderer(editor, executionContext.web3(), updateFiles, udapp, executionContext);
var formalVerification = new FormalVerification($('#verificationView'));
formalVerification.event.register('compilationError', this, function (message, container, noAnnotations) {
renderer.error(message, container, noAnnotations);
});
var compiler = new Compiler(editor, queryParams, handleGithubCall, updateFiles);
executionContext.event.register('contextChanged', this, function (context) {
$('#output').empty();
context = context === 'vm' ? 'VM' : context;
context = context === 'injected' ? 'EXTERNAL' : context;
context = context === 'web3' ? 'INTERNAL' : context;
transactionDebugger.switchProvider(context);
compiler.compile();
});
executionContext.event.register('web3EndpointChanged', this, function (context) {
$('#output').empty();
compiler.compile();
});
compiler.event.register('compilerLoaded', this, function (version) {
setVersionText(version);
compiler.compile();
});
compiler.event.register('compilationError', this, function (data) {
renderer.error(data);
});
compiler.event.register('compilationSucceed', this, function (data, source) {
if (!hidingRHP) {
renderer.contracts(data, source);
formalVerification.compilationFinished(data);
}
});
compiler.event.register('isCompiling', this, function () {
$('#output').empty();
formalVerification.compiling();
});
function setVersionText (text) { function setVersionText (text) {
$('#version').text(text); $('#version').text(text);
@ -459,9 +507,9 @@ var run = function () {
// Workers cannot load js on "file:"-URLs and we get a // Workers cannot load js on "file:"-URLs and we get a
// "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium, // "Uncaught RangeError: Maximum call stack size exceeded" error on Chromium,
// resort to non-worker version in that case. // resort to non-worker version in that case.
compiler.loadVersion(true, version, setVersionText); compiler.loadVersion(true, version);
} else { } else {
compiler.loadVersion(false, version, setVersionText); compiler.loadVersion(false, version);
} }
}; };

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

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

@ -1,8 +1,12 @@
var $ = require('jquery'); var $ = require('jquery');
var util = require('../lib/util');
function FormalVerification (outputElement, renderer) { /*
trigger compilationError
*/
function FormalVerification (outputElement) {
util.makeEventCapable(this);
this.outputElement = outputElement; this.outputElement = outputElement;
this.renderer = renderer;
} }
FormalVerification.prototype.compiling = function () { FormalVerification.prototype.compiling = function () {
@ -14,23 +18,19 @@ FormalVerification.prototype.compiling = function () {
FormalVerification.prototype.compilationFinished = function (compilationResult) { FormalVerification.prototype.compilationFinished = function (compilationResult) {
if (compilationResult.formal === undefined) { if (compilationResult.formal === undefined) {
this.renderer.error( this.event.trigger('compilationError', ['Formal verification not supported by this compiler version.', $('#formalVerificationErrors'), true]);
'Formal verification not supported by this compiler version.',
$('#formalVerificationErrors'),
true
);
} else { } else {
if (compilationResult.formal['why3'] !== undefined) { if (compilationResult.formal['why3'] !== undefined) {
$('#formalVerificationInput', this.outputElement).val( $('#formalVerificationInput', this.outputElement).val(
'(* copy this to http://why3.lri.fr/try/ *)' + '(* copy this to http://why3.lri.fr/try/ *)' +
compilationResult.formal['why3'] compilationResult.formal['why3']
) )
.show(); .show();
} }
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.renderer.error(errors[i], $('#formalVerificationErrors'), true); this.event.trigger('compilationError', [errors[i], $('#formalVerificationErrors'), true]);
} }
} }
} }

@ -1,276 +1,118 @@
var $ = require('jquery'); var $ = require('jquery');
var UniversalDApp = require('../universal-dapp.js');
var utils = require('./utils'); var utils = require('./utils');
var uiHelper = require('./ui-helper');
function Renderer (editor, web3, updateFiles, udapp, executionContext) {
this.editor = editor;
this.web3 = web3;
this.updateFiles = updateFiles;
this.udapp = udapp;
this.executionContext = executionContext;
}
function Renderer (editor, executionContext, updateFiles, transactionDebugger, vm) { Renderer.prototype.error = function (message, container, noAnnotations) {
var detailsOpen = {}; var type = utils.errortype(message);
var $pre = $('<pre />').text(message);
function renderError (message, container, noAnnotations) { var $error = $('<div class="sol ' + type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre);
var type = utils.errortype(message); if (container === undefined) {
var $pre = $('<pre />').text(message); container = $('#output');
var $error = $('<div class="sol ' + type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre); }
if (container === undefined) { container.append($error);
container = $('#output'); var err = message.match(/^([^:]*):([0-9]*):(([0-9]*):)? /);
} if (err) {
container.append($error); var errFile = err[1];
var err = message.match(/^([^:]*):([0-9]*):(([0-9]*):)? /); var errLine = parseInt(err[2], 10) - 1;
if (err) { var errCol = err[4] ? parseInt(err[4], 10) : 0;
var errFile = err[1]; if (!noAnnotations && (errFile === '' || errFile === utils.fileNameFromKey(this.editor.getCacheFile()))) {
var errLine = parseInt(err[2], 10) - 1; this.editor.addAnnotation({
var errCol = err[4] ? parseInt(err[4], 10) : 0; row: errLine,
if (!noAnnotations && (errFile === '' || errFile === utils.fileNameFromKey(editor.getCacheFile()))) { column: errCol,
editor.addAnnotation({ text: message,
row: errLine, type: type
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;
}); });
} }
$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
}
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.error = renderError;
// rendering function used by udapp. they need data and source
var combined = function (contractName, jsonInterface, bytecode) { var combined = function (contractName, jsonInterface, bytecode) {
return JSON.stringify([{ name: contractName, interface: jsonInterface, bytecode: bytecode }]); return JSON.stringify([{ name: contractName, interface: jsonInterface, bytecode: bytecode }]);
}; };
function renderContracts (data, source) { var renderOutputModifier = function (contractName, $contractOutput) {
var udappContracts = []; var contract = data.contracts[contractName];
for (var contractName in data.contracts) { if (contract.bytecode) {
var contract = data.contracts[contractName]; $contractOutput.append(uiHelper.textRow('Bytecode', contract.bytecode));
udappContracts.push({
name: contractName,
interface: contract['interface'],
bytecode: contract.bytecode
});
} }
vm.stateManager.revert(function () { $contractOutput.append(uiHelper.textRow('Interface', contract['interface']));
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));
}
}, transactionDebugger, vm);
var $contractOutput = dapp.render();
var $txOrigin = $('#txorigin');
function renderAccounts (err, accounts) { if (contract.bytecode) {
if (err) { $contractOutput.append(uiHelper.textRow('Web3 deploy', uiHelper.gethDeploy(contractName.toLowerCase(), contract['interface'], contract.bytecode), 'deploy'));
renderError(err.message); $contractOutput.append(uiHelper.textRow('uDApp', combined(contractName, contract['interface'], contract.bytecode), 'deploy'));
}
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 $contractOutput.append(uiHelper.getDetails(contract, source, contractName));
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.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( var self = this;
$('<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);
};
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 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); var getAddress = function () { return $('#txorigin').val(); };
if (gasEstimates) {
details.append($('<span class="col1">Gas Estimates</span>'));
details.append($('<pre/>').text(gasEstimates));
}
if (contract.runtimeBytecode && contract.runtimeBytecode.length > 0) { var getValue = function () {
details.append(tableRow('Runtime Bytecode', contract.runtimeBytecode)); var comp = $('#value').val().split(' ');
} return self.executionContext.web3().toWei(comp[0], comp.slice(1).join(' '));
if (contract.assembly !== null) {
details.append($('<span class="col1">Assembly</span>'));
var assembly = $('<pre/>').text(formatAssemblyText(contract.assembly, '', source));
details.append(assembly);
}
button.click(function () { detailsOpen[contractName] = !detailsOpen[contractName]; details.toggle(); });
if (detailsOpen[contractName]) {
details.show();
}
return $('<div class="contractDetails"/>').append(button).append(details);
}; };
var formatGasEstimates = function (data) { var getGasLimit = function () { return $('#gasLimit').val(); };
// 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) { this.udapp.reset(udappContracts, getAddress, getValue, getGasLimit, renderOutputModifier);
text += 'External:\n';
for (fun in data.external) {
text += ' ' + fun + ': ' + gasToText(data.external[fun]) + '\n';
}
}
if ('internal' in data) { var $contractOutput = this.udapp.render();
text += 'Internal:\n';
for (fun in data.internal) {
text += ' ' + fun + ': ' + gasToText(data.internal[fun]) + '\n';
}
}
return text; var $txOrigin = $('#txorigin');
};
var formatAssemblyText = function (asm, prefix, source) { this.udapp.getAccounts(function (err, accounts) {
if (typeof asm === typeof '' || asm === null || asm === undefined) { if (err) {
return prefix + asm + '\n'; self.error(err.message);
} }
var text = prefix + '.code\n'; if (accounts && accounts[0]) {
$.each(asm['.code'], function (i, item) { $txOrigin.empty();
var v = item.value === undefined ? '' : item.value; for (var a in accounts) { $txOrigin.append($('<option />').val(accounts[a]).text(accounts[a])); }
var src = ''; $txOrigin.val(accounts[0]);
if (item.begin !== undefined && item.end !== undefined) { } else {
src = source.slice(item.begin, item.end).replace('\n', '\\n', 'g'); $txOrigin.val('unknown');
}
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);
});
} }
});
return text; $contractOutput.find('.title').click(function (ev) { $(this).closest('.contract').toggleClass('hide'); });
}; $('#output').append($contractOutput);
$('.col2 input,textarea').click(function () { this.select(); });
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;
}
}
module.exports = Renderer; 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,41 @@
'use strict';
function eventManager () {
this.registered = {};
}
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;
}
}
};
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
});
};
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;

@ -0,0 +1,14 @@
'use strict';
var EventManager = require('./eventManager');
module.exports = {
extend: function (destination, source) {
for (var property in source) {
destination[property] = source[property];
}
},
makeEventCapable: function (destination) {
destination.event = new EventManager();
}
};

@ -6,31 +6,43 @@ var EthJSTX = require('ethereumjs-tx');
var ethJSABI = require('ethereumjs-abi'); var ethJSABI = require('ethereumjs-abi');
var EthJSBlock = require('ethereumjs-block'); var EthJSBlock = require('ethereumjs-block');
var BN = ethJSUtil.BN; var BN = ethJSUtil.BN;
var util = require('./lib/util');
function UniversalDApp (contracts, options, transactionDebugger, vm) { function UniversalDApp (executionContext, options, txdebugger) {
util.makeEventCapable(this);
var self = this; var self = this;
self.options = options || {}; self.options = options || {};
self.$el = $('<div class="udapp" />'); self.$el = $('<div class="udapp" />');
self.contracts = contracts; self.contracts;
self.renderOutputModifier = options.renderOutputModifier || function (name, content) { return content; }; self.getAddress;
self.getValue;
self.web3 = options.web3; self.getGasLimit;
self.transactionDebugger = transactionDebugger; 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; };
if (options.mode === 'vm') { self.renderOutputModifier = defaultRenderOutputModifier;
// FIXME: use `options.vm` or `self.vm` consistently self.web3 = executionContext.web3();
options.vm = true; self.vm = executionContext.vm();
self.executionContext = executionContext;
self.accounts = {}; self.executionContext.event.register('contextChanged', this, function (context) {
self.vm = vm; self.reset(self.contracts);
self.addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511'); });
self.addAccount('2ac6c190b09897cd8987869cc7b918cfea07ee82038d492abce033c75c1b1d0c');
} else if (options.mode !== 'web3') {
throw new Error('Either VM or Web3 mode must be selected');
}
} }
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) { UniversalDApp.prototype.addAccount = function (privateKey, balance) {
var self = this; var self = this;
@ -48,7 +60,7 @@ UniversalDApp.prototype.addAccount = function (privateKey, balance) {
UniversalDApp.prototype.getAccounts = function (cb) { UniversalDApp.prototype.getAccounts = function (cb) {
var self = this; var self = this;
if (!self.vm) { if (!self.executionContext.isVM()) {
self.web3.eth.getAccounts(cb); self.web3.eth.getAccounts(cb);
} else { } else {
if (!self.accounts) { if (!self.accounts) {
@ -64,7 +76,7 @@ UniversalDApp.prototype.getBalance = function (address, cb) {
address = ethJSUtil.stripHexPrefix(address); address = ethJSUtil.stripHexPrefix(address);
if (!self.vm) { if (!self.executionContext.isVM()) {
self.web3.eth.getBalance(address, function (err, res) { self.web3.eth.getBalance(address, function (err, res) {
if (err) { if (err) {
cb(err); cb(err);
@ -194,7 +206,7 @@ UniversalDApp.prototype.getInstanceInterface = function (contract, address, $tar
$close.click(function () { $instance.remove(); }); $close.click(function () { $instance.remove(); });
$instance.append($close); $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'); address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex');
var $title = $('<span class="title"/>').text(contract.name + ' at ' + address + ' (' + context + ')'); var $title = $('<span class="title"/>').text(contract.name + ' at ' + address + ' (' + context + ')');
@ -221,7 +233,7 @@ UniversalDApp.prototype.getInstanceInterface = function (contract, address, $tar
$events.append($event); $events.append($event);
}; };
if (self.options.vm) { if (self.executionContext.isVM()) {
// FIXME: support indexed events // FIXME: support indexed events
var eventABI = {}; var eventABI = {};
@ -354,7 +366,7 @@ UniversalDApp.prototype.getCallButton = function (args) {
var $debugTx = $('<div class="debugTx">'); var $debugTx = $('<div class="debugTx">');
var $button = $('<button title="Launch Debugger" class="debug"><i class="fa fa-bug"></i></button>'); var $button = $('<button title="Launch Debugger" class="debug"><i class="fa fa-bug"></i></button>');
$button.click(function () { $button.click(function () {
self.transactionDebugger.debug(result); self.event.trigger('debugRequested', [result]);
}); });
$debugTx.append($button); $debugTx.append($button);
return $debugTx; return $debugTx;
@ -364,7 +376,7 @@ UniversalDApp.prototype.getCallButton = function (args) {
var $debugTx = $('<div class="debugCall">'); var $debugTx = $('<div class="debugCall">');
var $button = $('<button title="Launch Debugger" class="debug"><i class="fa fa-bug"></i></button>'); var $button = $('<button title="Launch Debugger" class="debug"><i class="fa fa-bug"></i></button>');
$button.click(function () { $button.click(function () {
self.transactionDebugger.debug(result); self.event.trigger('debugRequested', [result]);
}); });
$debugTx.append($button); $debugTx.append($button);
return $debugTx; return $debugTx;
@ -504,16 +516,16 @@ UniversalDApp.prototype.getCallButton = function (args) {
if (err) { if (err) {
replaceOutput($result, $('<span/>').text(err).addClass('error')); replaceOutput($result, $('<span/>').text(err).addClass('error'));
// VM only // 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')); replaceOutput($result, $('<span/>').text('VM Exception: ' + result.vm.exceptionError).addClass('error'));
// VM only // 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')); replaceOutput($result, $('<span/>').text('Exception during execution.').addClass('error'));
} else if (isConstructor) { } else if (isConstructor) {
replaceOutput($result, getGasUsedOutput(result, result.vm)); replaceOutput($result, getGasUsedOutput(result, result.vm));
$result.append(getDebugTransaction(result)); $result.append(getDebugTransaction(result));
args.appendFunctions(self.options.vm ? result.createdAddress : result.contractAddress); args.appendFunctions(self.executionContext.isVM() ? result.createdAddress : result.contractAddress);
} else if (self.options.vm) { } else if (self.executionContext.isVM()) {
var outputObj = '0x' + result.vm.return.toString('hex'); var outputObj = '0x' + result.vm.return.toString('hex');
clearOutput($result); clearOutput($result);
$result.append(getReturnOutput(outputObj)).append(getGasUsedOutput(result, result.vm)); $result.append(getReturnOutput(outputObj)).append(getGasUsedOutput(result, result.vm));
@ -611,7 +623,7 @@ UniversalDApp.prototype.deployLibrary = function (contractName, cb) {
if (err) { if (err) {
return cb(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; self.getContractByName(contractName).address = address;
cb(err, address); cb(err, address);
}); });
@ -644,27 +656,27 @@ UniversalDApp.prototype.runTx = function (data, args, cb) {
} }
var gasLimit = 3000000; var gasLimit = 3000000;
if (self.options.getGasLimit) { if (self.getGasLimit) {
try { try {
gasLimit = self.options.getGasLimit(); gasLimit = self.getGasLimit();
} catch (e) { } catch (e) {
return cb(e); return cb(e);
} }
} }
var value = 0; var value = 0;
if (self.options.getValue) { if (self.getValue) {
try { try {
value = self.options.getValue(); value = self.getValue();
} catch (e) { } catch (e) {
return cb(e); return cb(e);
} }
} }
var tx; var tx;
if (!self.vm) { if (!self.executionContext.isVM()) {
tx = { 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, to: to,
data: data, data: data,
value: value value: value
@ -695,7 +707,7 @@ UniversalDApp.prototype.runTx = function (data, args, cb) {
} }
} else { } else {
try { 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]; var account = self.accounts[address];
tx = new EthJSTX({ tx = new EthJSTX({
nonce: new BN(account.nonce++), nonce: new BN(account.nonce++),
@ -715,7 +727,7 @@ UniversalDApp.prototype.runTx = function (data, args, cb) {
uncleHeaders: [] uncleHeaders: []
}); });
self.vm.runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}, function (err, result) { 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); cb(err, result);
}); });
} catch (e) { } catch (e) {

Loading…
Cancel
Save