Merge branch 'swap_it' of https://github.com/ethereum/remix-ide into swap_it

pull/1/head
Rob Stupay 6 years ago
commit bc04dfca73
  1. 13
      src/app.js
  2. 1
      src/app/execution/confirmDialog.js
  3. 1
      src/app/files/compiler-metadata.js
  4. 700
      src/app/tabs/run-tab.js
  5. 200
      src/app/tabs/runTab/contractDropdown.js
  6. 295
      src/app/tabs/runTab/model/dropdownlogic.js
  7. 157
      src/app/tabs/runTab/model/recorder.js
  8. 117
      src/app/tabs/runTab/model/settings.js
  9. 90
      src/app/tabs/runTab/recorder.js
  10. 264
      src/app/tabs/runTab/settings.js
  11. 23
      src/app/ui/util.js
  12. 2
      src/config.js
  13. 167
      src/universal-dapp-ui.js
  14. 215
      src/universal-dapp.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'}) registry.put({api: self._components.compilersArtefacts, name: 'compilersartefacts'})
// ----------------- UniversalDApp ----------------- // ----------------- UniversalDApp -----------------
var udapp = new UniversalDApp({ var udapp = new UniversalDApp(registry)
removable: false, // TODO: to remove when possible
removable_instances: true
})
registry.put({api: udapp, name: 'udapp'}) 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`<a href="${txLink}" target="_blank">${txLink}</a>`)
})
var udappUI = new UniversalDAppUI(udapp) var udappUI = new UniversalDAppUI(udapp, registry)
// TODO: to remove when possible
registry.put({api: udappUI, name: 'udappUI'}) registry.put({api: udappUI, name: 'udappUI'})
// ----------------- Tx listener ----------------- // ----------------- Tx listener -----------------

