aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2017-08-09 14:30:58 +0800
committerGitHub <noreply@github.com>2017-08-09 14:30:58 +0800
commit0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e (patch)
tree428154973b91dfb2f8519777e67da7a6c75530b3
parent5e9926b0d035a5ba946080e94777ac0bd887d396 (diff)
parent57f6fce6b2524c4b36b591da5e600d0652f4077e (diff)
downloadtangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.tar
tangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.tar.gz
tangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.tar.bz2
tangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.tar.lz
tangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.tar.xz
tangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.tar.zst
tangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.zip
Merge branch 'master' into NewUI-flat
-rw-r--r--.eslintrc4
-rw-r--r--README.md3
-rw-r--r--app/scripts/contentscript.js2
-rw-r--r--app/scripts/controllers/transactions.js198
-rw-r--r--app/scripts/lib/pending-tx-tracker.js163
-rw-r--r--app/scripts/lib/tx-utils.js43
-rw-r--r--app/scripts/lib/util.js36
-rw-r--r--gulpfile.js43
-rw-r--r--package.json71
-rw-r--r--test/stub/provider.js25
-rw-r--r--test/unit/message-manager-test.js2
-rw-r--r--test/unit/nonce-tracker-test.js3
-rw-r--r--test/unit/pending-tx-test.js260
-rw-r--r--test/unit/tx-controller-test.js79
-rw-r--r--test/unit/tx-utils-test.js58
-rw-r--r--test/unit/util-test.js41
-rw-r--r--ui/app/components/buy-button-subview.js2
-rw-r--r--ui/app/components/shapeshift-form.js148
-rw-r--r--ui/app/components/shift-list-item.js2
-rw-r--r--ui/app/components/transaction-list-item.js17
-rw-r--r--ui/app/reducers/app.js9
-rw-r--r--ui/app/store.js4
22 files changed, 767 insertions, 446 deletions
diff --git a/.eslintrc b/.eslintrc
index a57a93cdc..2eb0dc8b5 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -50,7 +50,7 @@
"eqeqeq": [2, "allow-null"],
"generator-star-spacing": [2, { "before": true, "after": true }],
"handle-callback-err": [1, "^(err|error)$" ],
- "indent": [2, 2, { "SwitchCase": 1 }],
+ "indent": "off",
"jsx-quotes": [2, "prefer-single"],
"key-spacing": 1,
"keyword-spacing": [2, { "before": true, "after": true }],
@@ -133,7 +133,7 @@
"no-with": 2,
"one-var": [2, { "initialized": "never" }],
"operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
- "padded-blocks": [1, "never"],
+ "padded-blocks": "off",
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
"semi": [2, "never"],
"semi-spacing": [2, { "before": false, "after": true }],
diff --git a/README.md b/README.md
index 9aded09c1..e39058790 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-# MetaMask Plugin [![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master)
+# MetaMask Plugin [![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/)
+
## Support
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 6fde0edcd..acacf5d4c 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -86,7 +86,7 @@ function suffixCheck () {
var currentUrl = window.location.href
var currentRegex
for (let i = 0; i < prohibitedTypes.length; i++) {
- currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`)
+ currentRegex = new RegExp(`\\.${prohibitedTypes[i]}$`)
if (currentRegex.test(currentUrl)) {
return false
}
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index 308d43cb0..58c468e22 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -5,6 +5,7 @@ const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const EthQuery = require('ethjs-query')
const TxProviderUtil = require('../lib/tx-utils')
+const PendingTransactionTracker = require('../lib/pending-tx-tracker')
const createId = require('../lib/random-id')
const NonceTracker = require('../lib/nonce-tracker')
@@ -20,6 +21,9 @@ module.exports = class TransactionController extends EventEmitter {
this.txHistoryLimit = opts.txHistoryLimit
this.provider = opts.provider
this.blockTracker = opts.blockTracker
+ this.signEthTx = opts.signTransaction
+ this.ethStore = opts.ethStore
+
this.nonceTracker = new NonceTracker({
provider: this.provider,
getPendingTransactions: (address) => {
@@ -31,15 +35,38 @@ module.exports = class TransactionController extends EventEmitter {
},
})
this.query = new EthQuery(this.provider)
- this.txProviderUtils = new TxProviderUtil(this.query)
- this.blockTracker.on('rawBlock', this.checkForTxInBlock.bind(this))
+ this.txProviderUtil = new TxProviderUtil(this.provider)
+
+ this.pendingTxTracker = new PendingTransactionTracker({
+ provider: this.provider,
+ nonceTracker: this.nonceTracker,
+ getBalance: (address) => {
+ const account = this.ethStore.getState().accounts[address]
+ if (!account) return
+ return account.balance
+ },
+ publishTransaction: this.txProviderUtil.publishTransaction.bind(this.txProviderUtil),
+ getPendingTransactions: () => {
+ const network = this.getNetwork()
+ return this.getFilteredTxList({
+ status: 'submitted',
+ metamaskNetworkId: network,
+ })
+ },
+ })
+
+ this.pendingTxTracker.on('txWarning', this.updateTx.bind(this))
+ this.pendingTxTracker.on('txFailed', this.setTxStatusFailed.bind(this))
+ this.pendingTxTracker.on('txConfirmed', this.setTxStatusConfirmed.bind(this))
+
+ this.blockTracker.on('rawBlock', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
// this is a little messy but until ethstore has been either
// removed or redone this is to guard against the race condition
// where ethStore hasent been populated by the results yet
- this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.resubmitPendingTxs.bind(this)))
- this.blockTracker.on('sync', this.queryPendingTxs.bind(this))
- this.signEthTx = opts.signTransaction
- this.ethStore = opts.ethStore
+ this.blockTracker.once('latest', () => {
+ this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
+ })
+ this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
// memstore is computed from a few different stores
this._updateMemstore()
this.store.subscribe(() => this._updateMemstore())
@@ -171,7 +198,7 @@ module.exports = class TransactionController extends EventEmitter {
async addUnapprovedTransaction (txParams) {
// validate
- await this.txProviderUtils.validateTxParams(txParams)
+ await this.txProviderUtil.validateTxParams(txParams)
// construct txMeta
const txMeta = {
id: createId(),
@@ -197,7 +224,7 @@ module.exports = class TransactionController extends EventEmitter {
txParams.gasPrice = gasPrice
}
// set gasLimit
- return await this.txProviderUtils.analyzeGasUsage(txMeta)
+ return await this.txProviderUtil.analyzeGasUsage(txMeta)
}
async updateAndApproveTransaction (txMeta) {
@@ -226,11 +253,7 @@ module.exports = class TransactionController extends EventEmitter {
// must set transaction to submitted/failed before releasing lock
nonceLock.releaseLock()
} catch (err) {
- this.setTxStatusFailed(txId, {
- stack: err.stack || err.message,
- errCode: err.errCode || err,
- message: err.message || 'Transaction failed during approval',
- })
+ this.setTxStatusFailed(txId, err)
// must set transaction to submitted/failed before releasing lock
if (nonceLock) nonceLock.releaseLock()
// continue with error chain
@@ -244,7 +267,7 @@ module.exports = class TransactionController extends EventEmitter {
const fromAddress = txParams.from
// add network/chain id
txParams.chainId = this.getChainId()
- const ethTx = this.txProviderUtils.buildEthTxFromParams(txParams)
+ const ethTx = this.txProviderUtil.buildEthTxFromParams(txParams)
await this.signEthTx(ethTx, fromAddress)
this.setTxStatusSigned(txMeta.id)
const rawTx = ethUtil.bufferToHex(ethTx.serialize())
@@ -255,7 +278,7 @@ module.exports = class TransactionController extends EventEmitter {
const txMeta = this.getTx(txId)
txMeta.rawTx = rawTx
this.updateTx(txMeta)
- const txHash = await this.txProviderUtils.publishTransaction(rawTx)
+ const txHash = await this.txProviderUtil.publishTransaction(rawTx)
this.setTxHash(txId, txHash)
this.setTxStatusSubmitted(txId)
}
@@ -359,9 +382,12 @@ module.exports = class TransactionController extends EventEmitter {
this._setTxStatus(txId, 'confirmed')
}
- setTxStatusFailed (txId, reason) {
+ setTxStatusFailed (txId, err) {
const txMeta = this.getTx(txId)
- txMeta.err = reason
+ txMeta.err = {
+ message: err.toString(),
+ stack: err.stack,
+ }
this.updateTx(txMeta)
this._setTxStatus(txId, 'failed')
}
@@ -374,73 +400,6 @@ module.exports = class TransactionController extends EventEmitter {
this.updateTx(txMeta)
}
- // checks if a signed tx is in a block and
- // if included sets the tx status as 'confirmed'
- checkForTxInBlock (block) {
- const signedTxList = this.getFilteredTxList({status: 'submitted'})
- if (!signedTxList.length) return
- signedTxList.forEach((txMeta) => {
- const txHash = txMeta.hash
- const txId = txMeta.id
-
- if (!txHash) {
- const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
- noTxHashErr.name = 'NoTxHashError'
- this.setTxStatusFailed(txId, noTxHashErr)
- }
-
-
- block.transactions.forEach((tx) => {
- if (tx.hash === txHash) this.setTxStatusConfirmed(txId)
- })
- })
- }
-
- queryPendingTxs ({oldBlock, newBlock}) {
- // check pending transactions on start
- if (!oldBlock) {
- this._checkPendingTxs()
- return
- }
- // if we synced by more than one block, check for missed pending transactions
- const diff = Number.parseInt(newBlock.number) - Number.parseInt(oldBlock.number)
- if (diff > 1) this._checkPendingTxs()
- }
-
- resubmitPendingTxs () {
- const pending = this.getTxsByMetaData('status', 'submitted')
- // only try resubmitting if their are transactions to resubmit
- if (!pending.length) return
- pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => {
- /*
- Dont marked as failed if the error is a "known" transaction warning
- "there is already a transaction with the same sender-nonce
- but higher/same gas price"
- */
- const errorMessage = err.message.toLowerCase()
- const isKnownTx = (
- // geth
- errorMessage.includes('replacement transaction underpriced')
- || errorMessage.includes('known transaction')
- // parity
- || errorMessage.includes('gas price too low to replace')
- || errorMessage.includes('transaction with the same hash was already imported')
- // other
- || errorMessage.includes('gateway timeout')
- || errorMessage.includes('nonce too low')
- )
- // ignore resubmit warnings, return early
- if (isKnownTx) return
- // encountered real error - transition to error state
- this.setTxStatusFailed(txMeta.id, {
- stack: err.stack || err.message,
- errCode: err.errCode || err,
- message: err.message,
- })
- }))
- }
-
-
/* _____________________________________
| |
| PRIVATE METHODS |
@@ -482,75 +441,4 @@ module.exports = class TransactionController extends EventEmitter {
})
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
}
-
- async _resubmitTx (txMeta) {
- const address = txMeta.txParams.from
- const balance = this.ethStore.getState().accounts[address].balance
- if (!('retryCount' in txMeta)) txMeta.retryCount = 0
-
- // if the value of the transaction is greater then the balance, fail.
- if (!this.txProviderUtils.sufficientBalance(txMeta.txParams, balance)) {
- const message = 'Insufficient balance.'
- this.setTxStatusFailed(txMeta.id, {
- stack: '_resubmitTx: custom tx-controller error',
- message,
- })
- log.error(message)
- return
- }
-
- // Only auto-submit already-signed txs:
- if (!('rawTx' in txMeta)) return
-
- // Increment a try counter.
- txMeta.retryCount++
- const rawTx = txMeta.rawTx
- return await this.txProviderUtils.publishTransaction(rawTx)
- }
-
- // checks the network for signed txs and
- // if confirmed sets the tx status as 'confirmed'
- async _checkPendingTxs () {
- const signedTxList = this.getFilteredTxList({status: 'submitted'})
- // in order to keep the nonceTracker accurate we block it while updating pending transactions
- const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
- try {
- await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
- } catch (err) {
- console.error('TransactionController - Error updating pending transactions')
- console.error(err)
- }
- nonceGlobalLock.releaseLock()
- }
-
- async _checkPendingTx (txMeta) {
- const txHash = txMeta.hash
- const txId = txMeta.id
- // extra check in case there was an uncaught error during the
- // signature and submission process
- if (!txHash) {
- const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
- noTxHashErr.name = 'NoTxHashError'
- this.setTxStatusFailed(txId, noTxHashErr)
- }
- // get latest transaction status
- let txParams
- try {
- txParams = await this.query.getTransactionByHash(txHash)
- if (!txParams) return
- if (txParams.blockNumber) {
- this.setTxStatusConfirmed(txId)
- }
- } catch (err) {
- if (err || !txParams) {
- txMeta.err = {
- isWarning: true,
- errorCode: err,
- message: 'There was a problem loading this transaction.',
- }
- this.updateTx(txMeta)
- throw err
- }
- }
- }
} \ No newline at end of file
diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js
new file mode 100644
index 000000000..19720db3f
--- /dev/null
+++ b/app/scripts/lib/pending-tx-tracker.js
@@ -0,0 +1,163 @@
+const EventEmitter = require('events')
+const EthQuery = require('ethjs-query')
+const sufficientBalance = require('./util').sufficientBalance
+/*
+
+ Utility class for tracking the transactions as they
+ go from a pending state to a confirmed (mined in a block) state
+
+ As well as continues broadcast while in the pending state
+
+ ~config is not optional~
+ requires a: {
+ provider: //,
+ nonceTracker: //see nonce tracker,
+ getBalnce: //(address) a function for getting balances,
+ getPendingTransactions: //() a function for getting an array of transactions,
+ publishTransaction: //(rawTx) a async function for publishing raw transactions,
+ }
+
+*/
+
+module.exports = class PendingTransactionTracker extends EventEmitter {
+ constructor (config) {
+ super()
+ this.query = new EthQuery(config.provider)
+ this.nonceTracker = config.nonceTracker
+
+ this.getBalance = config.getBalance
+ this.getPendingTransactions = config.getPendingTransactions
+ this.publishTransaction = config.publishTransaction
+ }
+
+ // checks if a signed tx is in a block and
+ // if included sets the tx status as 'confirmed'
+ checkForTxInBlock (block) {
+ const signedTxList = this.getPendingTransactions()
+ if (!signedTxList.length) return
+ signedTxList.forEach((txMeta) => {
+ const txHash = txMeta.hash
+ const txId = txMeta.id
+
+ if (!txHash) {
+ const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
+ noTxHashErr.name = 'NoTxHashError'
+ this.emit('txFailed', txId, noTxHashErr)
+ return
+ }
+
+
+ block.transactions.forEach((tx) => {
+ if (tx.hash === txHash) this.emit('txConfirmed', txId)
+ })
+ })
+ }
+
+ queryPendingTxs ({oldBlock, newBlock}) {
+ // check pending transactions on start
+ if (!oldBlock) {
+ this._checkPendingTxs()
+ return
+ }
+ // if we synced by more than one block, check for missed pending transactions
+ const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16)
+ if (diff > 1) this._checkPendingTxs()
+ }
+
+
+ resubmitPendingTxs () {
+ const pending = this.getPendingTransactions()
+ // only try resubmitting if their are transactions to resubmit
+ if (!pending.length) return
+ pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => {
+ /*
+ Dont marked as failed if the error is a "known" transaction warning
+ "there is already a transaction with the same sender-nonce
+ but higher/same gas price"
+ */
+ const errorMessage = err.message.toLowerCase()
+ const isKnownTx = (
+ // geth
+ errorMessage.includes('replacement transaction underpriced')
+ || errorMessage.includes('known transaction')
+ // parity
+ || errorMessage.includes('gas price too low to replace')
+ || errorMessage.includes('transaction with the same hash was already imported')
+ // other
+ || errorMessage.includes('gateway timeout')
+ || errorMessage.includes('nonce too low')
+ )
+ // ignore resubmit warnings, return early
+ if (isKnownTx) return
+ // encountered real error - transition to error state
+ this.emit('txFailed', txMeta.id, err)
+ }))
+ }
+
+ async _resubmitTx (txMeta) {
+ const address = txMeta.txParams.from
+ const balance = this.getBalance(address)
+ if (balance === undefined) return
+ if (!('retryCount' in txMeta)) txMeta.retryCount = 0
+
+ // if the value of the transaction is greater then the balance, fail.
+ if (!sufficientBalance(txMeta.txParams, balance)) {
+ const insufficientFundsError = new Error('Insufficient balance during rebroadcast.')
+ this.emit('txFailed', txMeta.id, insufficientFundsError)
+ log.error(insufficientFundsError)
+ return
+ }
+
+ // Only auto-submit already-signed txs:
+ if (!('rawTx' in txMeta)) return
+
+ // Increment a try counter.
+ txMeta.retryCount++
+ const rawTx = txMeta.rawTx
+ return await this.publishTransaction(rawTx)
+ }
+
+ async _checkPendingTx (txMeta) {
+ const txHash = txMeta.hash
+ const txId = txMeta.id
+ // extra check in case there was an uncaught error during the
+ // signature and submission process
+ if (!txHash) {
+ const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
+ noTxHashErr.name = 'NoTxHashError'
+ this.emit('txFailed', txId, noTxHashErr)
+ return
+ }
+ // get latest transaction status
+ let txParams
+ try {
+ txParams = await this.query.getTransactionByHash(txHash)
+ if (!txParams) return
+ if (txParams.blockNumber) {
+ this.emit('txConfirmed', txId)
+ }
+ } catch (err) {
+ txMeta.warning = {
+ error: err,
+ message: 'There was a problem loading this transaction.',
+ }
+ this.emit('txWarning', txMeta)
+ throw err
+ }
+ }
+
+ // checks the network for signed txs and
+ // if confirmed sets the tx status as 'confirmed'
+ async _checkPendingTxs () {
+ const signedTxList = this.getPendingTransactions()
+ // in order to keep the nonceTracker accurate we block it while updating pending transactions
+ const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
+ try {
+ await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
+ } catch (err) {
+ console.error('PendingTransactionWatcher - Error updating pending transactions')
+ console.error(err)
+ }
+ nonceGlobalLock.releaseLock()
+ }
+} \ No newline at end of file
diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js
index 3687a9652..b64ea6712 100644
--- a/app/scripts/lib/tx-utils.js
+++ b/app/scripts/lib/tx-utils.js
@@ -1,7 +1,11 @@
-const ethUtil = require('ethereumjs-util')
+const EthQuery = require('ethjs-query')
const Transaction = require('ethereumjs-tx')
const normalize = require('eth-sig-util').normalize
-const BN = ethUtil.BN
+const {
+ hexToBn,
+ BnMultiplyByFraction,
+ bnToHex,
+} = require('./util')
/*
tx-utils are utility methods for Transaction manager
@@ -9,9 +13,9 @@ its passed ethquery
and used to do things like calculate gas of a tx.
*/
-module.exports = class txProvideUtils {
- constructor (ethQuery) {
- this.query = ethQuery
+module.exports = class txProvideUtil {
+ constructor (provider) {
+ this.query = new EthQuery(provider)
}
async analyzeGasUsage (txMeta) {
@@ -91,31 +95,4 @@ module.exports = class txProvideUtils {
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
}
}
-
- sufficientBalance (txParams, hexBalance) {
- const balance = hexToBn(hexBalance)
- const value = hexToBn(txParams.value)
- const gasLimit = hexToBn(txParams.gas)
- const gasPrice = hexToBn(txParams.gasPrice)
-
- const maxCost = value.add(gasLimit.mul(gasPrice))
- return balance.gte(maxCost)
- }
-
-}
-
-// util
-
-function bnToHex (inputBn) {
- return ethUtil.addHexPrefix(inputBn.toString(16))
-}
-
-function hexToBn (inputHex) {
- return new BN(ethUtil.stripHexPrefix(inputHex), 16)
-}
-
-function BnMultiplyByFraction (targetBN, numerator, denominator) {
- const numBN = new BN(numerator)
- const denomBN = new BN(denominator)
- return targetBN.mul(numBN).div(denomBN)
-}
+} \ No newline at end of file
diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js
index bddd60ee8..6dee9edf0 100644
--- a/app/scripts/lib/util.js
+++ b/app/scripts/lib/util.js
@@ -1,8 +1,44 @@
+const ethUtil = require('ethereumjs-util')
+const assert = require('assert')
+const BN = require('bn.js')
+
module.exports = {
getStack,
+ sufficientBalance,
+ hexToBn,
+ bnToHex,
+ BnMultiplyByFraction,
}
function getStack () {
const stack = new Error('Stack trace generator - not an error').stack
return stack
}
+
+function sufficientBalance (txParams, hexBalance) {
+ // validate hexBalance is a hex string
+ assert.equal(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string')
+ assert.equal(hexBalance.slice(0, 2), '0x', 'sufficientBalance - hexBalance is not a hex string')
+
+ const balance = hexToBn(hexBalance)
+ const value = hexToBn(txParams.value)
+ const gasLimit = hexToBn(txParams.gas)
+ const gasPrice = hexToBn(txParams.gasPrice)
+
+ const maxCost = value.add(gasLimit.mul(gasPrice))
+ return balance.gte(maxCost)
+}
+
+function bnToHex (inputBn) {
+ return ethUtil.addHexPrefix(inputBn.toString(16))
+}
+
+function hexToBn (inputHex) {
+ return new BN(ethUtil.stripHexPrefix(inputHex), 16)
+}
+
+function BnMultiplyByFraction (targetBN, numerator, denominator) {
+ const numBN = new BN(numerator)
+ const denomBN = new BN(denominator)
+ return targetBN.mul(numBN).div(denomBN)
+}
diff --git a/gulpfile.js b/gulpfile.js
index f351c2e2f..a8333e950 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -258,26 +258,27 @@ function zipTask(target) {
}
}
-function generateBundler(opts) {
- var browserifyOpts = assign({}, watchify.args, {
+function generateBundler(opts, performBundle) {
+ const browserifyOpts = assign({}, watchify.args, {
entries: ['./app/scripts/'+opts.filename],
plugin: 'browserify-derequire',
debug: debug,
fullPaths: debug,
})
- return browserify(browserifyOpts)
-}
-
-function discTask(opts) {
- let bundler = generateBundler(opts)
+ let bundler = browserify(browserifyOpts)
if (opts.watch) {
bundler = watchify(bundler)
- // on any dep update, runs the bundler
+ // on any file update, re-runs the bundler
bundler.on('update', performBundle)
}
+ return bundler
+}
+
+function discTask(opts) {
+ const bundler = generateBundler(opts, performBundle)
// output build logs to terminal
bundler.on('log', gutil.log)
@@ -299,14 +300,7 @@ function discTask(opts) {
function bundleTask(opts) {
- let bundler = generateBundler(opts)
-
- if (opts.watch) {
- bundler = watchify(bundler)
- // on any file update, re-runs the bundler
- bundler.on('update', performBundle)
- }
-
+ const bundler = generateBundler(opts, performBundle)
// output build logs to terminal
bundler.on('log', gutil.log)
@@ -316,6 +310,17 @@ function bundleTask(opts) {
return (
bundler.bundle()
+
+ // handle errors
+ .on('error', (err) => {
+ beep()
+ if (opts.watch) {
+ console.warn(err.stack)
+ } else {
+ throw err
+ }
+ })
+
// convert bundle stream to gulp vinyl stream
.pipe(source(opts.filename))
// inject variables into bundle
@@ -324,7 +329,7 @@ function bundleTask(opts) {
.pipe(buffer())
// sourcemaps
// loads map from browserify file
- .pipe(gulpif(debug, sourcemaps.init({loadMaps: true})))
+ .pipe(gulpif(debug, sourcemaps.init({ loadMaps: true })))
// writes .map file
.pipe(gulpif(debug, sourcemaps.write('./')))
// write completed bundles
@@ -338,3 +343,7 @@ function bundleTask(opts) {
)
}
}
+
+function beep () {
+ process.stdout.write('\x07')
+}
diff --git a/package.json b/package.json
index 7dc1222d7..25dbc1ee9 100644
--- a/package.json
+++ b/package.json
@@ -49,19 +49,20 @@
]
},
"dependencies": {
- "async": "^1.5.2",
+ "async": "^2.5.0",
"await-semaphore": "^0.1.1",
"babel-runtime": "^6.23.0",
"bip39": "^2.2.0",
"bluebird": "^3.5.0",
"boron": "^0.2.3",
+ "bn.js": "^4.11.7",
"browser-passworder": "^2.0.3",
"browserify-derequire": "^0.9.4",
"client-sw-ready-event": "^3.3.0",
- "clone": "^1.0.2",
- "copy-to-clipboard": "^2.0.0",
+ "clone": "^2.1.1",
+ "copy-to-clipboard": "^3.0.8",
"debounce": "^1.0.0",
- "deep-extend": "^0.4.1",
+ "deep-extend": "^0.5.0",
"detect-node": "^2.0.3",
"disc": "^1.3.2",
"dnode": "^1.2.2",
@@ -76,7 +77,7 @@
"eth-simple-keyring": "^1.1.1",
"eth-token-tracker": "^1.1.2",
"ethereumjs-tx": "^1.3.0",
- "ethereumjs-util": "ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
+ "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9",
"ethereumjs-wallet": "^0.6.0",
"ethjs-ens": "^2.0.0",
"ethjs-query": "^0.2.6",
@@ -85,13 +86,14 @@
"extensionizer": "^1.0.0",
"fast-levenshtein": "^2.0.6",
"gulp-autoprefixer": "^4.0.0",
- "gulp-eslint": "^2.0.0",
"gulp-sass": "^3.1.0",
+ "gulp": "github:gulpjs/gulp#4.0",
+ "gulp-eslint": "^4.0.0",
"hat": "0.0.3",
- "idb-global": "^1.0.0",
- "identicon.js": "^1.2.1",
+ "idb-global": "^2.1.0",
+ "identicon.js": "^2.3.1",
"iframe": "^1.0.0",
- "iframe-stream": "^1.0.2",
+ "iframe-stream": "^3.0.0",
"inject-css": "^0.1.1",
"jazzicon": "^1.2.0",
"loglevel": "^1.4.1",
@@ -105,7 +107,7 @@
"ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0",
"polyfill-crypto.getrandomvalues": "^1.0.0",
- "post-message-stream": "^1.0.0",
+ "post-message-stream": "^3.0.0",
"promise-filter": "^1.1.0",
"promise-to-callback": "^1.0.0",
"pump": "^1.0.2",
@@ -114,9 +116,9 @@
"react": "^15.0.2",
"react-addons-css-transition-group": "^15.6.0",
"react-dom": "^15.5.4",
- "react-hyperscript": "^2.2.2",
+ "react-hyperscript": "^3.0.0",
"react-markdown": "^2.3.0",
- "react-redux": "^4.4.5",
+ "react-redux": "^5.0.5",
"react-select": "^1.0.0-rc.2",
"react-simple-file-input": "^1.0.0",
"react-tooltip-component": "^0.3.0",
@@ -124,25 +126,24 @@
"reactify": "^1.1.1",
"readable-stream": "^2.1.2",
"redux": "^3.0.5",
- "redux-logger": "^2.10.2",
- "redux-thunk": "^1.0.2",
+ "redux-logger": "^3.0.6",
+ "redux-thunk": "^2.2.0",
"request-promise": "^4.1.1",
"sandwich-expando": "^1.0.5",
"semaphore": "^1.0.5",
"sw-stream": "^2.0.0",
"textarea-caret": "^3.0.1",
- "three.js": "^0.73.2",
"through2": "^2.0.3",
"valid-url": "^1.0.9",
"vreme": "^3.0.2",
- "web3": "0.19.1",
+ "web3": "^0.20.1",
"web3-provider-engine": "^13.2.9",
"web3-stream-provider": "^3.0.1",
"xtend": "^4.0.1"
},
"devDependencies": {
"babel-core": "^6.24.1",
- "babel-eslint": "^6.0.5",
+ "babel-eslint": "^7.2.3",
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.23.0",
@@ -151,50 +152,52 @@
"babelify": "^7.2.0",
"beefy": "^2.1.5",
"brfs": "^1.4.3",
- "browserify": "^13.0.0",
- "chai": "^3.5.0",
+ "browserify": "^14.4.0",
+ "chai": "^4.1.0",
"coveralls": "^2.13.1",
"deep-freeze-strict": "^1.1.1",
- "del": "^2.2.0",
+ "del": "^3.0.0",
"envify": "^4.0.0",
"enzyme": "^2.8.2",
"eslint-plugin-chai": "0.0.1",
"eslint-plugin-mocha": "^4.9.0",
- "fs-promise": "^1.0.0",
+ "eth-json-rpc-middleware": "^1.2.7",
+ "fs-promise": "^2.0.3",
"gulp": "github:gulpjs/gulp#4.0",
"gulp-if": "^2.0.1",
"gulp-json-editor": "^2.2.1",
"gulp-livereload": "^3.8.1",
- "gulp-replace": "^0.5.4",
- "gulp-sourcemaps": "^1.6.0",
+ "gulp-replace": "^0.6.1",
+ "gulp-sourcemaps": "^2.6.0",
"gulp-util": "^3.0.7",
"gulp-watch": "^4.3.5",
- "gulp-zip": "^3.2.0",
+ "gulp-zip": "^4.0.0",
"isomorphic-fetch": "^2.2.1",
- "jsdom": "^8.1.0",
- "jsdom-global": "^1.7.0",
- "jshint-stylish": "~0.1.5",
+ "jsdom": "^11.1.0",
+ "jsdom-global": "^3.0.2",
+ "jshint-stylish": "~2.2.1",
+ "json-rpc-engine": "^3.0.1",
"lodash.assign": "^4.0.6",
- "mocha": "^2.4.5",
- "mocha-eslint": "^2.1.1",
+ "mocha": "^3.4.2",
+ "mocha-eslint": "^4.0.0",
"mocha-jsdom": "^1.1.0",
- "mocha-sinon": "^1.1.5",
- "nock": "^8.0.0",
+ "mocha-sinon": "^2.0.0",
+ "nock": "^9.0.14",
"nyc": "^11.0.3",
"open": "0.0.5",
"prompt": "^1.0.0",
"qs": "^6.2.0",
- "qunit": "^0.9.1",
+ "qunit": "^1.0.0",
"react-addons-test-utils": "^15.5.1",
"react-test-renderer": "^15.5.4",
"react-testutils-additions": "^15.2.0",
"sinon": "^2.3.8",
"tape": "^4.5.1",
"testem": "^1.10.3",
- "uglifyify": "^3.0.1",
+ "uglifyify": "^4.0.2",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
- "watchify": "^3.7.0"
+ "watchify": "^3.9.0"
},
"engines": {
"node": ">=0.8.0"
diff --git a/test/stub/provider.js b/test/stub/provider.js
new file mode 100644
index 000000000..8a306f6d9
--- /dev/null
+++ b/test/stub/provider.js
@@ -0,0 +1,25 @@
+const JsonRpcEngine = require('json-rpc-engine')
+const scaffoldMiddleware = require('eth-json-rpc-middleware/scaffold')
+
+module.exports = {
+ createEngineForTestData,
+ providerFromEngine,
+ scaffoldMiddleware,
+ createStubedProvider
+}
+
+
+function createEngineForTestData () {
+ return new JsonRpcEngine()
+}
+
+function providerFromEngine (engine) {
+ const provider = { sendAsync: engine.handle.bind(engine) }
+ return provider
+}
+
+function createStubedProvider (resultStub) {
+ const engine = createEngineForTestData()
+ engine.push(scaffoldMiddleware(resultStub))
+ return providerFromEngine(engine)
+} \ No newline at end of file
diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js
index 30cb4f067..9b76241ed 100644
--- a/test/unit/message-manager-test.js
+++ b/test/unit/message-manager-test.js
@@ -1,7 +1,7 @@
const assert = require('assert')
const MessageManger = require('../../app/scripts/lib/message-manager')
-describe('Transaction Manager', function () {
+describe('Message Manager', function () {
let messageManager
beforeEach(function () {
diff --git a/test/unit/nonce-tracker-test.js b/test/unit/nonce-tracker-test.js
index b0283e159..36025a360 100644
--- a/test/unit/nonce-tracker-test.js
+++ b/test/unit/nonce-tracker-test.js
@@ -31,12 +31,11 @@ describe('Nonce Tracker', function () {
})
describe('#getNonceLock', function () {
- it('should work', async function (done) {
+ it('should work', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '1', 'nonce should be 1')
await nonceLock.releaseLock()
- done()
})
})
})
diff --git a/test/unit/pending-tx-test.js b/test/unit/pending-tx-test.js
new file mode 100644
index 000000000..8c6d287f8
--- /dev/null
+++ b/test/unit/pending-tx-test.js
@@ -0,0 +1,260 @@
+const assert = require('assert')
+const ethUtil = require('ethereumjs-util')
+const EthTx = require('ethereumjs-tx')
+const ObservableStore = require('obs-store')
+const clone = require('clone')
+const { createStubedProvider } = require('../stub/provider')
+const PendingTransactionTracker = require('../../app/scripts/lib/pending-tx-tracker')
+const noop = () => true
+const currentNetworkId = 42
+const otherNetworkId = 36
+const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
+
+describe('PendingTransactionTracker', function () {
+ let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub, provider
+ this.timeout(10000)
+ beforeEach(function () {
+ txMeta = {
+ id: 1,
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ status: 'signed',
+ txParams: {
+ from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
+ nonce: '0x1',
+ value: '0xfffff',
+ },
+ rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
+ }
+ txMetaNoHash = {
+ id: 2,
+ status: 'signed',
+ txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'},
+ }
+ txMetaNoRawTx = {
+ hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
+ status: 'signed',
+ txParams: { from: '0x1678a085c290ebd122dc42cba69373b5953b831d'},
+ }
+ providerResultStub = {}
+ provider = createStubedProvider(providerResultStub)
+
+ pendingTxTracker = new PendingTransactionTracker({
+ provider,
+ getBalance: () => {},
+ nonceTracker: {
+ getGlobalLock: async () => {
+ return { releaseLock: () => {} }
+ }
+ },
+ getPendingTransactions: () => {return []},
+ sufficientBalance: () => {},
+ publishTransaction: () => {},
+ })
+ })
+
+ describe('#checkForTxInBlock', function () {
+ it('should return if no pending transactions', function () {
+ // throw a type error if it trys to do anything on the block
+ // thus failing the test
+ const block = Proxy.revocable({}, {}).revoke()
+ pendingTxTracker.checkForTxInBlock(block)
+ })
+ it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) {
+ const block = Proxy.revocable({}, {}).revoke()
+ pendingTxTracker.getPendingTransactions = () => [txMetaNoHash]
+ pendingTxTracker.once('txFailed', (txId, err) => {
+ assert(txId, txMetaNoHash.id, 'should pass txId')
+ done()
+ })
+ pendingTxTracker.checkForTxInBlock(block)
+ })
+ it('should emit \'txConfirmed\' if the tx is in the block', function (done) {
+ const block = { transactions: [txMeta]}
+ pendingTxTracker.getPendingTransactions = () => [txMeta]
+ pendingTxTracker.once('txConfirmed', (txId) => {
+ assert(txId, txMeta.id, 'should pass txId')
+ done()
+ })
+ pendingTxTracker.once('txFailed', (_, err) => { done(err) })
+ pendingTxTracker.checkForTxInBlock(block)
+ })
+ })
+ describe('#queryPendingTxs', function () {
+ it('should call #_checkPendingTxs if their is no oldBlock', function (done) {
+ let newBlock, oldBlock
+ newBlock = { number: '0x01' }
+ pendingTxTracker._checkPendingTxs = done
+ pendingTxTracker.queryPendingTxs({oldBlock, newBlock})
+ })
+ it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) {
+ let newBlock, oldBlock
+ oldBlock = { number: '0x01' }
+ newBlock = { number: '0x03' }
+ pendingTxTracker._checkPendingTxs = done
+ pendingTxTracker.queryPendingTxs({oldBlock, newBlock})
+ })
+ it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) {
+ let newBlock, oldBlock
+ oldBlock = { number: '0x1' }
+ newBlock = { number: '0x2' }
+ pendingTxTracker._checkPendingTxs = () => {
+ const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less')
+ done(err)
+ }
+ pendingTxTracker.queryPendingTxs({oldBlock, newBlock})
+ done()
+ })
+ })
+
+ describe('#_checkPendingTx', function () {
+ it('should emit \'txFailed\' if the txMeta does not have a hash', function (done) {
+ pendingTxTracker.once('txFailed', (txId, err) => {
+ assert(txId, txMetaNoHash.id, 'should pass txId')
+ done()
+ })
+ pendingTxTracker._checkPendingTx(txMetaNoHash)
+ })
+
+ it('should should return if query does not return txParams', function () {
+ providerResultStub.eth_getTransactionByHash = null
+ pendingTxTracker._checkPendingTx(txMeta)
+ })
+
+ it('should emit \'txConfirmed\'', function (done) {
+ providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'}
+ pendingTxTracker.once('txConfirmed', (txId) => {
+ assert(txId, txMeta.id, 'should pass txId')
+ done()
+ })
+ pendingTxTracker.once('txFailed', (_, err) => { done(err) })
+ pendingTxTracker._checkPendingTx(txMeta)
+ })
+ })
+
+ describe('#_checkPendingTxs', function () {
+ beforeEach(function () {
+ const txMeta2 = txMeta3 = txMeta
+ txMeta2.id = 2
+ txMeta3.id = 3
+ txList = [txMeta, txMeta2, txMeta3].map((tx) => {
+ tx.processed = new Promise ((resolve) => { tx.resolve = resolve })
+ return tx
+ })
+ })
+
+ it('should warp all txMeta\'s in #_checkPendingTx', function (done) {
+ pendingTxTracker.getPendingTransactions = () => txList
+ pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) }
+ const list = txList.map
+ Promise.all(txList.map((tx) => tx.processed))
+ .then((txCompletedList) => done())
+ .catch(done)
+
+ pendingTxTracker._checkPendingTxs()
+ })
+ })
+
+ describe('#resubmitPendingTxs', function () {
+ beforeEach(function () {
+ const txMeta2 = txMeta3 = txMeta
+ txList = [txMeta, txMeta2, txMeta3].map((tx) => {
+ tx.processed = new Promise ((resolve) => { tx.resolve = resolve })
+ return tx
+ })
+ })
+
+ it('should return if no pending transactions', function () {
+ pendingTxTracker.resubmitPendingTxs()
+ })
+ it('should call #_resubmitTx for all pending tx\'s', function (done) {
+ pendingTxTracker.getPendingTransactions = () => txList
+ pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) }
+ Promise.all(txList.map((tx) => tx.processed))
+ .then((txCompletedList) => done())
+ .catch(done)
+ pendingTxTracker.resubmitPendingTxs()
+ })
+ it('should not emit \'txFailed\' if the txMeta throws a known txError', function (done) {
+ knownErrors =[
+ // geth
+ ' Replacement transaction Underpriced ',
+ ' known transaction',
+ // parity
+ 'Gas price too low to replace ',
+ ' transaction with the sAme hash was already imported',
+ // other
+ ' gateway timeout',
+ ' noncE too low ',
+ ]
+ const enoughForAllErrors = txList.concat(txList)
+
+ pendingTxTracker.on('txFailed', (_, err) => done(err))
+
+ pendingTxTracker.getPendingTransactions = () => enoughForAllErrors
+ pendingTxTracker._resubmitTx = async (tx) => {
+ tx.resolve()
+ throw new Error(knownErrors.pop())
+ }
+ Promise.all(txList.map((tx) => tx.processed))
+ .then((txCompletedList) => done())
+ .catch(done)
+
+ pendingTxTracker.resubmitPendingTxs()
+ })
+ it('should emit \'txFailed\' if it encountered a real error', function (done) {
+ pendingTxTracker.once('txFailed', (id, err) => err.message === 'im some real error' ? txList[id - 1].resolve() : done(err))
+
+ pendingTxTracker.getPendingTransactions = () => txList
+ pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') }
+ Promise.all(txList.map((tx) => tx.processed))
+ .then((txCompletedList) => done())
+ .catch(done)
+
+ pendingTxTracker.resubmitPendingTxs()
+ })
+ })
+ describe('#_resubmitTx with a too-low balance', function () {
+ it('should return before publishing the transaction because to low of balance', function (done) {
+ const lowBalance = '0x0'
+ pendingTxTracker.getBalance = (address) => {
+ assert.equal(address, txMeta.txParams.from, 'Should pass the address')
+ return lowBalance
+ }
+ pendingTxTracker.publishTransaction = async (rawTx) => {
+ done(new Error('tried to publish transaction'))
+ }
+
+ // Stubbing out current account state:
+ // Adding the fake tx:
+ pendingTxTracker.once('txFailed', (txId, err) => {
+ assert(err, 'Should have a error')
+ done()
+ })
+ pendingTxTracker._resubmitTx(txMeta)
+ .catch((err) => {
+ assert.ifError(err, 'should not throw an error')
+ done(err)
+ })
+ })
+
+ it('should publishing the transaction', function (done) {
+ const enoughBalance = '0x100000'
+ pendingTxTracker.getBalance = (address) => {
+ assert.equal(address, txMeta.txParams.from, 'Should pass the address')
+ return enoughBalance
+ }
+ pendingTxTracker.publishTransaction = async (rawTx) => {
+ assert.equal(rawTx, txMeta.rawTx, 'Should pass the rawTx')
+ }
+
+ // Stubbing out current account state:
+ // Adding the fake tx:
+ pendingTxTracker._resubmitTx(txMeta)
+ .then(() => done())
+ .catch((err) => {
+ assert.ifError(err, 'should not throw an error')
+ done(err)
+ })
+ })
+ })
+}) \ No newline at end of file
diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js
index f290088a1..57d7deccd 100644
--- a/test/unit/tx-controller-test.js
+++ b/test/unit/tx-controller-test.js
@@ -10,16 +10,20 @@ const noop = () => true
const currentNetworkId = 42
const otherNetworkId = 36
const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex')
+const { createStubedProvider } = require('../stub/provider')
describe('Transaction Controller', function () {
- let txController
+ let txController, engine, provider, providerResultStub
beforeEach(function () {
+ providerResultStub = {}
+ provider = createStubedProvider(providerResultStub)
+
txController = new TransactionController({
+ provider,
networkStore: new ObservableStore(currentNetworkId),
txHistoryLimit: 10,
blockTracker: { getCurrentBlock: noop, on: noop, once: noop },
- provider: { sendAsync: noop },
ethStore: { getState: noop },
signTransaction: (ethTx) => new Promise((resolve) => {
ethTx.sign(privKey)
@@ -27,19 +31,7 @@ describe('Transaction Controller', function () {
}),
})
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
- txController.query = new Proxy({}, {
- get: (queryStubResult, key) => {
- if (key === 'stubResult') {
- return function (method, ...args) {
- queryStubResult[method] = args
- }
- } else {
- const returnValues = queryStubResult[key]
- return () => Promise.resolve(...returnValues)
- }
- },
- })
- txController.txProviderUtils = new TxProvideUtils(txController.query)
+ txController.txProviderUtils = new TxProvideUtils(txController.provider)
})
describe('#newUnapprovedTransaction', function () {
@@ -76,7 +68,6 @@ describe('Transaction Controller', function () {
it('should resolve when finished and status is submitted and resolve with the hash', function (done) {
txController.once('newUnaprovedTx', (txMetaFromEmit) => {
setTimeout(() => {
- console.log('HELLLO')
txController.setTxHash(txMetaFromEmit.id, '0x0')
txController.setTxStatusSubmitted(txMetaFromEmit.id)
}, 10)
@@ -93,7 +84,6 @@ describe('Transaction Controller', function () {
it('should reject when finished and status is rejected', function (done) {
txController.once('newUnaprovedTx', (txMetaFromEmit) => {
setTimeout(() => {
- console.log('HELLLO')
txController.setTxStatusRejected(txMetaFromEmit.id)
}, 10)
})
@@ -133,11 +123,9 @@ describe('Transaction Controller', function () {
'to':'0xc684832530fcbddae4b4230a47e991ddcec2831d',
},
}
-
- txController.query.stubResult('gasPrice', '0x4a817c800')
- txController.query.stubResult('getBlockByNumber', { gasLimit: '0x47b784' })
- txController.query.stubResult('estimateGas', '0x5209')
-
+ providerResultStub.eth_gasPrice = '4a817c800'
+ providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' }
+ providerResultStub.eth_estimateGas = '5209'
txController.addTxDefaults(txMeta)
.then((txMetaWithDefaults) => {
assert(txMetaWithDefaults.txParams.value, '0x0','should have added 0x0 as the value')
@@ -394,9 +382,8 @@ describe('Transaction Controller', function () {
const wrongValue = '0x05'
txController.addTx(txMeta)
-
- txController.query.stubResult('estimateGas', wrongValue)
- txController.query.stubResult('gasPrice', wrongValue)
+ providerResultStub.eth_gasPrice = wrongValue
+ providerResultStub.eth_estimateGas = '0x5209'
const signStub = sinon.stub(txController, 'signTransaction').callsFake(() => Promise.resolve())
@@ -429,46 +416,4 @@ describe('Transaction Controller', function () {
}).catch(done)
})
})
-
- describe('#_resubmitTx with a too-low balance', function () {
- it('should fail the transaction', function (done) {
- const from = '0xda0da0'
- const txMeta = {
- id: 1,
- status: 'submitted',
- metamaskNetworkId: currentNetworkId,
- txParams: {
- from,
- nonce: '0x1',
- value: '0xfffff',
- },
- }
-
- const lowBalance = '0x0'
- const fakeStoreState = { accounts: {} }
- fakeStoreState.accounts[from] = {
- balance: lowBalance,
- nonce: '0x0',
- }
-
- // Stubbing out current account state:
- const getStateStub = sinon.stub(txController.ethStore, 'getState')
- .returns(fakeStoreState)
-
- // Adding the fake tx:
- txController.addTx(clone(txMeta))
-
- txController._resubmitTx(txMeta)
- .then(() => {
- const updatedMeta = txController.getTx(txMeta.id)
- assert.notEqual(updatedMeta.status, txMeta.status, 'status changed.')
- assert.equal(updatedMeta.status, 'failed', 'tx set to failed.')
- done()
- })
- .catch((err) => {
- assert.ifError(err, 'should not throw an error')
- done(err)
- })
- })
- })
}) \ No newline at end of file
diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js
index a43bcfb35..43128b977 100644
--- a/test/unit/tx-utils-test.js
+++ b/test/unit/tx-utils-test.js
@@ -1,7 +1,7 @@
const assert = require('assert')
-const ethUtil = require('ethereumjs-util')
-const BN = ethUtil.BN
+const BN = require('bn.js')
+const { hexToBn, bnToHex } = require('../../app/scripts/lib/util')
const TxUtils = require('../../app/scripts/lib/tx-utils')
@@ -16,44 +16,6 @@ describe('txUtils', function () {
}))
})
- describe('#sufficientBalance', function () {
- it('returns true if max tx cost is equal to balance.', function () {
- const tx = {
- 'value': '0x1',
- 'gas': '0x2',
- 'gasPrice': '0x3',
- }
- const balance = '0x8'
-
- const result = txUtils.sufficientBalance(tx, balance)
- assert.ok(result, 'sufficient balance found.')
- })
-
- it('returns true if max tx cost is less than balance.', function () {
- const tx = {
- 'value': '0x1',
- 'gas': '0x2',
- 'gasPrice': '0x3',
- }
- const balance = '0x9'
-
- const result = txUtils.sufficientBalance(tx, balance)
- assert.ok(result, 'sufficient balance found.')
- })
-
- it('returns false if max tx cost is more than balance.', function () {
- const tx = {
- 'value': '0x1',
- 'gas': '0x2',
- 'gasPrice': '0x3',
- }
- const balance = '0x6'
-
- const result = txUtils.sufficientBalance(tx, balance)
- assert.ok(!result, 'insufficient balance found.')
- })
- })
-
describe('chain Id', function () {
it('prepares a transaction with the provided chainId', function () {
const txParams = {
@@ -96,7 +58,7 @@ describe('txUtils', function () {
assert(outputBn.eq(expectedBn), 'returns the original estimatedGas value')
})
- it('buffers up to reccomend gas limit reccomended ceiling', function () {
+ it('buffers up to recommend gas limit recommended ceiling', function () {
// naive estimatedGas: 0x16e360 (1.5 mil)
const inputHex = '0x16e360'
// dummy gas limit: 0x1e8480 (2 mil)
@@ -107,17 +69,7 @@ describe('txUtils', function () {
// const inputBn = hexToBn(inputHex)
// const outputBn = hexToBn(output)
const expectedHex = bnToHex(ceilGasLimitBn)
- assert.equal(output, expectedHex, 'returns the gas limit reccomended ceiling value')
+ assert.equal(output, expectedHex, 'returns the gas limit recommended ceiling value')
})
})
-})
-
-// util
-
-function hexToBn (inputHex) {
- return new BN(ethUtil.stripHexPrefix(inputHex), 16)
-}
-
-function bnToHex (inputBn) {
- return ethUtil.addHexPrefix(inputBn.toString(16))
-}
+}) \ No newline at end of file
diff --git a/test/unit/util-test.js b/test/unit/util-test.js
new file mode 100644
index 000000000..6da185b2c
--- /dev/null
+++ b/test/unit/util-test.js
@@ -0,0 +1,41 @@
+const assert = require('assert')
+const { sufficientBalance } = require('../../app/scripts/lib/util')
+
+
+describe('SufficientBalance', function () {
+ it('returns true if max tx cost is equal to balance.', function () {
+ const tx = {
+ 'value': '0x1',
+ 'gas': '0x2',
+ 'gasPrice': '0x3',
+ }
+ const balance = '0x8'
+
+ const result = sufficientBalance(tx, balance)
+ assert.ok(result, 'sufficient balance found.')
+ })
+
+ it('returns true if max tx cost is less than balance.', function () {
+ const tx = {
+ 'value': '0x1',
+ 'gas': '0x2',
+ 'gasPrice': '0x3',
+ }
+ const balance = '0x9'
+
+ const result = sufficientBalance(tx, balance)
+ assert.ok(result, 'sufficient balance found.')
+ })
+
+ it('returns false if max tx cost is more than balance.', function () {
+ const tx = {
+ 'value': '0x1',
+ 'gas': '0x2',
+ 'gasPrice': '0x3',
+ }
+ const balance = '0x6'
+
+ const result = sufficientBalance(tx, balance)
+ assert.ok(!result, 'insufficient balance found.')
+ })
+}) \ No newline at end of file
diff --git a/ui/app/components/buy-button-subview.js b/ui/app/components/buy-button-subview.js
index 87084f92d..759eb15bd 100644
--- a/ui/app/components/buy-button-subview.js
+++ b/ui/app/components/buy-button-subview.js
@@ -143,7 +143,7 @@ BuyButtonSubview.prototype.formVersionSubview = function () {
return h('div.flex-column', {
style: {
alignItems: 'center',
- margin: '50px',
+ margin: '20px 50px',
},
}, [
h('h3.text-transform-uppercase', {
diff --git a/ui/app/components/shapeshift-form.js b/ui/app/components/shapeshift-form.js
index 76a265d63..901a4a956 100644
--- a/ui/app/components/shapeshift-form.js
+++ b/ui/app/components/shapeshift-form.js
@@ -33,10 +33,10 @@ ShapeshiftForm.prototype.renderMain = function () {
return h('.flex-column', {
style: {
- // marginTop: '10px',
+ position: 'relative',
padding: '25px',
paddingTop: '5px',
- width: '100%',
+ width: '90%',
minHeight: '215px',
alignItems: 'center',
overflowY: 'auto',
@@ -58,7 +58,9 @@ ShapeshiftForm.prototype.renderMain = function () {
},
}),
- h('.input-container', [
+ h('.input-container', {
+ position: 'relative',
+ }, [
h('input#fromCoin.buy-inputs.ex-coins', {
type: 'text',
list: 'coinList',
@@ -79,27 +81,31 @@ ShapeshiftForm.prototype.renderMain = function () {
style: {
fontSize: '12px',
color: '#F7861C',
- position: 'relative',
- bottom: '48px',
- left: '106px',
+ position: 'absolute',
},
}),
]),
- h('.icon-control', [
- h('i.fa.fa-refresh.fa-4.orange', {
- style: {
- bottom: '5px',
- left: '5px',
- color: '#F7861C',
- },
- onClick: this.updateCoin.bind(this),
- }),
+ h('.icon-control', {
+ style: {
+ position: 'relative',
+ },
+ }, [
+ // Not visible on the screen, can't see it on master.
+
+ // h('i.fa.fa-refresh.fa-4.orange', {
+ // style: {
+ // bottom: '5px',
+ // left: '5px',
+ // color: '#F7861C',
+ // },
+ // onClick: this.updateCoin.bind(this),
+ // }),
h('i.fa.fa-chevron-right.fa-4.orange', {
style: {
- position: 'relative',
- bottom: '26px',
- left: '10px',
+ position: 'absolute',
+ bottom: '35%',
+ left: '0%',
color: '#F7861C',
},
onClick: this.updateCoin.bind(this),
@@ -117,69 +123,73 @@ ShapeshiftForm.prototype.renderMain = function () {
},
}),
]),
+
h('.flex-column', {
style: {
+ marginTop: '1%',
alignItems: 'flex-start',
},
}, [
- this.props.warning ? this.props.warning && h('span.error.flex-center', {
- style: {
- textAlign: 'center',
- width: '229px',
- height: '82px',
- },
- },
- this.props.warning) : this.renderInfo(),
+ this.props.warning
+ ? this.props.warning
+ && h('span.error.flex-center', {
+ style: {
+ textAlign: 'center',
+ width: '229px',
+ height: '82px',
+ },
+ }, this.props.warning)
+ : this.renderInfo(),
+
+ this.renderRefundAddressForCoin(coin),
]),
- h(this.activeToggle('.input-container'), {
- style: {
- padding: '10px',
- paddingTop: '0px',
- width: '100%',
- },
- }, [
+ ])
+}
+
+ShapeshiftForm.prototype.renderRefundAddressForCoin = function (coin) {
+ return h(this.activeToggle('.input-container'), {
+ style: {
+ marginTop: '1%',
+ },
+ }, [
- h('div', `${coin} Address:`),
+ h('div', `${coin} Address:`),
- h('input#fromCoinAddress.buy-inputs', {
- type: 'text',
- placeholder: `Your ${coin} Refund Address`,
- dataset: {
- persistentFormId: 'refund-address',
- },
- style: {
- boxSizing: 'border-box',
- width: '227px',
- height: '30px',
- padding: ' 5px ',
- },
- }),
+ h('input#fromCoinAddress.buy-inputs', {
+ type: 'text',
+ placeholder: `Your ${coin} Refund Address`,
+ dataset: {
+ persistentFormId: 'refund-address',
- h('i.fa.fa-pencil-square-o.edit-text', {
- style: {
- fontSize: '12px',
- color: '#F7861C',
- position: 'relative',
- bottom: '10px',
- right: '11px',
- },
- }),
- h('.flex-row', {
+ },
+ style: {
+ boxSizing: 'border-box',
+ width: '227px',
+ height: '30px',
+ padding: ' 5px ',
+ },
+ }),
+
+ h('i.fa.fa-pencil-square-o.edit-text', {
+ style: {
+ fontSize: '12px',
+ color: '#F7861C',
+ position: 'absolute',
+ },
+ }),
+ h('div.flex-row', {
+ style: {
+ justifyContent: 'flex-start',
+ },
+ }, [
+ h('button', {
+ onClick: this.shift.bind(this),
style: {
- justifyContent: 'flex-end',
+ marginTop: '1%',
},
- }, [
- h('button', {
- onClick: this.shift.bind(this),
- style: {
- marginTop: '10px',
- position: 'relative',
- bottom: '40px',
- },
- },
- 'Submit'),
- ]),
+ },
+ 'Submit'),
]),
])
}
diff --git a/ui/app/components/shift-list-item.js b/ui/app/components/shift-list-item.js
index 32bfbeda4..079f05e31 100644
--- a/ui/app/components/shift-list-item.js
+++ b/ui/app/components/shift-list-item.js
@@ -2,7 +2,7 @@ const inherits = require('util').inherits
const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
-const vreme = new (require('vreme'))
+const vreme = new (require('vreme'))()
const explorerLink = require('../../lib/explorer-link')
const actions = require('../actions')
const addressSummary = require('../util').addressSummary
diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js
index dbda66a31..9018bab06 100644
--- a/ui/app/components/transaction-list-item.js
+++ b/ui/app/components/transaction-list-item.js
@@ -6,7 +6,7 @@ const EthBalance = require('./eth-balance')
const addressSummary = require('../util').addressSummary
const explorerLink = require('../../lib/explorer-link')
const CopyButton = require('./copyButton')
-const vreme = new (require('vreme'))
+const vreme = new (require('vreme'))()
const Tooltip = require('./tooltip')
const numberToBN = require('number-to-bn')
@@ -154,12 +154,21 @@ function failIfFailed (transaction) {
if (transaction.status === 'rejected') {
return h('span.error', ' (Rejected)')
}
- if (transaction.err) {
+ if (transaction.err || transaction.warning) {
+ const { err, warning = {} } = transaction
+ const errFirst = !!(( err && warning ) || err)
+ const message = errFirst ? err.message : warning.message
+
+ errFirst ? err.message : warning.message
+
+
return h(Tooltip, {
- title: transaction.err.message,
+ title: message,
position: 'bottom',
}, [
- h('span.error', ' (Failed)'),
+ h(`span.${errFirst ? 'error' : 'warning'}`,
+ ` (${errFirst ? 'Failed' : 'Warning'})`
+ ),
])
}
}
diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js
index ac8cbf158..878852bf6 100644
--- a/ui/app/reducers/app.js
+++ b/ui/app/reducers/app.js
@@ -42,9 +42,12 @@ function reduceApp (state, action) {
accountDetail: {
subview: 'transactions',
},
- transForward: true, // Used to render transition direction
- isLoading: false, // Used to display loading indicator
- warning: null, // Used to display error text
+ // Used to render transition direction
+ transForward: true,
+ // Used to display loading indicator
+ isLoading: false,
+ // Used to display error text
+ warning: null,
}, state.appState)
switch (action.type) {
diff --git a/ui/app/store.js b/ui/app/store.js
index ba9e58b49..3bafdee11 100644
--- a/ui/app/store.js
+++ b/ui/app/store.js
@@ -1,8 +1,8 @@
const createStore = require('redux').createStore
const applyMiddleware = require('redux').applyMiddleware
-const thunkMiddleware = require('redux-thunk')
+const thunkMiddleware = require('redux-thunk').default
const rootReducer = require('./reducers')
-const createLogger = require('redux-logger')
+const createLogger = require('redux-logger').createLogger
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'