remove unneeded code

pull/1/head
yann300 7 years ago
parent e3a83e4299
commit 38b8de7138
  1. 321
      src/app/renderer.js
  2. 776
      src/universal-dapp.js

@ -3,73 +3,18 @@
var $ = require('jquery') var $ = require('jquery')
var utils = require('./utils') var utils = require('./utils')
var helper = require('../lib/helper.js')
// -------------- styling ---------------------- /**
var csjs = require('csjs-inject') * After refactor, the renderer is only used to render error/warning
var styleGuide = require('./style-guide') * TODO: This don't need to be an object anymore. Simplify and just export the renderError function.
var styles = styleGuide() *
*/
var css = csjs` function Renderer (appAPI) {
.col2 {
width: 70%;
float: left;
}
.col1 extends ${styles.titleL} {
width: 30%;
float: left;
}
.toggleText {
text-decoration: underline;
margin-left: 2px;
font-size: .9em;
}
.toggle {
font-size: 1.1em;
color: ${styles.colors.blue};
margin: 1em;
cursor: pointer;
font-weight: 400;
display: flex;
align-items: center;
}
.toggle:hover {
opacity: .8;
}
`
// ----------------------------------------------
function Renderer (appAPI, compilerEvent) {
this.appAPI = appAPI this.appAPI = appAPI
var self = this
compilerEvent.register('compilationFinished', this, function (success, data, source) {
$('#output').empty()
if (success) {
self.contracts(data, source)
$('#header #menu .envView').css('color', '')
} else {
// envView is the `Contract` tab, as a refactor the entire envView should have his own module
$('#header #menu .envView').css('color', '#FF8B8B')
}
// NOTE: still need to display as there might be warnings
if (data['error']) {
self.error(data['error'])
}
if (data['errors']) {
data['errors'].forEach(function (err) {
self.error(err)
})
}
})
setInterval(() => { updateAccountBalances(self, appAPI) }, 1000)
}
Renderer.prototype.clear = function () {
$('#output').empty()
} }
Renderer.prototype.error = function (message, container, options) { Renderer.prototype.error = function (message, container, options) {
if (container === undefined) return
var self = this var self = this
var opt = options || {} var opt = options || {}
if (!opt.type) { if (!opt.type) {
@ -82,9 +27,6 @@ Renderer.prototype.error = function (message, container, options) {
$pre = $(opt.useSpan ? '<span />' : '<pre />').text(message) $pre = $(opt.useSpan ? '<span />' : '<pre />').text(message)
} }
var $error = $('<div class="sol ' + opt.type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre) var $error = $('<div class="sol ' + opt.type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre)
if (container === undefined) {
container = $('#output')
}
container.append($error) container.append($error)
var err = message.match(/^([^:]*):([0-9]*):(([0-9]*):)? /) var err = message.match(/^([^:]*):([0-9]*):(([0-9]*):)? /)
if (err) { if (err) {
@ -110,253 +52,4 @@ Renderer.prototype.error = function (message, container, options) {
}) })
} }
Renderer.prototype.contracts = function (data, source) {
var self = this
var retrieveMetadataHash = function (bytecode) {
var match = /a165627a7a72305820([0-9a-f]{64})0029$/.exec(bytecode)
if (match) {
return match[1]
}
}
var udappContracts = []
for (var contractName in data.contracts) {
var contract = data.contracts[contractName]
udappContracts.push({
name: contractName,
interface: contract['interface'],
bytecode: contract.bytecode,
metadata: contract.metadata,
metadataHash: contract.bytecode && retrieveMetadataHash(contract.bytecode)
})
}
var tableRowItems = function (first, second, cls) {
second.get(0).classList.add(styles.textBoxL) // replace <pre> styling with textBoxL
return $('<div class="crow"/>')
.addClass(cls)
.append($(`<div class="${css.col1}">`).append(first))
.append($(`<div class="${css.col2}">`).append(second))
}
var tableRow = function (description, data) {
return tableRowItems(
$('<span/>').text(description),
$(`<input class="${css.col2} ${styles.textBoxL}" readonly="readonly"/>`).val(data))
}
var preRow = function (description, data) {
return tableRowItems(
$('<span/>').text(description),
$('<pre/>').text(data)
)
}
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
}
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 gethDeploy = function (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'
})
contractName = contractName.replace(/[:./]/g, '_')
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: '0x" + bytecode + "', " +
"\n gas: '" + self.appAPI.currentblockGasLimit() + "'" +
'\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
}
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 detailsOpen = {}
var getDetails = function (contract, source, contractName) {
var button = $(`<div class="${css.toggle}"><i class="fa fa-info-circle" aria-hidden="true"></i><div class="${css.toggleText}">Contract details (bytecode, interface etc.)</div></div>`)
var details = $('<div style="display: none;"/>')
if (contract.bytecode) {
details.append(preRow('Bytecode', contract.bytecode))
}
details.append(preRow('Interface', contract['interface']))
if (contract.bytecode) {
details.append(preRow('Web3 deploy', gethDeploy(contractName.toLowerCase(), contract['interface'], contract.bytecode), 'deploy'))
// check if there's a metadata hash appended
var metadataHash = retrieveMetadataHash(contract.bytecode)
if (metadataHash) {
details.append(preRow('Metadata location', 'bzzr://' + metadataHash))
}
}
if (contract.metadata) {
details.append(preRow('Metadata', contract.metadata))
}
var funHashes = ''
for (var fun in contract.functionHashes) {
funHashes += contract.functionHashes[fun] + ' ' + fun + '\n'
}
if (funHashes.length > 0) {
details.append(preRow('Functions', funHashes))
}
var gasEstimates = formatGasEstimates(contract.gasEstimates)
if (gasEstimates) {
details.append(preRow('Gas Estimates', gasEstimates))
}
if (contract.runtimeBytecode && contract.runtimeBytecode.length > 0) {
details.append(tableRow('Runtime Bytecode', contract.runtimeBytecode))
}
if (contract.opcodes !== undefined && contract.opcodes !== '') {
details.append(tableRow('Opcodes', contract.opcodes))
}
if (contract.assembly !== null) {
details.append(preRow('Assembly', formatAssemblyText(contract.assembly, '', source)))
}
button.click(function () {
detailsOpen[contractName] = !detailsOpen[contractName]
details.toggle()
})
if (detailsOpen[contractName]) {
details.show()
}
return $('<div class="contractDetails"/>').append(button).append(details)
}
var renderOutputModifier = function (contractName, $contractOutput) {
var contract = data.contracts[contractName]
var ctrSource = self.appAPI.currentCompiledSourceCode()
if (ctrSource) {
$contractOutput.append(getDetails(contract, ctrSource, contractName))
}
return $contractOutput
}
this.appAPI.resetDapp(udappContracts, renderOutputModifier)
var $contractOutput = this.appAPI.renderDapp()
var $txOrigin = $('#txorigin')
this.appAPI.getAccounts(function (err, accounts) {
if (err) {
self.error(err.message)
}
if (accounts && accounts[0]) {
$txOrigin.empty()
for (var a in accounts) { $txOrigin.append($('<option />').val(accounts[a]).text(accounts[a])) }
$txOrigin.val(accounts[0])
} else {
$txOrigin.val('unknown')
}
})
$('#output').append($contractOutput)
$('.' + css.col2 + ' input,textarea').click(function () { this.select() })
}
function updateAccountBalances (self, appAPI) {
var accounts = $('#txorigin').children('option')
accounts.each(function (index, value) {
(function (acc) {
appAPI.getBalance(accounts[acc].value, function (err, res) {
if (!err) {
accounts[acc].innerText = helper.shortenAddress(accounts[acc].value, res)
}
})
})(index)
})
}
module.exports = Renderer module.exports = Renderer