@ -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) { function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialParamsCb) {
var onGasPriceChange = function () { var onGasPriceChange = function () {
var gasPrice = el.querySelector('#gasprice').value var gasPrice = el.querySelector('#gasprice').value

@ -74,6 +74,7 @@ class CompilerMetadata {
return metadata return metadata
} }
// TODO: is only called by dropdownLogic and can be moved there
deployMetadataOf (contractName, callback) { deployMetadataOf (contractName, callback) {
var self = this var self = this
var provider = self._opts.fileManager.currentFileProvider() var provider = self._opts.fileManager.currentFileProvider()

@ -1,42 +1,28 @@
'use strict'
var $ = require('jquery') var $ = require('jquery')
var yo = require('yo-yo') 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 EventManager = require('../../lib/events')
var globlalRegistry = require('../../global/registry') var globlalRegistry = require('../../global/registry')
var helper = require('../../lib/helper.js')
var executionContext = require('../../execution-context') 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 Card = require('../ui/card')
var Recorder = require('../../recorder')
var addTooltip = require('../ui/tooltip')
var css = require('./styles/run-tab-styles') var css = require('./styles/run-tab-styles')
var MultiParamManager = require('../../multiParamManager')
var modalDialog = require('../ui/modaldialog') var Settings = require('./runTab/model/settings.js')
var CompilerAbstract = require('../compiler/compiler-abstract') var SettingsUI = require('./runTab/settings.js')
var tootip = require('../ui/tooltip')
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) { function runTab (opts, localRegistry) {
/* -------------------------
VARIABLES
--------------------------- */
var self = this var self = this
self.event = new EventManager() self.event = new EventManager()
self._view = {} self._view = {}
self.data = { self.data = {
count: 0, count: 0,
text: `All transactions (deployed contracts and function executions) text: `All transactions (deployed contracts and function executions) in this environment can be saved and replayed in
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.`
another environment. e.g Transactions created in
Javascript VM can be replayed in the Injected Web3.`
} }
self._components = {} self._components = {}
self._components.registry = localRegistry || globlalRegistry self._components.registry = localRegistry || globlalRegistry
@ -51,14 +37,8 @@ function runTab (opts, localRegistry) {
var index = select.selectedIndex var index = select.selectedIndex
var selectedUnit = select.querySelectorAll('option')[index].dataset.unit var selectedUnit = select.querySelectorAll('option')[index].dataset.unit
var unit = 'ether' // default var unit = 'ether' // default
if (selectedUnit === 'ether') { if (['ether', 'finney', 'gwei', 'wei'].indexOf(selectedUnit) >= 0) {
unit = 'ether' unit = selectedUnit
} else if (selectedUnit === 'finney') {
unit = 'finney'
} else if (selectedUnit === 'gwei') {
unit = 'gwei'
} else if (selectedUnit === 'wei') {
unit = 'wei'
} }
cb(null, executionContext.web3().toWei(number, unit)) cb(null, executionContext.web3().toWei(number, unit))
} catch (e) { } catch (e) {
@ -100,7 +80,21 @@ function runTab (opts, localRegistry) {
</div>` </div>`
var container = yo`<div class="${css.runTabView}" id="runTabView" ></div>` var container = yo`<div class="${css.runTabView}" id="runTabView" ></div>`
var recorderInterface = makeRecorder(localRegistry, self.event, self)
var recorder = new Recorder(self._deps.udapp, self._deps.fileManager, self._deps.config)
recorder.event.register('newTxRecorded', (count) => {
this.data.count = count
this._view.recorderCount.innerText = count
})
recorder.event.register('cleared', () => {
this.data.count = 0
this._view.recorderCount.innerText = 0
})
executionContext.event.register('contextChanged', recorder.clearAll.bind(recorder))
self.event.register('clearInstance', recorder.clearAll.bind(recorder))
var recorderInterface = new RecorderUI(recorder, self)
recorderInterface.render()
self._view.collapsedView = yo` self._view.collapsedView = yo`
<div class=${css.recorderCollapsedView}> <div class=${css.recorderCollapsedView}>
@ -135,620 +129,56 @@ function runTab (opts, localRegistry) {
status.appendChild(self._view.collapsedView) status.appendChild(self._view.collapsedView)
} }
}) })
/* -------------------------
MAIN HTML ELEMENT
--------------------------- */
var el = yo`
<div>
${settings(container, self)}
${contractDropdown(self.event, self)}
${recorderCard.render()}
${self._view.instanceContainer}
</div>
`
container.appendChild(el)
return { render () { return container } } var settings = new Settings(self._deps.udapp)
} var settingsUI = new SettingsUI(settings)
var accountListCallId = 0 self.event.register('clearInstance', () => {
var loadedAccounts = {} this._view.instanceContainer.innerHTML = '' // clear the instances list
function fillAccountsList (container, self) { this._view.instanceContainer.appendChild(self._view.instanceContainerTitle)
accountListCallId++ this._view.instanceContainer.appendChild(self._view.noInstancesText)
(function (callid) { })
var txOrigin = container.querySelector('#txorigin') settingsUI.event.register('clearInstance', () => {
self._deps.udapp.getAccounts((err, accounts) => { this.event.trigger('clearInstance', [])
if (accountListCallId > callid) return })
accountListCallId++
if (err) { addTooltip(`Cannot get account list: ${err}`) } var dropdownLogic = new DropdownLogic(
for (var loadedaddress in loadedAccounts) { this._deps.fileManager,
if (accounts.indexOf(loadedaddress) === -1) { this._deps.pluginManager,
txOrigin.removeChild(txOrigin.querySelector('option[value="' + loadedaddress + '"]')) this._deps.compilersArtefacts,
delete loadedAccounts[loadedaddress] this._deps.compiler,
} this._deps.config,
} this._deps.editor,
for (var i in accounts) { this._deps.udapp,
var address = accounts[i] this._deps.filePanel
if (!loadedAccounts[address]) { )
txOrigin.appendChild(yo`<option value="${address}" >${address}</option>`) var contractDropdownUI = new ContractDropdownUI(dropdownLogic, this._deps.logCallback)
loadedAccounts[address] = 1
} contractDropdownUI.event.register('clearInstance', () => {
} var noInstancesText = this._view.noInstancesText
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
})
recorder.event.register('cleared', () => {
self.data.count = 0
self._view.recorderCount.innerText = 0
})
executionContext.event.register('contextChanged', () => {
recorder.clearAll()
})
runTabEvent.register('clearInstance', () => {
recorder.clearAll()
})
var css2 = csjs`
.container {}
.runTxs {}
.recorder {}
`
var runButton = yo`<i class="fa fa-play runtransaction ${css2.runTxs} ${css.icon}" title="Run Transactions" aria-hidden="true"></i>`
var recordButton = yo`
<i class="fa fa-floppy-o savetransaction ${css2.recorder} ${css.icon}"
onclick=${triggerRecordButton} title="Save Transactions" aria-hidden="true">
</i>`
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) } 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))
}) })
} contractDropdownUI.event.register('newContractABIAdded', (abi, address) => {
} else { this._view.instanceContainer.appendChild(this._deps.udappUI.renderInstanceFromABI(abi, address, address))
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.')
}
}
}) })
} contractDropdownUI.event.register('newContractInstanceAdded', (contractObject, address, value) => {
this._view.instanceContainer.appendChild(this._deps.udappUI.renderInstance(contractObject, address, value))
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`<i title="Contract compilation failed. Please check the compile tab for more information." class="fa fa-times-circle ${css.errorIcon}" ></i>`
var info = yo`<i class="fa fa-info ${css.infoDeployAction}" aria-hidden="true" title="*.sol files allows deploying and accessing contracts. *.abi files only allows accessing contracts."></i>`
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`<option>(abi)</option>`)
selectContractNames.setAttribute('disabled', true)
} else if (/.(.sol)$/.exec(currentFile)) {
deployAction('block')
}
}) })
var atAddressButtonInput = yo`<input class="${css.input} ataddressinput" placeholder="Load contract from Address" title="atAddress" />` this._view.instanceContainer.appendChild(this._view.instanceContainerTitle)
var selectContractNames = yo`<select class="${css.contractNames}" disabled></select>` this._view.instanceContainer.appendChild(this._view.noInstancesText)
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`<div class="${css.button}"></div>`
self._view.orLabel = yo`<div class="${css.orLabel}">or</div>`
var el = yo` var el = yo`
<div class="${css.container}">
<div class="${css.subcontainer}">
${selectContractNames} ${compFails} ${info}
</div>
<div> <div>
${self._view.createPanel} ${settingsUI.render()}
${self._view.orLabel} ${contractDropdownUI.render()}
<div class="${css.button} ${css.atAddressSect}"> ${recorderCard.render()}
<div class="${css.atAddress}" onclick=${function () { loadFromAddress() }}>At Address</div> ${self._view.instanceContainer}
${atAddressButtonInput}
</div>
</div>
</div>
`
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()
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`<div>Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. <br>
More info: <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md" target="_blank">eip-170</a>
</div>`,
{
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`<option compiler="${compilerFullName}">${contract.name}</option>`)
})
} else {
selectContractNames.setAttribute('disabled', true)
}
setInputParamsPlaceHolder()
}
return el
}
/* ------------------------------------------------
section SETTINGS: Environment, Account, Gas, Value
------------------------------------------------ */
function settings (container, self) {
// VARIABLES
var net = yo`<span class=${css.network}></span>`
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 = `<i class="${css.networkItem} fa fa-plug" aria-hidden="true"></i> ${name} (${id || '-'})`
}
if (cb) cb(err, {id, name})
})
})(networkcallid)
}
var environmentEl = yo`
<div class="${css.crow}">
<div id="selectExEnv" class="${css.col1_1}">
Environment
</div>
<div class=${css.environment}>
${net}
<select id="selectExEnvOptions" onchange=${() => { updateNetwork() }} class="${css.select}">
<option id="vm-mode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm" checked name="executionContext"> JavaScript VM
</option>
<option id="injected-mode"
title="Execution environment has been provided by Metamask or similar provider."
value="injected" checked name="executionContext"> Injected Web3
</option>
<option id="web3-mode"
title="Execution environment connects to node at localhost (or via IPC if available), transactions will be sent to the network and can cause loss of money or worse!
If this page is served via https and you access your node via http, it might not work. In this case, try cloning the repository and serving it via http."
value="web3" name="executionContext"> Web3 Provider
</option>
</select>
<a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md" target="_blank"><i class="${css.icon} fa fa-info"></i></a>
</div>
</div>
`
var accountEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">
Account
<i class="fa fa-plus-circle ${css.icon}" aria-hidden="true" onclick=${newAccount} title="Create a new account"></i>
</div>
<div class=${css.account}>
<select name="txorigin" class="${css.select}" id="txorigin"></select>
${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)}
<i class="fa fa-pencil-square-o ${css.icon}" aria-hiden="true" onclick=${signMessage} title="Sign a message using this account key"></i>
</div>
</div>
`
var gasPriceEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">Gas limit</div>
<input type="number" class="${css.col2}" id="gasLimit" value="3000000">
</div>
`
var valueEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">Value</div>
<input type="text" class="${css.col2_1}" id="value" value="0" title="Enter the value and choose the unit">
<select name="unit" class="${css.col2_2}" id="unit">
<option data-unit="wei">wei</option>
<option data-unit="gwei">gwei</option>
<option data-unit="finney">finney</option>
<option data-unit="ether">ether</option>
</select>
</div>
`
// DOM ELEMENT
var el = yo`
<div class="${css.settings}">
${environmentEl}
${accountEl}
${gasPriceEl}
${valueEl}
</div> </div>
` `
// HELPER FUNCTIONS AND EVENTS container.appendChild(el)
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`<option
title="Manually added environment: ${network.url}"
value="${network.name}" name="executionContext"> ${network.name}
</option>`)
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`<div><b>hash:</b>${hash}<br><b>signature:</b>${signedData}</div>`)
}
}
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 return { render () { return container } }
} }
module.exports = runTab module.exports = runTab

@ -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`<option compiler="${compilerFullName}">${contract.name}</option>`)
})
} 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`<i title="Contract compilation failed. Please check the compile tab for more information." class="fa fa-times-circle ${css.errorIcon}" ></i>`
var info = yo`<i class="fa fa-info ${css.infoDeployAction}" aria-hidden="true" title="*.sol files allows deploying and accessing contracts. *.abi files only allows accessing contracts."></i>`
this.atAddressButtonInput = yo`<input class="${css.input} ataddressinput" placeholder="Load contract from Address" title="atAddress" />`
this.selectContractNames = yo`<select class="${css.contractNames}" disabled></select>`
this.createPanel = yo`<div class="${css.button}"></div>`
this.orLabel = yo`<div class="${css.orLabel}">or</div>`
var el = yo`
<div class="${css.container}">
<div class="${css.subcontainer}">
${this.selectContractNames} ${this.compFails} ${info}
</div>
<div>
${this.createPanel}
${this.orLabel}
<div class="${css.button} ${css.atAddressSect}">
<div class="${css.atAddress}" onclick=${this.loadFromAddress.bind(this)}>At Address</div>
${this.atAddressButtonInput}
</div>
</div>
</div>
`
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`<option>(abi)</option>`)
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`<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>
${msg}
</div>`,
{
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`<div>Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. <br>
More info: <a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md" target="_blank">eip-170</a>
</div>`,
{
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

@ -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

@ -1,11 +1,12 @@
var remixLib = require('remix-lib') var async = require('async')
var EventManager = require('./lib/events')
var ethutil = require('ethereumjs-util') 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 format = remixLib.execution.txFormat
var txHelper = remixLib.execution.txHelper var txHelper = remixLib.execution.txHelper
var async = require('async') var typeConversion = remixLib.execution.typeConversion
var modal = require('./app/ui/modal-dialog-custom') var helper = require('../../../../lib/helper.js')
/** /**
* Record transaction as long as the user create them. * Record transaction as long as the user create them.
@ -13,13 +14,15 @@ var modal = require('./app/ui/modal-dialog-custom')
* *
*/ */
class Recorder { class Recorder {
constructor (udapp, logCallBack) { constructor (udapp, fileManager, config) {
var self = this var self = this
self.logCallBack = logCallBack
self.event = new EventManager() self.event = new EventManager()
self.data = { _listen: true, _replay: false, journal: [], _createdContracts: {}, _createdContractsReverse: {}, _usedAccounts: {}, _abis: {}, _contractABIReferences: {}, _linkReferences: {} } 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 if (tx.useCall) return
var { from, to, value } = tx var { from, to, value } = tx
@ -52,7 +55,7 @@ class Recorder {
record.inputs = txHelper.serializeInputs(payLoad.funAbi) record.inputs = txHelper.serializeInputs(payLoad.funAbi)
record.type = payLoad.funAbi.type record.type = payLoad.funAbi.type
udapp.getAccounts((error, accounts) => { this.udapp.getAccounts((error, accounts) => {
if (error) return console.log(error) if (error) return console.log(error)
record.from = `account{${accounts.indexOf(from)}}` record.from = `account{${accounts.indexOf(from)}}`
self.data._usedAccounts[record.from] = 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 (error) return console.log(error)
if (call) return if (call) return
var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress
if (!address) return // not a contract creation 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 // save back created addresses for the convertion from tokens to real adresses
this.data._createdContracts[address] = timestamp this.data._createdContracts[address] = timestamp
this.data._createdContractsReverse[timestamp] = address this.data._createdContractsReverse[timestamp] = address
@ -172,15 +175,15 @@ class Recorder {
* @param {Function} newContractFn * @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 var self = this
self.setListen(false) self.setListen(false)
self.logCallBack(`Running ${records.length} transaction(s) ...`) logCallBack(`Running ${records.length} transaction(s) ...`)
async.eachOfSeries(records, function (tx, index, cb) { async.eachOfSeries(records, function (tx, index, cb) {
var record = self.resolveAddress(tx.record, accounts, options) var record = self.resolveAddress(tx.record, accounts, options)
var abi = abis[tx.record.abi] var abi = abis[tx.record.abi]
if (!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 return
} }
/* Resolve Library */ /* Resolve Library */
@ -204,7 +207,7 @@ class Recorder {
fnABI = txHelper.getFunction(abi, record.name + record.inputs) fnABI = txHelper.getFunction(abi, record.name + record.inputs)
} }
if (!fnABI) { 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') cb('cannot resolve abi')
return return
} }
@ -224,28 +227,29 @@ class Recorder {
tx.record.parameters[index] = value tx.record.parameters[index] = value
}) })
} catch (e) { } 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 return
} }
} }
var data = format.encodeData(fnABI, tx.record.parameters, tx.record.bytecode) var data = format.encodeData(fnABI, tx.record.parameters, tx.record.bytecode)
if (data.error) { 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) cb(data.error)
return return
} else { } else {
self.logCallBack(`(${index}) ${JSON.stringify(record, null, '\t')}`) logCallBack(`(${index}) ${JSON.stringify(record, null, '\t')}`)
self.logCallBack(`(${index}) data: ${data.data}`) logCallBack(`(${index}) data: ${data.data}`)
record.data = { dataHex: data.data, funArgs: tx.record.parameters, funAbi: fnABI, contractBytecode: tx.record.bytecode, contractName: tx.record.contractName } 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) { self.udapp.runTx(record, confirmationCb, continueCb, promptCb,
function (err, txResult) {
if (err) { if (err) {
console.error(err) console.error(err)
self.logCallBack(err + '. Execution failed at ' + index) logCallBack(err + '. Execution failed at ' + index)
} else { } else {
var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress var address = executionContext.isVM() ? txResult.result.createdAddress : txResult.result.contractAddress
if (address) { if (address) {
address = addressToString(address) address = self.addressToString(address)
// save back created addresses for the convertion from tokens to real adresses // save back created addresses for the convertion from tokens to real adresses
self.data._createdContracts[address] = tx.timestamp self.data._createdContracts[address] = tx.timestamp
self.data._createdContractsReverse[tx.timestamp] = address self.data._createdContractsReverse[tx.timestamp] = address
@ -253,12 +257,12 @@ class Recorder {
} }
} }
cb(err) cb(err)
})
}, () => { self.setListen(true); self.clearAll() })
} }
)
}, () => { self.setListen(true); self.clearAll() })
} }
function addressToString (address) { addressToString (address) {
if (!address) return null if (!address) return null
if (typeof address !== 'string') { if (typeof address !== 'string') {
address = address.toString('hex') address = address.toString('hex')
@ -269,4 +273,107 @@ function addressToString (address) {
return address return 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)
})
})
}
}
module.exports = Recorder module.exports = Recorder

@ -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

@ -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`<i class="fa fa-play runtransaction ${css2.runTxs} ${css.icon}" title="Run Transactions" aria-hidden="true"></i>`
this.recordButton = yo`
<i class="fa fa-floppy-o savetransaction ${css2.recorder} ${css.icon}"
onclick=${this.triggerRecordButton.bind(this)} title="Save Transactions" aria-hidden="true">
</i>`
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`<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>
${msg}
</div>`,
{
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

@ -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`<span class=${css.network}></span>`
var environmentEl = yo`
<div class="${css.crow}">
<div id="selectExEnv" class="${css.col1_1}">
Environment
</div>
<div class=${css.environment}>
${this.netUI}
<select id="selectExEnvOptions" onchange=${() => { this.updateNetwork() }} class="${css.select}">
<option id="vm-mode"
title="Execution environment does not connect to any node, everything is local and in memory only."
value="vm" checked name="executionContext"> JavaScript VM
</option>
<option id="injected-mode"
title="Execution environment has been provided by Metamask or similar provider."
value="injected" checked name="executionContext"> Injected Web3
</option>
<option id="web3-mode"
title="Execution environment connects to node at localhost (or via IPC if available), transactions will be sent to the network and can cause loss of money or worse!
If this page is served via https and you access your node via http, it might not work. In this case, try cloning the repository and serving it via http."
value="web3" name="executionContext"> Web3 Provider
</option>
</select>
<a href="https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md" target="_blank"><i class="${css.icon} fa fa-info"></i></a>
</div>
</div>
`
var accountEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">
Account
<i class="fa fa-plus-circle ${css.icon}" aria-hidden="true" onclick=${this.newAccount.bind(this)} title="Create a new account"></i>
</div>
<div class=${css.account}>
<select name="txorigin" class="${css.select}" id="txorigin"></select>
${copyToClipboard(() => document.querySelector('#runTabView #txorigin').value)}
<i class="fa fa-pencil-square-o ${css.icon}" aria-hiden="true" onclick=${this.signMessage.bind(this)} title="Sign a message using this account key"></i>
</div>
</div>
`
var gasPriceEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">Gas limit</div>
<input type="number" class="${css.col2}" id="gasLimit" value="3000000">
</div>
`
var valueEl = yo`
<div class="${css.crow}">
<div class="${css.col1_1}">Value</div>
<input type="text" class="${css.col2_1}" id="value" value="0" title="Enter the value and choose the unit">
<select name="unit" class="${css.col2_2}" id="unit">
<option data-unit="wei">wei</option>
<option data-unit="gwei">gwei</option>
<option data-unit="finney">finney</option>
<option data-unit="ether">ether</option>
</select>
</div>
`
var el = yo`
<div class="${css.settings}">
${environmentEl}
${accountEl}
${gasPriceEl}
${valueEl}
</div>
`
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`<option
title="Manually added environment: ${network.url}"
value="${network.name}" name="executionContext"> ${network.name}
</option>`)
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`<div><b>hash:</b>${msgHash}<br><b>signature:</b>${signedData}</div>`)
})
}, 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 = `<i class="${css.networkItem} fa fa-plug" aria-hidden="true"></i> ${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`<option value="${address}" >${address}</option>`)
this.loadedAccounts[address] = 1
}
}
txOrigin.setAttribute('value', accounts[0])
})
}
}
module.exports = SettingsUI

