diff options
-rw-r--r-- | CHANGELOG.md | 8 | ||||
-rw-r--r-- | app/_locales/en/messages.json | 2 | ||||
-rw-r--r-- | app/manifest.json | 2 | ||||
-rw-r--r-- | app/scripts/controllers/shapeshift.js | 180 | ||||
-rw-r--r-- | app/scripts/controllers/transactions/index.js | 11 | ||||
-rw-r--r-- | app/scripts/controllers/transactions/tx-gas-utils.js | 15 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 14 | ||||
-rw-r--r-- | test/e2e/beta/contract-test/contract.js | 25 | ||||
-rw-r--r-- | test/e2e/beta/contract-test/index.html | 2 | ||||
-rw-r--r-- | test/e2e/beta/from-import-beta-ui.spec.js | 55 | ||||
-rw-r--r-- | test/e2e/beta/metamask-beta-ui.spec.js | 160 | ||||
-rw-r--r-- | test/unit/app/controllers/metamask-controller-test.js | 2 | ||||
-rw-r--r-- | test/unit/app/controllers/transactions/tx-controller-test.js | 87 | ||||
-rw-r--r-- | ui/lib/account-link.js | 2 |
14 files changed, 343 insertions, 222 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 46cb9e063..129af1281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Current Develop Branch +## 6.5.2 Wed May 15 2019 + +- [#6613](https://github.com/MetaMask/metamask-extension/pull/6613): Hardware Wallet Fix +## 6.5.1 Tue May 14 2019 + +- Fix bug where approve method would show a warning. #6602 +- [#6593](https://github.com/MetaMask/metamask-extension/pull/6593): Fix wording of autoLogoutTimeLimitDescription + ## 6.5.0 Fri May 10 2019 - [#6568](https://github.com/MetaMask/metamask-extension/pull/6568): feature: integrate gaba/PhishingController diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index bef278f79..47c1cfde8 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1249,7 +1249,7 @@ "message": "Revert" }, "remove": { - "message": "remove" + "message": "Remove" }, "removeAccount": { "message": "Remove account" diff --git a/app/manifest.json b/app/manifest.json index 1d194a154..2002dee76 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "6.5.0", + "version": "6.5.2", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js deleted file mode 100644 index 9b0287007..000000000 --- a/app/scripts/controllers/shapeshift.js +++ /dev/null @@ -1,180 +0,0 @@ -const ObservableStore = require('obs-store') -const extend = require('xtend') -const log = require('loglevel') - -// every three seconds when an incomplete tx is waiting -const POLLING_INTERVAL = 3000 - -class ShapeshiftController { - - /** - * Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll - * that queries a shapeshift.io API for updates to any pending shapeshift transactions - * - * @typedef {Object} ShapeshiftController - * @param {object} opts Overrides the defaults for the initial state of this.store - * @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an - * shapeShiftTxList array. - * @property {array} shapeShiftTxList An array of ShapeShiftTx objects - * - */ - constructor (opts = {}) { - const initState = extend({ - shapeShiftTxList: [], - }, opts.initState) - this.store = new ObservableStore(initState) - this.pollForUpdates() - } - - /** - * Represents, and contains data about, a single shapeshift transaction. - * @typedef {Object} ShapeShiftTx - * @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the - * user's Metamask account - * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited. - * @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask - * @property {number} time - The time at which the tx was created - * @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link - * https://developer.mozilla.org/en-US/docs/Web/API/Response} - */ - - // - // PUBLIC METHODS - // - - /** - * A getter for the shapeShiftTxList property - * - * @returns {array<ShapeShiftTx>} - * - */ - getShapeShiftTxList () { - const shapeShiftTxList = this.store.getState().shapeShiftTxList - return shapeShiftTxList - } - - /** - * A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit. - * - * @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete - * - */ - getPendingTxs () { - const txs = this.getShapeShiftTxList() - const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') - return pending - } - - /** - * A poll that exists as long as there are pending transactions. Each call attempts to update the data of any - * pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and - * the polling stops. - * - * this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data - * is saved with saveTx. - * - */ - pollForUpdates () { - const pendingTxs = this.getPendingTxs() - - if (pendingTxs.length === 0) { - return - } - - Promise.all(pendingTxs.map((tx) => { - return this.updateTx(tx) - })) - .then((results) => { - results.forEach(tx => this.saveTx(tx)) - this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL) - }) - } - - /** - * Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties - * can be updated. The response property is updated with every call, but the time property is only updated when - * the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx - * depositAddress - * - * @param {ShapeShiftTx} tx The tx to update - * - */ - async updateTx (tx) { - try { - const url = `https://shapeshift.io/txStat/${tx.depositAddress}` - const response = await fetch(url) - const json = await response.json() - tx.response = json - if (tx.response.status === 'complete') { - tx.time = new Date().getTime() - } - return tx - } catch (err) { - log.warn(err) - } - } - - /** - * Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the - * shapeShiftTxList, nothing happens. - * - * @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList - * - */ - saveTx (tx) { - const { shapeShiftTxList } = this.store.getState() - const index = shapeShiftTxList.indexOf(tx) - if (index !== -1) { - shapeShiftTxList[index] = tx - this.store.updateState({ shapeShiftTxList }) - } - } - - /** - * Removes a ShapeShiftTx from the shapeShiftTxList - * - * @param {ShapeShiftTx} tx The tx to remove - * - */ - removeShapeShiftTx () { - const { shapeShiftTxList } = this.store.getState() - const index = shapeShiftTxList.indexOf(index) - if (index !== -1) { - shapeShiftTxList.splice(index, 1) - } - this.updateState({ shapeShiftTxList }) - } - - /** - * Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs - * - * @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the - * user's Metamask account - * @param {string} depositType - An abbreviation of the type of crypto currency to be deposited. - * - */ - createShapeShiftTx (depositAddress, depositType) { - const state = this.store.getState() - let { shapeShiftTxList } = state - - var shapeShiftTx = { - depositAddress, - depositType, - key: 'shapeshift', - time: new Date().getTime(), - response: {}, - } - - if (!shapeShiftTxList) { - shapeShiftTxList = [shapeShiftTx] - } else { - shapeShiftTxList.push(shapeShiftTx) - } - - this.store.updateState({ shapeShiftTxList }) - this.pollForUpdates() - } - -} - -module.exports = ShapeshiftController diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index dc6a043e4..79dba7833 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -191,7 +191,7 @@ class TransactionController extends EventEmitter { } txUtils.validateTxParams(normalizedTxParams) // construct txMeta - const { transactionCategory, code } = await this._determineTransactionCategory(txParams) + const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, @@ -204,7 +204,7 @@ class TransactionController extends EventEmitter { // check whether recipient account is blacklisted recipientBlacklistChecker.checkAccount(txMeta.metamaskNetworkId, normalizedTxParams.to) // add default tx params - txMeta = await this.addTxGasDefaults(txMeta, code) + txMeta = await this.addTxGasDefaults(txMeta, getCodeResponse) } catch (error) { log.warn(error) txMeta.loadingDefaults = false @@ -224,7 +224,7 @@ class TransactionController extends EventEmitter { @param txMeta {Object} - the txMeta object @returns {Promise<object>} resolves with txMeta */ - async addTxGasDefaults (txMeta, code) { + async addTxGasDefaults (txMeta, getCodeResponse) { const txParams = txMeta.txParams // ensure value txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0' @@ -235,7 +235,7 @@ class TransactionController extends EventEmitter { } txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) // set gasLimit - return await this.txGasUtil.analyzeGasUsage(txMeta, code) + return await this.txGasUtil.analyzeGasUsage(txMeta, getCodeResponse) } /** @@ -593,6 +593,7 @@ class TransactionController extends EventEmitter { try { code = await this.query.getCode(to) } catch (e) { + code = null log.warn(e) } // For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0' @@ -601,7 +602,7 @@ class TransactionController extends EventEmitter { result = codeIsEmpty ? SEND_ETHER_ACTION_KEY : CONTRACT_INTERACTION_KEY } - return { transactionCategory: result, code } + return { transactionCategory: result, getCodeResponse: code } } /** diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 2dc461f48..287fb6f44 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -6,6 +6,7 @@ const { } = require('../../lib/util') const log = require('loglevel') const { addHexPrefix } = require('ethereumjs-util') +const { SEND_ETHER_ACTION_KEY } = require('../../../../ui/app/helpers/constants/transactions.js') const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send. import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys' @@ -25,14 +26,13 @@ class TxGasUtil { /** @param txMeta {Object} - the txMeta object - @param code {string} - the code at the txs address, as returned by this.query.getCode(to) @returns {object} the txMeta object with the gas written to the txParams */ - async analyzeGasUsage (txMeta, code) { + async analyzeGasUsage (txMeta, getCodeResponse) { const block = await this.query.getBlockByNumber('latest', false) let estimatedGasHex try { - estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit, code) + estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit, getCodeResponse) } catch (err) { log.warn(err) txMeta.simulationFails = { @@ -55,10 +55,9 @@ class TxGasUtil { Estimates the tx's gas usage @param txMeta {Object} - the txMeta object @param blockGasLimitHex {string} - hex string of the block's gas limit - @param code {string} - the code at the txs address, as returned by this.query.getCode(to) @returns {string} the estimated gas limit as a hex string */ - async estimateTxGas (txMeta, blockGasLimitHex, code) { + async estimateTxGas (txMeta, blockGasLimitHex, getCodeResponse) { const txParams = txMeta.txParams // check if gasLimit is already specified @@ -75,9 +74,9 @@ class TxGasUtil { // see if we can set the gas based on the recipient if (hasRecipient) { // For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0' - const codeIsEmpty = !code || code === '0x' || code === '0x0' + const categorizedAsSimple = txMeta.transactionCategory === SEND_ETHER_ACTION_KEY - if (codeIsEmpty) { + if (categorizedAsSimple) { // if there's data in the params, but there's no contract code, it's not a valid transaction if (txParams.data) { const err = new Error('TxGasUtil - Trying to call a function on a non-contract address') @@ -85,7 +84,7 @@ class TxGasUtil { err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY // set the response on the error so that we can see in logs what the actual response was - err.getCodeResponse = code + err.getCodeResponse = getCodeResponse throw err } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 07054f84b..242028c92 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -27,7 +27,6 @@ const NetworkController = require('./controllers/network') const PreferencesController = require('./controllers/preferences') const AppStateController = require('./controllers/app-state') const CurrencyController = require('./controllers/currency') -const ShapeShiftController = require('./controllers/shapeshift') const InfuraController = require('./controllers/infura') const CachedBalancesController = require('./controllers/cached-balances') const RecentBlocksController = require('./controllers/recent-blocks') @@ -57,6 +56,7 @@ const ethUtil = require('ethereumjs-util') const sigUtil = require('eth-sig-util') const { AddressBookController, + ShapeShiftController, PhishingController, } = require('gaba') const backEndMetaMetricsEvent = require('./lib/backend-metametrics') @@ -237,9 +237,7 @@ module.exports = class MetamaskController extends EventEmitter { }) this.balancesController.updateAllBalances() - this.shapeshiftController = new ShapeShiftController({ - initState: initState.ShapeShiftController, - }) + this.shapeshiftController = new ShapeShiftController(undefined, initState.ShapeShiftController) this.networkController.lookupNetwork() this.messageManager = new MessageManager() @@ -265,7 +263,7 @@ module.exports = class MetamaskController extends EventEmitter { PreferencesController: this.preferencesController.store, AddressBookController: this.addressBookController, CurrencyController: this.currencyController.store, - ShapeShiftController: this.shapeshiftController.store, + ShapeShiftController: this.shapeshiftController, NetworkController: this.networkController.store, InfuraController: this.infuraController.store, CachedBalancesController: this.cachedBalancesController.store, @@ -287,7 +285,7 @@ module.exports = class MetamaskController extends EventEmitter { RecentBlocksController: this.recentBlocksController.store, AddressBookController: this.addressBookController, CurrencyController: this.currencyController.store, - ShapeshiftController: this.shapeshiftController.store, + ShapeshiftController: this.shapeshiftController, InfuraController: this.infuraController.store, ProviderApprovalController: this.providerApprovalController.store, }) @@ -345,7 +343,7 @@ module.exports = class MetamaskController extends EventEmitter { updatePublicConfigStore(this.getState()) publicConfigStore.destroy = () => { - this.removeEventListener('update', updatePublicConfigStore) + this.removeEventListener && this.removeEventListener('update', updatePublicConfigStore) } function updatePublicConfigStore (memState) { @@ -1633,7 +1631,7 @@ module.exports = class MetamaskController extends EventEmitter { * @property {string} depositType - An abbreviation of the type of crypto currency to be deposited. */ createShapeShiftTx (depositAddress, depositType) { - this.shapeshiftController.createShapeShiftTx(depositAddress, depositType) + this.shapeshiftController.createTransaction(depositAddress, depositType) } // network diff --git a/test/e2e/beta/contract-test/contract.js b/test/e2e/beta/contract-test/contract.js index e247e26ea..e1f886c58 100644 --- a/test/e2e/beta/contract-test/contract.js +++ b/test/e2e/beta/contract-test/contract.js @@ -37,6 +37,8 @@ web3.currentProvider.enable().then(() => { const createToken = document.getElementById('createToken') const transferTokens = document.getElementById('transferTokens') const approveTokens = document.getElementById('approveTokens') + const transferTokensWithoutGas = document.getElementById('transferTokensWithoutGas') + const approveTokensWithoutGas = document.getElementById('approveTokensWithoutGas') deployButton.addEventListener('click', async function () { document.getElementById('contractStatus').innerHTML = 'Deploying' @@ -135,6 +137,29 @@ web3.currentProvider.enable().then(() => { console.log(result) }) }) + + transferTokensWithoutGas.addEventListener('click', function (event) { + console.log(`event`, event) + contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '7', { + from: web3.eth.accounts[0], + to: contract.address, + data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', + gasPrice: '20000000000', + }, function (result) { + console.log('result', result) + }) + }) + + approveTokensWithoutGas.addEventListener('click', function () { + contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '7', { + from: web3.eth.accounts[0], + to: contract.address, + data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', + gasPrice: '20000000000', + }, function (result) { + console.log(result) + }) + }) } }) diff --git a/test/e2e/beta/contract-test/index.html b/test/e2e/beta/contract-test/index.html index 0d422ef20..6e134dc36 100644 --- a/test/e2e/beta/contract-test/index.html +++ b/test/e2e/beta/contract-test/index.html @@ -27,6 +27,8 @@ <button id="createToken">Create Token</button> <button id="transferTokens">Transfer Tokens</button> <button id="approveTokens">Approve Tokens</button> + <button id="transferTokensWithoutGas">Transfer Tokens Without Gas</button> + <button id="approveTokensWithoutGas">Approve Tokens Without Gas</button> </div> </div> diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js index a913caa79..625330dbb 100644 --- a/test/e2e/beta/from-import-beta-ui.spec.js +++ b/test/e2e/beta/from-import-beta-ui.spec.js @@ -27,6 +27,8 @@ describe('Using MetaMask with an existing account', function () { const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress' const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3' const testPrivateKey2 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6' + const testPrivateKey3 = 'F4EC2590A0C10DE95FBF4547845178910E40F5035320C516A18C117DE02B5669' + const tinyDelayMs = 200 const regularDelayMs = 1000 const largeDelayMs = regularDelayMs * 2 @@ -323,11 +325,60 @@ describe('Using MetaMask with an existing account', function () { }) }) - describe('Connects to a Hardware wallet', () => { - it('choose Connect Hardware Wallet from the account menu', async () => { + describe('Imports and removes an account', () => { + it('choose Create Account from the account menu', async () => { await driver.findElement(By.css('.account-menu__icon')).click() await delay(regularDelayMs) + const [importAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Import Account')]`)) + await importAccount.click() + await delay(regularDelayMs) + }) + + it('enter private key', async () => { + const privateKeyInput = await findElement(driver, By.css('#private-key-box')) + await privateKeyInput.sendKeys(testPrivateKey3) + await delay(regularDelayMs) + const importButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`)) + await importButtons[0].click() + await delay(regularDelayMs) + }) + + it('should open the remove account modal', async () => { + const [accountName] = await findElements(driver, By.css('.account-name')) + assert.equal(await accountName.getText(), 'Account 5') + await delay(regularDelayMs) + + await driver.findElement(By.css('.account-menu__icon')).click() + await delay(regularDelayMs) + + const accountListItems = await findElements(driver, By.css('.account-menu__account')) + assert.equal(accountListItems.length, 5) + + const removeAccountIcons = await findElements(driver, By.css('.remove-account-icon')) + await removeAccountIcons[1].click() + await delay(tinyDelayMs) + + await findElement(driver, By.css('.confirm-remove-account__account')) + }) + + it('should remove the account', async () => { + const removeButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Remove')]`)) + await removeButton.click() + + await delay(regularDelayMs) + + const [accountName] = await findElements(driver, By.css('.account-name')) + assert.equal(await accountName.getText(), 'Account 1') + await delay(regularDelayMs) + + const accountListItems = await findElements(driver, By.css('.account-menu__account')) + assert.equal(accountListItems.length, 4) + }) + }) + + describe('Connects to a Hardware wallet', () => { + it('choose Connect Hardware Wallet from the account menu', async () => { const [connectAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Connect Hardware Wallet')]`)) await connectAccount.click() await delay(regularDelayMs) diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js index 7fb3de6e4..06778ab99 100644 --- a/test/e2e/beta/metamask-beta-ui.spec.js +++ b/test/e2e/beta/metamask-beta-ui.spec.js @@ -812,10 +812,31 @@ describe('MetaMask', function () { await delay(regularDelayMs) const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input')) - await gasPriceInput.clear() + await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a')) + await delay(50) + + await gasPriceInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasPriceInput.sendKeys(Key.BACK_SPACE) + await delay(50) await gasPriceInput.sendKeys('10') - await gasLimitInput.clear() + await delay(50) + await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a')) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) await gasLimitInput.sendKeys('60001') + await delay(50) + await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e')) + await delay(50) const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`)) await save.click() @@ -1175,7 +1196,7 @@ describe('MetaMask', function () { const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens')]`)) await transferTokens.click() - await closeAllWindowHandlesExcept(driver, extension) + await closeAllWindowHandlesExcept(driver, [extension, dapp]) await driver.switchTo().window(extension) await delay(regularDelayMs) @@ -1228,21 +1249,31 @@ describe('MetaMask', function () { await delay(regularDelayMs) const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input')) - await gasPriceInput.clear() - await delay(tinyDelayMs) + await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a')) + await delay(50) + + await gasPriceInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasPriceInput.sendKeys(Key.BACK_SPACE) + await delay(50) await gasPriceInput.sendKeys('10') - await delay(tinyDelayMs) - await gasLimitInput.clear() - await delay(tinyDelayMs) + await delay(50) await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a')) - await gasLimitInput.sendKeys('60000') + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys(Key.BACK_SPACE) + await delay(50) + await gasLimitInput.sendKeys('60001') + await delay(50) await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e')) - - // Needed for different behaviour of input in different versions of firefox - const gasLimitInputValue = await gasLimitInput.getAttribute('value') - if (gasLimitInputValue === '600001') { - await gasLimitInput.sendKeys(Key.BACK_SPACE) - } + await delay(50) const save = await findElement(driver, By.css('.page-container__footer-button')) await save.click() @@ -1271,6 +1302,105 @@ describe('MetaMask', function () { }) }) + describe('Tranfers a custom token from dapp when no gas value is specified', () => { + it('transfers an already created token, without specifying gas', async () => { + const windowHandles = await driver.getAllWindowHandles() + const extension = windowHandles[0] + const dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) + await closeAllWindowHandlesExcept(driver, [extension, dapp]) + await delay(regularDelayMs) + + await driver.switchTo().window(dapp) + + const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Transfer Tokens Without Gas')]`)) + await transferTokens.click() + + await closeAllWindowHandlesExcept(driver, [extension, dapp]) + await driver.switchTo().window(extension) + await delay(regularDelayMs) + + await driver.wait(async () => { + const pendingTxes = await findElements(driver, By.css('.transaction-list__pending-transactions .transaction-list-item')) + return pendingTxes.length === 1 + }, 10000) + + const [txListItem] = await findElements(driver, By.css('.transaction-list-item')) + const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/)) + await txListItem.click() + await delay(regularDelayMs) + }) + + it('submits the transaction', async function () { + await delay(regularDelayMs) + const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + }) + + it('finds the transaction in the transactions list', async function () { + await driver.wait(async () => { + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + return confirmedTxes.length === 4 + }, 10000) + + const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/)) + const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) + await driver.wait(until.elementTextMatches(txStatuses[0], /Sent Tokens/)) + }) + }) + + describe('Approves a custom token from dapp when no gas value is specified', () => { + it('approves an already created token', async () => { + const windowHandles = await driver.getAllWindowHandles() + const extension = windowHandles[0] + const dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) + await closeAllWindowHandlesExcept(driver, [extension, dapp]) + await delay(regularDelayMs) + + await driver.switchTo().window(dapp) + await delay(tinyDelayMs) + + const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens Without Gas')]`)) + await transferTokens.click() + + await closeAllWindowHandlesExcept(driver, extension) + await driver.switchTo().window(extension) + await delay(regularDelayMs) + + await driver.wait(async () => { + const pendingTxes = await findElements(driver, By.css('.transaction-list__pending-transactions .transaction-list-item')) + return pendingTxes.length === 1 + }, 10000) + + const [txListItem] = await findElements(driver, By.css('.transaction-list-item')) + const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/)) + await txListItem.click() + await delay(regularDelayMs) + }) + + it('submits the transaction', async function () { + await delay(regularDelayMs) + const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`)) + await confirmButton.click() + await delay(regularDelayMs) + }) + + it('finds the transaction in the transactions list', async function () { + await driver.wait(async () => { + const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) + return confirmedTxes.length === 5 + }, 10000) + + const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) + await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/)) + const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) + await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/)) + }) + }) + describe('Hide token', () => { it('hides the token when clicked', async () => { const [hideTokenEllipsis] = await findElements(driver, By.css('.token-list-item__ellipsis')) diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 4768b7c64..a56b8adbd 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -464,7 +464,7 @@ describe('MetaMaskController', function () { depositAddress = '3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc' depositType = 'ETH' - shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList + shapeShiftTxList = metamaskController.shapeshiftController.state.shapeShiftTxList }) it('creates a shapeshift tx', async function () { diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index 9000cd364..8ff409207 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -8,6 +8,13 @@ const TransactionController = require('../../../../../app/scripts/controllers/tr const { TRANSACTION_TYPE_RETRY, } = require('../../../../../app/scripts/controllers/transactions/enums') +const { + TOKEN_METHOD_APPROVE, + TOKEN_METHOD_TRANSFER, + SEND_ETHER_ACTION_KEY, + DEPLOY_CONTRACT_ACTION_KEY, + CONTRACT_INTERACTION_KEY, +} = require('../../../../../ui/app/helpers/constants/transactions.js') const { createTestProviderTools, getTestAccounts } = require('../../../../stub/provider') const noop = () => true @@ -537,6 +544,86 @@ describe('Transaction Controller', function () { }) }) + describe('#_determineTransactionCategory', function () { + it('should return a simple send transactionCategory when to is truthy but data is falsey', async function () { + const result = await txController._determineTransactionCategory({ + to: '0xabc', + data: '', + }) + assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: undefined }) + }) + + it('should return a token transfer transactionCategory when data is for the respective method call', async function () { + const result = await txController._determineTransactionCategory({ + to: '0xabc', + data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a', + }) + assert.deepEqual(result, { transactionCategory: TOKEN_METHOD_TRANSFER, getCodeResponse: undefined }) + }) + + it('should return a token approve transactionCategory when data is for the respective method call', async function () { + const result = await txController._determineTransactionCategory({ + to: '0xabc', + data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', + }) + assert.deepEqual(result, { transactionCategory: TOKEN_METHOD_APPROVE, getCodeResponse: undefined }) + }) + + it('should return a contract deployment transactionCategory when to is falsey and there is data', async function () { + const result = await txController._determineTransactionCategory({ + to: '', + data: '0xabd', + }) + assert.deepEqual(result, { transactionCategory: DEPLOY_CONTRACT_ACTION_KEY, getCodeResponse: undefined }) + }) + + it('should return a simple send transactionCategory with a 0x getCodeResponse when there is data and but the to address is not a contract address', async function () { + const result = await txController._determineTransactionCategory({ + to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', + data: '0xabd', + }) + assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: '0x' }) + }) + + it('should return a simple send transactionCategory with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () { + const result = await txController._determineTransactionCategory({ + to: '0xabc', + data: '0xabd', + }) + assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: null }) + }) + + it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is truthy and there is data and it is not a token transaction', async function () { + const _providerResultStub = { + // 1 gwei + eth_gasPrice: '0x0de0b6b3a7640000', + // by default, all accounts are external accounts (not contracts) + eth_getCode: '0xa', + } + const _provider = createTestProviderTools({ scaffold: _providerResultStub }).provider + const _fromAccount = getTestAccounts()[0] + const _blockTrackerStub = new EventEmitter() + _blockTrackerStub.getCurrentBlock = noop + _blockTrackerStub.getLatestBlock = noop + const _txController = new TransactionController({ + provider: _provider, + getGasPrice: function () { return '0xee6b2800' }, + networkStore: new ObservableStore(currentNetworkId), + txHistoryLimit: 10, + blockTracker: _blockTrackerStub, + signTransaction: (ethTx) => new Promise((resolve) => { + ethTx.sign(_fromAccount.key) + resolve() + }), + }) + const result = await _txController._determineTransactionCategory({ + to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9', + data: 'abd', + }) + assert.deepEqual(result, { transactionCategory: CONTRACT_INTERACTION_KEY, getCodeResponse: '0x0a' }) + }) + }) + describe('#getPendingTransactions', function () { beforeEach(function () { txController.txStateManager._saveTxList([ diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js index f1428ba92..f2e321991 100644 --- a/ui/lib/account-link.js +++ b/ui/lib/account-link.js @@ -1,5 +1,5 @@ module.exports = function (address, network, rpcPrefs) { - if (rpcPrefs.blockExplorerUrl) { + if (rpcPrefs && rpcPrefs.blockExplorerUrl) { return `${rpcPrefs.blockExplorerUrl}/address/${address}` } |