From 9dc1ad76db3dc26d0911b3856e31d5699a673e8f Mon Sep 17 00:00:00 2001 From: Iuri Matias Date: Wed, 26 Dec 2018 13:32:18 -0500 Subject: [PATCH] move settings and contractsDropdown UI to their own modules --- src/app/panels/righthand-panel.js | 74 +-- src/app/tabs/run-tab.js | 675 +----------------------- src/app/tabs/runTab/contractDropdown.js | 391 ++++++++++++++ src/app/tabs/runTab/settings.js | 299 +++++++++++ 4 files changed, 732 insertions(+), 707 deletions(-) create mode 100644 src/app/tabs/runTab/contractDropdown.js create mode 100644 src/app/tabs/runTab/settings.js diff --git a/src/app/panels/righthand-panel.js b/src/app/panels/righthand-panel.js index b995c77d73..723339d585 100644 --- a/src/app/panels/righthand-panel.js +++ b/src/app/panels/righthand-panel.js @@ -11,8 +11,43 @@ const DraggableContent = require('../ui/draggableContent') const styles = styleguide.chooser() -module.exports = class RighthandPanel { - constructor ({pluginManager, tabs}, localRegistry) { +const css = csjs` + .righthandpanel { + display : flex; + flex-direction : column; + top : 0; + right : 0; + bottom : 0; + box-sizing : border-box; + overflow : hidden; + height : 100%; + } + .header { + height : 100%; + } + .dragbar { + position : absolute; + width : 0.5em; + top : 3em; + bottom : 0; + cursor : col-resize; + z-index : 999; + border-left : 2px solid ${styles.rightPanel.bar_Dragging}; + } + .ghostbar { + width : 3px; + background-color : ${styles.rightPanel.bar_Ghost}; + opacity : 0.5; + position : absolute; + cursor : col-resize; + z-index : 9999; + top : 0; + bottom : 0; + } +` + +class RighthandPanel { + constructor (localRegistry) { const self = this self._components = {} self._components.registry = localRegistry || globalRegistry @@ -125,37 +160,4 @@ module.exports = class RighthandPanel { } } -const css = csjs` - .righthandpanel { - display : flex; - flex-direction : column; - top : 0; - right : 0; - bottom : 0; - box-sizing : border-box; - overflow : hidden; - height : 100%; - } - .header { - height : 100%; - } - .dragbar { - position : absolute; - width : 0.5em; - top : 3em; - bottom : 0; - cursor : col-resize; - z-index : 999; - border-left : 2px solid ${styles.rightPanel.bar_Dragging}; - } - .ghostbar { - width : 3px; - background-color : ${styles.rightPanel.bar_Ghost}; - opacity : 0.5; - position : absolute; - cursor : col-resize; - z-index : 9999; - top : 0; - bottom : 0; - } -` +module.exports = RighthandPanel diff --git a/src/app/tabs/run-tab.js b/src/app/tabs/run-tab.js index ae6129d33d..a7c1c5a9d1 100644 --- a/src/app/tabs/run-tab.js +++ b/src/app/tabs/run-tab.js @@ -1,32 +1,18 @@ '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 typeConversion = remixLib.execution.typeConversion 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 confirmDialog = require('../execution/confirmDialog') -var modalCustom = require('../ui/modal-dialog-custom') +var settingsUI = require('./runTab/settings.js') +var contractDropdownUI = require('./runTab/contractDropdown.js') function runTab (opts, localRegistry) { /* ------------------------- @@ -144,8 +130,8 @@ function runTab (opts, localRegistry) { --------------------------- */ var el = yo`
- ${settings(container, self)} - ${contractDropdown(self.event, self)} + ${settingsUI(container, self)} + ${contractDropdownUI(self.event, self)} ${recorderCard.render()} ${self._view.instanceContainer}
@@ -155,47 +141,6 @@ function runTab (opts, localRegistry) { return { render () { return container } } } -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``) - 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 ------------------------------------------------ */ @@ -289,617 +234,5 @@ function makeRecorder (registry, runTabEvent, self) { 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``) - selectContractNames.setAttribute('disabled', true) - } else if (/.(.sol)$/.exec(currentFile)) { - deployAction('block') - } - }) - - 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} -
-
At Address
- ${atAddressButtonInput} -
-
-
- ` - - 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, - - (network, tx, gasEstimation, continueTxExecution, cancelCb) => { - if (network.name !== 'Main') { - return continueTxExecution(null) - } - var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether') - var content = confirmDialog(tx, amount, gasEstimation, self, - (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: () => { - self._deps.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.') - } - }) - }, - (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() - } - }, - function (okCb, cancelCb) { - modalCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) - }, - (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() - - 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, - (network, tx, gasEstimation, continueTxExecution, cancelCb) => { - if (network.name !== 'Main') { - return continueTxExecution(null) - } - var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether') - var content = confirmDialog(tx, amount, gasEstimation, self, - (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: () => { - self._deps.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.') - } - }) - }, - function (okCb, cancelCb) { - modalCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) - }, - 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``) - }) - } 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} - - -
-
- ` - var accountEl = yo` -
-
- Account - -
-
- - ${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)} - -
-
- ` - var gasPriceEl = yo` -
-
Gas limit
- -
- ` - var valueEl = yo` -
-
Value
- - -
- ` - // DOM ELEMENT - var el = yo` -
- ${environmentEl} - ${accountEl} - ${gasPriceEl} - ${valueEl} -
- ` - // 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``) - 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('', - (cb) => { - modalCustom.promptPassphraseCreation((error, passphrase) => { - if (error) { - return modalCustom.alert(error) - } - cb(passphrase) - }, () => {}) - }, - (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) - } - }) - } - - return el -} 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..e3c23cad13 --- /dev/null +++ b/src/app/tabs/runTab/contractDropdown.js @@ -0,0 +1,391 @@ +var yo = require('yo-yo') +var ethJSUtil = require('ethereumjs-util') +var css = require('../styles/run-tab-styles') +var executionContext = require('../../../execution-context') +var modalDialogCustom = require('../../ui/modal-dialog-custom') +var modalCustom = require('../../ui/modal-dialog-custom') +var CompilerAbstract = require('../../compiler/compiler-abstract') +var remixLib = require('remix-lib') +var txExecution = remixLib.execution.txExecution +var txFormat = remixLib.execution.txFormat +var txHelper = remixLib.execution.txHelper +var typeConversion = remixLib.execution.typeConversion +var confirmDialog = require('../../execution/confirmDialog') +var modalDialog = require('../../ui/modaldialog') +var MultiParamManager = require('../../../multiParamManager') + +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) => { + // TODO check whether the tab is configured + let compiler = new CompilerAbstract(languageVersion, data) + self._deps.compilersArtefacts[languageVersion] = compiler + self._deps.compilersArtefacts['__last'] = compiler + newlyCompiled(true, data, source, compiler, languageVersion) + }) + + self._deps.compiler.event.register('compilationFinished', (success, data, source) => { + var name = 'solidity' + let compiler = new CompilerAbstract(name, data) + self._deps.compilersArtefacts[name] = compiler + self._deps.compilersArtefacts['__last'] = compiler + newlyCompiled(success, data, source, self._deps.compiler, name) + }) + + 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``) + selectContractNames.setAttribute('disabled', true) + } else if (/.(.sol)$/.exec(currentFile)) { + deployAction('block') + } + }) + + var atAddressButtonInput = yo`` + var selectContractNames = yo`` + + function getSelectedContract () { + var contract = selectContractNames.children[selectContractNames.selectedIndex] + var contractName = contract.innerHTML + var compiler = self._deps.compilersArtefacts[contract.getAttribute('compiler')] + 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} +
+
At Address
+ ${atAddressButtonInput} +
+
+
+ ` + + 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, + + (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + if (network.name !== 'Main') { + return continueTxExecution(null) + } + var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether') + var content = confirmDialog(tx, amount, gasEstimation, self, + (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: () => { + self._deps.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.') + } + }) + }, + (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() + } + }, + function (okCb, cancelCb) { + modalCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) + }, + (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() + + 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, + (network, tx, gasEstimation, continueTxExecution, cancelCb) => { + if (network.name !== 'Main') { + return continueTxExecution(null) + } + var amount = executionContext.web3().fromWei(typeConversion.toInt(tx.value), 'ether') + var content = confirmDialog(tx, amount, gasEstimation, self, + (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: () => { + self._deps.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.') + } + }) + }, + function (okCb, cancelCb) { + modalCustom.promptPassphrase(null, 'Personal mode is enabled. Please provide passphrase of account', '', okCb, cancelCb) + }, + 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``) + }) + } else { + selectContractNames.setAttribute('disabled', true) + } + setInputParamsPlaceHolder() + } + + return el +} + +module.exports = contractDropdown diff --git a/src/app/tabs/runTab/settings.js b/src/app/tabs/runTab/settings.js new file mode 100644 index 0000000000..6c7bb20029 --- /dev/null +++ b/src/app/tabs/runTab/settings.js @@ -0,0 +1,299 @@ +var $ = require('jquery') +var yo = require('yo-yo') +var ethJSUtil = require('ethereumjs-util') +var Personal = require('web3-eth-personal') +var css = require('../styles/run-tab-styles') +var executionContext = require('../../../execution-context') +var copyToClipboard = require('../../ui/copy-to-clipboard') +var modalDialogCustom = require('../../ui/modal-dialog-custom') +var addTooltip = require('../../ui/tooltip') +var modalCustom = require('../../ui/modal-dialog-custom') +var tootip = require('../../ui/tooltip') +var helper = require('../../../lib/helper.js') + +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} + + +
+
+ ` + var accountEl = yo` +
+
+ Account + +
+
+ + ${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)} + +
+
+ ` + var gasPriceEl = yo` +
+
Gas limit
+ +
+ ` + var valueEl = yo` +
+
Value
+ + +
+ ` + // DOM ELEMENT + var el = yo` +
+ ${environmentEl} + ${accountEl} + ${gasPriceEl} + ${valueEl} +
+ ` + // 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``) + 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('', + (cb) => { + modalCustom.promptPassphraseCreation((error, passphrase) => { + if (error) { + return modalCustom.alert(error) + } + cb(passphrase) + }, () => {}) + }, + (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) + } + }) + } + + return el +} + +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``) + 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) + }) +} + +module.exports = settings