Merge branch 'master' into dgit2

pull/5370/head
bunsenstraat 4 years ago committed by GitHub
commit 237fa33b30
  1. 3
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  2. 3
      apps/remix-ide-e2e/src/tests/ballot_0_4_11.spec.ts
  3. 19
      apps/remix-ide/src/app/tabs/runTab/contractDropdown.js
  4. 59
      apps/remix-ide/src/app/ui/confirmDialog.js
  5. 51
      apps/remix-ide/src/blockchain/blockchain.js
  6. 6
      apps/remix-ide/src/remixAppManager.js
  7. 4
      libs/remix-lib/src/execution/txFormat.ts
  8. 1
      libs/remix-ui/file-explorer/src/lib/actions/fileSystem.ts
  9. 6
      libs/remix-ui/file-explorer/src/lib/file-explorer.tsx
  10. 54
      libs/remix-ui/file-explorer/src/lib/reducers/fileSystem.ts
  11. 28
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx

@ -51,7 +51,8 @@ module.exports = {
browser.clickLaunchIcon('udapp') browser.clickLaunchIcon('udapp')
.click('*[data-id="universalDappUiUdappClose"]') .click('*[data-id="universalDappUiUdappClose"]')
.addFile('ballot.abi', { content: ballotABI }) .addFile('ballot.abi', { content: ballotABI })
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false) // we are not changing the visibility for not checksumed contracts
// .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true) .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
.pause(500) .pause(500)

@ -60,7 +60,8 @@ module.exports = {
browser.clickLaunchIcon('udapp') browser.clickLaunchIcon('udapp')
.click('*[data-id="universalDappUiUdappClose"]') .click('*[data-id="universalDappUiUdappClose"]')
.addFile('ballot.abi', { content: ballotABI }) .addFile('ballot.abi', { content: ballotABI })
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false) // we are not changing the visibility for not checksumed contracts
// .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3B', true, false)
.clickLaunchIcon('filePanel') .clickLaunchIcon('filePanel')
.addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true) .addAtAddressInstance('0x692a70D2e424a56D2C6C27aA97D1a86395877b3A', true, true)
.pause(500) .pause(500)

@ -9,6 +9,7 @@ const confirmDialog = require('../../ui/confirmDialog')
const modalDialog = require('../../ui/modaldialog') const modalDialog = require('../../ui/modaldialog')
const MultiParamManager = require('../../ui/multiParamManager') const MultiParamManager = require('../../ui/multiParamManager')
const helper = require('../../../lib/helper') const helper = require('../../../lib/helper')
const addTooltip = require('../../ui/tooltip')
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
class ContractDropdownUI { class ContractDropdownUI {
@ -97,7 +98,10 @@ class ContractDropdownUI {
enableAtAddress (enable) { enableAtAddress (enable) {
if (enable) { if (enable) {
const address = this.atAddressButtonInput.value const address = this.atAddressButtonInput.value
if (!address || !ethJSUtil.isValidChecksumAddress(address)) return if (!address || !ethJSUtil.isValidAddress(address)) {
this.enableAtAddress(false)
return
}
this.atAddress.removeAttribute('disabled') this.atAddress.removeAttribute('disabled')
this.atAddress.setAttribute('title', 'Interact with the given contract.') this.atAddress.setAttribute('title', 'Interact with the given contract.')
} else { } else {
@ -387,10 +391,19 @@ class ContractDropdownUI {
loadFromAddress () { loadFromAddress () {
this.event.trigger('clearInstance') this.event.trigger('clearInstance')
var address = this.atAddressButtonInput.value let address = this.atAddressButtonInput.value
if (!ethJSUtil.isValidChecksumAddress(address)) {
addTooltip(yo`
<span>
It seems you are not using a checksumed address.
<br>A checksummed address is an address that contains uppercase letters, as specified in <a href="https://eips.ethereum.org/EIPS/eip-55" target="_blank">EIP-55</a>.
<br>Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.
</span>`)
address = ethJSUtil.toChecksumAddress(address)
}
this.dropdownLogic.loadContractFromAddress(address, this.dropdownLogic.loadContractFromAddress(address,
(cb) => { (cb) => {
modalDialogCustom.confirm(null, 'Do you really want to interact with ' + address + ' using the current ABI definition?', cb) modalDialogCustom.confirm('At Address', `Do you really want to interact with ${address} using the current ABI definition?`, cb)
}, },
(error, loadType, abi) => { (error, loadType, abi) => {
if (error) { if (error) {

@ -25,24 +25,49 @@ function confirmDialog (tx, amount, gasEstimation, self, newGasPriceCb, initialP
} }
var el = yo` var el = yo`
<div> <div>
<div>You are creating a transaction on the main network. Click confirm if you are sure to continue.</div> <div>You are about to create a transaction on the Main Network. Confirm the details to send the info to your provider.
<div class=${css.txInfoBox}> <br>The provider for many users is MetaMask. The provider will ask you to sign the transaction before it is sent to the Main Network.</div>
<div>From: ${tx.from}</div> <div class="mt-3 ${css.txInfoBox}">
<div>To: ${tx.to ? tx.to : '(Contract Creation)'}</div> <div>
<div>Amount: ${amount} Ether</div> <span class="text-dark mr-2">From:</span>
<div>Gas estimation: ${gasEstimation}</div> <span>${tx.from}</span>
<div>Gas limit: ${tx.gas}</div> </div>
<div>Gas price: <input id='gasprice' oninput=${onGasPriceChange} /> Gwei <span> (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> to get more info about gas price)</span></div> <div>
<div>Max transaction fee:<span id='txfee'></span></div> <span class="text-dark mr-2">To:</span>
<div>Data:</div> <span>${tx.to ? tx.to : '(Contract Creation)'}</span>
<pre class=${css.wrapword}>${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre> </div>
<div>
<span class="text-dark mr-2">Amount:</span>
<span>${amount} Ether</span>
</div>
<div>
<span class="text-dark mr-2">Gas estimation:</span>
<span>${gasEstimation}</span>
</div>
<div>
<span class="text-dark mr-2">Gas limit:</span>
<span>${tx.gas}</span>
</div>
<div>
<span class="text-dark mr-2">Max transaction fee:</span>
<span id='txfee'></span>
</div>
<div class="d-flex align-items-center my-1">
<span class="text-dark mr-2">Gas price:</span>
<input class="form-control mr-1" style='width: 40px; height: 28px;'id='gasprice' oninput=${onGasPriceChange} />
<span>Gwei (visit <a target='_blank' href='https://ethgasstation.info'>ethgasstation.info</a> for current gas price info.)</span>
</div>
<div class="d-flex align-items-center">
<span class="text-dark mr-2 mb-3">Data:</span>
<pre class=${css.wrapword}>${tx.data && tx.data.length > 50 ? tx.data.substring(0, 49) + '...' : tx.data} ${copyToClipboard(() => { return tx.data })}</pre>
</div>
</div>
<div class="d-flex py-1 align-items-center custom-control custom-checkbox ${css.checkbox}">
<input class="form-check-input custom-control-input" id='confirmsetting' type="checkbox">
<label class="m-0 form-check-label custom-control-label">Do not show this warning again.</label>
</div>
</div> </div>
<div class=${css.checkbox}>
<input id='confirmsetting' type="checkbox">
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i> Do not ask for confirmation again. (the setting will not be persisted for the next page reload)
</div>
</div>
` `
initialParamsCb((txFeeText, gasPriceValue, gasPriceStatus) => { initialParamsCb((txFeeText, gasPriceValue, gasPriceStatus) => {

@ -1,23 +1,14 @@
const remixLib = require('@remix-project/remix-lib') import Web3 from 'web3'
const txFormat = remixLib.execution.txFormat import { toBuffer } from 'ethereumjs-util'
const txExecution = remixLib.execution.txExecution import { waterfall } from 'async'
const typeConversion = remixLib.execution.typeConversion import { EventEmitter } from 'events'
const Txlistener = remixLib.execution.txListener import { ExecutionContext } from './execution-context'
const TxRunner = remixLib.execution.TxRunner import VMProvider from './providers/vm.js'
const TxRunnerWeb3 = remixLib.execution.TxRunnerWeb3 import InjectedProvider from './providers/injected.js'
const txHelper = remixLib.execution.txHelper import NodeProvider from './providers/node.js'
const EventManager = remixLib.EventManager import { execution, EventManager, helpers } from '@remix-project/remix-lib'
const { ExecutionContext } = require('./execution-context') const { txFormat, txExecution, typeConversion, txListener: Txlistener, TxRunner, TxRunnerWeb3, txHelper } = execution
const Web3 = require('web3') const { txResultHelper: resultToRemixTx } = helpers
const async = require('async')
const { EventEmitter } = require('events')
const { resultToRemixTx } = remixLib.helpers.txResultHelper
const VMProvider = require('./providers/vm.js')
const InjectedProvider = require('./providers/injected.js')
const NodeProvider = require('./providers/node.js')
class Blockchain { class Blockchain {
// NOTE: the config object will need to be refactored out in remix-lib // NOTE: the config object will need to be refactored out in remix-lib
@ -396,7 +387,7 @@ class Blockchain {
runTx (args, confirmationCb, continueCb, promptCb, cb) { runTx (args, confirmationCb, continueCb, promptCb, cb) {
const self = this const self = this
async.waterfall([ waterfall([
function getGasLimit (next) { function getGasLimit (next) {
if (self.transactionContextAPI.getGasLimit) { if (self.transactionContextAPI.getGasLimit) {
return self.transactionContextAPI.getGasLimit(next) return self.transactionContextAPI.getGasLimit(next)
@ -463,16 +454,24 @@ class Blockchain {
try { error = 'error: ' + JSON.stringify(error) } catch (e) {} try { error = 'error: ' + JSON.stringify(error) } catch (e) {}
} }
} }
next(error, result) next(error, result, tx)
} }
) )
} }
], ],
async (error, txResult) => { async (error, txResult, tx) => {
if (error) { if (error) {
return cb(error) return cb(error)
} }
/*
value of txResult is inconsistent:
- transact to contract:
{"receipt": { ... }, "tx":{ ... }, "transactionHash":"0x7ba4c05075210fdbcf4e6660258379db5cc559e15703f9ac6f970a320c2dee09"}
- call to contract:
{"result":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionHash":"0x5236a76152054a8aad0c7135bcc151f03bccb773be88fbf4823184e47fc76247"}
*/
const isVM = this.executionContext.isVM() const isVM = this.executionContext.isVM()
let execResult let execResult
let returnValue = null let returnValue = null
@ -480,7 +479,7 @@ class Blockchain {
execResult = await this.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash) execResult = await this.web3().eth.getExecutionResultFromSimulator(txResult.transactionHash)
if (execResult) { if (execResult) {
// if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value. // if it's not the VM, we don't have return value. We only have the transaction, and it does not contain the return value.
returnValue = (execResult && isVM) ? execResult.returnValue : txResult returnValue = execResult ? execResult.returnValue : toBuffer(txResult.result || '0x0000000000000000000000000000000000000000000000000000000000000000')
const vmError = txExecution.checkVMError(execResult, args.data.contractABI) const vmError = txExecution.checkVMError(execResult, args.data.contractABI)
if (vmError.error) { if (vmError.error) {
return cb(vmError.message) return cb(vmError.message)
@ -488,6 +487,10 @@ class Blockchain {
} }
} }
if (!isVM && tx && tx.useCall) {
returnValue = toBuffer(txResult.result)
}
let address = null let address = null
if (txResult && txResult.receipt) { if (txResult && txResult.receipt) {
address = txResult.receipt.contractAddress address = txResult.receipt.contractAddress

@ -111,6 +111,12 @@ export class RemixAppManager extends PluginManager {
try { try {
const res = await fetch(this.pluginsDirectory) const res = await fetch(this.pluginsDirectory)
plugins = await res.json() plugins = await res.json()
plugins = plugins.filter((plugin) => {
if (plugin.targets && Array.isArray(plugin.targets) && plugin.targets.length > 0) {
return (plugin.targets.includes('remix'))
}
return true
})
localStorage.setItem('plugins-directory', JSON.stringify(plugins)) localStorage.setItem('plugins-directory', JSON.stringify(plugins))
} catch (e) { } catch (e) {
console.log('getting plugins list from localstorage...') console.log('getting plugins list from localstorage...')

@ -357,14 +357,12 @@ export function decodeResponse (response, fnabi) {
if (fnabi.outputs && fnabi.outputs.length > 0) { if (fnabi.outputs && fnabi.outputs.length > 0) {
try { try {
let i let i
const outputTypes = [] const outputTypes = []
for (i = 0; i < fnabi.outputs.length; i++) { for (i = 0; i < fnabi.outputs.length; i++) {
const type = fnabi.outputs[i].type const type = fnabi.outputs[i].type
outputTypes.push(type.indexOf('tuple') === 0 ? makeFullTypeDefinition(fnabi.outputs[i]) : type) outputTypes.push(type.indexOf('tuple') === 0 ? makeFullTypeDefinition(fnabi.outputs[i]) : type)
} }
if (!response || !response.length) response = new Uint8Array(32 * fnabi.outputs.length) // ensuring the data is at least filled by 0 cause `AbiCoder` throws if there's not engouh data
if (!response.length) response = new Uint8Array(32 * fnabi.outputs.length) // ensuring the data is at least filled by 0 cause `AbiCoder` throws if there's not engouh data
// decode data // decode data
const abiCoder = new ethers.utils.AbiCoder() const abiCoder = new ethers.utils.AbiCoder()
const decodedObj = abiCoder.decode(outputTypes, response) const decodedObj = abiCoder.decode(outputTypes, response)

@ -237,7 +237,6 @@ export const init = (provider, workspaceName: string, plugin, registry) => (disp
fetchDirectory(provider, workspaceName)(dispatch) fetchDirectory(provider, workspaceName)(dispatch)
}) })
dispatch(fetchProviderSuccess(provider)) dispatch(fetchProviderSuccess(provider))
dispatch(setCurrentWorkspace(workspaceName))
} else { } else {
dispatch(fetchProviderError('No provider available')) dispatch(fetchProviderError('No provider available'))
} }

@ -126,10 +126,8 @@ export const FileExplorer = (props: FileExplorerProps) => {
const editRef = useRef(null) const editRef = useRef(null)
useEffect(() => { useEffect(() => {
if (props.filesProvider) { init(props.filesProvider, props.name, props.plugin, props.registry)(dispatch)
init(props.filesProvider, props.name, props.plugin, props.registry)(dispatch) }, [])
}
}, [props.filesProvider, props.name])
useEffect(() => { useEffect(() => {
const provider = fileSystem.provider.provider const provider = fileSystem.provider.provider

@ -9,7 +9,6 @@ export const fileSystemInitialState = {
files: { files: {
files: [], files: [],
expandPath: [], expandPath: [],
workspaceName: null,
blankPath: null, blankPath: null,
isRequesting: false, isRequesting: false,
isSuccessful: false, isSuccessful: false,
@ -83,7 +82,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state, ...state,
files: { files: {
...state.files, ...state.files,
files: resolveDirectory(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files), files: resolveDirectory(state.provider.provider, action.payload.path, state.files.files, action.payload.files),
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
error: null error: null
@ -135,21 +134,12 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
} }
} }
} }
case 'SET_CURRENT_WORKSPACE': {
return {
...state,
files: {
...state.files,
workspaceName: action.payload
}
}
}
case 'ADD_INPUT_FIELD': { case 'ADD_INPUT_FIELD': {
return { return {
...state, ...state,
files: { files: {
...state.files, ...state.files,
files: addInputField(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files), files: addInputField(state.provider.provider, action.payload.path, state.files.files, action.payload.files),
blankPath: action.payload.path, blankPath: action.payload.path,
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
@ -162,7 +152,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state, ...state,
files: { files: {
...state.files, ...state.files,
files: removeInputField(state.files.workspaceName, state.files.blankPath, state.files.files), files: removeInputField(state.provider.provider, state.files.blankPath, state.files.files),
blankPath: null, blankPath: null,
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
@ -175,7 +165,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state, ...state,
files: { files: {
...state.files, ...state.files,
files: fileAdded(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files), files: fileAdded(state.provider.provider, action.payload.path, state.files.files, action.payload.files),
expandPath: [...new Set([...state.files.expandPath, action.payload.path])], expandPath: [...new Set([...state.files.expandPath, action.payload.path])],
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
@ -188,7 +178,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state, ...state,
files: { files: {
...state.files, ...state.files,
files: folderAdded(state.files.workspaceName, action.payload.path, state.files.files, action.payload.files), files: folderAdded(state.provider.provider, action.payload.path, state.files.files, action.payload.files),
expandPath: [...new Set([...state.files.expandPath, action.payload.path])], expandPath: [...new Set([...state.files.expandPath, action.payload.path])],
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
@ -201,7 +191,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state, ...state,
files: { files: {
...state.files, ...state.files,
files: fileRemoved(state.files.workspaceName, action.payload.path, action.payload.removePath, state.files.files), files: fileRemoved(state.provider.provider, action.payload.path, action.payload.removePath, state.files.files),
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
error: null error: null
@ -213,7 +203,7 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
...state, ...state,
files: { files: {
...state.files, ...state.files,
files: fileRenamed(state.files.workspaceName, action.payload.path, action.payload.removePath, state.files.files, action.payload.files), files: fileRenamed(state.provider.provider, action.payload.path, action.payload.removePath, state.files.files, action.payload.files),
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
error: null error: null
@ -244,7 +234,9 @@ export const fileSystemReducer = (state = fileSystemInitialState, action: Action
} }
} }
const resolveDirectory = (root, path: string, files, content) => { const resolveDirectory = (provider, path: string, files, content) => {
const root = provider.workspace || provider.type
if (path === root) return { [root]: { ...content[root], ...files[root] } } if (path === root) return { [root]: { ...content[root], ...files[root] } }
const pathArr: string[] = path.split('/').filter(value => value) const pathArr: string[] = path.split('/').filter(value => value)
@ -287,14 +279,18 @@ const removePath = (root, path: string, pathName, files) => {
return files return files
} }
const addInputField = (root, path: string, files, content) => { const addInputField = (provider, path: string, files, content) => {
const root = provider.workspace || provider.type || ''
if (path === root) return { [root]: { ...content[root], ...files[root] } } if (path === root) return { [root]: { ...content[root], ...files[root] } }
const result = resolveDirectory(root, path, files, content) const result = resolveDirectory(provider, path, files, content)
return result return result
} }
const removeInputField = (root, path: string, files) => { const removeInputField = (provider, path: string, files) => {
const root = provider.workspace || provider.type || ''
if (path === root) { if (path === root) {
delete files[root][path + '/' + 'blank'] delete files[root][path + '/' + 'blank']
return files return files
@ -302,15 +298,17 @@ const removeInputField = (root, path: string, files) => {
return removePath(root, path, path + '/' + 'blank', files) return removePath(root, path, path + '/' + 'blank', files)
} }
const fileAdded = (root, path: string, files, content) => { const fileAdded = (provider, path: string, files, content) => {
return resolveDirectory(root, path, files, content) return resolveDirectory(provider, path, files, content)
} }
const folderAdded = (root, path: string, files, content) => { const folderAdded = (provider, path: string, files, content) => {
return resolveDirectory(root, path, files, content) return resolveDirectory(provider, path, files, content)
} }
const fileRemoved = (root, path: string, removedPath: string, files) => { const fileRemoved = (provider, path: string, removedPath: string, files) => {
const root = provider.workspace || provider.type || ''
if (path === root) { if (path === root) {
delete files[root][removedPath] delete files[root][removedPath]
@ -319,7 +317,9 @@ const fileRemoved = (root, path: string, removedPath: string, files) => {
return removePath(root, path, extractNameFromKey(removedPath), files) return removePath(root, path, extractNameFromKey(removedPath), files)
} }
const fileRenamed = (root, path: string, removePath: string, files, content) => { const fileRenamed = (provider, path: string, removePath: string, files, content) => {
const root = provider.workspace || provider.type || ''
if (path === root) { if (path === root) {
const allFiles = { [root]: { ...content[root], ...files[root] } } const allFiles = { [root]: { ...content[root], ...files[root] } }

@ -97,10 +97,10 @@ export const Workspace = (props: WorkspaceProps) => {
props.fileManager.setMode('browser') props.fileManager.setMode('browser')
} }
} }
props.localhost.event.off('disconnected', localhostDisconnect)
props.localhost.event.on('disconnected', localhostDisconnect)
useEffect(() => { useEffect(() => {
props.localhost.event.off('disconnected', localhostDisconnect)
props.localhost.event.on('disconnected', localhostDisconnect)
props.localhost.event.on('connected', () => { props.localhost.event.on('connected', () => {
remixdExplorer.show() remixdExplorer.show()
setWorkspace(LOCALHOST) setWorkspace(LOCALHOST)
@ -253,18 +253,18 @@ export const Workspace = (props: WorkspaceProps) => {
const remixdExplorer = { const remixdExplorer = {
hide: async () => { hide: async () => {
// If 'connect to localhost' is clicked from home tab, mode is not 'localhost' // If 'connect to localhost' is clicked from home tab, mode is not 'localhost'
if (props.fileManager.mode === 'localhost') { // if (props.fileManager.mode === 'localhost') {
await setWorkspace(NO_WORKSPACE) await setWorkspace(NO_WORKSPACE)
props.fileManager.setMode('browser') props.fileManager.setMode('browser')
setState(prevState => { setState(prevState => {
return { ...prevState, hideRemixdExplorer: true, loadingLocalhost: false } return { ...prevState, hideRemixdExplorer: true, loadingLocalhost: false }
}) })
} else { // } else {
// Hide spinner in file explorer // // Hide spinner in file explorer
setState(prevState => { // setState(prevState => {
return { ...prevState, loadingLocalhost: false } // return { ...prevState, loadingLocalhost: false }
}) // })
} // }
}, },
show: () => { show: () => {
props.fileManager.setMode('localhost') props.fileManager.setMode('localhost')

Loading…
Cancel
Save