diff --git a/src/app.js b/src/app.js
index b2ffe72169..87379f67a3 100644
--- a/src/app.js
+++ b/src/app.js
@@ -359,13 +359,16 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
registry.put({api: self._components.compilersArtefacts, name: 'compilersartefacts'})
// ----------------- UniversalDApp -----------------
- var udapp = new UniversalDApp({
- removable: false,
- removable_instances: true
- })
+ var udapp = new UniversalDApp(registry)
+ // TODO: to remove when possible
registry.put({api: udapp, name: 'udapp'})
+ udapp.event.register('transactionBroadcasted', (txhash, networkName) => {
+ var txLink = executionContext.txDetailsLink(networkName, txhash)
+ if (txLink) registry.get('logCallback').api.logCallback(yo`${txLink} `)
+ })
- var udappUI = new UniversalDAppUI(udapp)
+ var udappUI = new UniversalDAppUI(udapp, registry)
+ // TODO: to remove when possible
registry.put({api: udappUI, name: 'udappUI'})
// ----------------- Tx listener -----------------
diff --git a/src/app/execution/confirmDialog.js b/src/app/execution/confirmDialog.js
index c8921ac3a8..c139cbf6c1 100644
--- a/src/app/execution/confirmDialog.js
+++ b/src/app/execution/confirmDialog.js
@@ -16,6 +16,7 @@ var css = csjs`
}
`
+// TODO: self is not actually used and can be removed
function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialParamsCb) {
var onGasPriceChange = function () {
var gasPrice = el.querySelector('#gasprice').value
diff --git a/src/app/files/compiler-metadata.js b/src/app/files/compiler-metadata.js
index 3790651c3a..59959aae9e 100644
--- a/src/app/files/compiler-metadata.js
+++ b/src/app/files/compiler-metadata.js
@@ -74,6 +74,7 @@ class CompilerMetadata {
return metadata
}
+ // TODO: is only called by dropdownLogic and can be moved there
deployMetadataOf (contractName, callback) {
var self = this
var provider = self._opts.fileManager.currentFileProvider()
diff --git a/src/app/tabs/run-tab.js b/src/app/tabs/run-tab.js
index 4067e69e63..d624fc894d 100644
--- a/src/app/tabs/run-tab.js
+++ b/src/app/tabs/run-tab.js
@@ -1,42 +1,28 @@
-'use strict'
var $ = require('jquery')
var yo = require('yo-yo')
-var remixLib = require('remix-lib')
-var ethJSUtil = require('ethereumjs-util')
-var csjs = require('csjs-inject')
-var txExecution = remixLib.execution.txExecution
-var txFormat = remixLib.execution.txFormat
-var txHelper = remixLib.execution.txHelper
var EventManager = require('../../lib/events')
var globlalRegistry = require('../../global/registry')
-var helper = require('../../lib/helper.js')
var executionContext = require('../../execution-context')
-var modalDialogCustom = require('../ui/modal-dialog-custom')
-var copyToClipboard = require('../ui/copy-to-clipboard')
-const Buffer = require('safe-buffer').Buffer
-var Personal = require('web3-eth-personal')
var Card = require('../ui/card')
-var Recorder = require('../../recorder')
-var addTooltip = require('../ui/tooltip')
var css = require('./styles/run-tab-styles')
-var MultiParamManager = require('../../multiParamManager')
-var modalDialog = require('../ui/modaldialog')
-var CompilerAbstract = require('../compiler/compiler-abstract')
-var tootip = require('../ui/tooltip')
+
+var Settings = require('./runTab/model/settings.js')
+var SettingsUI = require('./runTab/settings.js')
+
+var DropdownLogic = require('./runTab/model/dropdownlogic.js')
+var ContractDropdownUI = require('./runTab/contractDropdown.js')
+
+var Recorder = require('./runTab/model/recorder.js')
+var RecorderUI = require('./runTab/recorder.js')
function runTab (opts, localRegistry) {
- /* -------------------------
- VARIABLES
- --------------------------- */
var self = this
self.event = new EventManager()
self._view = {}
self.data = {
count: 0,
- text: `All transactions (deployed contracts and function executions)
- in this environment can be saved and replayed in
- another environment. e.g Transactions created in
- Javascript VM can be replayed in the Injected Web3.`
+ text: `All transactions (deployed contracts and function executions) in this environment can be saved and replayed in
+ another environment. e.g Transactions created in Javascript VM can be replayed in the Injected Web3.`
}
self._components = {}
self._components.registry = localRegistry || globlalRegistry
@@ -51,14 +37,8 @@ function runTab (opts, localRegistry) {
var index = select.selectedIndex
var selectedUnit = select.querySelectorAll('option')[index].dataset.unit
var unit = 'ether' // default
- if (selectedUnit === 'ether') {
- unit = 'ether'
- } else if (selectedUnit === 'finney') {
- unit = 'finney'
- } else if (selectedUnit === 'gwei') {
- unit = 'gwei'
- } else if (selectedUnit === 'wei') {
- unit = 'wei'
+ if (['ether', 'finney', 'gwei', 'wei'].indexOf(selectedUnit) >= 0) {
+ unit = selectedUnit
}
cb(null, executionContext.web3().toWei(number, unit))
} catch (e) {
@@ -100,7 +80,21 @@ function runTab (opts, localRegistry) {
`
var container = yo`
@@ -135,620 +129,56 @@ function runTab (opts, localRegistry) {
status.appendChild(self._view.collapsedView)
}
})
- /* -------------------------
- MAIN HTML ELEMENT
- --------------------------- */
- var el = yo`
-
- ${settings(container, self)}
- ${contractDropdown(self.event, self)}
- ${recorderCard.render()}
- ${self._view.instanceContainer}
-
- `
- container.appendChild(el)
- return { render () { return container } }
-}
+ var settings = new Settings(self._deps.udapp)
+ var settingsUI = new SettingsUI(settings)
-var accountListCallId = 0
-var loadedAccounts = {}
-function fillAccountsList (container, self) {
- accountListCallId++
- (function (callid) {
- var txOrigin = container.querySelector('#txorigin')
- self._deps.udapp.getAccounts((err, accounts) => {
- if (accountListCallId > callid) return
- accountListCallId++
- if (err) { addTooltip(`Cannot get account list: ${err}`) }
- for (var loadedaddress in loadedAccounts) {
- if (accounts.indexOf(loadedaddress) === -1) {
- txOrigin.removeChild(txOrigin.querySelector('option[value="' + loadedaddress + '"]'))
- delete loadedAccounts[loadedaddress]
- }
- }
- for (var i in accounts) {
- var address = accounts[i]
- if (!loadedAccounts[address]) {
- txOrigin.appendChild(yo`
${address} `)
- loadedAccounts[address] = 1
- }
- }
- txOrigin.setAttribute('value', accounts[0])
- })
- })(accountListCallId)
-}
-
-function updateAccountBalances (container, self) {
- var accounts = $(container.querySelector('#txorigin')).children('option')
- accounts.each(function (index, value) {
- (function (acc) {
- self._deps.udapp.getBalanceInEther(accounts[acc].value, function (err, res) {
- if (!err) {
- accounts[acc].innerText = helper.shortenAddress(accounts[acc].value, res)
- }
- })
- })(index)
- })
-}
-
-/* ------------------------------------------------
- RECORDER
------------------------------------------------- */
-function makeRecorder (registry, runTabEvent, self) {
- var recorder = new Recorder(self._deps.udapp, self._deps.logCallback)
-
- recorder.event.register('newTxRecorded', (count) => {
- self.data.count = count
- self._view.recorderCount.innerText = count
+ self.event.register('clearInstance', () => {
+ this._view.instanceContainer.innerHTML = '' // clear the instances list
+ this._view.instanceContainer.appendChild(self._view.instanceContainerTitle)
+ this._view.instanceContainer.appendChild(self._view.noInstancesText)
})
- recorder.event.register('cleared', () => {
- self.data.count = 0
- self._view.recorderCount.innerText = 0
+ settingsUI.event.register('clearInstance', () => {
+ this.event.trigger('clearInstance', [])
})
- executionContext.event.register('contextChanged', () => {
- recorder.clearAll()
+ var dropdownLogic = new DropdownLogic(
+ this._deps.fileManager,
+ this._deps.pluginManager,
+ this._deps.compilersArtefacts,
+ this._deps.compiler,
+ this._deps.config,
+ this._deps.editor,
+ this._deps.udapp,
+ this._deps.filePanel
+ )
+ var contractDropdownUI = new ContractDropdownUI(dropdownLogic, this._deps.logCallback)
+
+ contractDropdownUI.event.register('clearInstance', () => {
+ var noInstancesText = this._view.noInstancesText
+ if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
})
-
- runTabEvent.register('clearInstance', () => {
- recorder.clearAll()
+ contractDropdownUI.event.register('newContractABIAdded', (abi, address) => {
+ this._view.instanceContainer.appendChild(this._deps.udappUI.renderInstanceFromABI(abi, address, address))
})
-
- var css2 = csjs`
- .container {}
- .runTxs {}
- .recorder {}
- `
-
- var runButton = yo`
`
- var recordButton = yo`
-
- `
-
- function triggerRecordButton () {
- var txJSON = JSON.stringify(recorder.getAll(), null, 2)
- var fileManager = self._deps.fileManager
- var path = fileManager.currentPath()
- modalDialogCustom.prompt(null, 'Transactions will be saved in a file under ' + path, 'scenario.json', input => {
- var fileProvider = fileManager.fileProviderOf(path)
- if (fileProvider) {
- var newFile = path + '/' + input
- helper.createNonClashingName(newFile, fileProvider, (error, newFile) => {
- if (error) return modalDialogCustom.alert('Failed to create file. ' + newFile + ' ' + error)
- if (!fileProvider.set(newFile, txJSON)) {
- modalDialogCustom.alert('Failed to create file ' + newFile)
- } else {
- fileManager.switchFile(newFile)
- }
- })
- }
- })
- }
-
- runButton.onclick = () => {
- /*
- @TODO
- update account address in scenario.json
- popup if scenario.json not open - "Open a file with transactions you want to replay and click play again"
- */
- var currentFile = self._deps.config.get('currentFile')
- self._deps.fileManager.fileProviderOf(currentFile).get(currentFile, (error, json) => {
- if (error) {
- modalDialogCustom.alert('Invalid Scenario File ' + error)
- } else {
- if (currentFile.match('.json$')) {
- try {
- var obj = JSON.parse(json)
- var txArray = obj.transactions || []
- var accounts = obj.accounts || []
- var options = obj.options || {}
- var abis = obj.abis || {}
- var linkReferences = obj.linkReferences || {}
- } catch (e) {
- return modalDialogCustom.alert('Invalid Scenario File, please try again')
- }
- if (txArray.length) {
- var noInstancesText = self._view.noInstancesText
- if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
- recorder.run(txArray, accounts, options, abis, linkReferences, self._deps.udapp, (abi, address, contractName) => {
- self._view.instanceContainer.appendChild(self._deps.udappUI.renderInstanceFromABI(abi, address, contractName))
- })
- }
- } else {
- modalDialogCustom.alert('A scenario file is required. Please make sure a scenario file is currently displayed in the editor. The file must be of type JSON. Use the "Save Transactions" Button to generate a new Scenario File.')
- }
- }
- })
- }
-
- return { recordButton, runButton }
-}
-/* ------------------------------------------------
- CONTRACT (deploy or access deployed)
------------------------------------------------- */
-
-function contractDropdown (events, self) {
- var instanceContainer = self._view.instanceContainer
- var instanceContainerTitle = self._view.instanceContainerTitle
- instanceContainer.appendChild(instanceContainerTitle)
- instanceContainer.appendChild(self._view.noInstancesText)
- var compFails = yo`
`
- var info = yo`
`
-
- var newlyCompiled = (success, data, source, compiler, compilerFullName) => {
- getContractNames(success, data, compiler, compilerFullName)
- if (success) {
- compFails.style.display = 'none'
- document.querySelector(`.${css.contractNames}`).classList.remove(css.contractNamesError)
- } else {
- compFails.style.display = 'block'
- document.querySelector(`.${css.contractNames}`).classList.add(css.contractNamesError)
- }
- }
-
- self._deps.pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => {
- let compiler = new CompilerAbstract(languageVersion, data, source)
- newlyCompiled(true, data, source, compiler, languageVersion)
- })
-
- var deployAction = (value) => {
- self._view.createPanel.style.display = value
- self._view.orLabel.style.display = value
- }
-
- self._deps.fileManager.event.register('currentFileChanged', (currentFile) => {
- document.querySelector(`.${css.contractNames}`).classList.remove(css.contractNamesError)
- var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`)
- contractNames.innerHTML = ''
- if (/.(.abi)$/.exec(currentFile)) {
- deployAction('none')
- compFails.style.display = 'none'
- contractNames.appendChild(yo`
(abi) `)
- selectContractNames.setAttribute('disabled', true)
- } else if (/.(.sol)$/.exec(currentFile)) {
- deployAction('block')
- }
+ contractDropdownUI.event.register('newContractInstanceAdded', (contractObject, address, value) => {
+ this._view.instanceContainer.appendChild(this._deps.udappUI.renderInstance(contractObject, address, value))
})
- var atAddressButtonInput = yo`
`
- var selectContractNames = yo`
`
-
- function getSelectedContract () {
- var contract = selectContractNames.children[selectContractNames.selectedIndex]
- var contractName = contract.innerHTML
- var compiler = self._deps.compilersArtefacts['__last']
- if (!compiler) return null
-
- if (contractName) {
- return {
- name: contractName,
- contract: compiler.getContract(contractName),
- compiler
- }
- }
- return null
- }
-
- self._view.createPanel = yo`
`
- self._view.orLabel = yo`
or
`
- var el = yo`
-
-
- ${selectContractNames} ${compFails} ${info}
-
-
- ${self._view.createPanel}
- ${self._view.orLabel}
-
-
-
- `
-
- function setInputParamsPlaceHolder () {
- self._view.createPanel.innerHTML = ''
- if (selectContractNames.selectedIndex >= 0 && selectContractNames.children.length > 0) {
- var selectedContract = getSelectedContract()
- var ctrabi = txHelper.getConstructorInterface(selectedContract.contract.object.abi)
- var ctrEVMbc = selectedContract.contract.object.evm.bytecode.object
- var createConstructorInstance = new MultiParamManager(0, ctrabi, (valArray, inputsValues) => {
- createInstance(inputsValues, selectedContract.compiler)
- }, txHelper.inputParametersDeclarationToString(ctrabi.inputs), 'Deploy', ctrEVMbc)
- self._view.createPanel.appendChild(createConstructorInstance.render())
- return
- } else {
- self._view.createPanel.innerHTML = 'No compiled contracts'
- }
- }
-
- selectContractNames.addEventListener('change', setInputParamsPlaceHolder)
-
- function createInstanceCallback (selectedContract, data) {
- self._deps.logCallback(`creation of ${selectedContract.name} pending...`)
- if (data) {
- data.contractName = selectedContract.name
- data.linkReferences = selectedContract.contract.object.evm.bytecode.linkReferences
- data.contractABI = selectedContract.contract.object.abi
- }
- self._deps.udapp.createContract(data, (error, txResult) => {
- if (!error) {
- var isVM = executionContext.isVM()
- if (isVM) {
- var vmError = txExecution.checkVMError(txResult)
- if (vmError.error) {
- self._deps.logCallback(vmError.message)
- return
- }
- }
- if (txResult.result.status && txResult.result.status === '0x0') {
- self._deps.logCallback(`creation of ${selectedContract.name} errored: transaction execution failed`)
- return
- }
- var noInstancesText = self._view.noInstancesText
- if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
- var address = isVM ? txResult.result.createdAddress : txResult.result.contractAddress
- instanceContainer.appendChild(self._deps.udappUI.renderInstance(selectedContract.contract.object, address, selectContractNames.value))
- } else {
- self._deps.logCallback(`creation of ${selectedContract.name} errored: ${error}`)
- }
- })
- }
-
- // DEPLOY INSTANCE
- function createInstance (args, compiler) {
- var selectedContract = getSelectedContract()
+ this._view.instanceContainer.appendChild(this._view.instanceContainerTitle)
+ this._view.instanceContainer.appendChild(this._view.noInstancesText)
- if (selectedContract.contract.object.evm.bytecode.object.length === 0) {
- modalDialogCustom.alert('This contract may be abstract, not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.')
- return
- }
-
- var forceSend = () => {
- var constructor = txHelper.getConstructorInterface(selectedContract.contract.object.abi)
- self._deps.filePanel.compilerMetadata().deployMetadataOf(selectedContract.name, (error, contractMetadata) => {
- if (error) return self._deps.logCallback(`creation of ${selectedContract.name} errored: ` + error)
- if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) {
- txFormat.buildData(selectedContract.name, selectedContract.contract.object, compiler.getContracts(), true, constructor, args, (error, data) => {
- if (error) return self._deps.logCallback(`creation of ${selectedContract.name} errored: ` + error)
- createInstanceCallback(selectedContract, data)
- }, (msg) => {
- self._deps.logCallback(msg)
- }, (data, runTxCallback) => {
- // called for libraries deployment
- self._deps.udapp.runTx(data, runTxCallback)
- })
- } else {
- if (Object.keys(selectedContract.contract.object.evm.bytecode.linkReferences).length) self._deps.logCallback(`linking ${JSON.stringify(selectedContract.contract.object.evm.bytecode.linkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`)
- txFormat.encodeConstructorCallAndLinkLibraries(selectedContract.contract.object, args, constructor, contractMetadata.linkReferences, selectedContract.contract.object.evm.bytecode.linkReferences, (error, data) => {
- if (error) return self._deps.logCallback(`creation of ${selectedContract.name} errored: ` + error)
- createInstanceCallback(selectedContract, data)
- })
- }
- })
- }
-
- if (selectedContract.contract.object.evm.deployedBytecode && selectedContract.contract.object.evm.deployedBytecode.object.length / 2 > 24576) {
- modalDialog('Contract code size over limit', yo`
Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails.
- More info:
eip-170
-
`,
- {
- label: 'Force Send',
- fn: () => {
- forceSend()
- }}, {
- label: 'Cancel',
- fn: () => {
- self._deps.logCallback(`creation of ${selectedContract.name} canceled by user.`)
- }
- })
- } else {
- forceSend()
- }
- }
-
- // ACCESS DEPLOYED INSTANCE
- function loadFromAddress () {
- var noInstancesText = self._view.noInstancesText
- if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
- var address = atAddressButtonInput.value
- if (!ethJSUtil.isValidAddress(address)) {
- return modalDialogCustom.alert('Invalid address.')
- }
- if (/[a-f]/.test(address) && /[A-F]/.test(address) && !ethJSUtil.isValidChecksumAddress(address)) {
- return modalDialogCustom.alert('Invalid checksum address.')
- }
- if (/.(.abi)$/.exec(self._deps.config.get('currentFile'))) {
- modalDialogCustom.confirm(null, 'Do you really want to interact with ' + address + ' using the current ABI definition ?', () => {
- var abi
- try {
- abi = JSON.parse(self._deps.editor.currentContent())
- } catch (e) {
- return modalDialogCustom.alert('Failed to parse the current file as JSON ABI.')
- }
- instanceContainer.appendChild(self._deps.udappUI.renderInstanceFromABI(abi, address, address))
- })
- } else {
- var selectedContract = getSelectedContract()
- instanceContainer.appendChild(self._deps.udappUI.renderInstance(selectedContract.contract.object, address, selectContractNames.value))
- }
- }
-
- // GET NAMES OF ALL THE CONTRACTS
- function getContractNames (success, data, compiler, compilerFullName) {
- var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`)
- contractNames.innerHTML = ''
- if (success) {
- selectContractNames.removeAttribute('disabled')
- compiler.visitContracts((contract) => {
- contractNames.appendChild(yo`
${contract.name} `)
- })
- } else {
- selectContractNames.setAttribute('disabled', true)
- }
- setInputParamsPlaceHolder()
- }
-
- return el
-}
-/* ------------------------------------------------
- section SETTINGS: Environment, Account, Gas, Value
------------------------------------------------- */
-function settings (container, self) {
- // VARIABLES
- var net = yo`
`
- var networkcallid = 0
- const updateNetwork = (cb) => {
- networkcallid++
- (function (callid) {
- executionContext.detectNetwork((err, { id, name } = {}) => {
- if (networkcallid > callid) return
- networkcallid++
- if (err) {
- console.error(err)
- net.innerHTML = 'can\'t detect network '
- } else {
- net.innerHTML = `
${name} (${id || '-'})`
- }
- if (cb) cb(err, {id, name})
- })
- })(networkcallid)
- }
- var environmentEl = yo`
-
-
- Environment
-
-
- ${net}
-
{ updateNetwork() }} class="${css.select}">
- JavaScript VM
-
- Injected Web3
-
- Web3 Provider
-
-
-
-
-
- `
- var accountEl = yo`
-
-
- Account
-
-
-
-
- ${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)}
-
-
-
- `
- var gasPriceEl = yo`
-
- `
- var valueEl = yo`
-
-
Value
-
-
- wei
- gwei
- finney
- ether
-
-
- `
- // DOM ELEMENT
var el = yo`
-
- ${environmentEl}
- ${accountEl}
- ${gasPriceEl}
- ${valueEl}
-
+
+ ${settingsUI.render()}
+ ${contractDropdownUI.render()}
+ ${recorderCard.render()}
+ ${self._view.instanceContainer}
+
`
- // HELPER FUNCTIONS AND EVENTS
- self._deps.udapp.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => {
- if (error) return
- if (!lookupOnly) el.querySelector('#value').value = '0'
- updateAccountBalances(container, self)
- })
-
- // DROPDOWN
- var selectExEnv = environmentEl.querySelector('#selectExEnvOptions')
-
- function setFinalContext () {
- // set the final context. Cause it is possible that this is not the one we've originaly selected
- selectExEnv.value = executionContext.getProvider()
- self.event.trigger('clearInstance', [])
- updateNetwork()
- fillAccountsList(el, self)
- }
-
- self.event.register('clearInstance', () => {
- var instanceContainer = self._view.instanceContainer
- var instanceContainerTitle = self._view.instanceContainerTitle
- instanceContainer.innerHTML = '' // clear the instances list
- instanceContainer.appendChild(instanceContainerTitle)
- instanceContainer.appendChild(self._view.noInstancesText)
- })
-
- executionContext.event.register('addProvider', (network) => {
- selectExEnv.appendChild(yo`
${network.name}
- `)
- tootip(`${network.name} [${network.url}] added`)
- })
-
- executionContext.event.register('removeProvider', (name) => {
- var env = selectExEnv.querySelector(`option[value="${name}"]`)
- if (env) {
- selectExEnv.removeChild(env)
- tootip(`${name} removed`)
- }
- })
-
- selectExEnv.addEventListener('change', function (event) {
- let context = selectExEnv.options[selectExEnv.selectedIndex].value
- executionContext.executionContextChange(context, null, () => {
- modalDialogCustom.confirm(null, 'Are you sure you want to connect to an ethereum node?', () => {
- modalDialogCustom.prompt(null, 'Web3 Provider Endpoint', 'http://localhost:8545', (target) => {
- executionContext.setProviderFromEndpoint(target, context, (alertMsg) => {
- if (alertMsg) {
- modalDialogCustom.alert(alertMsg)
- }
- setFinalContext()
- })
- }, setFinalContext)
- }, setFinalContext)
- }, (alertMsg) => {
- modalDialogCustom.alert(alertMsg)
- }, setFinalContext)
- })
-
- selectExEnv.value = executionContext.getProvider()
- executionContext.event.register('contextChanged', (context, silent) => {
- setFinalContext()
- })
-
- setInterval(() => {
- updateNetwork()
- fillAccountsList(el, self)
- }, 5000)
-
- setInterval(() => {
- updateAccountBalances(container, self)
- }, 10000)
-
- function newAccount () {
- self._deps.udapp.newAccount('', (error, address) => {
- if (!error) {
- addTooltip(`account ${address} created`)
- } else {
- addTooltip('Cannot create an account: ' + error)
- }
- })
- }
- function signMessage (event) {
- self._deps.udapp.getAccounts((err, accounts) => {
- if (err) { addTooltip(`Cannot get account list: ${err}`) }
- var signMessageDialog = { 'title': 'Sign a message', 'text': 'Enter a message to sign', 'inputvalue': 'Message to sign' }
- var $txOrigin = container.querySelector('#txorigin')
- var account = $txOrigin.selectedOptions[0].value
- var isVM = executionContext.isVM()
- var isInjected = executionContext.getProvider() === 'injected'
- function alertSignedData (error, hash, signedData) {
- if (error && error.message !== '') {
- console.log(error)
- addTooltip(error.message)
- } else {
- modalDialogCustom.alert(yo`
hash: ${hash}signature: ${signedData}
`)
- }
- }
- if (isVM) {
- modalDialogCustom.promptMulti(signMessageDialog, (message) => {
- const personalMsg = ethJSUtil.hashPersonalMessage(Buffer.from(message))
- var privKey = self._deps.udapp.accounts[account].privateKey
- try {
- var rsv = ethJSUtil.ecsign(personalMsg, privKey)
- var signedData = ethJSUtil.toRpcSig(rsv.v, rsv.r, rsv.s)
- alertSignedData(null, '0x' + personalMsg.toString('hex'), signedData)
- } catch (e) {
- addTooltip(e.message)
- return
- }
- }, false)
- } else if (isInjected) {
- modalDialogCustom.promptMulti(signMessageDialog, (message) => {
- const hashedMsg = executionContext.web3().sha3(message)
- try {
- executionContext.web3().eth.sign(account, hashedMsg, (error, signedData) => {
- alertSignedData(error, hashedMsg, signedData)
- })
- } catch (e) {
- addTooltip(e.message)
- console.log(e)
- return
- }
- })
- } else {
- modalDialogCustom.promptPassphrase('Passphrase to sign a message', 'Enter your passphrase for this account to sign the message', '', (passphrase) => {
- modalDialogCustom.promptMulti(signMessageDialog, (message) => {
- const hashedMsg = executionContext.web3().sha3(message)
- try {
- var personal = new Personal(executionContext.web3().currentProvider)
- personal.sign(hashedMsg, account, passphrase, (error, signedData) => {
- alertSignedData(error, hashedMsg, signedData)
- })
- } catch (e) {
- addTooltip(e.message)
- console.log(e)
- return
- }
- })
- }, false)
- }
- })
- }
+ container.appendChild(el)
- return el
+ return { render () { return container } }
}
module.exports = runTab
diff --git a/src/app/tabs/runTab/contractDropdown.js b/src/app/tabs/runTab/contractDropdown.js
new file mode 100644
index 0000000000..5d4fd60aec
--- /dev/null
+++ b/src/app/tabs/runTab/contractDropdown.js
@@ -0,0 +1,200 @@
+var yo = require('yo-yo')
+var css = require('../styles/run-tab-styles')
+var modalDialogCustom = require('../../ui/modal-dialog-custom')
+var remixLib = require('remix-lib')
+var EventManager = remixLib.EventManager
+var confirmDialog = require('../../execution/confirmDialog')
+var modalDialog = require('../../ui/modaldialog')
+var MultiParamManager = require('../../../multiParamManager')
+
+class ContractDropdownUI {
+ constructor (dropdownLogic, logCallback) {
+ this.dropdownLogic = dropdownLogic
+ this.logCallback = logCallback
+ this.event = new EventManager()
+
+ this.listenToEvents()
+ }
+
+ listenToEvents () {
+ this.dropdownLogic.event.register('newlyCompiled', (success, data, source, compiler, compilerFullName) => {
+ var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`)
+ contractNames.innerHTML = ''
+ if (success) {
+ this.selectContractNames.removeAttribute('disabled')
+ this.dropdownLogic.getCompiledContracts(compiler, compilerFullName).forEach((contract) => {
+ contractNames.appendChild(yo`
${contract.name} `)
+ })
+ } else {
+ this.selectContractNames.setAttribute('disabled', true)
+ }
+ this.setInputParamsPlaceHolder()
+
+ if (success) {
+ this.compFails.style.display = 'none'
+ document.querySelector(`.${css.contractNames}`).classList.remove(css.contractNamesError)
+ } else {
+ this.compFails.style.display = 'block'
+ document.querySelector(`.${css.contractNames}`).classList.add(css.contractNamesError)
+ }
+ })
+
+ this.dropdownLogic.event.register('currentFileChanged', this.changeCurrentFile.bind(this))
+ }
+
+ render () {
+ this.compFails = yo`
`
+ var info = yo`
`
+
+ this.atAddressButtonInput = yo`
`
+ this.selectContractNames = yo`
`
+
+ this.createPanel = yo`
`
+ this.orLabel = yo`
or
`
+ var el = yo`
+
+
+ ${this.selectContractNames} ${this.compFails} ${info}
+
+
+ ${this.createPanel}
+ ${this.orLabel}
+
+
+
+ `
+ this.selectContractNames.addEventListener('change', this.setInputParamsPlaceHolder.bind(this))
+
+ return el
+ }
+
+ changeCurrentFile (currentFile) {
+ document.querySelector(`.${css.contractNames}`).classList.remove(css.contractNamesError)
+ var contractNames = document.querySelector(`.${css.contractNames.classNames[0]}`)
+ contractNames.innerHTML = ''
+ if (/.(.abi)$/.exec(currentFile)) {
+ this.createPanel.style.display = 'none'
+ this.orLabel.style.display = 'none'
+ this.compFails.style.display = 'none'
+ contractNames.appendChild(yo`
(abi) `)
+ this.selectContractNames.setAttribute('disabled', true)
+ } else if (/.(.sol)$/.exec(currentFile)) {
+ this.createPanel.style.display = 'block'
+ this.orLabel.style.display = 'block'
+ }
+ }
+
+ setInputParamsPlaceHolder () {
+ this.createPanel.innerHTML = ''
+ if (this.selectContractNames.selectedIndex < 0 || this.selectContractNames.children.length <= 0) {
+ this.createPanel.innerHTML = 'No compiled contracts'
+ return
+ }
+
+ var selectedContract = this.getSelectedContract()
+ var createConstructorInstance = new MultiParamManager(0, selectedContract.getConstructorInterface(), (valArray, inputsValues) => {
+ this.createInstance(inputsValues)
+ }, selectedContract.getConstructorInputs(), 'Deploy', selectedContract.bytecodeObject)
+ this.createPanel.appendChild(createConstructorInstance.render())
+ }
+
+ getSelectedContract () {
+ var contract = this.selectContractNames.children[this.selectContractNames.selectedIndex]
+ var contractName = contract.innerHTML
+ var compilerAtributeName = contract.getAttribute('compiler')
+
+ return this.dropdownLogic.getSelectedContract(contractName, compilerAtributeName)
+ }
+
+ createInstance (args) {
+ var selectedContract = this.getSelectedContract()
+
+ if (selectedContract.bytecodeObject.length === 0) {
+ return modalDialogCustom.alert('This contract may be abstract, not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.')
+ }
+
+ var continueCb = (error, continueTxExecution, cancelCb) => {
+ if (error) {
+ var msg = typeof error !== 'string' ? error.message : error
+ modalDialog('Gas estimation failed', yo`
Gas estimation errored with the following message (see below).
+ The transaction execution will likely fail. Do you want to force sending?
+ ${msg}
+
`,
+ {
+ label: 'Send Transaction',
+ fn: () => {
+ continueTxExecution()
+ }}, {
+ label: 'Cancel Transaction',
+ fn: () => {
+ cancelCb()
+ }
+ })
+ } else {
+ continueTxExecution()
+ }
+ }
+
+ var promptCb = (okCb, cancelCb) => {
+ modalDialogCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
+ }
+
+ var statusCb = (msg) => {
+ return this.logCallback(msg)
+ }
+
+ var finalCb = (error, contractObject, address) => {
+ this.event.trigger('clearInstance')
+
+ if (error) {
+ return this.logCallback(error)
+ }
+
+ this.event.trigger('newContractInstanceAdded', [contractObject, address, this.selectContractNames.value])
+ }
+
+ if (selectedContract.isOverSizeLimit()) {
+ return modalDialog('Contract code size over limit', yo`
Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails.
+ More info:
eip-170
+
`,
+ {
+ label: 'Force Send',
+ fn: () => {
+ this.dropdownLogic.forceSend(selectedContract, args, continueCb, promptCb, modalDialogCustom, confirmDialog, statusCb, finalCb)
+ }}, {
+ label: 'Cancel',
+ fn: () => {
+ this.logCallback(`creation of ${selectedContract.name} canceled by user.`)
+ }
+ })
+ }
+ this.dropdownLogic.forceSend(selectedContract, args, continueCb, promptCb, modalDialogCustom, confirmDialog, statusCb, finalCb)
+ }
+
+ loadFromAddress () {
+ this.event.trigger('clearInstance')
+
+ var address = this.atAddressButtonInput.value
+ this.dropdownLogic.loadContractFromAddress(address,
+ (cb) => {
+ modalDialogCustom.confirm(null, 'Do you really want to interact with ' + address + ' using the current ABI definition ?', cb)
+ },
+ (error, loadType, abi) => {
+ if (error) {
+ return modalDialogCustom.alert(error)
+ }
+ if (loadType === 'abi') {
+ return this.event.trigger('newContractABIAdded', [abi, address])
+ }
+ var selectedContract = this.getSelectedContract()
+ this.event.trigger('newContractInstanceAdded', [selectedContract.object, address, this.selectContractNames.value])
+ }
+ )
+ }
+
+}
+
+module.exports = ContractDropdownUI
diff --git a/src/app/tabs/runTab/model/dropdownlogic.js b/src/app/tabs/runTab/model/dropdownlogic.js
new file mode 100644
index 0000000000..f05b5a9ef8
--- /dev/null
+++ b/src/app/tabs/runTab/model/dropdownlogic.js
@@ -0,0 +1,295 @@
+var ethJSUtil = require('ethereumjs-util')
+var remixLib = require('remix-lib')
+var txHelper = remixLib.execution.txHelper
+var txFormat = remixLib.execution.txFormat
+var executionContext = remixLib.execution.executionContext
+var typeConversion = remixLib.execution.typeConversion
+var txExecution = remixLib.execution.txExecution
+var CompilerAbstract = require('../../../compiler/compiler-abstract')
+var EventManager = remixLib.EventManager
+
+class DropdownLogic {
+ constructor (fileManager, pluginManager, compilersArtefacts, compiler, config, editor, udapp, filePanel) {
+ this.pluginManager = pluginManager
+ this.compilersArtefacts = compilersArtefacts
+ this.compiler = compiler
+ this.config = config
+ this.editor = editor
+ this.udapp = udapp
+ this.filePanel = filePanel
+
+ this.event = new EventManager()
+
+ this.listenToCompilationEvents()
+
+ fileManager.event.register('currentFileChanged', (currentFile) => {
+ this.event.trigger('currentFileChanged', [currentFile])
+ })
+ }
+
+ listenToCompilationEvents () {
+ this.pluginManager.event.register('sendCompilationResult', (file, source, languageVersion, data) => {
+ // TODO check whether the tab is configured
+ let compiler = new CompilerAbstract(languageVersion, data, source)
+ this.compilersArtefacts[languageVersion] = compiler
+ this.compilersArtefacts['__last'] = compiler
+ this.event.trigger('newlyCompiled', [true, data, source, compiler, languageVersion])
+ })
+ }
+
+ loadContractFromAddress (address, confirmCb, cb) {
+ if (!ethJSUtil.isValidAddress(address)) {
+ return cb('Invalid address.')
+ }
+ if (/[a-f]/.test(address) && /[A-F]/.test(address) && !ethJSUtil.isValidChecksumAddress(address)) {
+ return cb('Invalid checksum address.')
+ }
+ if (/.(.abi)$/.exec(this.config.get('currentFile'))) {
+ confirmCb(() => {
+ var abi
+ try {
+ abi = JSON.parse(this.editor.currentContent())
+ } catch (e) {
+ return cb('Failed to parse the current file as JSON ABI.')
+ }
+ cb(null, 'abi', abi)
+ })
+ }
+ cb(null, 'instance')
+ }
+
+ getCompiledContracts (compiler, compilerFullName) {
+ var contracts = []
+ compiler.visitContracts((contract) => {
+ contracts.push(contract)
+ })
+ return contracts
+ }
+
+ getSelectedContract (contractName, compilerAtributeName) {
+ if (!contractName) return null
+
+ var compiler = this.compilersArtefacts[compilerAtributeName]
+ if (!compiler) return null
+
+ var contract = compiler.getContract(contractName)
+
+ return {
+ name: contractName,
+ contract: contract,
+ compiler: compiler,
+ abi: contract.object.abi,
+ bytecodeObject: contract.object.evm.bytecode.object,
+ bytecodeLinkReferences: contract.object.evm.bytecode.linkReferences,
+ object: contract.object,
+ deployedBytecode: contract.object.evm.deployedBytecode,
+ getConstructorInterface: () => {
+ return txHelper.getConstructorInterface(contract.object.abi)
+ },
+ getConstructorInputs: () => {
+ var constructorInteface = txHelper.getConstructorInterface(contract.object.abi)
+ return txHelper.inputParametersDeclarationToString(constructorInteface.inputs)
+ },
+ isOverSizeLimit: () => {
+ var deployedBytecode = contract.object.evm.deployedBytecode
+ return (deployedBytecode && deployedBytecode.object.length / 2 > 24576)
+ }
+ }
+ }
+
+ fromWei (value, doTypeConversion, unit) {
+ if (doTypeConversion) {
+ return executionContext.web3().fromWei(typeConversion.toInt(value), unit || 'ether')
+ }
+ return executionContext.web3().fromWei(value.toString(10), unit || 'ether')
+ }
+
+ toWei (value, unit) {
+ return executionContext.web3().toWei(value, unit || 'gwei')
+ }
+
+ calculateFee (gas, gasPrice, unit) {
+ return executionContext.web3().toBigNumber(gas).mul(executionContext.web3().toBigNumber(executionContext.web3().toWei(gasPrice.toString(10), unit || 'gwei')))
+ }
+
+ getGasPrice (cb) {
+ return executionContext.web3().eth.getGasPrice(cb)
+ }
+
+ isVM () {
+ return executionContext.isVM()
+ }
+
+ // TODO: check if selectedContract and data can be joined
+ createContract (selectedContract, data, continueCb, promptCb, confirmDialog, modalDialog, finalCb) {
+ if (data) {
+ data.contractName = selectedContract.name
+ data.linkReferences = selectedContract.bytecodeLinkReferences
+ data.contractABI = selectedContract.abi
+ }
+
+ var confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
+ if (network.name !== 'Main') {
+ return continueTxExecution(null)
+ }
+ var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether')
+
+ // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily
+ var content = confirmDialog(tx, amount, gasEstimation, this.recorder,
+ (gasPrice, cb) => {
+ let txFeeText, priceStatus
+ // TODO: this try catch feels like an anti pattern, can/should be
+ // removed, but for now keeping the original logic
+ try {
+ var fee = executionContext.web3().toBigNumber(tx.gas).mul(executionContext.web3().toBigNumber(executionContext.web3().toWei(gasPrice.toString(10), 'gwei')))
+ txFeeText = ' ' + executionContext.web3().fromWei(fee.toString(10), 'ether') + ' Ether'
+ priceStatus = true
+ } catch (e) {
+ txFeeText = ' Please fix this issue before sending any transaction. ' + e.message
+ priceStatus = false
+ }
+ cb(txFeeText, priceStatus)
+ },
+ (cb) => {
+ executionContext.web3().eth.getGasPrice((error, gasPrice) => {
+ var warnMessage = ' Please fix this issue before sending any transaction. '
+ if (error) {
+ return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
+ }
+ try {
+ var gasPriceValue = executionContext.web3().fromWei(gasPrice.toString(10), 'gwei')
+ cb(null, gasPriceValue)
+ } catch (e) {
+ cb(warnMessage + e.message, null, false)
+ }
+ })
+ }
+ )
+ modalDialog('Confirm transaction', content,
+ { label: 'Confirm',
+ fn: () => {
+ this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
+ // TODO: check if this is check is still valid given the refactor
+ if (!content.gasPriceStatus) {
+ cancelCb('Given gas price is not correct')
+ } else {
+ var gasPrice = executionContext.web3().toWei(content.querySelector('#gasprice').value, 'gwei')
+ continueTxExecution(gasPrice)
+ }
+ }}, {
+ label: 'Cancel',
+ fn: () => {
+ return cancelCb('Transaction canceled by user.')
+ }
+ })
+ }
+
+ this.udapp.createContract(data, confirmationCb, continueCb, promptCb,
+ (error, txResult) => {
+ if (error) {
+ return finalCb(`creation of ${selectedContract.name} errored: ${error}`)
+ }
+ var isVM = executionContext.isVM()
+ if (isVM) {
+ var vmError = txExecution.checkVMError(txResult)
+ if (vmError.error) {
+ return finalCb(vmError.message)
+ }
+ }
+ if (txResult.result.status && txResult.result.status === '0x0') {
+ return finalCb(`creation of ${selectedContract.name} errored: transaction execution failed`)
+ }
+ var address = isVM ? txResult.result.createdAddress : txResult.result.contractAddress
+ finalCb(null, selectedContract, address)
+ }
+ )
+ }
+
+ runTransaction (data, continueCb, promptCb, modalDialog, confirmDialog, finalCb) {
+ var confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
+ if (network.name !== 'Main') {
+ return continueTxExecution(null)
+ }
+ var amount = this.fromWei(tx.value, true, 'ether')
+ var content = confirmDialog(tx, amount, gasEstimation, null,
+ (gasPrice, cb) => {
+ let txFeeText, priceStatus
+ // TODO: this try catch feels like an anti pattern, can/should be
+ // removed, but for now keeping the original logic
+ try {
+ var fee = this.calculateFee(tx.gas, gasPrice)
+ txFeeText = ' ' + this.fromWei(fee, false, 'ether') + ' Ether'
+ priceStatus = true
+ } catch (e) {
+ txFeeText = ' Please fix this issue before sending any transaction. ' + e.message
+ priceStatus = false
+ }
+ cb(txFeeText, priceStatus)
+ },
+ (cb) => {
+ this.getGasPrice((error, gasPrice) => {
+ var warnMessage = ' Please fix this issue before sending any transaction. '
+ if (error) {
+ return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
+ }
+ try {
+ var gasPriceValue = this.fromWei(gasPrice, false, 'gwei')
+ cb(null, gasPriceValue)
+ } catch (e) {
+ cb(warnMessage + e.message, null, false)
+ }
+ })
+ }
+ )
+ modalDialog('Confirm transaction', content,
+ { label: 'Confirm',
+ fn: () => {
+ this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
+ // TODO: check if this is check is still valid given the refactor
+ if (!content.gasPriceStatus) {
+ cancelCb('Given gas price is not correct')
+ } else {
+ var gasPrice = this.toWei(content.querySelector('#gasprice').value, 'gwei')
+ continueTxExecution(gasPrice)
+ }
+ }}, {
+ label: 'Cancel',
+ fn: () => {
+ return cancelCb('Transaction canceled by user.')
+ }
+ }
+ )
+ }
+
+ this.udapp.runTx(data, confirmationCb, continueCb, promptCb, finalCb)
+ }
+
+ forceSend (selectedContract, args, continueCb, promptCb, modalDialog, confirmDialog, statusCb, cb) {
+ var constructor = selectedContract.getConstructorInterface()
+ // TODO: deployMetadataOf can be moved here
+ this.filePanel.compilerMetadata().deployMetadataOf(selectedContract.name, (error, contractMetadata) => {
+ if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + error)
+ if (!contractMetadata || (contractMetadata && contractMetadata.autoDeployLib)) {
+ return txFormat.buildData(selectedContract.name, selectedContract.object, this.compilersArtefacts['__last'].getData().contracts, true, constructor, args, (error, data) => {
+ if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + error)
+
+ statusCb(`creation of ${selectedContract.name} pending...`)
+ this.createContract(selectedContract, data, continueCb, promptCb, modalDialog, confirmDialog, cb)
+ }, statusCb, (data, runTxCallback) => {
+ // called for libraries deployment
+ this.runTransaction(data, continueCb, promptCb, modalDialog, confirmDialog, runTxCallback)
+ })
+ }
+ if (Object.keys(selectedContract.bytecodeLinkReferences).length) statusCb(`linking ${JSON.stringify(selectedContract.bytecodeLinkReferences, null, '\t')} using ${JSON.stringify(contractMetadata.linkReferences, null, '\t')}`)
+ txFormat.encodeConstructorCallAndLinkLibraries(selectedContract.object, args, constructor, contractMetadata.linkReferences, selectedContract.bytecodeLinkReferences, (error, data) => {
+ if (error) return statusCb(`creation of ${selectedContract.name} errored: ` + error)
+
+ statusCb(`creation of ${selectedContract.name} pending...`)
+ this.createContract(selectedContract, data, continueCb, promptCb, modalDialog, confirmDialog, cb)
+ })
+ })
+ }
+
+}
+
+module.exports = DropdownLogic
diff --git a/src/recorder.js b/src/app/tabs/runTab/model/recorder.js
similarity index 50%
rename from src/recorder.js
rename to src/app/tabs/runTab/model/recorder.js
index 32ac7b648e..747aab6e5a 100644
--- a/src/recorder.js
+++ b/src/app/tabs/runTab/model/recorder.js
@@ -1,11 +1,12 @@
-var remixLib = require('remix-lib')
-var EventManager = require('./lib/events')
+var async = require('async')
var ethutil = require('ethereumjs-util')
-var executionContext = require('./execution-context')
+var remixLib = require('remix-lib')
+var EventManager = remixLib.EventManager
+var executionContext = remixLib.execution.executionContext
var format = remixLib.execution.txFormat
var txHelper = remixLib.execution.txHelper
-var async = require('async')
-var modal = require('./app/ui/modal-dialog-custom')
+var typeConversion = remixLib.execution.typeConversion
+var helper = require('../../../../lib/helper.js')
/**
* Record transaction as long as the user create them.
@@ -13,13 +14,15 @@ var modal = require('./app/ui/modal-dialog-custom')
*
*/
class Recorder {
- constructor (udapp, logCallBack) {
+ constructor (udapp, fileManager, config) {
var self = this
- self.logCallBack = logCallBack
self.event = new EventManager()
self.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} }
+ this.udapp = udapp
+ this.fileManager = fileManager
+ this.config = config
- udapp.event.register('initiatingTransaction', (timestamp, tx, payLoad) => {
+ this.udapp.event.register('initiatingTransaction', (timestamp, tx, payLoad) => {
if (tx.useCall) return
var { from, to, value } = tx
@@ -52,7 +55,7 @@ class Recorder {
record.inputs = txHelper.serializeInputs(payLoad.funAbi)
record.type = payLoad.funAbi.type
- udapp.getAccounts((error, accounts) => {
+ this.udapp.getAccounts((error, accounts) => {
if (error) return console.log(error)
record.from = `account{${accounts.indexOf(from)}}`
self.data._usedAccounts[record.from] = from
@@ -61,13 +64,13 @@ class Recorder {
}
})
- udapp.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp) => {
+ this.udapp.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp) => {
if (error) return console.log(error)
if (call) return
var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress
if (!address) return // not a contract creation
- address = addressToString(address)
+ address = this.addressToString(address)
// save back created addresses for the convertion from tokens to real adresses
this.data._createdContracts[address] = timestamp
this.data._createdContractsReverse[timestamp] = address
@@ -172,15 +175,15 @@ class Recorder {
* @param {Function} newContractFn
*
*/
- run (records, accounts, options, abis, linkReferences, udapp, newContractFn) {
+ run (records, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, newContractFn) {
var self = this
self.setListen(false)
- self.logCallBack(`Running ${records.length} transaction(s) ...`)
+ logCallBack(`Running ${records.length} transaction(s) ...`)
async.eachOfSeries(records, function (tx, index, cb) {
var record = self.resolveAddress(tx.record, accounts, options)
var abi = abis[tx.record.abi]
if (!abi) {
- modal.alert('cannot find ABI for ' + tx.record.abi + '. Execution stopped at ' + index)
+ alertCb('cannot find ABI for ' + tx.record.abi + '. Execution stopped at ' + index)
return
}
/* Resolve Library */
@@ -204,7 +207,7 @@ class Recorder {
fnABI = txHelper.getFunction(abi, record.name + record.inputs)
}
if (!fnABI) {
- modal.alert('cannot resolve abi of ' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index)
+ alertCb('cannot resolve abi of ' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index)
cb('cannot resolve abi')
return
}
@@ -224,49 +227,153 @@ class Recorder {
tx.record.parameters[index] = value
})
} catch (e) {
- modal.alert('cannot resolve input parameters ' + JSON.stringify(tx.record.parameters) + '. Execution stopped at ' + index)
+ alertCb('cannot resolve input parameters ' + JSON.stringify(tx.record.parameters) + '. Execution stopped at ' + index)
return
}
}
var data = format.encodeData(fnABI, tx.record.parameters, tx.record.bytecode)
if (data.error) {
- modal.alert(data.error + '. Record:' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index)
+ alertCb(data.error + '. Record:' + JSON.stringify(record, null, '\t') + '. Execution stopped at ' + index)
cb(data.error)
return
} else {
- self.logCallBack(`(${index}) ${JSON.stringify(record, null, '\t')}`)
- self.logCallBack(`(${index}) data: ${data.data}`)
+ logCallBack(`(${index}) ${JSON.stringify(record, null, '\t')}`)
+ logCallBack(`(${index}) data: ${data.data}`)
record.data = { dataHex: data.data, funArgs: tx.record.parameters, funAbi: fnABI, contractBytecode: tx.record.bytecode, contractName: tx.record.contractName }
}
- udapp.runTx(record, function (err, txResult) {
- if (err) {
- console.error(err)
- self.logCallBack(err + '. Execution failed at ' + index)
- } else {
- var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress
- if (address) {
- address = addressToString(address)
- // save back created addresses for the convertion from tokens to real adresses
- self.data._createdContracts[address] = tx.timestamp
- self.data._createdContractsReverse[tx.timestamp] = address
- newContractFn(abi, address, record.contractName)
+ self.udapp.runTx(record, confirmationCb, continueCb, promptCb,
+ function (err, txResult) {
+ if (err) {
+ console.error(err)
+ logCallBack(err + '. Execution failed at ' + index)
+ } else {
+ var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress
+ if (address) {
+ address = self.addressToString(address)
+ // save back created addresses for the convertion from tokens to real adresses
+ self.data._createdContracts[address] = tx.timestamp
+ self.data._createdContractsReverse[tx.timestamp] = address
+ newContractFn(abi, address, record.contractName)
+ }
}
+ cb(err)
}
- cb(err)
- })
+ )
}, () => { self.setListen(true); self.clearAll() })
}
-}
-function addressToString (address) {
- if (!address) return null
- if (typeof address !== 'string') {
- address = address.toString('hex')
+ addressToString (address) {
+ if (!address) return null
+ if (typeof address !== 'string') {
+ address = address.toString('hex')
+ }
+ if (address.indexOf('0x') === -1) {
+ address = '0x' + address
+ }
+ return address
}
- if (address.indexOf('0x') === -1) {
- address = '0x' + address
+
+ runScenario (continueCb, promptCb, alertCb, confirmDialog, modalDialog, logCallBack, cb) {
+ var currentFile = this.config.get('currentFile')
+ this.fileManager.fileProviderOf(currentFile).get(currentFile, (error, json) => {
+ if (error) {
+ return cb('Invalid Scenario File ' + error)
+ }
+ if (!currentFile.match('.json$')) {
+ return cb('A scenario file is required. Please make sure a scenario file is currently displayed in the editor. The file must be of type JSON. Use the "Save Transactions" Button to generate a new Scenario File.')
+ }
+ try {
+ var obj = JSON.parse(json)
+ var txArray = obj.transactions || []
+ var accounts = obj.accounts || []
+ var options = obj.options || {}
+ var abis = obj.abis || {}
+ var linkReferences = obj.linkReferences || {}
+ } catch (e) {
+ return cb('Invalid Scenario File, please try again')
+ }
+
+ if (!txArray.length) {
+ return
+ }
+
+ var confirmationCb = (network, tx, gasEstimation, continueTxExecution, cancelCb) => {
+ if (network.name !== 'Main') {
+ return continueTxExecution(null)
+ }
+ var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether')
+
+ // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily
+ var content = confirmDialog(tx, amount, gasEstimation, this.recorder,
+ (gasPrice, cb) => {
+ let txFeeText, priceStatus
+ // TODO: this try catch feels like an anti pattern, can/should be
+ // removed, but for now keeping the original logic
+ try {
+ var fee = executionContext.web3().toBigNumber(tx.gas).mul(executionContext.web3().toBigNumber(executionContext.web3().toWei(gasPrice.toString(10), 'gwei')))
+ txFeeText = ' ' + executionContext.web3().fromWei(fee.toString(10), 'ether') + ' Ether'
+ priceStatus = true
+ } catch (e) {
+ txFeeText = ' Please fix this issue before sending any transaction. ' + e.message
+ priceStatus = false
+ }
+ cb(txFeeText, priceStatus)
+ },
+ (cb) => {
+ executionContext.web3().eth.getGasPrice((error, gasPrice) => {
+ var warnMessage = ' Please fix this issue before sending any transaction. '
+ if (error) {
+ return cb('Unable to retrieve the current network gas price.' + warnMessage + error)
+ }
+ try {
+ var gasPriceValue = executionContext.web3().fromWei(gasPrice.toString(10), 'gwei')
+ cb(null, gasPriceValue)
+ } catch (e) {
+ cb(warnMessage + e.message, null, false)
+ }
+ })
+ }
+ )
+ modalDialog('Confirm transaction', content,
+ { label: 'Confirm',
+ fn: () => {
+ this.config.setUnpersistedProperty('doNotShowTransactionConfirmationAgain', content.querySelector('input#confirmsetting').checked)
+ // TODO: check if this is check is still valid given the refactor
+ if (!content.gasPriceStatus) {
+ cancelCb('Given gas price is not correct')
+ } else {
+ var gasPrice = executionContext.web3().toWei(content.querySelector('#gasprice').value, 'gwei')
+ continueTxExecution(gasPrice)
+ }
+ }}, {
+ label: 'Cancel',
+ fn: () => {
+ return cancelCb('Transaction canceled by user.')
+ }
+ })
+ }
+
+ this.run(txArray, accounts, options, abis, linkReferences, confirmationCb, continueCb, promptCb, alertCb, logCallBack, (abi, address, contractName) => {
+ cb(null, abi, address, contractName)
+ })
+ })
+ }
+
+ saveScenario (promptCb, cb) {
+ var txJSON = JSON.stringify(this.getAll(), null, 2)
+ var path = this.fileManager.currentPath()
+ promptCb(path, input => {
+ var fileProvider = this.fileManager.fileProviderOf(path)
+ if (!fileProvider) return
+ var newFile = path + '/' + input
+ helper.createNonClashingName(newFile, fileProvider, (error, newFile) => {
+ if (error) return cb('Failed to create file. ' + newFile + ' ' + error)
+ if (!fileProvider.set(newFile, txJSON)) return cb('Failed to create file ' + newFile)
+ this.fileManager.switchFile(newFile)
+ })
+ })
}
- return address
+
}
module.exports = Recorder
diff --git a/src/app/tabs/runTab/model/settings.js b/src/app/tabs/runTab/model/settings.js
new file mode 100644
index 0000000000..5d961c077c
--- /dev/null
+++ b/src/app/tabs/runTab/model/settings.js
@@ -0,0 +1,117 @@
+var ethJSUtil = require('ethereumjs-util')
+var Personal = require('web3-eth-personal')
+var remixLib = require('remix-lib')
+var EventManager = remixLib.EventManager
+var executionContext = remixLib.execution.executionContext
+
+class Settings {
+
+ constructor (udapp) {
+ this.udapp = udapp
+ this.event = new EventManager()
+
+ this.udapp.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => {
+ this.event.trigger('transactionExecuted', [error, from, to, data, lookupOnly, txResult])
+ })
+
+ executionContext.event.register('contextChanged', (context, silent) => {
+ this.event.trigger('contextChanged', [context, silent])
+ })
+
+ executionContext.event.register('addProvider', (network) => {
+ this.event.trigger('addProvider', [network])
+ })
+
+ executionContext.event.register('removeProvider', (name) => {
+ this.event.trigger('removeProvider', [name])
+ })
+
+ this.networkcallid = 0
+ }
+
+ changeExecutionContext (context, cb) {
+ return executionContext.executionContextChange(context, null, cb)
+ }
+
+ setProviderFromEndpoint (target, context, cb) {
+ return executionContext.setProviderFromEndpoint(target, context, cb)
+ }
+
+ getProvider () {
+ return executionContext.getProvider()
+ }
+
+ getAccountBalanceForAddress (address, cb) {
+ return this.udapp.getBalanceInEther(address, cb)
+ }
+
+ updateNetwork (cb) {
+ this.networkcallid++
+ ((callid) => {
+ executionContext.detectNetwork((err, { id, name } = {}) => {
+ if (this.networkcallid > callid) return
+ this.networkcallid++
+ if (err) {
+ return cb(err)
+ }
+ cb(null, {id, name})
+ })
+ })(this.networkcallid)
+ }
+
+ newAccount (passphraseCb, cb) {
+ return this.udapp.newAccount('', passphraseCb, cb)
+ }
+
+ getAccounts (cb) {
+ return this.udapp.getAccounts(cb)
+ }
+
+ isWeb3Provider () {
+ var isVM = executionContext.isVM()
+ var isInjected = executionContext.getProvider() === 'injected'
+ return (!isVM && !isInjected)
+ }
+
+ signMessage (message, account, passphrase, cb) {
+ var isVM = executionContext.isVM()
+ var isInjected = executionContext.getProvider() === 'injected'
+
+ if (isVM) {
+ const personalMsg = ethJSUtil.hashPersonalMessage(Buffer.from(message))
+ var privKey = this.udapp.accounts[account].privateKey
+ try {
+ var rsv = ethJSUtil.ecsign(personalMsg, privKey)
+ var signedData = ethJSUtil.toRpcSig(rsv.v, rsv.r, rsv.s)
+ cb(null, '0x' + personalMsg.toString('hex'), signedData)
+ } catch (e) {
+ cb(e.message)
+ }
+ return
+ }
+ if (isInjected) {
+ const hashedMsg = executionContext.web3().sha3(message)
+ try {
+ executionContext.web3().eth.sign(account, hashedMsg, (error, signedData) => {
+ cb(error, hashedMsg, signedData)
+ })
+ } catch (e) {
+ cb(e.message)
+ }
+ return
+ }
+
+ const hashedMsg = executionContext.web3().sha3(message)
+ try {
+ var personal = new Personal(executionContext.web3().currentProvider)
+ personal.sign(hashedMsg, account, passphrase, (error, signedData) => {
+ cb(error, hashedMsg, signedData)
+ })
+ } catch (e) {
+ cb(e.message)
+ }
+ }
+
+}
+
+module.exports = Settings
diff --git a/src/app/tabs/runTab/recorder.js b/src/app/tabs/runTab/recorder.js
new file mode 100644
index 0000000000..c14f221efc
--- /dev/null
+++ b/src/app/tabs/runTab/recorder.js
@@ -0,0 +1,90 @@
+var yo = require('yo-yo')
+var csjs = require('csjs-inject')
+var css = require('../styles/run-tab-styles')
+
+var modalDialogCustom = require('../../ui/modal-dialog-custom')
+var modalDialog = require('../../ui/modaldialog')
+var confirmDialog = require('../../execution/confirmDialog')
+
+class RecorderUI {
+
+ constructor (recorder, parentSelf) {
+ this.recorder = recorder
+ this.parentSelf = parentSelf
+ this.logCallBack = this.parentSelf._deps.logCallback
+ }
+
+ render () {
+ var css2 = csjs`
+ .container {}
+ .runTxs {}
+ .recorder {}
+ `
+
+ this.runButton = yo`
`
+ this.recordButton = yo`
+
+ `
+
+ this.runButton.onclick = this.runScenario.bind(this)
+ }
+
+ runScenario () {
+ var continueCb = (error, continueTxExecution, cancelCb) => {
+ if (error) {
+ var msg = typeof error !== 'string' ? error.message : error
+ modalDialog('Gas estimation failed', yo`
Gas estimation errored with the following message (see below).
+ The transaction execution will likely fail. Do you want to force sending?
+ ${msg}
+
`,
+ {
+ label: 'Send Transaction',
+ fn: () => {
+ continueTxExecution()
+ }}, {
+ label: 'Cancel Transaction',
+ fn: () => {
+ cancelCb()
+ }
+ })
+ } else {
+ continueTxExecution()
+ }
+ }
+
+ var promptCb = (okCb, cancelCb) => {
+ modalDialogCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
+ }
+
+ var alertCb = (msg) => {
+ modalDialogCustom.alert(msg)
+ }
+
+ // TODO: there is still a UI dependency to remove here, it's still too coupled at this point to remove easily
+ this.recorder.runScenario(continueCb, promptCb, alertCb, confirmDialog, modalDialog, this.logCallBack, (error, abi, address, contractName) => {
+ if (error) {
+ return modalDialogCustom.alert(error)
+ }
+
+ var noInstancesText = this.parentSelf._view.noInstancesText
+ if (noInstancesText.parentNode) { noInstancesText.parentNode.removeChild(noInstancesText) }
+
+ this.parentSelf._view.instanceContainer.appendChild(this.parentSelf._deps.udappUI.renderInstanceFromABI(abi, address, contractName))
+ })
+ }
+
+ triggerRecordButton () {
+ this.recorder.saveScenario(
+ (path, cb) => {
+ modalDialogCustom.prompt(null, 'Transactions will be saved in a file under ' + path, 'scenario.json', cb)
+ },
+ (error) => {
+ if (error) return modalDialogCustom.alert(error)
+ }
+ )
+ }
+
+}
+
+module.exports = RecorderUI
diff --git a/src/app/tabs/runTab/settings.js b/src/app/tabs/runTab/settings.js
new file mode 100644
index 0000000000..2a60b0f91e
--- /dev/null
+++ b/src/app/tabs/runTab/settings.js
@@ -0,0 +1,264 @@
+var $ = require('jquery')
+var yo = require('yo-yo')
+var remixLib = require('remix-lib')
+var EventManager = remixLib.EventManager
+var css = require('../styles/run-tab-styles')
+var copyToClipboard = require('../../ui/copy-to-clipboard')
+var modalDialogCustom = require('../../ui/modal-dialog-custom')
+var addTooltip = require('../../ui/tooltip')
+var helper = require('../../../lib/helper.js')
+
+class SettingsUI {
+
+ constructor (settings) {
+ this.settings = settings
+ this.event = new EventManager()
+
+ this.settings.event.register('transactionExecuted', (error, from, to, data, lookupOnly, txResult) => {
+ if (error) return
+ if (!lookupOnly) this.el.querySelector('#value').value = '0'
+ this.updateAccountBalances()
+ })
+
+ setInterval(() => {
+ this.updateAccountBalances()
+ }, 10 * 1000)
+
+ this.accountListCallId = 0
+ this.loadedAccounts = {}
+ }
+
+ updateAccountBalances () {
+ if (!this.el) return
+ var accounts = $(this.el.querySelector('#txorigin')).children('option')
+ accounts.each((index, account) => {
+ this.settings.getAccountBalanceForAddress(account.value, (err, balance) => {
+ if (err) return
+ account.innerText = helper.shortenAddress(account.value, balance)
+ })
+ })
+ }
+
+ render () {
+ this.netUI = yo`
`
+
+ var environmentEl = yo`
+
+
+ Environment
+
+
+ ${this.netUI}
+
{ this.updateNetwork() }} class="${css.select}">
+ JavaScript VM
+
+ Injected Web3
+
+ Web3 Provider
+
+
+
+
+
+ `
+
+ var accountEl = yo`
+
+
+ Account
+
+
+
+
+ ${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)}
+
+
+
+ `
+
+ var gasPriceEl = yo`
+
+ `
+
+ var valueEl = yo`
+
+
Value
+
+
+ wei
+ gwei
+ finney
+ ether
+
+
+ `
+
+ var el = yo`
+
+ ${environmentEl}
+ ${accountEl}
+ ${gasPriceEl}
+ ${valueEl}
+
+ `
+
+ var selectExEnv = environmentEl.querySelector('#selectExEnvOptions')
+ this.setDropdown(selectExEnv)
+
+ this.settings.event.register('contextChanged', (context, silent) => {
+ this.setFinalContext()
+ })
+
+ setInterval(() => {
+ this.updateNetwork()
+ this.fillAccountsList()
+ }, 5000)
+
+ this.el = el
+ return el
+ }
+
+ setDropdown (selectExEnv) {
+ this.selectExEnv = selectExEnv
+
+ this.settings.event.register('addProvider', (network) => {
+ selectExEnv.appendChild(yo`
${network.name}
+ `)
+ addTooltip(`${network.name} [${network.url}] added`)
+ })
+
+ this.settings.event.register('removeProvider', (name) => {
+ var env = selectExEnv.querySelector(`option[value="${name}"]`)
+ if (env) {
+ selectExEnv.removeChild(env)
+ addTooltip(`${name} removed`)
+ }
+ })
+
+ selectExEnv.addEventListener('change', (event) => {
+ let context = selectExEnv.options[selectExEnv.selectedIndex].value
+ this.settings.changeExecutionContext(context, () => {
+ modalDialogCustom.confirm(null, 'Are you sure you want to connect to an ethereum node?', () => {
+ modalDialogCustom.prompt(null, 'Web3 Provider Endpoint', 'http://localhost:8545', (target) => {
+ this.settings.setProviderFromEndpoint(target, context, (alertMsg) => {
+ if (alertMsg) {
+ modalDialogCustom.alert(alertMsg)
+ }
+ this.setFinalContext()
+ })
+ }, this.setFinalContext.bind(this))
+ }, this.setFinalContext.bind(this))
+ }, (alertMsg) => {
+ modalDialogCustom.alert(alertMsg)
+ }, this.setFinalContext.bind(this))
+ })
+
+ selectExEnv.value = this.settings.getProvider()
+ }
+
+ setFinalContext () {
+ // set the final context. Cause it is possible that this is not the one we've originaly selected
+ this.selectExEnv.value = this.settings.getProvider()
+ this.event.trigger('clearInstance', [])
+ this.updateNetwork()
+ this.fillAccountsList()
+ }
+
+ newAccount () {
+ this.settings.newAccount(
+ (cb) => {
+ modalDialogCustom.promptPassphraseCreation((error, passphrase) => {
+ if (error) {
+ return modalDialogCustom.alert(error)
+ }
+ cb(passphrase)
+ }, () => {})
+ },
+ (error, address) => {
+ if (error) {
+ return addTooltip('Cannot create an account: ' + error)
+ }
+ addTooltip(`account ${address} created`)
+ }
+ )
+ }
+
+ signMessage () {
+ this.settings.getAccounts((err, accounts) => {
+ if (err) {
+ return addTooltip(`Cannot get account list: ${err}`)
+ }
+
+ var signMessageDialog = { 'title': 'Sign a message', 'text': 'Enter a message to sign', 'inputvalue': 'Message to sign' }
+ var $txOrigin = this.el.querySelector('#txorigin')
+ var account = $txOrigin.selectedOptions[0].value
+
+ var promptCb = (passphrase) => {
+ modalDialogCustom.promptMulti(signMessageDialog, (message) => {
+ this.settings.signMessage(message, account, passphrase, (err, msgHash, signedData) => {
+ if (err) {
+ return addTooltip(err)
+ }
+ modalDialogCustom.alert(yo`
hash: ${msgHash}signature: ${signedData}
`)
+ })
+ }, false)
+ }
+
+ if (this.settings.isWeb3Provider()) {
+ return modalDialogCustom.promptPassphrase('Passphrase to sign a message', 'Enter your passphrase for this account to sign the message', '', promptCb, false)
+ }
+ promptCb()
+ })
+ }
+
+ updateNetwork () {
+ this.settings.updateNetwork((err, {id, name} = {}) => {
+ if (err) {
+ this.netUI.innerHTML = 'can\'t detect network '
+ return
+ }
+ this.netUI.innerHTML = `
${name} (${id || '-'})`
+ })
+ }
+
+ // TODO: unclear what's the goal of accountListCallId, feels like it can be simplified
+ fillAccountsList () {
+ this.accountListCallId++
+ var callid = this.accountListCallId
+ var txOrigin = this.el.querySelector('#txorigin')
+ this.settings.getAccounts((err, accounts) => {
+ if (this.accountListCallId > callid) return
+ this.accountListCallId++
+ if (err) { addTooltip(`Cannot get account list: ${err}`) }
+ for (var loadedaddress in this.loadedAccounts) {
+ if (accounts.indexOf(loadedaddress) === -1) {
+ txOrigin.removeChild(txOrigin.querySelector('option[value="' + loadedaddress + '"]'))
+ delete this.loadedAccounts[loadedaddress]
+ }
+ }
+ for (var i in accounts) {
+ var address = accounts[i]
+ if (!this.loadedAccounts[address]) {
+ txOrigin.appendChild(yo`
${address} `)
+ this.loadedAccounts[address] = 1
+ }
+ }
+ txOrigin.setAttribute('value', accounts[0])
+ })
+ }
+
+}
+
+module.exports = SettingsUI
diff --git a/src/app/ui/util.js b/src/app/ui/util.js
deleted file mode 100644
index 41f14324ec..0000000000
--- a/src/app/ui/util.js
+++ /dev/null
@@ -1,23 +0,0 @@
-var TreeView = require('./TreeView')
-var ethJSUtil = require('ethereumjs-util')
-var BN = ethJSUtil.BN
-var remixLib = require('remix-lib')
-var txFormat = remixLib.execution.txFormat
-
-module.exports = {
- decodeResponseToTreeView: function (response, fnabi) {
- var treeView = new TreeView({
- extractData: (item, parent, key) => {
- var ret = {}
- if (BN.isBN(item)) {
- ret.self = item.toString(10)
- ret.children = []
- } else {
- ret = treeView.extractDataDefault(item, parent, key)
- }
- return ret
- }
- })
- return treeView.render(txFormat.decodeResponse(response, fnabi))
- }
-}
diff --git a/src/config.js b/src/config.js
index 6a41ebd133..d5095b919b 100644
--- a/src/config.js
+++ b/src/config.js
@@ -53,6 +53,8 @@ function Config (storage) {
return this.unpersistedItems[key]
}
+ // TODO: this only used for *one* property "doNotShowTransactionConfirmationAgain"
+ // and can be removed once it's refactored away in txRunner
this.setUnpersistedProperty = function (key, value) {
this.unpersistedItems[key] = value
}
diff --git a/src/universal-dapp-ui.js b/src/universal-dapp-ui.js
index a900ff2cd9..276abf5186 100644
--- a/src/universal-dapp-ui.js
+++ b/src/universal-dapp-ui.js
@@ -3,13 +3,48 @@
var $ = require('jquery')
var yo = require('yo-yo')
+var ethJSUtil = require('ethereumjs-util')
+var BN = ethJSUtil.BN
var helper = require('./lib/helper')
var copyToClipboard = require('./app/ui/copy-to-clipboard')
var css = require('./universal-dapp-styles')
var MultiParamManager = require('./multiParamManager')
+var remixLib = require('remix-lib')
+var typeConversion = remixLib.execution.typeConversion
+var txExecution = remixLib.execution.txExecution
+var txFormat = remixLib.execution.txFormat
-function UniversalDAppUI (udapp, opts = {}) {
+var executionContext = require('./execution-context')
+
+var confirmDialog = require('./app/execution/confirmDialog')
+var modalCustom = require('./app/ui/modal-dialog-custom')
+var modalDialog = require('./app/ui/modaldialog')
+var TreeView = require('./app/ui/TreeView')
+
+function UniversalDAppUI (udapp, registry) {
this.udapp = udapp
+ this.registry = registry
+
+ this.compilerData = {contractsDetails: {}}
+ this._deps = {
+ compilersartefacts: registry.get('compilersartefacts').api
+ }
+}
+
+function decodeResponseToTreeView (response, fnabi) {
+ var treeView = new TreeView({
+ extractData: (item, parent, key) => {
+ var ret = {}
+ if (BN.isBN(item)) {
+ ret.self = item.toString(10)
+ ret.children = []
+ } else {
+ ret = treeView.extractDataDefault(item, parent, key)
+ }
+ return ret
+ }
+ })
+ return treeView.render(txFormat.decodeResponse(response, fnabi))
}
UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) {
@@ -38,10 +73,8 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address
${copyToClipboard(() => address)}
`
- if (self.udapp.removable_instances) {
- var close = yo`Gas estimation errored with the following message (see below).
+ The transaction execution will likely fail. Do you want to force sending?
+ ${msg}
+
`,
+ {
+ label: 'Send Transaction',
+ fn: () => {
+ continueTxExecution()
+ }}, {
+ label: 'Cancel Transaction',
+ fn: () => {
+ cancelCb()
+ }
+ })
+ } else {
+ continueTxExecution()
+ }
+ }
+
+ var outputCb = (decoded) => {
outputOverride.innerHTML = ''
outputOverride.appendChild(decoded)
+ }
+
+ var promptCb = (okCb, cancelCb) => {
+ modalCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb)
+ }
+
+ // contractsDetails is used to resolve libraries
+ txFormat.buildData(args.contractName, args.contractAbi, self._deps.compilersartefacts['__last'].getData().contracts, false, args.funABI, args.funABI.type !== 'fallback' ? value : '', (error, data) => {
+ if (!error) {
+ if (!args.funABI.constant) {
+ self.registry.get('logCallback').api(`${logMsg} pending ... `)
+ } else {
+ self.registry.get('logCallback').api(`${logMsg}`)
+ }
+ if (args.funABI.type === 'fallback') data.dataHex = value
+ self.udapp.callFunction(args.address, data, args.funABI, confirmationCb, continueCb, promptCb, (error, txResult) => {
+ if (!error) {
+ var isVM = executionContext.isVM()
+ if (isVM) {
+ var vmError = txExecution.checkVMError(txResult)
+ if (vmError.error) {
+ self.registry.get('logCallback').api(`${logMsg} errored: ${vmError.message} `)
+ return
+ }
+ }
+ if (lookupOnly) {
+ var decoded = decodeResponseToTreeView(executionContext.isVM() ? txResult.result.vm.return : ethJSUtil.toBuffer(txResult.result), args.funABI)
+ outputCb(decoded)
+ }
+ } else {
+ self.registry.get('logCallback').api(`${logMsg} errored: ${error} `)
+ }
+ })
+ } else {
+ self.registry.get('logCallback').api(`${logMsg} errored: ${error} `)
+ }
+ }, (msg) => {
+ self.registry.get('logCallback').api(msg)
+ }, (data, runTxCallback) => {
+ // called for libraries deployment
+ self.udapp.runTx(data, confirmationCb, runTxCallback)
})
}
diff --git a/src/universal-dapp.js b/src/universal-dapp.js
index 70a5b48adf..2262b576ac 100644
--- a/src/universal-dapp.js
+++ b/src/universal-dapp.js
@@ -1,42 +1,19 @@
-/* global */
-'use strict'
-
-var yo = require('yo-yo')
var async = require('async')
var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN
var remixLib = require('remix-lib')
-var EventManager = require('./lib/events')
var crypto = require('crypto')
var TxRunner = remixLib.execution.txRunner
-var txExecution = remixLib.execution.txExecution
-var txFormat = remixLib.execution.txFormat
var txHelper = remixLib.execution.txHelper
-var executionContext = require('./execution-context')
-var modalCustom = require('./app/ui/modal-dialog-custom')
-var uiUtil = require('./app/ui/util')
-var globalRegistry = require('./global/registry')
-
-var modalDialog = require('./app/ui/modaldialog')
-var typeConversion = remixLib.execution.typeConversion
-var confirmDialog = require('./app/execution/confirmDialog')
+var EventManager = remixLib.EventManager
+var executionContext = remixLib.execution.executionContext
-function UniversalDApp (opts, localRegistry) {
+function UniversalDApp (registry) {
this.event = new EventManager()
var self = this
- self.data = {}
- self._components = {}
- self._components.registry = localRegistry || globalRegistry
- self.removable = opts.removable
- self.removable_instances = opts.removable_instances
self._deps = {
- config: self._components.registry.get('config').api,
- compilersartefacts: self._components.registry.get('compilersartefacts').api,
- logCallback: self._components.registry.get('logCallback').api
+ config: registry.get('config').api
}
- executionContext.event.register('contextChanged', this, function (context) {
- self.resetEnvironment()
- })
self._txRunnerAPI = {
config: self._deps.config,
detectNetwork: (cb) => {
@@ -49,6 +26,14 @@ function UniversalDApp (opts, localRegistry) {
self.txRunner = new TxRunner({}, self._txRunnerAPI)
self.accounts = {}
self.resetEnvironment()
+ executionContext.event.register('contextChanged', this.resetEnvironment.bind(this))
+}
+
+UniversalDApp.prototype.profile = function () {
+ return {
+ type: 'udapp',
+ methods: ['runTestTx', 'getAccounts', 'createVMAccount']
+ }
}
UniversalDApp.prototype.profile = function () {
@@ -68,13 +53,22 @@ UniversalDApp.prototype.resetEnvironment = function () {
this._addAccount('71975fbf7fe448e004ac7ae54cad0a383c3906055a65468714156a07385e96ce', '0x56BC75E2D63100000')
executionContext.vm().stateManager.cache.flush(function () {})
}
- this.txRunner = new TxRunner(this.accounts, this._txRunnerAPI)
+ // TODO: most params here can be refactored away in txRunner
+ this.txRunner = new TxRunner(this.accounts, {
+ // TODO: only used to check value of doNotShowTransactionConfirmationAgain property
+ config: this.config,
+ // TODO: to refactor, TxRunner already has access to executionContext
+ detectNetwork: (cb) => {
+ executionContext.detectNetwork(cb)
+ },
+ personalMode: () => {
+ return this.config.get('settings/personal-mode')
+ }
+ })
this.txRunner.event.register('transactionBroadcasted', (txhash) => {
executionContext.detectNetwork((error, network) => {
- if (!error && network) {
- var txLink = executionContext.txDetailsLink(network.name, txhash)
- if (txLink) this._deps.logCallback(yo`Gas estimation errored with the following message (see below).
- The transaction execution will likely fail. Do you want to force sending?
- ${msg}
-
`,
- {
- label: 'Send Transaction',
- fn: () => {
- continueTxExecution()
- }}, {
- label: 'Cancel Transaction',
- fn: () => {
- cancelCb()
- }
- })
- } else {
- continueTxExecution()
- }
- },
- function (okCb, cancelCb) {
- modalCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account ' + tx.from, '', okCb, cancelCb)
- },
+ self.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
function (error, result) {
let eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted')
self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad])