Commit 602faa7f authored by chriseth's avatar chriseth

Merge pull request #21 from d11e9/gh-pages

Add multiple files and local imports
parents ec718645 4c3dbae4
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
var multi = function(func) { return func.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]; } var multi = function(func) { return func.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1]; }
var BALLOT_EXAMPLE = multi(function(){/*contract Ballot { var BALLOT_EXAMPLE = multi(function(){/*contract Ballot {
struct Voter { struct Voter {
uint weight; uint weight;
bool voted; bool voted;
......
...@@ -47,463 +47,617 @@ THE SOFTWARE. ...@@ -47,463 +47,617 @@ THE SOFTWARE.
</head> </head>
<body> <body>
<div id="editor"> <div id="editor">
<div id="input"></div> <div id="files">
</div> <span class="newFile" title="New File">+</span>
</div>
<div id="righthand-panel"> <div id="input"></div>
<div id="dragbar"></div> </div>
<div id="header">
<img id="solIcon" src="solidity.svg"> <div id="righthand-panel">
<h1>Solidity realtime<br/>compiler and runtime</h1> <div id="dragbar"></div>
<div class="info"> <div id="header">
<p>Version: <span id="version">(loading)</span><br/> <img id="solIcon" src="solidity.svg">
Change to: <select id="versionSelector"></select><br/> <h1>Solidity realtime<br/>compiler and runtime</h1>
Execution environment does not connect to any node, everyhing is local and in memory only.<br/> <div class="info">
<code>tx.origin = <span id="txorigin"/></code></p> <p>Version: <span id="version">(loading)</span><br/>
</div> Change to: <select id="versionSelector"></select><br/>
<div id="optimizeBox"> Execution environment does not connect to any node, everyhing is local and in memory only.<br/>
<input id="editorWrap" type="checkbox"><label for="editorWrap">Text Wrap</label> <code>tx.origin = <span id="txorigin"/></code></p>
<input id="optimize" type="checkbox"><label for="optimize">Enable Optimization</label> </div>
</div> <div id="optimizeBox">
</div> <input id="editorWrap" type="checkbox"><label for="editorWrap">Text Wrap</label>
<div id="output"></div> <input id="optimize" type="checkbox"><label for="optimize">Enable Optimization</label>
</div> </div>
</div>
<div id="output"></div>
</div>
<script>
// ----------------- editor ---------------------- <script>
var SOL_CACHE_KEY = "sol-cache";
// ----------------- editor ----------------------
var editor = ace.edit("input");
var session = editor.getSession(); var SOL_CACHE_FILE_PREFIX = 'sol-cache-file-';
var Range = ace.require('ace/range').Range; var SOL_CACHE_UNTITLED = SOL_CACHE_FILE_PREFIX + 'Untitled';
var errMarkerId = null; var SOL_CACHE_FILE = null;
var solCache = window.localStorage.getItem( SOL_CACHE_KEY ); var editor = ace.edit("input");
editor.setValue( solCache || BALLOT_EXAMPLE, 1 ); var session = editor.getSession();
var Range = ace.require('ace/range').Range;
session.setMode("ace/mode/javascript"); var errMarkerId = null;
session.setTabSize(4);
session.setUseSoftTabs(true); var untitledCount = '';
if (!getFiles().length || window.localStorage['sol-cache']) {
// ----------------- version selector------------- // Backwards-compatibility
while (window.localStorage[SOL_CACHE_UNTITLED + untitledCount])
// var soljsonSources is provided by bin/list.js untitledCount = (untitledCount - 0) + 1;
$('option', '#versionSelector').remove(); SOL_CACHE_FILE = SOL_CACHE_UNTITLED + untitledCount;
$.each(soljsonSources, function(i, file) { window.localStorage[SOL_CACHE_FILE] = window.localStorage['sol-cache'] || BALLOT_EXAMPLE;
if (file) { window.localStorage.removeItem('sol-cache');
var version = file.replace(/soljson-(.*).js/, "$1"); }
$('#versionSelector').append(new Option(version, file));
} SOL_CACHE_FILE = getFiles()[0];
});
$('#versionSelector').change(function() { editor.setValue( window.localStorage[SOL_CACHE_FILE], -1);
Module = null; session.setMode("ace/mode/javascript");
compileJSON = null; session.setTabSize(4);
var script = document.createElement('script'); session.setUseSoftTabs(true);
script.type = 'text/javascript';
script.src = 'bin/' + $('#versionSelector').val(); // ----------------- file selector-------------
$('head').append(script);
onCompilerLoaded(); var $filesEl = $('#files');
});
$filesEl.on('click','.newFile', function() {
// ----------------- resizeable ui --------------- while (window.localStorage[SOL_CACHE_UNTITLED + untitledCount])
untitledCount = (untitledCount - 0) + 1;
var EDITOR_SIZE_CACHE_KEY = "editor-size-cache"; SOL_CACHE_FILE = SOL_CACHE_UNTITLED + untitledCount;
var dragging = false; window.localStorage[SOL_CACHE_FILE] = '';
$('#dragbar').mousedown(function(e){ updateFiles();
e.preventDefault(); })
dragging = true;
var main = $('#righthand-panel'); $filesEl.on('click', '.file:not(.active)', showFileHandler);
var ghostbar = $('<div id="ghostbar">', {
css: { $filesEl.on('click', '.file.active', function(ev) {
top: main.offset().top, var $fileTabEl = $(this);
left: main.offset().left var originalName = $fileTabEl.find('.name').text();
} ev.preventDefault();
}).prependTo('body'); if ($(this).find('input').length > 0) return false;
var $fileNameInputEl = $('<input value="'+originalName+'"/>');
$(document).mousemove(function(e){ $fileTabEl.html($fileNameInputEl);
ghostbar.css("left",e.pageX+2); $fileNameInputEl.focus();
}); $fileNameInputEl.select();
}); $fileNameInputEl.on('blur', handleRename);
$fileNameInputEl.keyup(handleRename);
var $body = $('body');
function handleRename(ev) {
function setEditorSize (delta) { ev.preventDefault();
$('#righthand-panel').css("width", delta); if (ev.which && ev.which !== 13) return false;
$('#editor').css("right", delta); var newName = ev.target.value;
onResize(); $fileNameInputEl.off('blur');
} $fileNameInputEl.off('keyup');
$(document).mouseup(function(e){ if (newName !== originalName && confirm("Are you sure you want to rename: " + originalName + " to " + newName + '?')) {
if (dragging) { var content = window.localStorage.getItem( fileKey(originalName) );
var delta = $body.width() - e.pageX+2; window.localStorage[fileKey( newName )] = content;
$('#ghostbar').remove(); window.localStorage.removeItem( fileKey( originalName) );
$(document).unbind('mousemove'); SOL_CACHE_FILE = fileKey( newName );
dragging = false; }
setEditorSize( delta )
window.localStorage.setItem( EDITOR_SIZE_CACHE_KEY, delta ); updateFiles();
} return false;
}); }
// set cached defaults return false;
var cachedSize = window.localStorage.getItem( EDITOR_SIZE_CACHE_KEY ); })
if (cachedSize) setEditorSize( cachedSize );
$filesEl.on('click', '.file .remove', function(ev) {
ev.preventDefault();
// ----------------- editor resize --------------- var name = $(this).parent().find('.name').text();
var index = getFiles().indexOf( fileKey(name) );
function onResize() {
editor.resize(); if (confirm("Are you sure you want to remove: " + name + " from local storage?")) {
session.setUseWrapMode(document.querySelector('#editorWrap').checked); window.localStorage.removeItem( fileKey( name ) );
if(session.getUseWrapMode()) { SOL_CACHE_FILE = getFiles()[ Math.max(0, index - 1)];
var characterWidth = editor.renderer.characterWidth; updateFiles();
var contentWidth = editor.container.ownerDocument.getElementsByClassName("ace_scroller")[0].clientWidth; }
return false;
if(contentWidth > 0) { });
session.setWrapLimit(parseInt(contentWidth / characterWidth, 10));
} function showFileHandler(ev) {
} ev.preventDefault();
} SOL_CACHE_FILE = fileKey( $(this).find('.name').text() );
window.onresize = onResize; updateFiles();
onResize(); return false;
}
document.querySelector('#editor').addEventListener('change', onResize );
function fileTabFromKey(key) {
var name = fileNameFromKey(key);
// ----------------- compiler ---------------------- return $('#files .file').filter(function(){ return $(this).find('.name').text() == name; });
var compileJSON; }
var previousInput = '';
var sourceAnnotations = []; function updateFiles() {
var compile = function() { $filesEl.find('.file').remove();
editor.getSession().clearAnnotations(); var files = getFiles();
sourceAnnotations = []; for (var f in files) {
editor.getSession().removeMarker(errMarkerId); $filesEl.append(fileTabTemplate(files[f]));
$('#output').empty(); }
var input = editor.getValue();
var optimize = document.querySelector('#optimize').checked; if (SOL_CACHE_FILE) {
try { var active = fileTabFromKey(SOL_CACHE_FILE);
var data = $.parseJSON(compileJSON(input, optimize ? 1 : 0)); active.addClass('active');
} catch (exception) { editor.setValue( window.localStorage[SOL_CACHE_FILE] || '', -1);
renderError("Uncaught JavaScript Exception:\n" + exception); editor.focus();
return; $('#input').toggle( true );
} } else {
if (data['error'] !== undefined) $('#input').toggle( false );
renderError(data['error']); }
if (data['errors'] != undefined) }
$.each(data['errors'], function(i, err) {
renderError(err); function fileTabTemplate(key) {
}); var name = fileNameFromKey(key);
else return $('<span class="file"><span class="name">'+name+'</span><span class="remove">x</span></span>');
renderContracts(data, input); }
} function fileKey( name ) {
var compileTimeout = null; return SOL_CACHE_FILE_PREFIX + name;
var onChange = function() { }
var input = editor.getValue();
if (input === "") { function fileNameFromKey(key) {
window.localStorage.setItem( SOL_CACHE_KEY, '' ) return key.replace( SOL_CACHE_FILE_PREFIX, '' );
return; }
}
if (input === previousInput) function getFiles() {
return; var files = [];
previousInput = input; for (var f in localStorage ) {
if (compileTimeout) window.clearTimeout(compileTimeout); if (f.indexOf( SOL_CACHE_FILE_PREFIX, 0 ) === 0) {
compileTimeout = window.setTimeout(compile, 300); files.push(f);
}; }
}
var onCompilerLoaded = function() { return files;
compileJSON = Module.cwrap("compileJSON", "string", ["string", "number"]); }
$('#version').text(Module.cwrap("version", "string", [])());
previousInput = ''; updateFiles();
onChange();
}; // ----------------- version selector-------------
if (Module)
onCompilerLoaded(); // var soljsonSources is provided by bin/list.js
$('option', '#versionSelector').remove();
editor.getSession().on('change', onChange); $.each(soljsonSources, function(i, file) {
if (file) {
document.querySelector('#optimize').addEventListener('change', compile); var version = file.replace(/soljson-(.*).js/, "$1");
$('#versionSelector').append(new Option(version, file));
// ----------------- compiler output renderer ---------------------- }
var detailsOpen = {}; });
$('#versionSelector').change(function() {
var renderError = function(message) { Module = null;
$('#output') compileJSON = null;
.append($('<pre class="error"></pre>').text(message)); var script = document.createElement('script');
var err = message.match(/^:([0-9]*):([0-9]*)/) script.type = 'text/javascript';
if (err && err.length) { script.src = 'bin/' + $('#versionSelector').val();
var errLine = parseInt( err[1], 10 ) - 1; $('head').append(script);
var errCol = err[2] ? parseInt( err[2], 10 ) : 0; onCompilerLoaded();
sourceAnnotations[sourceAnnotations.length] ={ });
row: errLine,
column: errCol, // ----------------- resizeable ui ---------------
text: message,
type: "error" var EDITOR_SIZE_CACHE_KEY = "editor-size-cache";
}; var dragging = false;
editor.getSession().setAnnotations(sourceAnnotations); $('#dragbar').mousedown(function(e){
} e.preventDefault();
}; dragging = true;
var main = $('#righthand-panel');
var gethDeploy = function(contractName, interface, bytecode){ var ghostbar = $('<div id="ghostbar">', {
var code = ""; css: {
var funABI = getConstructorInterface($.parseJSON(interface)); top: main.offset().top,
left: main.offset().left
$.each(funABI.inputs, function(i, inp) { }
code += "var "+inp.name+" = /* var of type " + inp.type + " here */ ;\n"; }).prependTo('body');
});
$(document).mousemove(function(e){
code += "\nvar "+contractName+"Contract = web3.eth.contract("+interface.replace("\n","")+");" ghostbar.css("left",e.pageX+2);
+"\nvar "+contractName+" = "+contractName+"Contract.new("; });
});
$.each(funABI.inputs, function(i, inp) {
code += "\n "+inp.name+","; var $body = $('body');
});
function setEditorSize (delta) {
code += "\n {"+ $('#righthand-panel').css("width", delta);
"\n from: web3.eth.accounts[0], "+ $('#editor').css("right", delta);
"\n data: '"+bytecode+"', "+ onResize();
"\n gas: 3000000"+ }
"\n }, function(e, contract){"+
"\n if (typeof contract.address != 'undefined') {"+ $(document).mouseup(function(e){
"\n console.log(e, contract);"+ if (dragging) {
"\n console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);" + var delta = $body.width() - e.pageX+2;
"\n }" + $('#ghostbar').remove();
"\n })"; $(document).unbind('mousemove');
dragging = false;
setEditorSize(delta)
return code; window.localStorage.setItem(EDITOR_SIZE_CACHE_KEY, delta);
}; }
});
var combined = function(contractName, interface, bytecode){
return JSON.stringify( [{name: contractName, interface: interface, bytecode: bytecode}]); // set cached defaults
var cachedSize = window.localStorage.getItem(EDITOR_SIZE_CACHE_KEY);
}; if (cachedSize) setEditorSize(cachedSize);
var renderContracts = function(data, source) {
window.localStorage.setItem( SOL_CACHE_KEY, source ); // ----------------- editor resize ---------------
$('#output').empty(); function onResize() {
for (var contractName in data.contracts) { editor.resize();
var contract = data.contracts[contractName]; session.setUseWrapMode(document.querySelector('#editorWrap').checked);
var title = $('<h3 class="title"/>').text(contractName); if(session.getUseWrapMode()) {
var contractOutput = $('<div class="contractOutput"/>') var characterWidth = editor.renderer.characterWidth;
.append(title); var contentWidth = editor.container.ownerDocument.getElementsByClassName("ace_scroller")[0].clientWidth;
var body = $('<div class="body" />')
contractOutput.append( body ); if(contentWidth > 0) {
if (contract.bytecode.length > 0) session.setWrapLimit(parseInt(contentWidth / characterWidth, 10));
title.append($('<div class="size"/>').text((contract.bytecode.length / 2) + ' bytes')) }
body.append(getExecuteInterface(contract, contractName)) }
.append(tableRow('Bytecode', contract.bytecode)); }
body.append(tableRow('Interface', contract['interface'])) window.onresize = onResize;
.append(textRow('Web3 deploy', gethDeploy(contractName.toLowerCase(),contract['interface'],contract.bytecode), 'deploy')) onResize();
.append(textRow('uDApp', combined(contractName,contract['interface'],contract.bytecode), 'deploy'))
.append(getDetails(contract, source, contractName)); document.querySelector('#editor').addEventListener('change', onResize);
$('#output').append(contractOutput);
title.click(function(ev){ $(this).parent().toggleClass('hide') }); // ----------------- compiler ----------------------
} var compileJSON;
$('.col2 input,textarea').click(function() { this.select(); } ); var previousInput = '';
}; var sourceAnnotations = [];
var tableRowItems = function(first, second, cls) { var compile = function() {
return $('<div class="row"/>')
.addClass(cls) editor.getSession().clearAnnotations();
.append($('<div class="col1">').append(first)) sourceAnnotations = [];
.append($('<div class="col2">').append(second)); editor.getSession().removeMarker(errMarkerId);
}; $('#output').empty();
var tableRow = function(description, data) { var input = editor.getValue();
return tableRowItems( window.localStorage.setItem(SOL_CACHE_FILE, input);
$('<span/>').text(description),
$('<input readonly="readonly"/>').val(data)); var inputIncludingImports = includeLocalImports(input);
}; var optimize = document.querySelector('#optimize').checked;
var textRow = function(description, data, cls) {
return tableRowItems( try {
$('<strong/>').text(description), var data = $.parseJSON(compileJSON(inputIncludingImports, optimize ? 1 : 0));
$('<textarea readonly="readonly" class="gethDeployText"/>').val(data), } catch (exception) {
cls); renderError("Uncaught JavaScript Exception:\n" + exception);
}; return;
var getDetails = function(contract, source, contractName) { }
var button = $('<button>Details</button>'); if (data['error'] !== undefined)
var details = $('<div style="display: none;"/>') renderError(data['error']);
.append(tableRow('Solidity Interface', contract.solidity_interface)) if (data['errors'] != undefined)
.append(tableRow('Opcodes', contract.opcodes)); $.each(data['errors'], function(i, err) {
var funHashes = ''; renderError(err);
for (var fun in contract.functionHashes) });
funHashes += contract.functionHashes[fun] + ' ' + fun + '\n'; else
details.append($('<span class="col1">Functions</span>')); renderContracts(data, input);
details.append($('<pre/>').text(funHashes));
details.append($('<span class="col1">Gas Estimates</span>')); }
details.append($('<pre/>').text(formatGasEstimates(contract.gasEstimates)));
if (contract.runtimeBytecode && contract.runtimeBytecode.length > 0) var compileTimeout = null;
details.append(tableRow('Runtime Bytecode', contract.runtimeBytecode)); var onChange = function() {
if (contract.assembly !== null) var input = editor.getValue();
{ if (input === "") {
details.append($('<span class="col1">Assembly</span>')); window.localStorage.setItem(SOL_CACHE_FILE, '')
var assembly = $('<pre/>').text(formatAssemblyText(contract.assembly, '', source)); return;
details.append(assembly); }
} if (input === previousInput)
button.click(function() { detailsOpen[contractName] = !detailsOpen[contractName]; details.toggle(); }); return;
if (detailsOpen[contractName]) previousInput = input;
details.show(); if (compileTimeout) window.clearTimeout(compileTimeout);
return $('<div/>').append(button).append(details); compileTimeout = window.setTimeout(compile, 300);
}; };
var formatGasEstimates = function(data) {
var gasToText = function(g) { return g === null ? 'unknown' : g; } var onCompilerLoaded = function() {
var text = ''; compileJSON = Module.cwrap("compileJSON", "string", ["string", "number"]);
if ('creation' in data) $('#version').text(Module.cwrap("version", "string", [])());
text += 'Creation: ' + gasToText(data.creation[0]) + ' + ' + gasToText(data.creation[1]) + '\n'; previousInput = '';
text += 'External:\n'; onChange();
for (var fun in data.external) };
text += ' ' + fun + ': ' + gasToText(data.external[fun]) + '\n';
text += 'Internal:\n'; function includeLocalImports(input) {
for (var fun in data.internal) var importRegex = /import\s[\'\"]([^\'\"]+)[\'\"];/g
text += ' ' + fun + ': ' + gasToText(data.internal[fun]) + '\n'; var imports = [];
return text; var matches = [];
}; var match;
var formatAssemblyText = function(asm, prefix, source) { while ((match = importRegex.exec(input)) !== null) {
if (typeof(asm) == typeof('') || asm === null || asm === undefined) if (match[1] && getFiles().indexOf(fileKey(match[1])) !== -1) {
return prefix + asm + '\n'; imports.push(match[1])
var text = prefix + '.code\n'; matches.push(match[0])
$.each(asm['.code'], function(i, item) { }
var v = item.value === undefined ? '' : item.value; }
var src = ''; for (var i in imports) {
if (item.begin !== undefined && item.end != undefined) imported = includeLocalImports(window.localStorage.getItem( fileKey(imports[i]) ))
src = source.slice(item.begin, item.end).replace('\n', '\\n', 'g'); input = input.replace(matches[i], imported);
if (src.length > 30) }
src = src.slice(0, 30) + '...'; return input;
if (item.name != 'tag') }
text += ' ';
text += prefix + item.name + ' ' + v + '\t\t\t' + src + '\n'; if (Module)
}); onCompilerLoaded();
text += prefix + '.data\n';
if (asm['.data']) editor.getSession().on('change', onChange);
$.each(asm['.data'], function(i, item) {
text += ' ' + prefix + '' + i + ':\n'; document.querySelector('#optimize').addEventListener('change', compile);
text += formatAssemblyText(item, prefix + ' ', source);
}); // ----------------- compiler output renderer ----------------------
var detailsOpen = {};
return text;
}; var renderError = function(message) {
$('.asmOutput button').click(function() {$(this).parent().find('pre').toggle(); } ) $('#output')
.append($('<pre class="error"></pre>').text(message));
// ----------------- VM ---------------------- var err = message.match(/^:([0-9]*):([0-9]*)/)
if (err && err.length) {
var stateTrie = new EthVm.Trie(); var errLine = parseInt(err[1], 10) - 1;
var vm = new EthVm.VM(stateTrie); var errCol = err[2] ? parseInt(err[2], 10) : 0;
//@todo this does not calculate the gas costs correctly but gets the job done. sourceAnnotations[sourceAnnotations.length] ={
var identityCode = 'return { gasUsed: 1, return: opts.data, exception: 1 };'; row: errLine,
var identityAddr = ethUtil.pad(new Buffer('04', 'hex'), 20) column: errCol,
vm.loadPrecompiled(identityAddr, identityCode); text: message,
var secretKey = '3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511' type: "error"
var publicKey = '0406cc661590d48ee972944b35ad13ff03c7876eae3fd191e8a2f77311b0a3c6613407b5005e63d7d8d76b89d5f900cde691497688bb281e07a5052ff61edebdc0' };
var address = ethUtil.pubToAddress(new Buffer(publicKey, 'hex')); editor.getSession().setAnnotations(sourceAnnotations);
$('#txorigin').text('0x' + address.toString('hex')); }
var account = new EthVm.Account(); };
account.balance = 'f00000000000000001';
var nonce = 0; var gethDeploy = function(contractName, interface, bytecode){
stateTrie.put(address, account.serialize()); var code = "";
var runTx = function(data, to, cb) { var funABI = getConstructorInterface($.parseJSON(interface));
var tx = new EthVm.Transaction({
nonce: new Buffer([nonce++]), //@todo count beyond 255 $.each(funABI.inputs, function(i, inp) {
gasPrice: '01', code += "var " + inp.name + " = /* var of type " + inp.type + " here */ ;\n";
gasLimit: '3000000000', // plenty });
to: to,
data: data code += "\nvar " + contractName + "Contract = web3.eth.contract(" + interface.replace("\n","") + ");"
}); +"\nvar " + contractName + " = " + contractName + "Contract.new(";
tx.sign(new Buffer(secretKey, 'hex'));
vm.runTx({tx: tx}, cb); $.each(funABI.inputs, function(i, inp) {
}; code += "\n " + inp.name + ",";
});
var getConstructorInterface = function(abi) {
var funABI = {'name':'','inputs':[],'type':'constructor','outputs':[]}; code += "\n {"+
for (var i = 0; i < abi.length; i++) "\n from: web3.eth.accounts[0], "+
if (abi[i].type == 'constructor') { "\n data: '"+bytecode+"', "+
funABI.inputs = abi[i].inputs || []; "\n gas: 3000000"+
break; "\n }, function(e, contract){"+
} "\n if (typeof contract.address != 'undefined') {"+
return funABI; "\n console.log(e, contract);"+
}; "\n console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);" +
"\n }" +
var getCallButton = function(args) { "\n })";
// args.abi, args.bytecode [constr only], args.address [fun only]
// args.appendFunctions [constr only]
var isConstructor = args.bytecode !== undefined; return code;
var fun = new web3.eth.function(args.abi); };
var inputs = '';
$.each(args.abi.inputs, function(i, inp) { var combined = function(contractName, interface, bytecode){
if (inputs != '') inputs += ', '; return JSON.stringify([{name: contractName, interface: interface, bytecode: bytecode}]);
inputs += inp.type + ' ' + inp.name;
}); };
var inputField = $('<input/>').attr('placeholder', inputs);
var outputSpan = $('<div class="output"/>'); var renderContracts = function(data, source) {
var button = $('<button/>') $('#output').empty();
.text(args.bytecode ? 'Create' : fun.displayName()) for (var contractName in data.contracts) {
.click(function() { var contract = data.contracts[contractName];
var funArgs = $.parseJSON('[' + inputField.val() + ']'); var title = $('<h3 class="title"/>').text(contractName);
var data = fun.toPayload(funArgs).data; var contractOutput = $('<div class="contractOutput"/>')
if (data.slice(0, 2) == '0x') data = data.slice(2); .append(title);
if (isConstructor) var body = $('<div class="body" />')
data = args.bytecode + data.slice(8); contractOutput.append(body);
outputSpan.text('...'); if (contract.bytecode.length > 0)
runTx(data, args.address, function(err, result) { title.append($('<div class="size"/>').text((contract.bytecode.length / 2) + ' bytes'))
if (err) body.append(getExecuteInterface(contract, contractName))
outputSpan.text(err); .append(tableRow('Bytecode', contract.bytecode));
else if (isConstructor) { body.append(tableRow('Interface', contract['interface']))
outputSpan.text(' Creation used ' + result.vm.gasUsed.toString(10) + ' gas.'); .append(textRow('Web3 deploy', gethDeploy(contractName.toLowerCase(),contract['interface'],contract.bytecode), 'deploy'))
args.appendFunctions(result.createdAddress); .append(textRow('uDApp', combined(contractName,contract['interface'],contract.bytecode), 'deploy'))
} else { .append(getDetails(contract, source, contractName));
var outputObj = fun.unpackOutput('0x' + result.vm.return.toString('hex'));
outputSpan.text(' Returned: ' + JSON.stringify(outputObj)); $('#output').append(contractOutput);
} title.click(function(ev){ $(this).parent().toggleClass('hide') });
}); }
});
if (!isConstructor) $('.col2 input,textarea').click(function() { this.select(); });
button.addClass('runButton'); };
var c = $('<div class="contractProperty"/>') var tableRowItems = function(first, second, cls) {
.append(button); return $('<div class="row"/>')
if (args.abi.inputs.length > 0) .addClass(cls)
c.append(inputField); .append($('<div class="col1">').append(first))
return c.append(outputSpan); .append($('<div class="col2">').append(second));
}; };
var tableRow = function(description, data) {
var getExecuteInterface = function(contract, name) { return tableRowItems(
var abi = $.parseJSON(contract.interface); $('<span/>').text(description),
var execInter = $('<div/>'); $('<input readonly="readonly"/>').val(data));
var funABI = getConstructorInterface(abi); };
var textRow = function(description, data, cls) {
var appendFunctions = function(address) { return tableRowItems(
var instance = $('<div class="contractInstance"/>'); $('<strong/>').text(description),
var title = $('<span class="title"/>').text('Contract at ' + address.toString('hex') ); $('<textarea readonly="readonly" class="gethDeployText"/>').val(data),
instance.append(title); cls);
$.each(abi, function(i, funABI) { };
if (funABI.type != 'function') return; var getDetails = function(contract, source, contractName) {
instance.append(getCallButton({ var button = $('<button>Details</button>');
abi: funABI, var details = $('<div style="display: none;"/>')
address: address .append(tableRow('Solidity Interface', contract.solidity_interface))
})); .append(tableRow('Opcodes', contract.opcodes));
}); var funHashes = '';
execInter.append(instance); for (var fun in contract.functionHashes)
title.click(function(ev){ $(this).parent().toggleClass('hide') }); funHashes += contract.functionHashes[fun] + ' ' + fun + '\n';
}; details.append($('<span class="col1">Functions</span>'));
details.append($('<pre/>').text(funHashes));
if (contract.bytecode.length > 0) details.append($('<span class="col1">Gas Estimates</span>'));
execInter details.append($('<pre/>').text(formatGasEstimates(contract.gasEstimates)));
.append(getCallButton({ if (contract.runtimeBytecode && contract.runtimeBytecode.length > 0)
abi: funABI, details.append(tableRow('Runtime Bytecode', contract.runtimeBytecode));
bytecode: contract.bytecode, if (contract.assembly !== null)
appendFunctions: appendFunctions {
})); details.append($('<span class="col1">Assembly</span>'));
return execInter; var assembly = $('<pre/>').text(formatAssemblyText(contract.assembly, '', source));
}; details.append(assembly);
}
</script> button.click(function() { detailsOpen[contractName] = !detailsOpen[contractName]; details.toggle(); });
if (detailsOpen[contractName])
details.show();
return $('<div/>').append(button).append(details);
};
var formatGasEstimates = function(data) {
var gasToText = function(g) { return g === null ? 'unknown' : g; }
var text = '';
if ('creation' in data)
text += 'Creation: ' + gasToText(data.creation[0]) + ' + ' + gasToText(data.creation[1]) + '\n';
text += 'External:\n';
for (var fun in data.external)
text += ' ' + fun + ': ' + gasToText(data.external[fun]) + '\n';
text += 'Internal:\n';
for (var 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;
};
$('.asmOutput button').click(function() {$(this).parent().find('pre').toggle(); })
// ----------------- VM ----------------------
var stateTrie = new EthVm.Trie();
var vm = new EthVm.VM(stateTrie);
//@todo this does not calculate the gas costs correctly but gets the job done.
var identityCode = 'return { gasUsed: 1, return: opts.data, exception: 1 };';
var identityAddr = ethUtil.pad(new Buffer('04', 'hex'), 20)
vm.loadPrecompiled(identityAddr, identityCode);
var secretKey = '3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511'
var publicKey = '0406cc661590d48ee972944b35ad13ff03c7876eae3fd191e8a2f77311b0a3c6613407b5005e63d7d8d76b89d5f900cde691497688bb281e07a5052ff61edebdc0'
var address = ethUtil.pubToAddress(new Buffer(publicKey, 'hex'));
$('#txorigin').text('0x' + address.toString('hex'));
var account = new EthVm.Account();
account.balance = 'f00000000000000001';
var nonce = 0;
stateTrie.put(address, account.serialize());
var runTx = function(data, to, cb) {
var tx = new EthVm.Transaction({
nonce: new Buffer([nonce++]), //@todo count beyond 255
gasPrice: '01',
gasLimit: '3000000000', // plenty
to: to,
data: data
});
tx.sign(new Buffer(secretKey, 'hex'));
vm.runTx({tx: tx}, cb);
};
var 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;
};
var getCallButton = function(args) {
// args.abi, args.bytecode [constr only], args.address [fun only]
// args.appendFunctions [constr only]
var isConstructor = args.bytecode !== undefined;
var fun = new web3.eth.function(args.abi);
var inputs = '';
$.each(args.abi.inputs, function(i, inp) {
if (inputs != '') inputs += ', ';
inputs += inp.type + ' ' + inp.name;
});
var inputField = $('<input/>').attr('placeholder', inputs);
var outputSpan = $('<div class="output"/>');
var button = $('<button/>')
.text(args.bytecode ? 'Create' : fun.displayName())
.click(function() {
var funArgs = $.parseJSON('[' + inputField.val() + ']');
var data = fun.toPayload(funArgs).data;
if (data.slice(0, 2) == '0x') data = data.slice(2);
if (isConstructor)
data = args.bytecode + data.slice(8);
outputSpan.text('...');
runTx(data, args.address, function(err, result) {
if (err)
outputSpan.text(err);
else if (isConstructor) {
outputSpan.text(' Creation used ' + result.vm.gasUsed.toString(10) + ' gas.');
args.appendFunctions(result.createdAddress);
} else {
var outputObj = fun.unpackOutput('0x' + result.vm.return.toString('hex'));
outputSpan.text(' Returned: ' + JSON.stringify(outputObj));
}
});
});
if (!isConstructor)
button.addClass('runButton');
var c = $('<div class="contractProperty"/>')
.append(button);
if (args.abi.inputs.length > 0)
c.append(inputField);
return c.append(outputSpan);
};
var getExecuteInterface = function(contract, name) {
var abi = $.parseJSON(contract.interface);
var execInter = $('<div/>');
var funABI = getConstructorInterface(abi);
var appendFunctions = function(address) {
var instance = $('<div class="contractInstance"/>');
var title = $('<span class="title"/>').text('Contract at ' + address.toString('hex'));
instance.append(title);
$.each(abi, function(i, funABI) {
if (funABI.type != 'function') return;
instance.append(getCallButton({
abi: funABI,
address: address
}));
});
execInter.append(instance);
title.click(function(ev){ $(this).parent().toggleClass('hide') });
};
if (contract.bytecode.length > 0)
execInter
.append(getCallButton({
abi: funABI,
bytecode: contract.bytecode,
appendFunctions: appendFunctions
}));
return execInter;
};
</script>
</body> </body>
</html> </html>
...@@ -10,12 +10,66 @@ body { ...@@ -10,12 +10,66 @@ body {
width: auto; width: auto;
bottom: 0px; bottom: 0px;
right: 37em; right: 37em;
} }
#input {
#files {
font-size: 15px; font-size: 15px;
height: 2.5em;
box-sizing: border-box;
line-height: 2em;
padding: 0.5em 0.5em 0;
}
#files .file,
#files .newFile {
display: inline-block;
padding: 0 0.6em;
box-sizing: border-box;
background-color: #f0f0f0;
cursor: pointer;
margin-right: 0.5em;
position: relative;
}
#files .newFile {
background-color: #B1EAC5;
font-weight: bold;
color: #4E775D;
}
#files .file.active {
font-weight: bold;
border-bottom: 0 none;
padding-right: 2.5em;
}
#files .file .remove {
position: absolute; position: absolute;
right: 0;
top: 0; top: 0;
height: 1.25em;
width: 1.25em;
line-height: 1em;
border-radius: 1em;
color: #FF8080;
display: none;
margin: 0.4em;
text-align: center;
}
#files .file input {
background-color: transparent;
border: 0 none;
border-bottom: 1px dotted black;
line-height: 1em;
margin: 0.5em 0;
}
#files .file.active .remove { display: inline-block; }
#input {
font-size: 15px;
position: absolute;
top: 2.5em;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment