add event manager

pull/1/head
yann300 8 years ago
parent 63743b221a
commit e6adf1da19
  1. 78
      src/app.js
  2. 50
      src/app/compiler.js
  3. 35
      src/app/execution-context.js
  4. 16
      src/app/formalVerification.js
  5. 254
      src/app/renderer.js
  6. 161
      src/app/ui-helper.js
  7. 41
      src/lib/eventManager.js
  8. 14
      src/lib/util.js
  9. 84
      src/universal-dapp.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 util = require('./lib/util');
// 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 selectTab
*/
var run = function () {
var self = this;
util.makeEventCapable(this);
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 --------------
@ -279,7 +285,7 @@ var run = function () {
}
// function widthOfHidden () {
// return ($filesWrapper.outerWidth() - widthOfList() - getLeftPosi());
// return ($filesWrapper.outerWidth() - widthOfList() - getLeftPosi())
// }
function widthOfVisible () {
@ -408,8 +414,6 @@ var run = function () {
if (!hidingRHP) compiler.compile();
});
function getHidingRHP () { return hidingRHP; }
// ----------------- editor resize ---------------
function onResize () {
@ -433,19 +437,63 @@ var run = function () {
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();
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 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) {
$('#version').text(text);
@ -459,9 +507,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 util = require('../lib/util');
/*
trigger compilationError, compilationSucceed, compilerLoaded, isCompiling
*/
function Compiler (editor, queryParams, handleGithubCall, updateFiles) {
var self = this;
util.makeEventCapable(this);
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('isCompiling', []);
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('compilationError', [error]);
} 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('compilationError', [data['error']]);
if (utils.errortype(data['error']) !== 'warning') {
noFatalErrors = false;
}
}
if (data['errors'] !== undefined) {
data['errors'].forEach(function (err) {
renderer.error(err);
self.event.trigger('compilationError', [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('compilationSucceed', [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;

@ -2,6 +2,8 @@
var $ = require('jquery');
var Web3 = require('web3');
var util = require('../lib/util');
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
*/
function ExecutionContext () {
var self = this;
util.makeEventCapable(this);
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,8 +1,12 @@
var $ = require('jquery');
var util = require('../lib/util');
function FormalVerification (outputElement, renderer) {
/*
trigger compilationError
*/
function FormalVerification (outputElement) {
util.makeEventCapable(this);
this.outputElement = outputElement;
this.renderer = renderer;
}
FormalVerification.prototype.compiling = function () {
@ -14,11 +18,7 @@ FormalVerification.prototype.compiling = function () {
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('compilationError', ['Formal verification not supported by this compiler version.', $('#formalVerificationErrors'), true]);
} else {
if (compilationResult.formal['why3'] !== undefined) {
$('#formalVerificationInput', this.outputElement).val(
@ -30,7 +30,7 @@ FormalVerification.prototype.compilationFinished = function (compilationResult)
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('compilationError', [errors[i], $('#formalVerificationErrors'), true]);
}
}
}

@ -1,13 +1,17 @@
var $ = require('jquery');
var UniversalDApp = require('../universal-dapp.js');
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) {
var detailsOpen = {};
function renderError (message, container, noAnnotations) {
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);
@ -20,8 +24,8 @@ function Renderer (editor, executionContext, updateFiles, transactionDebugger, v
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({
if (!noAnnotations && (errFile === '' || errFile === utils.fileNameFromKey(this.editor.getCacheFile()))) {
this.editor.addAnnotation({
row: errLine,
column: errCol,
text: message,
@ -29,13 +33,13 @@ function Renderer (editor, executionContext, updateFiles, transactionDebugger, v
});
}
$error.click(function (ev) {
if (errFile !== '' && errFile !== utils.fileNameFromKey(editor.getCacheFile()) && editor.hasFile(errFile)) {
if (errFile !== '' && errFile !== utils.fileNameFromKey(this.editor.getCacheFile()) && this.editor.hasFile(errFile)) {
// Switch to file
editor.setCacheFile(utils.fileKey(errFile));
updateFiles();
this.editor.setCacheFile(utils.fileKey(errFile));
this.updateFiles();
// @TODO could show some error icon in files with errors
}
editor.handleErrorClick(errLine, errCol);
this.editor.handleErrorClick(errLine, errCol);
});
$error.find('.close').click(function (ev) {
ev.preventDefault();
@ -43,14 +47,9 @@ function Renderer (editor, executionContext, updateFiles, transactionDebugger, v
return false;
});
}
}
this.error = renderError;
var combined = function (contractName, jsonInterface, bytecode) {
return JSON.stringify([{ name: contractName, interface: jsonInterface, bytecode: bytecode }]);
};
};
function renderContracts (data, source) {
Renderer.prototype.contracts = function (data, source) {
var udappContracts = [];
for (var contractName in data.contracts) {
var contract = data.contracts[contractName];
@ -61,43 +60,46 @@ function Renderer (editor, executionContext, updateFiles, transactionDebugger, v
});
}
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) {
// 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 renderOutputModifier = function (contractName, $contractOutput) {
var contract = data.contracts[contractName];
if (contract.bytecode) {
$contractOutput.append(textRow('Bytecode', contract.bytecode));
$contractOutput.append(uiHelper.textRow('Bytecode', contract.bytecode));
}
$contractOutput.append(textRow('Interface', contract['interface']));
$contractOutput.append(uiHelper.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));
$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'));
}
}, transactionDebugger, vm);
return $contractOutput.append(uiHelper.getDetails(contract, source, contractName));
};
// //
var self = this;
var getAddress = function () { return $('#txorigin').val(); };
var getValue = function () {
var comp = $('#value').val().split(' ');
return self.executionContext.web3().toWei(comp[0], comp.slice(1).join(' '));
};
var $contractOutput = dapp.render();
var getGasLimit = function () { return $('#gasLimit').val(); };
this.udapp.reset(udappContracts, getAddress, getValue, getGasLimit, renderOutputModifier);
var $contractOutput = this.udapp.render();
var $txOrigin = $('#txorigin');
function renderAccounts (err, accounts) {
this.udapp.getAccounts(function (err, accounts) {
if (err) {
renderError(err.message);
self.error(err.message);
}
if (accounts && accounts[0]) {
$txOrigin.empty();
@ -106,171 +108,11 @@ function Renderer (editor, executionContext, updateFiles, transactionDebugger, v
} 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.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);
};
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);
if (gasEstimates) {
details.append($('<span class="col1">Gas Estimates</span>'));
details.append($('<pre/>').text(gasEstimates));
}
if (contract.runtimeBytecode && contract.runtimeBytecode.length > 0) {
details.append(tableRow('Runtime Bytecode', contract.runtimeBytecode));
}
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) {
// 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;
};
var formatAssemblyText = function (asm, prefix, source) {
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 += formatAssemblyText(item, prefix + ' ', source);
});
}
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;
}
}
};
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,30 +6,42 @@ var EthJSTX = require('ethereumjs-tx');
var ethJSABI = require('ethereumjs-abi');
var EthJSBlock = require('ethereumjs-block');
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;
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.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);
});
}
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');
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 +60,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 +76,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 +206,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 +233,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 +366,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 +376,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 +516,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 +623,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 +656,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 +707,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 +727,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) {

Loading…
Cancel
Save