aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md8
-rw-r--r--app/_locales/en/messages.json2
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/controllers/shapeshift.js180
-rw-r--r--app/scripts/controllers/transactions/index.js11
-rw-r--r--app/scripts/controllers/transactions/tx-gas-utils.js15
-rw-r--r--app/scripts/metamask-controller.js14
-rw-r--r--test/e2e/beta/contract-test/contract.js25
-rw-r--r--test/e2e/beta/contract-test/index.html2
-rw-r--r--test/e2e/beta/from-import-beta-ui.spec.js55
-rw-r--r--test/e2e/beta/metamask-beta-ui.spec.js160
-rw-r--r--test/unit/app/controllers/metamask-controller-test.js2
-rw-r--r--test/unit/app/controllers/transactions/tx-controller-test.js87
-rw-r--r--ui/lib/account-link.js2
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}`
}