diff --git a/index.html b/index.html index 4d35a995ac..c65304973c 100644 --- a/index.html +++ b/index.html @@ -500,34 +500,44 @@ THE SOFTWARE. }; var renderContracts = function(data, source) { + var udappContracts = []; for (var contractName in data.contracts) { var contract = data.contracts[contractName]; - var dapp = new UniversalDApp([{ + udappContracts.push({ name: contractName, - interface: contract['interface'], + interface: contract['interface'], bytecode: contract.bytecode - }], { - vm: executionContext === 'vm', - removable: false, - removable_instances: true }); - var $contractOutput = dapp.render(); - $contractOutput + } + var dapp = new UniversalDApp(udappContracts, { + vm: executionContext === 'vm', + removable: false, + removable_instances: true, + renderOutputModifier: function(contractName, $contractOutput) { + var contract = data.contracts[contractName]; + return $contractOutput .append(textRow('Bytecode', contract.bytecode)) .append(textRow('Interface', contract['interface'])) .append(textRow('Web3 deploy', gethDeploy(contractName.toLowerCase(),contract['interface'],contract.bytecode), 'deploy')) .append(textRow('uDApp', combined(contractName,contract['interface'],contract.bytecode), 'deploy')) .append(getDetails(contract, source, contractName)); - - if (executionContext === 'vm') $('#txorigin').text('0x' + dapp.address.toString('hex')); - else web3.eth.getAccounts( function(err,accounts) { - if (err) renderError(err.message); - $('#txorigin').text(accounts[0]); + }}); + var $contractOutput = dapp.render(); + + if (executionContext === 'vm') + $('#txorigin').text('0x' + dapp.address.toString('hex')); + else + web3.eth.getAccounts(function(err, accounts) { + if (err) + renderError(err.message); + if (accounts && accounts[0]) + $('#txorigin').text(accounts[0]); + else + $('#txorigin').text('unknown'); }); - $contractOutput.find('.title').click(function(ev){ $(this).closest('.udapp').toggleClass('hide') }); - $('#output').append( $contractOutput ); - } + $contractOutput.find('.title').click(function(ev){ $(this).closest('.contract').toggleClass('hide'); }); + $('#output').append( $contractOutput ); $('.col2 input,textarea').click(function() { this.select(); }); }; var tableRowItems = function(first, second, cls) { diff --git a/libs/universal-dapp.js b/libs/universal-dapp.js index 63c1fe69e7..b052635e81 100644 --- a/libs/universal-dapp.js +++ b/libs/universal-dapp.js @@ -2,6 +2,7 @@ function UniversalDApp (contracts, options) { this.options = options || {}; this.$el = $('
'); this.contracts = contracts; + this.renderOutputModifier = options.renderOutputModifier || function(name, content) { return content; }; if (!options.vm && web3.currentProvider) { @@ -44,7 +45,7 @@ UniversalDApp.prototype.render = function () { } $contractEl.append( $title ).append( this.getCreateInterface( $contractEl, this.contracts[c]) ); } - this.$el.append( $contractEl ); + this.$el.append(this.renderOutputModifier(this.contracts[c].name, $contractEl)); } } $legend = $('') @@ -59,10 +60,17 @@ UniversalDApp.prototype.render = function () { return this.$el; } +UniversalDApp.prototype.getContractByName = function(contractName) { + for (var c in this.contracts) + if (this.contracts[c].name == contractName) + return this.contracts[c]; + return null; +}; + UniversalDApp.prototype.getABIInputForm = function (cb){ var self = this; var $el = $(''); - var $jsonInput = $('') + var $jsonInput = $('') var $createButton = $('').text('Create a Universal ÐApp') $createButton.click(function(ev){ var contracts = $.parseJSON( $jsonInput.val() ); @@ -157,6 +165,7 @@ UniversalDApp.prototype.getInstanceInterface = function (contract, address, $tar if (!address || !$target) { $createInterface.append( this.getCallButton({ abi: funABI, + contractName: contract.name, bytecode: contract.bytecode, appendFunctions: appendFunctions })); @@ -179,7 +188,7 @@ UniversalDApp.prototype.getConstructorInterface = function(abi) { UniversalDApp.prototype.getCallButton = function(args) { var self = this; - // args.abi, args.bytecode [constr only], args.address [fun only] + // args.abi, args.contractName [constr only], args.bytecode, args.address [fun only] // args.appendFunctions [constr only] var isConstructor = args.bytecode !== undefined; var lookupOnly = ( args.abi.constant && !isConstructor ); @@ -212,40 +221,65 @@ UniversalDApp.prototype.getCallButton = function(args) { } var getOutput = function() { - var values = Array.prototype.slice.call(arguments); var $result = $(''); var $close = $(''); $close.click( function(){ $result.remove(); } ); $result.append( $close ); - for( var v in values ) { $result.append( values[v] ); } return $result; - } - - var handleCallButtonClick = function( ev ) { + }; + var clearOutput = function($result) { + $(':not(.udapp-close)', $result).remove(); + }; + var replaceOutput = function($result, message) { + clearOutput($result); + $result.append(message); + }; + + var handleCallButtonClick = function(ev, $result) { 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); - - var $result = getOutput( $('Polling for tx receipt...') ); - if (lookupOnly && !inputs.length) { - $outputOverride.empty().append( $result ); - } else { - outputSpan.append( $result ); + if (!$result) { + $result = getOutput(); + if (lookupOnly && !inputs.length) + $outputOverride.empty().append( $result ); + else + outputSpan.append( $result ); + } + replaceOutput($result, $('Waiting for transaction to be mined...')); + + if (isConstructor) { + if (args.bytecode.indexOf('_') >= 0) { + replaceOutput($result, $('Deploying and linking required libraries...')); + if (self.options.vm) + self.linkBytecode(args.contractName, function(err, bytecode) { + if (err) + replaceOutput($result, $('').text('Error deploying required libraries: ' + err)); + else { + args.bytecode = bytecode; + handleCallButtonClick(ev, $result); + } + }); + else + replaceOutput($result, $('Contract needs to be linked to a library, this is only supported in the JavaScript VM for now.')); + return; + } else + data = args.bytecode + data.slice(8); } self.runTx(data, args, function(err, result) { if (err) { - $result.replaceWith( getOutput( $('').text(err).addClass('error') ) ); + replaceOutput($result, $('').text(err).addClass('error')); } else if (self.options.vm && isConstructor) { - $result.replaceWith( getOutput( getGasUsedOutput( result ) ) ); + replaceOutput($result, getGasUsedOutput(result)); args.appendFunctions(result.createdAddress); } else if (self.options.vm){ var outputObj = fun.unpackOutput('0x' + result.vm.return.toString('hex')); - $result.replaceWith( getOutput( getReturnOutput( outputObj ), getGasUsedOutput( result.vm ) ) ); + clearOutput($result); + $result.append(getReturnOutput(outputObj)).append(getGasUsedOutput(result.vm)); } else if (args.abi.constant && !isConstructor) { - $result.replaceWith( getOutput( getReturnOutput( result ) ) ); + replaceOutput($result, getReturnOutput(result)); } else { function tryTillResponse (txhash, done) { @@ -262,7 +296,10 @@ UniversalDApp.prototype.getCallButton = function(args) { if (isConstructor) { $result.html(''); args.appendFunctions(result.contractAddress); - } else $result.replaceWith( getOutput( getReturnOutput( result ), getGasUsedOutput( result ) ) ); + } else { + clearOutput($result); + $result.append(getReturnOutput(result)).append(getGasUsedOutput(result)); + } }) } @@ -289,6 +326,49 @@ UniversalDApp.prototype.getCallButton = function(args) { return $contractProperty.append(outputSpan); } +UniversalDApp.prototype.linkBytecode = function(contractName, cb) { + var bytecode = this.getContractByName(contractName).bytecode; + if (bytecode.indexOf('_') < 0) + return cb(null, bytecode); + var m = bytecode.match(/__([^_]{1,36})__/); + if (!m) + return cb("Invalid bytecode format."); + var libraryName = m[1]; + if (!this.getContractByName(contractName)) + return cb("Library " + libraryName + " not found."); + var self = this; + this.deployLibrary(libraryName, function(err, address) { + if (err) return cb(err); + var libLabel = '__' + libraryName + Array(39 - libraryName.length).join('_'); + var hexAddress = address.toString('hex'); + if (hexAddress.slice(0, 2) == '0x') hexAddress = hexAddress.slice(2); + hexAddress = Array(40 - hexAddress.length + 1).join('0') + hexAddress; + while (bytecode.indexOf(libLabel) >= 0) + bytecode = bytecode.replace(libLabel, hexAddress); + self.getContractByName(contractName).bytecode = bytecode; + self.linkBytecode(contractName, cb); + }); +}; + +UniversalDApp.prototype.deployLibrary = function(contractName, cb) { + if (this.getContractByName(contractName).address) + return cb(null, this.getContractByName(contractName).address); + var self = this; + var bytecode = this.getContractByName(contractName).bytecode; + if (bytecode.indexOf('_') >= 0) + this.linkBytecode(contractName, function(err, bytecode) { + if (err) cb(err); + else self.deployLibrary(contractName, cb); + }); + else { + this.runTx(bytecode, {abi: {constant: false}, bytecode: bytecode}, function(err, result) { + if (err) return cb(err); + self.getContractByName(contractName).address = result.createdAddress; + cb(err, result.createdAddress); + }); + } +}; + UniversalDApp.prototype.clickNewContract = function ( self, $contract, contract ) { $contract.append( self.getInstanceInterface(contract) ); } diff --git a/stylesheets/browser-solidity.css b/stylesheets/browser-solidity.css index 1040eaf84c..6a67373938 100644 --- a/stylesheets/browser-solidity.css +++ b/stylesheets/browser-solidity.css @@ -126,19 +126,15 @@ body { float: left; } -.udapp.hide { +.contract.hide { padding-bottom: 0; } -.udapp.hide > *:not(.contract) { - display: none; -} - -.udapp.hide .contract { +.contract.hide { margin: 0; } -.udapp.hide .contract > *:not(.title) { +.contract.hide > *:not(.title) { display: none; } @@ -153,7 +149,7 @@ body { font-size: 10px; } -.udapp.hide > .contract > .title:before { +.contract.hide > .title:before { content: "\25B6"; } diff --git a/stylesheets/universal-dapp.css b/stylesheets/universal-dapp.css index e05c89ec2b..a99e6506e4 100644 --- a/stylesheets/universal-dapp.css +++ b/stylesheets/universal-dapp.css @@ -43,6 +43,7 @@ .udapp .contract { margin-bottom: 1em; + clear: both; } .udapp .create {