@ -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))
}
}

@ -53,6 +53,8 @@ function Config (storage) {
return this.unpersistedItems[key] 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.setUnpersistedProperty = function (key, value) {
this.unpersistedItems[key] = value this.unpersistedItems[key] = value
} }

@ -3,13 +3,48 @@
var $ = require('jquery') var $ = require('jquery')
var yo = require('yo-yo') var yo = require('yo-yo')
var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN
var helper = require('./lib/helper') var helper = require('./lib/helper')
var copyToClipboard = require('./app/ui/copy-to-clipboard') var copyToClipboard = require('./app/ui/copy-to-clipboard')
var css = require('./universal-dapp-styles') var css = require('./universal-dapp-styles')
var MultiParamManager = require('./multiParamManager') 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.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) { UniversalDAppUI.prototype.renderInstance = function (contract, address, contractName) {
@ -38,10 +73,8 @@ UniversalDAppUI.prototype.renderInstanceFromABI = function (contractABI, address
${copyToClipboard(() => address)} ${copyToClipboard(() => address)}
</div>` </div>`
if (self.udapp.removable_instances) {
var close = yo`<div class="${css.udappClose}" onclick=${remove}><i class="${css.closeIcon} fa fa-close" aria-hidden="true"></i></div>` var close = yo`<div class="${css.udappClose}" onclick=${remove}><i class="${css.closeIcon} fa fa-close" aria-hidden="true"></i></div>`
title.appendChild(close) title.appendChild(close)
}
function remove () { function remove () {
instance.remove() instance.remove()
@ -92,9 +125,135 @@ UniversalDAppUI.prototype.getCallButton = function (args) {
var outputOverride = yo`<div class=${css.value}></div>` // show return value var outputOverride = yo`<div class=${css.value}></div>` // show return value
function clickButton (valArr, inputsValues) { function clickButton (valArr, inputsValues) {
self.udapp.call(true, args, inputsValues, lookupOnly, (decoded) => { var logMsg
if (!args.funABI.constant) {
logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
} else {
logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
}
var value = inputsValues
var confirmationCb = (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.udapp,
(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.udapp._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.')
}
})
}
var continueCb = (error, continueTxExecution, cancelCb) => {
if (error) {
var msg = typeof error !== 'string' ? error.message : error
modalDialog('Gas estimation failed', yo`<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>
${msg}
</div>`,
{
label: 'Send Transaction',
fn: () => {
continueTxExecution()
}}, {
label: 'Cancel Transaction',
fn: () => {
cancelCb()
}
})
} else {
continueTxExecution()
}
}
var outputCb = (decoded) => {
outputOverride.innerHTML = '' outputOverride.innerHTML = ''
outputOverride.appendChild(decoded) 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)
}) })
} }

@ -1,42 +1,19 @@
/* global */
'use strict'
var yo = require('yo-yo')
var async = require('async') var async = require('async')
var ethJSUtil = require('ethereumjs-util') var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN var BN = ethJSUtil.BN
var remixLib = require('remix-lib') var remixLib = require('remix-lib')
var EventManager = require('./lib/events')
var crypto = require('crypto') var crypto = require('crypto')
var TxRunner = remixLib.execution.txRunner var TxRunner = remixLib.execution.txRunner
var txExecution = remixLib.execution.txExecution
var txFormat = remixLib.execution.txFormat
var txHelper = remixLib.execution.txHelper var txHelper = remixLib.execution.txHelper
var executionContext = require('./execution-context') var EventManager = remixLib.EventManager
var modalCustom = require('./app/ui/modal-dialog-custom') var executionContext = remixLib.execution.executionContext
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')
function UniversalDApp (opts, localRegistry) { function UniversalDApp (registry) {
this.event = new EventManager() this.event = new EventManager()
var self = this var self = this
self.data = {}
self._components = {}
self._components.registry = localRegistry || globalRegistry
self.removable = opts.removable
self.removable_instances = opts.removable_instances
self._deps = { self._deps = {
config: self._components.registry.get('config').api, config: registry.get('config').api
compilersartefacts: self._components.registry.get('compilersartefacts').api,
logCallback: self._components.registry.get('logCallback').api
} }
executionContext.event.register('contextChanged', this, function (context) {
self.resetEnvironment()
})
self._txRunnerAPI = { self._txRunnerAPI = {
config: self._deps.config, config: self._deps.config,
detectNetwork: (cb) => { detectNetwork: (cb) => {
@ -49,6 +26,14 @@ function UniversalDApp (opts, localRegistry) {
self.txRunner = new TxRunner({}, self._txRunnerAPI) self.txRunner = new TxRunner({}, self._txRunnerAPI)
self.accounts = {} self.accounts = {}
self.resetEnvironment() 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 () { UniversalDApp.prototype.profile = function () {
@ -68,13 +53,22 @@ UniversalDApp.prototype.resetEnvironment = function () {
this._addAccount('71975fbf7fe448e004ac7ae54cad0a383c3906055a65468714156a07385e96ce', '0x56BC75E2D63100000') this._addAccount('71975fbf7fe448e004ac7ae54cad0a383c3906055a65468714156a07385e96ce', '0x56BC75E2D63100000')
executionContext.vm().stateManager.cache.flush(function () {}) 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) => { this.txRunner.event.register('transactionBroadcasted', (txhash) => {
executionContext.detectNetwork((error, network) => { executionContext.detectNetwork((error, network) => {
if (!error && network) { if (error || !network) return
var txLink = executionContext.txDetailsLink(network.name, txhash) this.event.trigger('transactionBroadcasted', [txhash, network.name])
if (txLink) this._deps.logCallback(yo`<a href="${txLink}" target="_blank">${txLink}</a>`)
}
}) })
}) })
} }
@ -91,18 +85,14 @@ UniversalDApp.prototype.createVMAccount = function (privateKey, balance, cb) {
cb(null, '0x' + ethJSUtil.privateToAddress(privateKey).toString('hex')) cb(null, '0x' + ethJSUtil.privateToAddress(privateKey).toString('hex'))
} }
UniversalDApp.prototype.newAccount = function (password, cb) { UniversalDApp.prototype.newAccount = function (password, passwordPromptCb, cb) {
if (!executionContext.isVM()) { if (!executionContext.isVM()) {
if (!this._deps.config.get('settings/personal-mode')) { if (!this.config.get('settings/personal-mode')) {
return cb('Not running in personal mode') return cb('Not running in personal mode')
} }
modalCustom.promptPassphraseCreation((error, passphrase) => { passwordPromptCb((passphrase) => {
if (error) {
modalCustom.alert(error)
} else {
executionContext.web3().personal.newAccount(passphrase, cb) executionContext.web3().personal.newAccount(passphrase, cb)
} })
}, () => {})
} else { } else {
var privateKey var privateKey
do { do {
@ -138,7 +128,7 @@ UniversalDApp.prototype.getAccounts = function (cb) {
// Weirdness of web3: listAccounts() is sync, `getListAccounts()` is async // Weirdness of web3: listAccounts() is sync, `getListAccounts()` is async
// See: https://github.com/ethereum/web3.js/issues/442 // See: https://github.com/ethereum/web3.js/issues/442
if (this._deps.config.get('settings/personal-mode')) { if (this._deps.config.get('settings/personal-mode')) {
executionContext.web3().personal.getListAccounts(cb) return executionContext.web3().personal.getListAccounts(cb)
} else { } else {
executionContext.web3().eth.getAccounts(cb) executionContext.web3().eth.getAccounts(cb)
} }
@ -190,71 +180,18 @@ UniversalDApp.prototype.getBalanceInEther = function (address, callback) {
}) })
} }
UniversalDApp.prototype.pendingTransactions = function () {
return this.txRunner.pendingTxs
}
UniversalDApp.prototype.pendingTransactionsCount = function () { UniversalDApp.prototype.pendingTransactionsCount = function () {
return Object.keys(this.txRunner.pendingTxs).length return Object.keys(this.txRunner.pendingTxs).length
} }
UniversalDApp.prototype.call = function (isUserAction, args, value, lookupOnly, outputCb) {
const self = this
var logMsg
if (isUserAction) {
if (!args.funABI.constant) {
logMsg = `transact to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
} else {
logMsg = `call to ${args.contractName}.${(args.funABI.name) ? args.funABI.name : '(fallback)'}`
}
}
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 (isUserAction) {
if (!args.funABI.constant) {
self._deps.logCallback(`${logMsg} pending ... `)
} else {
self._deps.logCallback(`${logMsg}`)
}
}
if (args.funABI.type === 'fallback') data.dataHex = value
self.callFunction(args.address, data, args.funABI, (error, txResult) => {
if (!error) {
var isVM = executionContext.isVM()
if (isVM) {
var vmError = txExecution.checkVMError(txResult)
if (vmError.error) {
self._deps.logCallback(`${logMsg} errored: ${vmError.message} `)
return
}
}
if (lookupOnly) {
var decoded = uiUtil.decodeResponseToTreeView(executionContext.isVM() ? txResult.result.vm.return : ethJSUtil.toBuffer(txResult.result), args.funABI)
outputCb(decoded)
}
} else {
self._deps.logCallback(`${logMsg} errored: ${error} `)
}
})
} else {
self._deps.logCallback(`${logMsg} errored: ${error} `)
}
}, (msg) => {
self._deps.logCallback(msg)
}, (data, runTxCallback) => {
// called for libraries deployment
self.runTx(data, runTxCallback)
})
}
/** /**
* deploy the given contract * deploy the given contract
* *
* @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ). * @param {String} data - data to send with the transaction ( return of txFormat.buildData(...) ).
* @param {Function} callback - callback. * @param {Function} callback - callback.
*/ */
UniversalDApp.prototype.createContract = function (data, callback) { UniversalDApp.prototype.createContract = function (data, confirmationCb, continueCb, promptCb, callback) {
this.runTx({data: data, useCall: false}, (error, txResult) => { this.runTx({data: data, useCall: false}, confirmationCb, continueCb, promptCb, (error, txResult) => {
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case) // see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult) callback(error, txResult)
}) })
@ -268,8 +205,8 @@ UniversalDApp.prototype.createContract = function (data, callback) {
* @param {Object} funAbi - abi definition of the function to call. * @param {Object} funAbi - abi definition of the function to call.
* @param {Function} callback - callback. * @param {Function} callback - callback.
*/ */
UniversalDApp.prototype.callFunction = function (to, data, funAbi, callback) { UniversalDApp.prototype.callFunction = function (to, data, funAbi, confirmationCb, continueCb, promptCb, callback) {
this.runTx({to: to, data: data, useCall: funAbi.constant}, (error, txResult) => { this.runTx({to: to, data: data, useCall: funAbi.constant}, confirmationCb, continueCb, promptCb, (error, txResult) => {
// see universaldapp.js line 660 => 700 to check possible values of txResult (error case) // see universaldapp.js line 660 => 700 to check possible values of txResult (error case)
callback(error, txResult) callback(error, txResult)
}) })
@ -338,7 +275,7 @@ UniversalDApp.prototype.silentRunTx = function (tx, cb) {
cb) cb)
} }
UniversalDApp.prototype.runTx = function (args, cb) { UniversalDApp.prototype.runTx = function (args, confirmationCb, continueCb, promptCb, cb) {
const self = this const self = this
async.waterfall([ async.waterfall([
function getGasLimit (next) { function getGasLimit (next) {
@ -384,85 +321,7 @@ UniversalDApp.prototype.runTx = function (args, cb) {
var timestamp = Date.now() var timestamp = Date.now()
self.event.trigger('initiatingTransaction', [timestamp, tx, payLoad]) self.event.trigger('initiatingTransaction', [timestamp, tx, payLoad])
self.txRunner.rawRun(tx, self.txRunner.rawRun(tx, confirmationCb, continueCb, promptCb,
(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`<div>Gas estimation errored with the following message (see below).
The transaction execution will likely fail. Do you want to force sending? <br>
${msg}
</div>`,
{
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)
},
function (error, result) { function (error, result) {
let eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted') let eventName = (tx.useCall ? 'callExecuted' : 'transactionExecuted')
self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad]) self.event.trigger(eventName, [error, tx.from, tx.to, tx.data, tx.useCall, result, timestamp, payLoad])

Loading…
Cancel
Save