|
|
|
@ -2,6 +2,7 @@ function UniversalDApp (contracts, options) { |
|
|
|
|
this.options = options || {}; |
|
|
|
|
this.$el = $('<div class="udapp" />'); |
|
|
|
|
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 = $('<div class="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 = $('<div class="udapp-setup" />'); |
|
|
|
|
var $jsonInput = $('<textarea class="json" placeholder=\'[ { "name": name, "bytecode": bytyecode, "interface": abi }, { ... } ]\'/>') |
|
|
|
|
var $jsonInput = $('<textarea class="json" placeholder=\'[ { "name": name, "bytecode": bytecode, "interface": abi }, { ... } ]\'/>') |
|
|
|
|
var $createButton = $('<button class="udapp-create"/>').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 = $('<div class="result" />'); |
|
|
|
|
var $close = $('<div class="udapp-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( $('<a class="waiting" href="#" title="Waiting for transaction to be mined.">Polling for tx receipt...</a>') ); |
|
|
|
|
|
|
|
|
|
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, $('<span>Waiting for transaction to be mined...</span>')); |
|
|
|
|
|
|
|
|
|
if (isConstructor) { |
|
|
|
|
if (args.bytecode.indexOf('_') >= 0) { |
|
|
|
|
replaceOutput($result, $('<span>Deploying and linking required libraries...</span>')); |
|
|
|
|
if (self.options.vm) |
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
else |
|
|
|
|
replaceOutput($result, $('<span>Contract needs to be linked to a library, this is only supported in the JavaScript VM for now.</span>')); |
|
|
|
|
return; |
|
|
|
|
} else |
|
|
|
|
data = args.bytecode + data.slice(8); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.runTx(data, args, function(err, result) { |
|
|
|
|
if (err) { |
|
|
|
|
$result.replaceWith( getOutput( $('<span/>').text(err).addClass('error') ) ); |
|
|
|
|
replaceOutput($result, $('<span/>').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) ); |
|
|
|
|
} |
|
|
|
|