@ -1,4 +1,4 @@
/* global prompt */ /* global alert */
'use strict' 'use strict'
var $ = require('jquery') var $ = require('jquery')
@ -9,8 +9,11 @@ var EventManager = require('ethereum-remix').lib.EventManager
var crypto = require('crypto') var crypto = require('crypto')
var async = require('async') var async = require('async')
var TxRunner = require('./app/txRunner') var TxRunner = require('./app/txRunner')
var helper = require('./lib/helper')
var yo = require('yo-yo') var yo = require('yo-yo')
var txFormat = require('./app/execution/txFormat')
var txHelper = require('./app/execution/txHelper')
var txExecution = require('./app/execution/txExecution')
var helper = require('./lib/helper')
// copy to copyToClipboard // copy to copyToClipboard
const copy = require('clipboard-copy') const copy = require('clipboard-copy')
@ -21,88 +24,73 @@ var styleGuide = require('./app/style-guide')
var styles = styleGuide() var styles = styleGuide()
var css = csjs` var css = csjs`
html { .instanceTitleContainer {
overflow: hidden; display: flex;
} align-items: center;
.options {
float: left;
padding: 0.7em 0.3em;
font-size: 0.9em;
cursor: pointer;
background-color: transparent;
margin-right: 0.5em;
font-size: 1em;
}
.title extends ${styles.titleBox} {
cursor: pointer;
background-color: ${styles.colors.violet};
justify-content: flex-start;
min-width: 400px;
}
.contract .title:before {
margin-right: 30%;
margin-left: 5%;
content: "\\25BE";
}
.contract.hidesub .title:before {
margin-right: 30%;
margin-left: 5%;
content: "\\25B8";
}
.contract.hidesub {
padding-bottom: 0;
margin: 0;
}
.contract.hidesub > *:not(.title) {
display: none;
}
.size {
margin-left: 20%;
} }
` .title extends ${styles.dropdown} {
var cssInstance = csjs`
.title {
display: flex; display: flex;
justify-content: space-around;
min-width: 400px;
align-items: center; align-items: center;
margin-bottom: 1em; justify-content: space-between;
padding: .3em; height: 32px;
font-size: .95em; font-size: 11px;
cursor: pointer; width: 100%;
background-color: ${styles.colors.violet}; overflow: hidden;
border: 2px dotted ${styles.colors.blue}; word-break: break-word;
border-radius: 5px; line-height: initial;
font-weight: bold;
background-color: ${styles.colors.white};
} }
.titleText { .titleText {
margin-right: 1em; margin-right: 1em;
word-break: break-all; word-break: break-word;
min-width: 230px;
}
.instance extends ${styles.displayBox} {
padding: 10px 15px 6px 15px;
} }
.instance .title:before { .instance .title:before {
content: "\\25BE"; content: "\\25BE";
margin-right: .5em; margin-right: 5%;
margin-left: .5em;
} }
.instance.hidesub .title:before { .instance.hidesub .title:before {
content: "\\25B8"; content: "\\25B8";
margin-right: .5em; margin-right: 5%;
margin-left: .5em;
} }
.instance.hidesub { .instance.hidesub {
padding-bottom: 0;
margin: 0; margin: 0;
} }
.instance.hidesub > *:not(.title) { .instance.hidesub > * {
display: none; display: none;
} }
.copy extends ${styles.button} { .instance.hidesub .title {
border: 1px dotted ${styles.colors.grey}; display: flex;
padding: 0 .3em; }
font-weight: bold; .copy {
font-size: 13px;
cursor: pointer;
opacity: 0.8;
margin-left: 3%;
color: ${styles.colors.black};
opacity: .5;
} }
.copy:hover{ .copy:hover{
opacity: .7; opacity: 1;
}
.buttonsContainer {
margin-top: 2%;
display: flex;
}
.instanceButton {}
.closeIcon {
font-size: 10px;
position: relative;
top: -5px;
right: -2px;
}
.udappClose {
margin-left: 3%;
align-self: center;
} }
` `
@ -114,12 +102,10 @@ function UniversalDApp (executionContext, options) {
var self = this var self = this
self.options = options || {} self.options = options || {}
self.$el = $('<div class="udapp" />') self.el = yo`<div class="udapp"></div>`
self.personalMode = self.options.personalMode || false self.personalMode = self.options.personalMode || false
self.contracts self.contracts
self.transactionContextAPI self.transactionContextAPI
var defaultRenderOutputModifier = function (name, content) { return content }
self.renderOutputModifier = defaultRenderOutputModifier
self.web3 = executionContext.web3() self.web3 = executionContext.web3()
self.vm = executionContext.vm() self.vm = executionContext.vm()
self.executionContext = executionContext self.executionContext = executionContext
@ -132,11 +118,10 @@ function UniversalDApp (executionContext, options) {
}) })
} }
UniversalDApp.prototype.reset = function (contracts, transactionContextAPI, renderer) { UniversalDApp.prototype.reset = function (contracts, transactionContextAPI) {
this.$el.empty() this.el.innerHTML = ''
this.contracts = contracts this.contracts = contracts
this.transactionContextAPI = transactionContextAPI this.transactionContextAPI = transactionContextAPI
this.renderOutputModifier = renderer
this.accounts = {} this.accounts = {}
if (this.executionContext.isVM()) { if (this.executionContext.isVM()) {
this._addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511') this._addAccount('3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511')
@ -234,609 +219,182 @@ UniversalDApp.prototype.getBalance = function (address, cb) {
} }
} }
UniversalDApp.prototype.render = function () { // TODO this function was named before "appendChild".
var self = this // this will render an instance: contract name, contract address, and all the public functions
// basically this has to be called for the "atAddress" (line 393) and when a contract creation succeed
// NOTE: don't display anything if there are no contracts to display // this returns a DOM element
if (self.contracts.length === 0) { UniversalDApp.prototype.renderInstance = function (contract, address, contractName) {
return self.$el function remove () { $instance.remove() }
} var $instance = $(`<div class="instance ${css.instance}"/>`)
var context = this.executionContext.isVM() ? 'memory' : 'blockchain'
var $legend = $('<div class="legend" />')
.append($('<div class="publish"/>').text('Publish'))
.append($('<div class="attach"/>').text('Attach'))
.append($('<div class="transact"/>').text('Transact'))
.append($('<div class="payable"/>').text('Transact(Payable)'))
.append($('<div class="call"/>').text('Call'))
self.$el.append($legend)
for (var c in self.contracts) {
var $contractEl = $(`<div class="contract ${css.contract}"/>`)
if (self.contracts[c].address) {
self.getInstanceInterface(self.contracts[c], self.contracts[c].address, $contractEl)
} else {
var $title = $(`<span class="${css.title}"/>`).text(self.contracts[c].name)
$title.click(function (ev) { $(this).closest(`.${css.contract}`).toggleClass(`${css.hidesub}`) })
if (self.contracts[c].bytecode) {
$title.append($(`<div class="${css.size}"></div>`).text((self.contracts[c].bytecode.length / 2) + ' bytes'))
}
$contractEl.append($title).append(self.getCreateInterface($contractEl, self.contracts[c]))
}
self.$el.append(self.renderOutputModifier(self.contracts[c].name, $contractEl))
}
return self.$el
}
UniversalDApp.prototype.getContractByName = function (contractName) {
var self = this
for (var c in self.contracts) {
if (self.contracts[c].name === contractName) {
return self.contracts[c]
}
}
return null
}
UniversalDApp.prototype.getCreateInterface = function ($container, contract) {
function remove () {
self.$el.remove()
}
var self = this
var createInterface = yo`<div class="create"></div>`
if (self.options.removable) {
var close = yo`<div class="udapp-close" onclick=${remove}></div>`
createInterface.appendChild(close)
}
var $publishButton = $(`<button class="publishContract"/>`).text('Publish').click(function () { self.event.trigger('publishContract', [contract]) })
createInterface.appendChild($publishButton.get(0))
var $atButton = $('<button class="atAddress"/>').text('At Address').click(function () { self.clickContractAt(self, $container.find('.createContract'), contract) })
createInterface.appendChild($atButton.get(0))
var $newButton = self.getInstanceInterface(contract)
if (!$newButton) {
return createInterface
}
createInterface.appendChild($newButton)
// Only display creation interface for non-abstract contracts. address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
// FIXME: maybe have a flag for this in the JSON? var shortAddress = helper.shortenAddress(address)
// FIXME: maybe fix getInstanceInterface() below for this case var title = yo`<div class="${css.title}" onclick=${toggleClass}>
if (contract.bytecode.length === 0) { <div class="${css.titleText}"> ${contractName} at ${shortAddress} (${context}) </div>
var $createButton = $newButton.querySelector('.constructor .call') <i class="fa fa-clipboard ${css.copy}" aria-hidden="true" onclick=${copyToClipboard} title='Copy to clipboard'></i>
</div>`
// NOTE: we must show the button to have CSS properly lined up if (this.options.removable_instances) {
$createButton.innerText = 'Create' var close = yo`<div class="${css.udappClose}" onclick=${remove}><i class="${css.closeIcon} fa fa-close" aria-hidden="true"></i></div>`
$createButton.setAttribute('disabled', 'disabled') title.appendChild(close)
$createButton.setAttribute('title', 'This contract does not implement all functions and thus cannot be created.')
} }
if ((contract.metadata === undefined) || (contract.metadata.length === 0)) { function toggleClass () {
$publishButton.attr('disabled', 'disabled') $instance.toggleClass(`${css.hidesub}`)
$publishButton.attr('title', 'This contract does not implement all functions and thus cannot be published.')
} }
return createInterface function copyToClipboard (event) {
} event.stopPropagation()
copy(address)
UniversalDApp.prototype.getInstanceInterface = function (contract, address, $target) {
var self = this
var abi = JSON.parse(contract.interface).sort(function (a, b) {
if (a.name > b.name) {
return -1
} else {
return 1
}
}).sort(function (a, b) {
if (a.constant === true) {
return -1
} else {
return 1
}
})
var funABI = self.getConstructorInterface(abi)
if (!funABI) {
return
} }
var createInterface = yo`<div class="createContract"></div>` var $events = $('<div class="events"/>')
var appendFunctions = function (address, $el) {
if ($el) $el = $el.get(0)
function remove () { $instance.remove() }
var $instance = $(`<div class="instance ${cssInstance.instance}"/>`)
if (self.options.removable_instances) {
var close = yo`<div class="udapp-close" onclick=${remove}></div>`
$instance.get(0).appendChild(close)
}
var context = self.executionContext.isVM() ? 'memory' : 'blockchain'
address = (address.slice(0, 2) === '0x' ? '' : '0x') + address.toString('hex')
var shortAddress = helper.shortenAddress(address)
var title = yo`
<div class="${cssInstance.title}" onclick=${toggleClass}>
<div class="${cssInstance.titleText}"> ${contract.name} at ${shortAddress} (${context}) </div>
<div class="${cssInstance.copy}" onclick=${copyToClipboard}> <i class="fa fa-clipboard" aria-hidden="true"></i> Copy address </div>
</div>
`
function toggleClass () {
$instance.toggleClass(`${cssInstance.hidesub}`)
}
function copyToClipboard (event) {
event.stopPropagation()
copy(address)
}
var $events = $('<div class="events"/>')
var parseLogs = function (err, response) {
if (err) {
return
}
var $event = $('<div class="event" />') var parseLogs = function (err, response) {
if (err) {
var close = yo`<div class="udapp-close" onclick=${remove}></div>` return
function remove () { $event.remove() }
$event.append($('<span class="name"/>').text(response.event))
.append($('<span class="args" />').text(JSON.stringify(response.args, null, 2)))
$event.get(0).appendChild(close)
$events.append($event)
} }
if (self.executionContext.isVM()) { var $event = $('<div class="event" />')
// FIXME: support indexed events
var eventABI = {}
$.each(abi, function (i, funABI) { var close = yo`<div class="udapp-close" onclick=${remove}><i class="${css.closeIcon} fa fa-close" aria-hidden="true"></i></div>`
if (funABI.type !== 'event') { function remove () { $event.remove() }
return
}
var hash = ethJSABI.eventID(funABI.name, funABI.inputs.map(function (item) { return item.type }))
eventABI[hash.toString('hex')] = { event: funABI.name, inputs: funABI.inputs }
})
self.vm.on('afterTx', function (response) { $event.append($('<span class="name"/>').text(response.event))
for (var i in response.vm.logs) { .append($('<span class="args" />').text(JSON.stringify(response.args, null, 2)))
// [address, topics, mem] $event.get(0).appendChild(close)
var log = response.vm.logs[i] $events.append($event)
var event }
var decoded
try {
var abi = eventABI[log[1][0].toString('hex')]
event = abi.event
var types = abi.inputs.map(function (item) {
return item.type
})
decoded = ethJSABI.rawDecode(types, log[2])
decoded = ethJSABI.stringify(types, decoded)
} catch (e) {
decoded = '0x' + log[2].toString('hex')
}
parseLogs(null, { event: event, args: decoded })
}
})
} else {
var eventFilter = self.web3.eth.contract(abi).at(address).allEvents()
eventFilter.watch(parseLogs)
}
$instance.get(0).appendChild(title)
// Add the fallback function
var fallback = self.getFallbackInterface(abi)
if (fallback) {
$instance.append(self.getCallButton({
abi: fallback,
encode: function (args) {
return ''
},
address: address
}))
}
var abi = txHelper.sortAbiFunction(contract)
if (this.executionContext.isVM()) {
// FIXME: support indexed events
var eventABI = {}
$.each(abi, function (i, funABI) { $.each(abi, function (i, funABI) {
if (funABI.type !== 'function') { if (funABI.type !== 'event') {
return return
} }
// @todo getData cannot be used with overloaded functions
$instance.append(self.getCallButton({
abi: funABI,
encode: function (args) {
var types = []
for (var i = 0; i < funABI.inputs.length; i++) {
types.push(funABI.inputs[i].type)
}
return Buffer.concat([ ethJSABI.methodID(funABI.name, types), ethJSABI.rawEncode(types, args) ]).toString('hex') var hash = ethJSABI.eventID(funABI.name, funABI.inputs.map(function (item) { return item.type }))
}, eventABI[hash.toString('hex')] = { event: funABI.name, inputs: funABI.inputs }
address: address
}))
}) })
$el = $el || createInterface this.vm.on('afterTx', function (response) {
$el.appendChild($instance.append($events).get(0)) for (var i in response.vm.logs) {
} // [address, topics, mem]
var log = response.vm.logs[i]
var event
var decoded
if (!address || !$target) { try {
createInterface.appendChild(self.getCallButton({ var abi = eventABI[log[1][0].toString('hex')]
abi: funABI, event = abi.event
encode: function (args) { var types = abi.inputs.map(function (item) {
var types = [] return item.type
for (var i = 0; i < funABI.inputs.length; i++) { })
types.push(funABI.inputs[i].type) decoded = ethJSABI.rawDecode(types, log[2])
decoded = ethJSABI.stringify(types, decoded)
} catch (e) {
decoded = '0x' + log[2].toString('hex')
} }
// NOTE: the caller will concatenate the bytecode and this parseLogs(null, { event: event, args: decoded })
// it could be done here too for consistency }
return ethJSABI.rawEncode(types, args).toString('hex') })
},
contractName: contract.name,
bytecode: contract.bytecode,
appendFunctions: appendFunctions
}).get(0))
} else { } else {
appendFunctions(address, $target) var eventFilter = this.web3.eth.contract(abi).at(address).allEvents()
eventFilter.watch(parseLogs)
} }
return createInterface $instance.get(0).appendChild(title)
}
UniversalDApp.prototype.getConstructorInterface = function (abi) { // Add the fallback function
for (var i = 0; i < abi.length; i++) { var fallback = txHelper.getFallbackInterface(abi)
if (abi[i].type === 'constructor') { if (fallback) {
return abi[i] $instance.append(this.getCallButton({
} funABI: fallback,
address: address,
contractAbi: abi
}))
} }
return { 'type': 'constructor', 'payable': false, 'inputs': [] } $.each(abi, (i, funABI) => {
} if (funABI.type !== 'function') {
return
UniversalDApp.prototype.getFallbackInterface = function (abi) {
for (var i = 0; i < abi.length; i++) {
if (abi[i].type === 'fallback') {
return abi[i]
} }
} // @todo getData cannot be used with overloaded functions
$instance.append(this.getCallButton({
funABI: funABI,
address: address,
contractAbi: abi
}))
})
$instance.append($events)
return $instance.get(0)
} }
// TODO this is used by renderInstance when a new instance is displayed.
// this returns a DOM element.
UniversalDApp.prototype.getCallButton = function (args) { UniversalDApp.prototype.getCallButton = function (args) {
var self = this var self = this
// args.abi, args.encode, args.bytecode [constr only], args.address [fun only] // args.funABI, args.address [fun only]
// args.contractName [constr only], args.appendFunctions [constr only] // args.contractName [constr only]
var isConstructor = args.bytecode !== undefined var lookupOnly = args.funABI.constant
var lookupOnly = (args.abi.constant && !isConstructor)
var inputs = '' var inputs = ''
if (args.abi.inputs) { if (args.funABI.inputs) {
$.each(args.abi.inputs, function (i, inp) { inputs = txHelper.inputParametersDeclarationToString(args.funABI.inputs)
if (inputs !== '') {
inputs += ', '
}
inputs += inp.type + ' ' + inp.name
})
} }
var inputField = $('<input/>').attr('placeholder', inputs).attr('title', inputs) var inputField = $('<input/>').attr('placeholder', inputs).attr('title', inputs)
var $outputOverride = $('<div class="value" />') var $outputOverride = $('<div class="value" />')
var outputSpan = $('<div class="output"/>')
var getReturnOutput = function (result) {
var returnName = lookupOnly ? 'Value' : 'Result'
var returnCls = lookupOnly ? 'value' : 'returned'
return $('<div class="' + returnCls + '">').html('<strong>' + returnName + ':</strong> ' + JSON.stringify(result, null, 2))
}
var getDebugTransaction = function (result) {
var $debugTx = $('<div class="debugTx">')
var $button = $('<button title="Launch Debugger" class="debug"><i class="fa fa-bug"></i> Launch debugger </button>')
$button.click(function () {
self.event.trigger('debugRequested', [result])
})
$debugTx.append($button)
return $debugTx
}
var getDebugCall = function (result) {
var $debugTx = $('<div class="debugCall">')
var $button = $('<button title="Launch Debugger" class="debug"><i class="fa fa-bug"></i> Launch debugger </button>')
$button.click(function () {
self.event.trigger('debugRequested', [result])
})
$debugTx.append($button)
return $debugTx
}
var getGasUsedOutput = function (result, vmResult) {
var $gasUsed = $('<div class="gasUsed"></div>')
var caveat = lookupOnly ? '<em>(<span class="caveat" title="Cost only applies when called by a contract">caveat</span>)</em>' : ''
var gas
if (result.gasUsed) {
gas = result.gasUsed.toString(10)
$gasUsed.html('<strong>Transaction cost:</strong> ' + gas + ' gas. ' + caveat)
}
if (vmResult && vmResult.gasUsed) {
var $callGasUsed = $('<div class="gasUsed">')
gas = vmResult.gasUsed.toString(10)
$callGasUsed.append('<strong>Execution cost:</strong> ' + gas + ' gas.')
$gasUsed.append($callGasUsed)
}
return $gasUsed
}
var getDecodedOutput = function (result) {
var $decoded
if (Array.isArray(result)) {
$decoded = $('<ol>')
for (var i = 0; i < result.length; i++) {
$decoded.append($('<li>').text(result[i]))
}
} else {
$decoded = result
}
return $('<div class="decoded">').html('<strong>Decoded:</strong> ').append($decoded)
}
var getOutput = function () {
var $result = $('<div class="result" />')
var close = yo`<div class="udapp-close" onclick=${remove}></div>`
function remove () { $result.remove() }
$result.get(0).appendChild(close)
return $result
}
var clearOutput = function ($result) {
$(':not(.udapp-close)', $result).remove()
}
var replaceOutput = function ($result, message) {
clearOutput($result)
$result.append(message)
}
var handleCallButtonClick = function (ev, $result) {
if (!$result) {
$result = getOutput()
if (lookupOnly && !inputs.length) {
$outputOverride.empty().append($result)
} else {
outputSpan.append($result)
}
}
var funArgs = ''
try {
funArgs = $.parseJSON('[' + inputField.val() + ']')
} catch (e) {
replaceOutput($result, $('<span/>').text('Error encoding arguments: ' + e))
return
}
var data = ''
if (!isConstructor || funArgs.length > 0) {
try {
data = args.encode(funArgs)
} catch (e) {
replaceOutput($result, $('<span/>').text('Error encoding arguments: ' + e))
return
}
}
if (data.slice(0, 9) === 'undefined') {
data = data.slice(9)
}
if (data.slice(0, 2) === '0x') {
data = data.slice(2)
}
replaceOutput($result, $('<span>Waiting for transaction to be mined...</span>'))
if (isConstructor) {
if (args.bytecode.indexOf('_') >= 0) {
replaceOutput($result, $('<span>Deploying and linking required libraries...</span>'))
self.linkBytecode(args.contractName, function (err, bytecode) {
if (err) {
replaceOutput($result, $('<span/>').text('Error deploying required libraries: ' + err))
} else {
args.bytecode = bytecode
handleCallButtonClick(ev, $result)
}
})
return
} else {
data = args.bytecode + data
}
}
var decodeResponse = function (response) {
// Only decode if there supposed to be fields
if (args.abi.outputs && args.abi.outputs.length > 0) {
try {
var i
var outputTypes = []
for (i = 0; i < args.abi.outputs.length; i++) {
outputTypes.push(args.abi.outputs[i].type)
}
// decode data
var decodedObj = ethJSABI.rawDecode(outputTypes, response)
// format decoded data
decodedObj = ethJSABI.stringify(outputTypes, decodedObj)
for (i = 0; i < outputTypes.length; i++) {
var name = args.abi.outputs[i].name
if (name.length > 0) {
decodedObj[i] = outputTypes[i] + ' ' + name + ': ' + decodedObj[i]
} else {
decodedObj[i] = outputTypes[i] + ': ' + decodedObj[i]
}
}
return getDecodedOutput(decodedObj)
} catch (e) {
return getDecodedOutput('Failed to decode output: ' + e)
}
}
}
var decoded
self.runTx({ to: args.address, data: data, useCall: lookupOnly }, function (err, txResult) {
self.event.trigger('transactionExecuted', [args.address, data, lookupOnly, txResult])
if (!txResult) {
replaceOutput($result, $('<span/>').text('callback contain no result ' + err).addClass('error'))
return
}
var result = txResult.result
if (err) {
replaceOutput($result, $('<span/>').text(err).addClass('error'))
// VM only
} else if (self.executionContext.isVM() && result.vm.exception === 0 && result.vm.exceptionError) {
replaceOutput($result, $('<span/>').text('Exception during execution. (' + result.vm.exceptionError + '). Please debug the transaction for more information.').addClass('error'))
$result.append(getDebugTransaction(txResult))
// VM only
} else if (self.executionContext.isVM() && result.vm.return === undefined) {
replaceOutput($result, $('<span/>').text('Exception during execution. Please debug the transaction for more information.').addClass('error'))
$result.append(getDebugTransaction(txResult))
} else if (isConstructor) {
replaceOutput($result, getGasUsedOutput(result, result.vm))
$result.append(getDebugTransaction(txResult))
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))
decoded = decodeResponse(result.vm.return)
if (decoded) {
$result.append(decoded)
}
if (lookupOnly) {
$result.append(getDebugCall(txResult))
} else {
$result.append(getDebugTransaction(txResult))
}
} else if (lookupOnly) {
clearOutput($result)
$result.append(getReturnOutput(result)).append(getGasUsedOutput({}))
decoded = decodeResponse(ethJSUtil.toBuffer(result))
if (decoded) {
$result.append(decoded)
}
} else {
clearOutput($result)
$result.append(getReturnOutput(result)).append(getGasUsedOutput(result))
$result.append(getDebugTransaction(txResult))
}
})
}
var title var title
if (isConstructor) { if (args.funABI.name) {
title = 'Create' title = args.funABI.name
} else if (args.abi.name) {
title = args.abi.name
} else { } else {
title = '(fallback)' title = '(fallback)'
} }
var button = $('<button />') var button = $(`<button class="${css.instanceButton}"/>`)
.addClass('call') .addClass('call')
.attr('title', title) .attr('title', title)
.text(title) .text(title)
.click(handleCallButtonClick) .click(() => {
txFormat.buildData(args.contractAbi, self.contracts, false, args.funABI, inputField.val(), self, self.executionContext, (error, data) => {
if (!error) {
txExecution.callFunction(args.address, data, args.funABI, self, (error, txResult) => {
// TODO here should send the result to the dom-console
console.log('function call', error, txResult)
alert(error + ' ' + txResult.transactionHash)
})
} else {
alert(error)
}
})
})
if (lookupOnly && !inputs.length) { // TODO the auto call to constant function has been removed. needs to readd it later.
handleCallButtonClick()
}
var $contractProperty = $('<div class="contractProperty"/>') var $contractProperty = $(`<div class="contractProperty ${css.buttonsContainer}"></div>`)
$contractProperty $contractProperty
.append(button) .append(button)
.append((lookupOnly && !inputs.length) ? $outputOverride : inputField) .append((lookupOnly && !inputs.length) ? $outputOverride : inputField)
if (isConstructor) {
$contractProperty.addClass('constructor')
}
if (lookupOnly) { if (lookupOnly) {
$contractProperty.addClass('constant') $contractProperty.addClass('constant')
} }
if (args.abi.inputs && args.abi.inputs.length > 0) { if (args.funABI.inputs && args.funABI.inputs.length > 0) {
$contractProperty.addClass('hasArgs') $contractProperty.addClass('hasArgs')
} }
if (args.abi.payable === true) { if (args.funABI.payable === true) {
$contractProperty.addClass('payable') $contractProperty.addClass('payable')
} }
return $contractProperty.append(outputSpan) return $contractProperty
}
UniversalDApp.prototype.linkBytecode = function (contractName, cb) {
var self = this
var bytecode = self.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 (!self.getContractByName(libraryName)) {
return cb('Library ' + libraryName + ' not found.')
}
self.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) {
var self = this
if (self.getContractByName(contractName).address) {
return cb(null, self.getContractByName(contractName).address)
}
var bytecode = self.getContractByName(contractName).bytecode
if (bytecode.indexOf('_') >= 0) {
self.linkBytecode(contractName, function (err, bytecode) {
if (err) cb(err)
else self.deployLibrary(contractName, cb)
})
} else {
self.runTx({ data: bytecode, useCall: false }, function (err, txResult) {
if (err) {
return cb(err)
}
var address = self.executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress
self.getContractByName(contractName).address = address
cb(err, address)
})
}
}
UniversalDApp.prototype.clickContractAt = function (self, $output, contract) {
var address = prompt('What Address is this contract at in the Blockchain? ie: 0xdeadbeaf...')
self.getInstanceInterface(contract, address, $output)
} }
UniversalDApp.prototype.runTx = function (args, cb) { UniversalDApp.prototype.runTx = function (args, cb) {

Loading…
Cancel
Save