Unverified Commit 236f26a8 authored by yann300's avatar yann300 Committed by GitHub

Merge pull request #1648 from ethereum/refactor_runtab

Refactor udapp, run tab and related components
parents 1fe0ede5 296c28cb
...@@ -313,13 +313,16 @@ Please make a backup of your contracts and start using http://remix.ethereum.org ...@@ -313,13 +313,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 -----------------
...@@ -434,7 +437,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org ...@@ -434,7 +437,7 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
self._components.righthandpanel.init() self._components.righthandpanel.init()
self._components.righthandpanel.event.register('resize', delta => self._adjustLayout('right', delta)) self._components.righthandpanel.event.register('resize', delta => self._adjustLayout('right', delta))
var txLogger = new TxLogger() // eslint-disable-line var txLogger = new TxLogger() // eslint-disable-line
var queryParams = new QueryParams() var queryParams = new QueryParams()
......
...@@ -16,6 +16,7 @@ var css = csjs` ...@@ -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 { ...@@ -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()
......
...@@ -11,7 +11,42 @@ const DraggableContent = require('../ui/draggableContent') ...@@ -11,7 +11,42 @@ const DraggableContent = require('../ui/draggableContent')
const styles = styleguide.chooser() const styles = styleguide.chooser()
module.exports = class RighthandPanel { const css = csjs`
.righthandpanel {
display : flex;
flex-direction : column;
top : 0;
right : 0;
bottom : 0;
box-sizing : border-box;
overflow : hidden;
height : 100%;
}
.header {
height : 100%;
}
.dragbar {
position : absolute;
width : 0.5em;
top : 3em;
bottom : 0;
cursor : col-resize;
z-index : 999;
border-left : 2px solid ${styles.rightPanel.bar_Dragging};
}
.ghostbar {
width : 3px;
background-color : ${styles.rightPanel.bar_Ghost};
opacity : 0.5;
position : absolute;
cursor : col-resize;
z-index : 9999;
top : 0;
bottom : 0;
}
`
class RighthandPanel {
constructor ({pluginManager, tabs}, localRegistry) { constructor ({pluginManager, tabs}, localRegistry) {
const self = this const self = this
self._components = {} self._components = {}
...@@ -125,37 +160,4 @@ module.exports = class RighthandPanel { ...@@ -125,37 +160,4 @@ module.exports = class RighthandPanel {
} }
} }
const css = csjs` module.exports = RighthandPanel
.righthandpanel {
display : flex;
flex-direction : column;
top : 0;
right : 0;
bottom : 0;
box-sizing : border-box;
overflow : hidden;
height : 100%;
}
.header {
height : 100%;
}
.dragbar {
position : absolute;
width : 0.5em;
top : 3em;
bottom : 0;
cursor : col-resize;
z-index : 999;
border-left : 2px solid ${styles.rightPanel.bar_Dragging};
}
.ghostbar {
width : 3px;
background-color : ${styles.rightPanel.bar_Ghost};
opacity : 0.5;
position : absolute;
cursor : col-resize;
z-index : 9999;
top : 0;
bottom : 0;
}
`
This diff is collapsed.
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
This diff is collapsed.
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
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
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
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) { ...@@ -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 @@ ...@@ -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 ...@@ -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) { ...@@ -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)
}) })
} }
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment